From eb8b6c49d2758761a48722f906ec47d828be4bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Wed, 11 Mar 2026 18:59:47 +0100 Subject: [PATCH] chore(agents): rewrite all agent definitions for current architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major updates across all 8 agents: - Architecture: no more blender-renderer HTTP (port 8100), all via render-worker Celery - Task location: backend/app/domains/pipeline/tasks/ (not backend/app/tasks/) - Roles: global_admin/tenant_admin hierarchy (not just admin) - Queues: thumbnail_rendering on render-worker (not worker-thumbnail) - USD pipeline awareness: pxr/usd-core, partKey, primvars, FlattenLayerStack New: Planner <-> Implementer failure loop: - implement.md: Failure Protocol — [BLOCKED] tag + report to planner, stop - plan.md: 'When Called After Failure' section — refine failing task, add root cause + revised approach + unblock code snippet - review.md: on blocking issues, also update plan.md with [BLOCKED] tag Agent-specific updates: - plan.md: ROADMAP.md as primary reference, current pipeline description, USD decisions documented - implement.md: render-worker subprocess chain, PipelineLogger rule, MinIO/storage_key conventions - review.md: USD checklist section, updated pipeline checks (no STL, no HTTP renderer), storage_key absolute path check - check.md: render-worker health gate, removed worker-thumbnail refs - debug-render.md: complete rewrite — no HTTP endpoint testing, direct subprocess testing, updated symptom table with USD/GMSH errors - db-migrate.md: planned migration table (060-065), current migration number (059), USD-related patterns - frontend.md: role hierarchy, sceneManifest.ts reference, X-Tenant-ID interceptor note - excel-import.md: minor cleanup, consistent format Co-Authored-By: Claude Sonnet 4.6 --- .claude/commands/check.md | 158 +++++++++++-------- .claude/commands/db-migrate.md | 133 +++++++++------- .claude/commands/debug-render.md | 257 +++++++++++++++++-------------- .claude/commands/excel-import.md | 110 +++++++------ .claude/commands/frontend.md | 248 ++++++++++++++++------------- .claude/commands/implement.md | 139 ++++++++++++----- .claude/commands/plan.md | 133 ++++++++++++---- .claude/commands/review.md | 129 +++++++++------- 8 files changed, 783 insertions(+), 524 deletions(-) diff --git a/.claude/commands/check.md b/.claude/commands/check.md index e52ffb7..fd702e5 100644 --- a/.claude/commands/check.md +++ b/.claude/commands/check.md @@ -1,81 +1,109 @@ -Führe alle Quality Gates aus und berichte das Ergebnis: +# Check Agent — Quality Gates -## Frontend Quality Gates +Run all quality gates and report the result. Fix any red gate before committing. -1. **TypeScript-Check** (wichtigster Gate!): - ```bash - docker compose exec frontend npx tsc --noEmit 2>&1 - ``` - Prüft auf fehlende Imports, Typfehler, undefinierte Variablen. - → Fehler hier = Blank Page im Browser. Immer als erstes prüfen. +## Gate 1 — TypeScript Check (most important) -2. **Vite Build** (optional, langsamer): - ```bash - docker compose exec frontend npm run build 2>&1 | tail -20 - ``` +```bash +docker compose exec frontend npx tsc --noEmit 2>&1 +``` -3. **Tests**: - ```bash - docker compose exec frontend npm test 2>&1 | tail -20 - ``` - Hinweis: `npm run lint` existiert nicht — TypeScript-Check ersetzt es. +Catches missing imports, type errors, undefined variables. A failure here causes a blank page in the browser. **Always run this first.** -## Backend Quality Gates +Expected: zero errors. -4. **Python Import-Check**: - ```bash - docker compose exec backend python -c "from app.main import app; print('OK')" 2>&1 - ``` - Prüft ob alle Python-Imports auflösbar sind. +## Gate 2 — Backend Import Check -5. **Backend Startup-Logs**: - ```bash - docker compose logs backend 2>&1 | tail -20 - ``` - Auf `Application startup complete` prüfen, keine Exceptions. +```bash +docker compose exec backend python -c "from app.main import app; print('OK')" 2>&1 +``` -## Daten-Integrität Gates +Verifies all Python imports resolve. Catches `ImportError` before they reach production. -7. **Keine absoluten storage_key-Pfade in media_assets**: - ```bash - docker compose exec backend python -c " - import asyncio - from sqlalchemy import text - from app.database import AsyncSessionLocal - async def main(): - async with AsyncSessionLocal() as db: - r = await db.execute(text(\"SELECT COUNT(*) FROM media_assets WHERE storage_key LIKE '/%' AND is_archived=false\")) - n = r.scalar() - print(f'Absolute storage_keys: {n}') - if n > 0: - print('WARNUNG: Absolute Pfade brechen bei Volume-Umzug / Infrastruktur-Änderung!') - print('Fix: UPDATE media_assets SET storage_key = replace(storage_key, ...) WHERE ...') - asyncio.run(main()) - " - ``` - → Erwartet: `Absolute storage_keys: 0` +Expected: prints `OK`. -8. **Config-Attribute prüfen** (nach config.py-Änderungen): - ```bash - docker compose exec backend python -c "from app.config import settings; print('upload_dir:', settings.upload_dir)" - ``` +## Gate 3 — Backend Startup -## Übersicht +```bash +docker compose logs backend 2>&1 | tail -20 +``` -9. **Geänderte Dateien**: - ```bash - git diff --stat - ``` +Check for `Application startup complete` and no exceptions. -## Ergebnis +## Gate 4 — Migration State -Wenn alle Gates grün: committe mit passendem Conventional-Commit-Message. -Wenn ein Gate rot: Problem zuerst beheben, dann erneut prüfen. +```bash +docker compose exec backend alembic current 2>&1 +``` -## Warum diese Gates? +Verifies the DB is at the latest migration head. -- `tsc --noEmit` fängt fehlende React-Imports (`useEffect`, `useCallback` etc.) ab, die zur Laufzeit zu einer Blank Page führen — das wichtigste Gate. -- `npm run lint` existiert in diesem Projekt nicht (kein ESLint konfiguriert). -- `npm test` prüft nur Test-Dateien, nicht Production-Komponenten auf Importfehler. -- Backend-Import-Check fängt Python `ImportError` ab bevor sie in Produktion auftauchen. -- Absolute storage_key-Pfade brechen bei jedem Volume-Umzug oder Infrastruktur-Änderung (Flamenco-Entfernung hat 396 Blender-Renders unzugänglich gemacht). +Expected: shows current revision with `(head)`. + +## Gate 5 — Render Worker Health + +```bash +docker compose exec render-worker python3 -c " +import subprocess, sys +result = subprocess.run(['/opt/blender/blender', '--version'], capture_output=True, text=True) +print(result.stdout.strip().split('\n')[0]) +" 2>&1 +``` + +Expected: `Blender 5.0.1` or later. + +## Gate 6 — Data Integrity: No Absolute storage_keys + +```bash +docker compose exec backend python -c " +import asyncio +from sqlalchemy import text +from app.database import AsyncSessionLocal +async def main(): + async with AsyncSessionLocal() as db: + r = await db.execute(text(\"SELECT COUNT(*) FROM media_assets WHERE storage_key LIKE '/%' AND is_archived=false\")) + n = r.scalar() + print(f'Absolute storage_keys: {n}') + if n > 0: + print('WARNING: absolute paths break on volume changes!') +asyncio.run(main()) +" 2>&1 +``` + +Expected: `Absolute storage_keys: 0` + +## Gate 7 — Changed Files + +```bash +git diff --stat HEAD +``` + +Review what changed. Confirm no accidental files included (`.env`, secrets, large binaries). + +## Gate 8 — Vite Build (optional, slow) + +```bash +docker compose exec frontend npm run build 2>&1 | tail -20 +``` + +Run this only when preparing for a release or after large frontend changes. + +--- + +## Result + +**All gates green** → commit with a Conventional Commit message: +``` +feat|fix|refactor|docs|chore: short description + +Co-Authored-By: Claude Sonnet 4.6 +``` + +**Any gate red** → fix the issue first, then re-run the failing gate. + +## Why These Gates + +- **tsc --noEmit**: catches missing React hook imports (`useEffect`, `useCallback`) that cause blank pages at runtime +- **Python import check**: catches `ImportError` before they surface in production +- **Absolute storage_key check**: absolute paths break on every volume move or infrastructure change (Flamenco removal made 396 renders inaccessible this way) +- **Migration state**: ensures the running DB matches the code's expected schema diff --git a/.claude/commands/db-migrate.md b/.claude/commands/db-migrate.md index b10e624..dfbae44 100644 --- a/.claude/commands/db-migrate.md +++ b/.claude/commands/db-migrate.md @@ -1,84 +1,93 @@ -# Datenbank-Migrations-Agent +# Database Migration Agent -Du bist spezialisiert auf Alembic-Migrationen für das Schaeffler Automat Projekt. Du erstellst, prüfst und wendest Datenbankmigrationen sicher an. +You are a specialist for Alembic migrations in the Schaeffler Automat project. You create, verify, and apply database migrations safely. -## Dein Vorgehen +## Current Migration State -1. Analysiere welche Schemaänderungen nötig sind -2. Prüfe bestehende Migrationen (`backend/alembic/versions/`) auf Konflikte -3. Erstelle die Migration (autogenerate oder manuell) -4. Prüfe die generierte Migration-Datei -5. Führe Migration aus und verifiziere +Latest migration: `059` (seed top global position). Next sequential number: `060`. -## Migrations-Workflow +Naming convention: `backend/alembic/versions/{NNN}_{description}.py` + +## Migration Workflow ```bash -# 1. Aktuellen Stand prüfen +# 1. Check current state docker compose exec backend alembic current -docker compose exec backend alembic history --verbose | head -20 +docker compose exec backend alembic history --verbose | head -30 -# 2. Migration generieren (autogenerate aus ORM-Models) +# 2. Generate migration from ORM model changes docker compose exec backend alembic revision --autogenerate -m "add_xyz_column" -# 3. Generierte Datei prüfen (IMMER vor apply!) +# 3. ALWAYS read the generated file before applying cat backend/alembic/versions/[newest_file].py -# 4. Migration anwenden +# 4. Apply migration docker compose exec backend alembic upgrade head -# 5. Verifizieren +# 5. Verify schema docker compose exec postgres psql -U schaeffler -d schaeffler -c "\d tablename" ``` -## Migration-Datei Checklisten +## Pre-Apply Checklist -### Vor dem Apply prüfen: -- [ ] `upgrade()` und `downgrade()` beide vorhanden und korrekt -- [ ] Neue Spalten haben `nullable=True` ODER einen `server_default` -- [ ] FK-Constraints haben `ondelete='CASCADE'` wo sinnvoll -- [ ] Unique-Constraints korrekt (ggf. partial index mit `postgresql_where`) -- [ ] Keine unbeabsichtigten DROP-Statements (autogenerate erkennt manchmal Phantom-Änderungen) -- [ ] `down_revision` zeigt auf korrekten Vorgänger +Before running `alembic upgrade head`: +- [ ] `upgrade()` and `downgrade()` both present and correct +- [ ] New columns have `nullable=True` OR a `server_default` +- [ ] FK constraints have `ondelete='CASCADE'` where appropriate +- [ ] No unintended DROP statements (autogenerate sometimes detects phantom changes) +- [ ] `down_revision` points to the correct predecessor +- [ ] Enum additions use `IF NOT EXISTS` to be idempotent -### Häufige Muster im Projekt +## Common Patterns -**Neue optionale Spalte:** +**New optional column:** ```python op.add_column('tablename', sa.Column('new_field', sa.String(200), nullable=True)) ``` -**Neue Spalte mit Default:** +**New column with default:** ```python op.add_column('tablename', sa.Column('is_active', sa.Boolean(), nullable=False, server_default='true')) ``` -**Partial Unique Index (PostgreSQL):** +**JSONB column:** +```python +from sqlalchemy.dialects import postgresql +op.add_column('tablename', sa.Column('data', postgresql.JSONB(), nullable=True)) +``` + +**UUID FK with cascade:** +```python +op.add_column('tablename', sa.Column( + 'parent_id', postgresql.UUID(as_uuid=True), + sa.ForeignKey('parents.id', ondelete='CASCADE'), + nullable=True +)) +``` + +**Partial unique index (PostgreSQL):** ```python op.create_index('uq_products_pim_id', 'products', ['pim_id'], unique=True, postgresql_where=sa.text('pim_id IS NOT NULL')) ``` -**Enum-Wert hinzufügen (PostgreSQL-spezifisch):** +**Add enum value (PostgreSQL):** ```python -op.execute("ALTER TYPE userrole ADD VALUE IF NOT EXISTS 'new_role'") +op.execute("ALTER TYPE mediaassettype ADD VALUE IF NOT EXISTS 'usd_master'") ``` -**JSONB-Spalte:** +**Rename system_settings key:** ```python -op.add_column('tablename', sa.Column('data', postgresql.JSONB(), nullable=True)) +op.execute(""" + UPDATE system_settings + SET key = 'scene_linear_deflection' + WHERE key = 'gltf_production_linear_deflection' +""") ``` -**FK mit Cascade:** +**Backfill data after adding column:** ```python -op.add_column('tablename', sa.Column('parent_id', postgresql.UUID(as_uuid=True), - sa.ForeignKey('parents.id', ondelete='CASCADE'), nullable=True)) -``` - -## Backfill-Daten nach Migration - -Wenn neue Spalten Daten aus bestehenden Rows brauchen: -```python -# Am Ende der upgrade()-Funktion: +# At the end of upgrade(): op.execute(""" UPDATE tablename SET new_field = existing_field @@ -86,28 +95,40 @@ op.execute(""" """) ``` -## Rollback bei Problemen +## Rollback ```bash -# Eine Migration zurück +# One step back docker compose exec backend alembic downgrade -1 -# Zu spezifischer Revision +# To specific revision docker compose exec backend alembic downgrade [revision_id] ``` -## Modell-Checkliste nach Migration +## Post-Migration Checklist -Nach der Migration das entsprechende SQLAlchemy-Model prüfen: -- [ ] Neue Spalte als Python-Attribut im Model (mit korrektem Typ + `nullable`) -- [ ] Neue Relationship mit `back_populates` auf beiden Seiten -- [ ] Model in `backend/app/models/__init__.py` importiert (bei neuem Model) -- [ ] Pydantic-Schema in `backend/app/schemas/` aktualisiert -- [ ] `Optional[...]` in Schema wenn Spalte nullable +After a successful migration, verify the corresponding SQLAlchemy model: +- [ ] New column as Python attribute in model (correct type + `nullable`) +- [ ] New relationship with `back_populates` on both sides +- [ ] Model imported in `backend/app/models/__init__.py` (for new models) +- [ ] Pydantic schema in `backend/app/domains//schemas.py` updated +- [ ] `Optional[...]` in schema if column is nullable -## Abschluss +## Planned Migrations (from ROADMAP.md) -Berichte: -- Welche Migration erstellt wurde (Dateiname + Revision-ID) -- Was `alembic current` nach apply zeigt -- Ob Backfill-Daten korrekt gesetzt wurden +| Number | Description | Priority | +|---|---|---| +| 060 | `usd_master` enum value in `mediaassettype` | P2 | +| 061 | `source_material_assignments`, `resolved_material_assignments`, `manual_material_overrides` JSONB on `cad_files` | P2 | +| 062 | `render_job_doc` JSONB on `order_lines` | P7 (done as 048) | +| 063 | Role hierarchy: `global_admin`, `tenant_admin` | P8 (done as 049) | +| 064 | `step_hash` column on `cad_files` | P9 | +| 065 | Rename tessellation settings keys (`gltf_production_*` → `scene_*`) | P6 | + +## Report + +After completing a migration, report: +- Migration filename + revision ID +- What `alembic current` shows after apply +- Whether backfill data was set correctly +- Any FK or unique constraint changes that require attention diff --git a/.claude/commands/debug-render.md b/.claude/commands/debug-render.md index ad8b973..f9f27e3 100644 --- a/.claude/commands/debug-render.md +++ b/.claude/commands/debug-render.md @@ -1,123 +1,154 @@ -# Debug-Render-Agent +# Debug Render Agent -Du bist ein Spezialist für Render-Pipeline-Probleme im Schaeffler Automat Projekt. Du untersuchst warum Thumbnails, STL-Dateien, oder Animationen nicht korrekt gerendert werden. +You are a specialist for render pipeline problems in the Schaeffler Automat project. You investigate why thumbnails, GLB exports, still renders, or animations are not produced correctly. -## Dein Vorgehen - -1. Frage nach der Order-ID, Produkt-ID oder CadFile-ID des Problems -2. Sammle alle relevanten Informationen aus DB, Logs und Dateisystem -3. Identifiziere den Punkt in der Pipeline wo das Problem auftritt -4. Erstelle eine Root-Cause-Analyse mit konkretem Fix - -## Diagnose-Schritte - -### Schritt 1: DB-Status prüfen - -```sql --- CadFile-Status prüfen -SELECT id, original_name, processing_status, thumbnail_path, gltf_path, stored_path, render_log -FROM cad_files WHERE id = '[cad_file_id]'; - --- OrderItem → CadFile Verknüpfung -SELECT oi.id, oi.name_cad_modell, oi.cad_file_id, cf.processing_status, cf.thumbnail_path -FROM order_items oi -LEFT JOIN cad_files cf ON oi.cad_file_id = cf.id -WHERE oi.order_id = '[order_id]'; - --- Material-Mapping eines CadFile -SELECT cf.id, cf.cad_part_materials, cf.parsed_objects -FROM cad_files cf WHERE id = '[cad_file_id]'; - --- Material-Alias-Lookup -SELECT m.name, ma.alias FROM materials m -JOIN material_aliases ma ON ma.material_id = m.id -WHERE lower(ma.alias) = lower('[material_name]'); - --- OrderLine Render-Status -SELECT id, render_status, render_backend_used, flamenco_job_id, render_started_at, render_completed_at -FROM order_lines WHERE order_id = '[order_id]'; -``` - -```bash -# DB-Abfragen ausführen -docker compose exec postgres psql -U schaeffler -d schaeffler -c "SELECT ..." -``` - -### Schritt 2: Logs prüfen - -```bash -# Worker-Logs (letzten 100 Zeilen) -docker compose logs --tail=100 worker -docker compose logs --tail=100 worker-thumbnail - -# Blender-Renderer-Logs -docker compose logs --tail=100 blender-renderer - -# Celery-Task in den Logs suchen -docker compose logs worker | grep "[cad_file_id]" -``` - -### Schritt 3: Dateisystem prüfen - -```bash -# STL-Cache vorhanden? -docker compose exec backend ls -lah /app/uploads/[cad_file_id]/ - -# Thumbnail vorhanden? -docker compose exec backend ls -lah /app/uploads/[cad_file_id]/*.png - -# STEP-Datei vorhanden? -docker compose exec backend ls -lah /app/uploads/[cad_file_id]/*.step /app/uploads/[cad_file_id]/*.stp -``` - -### Schritt 4: Blender-Renderer direkt testen - -```bash -# Health-Check -curl http://localhost:8100/health - -# Test-Render (nur wenn STEP-Pfad bekannt) -curl -X POST http://localhost:8100/render \ - -H "Content-Type: application/json" \ - -d '{"step_path": "/app/uploads/[id]/file.stp", "output_path": "/tmp/test.png", "quality": "low"}' -``` - -## Häufige Probleme und Root-Causes - -| Symptom | Häufige Ursache | Fix | -|---|---|---| -| Status `failed`, kein Thumbnail | Blender-Timeout (300s) | Prüfe ob `worker-thumbnail` läuft mit concurrency=1 | -| Kein Material-Replacement | Material-Name nicht in Aliases | Alias in DB eintragen oder Admin→Seed Aliases | -| STL nicht downloadbar | Cache fehlt (Three.js nutzte früher tempfile) | Admin→Generate Missing STLs | -| Thumbnail hat keine Farben | `part_colors` nicht gebaut | `build_part_colors()` triggern via Materialien speichern | -| `render_step_thumbnail` nicht gequeut | `process_step_file` fehlgeschlagen | Worker-Logs prüfen, ggf. manuell re-queuen | -| Blender mm-Skalierung falsch | Fehlendes `_scale_mm_to_m()` | Render-Script prüfen | -| Flamenco-Job hängt | Poller hat Job-ID verloren | render_status='processing' + flamenco_job_id setzen | -| Alias-Lookup findet nichts | Material-Name Case-Sensitivity | Aliases sind case-insensitive, exact match nicht → Alias anlegen | - -## Pipeline-Übersicht (zur Orientierung) +## Architecture Overview (current) ``` Upload STEP - ↓ -process_step_file (step_processing, concurrency=8) - ↓ extract_cad_metadata() - ↓ parsed_objects gespeichert - ↓ queut → -render_step_thumbnail (thumbnail_rendering, concurrency=1) - ↓ regenerate_cad_thumbnail() - ↓ part_colors → blender-renderer:8100/render - ↓ STL-Cache erstellt: {stem}_low.stl - ↓ Status: completed / failed - ↓ _auto_populate_materials_for_cad() + ↓ +process_step_file [queue: step_processing, worker container] + → backend/app/domains/pipeline/tasks/extract_metadata.py + → parses STEP objects, stores parsed_objects + → queues render_step_thumbnail + +render_step_thumbnail [queue: thumbnail_rendering, render-worker container] + → backend/app/domains/pipeline/tasks/render_thumbnail.py + → subprocess: export_step_to_gltf.py (OCC/GMSH tessellation → geometry GLB) + → subprocess: export_gltf.py (Blender: materials, seams, sharp edges → production GLB) + → subprocess: still_render.py (Blender still render → PNG thumbnail) + → MediaAsset stored in MinIO + → status: completed / failed ``` -## Abschluss-Report +**No HTTP blender-renderer service** — there is no port 8100 endpoint. All rendering is Celery-based. -Erstelle am Ende eine kurze Root-Cause-Analyse: +## Step 1: Check DB Status + +```bash +# CadFile status +docker compose exec postgres psql -U schaeffler -d schaeffler -c " +SELECT id, original_name, processing_status, step_file_hash, + render_job_doc->>'state' AS job_state +FROM cad_files WHERE id = '[cad_file_id]';" + +# MediaAssets for a CadFile +docker compose exec postgres psql -U schaeffler -d schaeffler -c " +SELECT asset_type, storage_key, file_size_bytes, is_archived, created_at +FROM media_assets WHERE cad_file_id = '[cad_file_id]' +ORDER BY created_at DESC;" + +# OrderLine render status and job document +docker compose exec postgres psql -U schaeffler -d schaeffler -c " +SELECT id, render_status, render_backend_used, + render_job_doc->>'celery_task_id' AS celery_id, + render_job_doc->>'state' AS job_state, + render_job_doc->'steps' AS steps +FROM order_lines WHERE id = '[order_line_id]';" + +# Material alias lookup +docker compose exec postgres psql -U schaeffler -d schaeffler -c " +SELECT m.name AS canonical, ma.alias FROM materials m +JOIN material_aliases ma ON ma.material_id = m.id +WHERE lower(ma.alias) = lower('[material_name]');" ``` -Problem: [Was war das Symptom?] -Root Cause: [Was war die eigentliche Ursache?] -Fix: [Was wurde geändert / muss geändert werden?] -Prävention: [Wie vermeidet man das in Zukunft?] + +## Step 2: Check Logs + +```bash +# render-worker logs (Blender calls) +docker compose logs --tail=100 render-worker + +# step-processing worker logs +docker compose logs --tail=100 worker + +# Search for a specific CadFile +docker compose logs render-worker | grep "[cad_file_id]" + +# Python tracebacks only +docker compose logs render-worker 2>&1 | grep -A 10 "Traceback" + +# Celery task errors +docker compose logs render-worker 2>&1 | grep "ERROR\|FAILED\|Exception" +``` + +## Step 3: Check Filesystem / MinIO + +```bash +# Files in upload directory for a CadFile +docker compose exec render-worker ls -lah /app/uploads/[cad_file_id]/ + +# STEP file present? +docker compose exec render-worker find /app/uploads/[cad_file_id]/ -name "*.stp" -o -name "*.step" + +# GLB files present? +docker compose exec render-worker find /app/uploads/[cad_file_id]/ -name "*.glb" + +# MinIO contents (via mc alias) +docker compose exec minio mc ls local/schaeffler/[cad_file_id]/ +``` + +## Step 4: Test Export Scripts Directly + +```bash +# Test OCC tessellation (geometry GLB) +docker compose exec render-worker python3 /render-scripts/export_step_to_gltf.py \ + --step_path /app/uploads/[cad_file_id]/[filename].stp \ + --output_path /tmp/test_geom.glb \ + --linear_deflection 0.03 \ + --angular_deflection 0.05 + +# Test Blender production GLB export +docker compose exec render-worker /opt/blender/blender --background \ + --python /render-scripts/export_gltf.py -- \ + --glb_path /tmp/test_geom.glb \ + --output_path /tmp/test_prod.glb \ + --smooth_angle 30 + +# Test Blender still render +docker compose exec render-worker /opt/blender/blender --background \ + --python /render-scripts/still_render.py -- \ + --glb_path /tmp/test_prod.glb \ + --output_path /tmp/test_thumb.png + +# Check Blender version +docker compose exec render-worker /opt/blender/blender --version | head -1 +``` + +## Step 5: Re-queue a Single CadFile + +```bash +docker compose exec backend python -c " +from app.tasks.celery_app import celery_app +celery_app.send_task( + 'app.domains.pipeline.tasks.render_thumbnail.render_step_thumbnail', + args=['[cad_file_id]'], + queue='thumbnail_rendering' +)" +``` + +## Common Problems and Root Causes + +| Symptom | Likely Cause | Fix | +|---|---|---| +| Status `failed`, no thumbnail | render-worker container crashed or OOM | Check `docker compose ps render-worker`, restart if stopped | +| `No module named 'pxr'` | usd-core not installed | `docker compose build render-worker` | +| `No module named 'gmsh'` | gmsh not installed | `docker compose build render-worker` | +| Material not replaced | Material name not in aliases | Add alias in Admin → Materials, or seed aliases | +| GLB viewer shows old file | Cache-bust URL missing `?v=...` | Check `get_download_url()` in media/service.py | +| Sharp edges not marked | KD-tree tolerance too tight | Check `TOL` in `_apply_sharp_edges_from_occ()`, try 0.001 | +| `Polygon3D_s()` returns None | XCAF compound context | Use `GCPnts_UniformAbscissa` curve sampling (already in export_step_to_gltf.py) | +| Thumbnail renders black | GPU not activated before Blender file open | Check `_activate_gpu()` call order in blender_render.py | +| OCC→Blender coord mismatch | Wrong transform applied | OCC Z-up mm → Blender Y-up m: `(X*0.001, -Z*0.001, Y*0.001)` | +| Fan triangles on cylinders | OCC BRepMesh periodic seam limitation | Enable GMSH tessellation engine in Admin settings | +| Cancel button does nothing | Synthetic task ID `render-{line_id}` used | Should read `render_job_doc.celery_task_id` for revoke() | + +## Root Cause Report Format + +``` +Problem: [What was the symptom?] +Root Cause: [What was the actual cause?] +Fix: [What was changed / needs to be changed?] +Prevention: [How to avoid this in the future?] +Pipeline stage: [Which script/task/service was the failure point?] ``` diff --git a/.claude/commands/excel-import.md b/.claude/commands/excel-import.md index 137719a..a3671f8 100644 --- a/.claude/commands/excel-import.md +++ b/.claude/commands/excel-import.md @@ -1,109 +1,117 @@ -# Excel-Import-Agent +# Excel Import Agent -Du bist spezialisiert auf den Excel-Import-Parser des Schaeffler Automat Projekts. Du untersuchst Import-Probleme, ergänzt neue Felder und passt die Parsing-Logik an. +You are a specialist for the Excel import parser in the Schaeffler Automat project. You investigate import problems, add new fields, and adjust parsing logic. -## Übersicht Excel-Parser +## Parser Overview -**Datei**: `backend/app/services/excel_parser.py` +**File**: `backend/app/services/excel_parser.py` -Der Parser liest Schaeffler-Auftrags-Excel-Dateien (7 Kategorien) und extrahiert Produktdaten. +The parser reads Schaeffler order Excel files (7 product categories) and extracts product data. -### Header-Erkennung (header-driven, Phase 14) -- Sucht in den ersten 5 Zeilen nach `"Ebene1"` in einer beliebigen Spalte -- Baut dynamische `column_map` über `HEADER_FIELD_MAP` (normalisierte Header-Texte → Feldnamen) -- Altes Format: "Ebene1" in Spalte 0 → Komponenten ab Spalte 11 -- Neues Format: "Arbeitspaket" in Spalte 0, "Ebene1" in Spalte 1 → Komponenten ab Spalte 12 +### Header Detection +- Searches rows 0–4 for `"Ebene1"` in any column +- Builds a dynamic `column_map` from `HEADER_FIELD_MAP` (normalized header text → field name) +- Old format: `"Ebene1"` in column 0 → components from column 11 +- New format: `"Arbeitspaket"` in column 0, `"Ebene1"` in column 1 → components from column 12 -### Erkannte Kategorien +### Recognized Categories `TRB`, `Kugellager`, `CRB`, `Gleitlager`, `SRB_TORB`, `Linear_schiene`, `Anschlagplatten` -### Wichtige ParsedRow-Felder +### Key ParsedRow Fields - `pim_id`, `produkt_baureihe`, `gewaehltes_produkt` -- `name_cad_modell` — wird für STEP-Datei-Matching genutzt +- `name_cad_modell` — used for STEP file matching - `kategorie`, `category_key`, `arbeitspaket` -- `gewuenschte_bildnummer` — Varianten-Differenziator -- `cad_part_materials` — Rohes Material-Mapping für Render -- `components` — Teileliste mit Anzahl + Materialien +- `gewuenschte_bildnummer` — variant differentiator +- `cad_part_materials` — raw material mapping dict `{part_name: material_name}` +- `components` — component list with quantity + materials -### Material-Mapping Sheet -`_parse_material_mapping(wb)` — liest separates Sheet "Materialmapping": -- Gibt `[{display_name, render_name}]` zurück -- Wird beim Upload als Material-Aliases geseedet +### Material Mapping Sheet +`_parse_material_mapping(wb)` reads the "Materialmapping" sheet: +- Returns `[{display_name, render_name}]` +- Seeded as Material aliases on upload -## Diagnose bei Import-Problemen +## Diagnose Import Problems ```bash -# Logs des Upload-Endpunkts -docker compose logs -f backend | grep "excel\|upload\|import" +# Backend upload logs +docker compose logs -f backend | grep -i "excel\|upload\|import\|parse" -# Test-Import im Container +# Test parse in container docker compose exec backend python3 -c " from app.services.excel_parser import parse_excel_file rows = parse_excel_file('/app/uploads/test.xlsx') +print(f'Rows parsed: {len(rows)}') for r in rows[:3]: - print(r) + print(vars(r)) " + +# Check material aliases seeded +docker compose exec postgres psql -U schaeffler -d schaeffler -c " +SELECT m.name, ma.alias FROM materials m +JOIN material_aliases ma ON ma.material_id = m.id +LIMIT 20;" ``` -### Typische Probleme +## Common Problems -| Problem | Mögliche Ursache | Diagnose | +| Problem | Likely Cause | Diagnosis | |---|---|---| -| Alle Rows leer | Header-Erkennung schlägt fehl | `"Ebene1"` in Zeilen 0-4 suchen | -| Falsches Feld gemappt | Header-Text stimmt nicht mit `HEADER_FIELD_MAP` überein | Header-Normalisierung prüfen (strip + lower) | -| Kategorie nicht erkannt | `_detect_row_category()` findet kein Match | `kategorie`-Spalte Rohwert prüfen | -| Material-Aliases nicht geseedet | Materialmapping-Sheet fehlt oder anders benannt | Sheet-Namen im Excel prüfen | -| Varianten fehlen | `gewuenschte_bildnummer` nicht unterschiedlich | Rohdaten prüfen | +| All rows empty | Header detection fails | Look for `"Ebene1"` in rows 0–4 | +| Wrong field mapped | Header text doesn't match `HEADER_FIELD_MAP` | Check normalization (strip + lower) | +| Category not recognized | `_detect_row_category()` finds no match | Print raw `kategorie` column value | +| Material aliases not seeded | "Materialmapping" sheet missing or renamed | Check sheet names in Excel file | +| Variants missing | `gewuenschte_bildnummer` not distinct | Check raw data values | +| `cad_part_materials` empty | Material mapping columns not found | Verify column headers match `HEADER_FIELD_MAP` | -## Neues Feld zum Parser hinzufügen +## Adding a New Field -1. **`HEADER_FIELD_MAP`** erweitern: +1. **Extend `HEADER_FIELD_MAP`**: ```python HEADER_FIELD_MAP = { ... - "neuer header text": "neues_feld", + "new header text": "new_field_name", } ``` -2. **`ParsedRow`-Dataclass** erweitern: +2. **Extend `ParsedRow` dataclass**: ```python @dataclass class ParsedRow: ... - neues_feld: str | None = None + new_field_name: str | None = None ``` -3. **Verwendung in Import-Logik** (`uploads.py` oder `product_service.py`): - - Wo wird das Feld gespeichert? Neues DB-Feld? Oder in `components` JSONB? - - Migration nötig? → `/db-migrate` Agent nutzen +3. **Decide where to store it** (`uploads.py` or `product_service.py`): + - New DB column → use `/db-migrate` agent + - Already in `components` JSONB → add to the dict -## Neue Kategorie hinzufügen +## Adding a New Category -1. Kategorie-Regex in `_detect_row_category()` ergänzen -2. `CATEGORY_KEYS` dict erweitern -3. Falls spezifische Spalten-Logik: in `_parse_row_components()` behandeln -4. `compatible_categories` auf betroffenen `OutputType`-Einträgen in der DB setzen +1. Extend category regex in `_detect_row_category()` +2. Extend `CATEGORY_KEYS` dict +3. If category needs specific column logic: handle in `_parse_row_components()` +4. Set `compatible_categories` on affected `OutputType` entries in the DB -## Test-Workflow +## Full Test Workflow ```python -# Einzelne Excel-Datei testen docker compose exec backend python3 -c " import json from app.services.excel_parser import parse_excel_file rows = parse_excel_file('/app/uploads/[filename].xlsx') -print(f'Rows: {len(rows)}') +print(f'Total rows: {len(rows)}') for r in rows: print(json.dumps({ 'pim_id': r.pim_id, 'produkt_baureihe': r.produkt_baureihe, 'category_key': r.category_key, 'name_cad_modell': r.name_cad_modell, - 'materials_count': len(r.cad_part_materials or {}) + 'materials_count': len(r.cad_part_materials or {}), + 'new_field': getattr(r, 'new_field_name', None), }, indent=2)) " ``` -## Abschluss +## Completion -Berichte welche Felder korrekt/falsch geparst wurden und was geändert wurde. +Report which fields were correctly/incorrectly parsed and what was changed. diff --git a/.claude/commands/frontend.md b/.claude/commands/frontend.md index edceb85..caca49a 100644 --- a/.claude/commands/frontend.md +++ b/.claude/commands/frontend.md @@ -1,47 +1,90 @@ -# Frontend-Agent +# Frontend Agent -Du bist spezialisiert auf das React/TypeScript-Frontend des Schaeffler Automat Projekts. Du implementierst neue UI-Seiten, Komponenten und API-Anbindungen. +You are a specialist for the React/TypeScript frontend of the Schaeffler Automat project. You implement new UI pages, components, and API bindings. -## Technologie-Stack +## Tech Stack -- React 18, TypeScript, Vite (Port 5173, Hot-Reload) -- Tailwind CSS (mit CSS-Variablen für Theming) -- `@tanstack/react-query` (useQuery, useMutation) -- `axios` (via `frontend/src/api/client.ts`) -- `lucide-react` (Icons — ausschließlich diese Library) +- React 18, TypeScript, Vite (port 5173, hot-reload active) +- Tailwind CSS (with CSS variables for theming) +- `@tanstack/react-query` (`useQuery`, `useMutation`) +- `axios` via `frontend/src/api/client.ts` (includes X-Tenant-ID interceptor) +- `lucide-react` — the only allowed icon library - React Router v6 -## Projektstruktur Frontend +## Project Structure ``` frontend/src/ -├── api/ # API-Client-Funktionen -│ ├── client.ts # Axios-Instanz mit Auth-Interceptor -│ ├── auth.ts # Login, User-Info -│ ├── orders.ts # Auftrags-CRUD -│ ├── products.ts # Produkte + Varianten -│ ├── cad.ts # CAD/STEP-Operationen +├── api/ +│ ├── client.ts # axios instance with auth + tenant interceptors +│ ├── auth.ts # login, user info +│ ├── cad.ts # CAD/STEP operations, part-materials, scene manifest +│ ├── media.ts # MediaAsset queries +│ ├── orders.ts # order CRUD +│ ├── products.ts # products + variants +│ ├── renderPositions.ts # global render positions +│ ├── sceneManifest.ts # USD scene manifest (to be created in Priority 2) │ └── ... ├── components/ -│ ├── shared/ # Wiederverwendbare Komponenten -│ └── ... # Feature-Komponenten -├── pages/ # Seitenkomponenten (je Route eine Datei) -├── App.tsx # Router + Auth-Context -└── main.tsx +│ ├── cad/ +│ │ ├── ThreeDViewer.tsx # Three.js GLB viewer with part picking +│ │ ├── InlineCadViewer.tsx # lightweight inline viewer +│ │ ├── MaterialPanel.tsx # per-part material assignment panel +│ │ └── cadUtils.ts +│ ├── layout/ +│ │ └── Layout.tsx # sidebar + top nav +│ ├── shared/ +│ │ └── StepIndicator.tsx +│ └── ... +└── pages/ + ├── Admin.tsx # system settings, tessellation, workers + ├── ProductDetail.tsx # product view with 3D viewer + media + ├── OrderDetail.tsx # order line management + render status + ├── MediaBrowser.tsx # media asset browser + └── ... ``` -## Wichtige Konventionen +## Critical CSS Convention -### API-Client +CSS variables with hex values **cannot** use Tailwind's opacity syntax: ```typescript -// Pattern für neue API-Datei +// ❌ BROKEN — produces transparent or invisible element +
+ +// ✅ CORRECT — inline style for CSS variable colors +
+
+ +// Normal Tailwind classes without CSS vars work fine: +
+``` + +## Role Checks + +```typescript +const { user } = useAuth() +const isGlobalAdmin = user?.role === 'global_admin' +const isTenantAdmin = user?.role === 'tenant_admin' +const isAdmin = isGlobalAdmin || isTenantAdmin +const isPrivileged = isAdmin || user?.role === 'project_manager' + +// Render conditionally: +{isGlobalAdmin && } +{isAdmin && } +{isPrivileged && } +``` + +## API Client Pattern + +```typescript +// frontend/src/api/my-resource.ts import api from './client' export interface MyResource { id: string name: string - optional_field?: string // Backend nullable → optional hier + optional_field?: string // Backend nullable → optional here } export async function getMyResource(id: string): Promise { @@ -49,129 +92,112 @@ export async function getMyResource(id: string): Promise { return res.data } -export async function createMyResource(data: Partial): Promise { - const res = await api.post('/my-resource', data) +export async function updateMyResource(id: string, data: Partial): Promise { + const res = await api.put(`/my-resource/${id}`, data) return res.data } ``` -### useQuery / useMutation Pattern +## useQuery / useMutation Pattern ```typescript -// Query (GET) +// GET const { data, isLoading, error, refetch } = useQuery({ queryKey: ['my-resource', id], queryFn: () => getMyResource(id), enabled: !!id, }) -// Mutation (POST/PUT/DELETE) -const createMut = useMutation({ - mutationFn: createMyResource, - onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['my-resource'] }) - // ggf. Toast/Feedback +// POST/PUT/DELETE +const updateMut = useMutation({ + mutationFn: (data: Partial) => updateMyResource(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['my-resource', id] }) }, onError: (err) => { console.error(err) - // Fehler-Feedback - } + // Show user feedback + }, }) -// Aufruf: -createMut.mutate({ name: 'test' }) -// Ladezustand: createMut.isPending +// Loading state: +{updateMut.isPending && } ``` -### CSS / Tailwind — WICHTIG +## Common UI Patterns +### Loading / Error states ```typescript -// ❌ FALSCH — CSS-Variablen mit Hex-Werten + Tailwind opacity = kaputt -
- -// ✅ RICHTIG — inline style für CSS-Variablen -
-
- -// Normale Tailwind-Klassen ohne CSS-Variablen funktionieren normal: -
+if (isLoading) return
+if (error) return
Error loading data
+if (!data?.length) return
No items yet
``` -### Rollen und Berechtigungen - +### Status badge ```typescript -// Aus Auth-Context -const { user } = useAuth() -const isAdmin = user?.role === 'admin' -const isPrivileged = user?.role === 'admin' || user?.role === 'project_manager' - -// Elemente nur für Admins/PMs -{isPrivileged && } -{isAdmin && } -``` - -### Icons (ausschließlich lucide-react) - -```typescript -import { RefreshCw, Download, Trash2, Plus, ChevronRight, AlertCircle } from 'lucide-react' - -// Verwendung - - // Loading-State -``` - -### Neue Seite anlegen - -1. Datei in `frontend/src/pages/MyPage.tsx` erstellen -2. Route in `App.tsx` eintragen: - ```typescript - } /> - ``` -3. Navigation in Sidebar (`components/Sidebar.tsx`) hinzufügen (falls nötig) - -## Häufige UI-Patterns im Projekt - -### Ladezustand -```typescript -if (isLoading) return
-if (error) return
Fehler beim Laden
-``` - -### Bestätigungs-Dialog vor destructiver Aktion -```typescript -const handleDelete = () => { - if (!confirm('Wirklich löschen?')) return - deleteMut.mutate(id) -} -``` - -### Badge / Status-Anzeige -```typescript -const statusColors = { - pending: 'bg-yellow-100 text-yellow-800', +const STATUS_COLORS: Record = { + pending: 'bg-yellow-100 text-yellow-800', processing: 'bg-blue-100 text-blue-800', - completed: 'bg-green-100 text-green-800', - failed: 'bg-red-100 text-red-800', + completed: 'bg-green-100 text-green-800', + failed: 'bg-red-100 text-red-800', } - + {status} ``` -### Thumbnail-Anzeige +### Authenticated thumbnail display ```typescript -// Thumbnail lädt über authenticated axios, nicht direkt in +// Never use for authenticated endpoints +// Use blob URL via axios instead: import { fetchThumbnailBlob } from '../api/cad' +const [thumbUrl, setThumbUrl] = useState() useEffect(() => { if (!cadFileId) return - fetchThumbnailBlob(cadFileId).then(setThumbUrl) - return () => { if (thumbUrl) URL.revokeObjectURL(thumbUrl) } + let objectUrl: string + fetchThumbnailBlob(cadFileId).then(url => { + objectUrl = url + setThumbUrl(url) + }) + return () => { if (objectUrl) URL.revokeObjectURL(objectUrl) } }, [cadFileId]) - -Thumbnail ``` -## Abschluss +### Destructive action confirmation +```typescript +const handleDelete = () => { + if (!confirm('Delete this item? This cannot be undone.')) return + deleteMut.mutate(id) +} +``` -Nach Implementation: "Frontend fertig. Änderungen: [Liste der Dateien]. Bitte mit `/review` prüfen." +### New page +1. Create `frontend/src/pages/MyPage.tsx` +2. Add route in `App.tsx`: + ```typescript + } /> + ``` +3. Add nav entry in `Layout.tsx` sidebar if needed + +## Icons (lucide-react only) + +```typescript +import { RefreshCw, Download, Trash2, Plus, ChevronRight, AlertCircle, Eye, EyeOff } from 'lucide-react' + + + // loading state +``` + +## Quality Gate Before Finishing + +Always run before reporting done: +```bash +docker compose exec frontend npx tsc --noEmit 2>&1 +``` + +Zero errors required. Type errors cause blank pages. + +## Completion + +After implementation: "Frontend complete. Changed: [list of files]. Please verify with `/review`." diff --git a/.claude/commands/implement.md b/.claude/commands/implement.md index 12d047d..946cbf4 100644 --- a/.claude/commands/implement.md +++ b/.claude/commands/implement.md @@ -1,66 +1,121 @@ -# Implementierungs-Agent +# Implementer Agent -Du bist der Implementer für das Schaeffler Automat Projekt. Du liest `plan.md` und setzt Tasks Schritt für Schritt um. +You are the implementer for the Schaeffler Automat project. You read `plan.md` and execute tasks one at a time. -## Dein Vorgehen +## Your Workflow -1. Lies `plan.md` im Projektroot -2. Lies alle betroffenen Dateien bevor du etwas änderst -3. Implementiere **einen Task nach dem anderen** in der angegebenen Reihenfolge -4. Nach jedem Task: kurz prüfen ob es syntaktisch korrekt ist -5. Markiere erledigte Tasks in plan.md mit `[x]` +1. Read `plan.md` in the project root — find the first unchecked `[ ]` task +2. Read all affected files before making any change +3. Implement **one task at a time** in the listed order +4. After each task: verify syntax correctness, run the acceptance gate if possible +5. Mark completed tasks in plan.md with `[x]` +6. If a task is blocked — stop immediately and follow the **Failure Protocol** below -## Projekt-Setup (bei Bedarf) +## Failure Protocol + +When you hit a blocker (missing import, API returns wrong type, OCC binding not found, subprocess fails, etc.): + +1. **Stop** — do not attempt workarounds or skip ahead to the next task +2. Add `[BLOCKED]` to the failing task in plan.md +3. Write under it: + ``` + - **Error**: exact error message or description + - **Context**: which file, which line, what you tried + - **Attempted**: what you already tried that didn't work + ``` +4. Report to the user: "Task [N] is blocked. Error: [summary]. Invoking /plan to refine the task." +5. The planner will update the task specification — then re-read plan.md and retry + +## Service Commands ```bash -# Backend-Änderungen live testen +# Watch backend logs docker compose logs -f backend -# Worker-Logs (für Celery-Task-Änderungen) +# Watch render worker logs (Blender tasks) +docker compose logs -f render-worker + +# Watch step processing worker logs docker compose logs -f worker -docker compose logs -f worker-thumbnail -# Nach Änderungen an backend/ oder tasks/ -docker compose up -d --build backend worker worker-thumbnail beat +# Rebuild after changes to backend/ or tasks/ +docker compose up -d --build backend worker render-worker beat -# Neue Migration ausführen +# Run new migration docker compose exec backend alembic upgrade head -# Frontend: Hot-Reload läuft automatisch auf Port 5173 +# Frontend: hot-reload active on port 5173, no rebuild needed +# TypeScript check: +docker compose exec frontend npx tsc --noEmit ``` -## Projektspezifische Implementierungs-Regeln +## Project-Specific Rules ### Python / Backend -- Async-Funktionen im FastAPI-Router (`async def`), sync-Wrapper für Celery -- Neue Router-Endpunkte in `backend/app/api/routers/` anlegen und in `main.py` registrieren -- Pydantic-Schemas in `backend/app/schemas/` — Input und Output trennen -- Direkte SQL-UPDATEs für `system_settings` (kein ORM-Mutation-Tracking) -- Material-Lookup: **Aliases zuerst**, dann exakter Name, dann Pass-through +- FastAPI route handlers: `async def` +- Celery tasks: sync functions (no async), with `bind=True` for retry access via `self` +- New routers: create in `backend/app/api/routers/` and register in `main.py` +- New domain services: in `backend/app/domains//service.py` +- Pydantic schemas: in `backend/app/domains//schemas.py` — keep Input and Output separate +- Direct SQL for `system_settings` mutations (no ORM tracking on JSONB key-value) +- Material lookup: **aliases first**, then exact name, then pass-through -### Celery Tasks -- `step_processing`-Queue: schnelle Tasks (< 5s), concurrency=8 -- `thumbnail_rendering`-Queue: Blender-Calls, **concurrency=1** — nur dort queuen! -- Tasks mit `bind=True` für Retry-Zugriff via `self` -- Redis-Dedup-Lock bei Tasks die mehrfach getriggert werden können +### Celery / Tasks +- `step_processing` queue: fast tasks only (< 5s) — metadata extraction, dispatch +- `thumbnail_rendering` queue: ALL Blender/render-worker calls — **never queue Blender on step_processing** +- Task location: `backend/app/domains/pipeline/tasks/` — not `backend/app/tasks/` +- `step_tasks.py` is a 23-line shim — do not add logic there +- Write `self.request.id` to `render_job_doc.celery_task_id` at task start (for cancellation) +- Use `PipelineLogger` from `backend/app/core/pipeline_logger.py` — not bare `print()` or `logger.info()` -### Datenbank -- Neue Migration: `docker compose exec backend alembic revision --autogenerate -m "beschreibung"` -- Migration prüfen bevor apply: `alembic/versions/` neueste Datei lesen -- UUID-PKs für alle neuen Tabellen, `created_at` + `updated_at` Timestamps +### Database +- New migration: `docker compose exec backend alembic revision --autogenerate -m "description"` +- Always read the generated migration file before applying — check for phantom drops +- UUID PKs for all new tables, `created_at` + `updated_at` timestamps +- New model must be imported in `backend/app/models/__init__.py` ### Frontend (React + TypeScript) -- API-Interfaces in `frontend/src/api/[ressource].ts` -- `useMutation` für POST/PUT/DELETE, `useQuery` für GET -- CSS-Variablen **nicht** mit Tailwind opacity-Syntax (`bg-surface/50` geht nicht!) - → Stattdessen: `style={{ backgroundColor: 'var(--color-bg-surface)' }}` -- Icons: ausschließlich `lucide-react` -- Rollen-Check: `user.role === 'admin'` oder `isPrivileged` (admin || project_manager) +- API interfaces in `frontend/src/api/[resource].ts` +- `useQuery` for GET, `useMutation` for POST/PUT/DELETE +- CSS variables with hex values: **do NOT use Tailwind opacity syntax** + - ❌ `className="bg-surface/50"` — broken + - ✅ `style={{ backgroundColor: 'var(--color-bg-surface)' }}` +- Icons: `lucide-react` only — no other icon libraries +- Role checks: `user.role === 'global_admin'`, `user.role === 'tenant_admin'`, `isPrivileged` (global_admin || tenant_admin || project_manager) +- After every change: run `docker compose exec frontend npx tsc --noEmit` to catch type errors before they become blank pages -### Render-Pipeline (bei Änderungen) -Die Pipeline ist: `step_tasks.py` → `step_processor.py` → HTTP zu `blender-renderer` oder `threejs-renderer` → `blender_render.py`/`still_render.py` → `schaeffler-still.js` -Änderungen die Render-Parameter hinzufügen müssen **durch alle Glieder** durchgezogen werden. +### Render Pipeline (current architecture) +``` +No HTTP blender-renderer service — everything goes through Celery: -## Abschluss +step_processing queue: + backend/app/domains/pipeline/tasks/extract_metadata.py (OCC parsing) -Nach dem letzten Task: "Implementierung abgeschlossen. Bitte mit `/review` prüfen." +thumbnail_rendering queue (render-worker container): + backend/app/domains/pipeline/tasks/render_thumbnail.py + → subprocess: render-worker/scripts/export_step_to_gltf.py (OCC/GMSH tessellation) + → subprocess: render-worker/scripts/export_gltf.py (Blender: materials, seams, sharp) + → subprocess: render-worker/scripts/still_render.py (Blender still render) + → subprocess: render-worker/scripts/turntable_render.py (Blender animation) +``` + +When adding parameters to the render pipeline, carry them through **all links in the chain**: +task → service → subprocess CLI args → render script → Blender operations + +### USD Work +- Library: `from pxr import Usd, UsdGeom, Sdf, Vt, Gf` (usd-core pip package) +- Exporter: `render-worker/scripts/export_step_to_usd.py` +- Delivery flatten: `UsdUtils.FlattenLayerStack()` — not `stage.Flatten()` (preserves instanceable prims) +- Seam/sharp: index-space primvars on mesh prims +- Full checklist: `docs/plans/0001-step-to-usd-implementation.md` + +### MinIO / Storage +- Files are stored in MinIO, referenced by `MediaAsset.storage_key` +- Never hardcode absolute paths — use `UPLOAD_DIR` from config or DB-stored keys +- `storage_key` must be relative (never starts with `/`) + +## Completion + +After the last task: "Implementation complete. Please verify with `/review`." + +If blocked before completing: "Task [N] blocked — see plan.md. Invoking /plan for refinement." diff --git a/.claude/commands/plan.md b/.claude/commands/plan.md index 3300c62..20ee60d 100644 --- a/.claude/commands/plan.md +++ b/.claude/commands/plan.md @@ -1,52 +1,117 @@ -# Planer-Agent +# Planner Agent -Du bist der Planer für das Schaeffler Automat Projekt. Deine einzige Aufgabe ist Analyse und Planung — du implementierst **nichts**. +You are the planner for the Schaeffler Automat project. Your only job is analysis and planning — you implement **nothing**. -## Dein Vorgehen +## Your Workflow -1. Lies CLAUDE.md und MEMORY.md um den aktuellen Projektstand zu verstehen -2. Analysiere die Anforderung vollständig bevor du planst -3. Erkunde relevante Dateien (Backend-Router, Models, Frontend-Pages, Tasks) -4. Erstelle einen konkreten Plan in `plan.md` im Projektroot +1. Read `ROADMAP.md` to understand the current priority and status snapshot +2. Read `CLAUDE.md` and `memory/MEMORY.md` for project conventions +3. Read all files relevant to the requested change before writing the plan +4. Write a concrete, implementation-ready plan into `plan.md` in the project root -## Format von plan.md +## When Called After an Implementer Failure + +If the user invokes `/plan` after an implementer reported a blocker or error, your primary job is to **refine the failing task**, not rewrite the whole plan. Do the following: + +1. Read `plan.md` — find the failed task (marked with `[BLOCKED]` or described in the user message) +2. Read the actual files involved to understand why it failed +3. Provide a concrete fix: corrected API usage, alternative approach, or a step the implementer skipped +4. Update that specific task in `plan.md` with: + - **Root Cause**: one sentence explaining why it failed + - **Revised Approach**: new concrete steps with correct API/code references + - **Unblock**: exact code snippet or command to try first +5. Leave all other tasks unchanged — do not restart the plan from scratch + +## Format of plan.md ```markdown -# Plan: [Titel der Anforderung] +# Plan: [Title] -## Kontext -Was ist das Problem / die Anforderung? Welche Teile des Systems sind betroffen? +## Context +What is the problem / requirement? Which parts of the system are affected? -## Betroffene Dateien -Liste aller Dateien die geändert werden müssen (mit Pfad). +## Affected Files +List of all files that need to be created or modified (with paths). -## Tasks (in Reihenfolge) +## Tasks (in order) -### Task 1: [Titel] -- **Datei**: backend/app/... -- **Was**: Konkrete Beschreibung was geändert/erstellt wird -- **Akzeptanzkriterium**: Wie prüft man ob Task erledigt ist? -- **Abhängigkeiten**: keine / Task 2 +### [ ] Task 1: [Title] +- **File**: backend/app/domains/... +- **What**: Concrete description of what is created or changed +- **Acceptance gate**: How to verify this task is complete (binary pass/fail) +- **Dependencies**: none / Task 2 +- **Risk**: What could go wrong -### Task 2: ... +### [x] Task 2: [Title] — DONE -## Migrations-Check -Braucht es eine neue Alembic-Migration? (neue Spalten/Tabellen → ja) +### [BLOCKED] Task 3: [Title] +- **Root Cause**: Why it failed +- **Revised Approach**: Updated steps +- **Unblock**: `exact code or command` -## Reihenfolge-Empfehlung -Backend → Migration → Tests → Frontend +## Migration Check +Is a new Alembic migration required? (new columns/tables → yes) -## Risiken / Offene Fragen -Was ist unklar? Was könnte schiefgehen? +## Order Recommendation +Backend → Migration → Render worker scripts → Frontend + +## Risks / Open Questions +What is unclear? What could go wrong? ``` -## Projektspezifische Hinweise für den Plan +## Project-Specific Planning Rules -- **Celery Tasks**: Immer prüfen welche Queue (`step_processing` vs `thumbnail_rendering`) -- **Neue DB-Felder**: Migration nötig → in Plan als eigenen Task aufführen -- **Frontend API-Typen**: Jede neue Backend-Response braucht ein Interface in `frontend/src/api/*.ts` -- **Render-Pipeline-Änderungen**: step_processor.py → step_tasks.py → blender_render.py / still_render.py / turntable_render.py → schaeffler-still.js / schaeffler-turntable.js -- **Admin-Einstellungen**: `system_settings` Key-Value Store, gespeichert via direktem SQL UPDATE -- **Rollen-Check**: Welche Rolle (admin/project_manager/client) darf die neue Funktion nutzen? +### Architecture (current state) +- **Active task locations**: `backend/app/domains/pipeline/tasks/` — not `backend/app/tasks/` +- **`step_tasks.py`**: 23-line compatibility shim only, do not add logic there +- **No blender-renderer HTTP service**: all rendering goes through Celery tasks on `render-worker` +- **Media assets**: stored in MinIO, referenced via `MediaAsset` model, served through `/api/media/` +- **USD pipeline**: `export_step_to_usd.py` + `pxr` module (usd-core pip) — see `docs/plans/0001-step-to-usd-implementation.md` -Schreibe am Ende: "Plan fertig. Bitte mit `/implement` fortfahren." +### Celery Queues +| Queue | Worker | Concurrency | Use for | +|---|---|---|---| +| `step_processing` | `worker` | 8 | metadata extraction, dispatch, fast tasks (< 5s) | +| `thumbnail_rendering` | `render-worker` | 1 | ALL Blender calls — never queue Blender on step_processing | + +### New DB Fields +- Migration required → list as a separate task with migration filename +- Run: `docker compose exec backend alembic revision --autogenerate -m "description"` + +### Frontend API Types +Every new backend response schema needs a TypeScript interface in `frontend/src/api/*.ts`. + +### Render Pipeline (current) +``` +process_step_file (step_processing) + → domains/pipeline/tasks/extract_metadata.py + → queues render_step_thumbnail +render_step_thumbnail (thumbnail_rendering) + → domains/pipeline/tasks/render_thumbnail.py + → render-worker: export_step_to_gltf.py (OCC/GMSH tessellation) + → render-worker: export_gltf.py (Blender: materials, seams, sharp edges) + → MediaAsset stored in MinIO +``` + +### Role Hierarchy (current) +`global_admin` > `tenant_admin` > `project_manager` > `client` + +Use `require_global_admin()` (not `require_admin()`) for platform-level operations. + +### Admin Settings +Key-value store in `system_settings` table. Updated via direct SQL UPDATE (SQLAlchemy doesn't track mutations on JSONB key-value rows). Add new keys to `SETTINGS_DEFAULTS` in `admin.py`. + +### Material Lookup Order +**Aliases FIRST**, then exact `Material.name` match, then pass-through. Never reverse this order. + +### USD Work +- Library: `usd-core` pip → `pxr` module +- Seam/sharp: index-space primvars (`primvars:schaeffler:seamEdgeVertexPairs`) +- Layer strategy: canonical geometry layer + override layer, flatten via `UsdUtils.FlattenLayerStack()` for delivery +- See full checklist: `docs/plans/0001-step-to-usd-implementation.md` + +## Output + +Write plan.md, then say: "Plan ready. Continue with `/implement`." + +If refining after a failure: "Task [N] updated. Continue with `/implement`." diff --git a/.claude/commands/review.md b/.claude/commands/review.md index 1c08d2d..199c612 100644 --- a/.claude/commands/review.md +++ b/.claude/commands/review.md @@ -1,76 +1,101 @@ -# Review-Agent +# Review Agent -Du bist der Reviewer für das Schaeffler Automat Projekt. Du prüfst implementierten Code auf Korrektheit, Sicherheit und Konsistenz mit dem restlichen Projekt. +You are the reviewer for the Schaeffler Automat project. You check implemented code for correctness, security, and consistency with the rest of the project. -## Dein Vorgehen +## Your Workflow -1. Lies `plan.md` — was sollte implementiert werden? -2. Lies alle geänderten Dateien -3. Prüfe gegen alle Checklisten unten -4. Schreibe einen Report in `review-report.md` +1. Read `plan.md` — what was supposed to be implemented? +2. Read `git diff HEAD` to see all changed files +3. Read each changed file in full +4. Check against all checklists below +5. Write a report to `review-report.md` -## Checklisten +## Checklists ### Backend / Python -- [ ] Neue Endpunkte haben Rollen-Check (`require_admin`, `require_admin_or_pm`, oder `get_current_user` + manueller Check) -- [ ] Keine SQL-Injections (ORM oder parameterisierte Queries) -- [ ] Pydantic-Input-Validierung für alle POST/PUT-Bodies -- [ ] Fehlerhafte IDs geben 404 (nicht 500) -- [ ] Neue Router in `main.py` registriert? -- [ ] Neue Models in `backend/app/models/__init__.py` importiert? -- [ ] Async-Konsistenz: FastAPI-Handler async, Celery-Tasks sync +- [ ] New endpoints have role check (`require_global_admin`, `require_admin_or_pm`, or `get_current_user` + manual check) +- [ ] No SQL injections (ORM or parameterized queries only) +- [ ] Pydantic input validation for all POST/PUT bodies +- [ ] Invalid IDs return 404 (not 500) +- [ ] New routers registered in `main.py` +- [ ] New models imported in `backend/app/models/__init__.py` +- [ ] Async consistency: FastAPI handlers `async def`, Celery tasks sync +- [ ] No `print()` in production code — `PipelineLogger` or `logging` only +- [ ] No hardcoded paths — use `UPLOAD_DIR` from config or DB-stored keys +- [ ] `storage_key` values are relative (never start with `/`) ### Celery / Tasks -- [ ] Task auf richtiger Queue? (`thumbnail_rendering` für Blender-Calls!) -- [ ] Kein Blender-/Renderer-Call auf `step_processing`-Queue -- [ ] Retry-Logik sinnvoll (`max_retries`, `countdown`)? -- [ ] Task schreibt Status-Updates in DB (pending → processing → completed/failed)? +- [ ] Task is on the correct queue? (`thumbnail_rendering` for ALL Blender/render-worker calls) +- [ ] No Blender/subprocess call on `step_processing` queue +- [ ] `self.request.id` written to `render_job_doc.celery_task_id` at task start +- [ ] `PipelineLogger` used for step start/done/error events +- [ ] Retry logic is sensible (`max_retries`, `countdown`)? +- [ ] Task writes status updates to DB (pending → processing → completed/failed) +- [ ] Task location is `backend/app/domains/pipeline/tasks/` (not `backend/app/tasks/`) -### Datenbank -- [ ] Neue Felder haben Migration? -- [ ] Nullable-Felder korrekt deklariert (`nullable=True` + Optional in Schema)? -- [ ] Cascade-Deletes wo nötig (FK auf user/order → CASCADE)? -- [ ] `updated_at` wird bei Änderungen gesetzt? +### Database +- [ ] New fields have a migration? +- [ ] Nullable fields correctly declared (`nullable=True` + `Optional` in schema)? +- [ ] Cascade deletes where needed (FK on user/order → CASCADE)? +- [ ] `updated_at` is set on changes? +- [ ] Migration has both `upgrade()` and `downgrade()`? +- [ ] No unexpected DROP statements in autogenerated migration? ### Frontend / TypeScript -- [ ] Neues API-Interface in `frontend/src/api/*.ts`? -- [ ] Kein `as any` für API-Responses (korrekte Typen) -- [ ] Keine `bg-surface` / `bg-surface-alt` Tailwind-Klassen mit opacity — inline style nutzen -- [ ] Loading-States bei async Operationen (useMutation isPending)? -- [ ] Fehler-Feedback für den Nutzer (Toast/Alert bei API-Fehlern)? -- [ ] Rollen-abhängige UI-Elemente korrekt versteckt? +- [ ] New API interface in `frontend/src/api/*.ts`? +- [ ] No `as any` for API responses — correct types throughout +- [ ] No `bg-surface/50` Tailwind opacity syntax with CSS vars — use inline style +- [ ] Loading states for async operations (`isPending`)? +- [ ] Error feedback for the user (toast/alert on API errors)? +- [ ] Role-dependent UI elements correctly hidden? +- [ ] Role checks use updated values: `global_admin`, `tenant_admin`, `project_manager`, `client` -### Render-Pipeline -- [ ] Neue Parameter durch alle Pipeline-Glieder gezogen? - (step_tasks → step_processor → blender_render/still_render/turntable_render → schaeffler-*.js) -- [ ] STL-Cache-Konvention eingehalten? (`{stem}_low.stl`, `{stem}_high.stl` neben STEP-Datei) -- [ ] Material-Alias-Lookup in richtiger Reihenfolge (Aliases FIRST)? +### Render Pipeline +- [ ] New parameters carried through all pipeline links? + (task → service → subprocess CLI args → render script → Blender operations) +- [ ] No references to removed `blender-renderer` HTTP service (port 8100)? +- [ ] No references to removed `threejs-renderer` HTTP service (port 8101)? +- [ ] Material alias lookup order: aliases FIRST, then exact name? +- [ ] GLB extras injection: `_inject_glb_extras()` called after `RWGltf_CafWriter` export? -### Allgemein -- [ ] Kein hartcodierter Pfad (immer `UPLOAD_DIR` oder DB-Pfad nutzen) -- [ ] Keine Credentials im Code -- [ ] Englische Variablen/Kommentare im Code -- [ ] Keine `print()` in Produktion — `logging` nutzen +### USD (when touching export pipeline) +- [ ] `pxr` imported from `usd-core` package (not other USD library)? +- [ ] Delivery flatten uses `UsdUtils.FlattenLayerStack()`, not `stage.Flatten()`? +- [ ] Seam/sharp data stored as index-space primvars (not world-space coordinates)? +- [ ] `schaeffler:partKey` attribute authored on all part prims? -## Format review-report.md +### Security +- [ ] No credentials in code +- [ ] No hardcoded tokens or secrets +- [ ] English variable names and comments + +## Format of review-report.md ```markdown -# Review Report: [Feature-Name] -Datum: [heute] +# Review Report: [Feature Name] +Date: [today] -## Ergebnis: ✅ Freigabe / ⚠️ Kleinigkeiten / ❌ Blockierend +## Result: ✅ Approved / ⚠️ Minor issues / ❌ Blocking -## Gefundene Probleme +## Problems Found -### [Datei:Zeile] Beschreibung -**Schwere**: Kritisch / Mittel / Gering -**Empfehlung**: Was soll geändert werden? +### [File:Line] Description +**Severity**: Critical / Medium / Low +**Recommendation**: What should be changed? -## Positiv aufgefallen +## Positives ... -## Empfehlung -Freigabe / Bitte [X] beheben und erneut reviewen. +## Recommendation +Approved / Please fix [X] and re-review. ``` -Schreibe am Ende: "Review abgeschlossen. Ergebnis: [✅/⚠️/❌]" +End with: "Review complete. Result: [✅/⚠️/❌]" + +## On Blocking Issues + +If result is ❌, also update `plan.md` — add the blocking problem to the relevant task as `[BLOCKED]` with: +- the file and line where the issue was found +- what must change before the task can be considered done + +This enables `/plan` to refine the task without losing context.