---
title: QAF-Differences — Implementation Plan (Phase 1A Architektur)
type: project
tags: [kadi-v2, qaf, supplierpulse, KAR-799]
date: 2026-06-25
status: aktiv
related_memory: [project_kadi_v2_tenant_later, reference_kadi_v2_supabase_postgrest_only_no_ddl, project_kadi_v2_gstack_required]
---

# QAF-Differences — Architektur & Implementierungsplan

**KAR:** KAR-799 · **Repo:** `/root/projekte/Kadi-v2` · **Branch:** `feature/qaf-differences` (geplant)
**Spec-Quelle:** Telegram-Doc `QAF-Differences_Aria-Prompt_FINAL.md` (Kais, 25.06.2026)
**Sub-Agent-Research:** `/root/aria/state/sub-agent-context/qaf-differences/` (3 Reports)

## 1. Repo-Erkenntnisse / Bestätigter Stack
- Next.js 16 App Router (React 19), Supabase (Postgres+Auth+Storage), Tailwind v4 + shadcn, **ExcelJS** (read+write) bereits Dependency.
- Gates: `CHECK_FORBIDDEN_LEVEL=error npm run check:portability` (forbidden/boundaries/colors/csp/secrets…), `next build`, `vitest`. gstack-Pflicht (OK verifiziert).
- **Single-Tenant** (Multi-Tenant-Layer 2026-05-26 entfernt; ADR nötig zum Reintroducen). Kein `tenant_id`.
- DB-DDL **operator-applied** (Service-Key nur PostgREST, KAR-795): Migration als SQL-File liefern, Kais spielt via SQL-Editor ein.

## 2. Existierender QAF-Stack (REUSE-Basis, KAR-90/101/102/103/339/340/341)
| Was | Datei | Zustand |
|---|---|---|
| Parser (Fertigungskosten-Blatt, 22 Spalten, DE+EN, Formel-`.result`) | `lib/qaf-parser.ts` | REUSE, EXTEND |
| Delta absolut + sentiment + reference-modes | `lib/qaf/comparison.ts` | REUSE (+% / %-Punkte ergänzen) |
| 2-Stufen-Matching (exact + Jaccard-fuzzy) | `lib/qaf/process-mapping.ts` | EXTEND → 5-Stufen-Kaskade |
| Column-Groups, File-Colors | `lib/qaf/column-groups.ts`, `file-colors.ts` | REUSE |
| Comparison-Board UI (Drag-Drop, 5 Slots) | `components/qaf/qaf-comparison-board.tsx` | EXTEND (major) |
| Mapping-Dialog | `components/qaf/qaf-mapping-dialog.tsx` | EXTEND |
| Tabelle `qaf_uploads` (parsed_data JSONB) | migration | REUSE als Roh-Store |
| Tabelle `qaf_process_mappings` | migration | EXTEND (+requires_review, match_stage) ⚠ RLS schwach |

**Gaps (NEU):** Zusammenfassung-Blatt-Parser, Source-Cell-Tracking, Normalizer, Baseline-Logik, Root-Cause (deterministisch), Plausi-Check, 8-Blatt-Export, ~13 neue Qaf*-Tabellen (relational), Audit-Log.

## 3. Architektur-Entscheidungen
- **ADR-Style Modul:** `npm run new:module -- qaf-differences --with-route`. Engine-Logik ausschließlich in `lib/qaf-differences/internal/`, Export nur über `lib/qaf-differences/index.ts` (Boundary-Gate). API-Route importiert nur via Barrel.
- **Engine UI-unabhängig:** reine Funktionen (parser→normalizer→matcher→differ→rootcause→plausi→exporter), kein React-Import, batch/CLI/test-fähig. UI = dünne Nutzungsschicht.
- **Relationale Normalisierung statt JSONB-Blob:** `qaf_uploads.parsed_data` bleibt Roh-Cache; Engine schreibt normalisierte Zeilen in `qaf_manufacturing_step` / `qaf_summary_metric` etc. → SQL-Level-Diff möglich, reproduzierbar (Versionen gespeichert).
- **Eigene Top-Level-Seite** `/qaf-differences` (nicht projekt-sub). Nav-Eintrag in `components/layout/app-shell.tsx` `BASE_ITEMS` + i18n-Keys in 4 Locale-JSONs.
- **Tenant Isolation = ownership-RLS** (project_id→projects.user_id, `_own`+`_admin` Dual-Policy via `current_user_role()`). KEIN echtes Multi-Tenant (Spec-Wording uminterpretiert, single-tenant-konform). ⚠ Mit Kais final bestätigen.

