chore(agents): rewrite all agent definitions for current architecture
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 <noreply@anthropic.com>
This commit is contained in:
+93
-65
@@ -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!):
|
## Gate 1 — TypeScript Check (most important)
|
||||||
```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.
|
|
||||||
|
|
||||||
2. **Vite Build** (optional, langsamer):
|
```bash
|
||||||
```bash
|
docker compose exec frontend npx tsc --noEmit 2>&1
|
||||||
docker compose exec frontend npm run build 2>&1 | tail -20
|
```
|
||||||
```
|
|
||||||
|
|
||||||
3. **Tests**:
|
Catches missing imports, type errors, undefined variables. A failure here causes a blank page in the browser. **Always run this first.**
|
||||||
```bash
|
|
||||||
docker compose exec frontend npm test 2>&1 | tail -20
|
|
||||||
```
|
|
||||||
Hinweis: `npm run lint` existiert nicht — TypeScript-Check ersetzt es.
|
|
||||||
|
|
||||||
## Backend Quality Gates
|
Expected: zero errors.
|
||||||
|
|
||||||
4. **Python Import-Check**:
|
## Gate 2 — Backend 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.
|
|
||||||
|
|
||||||
5. **Backend Startup-Logs**:
|
```bash
|
||||||
```bash
|
docker compose exec backend python -c "from app.main import app; print('OK')" 2>&1
|
||||||
docker compose logs backend 2>&1 | tail -20
|
```
|
||||||
```
|
|
||||||
Auf `Application startup complete` prüfen, keine Exceptions.
|
|
||||||
|
|
||||||
## Daten-Integrität Gates
|
Verifies all Python imports resolve. Catches `ImportError` before they reach production.
|
||||||
|
|
||||||
7. **Keine absoluten storage_key-Pfade in media_assets**:
|
Expected: prints `OK`.
|
||||||
```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`
|
|
||||||
|
|
||||||
8. **Config-Attribute prüfen** (nach config.py-Änderungen):
|
## Gate 3 — Backend Startup
|
||||||
```bash
|
|
||||||
docker compose exec backend python -c "from app.config import settings; print('upload_dir:', settings.upload_dir)"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Übersicht
|
```bash
|
||||||
|
docker compose logs backend 2>&1 | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
9. **Geänderte Dateien**:
|
Check for `Application startup complete` and no exceptions.
|
||||||
```bash
|
|
||||||
git diff --stat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ergebnis
|
## Gate 4 — Migration State
|
||||||
|
|
||||||
Wenn alle Gates grün: committe mit passendem Conventional-Commit-Message.
|
```bash
|
||||||
Wenn ein Gate rot: Problem zuerst beheben, dann erneut prüfen.
|
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.
|
Expected: shows current revision with `(head)`.
|
||||||
- `npm run lint` existiert in diesem Projekt nicht (kein ESLint konfiguriert).
|
|
||||||
- `npm test` prüft nur Test-Dateien, nicht Production-Komponenten auf Importfehler.
|
## Gate 5 — Render Worker Health
|
||||||
- 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).
|
```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 <noreply@anthropic.com>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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
|
||||||
|
|||||||
@@ -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
|
Latest migration: `059` (seed top global position). Next sequential number: `060`.
|
||||||
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
|
|
||||||
|
|
||||||
## Migrations-Workflow
|
Naming convention: `backend/alembic/versions/{NNN}_{description}.py`
|
||||||
|
|
||||||
|
## Migration Workflow
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Aktuellen Stand prüfen
|
# 1. Check current state
|
||||||
docker compose exec backend alembic current
|
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"
|
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
|
cat backend/alembic/versions/[newest_file].py
|
||||||
|
|
||||||
# 4. Migration anwenden
|
# 4. Apply migration
|
||||||
docker compose exec backend alembic upgrade head
|
docker compose exec backend alembic upgrade head
|
||||||
|
|
||||||
# 5. Verifizieren
|
# 5. Verify schema
|
||||||
docker compose exec postgres psql -U schaeffler -d schaeffler -c "\d tablename"
|
docker compose exec postgres psql -U schaeffler -d schaeffler -c "\d tablename"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Migration-Datei Checklisten
|
## Pre-Apply Checklist
|
||||||
|
|
||||||
### Vor dem Apply prüfen:
|
Before running `alembic upgrade head`:
|
||||||
- [ ] `upgrade()` und `downgrade()` beide vorhanden und korrekt
|
- [ ] `upgrade()` and `downgrade()` both present and correct
|
||||||
- [ ] Neue Spalten haben `nullable=True` ODER einen `server_default`
|
- [ ] New columns have `nullable=True` OR a `server_default`
|
||||||
- [ ] FK-Constraints haben `ondelete='CASCADE'` wo sinnvoll
|
- [ ] FK constraints have `ondelete='CASCADE'` where appropriate
|
||||||
- [ ] Unique-Constraints korrekt (ggf. partial index mit `postgresql_where`)
|
- [ ] No unintended DROP statements (autogenerate sometimes detects phantom changes)
|
||||||
- [ ] Keine unbeabsichtigten DROP-Statements (autogenerate erkennt manchmal Phantom-Änderungen)
|
- [ ] `down_revision` points to the correct predecessor
|
||||||
- [ ] `down_revision` zeigt auf korrekten Vorgänger
|
- [ ] Enum additions use `IF NOT EXISTS` to be idempotent
|
||||||
|
|
||||||
### Häufige Muster im Projekt
|
## Common Patterns
|
||||||
|
|
||||||
**Neue optionale Spalte:**
|
**New optional column:**
|
||||||
```python
|
```python
|
||||||
op.add_column('tablename', sa.Column('new_field', sa.String(200), nullable=True))
|
op.add_column('tablename', sa.Column('new_field', sa.String(200), nullable=True))
|
||||||
```
|
```
|
||||||
|
|
||||||
**Neue Spalte mit Default:**
|
**New column with default:**
|
||||||
```python
|
```python
|
||||||
op.add_column('tablename', sa.Column('is_active', sa.Boolean(), nullable=False, server_default='true'))
|
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
|
```python
|
||||||
op.create_index('uq_products_pim_id', 'products', ['pim_id'],
|
op.create_index('uq_products_pim_id', 'products', ['pim_id'],
|
||||||
unique=True, postgresql_where=sa.text('pim_id IS NOT NULL'))
|
unique=True, postgresql_where=sa.text('pim_id IS NOT NULL'))
|
||||||
```
|
```
|
||||||
|
|
||||||
**Enum-Wert hinzufügen (PostgreSQL-spezifisch):**
|
**Add enum value (PostgreSQL):**
|
||||||
```python
|
```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
|
```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
|
```python
|
||||||
op.add_column('tablename', sa.Column('parent_id', postgresql.UUID(as_uuid=True),
|
# At the end of upgrade():
|
||||||
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:
|
|
||||||
op.execute("""
|
op.execute("""
|
||||||
UPDATE tablename
|
UPDATE tablename
|
||||||
SET new_field = existing_field
|
SET new_field = existing_field
|
||||||
@@ -86,28 +95,40 @@ op.execute("""
|
|||||||
""")
|
""")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rollback bei Problemen
|
## Rollback
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Eine Migration zurück
|
# One step back
|
||||||
docker compose exec backend alembic downgrade -1
|
docker compose exec backend alembic downgrade -1
|
||||||
|
|
||||||
# Zu spezifischer Revision
|
# To specific revision
|
||||||
docker compose exec backend alembic downgrade [revision_id]
|
docker compose exec backend alembic downgrade [revision_id]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Modell-Checkliste nach Migration
|
## Post-Migration Checklist
|
||||||
|
|
||||||
Nach der Migration das entsprechende SQLAlchemy-Model prüfen:
|
After a successful migration, verify the corresponding SQLAlchemy model:
|
||||||
- [ ] Neue Spalte als Python-Attribut im Model (mit korrektem Typ + `nullable`)
|
- [ ] New column as Python attribute in model (correct type + `nullable`)
|
||||||
- [ ] Neue Relationship mit `back_populates` auf beiden Seiten
|
- [ ] New relationship with `back_populates` on both sides
|
||||||
- [ ] Model in `backend/app/models/__init__.py` importiert (bei neuem Model)
|
- [ ] Model imported in `backend/app/models/__init__.py` (for new models)
|
||||||
- [ ] Pydantic-Schema in `backend/app/schemas/` aktualisiert
|
- [ ] Pydantic schema in `backend/app/domains/<domain>/schemas.py` updated
|
||||||
- [ ] `Optional[...]` in Schema wenn Spalte nullable
|
- [ ] `Optional[...]` in schema if column is nullable
|
||||||
|
|
||||||
## Abschluss
|
## Planned Migrations (from ROADMAP.md)
|
||||||
|
|
||||||
Berichte:
|
| Number | Description | Priority |
|
||||||
- Welche Migration erstellt wurde (Dateiname + Revision-ID)
|
|---|---|---|
|
||||||
- Was `alembic current` nach apply zeigt
|
| 060 | `usd_master` enum value in `mediaassettype` | P2 |
|
||||||
- Ob Backfill-Daten korrekt gesetzt wurden
|
| 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
|
||||||
|
|||||||
+144
-113
@@ -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
|
## Architecture Overview (current)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Upload STEP
|
Upload STEP
|
||||||
↓
|
↓
|
||||||
process_step_file (step_processing, concurrency=8)
|
process_step_file [queue: step_processing, worker container]
|
||||||
↓ extract_cad_metadata()
|
→ backend/app/domains/pipeline/tasks/extract_metadata.py
|
||||||
↓ parsed_objects gespeichert
|
→ parses STEP objects, stores parsed_objects
|
||||||
↓ queut →
|
→ queues render_step_thumbnail
|
||||||
render_step_thumbnail (thumbnail_rendering, concurrency=1)
|
|
||||||
↓ regenerate_cad_thumbnail()
|
render_step_thumbnail [queue: thumbnail_rendering, render-worker container]
|
||||||
↓ part_colors → blender-renderer:8100/render
|
→ backend/app/domains/pipeline/tasks/render_thumbnail.py
|
||||||
↓ STL-Cache erstellt: {stem}_low.stl
|
→ subprocess: export_step_to_gltf.py (OCC/GMSH tessellation → geometry GLB)
|
||||||
↓ Status: completed / failed
|
→ subprocess: export_gltf.py (Blender: materials, seams, sharp edges → production GLB)
|
||||||
↓ _auto_populate_materials_for_cad()
|
→ 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?]
|
## Step 2: Check Logs
|
||||||
Fix: [Was wurde geändert / muss geändert werden?]
|
|
||||||
Prävention: [Wie vermeidet man das in Zukunft?]
|
```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?]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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)
|
### Header Detection
|
||||||
- Sucht in den ersten 5 Zeilen nach `"Ebene1"` in einer beliebigen Spalte
|
- Searches rows 0–4 for `"Ebene1"` in any column
|
||||||
- Baut dynamische `column_map` über `HEADER_FIELD_MAP` (normalisierte Header-Texte → Feldnamen)
|
- Builds a dynamic `column_map` from `HEADER_FIELD_MAP` (normalized header text → field name)
|
||||||
- Altes Format: "Ebene1" in Spalte 0 → Komponenten ab Spalte 11
|
- Old format: `"Ebene1"` in column 0 → components from column 11
|
||||||
- Neues Format: "Arbeitspaket" in Spalte 0, "Ebene1" in Spalte 1 → Komponenten ab Spalte 12
|
- 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`
|
`TRB`, `Kugellager`, `CRB`, `Gleitlager`, `SRB_TORB`, `Linear_schiene`, `Anschlagplatten`
|
||||||
|
|
||||||
### Wichtige ParsedRow-Felder
|
### Key ParsedRow Fields
|
||||||
- `pim_id`, `produkt_baureihe`, `gewaehltes_produkt`
|
- `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`
|
- `kategorie`, `category_key`, `arbeitspaket`
|
||||||
- `gewuenschte_bildnummer` — Varianten-Differenziator
|
- `gewuenschte_bildnummer` — variant differentiator
|
||||||
- `cad_part_materials` — Rohes Material-Mapping für Render
|
- `cad_part_materials` — raw material mapping dict `{part_name: material_name}`
|
||||||
- `components` — Teileliste mit Anzahl + Materialien
|
- `components` — component list with quantity + materials
|
||||||
|
|
||||||
### Material-Mapping Sheet
|
### Material Mapping Sheet
|
||||||
`_parse_material_mapping(wb)` — liest separates Sheet "Materialmapping":
|
`_parse_material_mapping(wb)` reads the "Materialmapping" sheet:
|
||||||
- Gibt `[{display_name, render_name}]` zurück
|
- Returns `[{display_name, render_name}]`
|
||||||
- Wird beim Upload als Material-Aliases geseedet
|
- Seeded as Material aliases on upload
|
||||||
|
|
||||||
## Diagnose bei Import-Problemen
|
## Diagnose Import Problems
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Logs des Upload-Endpunkts
|
# Backend upload logs
|
||||||
docker compose logs -f backend | grep "excel\|upload\|import"
|
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 "
|
docker compose exec backend python3 -c "
|
||||||
from app.services.excel_parser import parse_excel_file
|
from app.services.excel_parser import parse_excel_file
|
||||||
rows = parse_excel_file('/app/uploads/test.xlsx')
|
rows = parse_excel_file('/app/uploads/test.xlsx')
|
||||||
|
print(f'Rows parsed: {len(rows)}')
|
||||||
for r in rows[:3]:
|
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 |
|
| All rows empty | Header detection fails | Look for `"Ebene1"` in rows 0–4 |
|
||||||
| Falsches Feld gemappt | Header-Text stimmt nicht mit `HEADER_FIELD_MAP` überein | Header-Normalisierung prüfen (strip + lower) |
|
| Wrong field mapped | Header text doesn't match `HEADER_FIELD_MAP` | Check normalization (strip + lower) |
|
||||||
| Kategorie nicht erkannt | `_detect_row_category()` findet kein Match | `kategorie`-Spalte Rohwert prüfen |
|
| Category not recognized | `_detect_row_category()` finds no match | Print raw `kategorie` column value |
|
||||||
| Material-Aliases nicht geseedet | Materialmapping-Sheet fehlt oder anders benannt | Sheet-Namen im Excel prüfen |
|
| Material aliases not seeded | "Materialmapping" sheet missing or renamed | Check sheet names in Excel file |
|
||||||
| Varianten fehlen | `gewuenschte_bildnummer` nicht unterschiedlich | Rohdaten prüfen |
|
| 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
|
```python
|
||||||
HEADER_FIELD_MAP = {
|
HEADER_FIELD_MAP = {
|
||||||
...
|
...
|
||||||
"neuer header text": "neues_feld",
|
"new header text": "new_field_name",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **`ParsedRow`-Dataclass** erweitern:
|
2. **Extend `ParsedRow` dataclass**:
|
||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class ParsedRow:
|
class ParsedRow:
|
||||||
...
|
...
|
||||||
neues_feld: str | None = None
|
new_field_name: str | None = None
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Verwendung in Import-Logik** (`uploads.py` oder `product_service.py`):
|
3. **Decide where to store it** (`uploads.py` or `product_service.py`):
|
||||||
- Wo wird das Feld gespeichert? Neues DB-Feld? Oder in `components` JSONB?
|
- New DB column → use `/db-migrate` agent
|
||||||
- Migration nötig? → `/db-migrate` Agent nutzen
|
- 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
|
1. Extend category regex in `_detect_row_category()`
|
||||||
2. `CATEGORY_KEYS` dict erweitern
|
2. Extend `CATEGORY_KEYS` dict
|
||||||
3. Falls spezifische Spalten-Logik: in `_parse_row_components()` behandeln
|
3. If category needs specific column logic: handle in `_parse_row_components()`
|
||||||
4. `compatible_categories` auf betroffenen `OutputType`-Einträgen in der DB setzen
|
4. Set `compatible_categories` on affected `OutputType` entries in the DB
|
||||||
|
|
||||||
## Test-Workflow
|
## Full Test Workflow
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Einzelne Excel-Datei testen
|
|
||||||
docker compose exec backend python3 -c "
|
docker compose exec backend python3 -c "
|
||||||
import json
|
import json
|
||||||
from app.services.excel_parser import parse_excel_file
|
from app.services.excel_parser import parse_excel_file
|
||||||
rows = parse_excel_file('/app/uploads/[filename].xlsx')
|
rows = parse_excel_file('/app/uploads/[filename].xlsx')
|
||||||
print(f'Rows: {len(rows)}')
|
print(f'Total rows: {len(rows)}')
|
||||||
for r in rows:
|
for r in rows:
|
||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
'pim_id': r.pim_id,
|
'pim_id': r.pim_id,
|
||||||
'produkt_baureihe': r.produkt_baureihe,
|
'produkt_baureihe': r.produkt_baureihe,
|
||||||
'category_key': r.category_key,
|
'category_key': r.category_key,
|
||||||
'name_cad_modell': r.name_cad_modell,
|
'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))
|
}, 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.
|
||||||
|
|||||||
+137
-111
@@ -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)
|
- React 18, TypeScript, Vite (port 5173, hot-reload active)
|
||||||
- Tailwind CSS (mit CSS-Variablen für Theming)
|
- Tailwind CSS (with CSS variables for theming)
|
||||||
- `@tanstack/react-query` (useQuery, useMutation)
|
- `@tanstack/react-query` (`useQuery`, `useMutation`)
|
||||||
- `axios` (via `frontend/src/api/client.ts`)
|
- `axios` via `frontend/src/api/client.ts` (includes X-Tenant-ID interceptor)
|
||||||
- `lucide-react` (Icons — ausschließlich diese Library)
|
- `lucide-react` — the only allowed icon library
|
||||||
- React Router v6
|
- React Router v6
|
||||||
|
|
||||||
## Projektstruktur Frontend
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
frontend/src/
|
frontend/src/
|
||||||
├── api/ # API-Client-Funktionen
|
├── api/
|
||||||
│ ├── client.ts # Axios-Instanz mit Auth-Interceptor
|
│ ├── client.ts # axios instance with auth + tenant interceptors
|
||||||
│ ├── auth.ts # Login, User-Info
|
│ ├── auth.ts # login, user info
|
||||||
│ ├── orders.ts # Auftrags-CRUD
|
│ ├── cad.ts # CAD/STEP operations, part-materials, scene manifest
|
||||||
│ ├── products.ts # Produkte + Varianten
|
│ ├── media.ts # MediaAsset queries
|
||||||
│ ├── cad.ts # CAD/STEP-Operationen
|
│ ├── 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/
|
├── components/
|
||||||
│ ├── shared/ # Wiederverwendbare Komponenten
|
│ ├── cad/
|
||||||
│ └── ... # Feature-Komponenten
|
│ │ ├── ThreeDViewer.tsx # Three.js GLB viewer with part picking
|
||||||
├── pages/ # Seitenkomponenten (je Route eine Datei)
|
│ │ ├── InlineCadViewer.tsx # lightweight inline viewer
|
||||||
├── App.tsx # Router + Auth-Context
|
│ │ ├── MaterialPanel.tsx # per-part material assignment panel
|
||||||
└── main.tsx
|
│ │ └── 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
|
```typescript
|
||||||
// Pattern für neue API-Datei
|
// ❌ BROKEN — produces transparent or invisible element
|
||||||
|
<div className="bg-surface/50 text-surface-alt">
|
||||||
|
|
||||||
|
// ✅ CORRECT — inline style for CSS variable colors
|
||||||
|
<div style={{ backgroundColor: 'var(--color-bg-surface)' }}>
|
||||||
|
<div style={{ color: 'var(--color-text-primary)' }}>
|
||||||
|
|
||||||
|
// Normal Tailwind classes without CSS vars work fine:
|
||||||
|
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 shadow">
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 && <button>Manage all tenants</button>}
|
||||||
|
{isAdmin && <button>Change settings</button>}
|
||||||
|
{isPrivileged && <button>Trigger render</button>}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Client Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/src/api/my-resource.ts
|
||||||
import api from './client'
|
import api from './client'
|
||||||
|
|
||||||
export interface MyResource {
|
export interface MyResource {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
optional_field?: string // Backend nullable → optional hier
|
optional_field?: string // Backend nullable → optional here
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMyResource(id: string): Promise<MyResource> {
|
export async function getMyResource(id: string): Promise<MyResource> {
|
||||||
@@ -49,129 +92,112 @@ export async function getMyResource(id: string): Promise<MyResource> {
|
|||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createMyResource(data: Partial<MyResource>): Promise<MyResource> {
|
export async function updateMyResource(id: string, data: Partial<MyResource>): Promise<MyResource> {
|
||||||
const res = await api.post<MyResource>('/my-resource', data)
|
const res = await api.put<MyResource>(`/my-resource/${id}`, data)
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### useQuery / useMutation Pattern
|
## useQuery / useMutation Pattern
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Query (GET)
|
// GET
|
||||||
const { data, isLoading, error, refetch } = useQuery({
|
const { data, isLoading, error, refetch } = useQuery({
|
||||||
queryKey: ['my-resource', id],
|
queryKey: ['my-resource', id],
|
||||||
queryFn: () => getMyResource(id),
|
queryFn: () => getMyResource(id),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mutation (POST/PUT/DELETE)
|
// POST/PUT/DELETE
|
||||||
const createMut = useMutation({
|
const updateMut = useMutation({
|
||||||
mutationFn: createMyResource,
|
mutationFn: (data: Partial<MyResource>) => updateMyResource(id, data),
|
||||||
onSuccess: (data) => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['my-resource'] })
|
queryClient.invalidateQueries({ queryKey: ['my-resource', id] })
|
||||||
// ggf. Toast/Feedback
|
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
// Fehler-Feedback
|
// Show user feedback
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Aufruf:
|
// Loading state:
|
||||||
createMut.mutate({ name: 'test' })
|
{updateMut.isPending && <RefreshCw className="w-4 h-4 animate-spin" />}
|
||||||
// Ladezustand: createMut.isPending
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### CSS / Tailwind — WICHTIG
|
## Common UI Patterns
|
||||||
|
|
||||||
|
### Loading / Error states
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ FALSCH — CSS-Variablen mit Hex-Werten + Tailwind opacity = kaputt
|
if (isLoading) return <div className="flex justify-center p-8"><RefreshCw className="w-6 h-6 animate-spin" /></div>
|
||||||
<div className="bg-surface/50 bg-surface-alt">
|
if (error) return <div className="text-red-500 p-4">Error loading data</div>
|
||||||
|
if (!data?.length) return <div className="text-gray-400 p-8 text-center">No items yet</div>
|
||||||
// ✅ RICHTIG — inline style für CSS-Variablen
|
|
||||||
<div style={{ backgroundColor: 'var(--color-bg-surface)' }}>
|
|
||||||
<div style={{ backgroundColor: 'var(--color-bg-app)' }}>
|
|
||||||
|
|
||||||
// Normale Tailwind-Klassen ohne CSS-Variablen funktionieren normal:
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-4">
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rollen und Berechtigungen
|
### Status badge
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Aus Auth-Context
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
const { user } = useAuth()
|
pending: 'bg-yellow-100 text-yellow-800',
|
||||||
const isAdmin = user?.role === 'admin'
|
|
||||||
const isPrivileged = user?.role === 'admin' || user?.role === 'project_manager'
|
|
||||||
|
|
||||||
// Elemente nur für Admins/PMs
|
|
||||||
{isPrivileged && <button>Render dispatchen</button>}
|
|
||||||
{isAdmin && <button>Einstellung ändern</button>}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Icons (ausschließlich lucide-react)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { RefreshCw, Download, Trash2, Plus, ChevronRight, AlertCircle } from 'lucide-react'
|
|
||||||
|
|
||||||
// Verwendung
|
|
||||||
<RefreshCw className="w-4 h-4" />
|
|
||||||
<RefreshCw className="w-4 h-4 animate-spin" /> // Loading-State
|
|
||||||
```
|
|
||||||
|
|
||||||
### Neue Seite anlegen
|
|
||||||
|
|
||||||
1. Datei in `frontend/src/pages/MyPage.tsx` erstellen
|
|
||||||
2. Route in `App.tsx` eintragen:
|
|
||||||
```typescript
|
|
||||||
<Route path="/my-page" element={<MyPage />} />
|
|
||||||
```
|
|
||||||
3. Navigation in Sidebar (`components/Sidebar.tsx`) hinzufügen (falls nötig)
|
|
||||||
|
|
||||||
## Häufige UI-Patterns im Projekt
|
|
||||||
|
|
||||||
### Ladezustand
|
|
||||||
```typescript
|
|
||||||
if (isLoading) return <div className="flex justify-center p-8"><RefreshCw className="animate-spin" /></div>
|
|
||||||
if (error) return <div className="text-red-500 p-4">Fehler beim Laden</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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',
|
|
||||||
processing: 'bg-blue-100 text-blue-800',
|
processing: 'bg-blue-100 text-blue-800',
|
||||||
completed: 'bg-green-100 text-green-800',
|
completed: 'bg-green-100 text-green-800',
|
||||||
failed: 'bg-red-100 text-red-800',
|
failed: 'bg-red-100 text-red-800',
|
||||||
}
|
}
|
||||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${statusColors[status]}`}>
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${STATUS_COLORS[status] ?? 'bg-gray-100 text-gray-600'}`}>
|
||||||
{status}
|
{status}
|
||||||
</span>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Thumbnail-Anzeige
|
### Authenticated thumbnail display
|
||||||
```typescript
|
```typescript
|
||||||
// Thumbnail lädt über authenticated axios, nicht direkt in <img src>
|
// Never use <img src={url}> for authenticated endpoints
|
||||||
|
// Use blob URL via axios instead:
|
||||||
import { fetchThumbnailBlob } from '../api/cad'
|
import { fetchThumbnailBlob } from '../api/cad'
|
||||||
|
|
||||||
|
const [thumbUrl, setThumbUrl] = useState<string>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cadFileId) return
|
if (!cadFileId) return
|
||||||
fetchThumbnailBlob(cadFileId).then(setThumbUrl)
|
let objectUrl: string
|
||||||
return () => { if (thumbUrl) URL.revokeObjectURL(thumbUrl) }
|
fetchThumbnailBlob(cadFileId).then(url => {
|
||||||
|
objectUrl = url
|
||||||
|
setThumbUrl(url)
|
||||||
|
})
|
||||||
|
return () => { if (objectUrl) URL.revokeObjectURL(objectUrl) }
|
||||||
}, [cadFileId])
|
}, [cadFileId])
|
||||||
|
|
||||||
<img src={thumbUrl} alt="Thumbnail" className="w-full h-full object-contain" />
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
||||||
|
<Route path="/my-page" element={<MyPage />} />
|
||||||
|
```
|
||||||
|
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'
|
||||||
|
|
||||||
|
<RefreshCw className="w-4 h-4" />
|
||||||
|
<RefreshCw className="w-4 h-4 animate-spin" /> // 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`."
|
||||||
|
|||||||
@@ -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
|
1. Read `plan.md` in the project root — find the first unchecked `[ ]` task
|
||||||
2. Lies alle betroffenen Dateien bevor du etwas änderst
|
2. Read all affected files before making any change
|
||||||
3. Implementiere **einen Task nach dem anderen** in der angegebenen Reihenfolge
|
3. Implement **one task at a time** in the listed order
|
||||||
4. Nach jedem Task: kurz prüfen ob es syntaktisch korrekt ist
|
4. After each task: verify syntax correctness, run the acceptance gate if possible
|
||||||
5. Markiere erledigte Tasks in plan.md mit `[x]`
|
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
|
```bash
|
||||||
# Backend-Änderungen live testen
|
# Watch backend logs
|
||||||
docker compose logs -f backend
|
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
|
||||||
docker compose logs -f worker-thumbnail
|
|
||||||
|
|
||||||
# Nach Änderungen an backend/ oder tasks/
|
# Rebuild after changes to backend/ or tasks/
|
||||||
docker compose up -d --build backend worker worker-thumbnail beat
|
docker compose up -d --build backend worker render-worker beat
|
||||||
|
|
||||||
# Neue Migration ausführen
|
# Run new migration
|
||||||
docker compose exec backend alembic upgrade head
|
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
|
### Python / Backend
|
||||||
- Async-Funktionen im FastAPI-Router (`async def`), sync-Wrapper für Celery
|
- FastAPI route handlers: `async def`
|
||||||
- Neue Router-Endpunkte in `backend/app/api/routers/` anlegen und in `main.py` registrieren
|
- Celery tasks: sync functions (no async), with `bind=True` for retry access via `self`
|
||||||
- Pydantic-Schemas in `backend/app/schemas/` — Input und Output trennen
|
- New routers: create in `backend/app/api/routers/` and register in `main.py`
|
||||||
- Direkte SQL-UPDATEs für `system_settings` (kein ORM-Mutation-Tracking)
|
- New domain services: in `backend/app/domains/<domain>/service.py`
|
||||||
- Material-Lookup: **Aliases zuerst**, dann exakter Name, dann Pass-through
|
- Pydantic schemas: in `backend/app/domains/<domain>/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
|
### Celery / Tasks
|
||||||
- `step_processing`-Queue: schnelle Tasks (< 5s), concurrency=8
|
- `step_processing` queue: fast tasks only (< 5s) — metadata extraction, dispatch
|
||||||
- `thumbnail_rendering`-Queue: Blender-Calls, **concurrency=1** — nur dort queuen!
|
- `thumbnail_rendering` queue: ALL Blender/render-worker calls — **never queue Blender on step_processing**
|
||||||
- Tasks mit `bind=True` für Retry-Zugriff via `self`
|
- Task location: `backend/app/domains/pipeline/tasks/` — not `backend/app/tasks/`
|
||||||
- Redis-Dedup-Lock bei Tasks die mehrfach getriggert werden können
|
- `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
|
### Database
|
||||||
- Neue Migration: `docker compose exec backend alembic revision --autogenerate -m "beschreibung"`
|
- New migration: `docker compose exec backend alembic revision --autogenerate -m "description"`
|
||||||
- Migration prüfen bevor apply: `alembic/versions/` neueste Datei lesen
|
- Always read the generated migration file before applying — check for phantom drops
|
||||||
- UUID-PKs für alle neuen Tabellen, `created_at` + `updated_at` Timestamps
|
- 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)
|
### Frontend (React + TypeScript)
|
||||||
- API-Interfaces in `frontend/src/api/[ressource].ts`
|
- API interfaces in `frontend/src/api/[resource].ts`
|
||||||
- `useMutation` für POST/PUT/DELETE, `useQuery` für GET
|
- `useQuery` for GET, `useMutation` for POST/PUT/DELETE
|
||||||
- CSS-Variablen **nicht** mit Tailwind opacity-Syntax (`bg-surface/50` geht nicht!)
|
- CSS variables with hex values: **do NOT use Tailwind opacity syntax**
|
||||||
→ Stattdessen: `style={{ backgroundColor: 'var(--color-bg-surface)' }}`
|
- ❌ `className="bg-surface/50"` — broken
|
||||||
- Icons: ausschließlich `lucide-react`
|
- ✅ `style={{ backgroundColor: 'var(--color-bg-surface)' }}`
|
||||||
- Rollen-Check: `user.role === 'admin'` oder `isPrivileged` (admin || project_manager)
|
- 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)
|
### Render Pipeline (current architecture)
|
||||||
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.
|
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."
|
||||||
|
|||||||
+99
-34
@@ -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
|
1. Read `ROADMAP.md` to understand the current priority and status snapshot
|
||||||
2. Analysiere die Anforderung vollständig bevor du planst
|
2. Read `CLAUDE.md` and `memory/MEMORY.md` for project conventions
|
||||||
3. Erkunde relevante Dateien (Backend-Router, Models, Frontend-Pages, Tasks)
|
3. Read all files relevant to the requested change before writing the plan
|
||||||
4. Erstelle einen konkreten Plan in `plan.md` im Projektroot
|
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
|
```markdown
|
||||||
# Plan: [Titel der Anforderung]
|
# Plan: [Title]
|
||||||
|
|
||||||
## Kontext
|
## Context
|
||||||
Was ist das Problem / die Anforderung? Welche Teile des Systems sind betroffen?
|
What is the problem / requirement? Which parts of the system are affected?
|
||||||
|
|
||||||
## Betroffene Dateien
|
## Affected Files
|
||||||
Liste aller Dateien die geändert werden müssen (mit Pfad).
|
List of all files that need to be created or modified (with paths).
|
||||||
|
|
||||||
## Tasks (in Reihenfolge)
|
## Tasks (in order)
|
||||||
|
|
||||||
### Task 1: [Titel]
|
### [ ] Task 1: [Title]
|
||||||
- **Datei**: backend/app/...
|
- **File**: backend/app/domains/...
|
||||||
- **Was**: Konkrete Beschreibung was geändert/erstellt wird
|
- **What**: Concrete description of what is created or changed
|
||||||
- **Akzeptanzkriterium**: Wie prüft man ob Task erledigt ist?
|
- **Acceptance gate**: How to verify this task is complete (binary pass/fail)
|
||||||
- **Abhängigkeiten**: keine / Task 2
|
- **Dependencies**: none / Task 2
|
||||||
|
- **Risk**: What could go wrong
|
||||||
|
|
||||||
### Task 2: ...
|
### [x] Task 2: [Title] — DONE
|
||||||
|
|
||||||
## Migrations-Check
|
### [BLOCKED] Task 3: [Title]
|
||||||
Braucht es eine neue Alembic-Migration? (neue Spalten/Tabellen → ja)
|
- **Root Cause**: Why it failed
|
||||||
|
- **Revised Approach**: Updated steps
|
||||||
|
- **Unblock**: `exact code or command`
|
||||||
|
|
||||||
## Reihenfolge-Empfehlung
|
## Migration Check
|
||||||
Backend → Migration → Tests → Frontend
|
Is a new Alembic migration required? (new columns/tables → yes)
|
||||||
|
|
||||||
## Risiken / Offene Fragen
|
## Order Recommendation
|
||||||
Was ist unklar? Was könnte schiefgehen?
|
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`)
|
### Architecture (current state)
|
||||||
- **Neue DB-Felder**: Migration nötig → in Plan als eigenen Task aufführen
|
- **Active task locations**: `backend/app/domains/pipeline/tasks/` — not `backend/app/tasks/`
|
||||||
- **Frontend API-Typen**: Jede neue Backend-Response braucht ein Interface in `frontend/src/api/*.ts`
|
- **`step_tasks.py`**: 23-line compatibility shim only, do not add logic there
|
||||||
- **Render-Pipeline-Änderungen**: step_processor.py → step_tasks.py → blender_render.py / still_render.py / turntable_render.py → schaeffler-still.js / schaeffler-turntable.js
|
- **No blender-renderer HTTP service**: all rendering goes through Celery tasks on `render-worker`
|
||||||
- **Admin-Einstellungen**: `system_settings` Key-Value Store, gespeichert via direktem SQL UPDATE
|
- **Media assets**: stored in MinIO, referenced via `MediaAsset` model, served through `/api/media/`
|
||||||
- **Rollen-Check**: Welche Rolle (admin/project_manager/client) darf die neue Funktion nutzen?
|
- **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`."
|
||||||
|
|||||||
+77
-52
@@ -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?
|
1. Read `plan.md` — what was supposed to be implemented?
|
||||||
2. Lies alle geänderten Dateien
|
2. Read `git diff HEAD` to see all changed files
|
||||||
3. Prüfe gegen alle Checklisten unten
|
3. Read each changed file in full
|
||||||
4. Schreibe einen Report in `review-report.md`
|
4. Check against all checklists below
|
||||||
|
5. Write a report to `review-report.md`
|
||||||
|
|
||||||
## Checklisten
|
## Checklists
|
||||||
|
|
||||||
### Backend / Python
|
### Backend / Python
|
||||||
- [ ] Neue Endpunkte haben Rollen-Check (`require_admin`, `require_admin_or_pm`, oder `get_current_user` + manueller Check)
|
- [ ] New endpoints have role check (`require_global_admin`, `require_admin_or_pm`, or `get_current_user` + manual check)
|
||||||
- [ ] Keine SQL-Injections (ORM oder parameterisierte Queries)
|
- [ ] No SQL injections (ORM or parameterized queries only)
|
||||||
- [ ] Pydantic-Input-Validierung für alle POST/PUT-Bodies
|
- [ ] Pydantic input validation for all POST/PUT bodies
|
||||||
- [ ] Fehlerhafte IDs geben 404 (nicht 500)
|
- [ ] Invalid IDs return 404 (not 500)
|
||||||
- [ ] Neue Router in `main.py` registriert?
|
- [ ] New routers registered in `main.py`
|
||||||
- [ ] Neue Models in `backend/app/models/__init__.py` importiert?
|
- [ ] New models imported in `backend/app/models/__init__.py`
|
||||||
- [ ] Async-Konsistenz: FastAPI-Handler async, Celery-Tasks sync
|
- [ ] 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
|
### Celery / Tasks
|
||||||
- [ ] Task auf richtiger Queue? (`thumbnail_rendering` für Blender-Calls!)
|
- [ ] Task is on the correct queue? (`thumbnail_rendering` for ALL Blender/render-worker calls)
|
||||||
- [ ] Kein Blender-/Renderer-Call auf `step_processing`-Queue
|
- [ ] No Blender/subprocess call on `step_processing` queue
|
||||||
- [ ] Retry-Logik sinnvoll (`max_retries`, `countdown`)?
|
- [ ] `self.request.id` written to `render_job_doc.celery_task_id` at task start
|
||||||
- [ ] Task schreibt Status-Updates in DB (pending → processing → completed/failed)?
|
- [ ] `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
|
### Database
|
||||||
- [ ] Neue Felder haben Migration?
|
- [ ] New fields have a migration?
|
||||||
- [ ] Nullable-Felder korrekt deklariert (`nullable=True` + Optional in Schema)?
|
- [ ] Nullable fields correctly declared (`nullable=True` + `Optional` in schema)?
|
||||||
- [ ] Cascade-Deletes wo nötig (FK auf user/order → CASCADE)?
|
- [ ] Cascade deletes where needed (FK on user/order → CASCADE)?
|
||||||
- [ ] `updated_at` wird bei Änderungen gesetzt?
|
- [ ] `updated_at` is set on changes?
|
||||||
|
- [ ] Migration has both `upgrade()` and `downgrade()`?
|
||||||
|
- [ ] No unexpected DROP statements in autogenerated migration?
|
||||||
|
|
||||||
### Frontend / TypeScript
|
### Frontend / TypeScript
|
||||||
- [ ] Neues API-Interface in `frontend/src/api/*.ts`?
|
- [ ] New API interface in `frontend/src/api/*.ts`?
|
||||||
- [ ] Kein `as any` für API-Responses (korrekte Typen)
|
- [ ] No `as any` for API responses — correct types throughout
|
||||||
- [ ] Keine `bg-surface` / `bg-surface-alt` Tailwind-Klassen mit opacity — inline style nutzen
|
- [ ] No `bg-surface/50` Tailwind opacity syntax with CSS vars — use inline style
|
||||||
- [ ] Loading-States bei async Operationen (useMutation isPending)?
|
- [ ] Loading states for async operations (`isPending`)?
|
||||||
- [ ] Fehler-Feedback für den Nutzer (Toast/Alert bei API-Fehlern)?
|
- [ ] Error feedback for the user (toast/alert on API errors)?
|
||||||
- [ ] Rollen-abhängige UI-Elemente korrekt versteckt?
|
- [ ] Role-dependent UI elements correctly hidden?
|
||||||
|
- [ ] Role checks use updated values: `global_admin`, `tenant_admin`, `project_manager`, `client`
|
||||||
|
|
||||||
### Render-Pipeline
|
### Render Pipeline
|
||||||
- [ ] Neue Parameter durch alle Pipeline-Glieder gezogen?
|
- [ ] New parameters carried through all pipeline links?
|
||||||
(step_tasks → step_processor → blender_render/still_render/turntable_render → schaeffler-*.js)
|
(task → service → subprocess CLI args → render script → Blender operations)
|
||||||
- [ ] STL-Cache-Konvention eingehalten? (`{stem}_low.stl`, `{stem}_high.stl` neben STEP-Datei)
|
- [ ] No references to removed `blender-renderer` HTTP service (port 8100)?
|
||||||
- [ ] Material-Alias-Lookup in richtiger Reihenfolge (Aliases FIRST)?
|
- [ ] 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
|
### USD (when touching export pipeline)
|
||||||
- [ ] Kein hartcodierter Pfad (immer `UPLOAD_DIR` oder DB-Pfad nutzen)
|
- [ ] `pxr` imported from `usd-core` package (not other USD library)?
|
||||||
- [ ] Keine Credentials im Code
|
- [ ] Delivery flatten uses `UsdUtils.FlattenLayerStack()`, not `stage.Flatten()`?
|
||||||
- [ ] Englische Variablen/Kommentare im Code
|
- [ ] Seam/sharp data stored as index-space primvars (not world-space coordinates)?
|
||||||
- [ ] Keine `print()` in Produktion — `logging` nutzen
|
- [ ] `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
|
```markdown
|
||||||
# Review Report: [Feature-Name]
|
# Review Report: [Feature Name]
|
||||||
Datum: [heute]
|
Date: [today]
|
||||||
|
|
||||||
## Ergebnis: ✅ Freigabe / ⚠️ Kleinigkeiten / ❌ Blockierend
|
## Result: ✅ Approved / ⚠️ Minor issues / ❌ Blocking
|
||||||
|
|
||||||
## Gefundene Probleme
|
## Problems Found
|
||||||
|
|
||||||
### [Datei:Zeile] Beschreibung
|
### [File:Line] Description
|
||||||
**Schwere**: Kritisch / Mittel / Gering
|
**Severity**: Critical / Medium / Low
|
||||||
**Empfehlung**: Was soll geändert werden?
|
**Recommendation**: What should be changed?
|
||||||
|
|
||||||
## Positiv aufgefallen
|
## Positives
|
||||||
...
|
...
|
||||||
|
|
||||||
## Empfehlung
|
## Recommendation
|
||||||
Freigabe / Bitte [X] beheben und erneut reviewen.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user