# Ist-Architektur QAF-Vergleich — verifiziert gegen `main` = `d639c0b`

Stand: 03.08.2026 · Quelle: Repo-Inspektion (nicht Snapshot-Dokumente)
Zahlen: 131 Kern-Module (lib/qaf-differences), 54 UI, 10 Routen/Actions,
77.949 Zeilen Produktion gesamt.

## Datenfluss heute (Zwei-Datei-Standardvergleich)

```mermaid
flowchart TD
    subgraph Upload["Upload (Client)"]
        U1[createQafUploadTargets<br/>signed URLs] --> U2[Browser lädt direkt<br/>in QAF_UPLOAD_BUCKET]
    end

    subgraph Ingest["Ingest (Server Action, actions.ts 4.771 Z.)"]
        I1[Storage-Download + Größen-Gate] --> I2[SHA-256 file_hash]
        I2 --> I3[workbook-safety:<br/>ZIP-Bomb echt dekomprimiert,<br/>Makro, ext. Links, BIFF-Zweig]
        I3 --> I4[loadExcelWorkbook<br/>EIN Load pro Datei]
        I4 --> I5[Dimension-Gate + Foreign-Form<br/>WAF/LAF/LEK-Reject]
        I5 --> I6[Multi-QAF-Detektor<br/>Container vs. Standard]
        I6 --> I7["parseQafFile: Summary + Steps +<br/>SummaryMetrics (Preisblock)"]
        I7 --> I8[Sheet-Parser additiv:<br/>Material, SBM, RMR,<br/>Logistik, LC-CN, CO2e]
        I8 --> I9[(qaf_file + qaf_part +<br/>qaf_summary_metric +<br/>qaf_manufacturing_step)]
    end

    subgraph Engine["Deterministischer Kern (lib/qaf-differences)"]
        E1[batch.ts: Gruppierung je Sachnummer,<br/>selectBaseline, EIN ALT/NEU-Paar] --> E2[compareQafPair]
        E2 --> E3[Matching: Material-Kaskade,<br/>Fertigung Parameteridentität,<br/>Tooling Name→Suffix]
        E2 --> E4[Formel-Engine, Zellzustände]
        E2 --> E5[Reconciliation: unabhängig /<br/>gleiche Quelle / nicht prüfbar]
        E2 --> E6[Data-Quality D01-D14<br/>D15-D17 nur Typ]
        E3 --> E7[all-differences Katalog:<br/>NUR tooling + findings<br/>Material/Fertigung FEHLEN]
        E5 --> E8[view-specs, chart-specs,<br/>Waterfall ohne Restbalken]
    end

    subgraph Persist["Persistenz + Rehydration"]
        P1[(qaf_comparison +<br/>step_match + diffs)] --> P2[rehydrateFile:<br/>Werte + Zellen JA,<br/>Formeln + Blattnamen NEIN]
    end

    subgraph UI["UI (V1)"]
        V1["[id]/page.tsx 888 Z. →<br/>qaf-comparison-detail.tsx 1.743 Z.<br/>18 Sektionen, eine Langseite"]
        V2["[id]/v2/page.tsx 137 Z.<br/>V2-Bausteine, aus DB-Zeilen<br/>(ohne Formel-Provenienz)"]
        V3[referenz/page.tsx<br/>V2-Ansichten mit Fixtures]
    end

    subgraph Export["Export"]
        X1[qaf-export-controls.tsx:<br/>ViewModel-JSON CLIENTSEITIG<br/>identisch zur Ansicht] 
        X2[buildV2Export 19 Bereiche<br/>0 Produktions-Aufrufer]
        X3[XLSX je Modus serverseitig]
    end

    Upload --> Ingest --> Engine --> Persist --> UI
    E8 --> V1
    V1 --> X1
    P2 --> V2
```

## Verifizierte Architektur-Fakten

| Fakt | Beleg |
|---|---|
| Öffentlicher Barrel 1.436 Z., interna via `internal/` | `lib/qaf-differences/index.ts` |
| 5 Vergleichsmodi zentral registriert | `comparison-mode.ts` (Exhaustiveness) |
| 22 harte Modus-Gates außerhalb der Registry | B-09, v. a. Client + actions |
| Differenzkatalog: nur Tooling + Findings | `differences-from-*.ts` (2 von 4) |
| Original bleibt im Bucket, `storage_key` nie geschrieben | B-04 |
| ViewModel-Export clientseitig, V2-Envelope tot | B-05 |
| Zoom-Verbot im Root-Layout | B-11, `app/layout.tsx:51` |
| E2E: Playwright-Scaffold, alles fixme | B-10 |
| 40 real-files-Tests, lokal echt, CI skippt | B-06 |
| Kein globales A-F-Set: Batch wählt je Gruppe EIN Paar | `batch.ts:146ff` |