## 4. Phase-1- vs Phase-2-Scope
**Phase 1 (dieser Auftrag):** Seite + Upload(xlsx, optional zip) + Parser(beide Blätter) + Baseline + Matching-Kaskade(5 Stufen, confidence, requires_review) + Diff(abs/%/%-Punkte) + Neu/Entfallen/Strukturänderung + BW/AW-Währungslogik + Plausi + deterministische Root-Cause + 8-Blatt-Export + Audit-Log + ownership-RLS + Tests (Parser/Matching/Diff/Plausi/Export/Access).
**Phase 2 (nur vorbereiten):** template-agnostischer Parser + Template-Registry/Mapping-Assistant + EN-Header-Standard + Produktidentität ≠ BMW-Sachnummer + weitere QAF-Bereiche (Material/Werkzeug/Verpackung/Transport/Zoll) + trainierbare Mappings + externe API.

## 5. Datenmodell (neue Tabellen, alle ownership-RLS, alle reproduzierbar)
QafBatch · QafFile · QafPart · QafSummaryMetric · QafManufacturingStep · QafComparison · QafSummaryDiff · QafManufacturingDiff · QafStepMatch · QafStructureChange · QafPlausibilityIssue · QafRootCause · QafExport · QafAuditLog.
Jede: `id, project_id` (ownership-Anker), Engine-Versionsfelder (parser/normalizer/diff), Zeitstempel, user. Source-Refs (datei/blatt/zeile/zelle) als Spalten oder JSONB pro Kennzahl.

## 6. Matching-Kaskade (Pflichtkern B3)
Pro Step-Match: `match_status, confidence_score, match_method, matched_fields, conflicting_fields, explanation, requires_review, persisted_match_result`.
- S1 safe_match (Pos+Prozess+Anlage+Teil identisch/normalisiert) requires_review=false
- S2 probable_match (Pos identisch + Prozess|Anlage ähnlich, fuzzy>Schwelle)
- S3 possible_structure_change (Pos identisch, Prozess|Anlage stark abweichend) review=true
- S4 candidate_match (keine Pos-Gleichheit, Prozess+Anlage+Teil ähnlich) review=true, NICHT auto in Delta
- S5 new_step / removed_step
**Wichtig:** niedrige Confidence/requires_review = **Laufzeit-Review**, KEIN Entwicklungs-STOPP (B15). Reproduzierbar: Lib+Version+Schwellen persistieren (B4).

## 7. Baseline-Logik (B5)
„Älteste vs neueste" nur Default. UI-Auswahl je Sachnummer (erste/letzte freigegebene/letzter Upload/manuell/sequenziell/Vergabe-vs-aktuell). Baseline+Vergleichsdatei in `qaf_comparison` speichern; bei mehreren plausiblen → Status „Baseline Review erforderlich", nicht still wählen.

## 8. Währungslogik (B7)
BW/AW pro Prozesszeile getrennt speichern (+Wechselkurs, FK_BW, FK_AW, AW1 separat). Unterschiedliche AW ALT/NEU → Status „Währungswechsel prüfen", kein Prozentdelta ohne Warnung; Wechselkurs als eigener Root-Cause-Treiber. **Akzeptanz:** nie ungekennzeichnet vermischte Währungen in einem Delta.

