# Plan: Phase N — Workflow-Pipeline, 3D-Viewer Production-Modus, Worker-Management, QC-Tests ## Kontext Vier offene Bereiche aus dem PLAN.md müssen abgeschlossen werden: 1. **Workflow-Pipeline verdrahten**: `workflow_builder.py` enthält nur defekte Stubs. `_build_still` übergibt `order_line_id` als `step_path` an `render_still_task` → würde crashen. Der neue `still_with_exports`-Workflow (still + gltf_export + blend_export) ist nicht implementiert. Die Celery-Tasks für export_gltf/export_blend fehlen in `domains/rendering/tasks.py`. 2. **K6: 3D-Viewer Production-Modus**: `ThreeDViewer.tsx` hat keinen Mode-Toggle, Wireframe, Env-Preset oder Download-Buttons. Für Testdaten wird `POST /api/cad/{id}/generate-gltf-geometry` benötigt (trimesh STL→GLB, kein Blender nötig). 3. **L3: Worker-Management UI**: `WorkerManagement.tsx` fehlt. Backend braucht `/celery-workers` (Celery inspect) und `/scale` (docker compose subprocess). Backend-Container bekommt Docker-Socket-Mount. 4. **M: QC-Tests**: `pytest` ist im Backend-Container nicht installiert. Dockerfile: `pip install -e ".[dev]"`. Neue Service-Tests für rendering und orders domains. 2 neue Vitest-Dateien. --- ## Betroffene Dateien | Datei | Änderung | |-------|----------| | `backend/app/domains/rendering/tasks.py` | 3 neue Tasks: `render_order_line_still_task`, `export_gltf_for_order_line_task`, `export_blend_for_order_line_task` | | `backend/app/domains/rendering/workflow_builder.py` | Stubs ersetzen durch order-line-aware Tasks, `still_with_exports` hinzufügen | | `backend/app/api/routers/cad.py` | `POST /{id}/generate-gltf-geometry` Endpoint | | `backend/app/api/routers/worker.py` | `GET /celery-workers`, `POST /scale` Endpoints | | `backend/Dockerfile` | `pip install -e ".[dev]"` | | `docker-compose.yml` | Backend + Worker: Docker-Socket + Compose-File-Mount | | `frontend/src/components/cad/ThreeDViewer.tsx` | Mode-Toggle, Wireframe, Env-Preset, Download-Buttons | | `frontend/src/pages/WorkerManagement.tsx` | NEU: Worker-Liste, Queue-Stats, Scale-Button | | `frontend/src/api/worker.ts` | Neue Interfaces + API-Funktionen | | `frontend/src/App.tsx` | Route für /workers | | `frontend/src/components/layout/Layout.tsx` | Sidebar-Link Workers | | `backend/tests/domains/test_rendering_service.py` | NEU: ≥5 Tests für Rendering-Tasks und Workflow-Builder | | `backend/tests/domains/test_orders_service.py` | NEU: ≥5 Tests für Orders-Endpoints | | `frontend/src/__tests__/pages/WorkerActivity.test.tsx` | NEU: Vitest-Tests | | `frontend/src/__tests__/pages/WorkerManagement.test.tsx` | NEU: Vitest-Tests | --- ## Tasks (in Reihenfolge) ### Task 1: Backend — Neue order-line-aware Rendering-Tasks - **Datei**: `backend/app/domains/rendering/tasks.py` - **Was**: Drei neue Celery-Tasks hinzufügen (UNTER den bestehenden Tasks): **`render_order_line_still_task(order_line_id, **params)`** — Queue `thumbnail_rendering`: - Lädt OrderLine + CadFile via sync SQLAlchemy (wie `publish_asset`) - Setzt `render_status = 'processing'` - Ruft `render_still()` aus `app.services.render_blender` auf - Setzt `render_status = 'completed'`, speichert `render_log` - Bei Fehler: `render_status = 'failed'` - Returns dict mit `output_path` **`export_gltf_for_order_line_task(order_line_id)`** — Queue `thumbnail_rendering`: - Lädt OrderLine + CadFile sync - Sucht STL-Cache (`{step_stem}_low.stl`) - Ruft Blender subprocess mit `export_gltf.py` auf: `blender --background --python export_gltf.py -- --stl_path X --output_path Y` - Lädt GLB nach MinIO `production-exports/{cad_file_id}/{order_line_id}.glb` - Erstellt `MediaAsset(asset_type=gltf_production, storage_key=...)` - Returns `storage_key` **`export_blend_for_order_line_task(order_line_id)`** — Queue `thumbnail_rendering`: - Analog zu export_gltf, aber mit `export_blend.py` - MediaAsset type: `blend_production` - **Akzeptanzkriterium**: Tasks in `domains/rendering/tasks.py` vorhanden, keine Import-Fehler - **Abhängigkeiten**: keine ### Task 2: Backend — workflow_builder.py reparieren + still_with_exports - **Datei**: `backend/app/domains/rendering/workflow_builder.py` - **Was**: - `_build_still`: Nutzt `render_order_line_still_task` statt `render_still_task` - `_build_turntable`: Bleibt vorerst mit `render_turntable_task` (file-path-basiert, funktioniert via legacy path) - `_build_multi_angle`: Nutzt `render_order_line_still_task` mit `camera_angle` param - **NEU** `_build_still_with_exports(order_line_id, params)`: ```python from celery import chain, group return chain( render_order_line_still_task.si(order_line_id, **params), group( export_gltf_for_order_line_task.si(order_line_id), export_blend_for_order_line_task.si(order_line_id), ) ) ``` - `dispatch_workflow()`: `"still_with_exports"` zu `builders` hinzufügen - **Akzeptanzkriterium**: `dispatch_workflow("still_with_exports", order_line_id)` löst keine Exception aus - **Abhängigkeiten**: Task 1 ### Task 3: Backend — generate-gltf-geometry Endpoint (Testdaten für K6) - **Datei**: `backend/app/api/routers/cad.py` - **Was**: Neuer Endpoint `POST /api/cad/{id}/generate-gltf-geometry` (require_admin_or_pm): - Prüft ob CadFile existiert + STL-Cache vorhanden (`{step_dir}/{stem}_low.stl`) - Queut neuen Celery-Task `generate_gltf_geometry_task.delay(str(cad_file.id))` - Returns `{"task_id": ..., "message": "GLB generation queued"}` Neuer Task `generate_gltf_geometry_task` in `domains/rendering/tasks.py` (Queue `thumbnail_rendering`): - Lädt CadFile sync, findet STL-Cache - **Nutzt trimesh** (kein Blender): `import trimesh; mesh = trimesh.load(stl_path); mesh.export(glb_path)` → Warum trimesh: Schnell, kein Blender nötig, läuft auf worker-Container (trimesh in pyproject.toml cad-extras) - Lädt GLB nach MinIO `uploads/{cad_file_id}/geometry.glb` - Erstellt/aktualisiert `MediaAsset(asset_type=gltf_geometry, storage_key=..., cad_file_id=...)` → `MediaAsset` braucht `cad_file_id` FK — prüfen ob vorhanden **Wichtig**: Prüfen ob `media_assets.cad_file_id` existiert. Falls nicht: Migration 047 notwendig. - **Akzeptanzkriterium**: `POST /api/cad/{id}/generate-gltf-geometry` gibt 202 zurück, nach Task-Ausführung existiert MediaAsset mit type=gltf_geometry - **Abhängigkeiten**: Task 1 ### Task 4: Migration 047 — media_assets.cad_file_id (wenn nötig) - **Datei**: `backend/alembic/versions/047_media_assets_cad_file_id.py` - **Was**: Nullable FK `cad_file_id UUID REFERENCES cad_files(id) ON DELETE SET NULL` auf `media_assets` - **Prüfen**: `grep -n "cad_file_id" backend/app/domains/media/models.py` — falls schon vorhanden: Task überspringen - **Akzeptanzkriterium**: `alembic upgrade head` erfolgreich - **Abhängigkeiten**: keine ### Task 5: ThreeDViewer.tsx — Production-Modus, Wireframe, Env-Preset, Downloads - **Datei**: `frontend/src/components/cad/ThreeDViewer.tsx` - **Was**: Props erweitern + Toolbar-Erweiterung: ```typescript interface ThreeDViewerProps { cadFileId: string onClose: () => void productionGltfUrl?: string // wenn vorhanden: Mode-Toggle anzeigen downloadUrls?: { glb?: string; blend?: string } } ``` **Neuer State:** - `mode: 'geometry' | 'production'` (default: 'geometry') - `wireframe: boolean` (default: false) - `envPreset: 'city' | 'studio' | 'sunset'` (default: 'city') **Toolbar** (neu, rechts vom "Capture Angle"-Button): - Mode-Toggle (nur wenn `productionGltfUrl` gesetzt): Button-Gruppe "Geometry | Production" - Wireframe-Toggle: Button - Env-Preset-Dropdown: `