## 9. Root-Cause (B8, deterministisch)
Regelbasiert, keine KI-Erfindung. Input-Treiber (Zykluszeit, Teile/Zyklus, MA, Lohn, SGK, MSS, Rüst, RFGK, Ausschuss, Anzahl/Angebotsteil) vs Ergebnis-Kosten getrennt. Größte abs. + größte rel. Treiber je Sachnummer. Management-Fazit 2–4 Sätze nur aus berechneten Deltas; bei unsicherem Matching Review-Hinweis.

## 10. Export (B9, Referenzqualität)
Server-side API-Route `app/api/qaf-differences/export/route.ts` (`maxDuration=60`), Buffer-Response. Blätter: README, Import_Log, Zusammenfassung_Vergleich, Fertigungskosten_Vergleich, Neu_Entfallen, Delta_Highlights, Top_Treiber_Prozess, Plausibilitätscheck (+optional Maßnahmen, Audit_Log). Reuse `lib/excel/template-helpers.ts` (writeHeader/frozen panes/TEMPLATE_FILL), `lib/assessment-export.ts` (conditional fills), `lib/master-data/excel-service.ts::sanitizeMasterDataCell()` (Formel-Injection-Schutz). Autofilter, fixierte Kopfzeilen, Zahl-/Prozentformate, bedingte Formatierung, Quellenangaben.

## 11. Migrationsstrategie (D2) + Rollback
Bestehende Konvention beibehalten: flache `supabase/migrations/supabase-migration-qaf-differences-*.sql` + paired `-rollback.sql`, `BEGIN;…COMMIT;`, idempotent (`IF NOT EXISTS`, `DROP POLICY IF EXISTS`), Katalog-Zeile in `MIGRATIONS.md`. Mehrere Entitäten ggf. nummeriert (01-tables, 02-rls, 03-indexes). **Keine eigenmächtige Umstellung der globalen Migrationsstrategie.** Einspielen = Kais (SQL-Editor) — STOP-Kriterium B15.1.

## 12. Security-Konzept (D1)
Ownership-RLS jede Tabelle (`_own`+`_admin`). Upload: nur xlsx/zip, Größenlimit, Hash-Dedup, serverseitige Inhaltsprüfung, keine Makro-Ausführung, keine externen Links aktiv, **keine externen LLM-Calls mit QAF-Daten**, kein öffentlicher Download (signed URL 60s), Service-Role nur falls nötig + Intent-Register-Eintrag. `qaf_process_mappings`-RLS-Schwäche mitfixen.

## 13. Tests (D4)
Unit co-located `lib/qaf-differences/__tests__/`. Matching/Baseline/Währung/Plausi/Diff/%-Punkte/Strukturänderung. RLS-Iso über `__tests__/security/rls-qaf-differences-isolation.test.ts` (raw pg.Client, SET ROLE authenticated, USER_A/USER_B, 4 Assertions: SELECT empty, UPDATE/DELETE rowCount=0, INSERT foreign rejected). `describe.skipIf(!DB_URL)`, läuft via `scripts/rls-test/run.sh`. Export: alle Pflichtblätter, Quellen, Matching-Status, keine vermischten Währungen.

## 14. Offene Punkte / Risiken
1. **Referenzdatei** `QAF_Fertigungskosten_Vergleich_260608.xlsx` fehlt — Export-Fidelity + Zusammenfassung-Zellmapping (I8/C8/I5/M5/M8) erst nach Erhalt final verifizierbar. Engine baut gegen Spec-Adressen + 22 bekannte Spalten.
2. Single-Tenant-Interpretation final mit Kais bestätigen (Plan: ownership-RLS).
3. ≥100 Files/Batch synchron in 60s? — falls eng: V2-Chunking/Queue. V1 Storage-by-id, kein Re-Upload.
4. `BMW`-String in product-code verboten (check:forbidden) — Engine generisch halten, Test-Fixtures `// allow-customer-string`.

## 15. Output-Gate (C5/E)
Voll autonom bis PR auf `feature/qaf-differences`. **Kein Merge main/prod, kein Prod-Deploy.** Abschluss: „Bereit zum Prod-Merge. Warte auf Freigabe. Es wurde kein Merge und kein Prod-Deploy durchgeführt."
