# Aria — Master-Prompt: Intelligenter QAF-Vergleich für SupplierPulse

> Kopiere diesen Prompt vollständig an Aria (Claude Code CLI). Er ist self-contained.
> Alle Zahlen im Abschnitt **§2 Verifizierte Referenz-Fakten** sind an den echten Dateien geprüft und gelten als Akzeptanz-Oracle. Wenn dein Code sie nicht reproduziert, ist der Code falsch — nicht die Zahl.

---

## §0 Rolle, Kontext, Grundregeln

Du bist Senior Full-Stack- und Data-Engineer mit Spezialisierung auf BMW-QAF-Kostenanalysen. Arbeite **extrem sorgfältig, prüfbar und ohne Annahmen, die nicht durch Dateiinhalt oder Code belegbar sind**. Wenn etwas unklar ist: erst am File verifizieren, dann coden.

**Ziel:** Ein QAF-Vergleich, der *extrem intelligent* ist und mit verschiedenen QAF-Formaten umgeht — auch wenn Reiter verschoben/umbenannt sind, Zeilen verrutscht sind oder das Formular auf Deutsch **oder** Englisch ist.

**Bestand, der nicht zurückgebaut werden darf:** Die Datei `SupplierPulse_QAF_Compare_V6.html` ist ein lokaler Standalone-Browser-Vergleich (self-contained, ECharts + SheetJS inline, kein CDN, `lang="de"`, BMW-/Petrol-Palette). Drag-and-Drop von zwei QAF-Excels, Auto-Start nach Upload, Tab 1 Meta-Vergleich, Tab 2 Prozessanalyse, Szenario-Parameter, editierbare Prozesswerte, Reload, PDF-Report. **Diese Funktionalität bleibt vollständig erhalten.**

**Harte Sicherheits-/Datenschutzregeln (nicht verhandelbar):**
- Keine QAF-Makros ausführen. Keine externen Excel-Links auflösen. Keine QAF-Inhalte an externe Dienste senden. Keine versteckten Uploads. Keine Kostendaten in Logs.
- Die Standalone-HTML bleibt **rein lokal im Browser** (keine Netzwerk-Requests im Vergleichspfad).
- Der Vergleich muss **ohne KI und ohne Backend** vollständig funktionieren. Alle Berechnungen, Deltas, Matching-Ergebnisse, Plausibilitätsprüfungen und Fazits sind **deterministisch, auditierbar und ohne KI identisch reproduzierbar**. KI ist nur eine optionale Erklärungsschicht auf bereits berechneten Ergebnissen.

**Arbeitsumgebung:** Standalone-Artefakt bleibt eine eigenständige, per Doppelklick lauffähige HTML-Datei. Die spätere SupplierPulse-Integration (Repo `~/KADi/backend-nextjs`, Next.js 16 / Supabase / RLS) wird **vorbereitet**, aber nicht erzwungen — siehe §12. Standalone und App teilen sich dieselbe DOM-freie Engine.

---

## §1 Auftrag in einem Satz

Extrahiere die V6-Engine sauber aus der UI, mache sie template-robust und mehrsprachig, ergänze eine mehrstufige Matching-Kaskade mit Review, füge Plausibilitätschecks und DE/EN-i18n hinzu, bereite die optionale KI-Schicht (deaktiviert) vor — und **breche V6 dabei nicht**.

**Intelligenz-Prinzip (das Herzstück):** Verlasse dich **nicht** auf feste Zeilen-/Spaltenpositionen als einzige Wahrheit. Verankere Ergebniswerte über **zweisprachiges Label-Matching** in einem Suchfenster und die feste Position nur als *Prior* und *Kreuzcheck*. Finde die Wertspalte über den **Währungs-Header**. So übersteht die Engine verschobene Reiter/Zeilen und beide Sprachen. Jeder extrahierte Wert trägt Herkunft und Vertrauen:

```
ExtractedValue = { value, sheet, cell, howLocated: 'fixedRow'|'labelMatch'|'headerMatch'|'aggregate', confidence: 0..1 }
```

---

## §2 Verifizierte Referenz-Fakten (Ground Truth — Akzeptanz-Oracle)

> Diese Werte wurden an den echten Dateien mit openpyxl (data_only, ohne Makros) verifiziert. Rundungsabweichungen bis **0,01** sind ok.

### 2.1 Template `BMW_DETAIL_TABS_G60` — `100 - G60_DP_QAF_Basis_24_10_BMW.xlsm` vs `20260508_BMW QAF_G60_DP_RePricing_HO.xlsm`
- Basis: **240** Sheets gesamt. RePricing: **241**. Meta-Sheets unterscheiden sich (Basis hat `CNY`; RePricing hat zusätzlich Jahres-Sheets `2026`, `2027`). Erwarte solche Unterschiede und behandle sie als Meta, nicht als Kostenreiter.
- **Kostenreiter = Sheets, die auf `^\d+_2` matchen → exakt 112 pro Datei.** (Die `NN`-Sheets ohne `_2` sind Komponenten-/Inputblätter, nicht die Kostendetails.)
- **111 exakte Namensmatches. Genau 1 Alias:** Basis `75_2_a` ↔ RePricing `75_2a`.
- **Kollisionsfalle (real, bestätigt):** eine naive Normalisierung `[^0-9a-z]` kollabiert `8_2 (2)` und `82_2` beide auf `822`. **Niemals mergen.** Die kollisionssichere Lösung steht in §7.2.
- `INPUT!C19 = 'EUR'` (beide). Master-Raten in `INPUT!C20:C44` und `INPUT!C58`. `Stückzahlen!C4:K4` = Jahre 2023–2031, `Stückzahlen!C5:K5` = Volumen je Jahr.
- **Aggregat aus Zeile 41 über alle 112 Reiter reproduziert die Headline exakt:**

| Kennzahl (Σ Zeile 41) | Basis | RePricing | Δ abs | Δ % |
|---|---|---|---|---|
| `DJ41` Quotation price | **384,62** | **505,13** | **+120,51** | **+31,33 %** |
| `BF41` Total prod. costs | 304,11 | 337,01 | +32,90 | — |
| `W41` Material | 255,75 | 242,75 | −13,00 | — |
| `AT41` Machine | 16,94 | 49,74 | +32,80 | — |

  → Der Maschinenkostenblock verdreifacht sich fast (globaler Maschinensatz-Faktor ≈ **2,95**). Das ist ein echter Treiber, nicht Rauschen.
- **Top-5 Treiber nach |Δ DJ41|** (absteigend): `61_2` (+14,35), `06_2` (+10,93), `8_2 (2)` (+3,30), `83_2` (+2,90), `08_2` (+2,89).
- Zeile-41-Summenzellen (Reihenfolge Material→Angebotspreis): `W41, Y41, AS41, AT41, AV41, BB41, BD41, BF41, DG41, DI41, DJ41`. Prozesszeilen 15–38 mit Spalten `AK, D, E, L, W, AO, AP, AQ, AR, AS, AT, AX, BA, AZ, BC, AY` (siehe §6.3).

### 2.2 Template `QAF_LEGACY_DE_SUMMARY` — Kiekert/Brose 2023
- Sheets: `Zusammenfassung`, `Material`, `Fertigungskosten` (QAF **Version 8.x**).
- **Wertspalte = `P` (Spalte 16) = AW1.** Header `P10 = 'EUR'`; Bestellwährung `C26 = 'EUR'`. `N`/`O` sind AW2/AW3 (leer → 0). **Nicht** `P` hart verdrahten — Spalte über Header finden (§5.3), `P` nur als Fallback.
- Ergebniszeilen (Wert in Spalte P): 11 Materialkosten · 12 Fertigungskosten · 13 Summe Herstellkosten · 14 Verpackung/Transport · 15 Zölle · 17 Vorrichtungen/Folgewerkzeuge · 18/19 Ausschuss Material/Fertigung · 21 Gesamtkosten · 23 Sonstige Zuschläge · 24 Angebotsbasispreis · 26 Rohstoff-Preisanteil · **29 Angebotspreis**.
- Meta: `M5` Teilebenennung · `M8` Variante · `I8` BMW-Sachnummer (**in Legacy oft LEER** — Kiekert FTL: `I8` leer) · `I6` Lieferanten-Nr.

| Datei | M5 | M8 | P11 Mat | P12 Fert | P13 HK | P24 ABP | P29 AP |
|---|---|---|---|---|---|---|---|
| Kiekert `...FTL ZV_fin` | Systemschloss eÖ | FTL ZV | 4,8211 | 1,3321 | 6,1532 | 9,0236 | 9,0236 |
| Kiekert `...HT ZV MKS_fin` | Systemschloss eÖ | TSH ZV MKS | 5,2650 | 1,3321 | 6,5971 | 9,5706 | 9,5706 |
| Brose Anlage 16 (FT links) | — (I8=`5A69441`) | Fahrertür links | 5,2984 | 1,3504 | 6,6489 | — | 7,6804 |
| Brose Anlage 18 (HT links) | — (I8=`5A69451`) | Hintertür links | 5,3754 | 1,3504 | 6,7259 | — | 7,6461 |

### 2.3 Template `QAF_V9_SUMMARY` — Brose 2026 (aktuell)
- Sheets: `SUMMARY`, `MATERIAL`, `MANUFACTURING COSTS`, `A1` (bilingual) (QAF **Version 9.0**).
- **Wertspalte = `P` (Spalte 16) = AW1.** Header `P10 = 'EUR'`; Bestellwährung **`C25 = 'EUR'`** (Achtung: `C25`, nicht `C26` wie Legacy!).
- **Zeilen sind ggü. Legacy verschoben** (perfektes Beispiel für "Reiter/Zeilen verschoben"): 11 Material costs · 12 Manufacturing costs · 13 Total production costs · 14 Packaging/transport · 15 Customs · 17 Devices/tools · 18/19 Scrap Material/Manufacturing · 21 Total costs · 23 Other surcharges · 24 Quotation base price · **26 Rohstoff Material (6a)** · **27 Rohstoff Energie (6b)** · 28 Customs/Verlust (**kann negativ sein**) · 29 Transport · **30 Quotation price** (um +1 verschoben ggü. Legacy Zeile 29!).
- Meta: `M5`, `M8`, `I8` (**gefüllt**, z. B. `5A69441`), `I6`.

| Datei | I8 | M5 | M8 | P11 Mat | P12 Mfg | P13 TPC | P24 QBP | P30 QP | Hinweis |
|---|---|---|---|---|---|---|---|---|---|
| `aktuell_Brose_...FT_links_ZV` | 5A69441 | B-Schloss Fahrertür links ZV | FT links ZV | 5,9353 | 1,9610 | 7,8963 | 9,1084 | 8,0268 | `P28 = −1,0816` (Verlust Brose) |
| `aktuell_Brose_...HT_links_3.MKS` | 5A8FBD3 | B-Schloss Hintertür links 3.MKS Kisi | HT links 3.MKS | 6,4751 | 2,0259 | 8,5010 | 9,7419 | 8,6015 | — |

→ Negative Customs-Zeile ist **kein Fehler**, sondern eine **Plausibilitätswarnung mit Quelle** (§9).

### 2.4 Template `UNKNOWN_QAF_REVIEW_REQUIRED`
Kein Template sicher erkannt → **keinen falschen Vergleich erzeugen.** Stattdessen Review-Meldung mit gefundenen Sheets, Kandidaten und fehlenden Pflichtfeldern.

---

## §3 Architektur — saubere Schichten

Zuerst so wenig wie möglich an V6 ändern, damit die Datei weiter läuft. Danach trennen in:

1. `QAFCore` — reine Engine, **kein DOM, keine UI**.
2. `QAFParserRegistry` — automatische Template-Erkennung.
3. `QAFNormalizer` — einheitliches Datenmodell (§6).
4. `QAFMatcher` — Matching für Dateien, Bauteile, Reiter, Prozesszeilen (§7).
5. `QAFDiffEngine` — deterministische Deltas (§8).
6. `QAFRootCauseEngine` — regelbasierte Erklärung ohne KI.
7. `QAFUIAdapter` — bestehende V6-UI + spätere SupplierPulse-Integration.
8. `QAFOptionalAIAdapter` — optional, deaktiviert, nur Zusammenfassung auf bereits berechneten Ergebnissen (§11).

Jede Schicht trägt eine Version im Ergebnis: `parserVersion, normalizerVersion, matcherVersion, engineVersion`.

---

## §4 Intelligente Template-Erkennung (`QAFParserRegistry`)

Erkennung **scorebasiert**, nicht per erstem Treffer. Für jeden Kandidaten Merkmale sammeln, Score bilden, bestes Template wählen; bei Gleichstand oder zu niedrigem Score → `UNKNOWN_QAF_REVIEW_REQUIRED`.

- `BMW_DETAIL_TABS_G60`: Sheets `INPUT` **und** `Stückzahlen` vorhanden; viele `^\d+_2`-Detailreiter; Zeile-41-Summenzellen belegt (Stichprobe `W41, BF41, DJ41`).
- `QAF_LEGACY_DE_SUMMARY`: Sheet `Zusammenfassung` + `Material` + `Fertigungskosten`; deutsche Labels in Spalte G; Wertspalte-Header row 10; QAF-Version-String in `B4` (`QAF Version 8...`).
- `QAF_V9_SUMMARY`: Sheet `SUMMARY` + `MATERIAL` + `MANUFACTURING COSTS` + `A1`; QAF-Version-String `B4` (`QAF Version 9...`); englische Labels.
- Bestätige die Wahl über **Label-Anchoring** (§5): finde ≥ N erwartete Ergebniszeilen per Synonym-Match. Wenn zu wenige gefunden → Score senken.

---

## §5 Intelligentes Anchoring (Kern der "Intelligenz")

### 5.1 Zeilenverankerung per zweisprachigem Label-Match
Definiere pro Kennzahl Synonym-Sets (DE/EN), z. B.:
```
materialCosts       : ["Materialkosten", "Material costs", "Material cost"]
manufacturingCosts  : ["Fertigungskosten", "Manufacturing costs"]
totalProductionCosts: ["Summe Herstellkosten", "Total production costs", "Herstellkosten"]
packagingTransport  : ["Verpackung und Transport", "Packaging and transport"]
customsIncluded     : ["Enthaltene Zölle", "Customs"]
devicesAndTools     : ["Vorrichtungen und Folgewerkzeuge", "Devices and tools"]
scrapMaterial       : ["Ausschusskosten ... Material", "Scrap ... material"]
totalCosts          : ["GESAMTKOSTEN", "Total costs"]
otherSurcharges     : ["Sonstige Zuschläge", "Other surcharges"]
quotationBasePrice  : ["Angebotsbasispreis", "Quotation base price"]
rawMatMaterial      : ["Rohstoff Preisanteil Material", "Raw material price share material", "Rohstoff Preisanteil"]
rawMatEnergy        : ["Rohstoff Preisanteil Energie", "Raw material price share energy"]
customsSupplierBMW  : ["Zölle  Lieferant - BMW", "Customs Supplier - BMW", "Verlust"]
transportSupplierBMW: ["Transportkosten  Lieferant - BMW", "Transport ..."]
quotationPrice      : ["Angebotspreis", "Quotation price", "Offer price"]
```
```
locateRow(sheet, synonyms, expectedRow, labelCol='G', window=±3):
  1) Scanne labelCol im Fenster [expectedRow-window, expectedRow+window] auf normalisierten Synonym-Match (case-insensitive, trim, Ziffernpräfixe wie "1." ignorieren).
  2) Genau EIN Treffer  -> nutze diese Zeile, howLocated='labelMatch', confidence=1.0
  3) Kein Treffer       -> Fallback expectedRow, howLocated='fixedRow', confidence=0.6
  4) fixedRow-Wert und labelMatch-Wert weichen über Toleranz ab -> Status 'review_required'
  5) >1 Treffer         -> 'review_required' (Kollision der Labels)
```
Das löst genau das V9-vs-Legacy-Problem (Angebotspreis Zeile 29 vs 30) **automatisch**, ohne pro Template neue Zeilennummern zu raten. Die geprüften Zeilen aus §2 sind der `expectedRow`-Prior und der Testanker.

### 5.2 Prozentfeld vs. Betrag
Ein Feld, das per Format/Label ein Prozentfeld ist, wird als Prozentpunkt behandelt, nie als Betrag (und umgekehrt). Fehlklassifikation → Plausibilitätswarnung (§9).

### 5.3 Wertspalte per Währungs-Header finden
```
locateAW1Column(sheet, templateType):
  awCode = value(templateType == V9 ? 'C25' : 'C26')            # z.B. 'EUR'
  scanne Header-Zeile 10 (Bereich M10..R10) nach Zelle == awCode -> deren Spalte = AW1
  Fallback: Spalte P (16); howLocated='headerMatch' bzw. 'fixedRow'
```

---

## §6 Einheitliches Datenmodell (`QAFNormalizer`)

Jede Datei auf ein Modell normalisieren. Jeder numerische Wert ist ein `ExtractedValue` (§1) mit Quelle.

### 6.1 `QafFile`
`fileName, fileHash, templateType, parserVersion, detectedLanguage, qafVersion, supplier, project, bmwPartNumber, partDesignation, variant, quoteDate, currencyAW1, volumeTotal, peakVolume, runtimeYears, sourceCells, warnings, requiresReview`

### 6.2 `QafSummaryMetrics`
`materialCosts, manufacturingCosts, totalProductionCosts, packagingTransportIncluded, customsIncluded, devicesAndTools, scrapMaterial, scrapManufacturing, totalCosts, otherSurcharges, quotationBasePrice, rawMaterialPriceShareMaterial, rawMaterialPriceShareEnergy, customsSupplierToBMW, transportSupplierToBMW, quotationPrice, oneTimeDevelopment, oneTimeTools, totalOneTimePayment, pcfMaterial, pcfManufacturing, pcfTotal`

### 6.3 `QafDetailPosition` (BMW-G60)
`tabName, normalizedTabKey, rowNumber, partName, processName, materialDescription, machineOrAsset, location, currencyBW, currencyAW, exchangeRate, material, rawMaterialSurcharge, personnel, machine, FEK, manufacturingTotal, scrap, totalProductionCosts, totalCosts, surcharges, quotationPrice, cycleTime, partsPerCycle, directEmployees, laborRate, inefficiency, machineHourRate, scrapPerStep, sourceCells`

**BMW-G60-Parser (robust):** Pflicht `INPUT!C19` (Währung), `INPUT!C20:C44` + `INPUT!C58` (Raten), `Stückzahlen!C4:K4`/`C5:K5` (Jahre/Volumen). Detailreiter: Prozesswerte aus Zeilen 15–38 (Spalten oben), Aggregate aus Zeile 41 (`W41,Y41,AS41,AT41,AV41,BB41,BD41,BF41,DG41,DI41,DJ41`). **Kreuzcheck:** Summe passender Detailzeilen 15–38 gegen Zeile-41-Aggregat vergleichen; große Abweichung → Layout verschoben → `review_required`.

---

## §7 Matching-Kaskade (`QAFMatcher`) — nie still Positionen verlieren

Kein simples `tabName in otherWorkbook`. Vierstufig, mit Protokoll pro Position.

### 7.1 Stufen
- **Stufe 1 — exakt:** Sheet-Name gleich; BMW-Sachnummer (falls vorhanden) gleich; Part-Name gleich oder auf einer Seite leer. Kein Review.
- **Stufe 2 — normalisierter Tab-Match:** nur auf nach Stufe 1 offene Reiter. Match nur, wenn **genau ein** Kandidat links und **genau einer** rechts übrig ist. Beispiel-Ziel: `75_2_a` ↔ `75_2a`.
- **Stufe 3 — fuzzy:** Part-Name, Sachnummer, Positionsnummer, Prozessname, Maschinenname, Kostenstruktur. Speichere `confidence_score, match_method, requires_review, matched_fields, conflicting_fields`. **Unsichere Matches fließen NICHT still in finale Deltas** — nur in den Review-Bereich.
- **Stufe 4 — neu/entfallen:** nur links → `removed`; nur rechts → `new`.

Jede nicht gematchte Position ist sichtbar (`new`/`removed`/`review_required`).

### 7.2 Kollisionssicherer strukturierter Reiter-Key (bewiesen an den echten Dateien)
```
parseCostTabKey(name):
  m = /^(\d+)_2(.*)$/.match(name.trim())
  if not m: return null                      # kein Kostenreiter
  station = int(m[1])                          # führende Nullen normalisiert: "08" -> 8
  rest    = m[2].trim()
  inst = ""
  paren  = /\((\d+)\)/.match(rest)             # Komponenten-Instanz (2),(3)
  letter = /_?([A-Za-z])$/.match(rest)         # Buchstaben-Variante a,b
  if paren:      inst = "p" + paren[1]
  elif letter:   inst = "l" + letter[1].toLowerCase()   # 75_2a UND 75_2_a -> "la"  (Alias gelöst)
  return (station, inst)

# GUARD: mappen zwei Rohnamen EINER Datei auf denselben (station,inst) -> Kollision -> 'review_required', NIEMALS mergen.
```
Belegt: liefert **112 eindeutige Keys pro Datei, 0 Kollisionen**, matcht alle **112** über beide Dateien, und `75_2_a`/`75_2a` → beide `(75,"la")`. Gegenprobe: `8_2 (2)`→`(8,"p2")`, `82_2`→`(82,"")`, `08_2`→`(8,"")` bleiben distinkt.

---

## §8 Vergleichslogik (`QAFDiffEngine`)

Pro Kennzahl: `Wert ALT`, `Wert NEU`, `Δ abs = NEU−ALT`, `Δ % = Δabs/ALT` (nur wenn ALT sinnvoll ≠ 0, sonst `n.a.`), `Prozentpunkte` bei Prozentfeldern, `Quelle ALT` (Sheet+Zelle), `Quelle NEU` (Sheet+Zelle), `Status ∈ {ok, warning, review_required, new, removed}`.

Immer auf der niedrigsten sicheren Ebene vergleichen:
- BMW-G60: Detailreiter **und** aggregiert (Zeile 41).
- Legacy: Summary-Kennzahlen; wenn möglich Material-/Fertigungskostenzeilen.
- V9: Summary-Kennzahlen; PCF-Block optional; Material-/Manufacturing-Details, wenn sauber auslesbar.

---

## §9 Plausibilitäts- und Fehlerchecks

Mindestens: keine Excel-Datei · beschädigte Datei · Pflichtsheet fehlt · Pflichtzelle fehlt · **Währung ALT ≠ NEU** · Sachnummer aus Dateiname ≠ Blattinhalt · mehrere plausible Sachnummern · Detailreiter fehlen in einer Datei · normalisierte Tab-Namen kollidieren (§7.2) · Formula-Cached-Value fehlt (data_only leer) · Prozentfeld als Betrag gelesen (oder umgekehrt) · **Angebotspreis < Total Costs ohne erklärenden negativen Zuschlag** · **Δ% bei ALT=0 → `n.a.`, nicht rechnen** · **negative Kosten → Plausibilitätswarnung mit Quelle, kein Auto-Fehler** (z. B. V9 `P28 = −1,0816` „Verlust Brose") · jede nicht gematchte Position sichtbar.

---

## §10 UI (`QAFUIAdapter`)

**V6 bleibt vollständig:** Upload zweier QAFs · Auto-Start · deutsche Standardansicht · Tab 1 Meta-Vergleich · Tab 2 Prozessanalyse · Auswahl ALT↔NEU · KPI-Kacheln · QAF-Übersicht · Kostenstrukturdiagramm · Waterfall · Produktionssicht · größte Preistreiber · interaktive Hochrechnung · Quick Wins / große Bringer · Hebel & Gegenmaßnahmen · Anomalien & Treiber · PDF-Report · Neu laden.

**Design-Tokens von V6 erhalten** (nicht neu erfinden): `--teal:#3C6674 · --steel:#A2B9C1 · --green:#3C6A3C · --pet:#037493 · --ink:#1A2227 · --mut:#5B6B73 · --line:#E6EDEF · --callout:#C0392B`, Font-Stack Inter.

**Erweitern um:**
1. Sprachumschalter **DE/EN** über i18n-Dictionary (keine hart verdrahteten UI-Texte).
2. Anzeige: erkannter Template-Typ, Parser-Version, Matching-Status, Review-Hinweise, „vollständig vs. teilweise reviewpflichtig".
3. Bereich **`Matching Review`** und Bereich **`Plausibilitätscheck`**.
4. Export **JSON** und (falls in V6 sinnvoll) CSV/Excel-kompatibles Tabellenformat.

**Englische Fachbegriffe:** Vergabe→`Award baseline` · RePricing→`RePricing` · Bestandsaufnahme→`Current assessment` · Potential→`Potential` · Angebotsbasispreis→`Quotation base price` · Angebotspreis→`Quotation price` · Fertigungskosten→`Manufacturing costs` · Herstellkosten→`Total production costs` · Ausschusskosten→`Scrap costs` · Sonstige Zuschläge→`Other surcharges` · Verhandlung→`Negotiation` · Quick Wins→`Quick wins` · Große Bringer→`Major levers`.

---

## §11 Optionale KI-Schicht (`QAFOptionalAIAdapter`, **deaktiviert vorbereiten**)

KI darf **nur optional** für: Management-Summary, Verhandlungsempfehlungen, Treiber-Erklärung in natürlicher Sprache, DE/EN-Übersetzung, Rückfragen an Lieferanten. KI darf **niemals** Berechnungen ersetzen, Deltas erfinden, Matching still korrigieren, Review-Status übergehen, QAF-Daten an externe Dienste senden (solange kein freigegebenes SupplierPulse-KI-/Datenschutzkonzept existiert), Kostendaten in Logs schreiben.

Eingabe an die KI = **ausschließlich ein redigiertes, bereits berechnetes Ergebnisobjekt** (Zahlen, Quellen, Matching-Status, Warnungen). Jede KI-Ausgabe ist sichtbar als **KI-generiert** markiert und basiert nur auf berechneten Daten. Standard: Adapter aus, per Flag zuschaltbar.

---

## §12 SupplierPulse-Integration vorbereiten (nicht erzwingen)

Engine DOM-/UI-unabhängig · UI-Adapter für Standalone · späterer App-Adapter · persistierbare Ergebnisstruktur · Datei-Hash für Audit · alle vier Versionen im Ergebnis. **Keine Backend-Annahme, bevor der SupplierPulse-Stack geprüft ist.** Wenn Supabase/RLS/Next.js/Azure/bestehende Upload-Komponenten vorhanden sind → vorhandene Patterns nutzen. **Keine neue DB-Tabelle ohne Sicherheitskonzept. Keine externen Downloads ohne Rollenprüfung. Kein Zugriff auf Originaldateien ohne Berechtigungsprüfung.**

---

## §13 gstack-Workflow (Lite-Mode, wie eingerichtet)

- `/review` vor jedem PR.
- `/qa` für App-/Komponentenlogik (Engine, Matcher, UI-Adapter).
- Für die **spätere** SupplierPulse-Integration mit DB/RLS: `/careful` bzw. `/guard` während DB-/Migrationsarbeit; **`/investigate` IMMER** vor dem Fix von RLS-/Auth-/Tenant-Isolation-Bugs — nie direkt patchen. `/cso` bei RLS/Auth-Änderungen.
- Situativ: `/plan-eng-review`, `/codex`, `/freeze`.
- Der Standalone-Teil hat keine DB → dort greifen `/review` und `/qa`; DB-Kommandos erst in Phase Integration.

---

## §14 Implementierungsreihenfolge

1. `SupplierPulse_QAF_Compare_V6.html` analysieren, aktuelle Funktionen dokumentieren.
2. Parser & Engine isolieren, ohne UI zu brechen (V6 muss weiter starten).
3. BMW-G60-Referenzvergleich wiederherstellen (Zahlen §2.1 treffen).
4. Tab-Alias-Matching `75_2_a` ↔ `75_2a` ergänzen (strukturierter Key §7.2).
5. Legacy-Summary-Parser (§2.2) mit Label-Anchoring (§5) ergänzen.
6. QAF-V9-Summary-Parser (§2.3) ergänzen — inkl. Zeilen-Shift 29↔30.
7. Einheitliches Datenmodell (§6) einführen.
8. Matching-Review-Bereich ergänzen.
9. Plausibilitätscheck-Bereich ergänzen.
10. DE/EN-i18n ergänzen.
11. Optionale KI-Schnittstelle als **deaktivierten** Adapter vorbereiten.
12. Tests mit **allen 9** Referenzdateien ausführen (§15).
13. Ergebnis dokumentieren (§17).

---

## §15 Testanforderungen (mit verifizierten Sollwerten)

Erstelle und dokumentiere Tests für: Template-Erkennung · BMW-G60-Parser · Legacy-Parser · V9-Parser · Tab-Match exakt · Tab-Match Alias · Kollisionserkennung normalisierter Tab-Namen · Summary-Diff · Detail-Diff · Währungslogik · Prozentfeldlogik · Plausibilitätswarnungen · deutsche UI · englische UI · Standalone-HTML-Upload · PDF-Report · JSON-Export · **keine externen Requests** · **keine Makroausführung** · **keine still verlorenen Positionen**.

**Konkrete Assertions (Toleranz 0,01):**
- BMW-G60: 112 Kostenreiter je Datei; 111 exakte + 1 Alias; Σ`DJ41` Basis = **384,62**, RePricing = **505,13**, Δ = **120,51** (**31,33 %**); Top-Treiber enthalten `61_2, 06_2, 8_2 (2), 83_2, 08_2`; `8_2 (2)` und `82_2` bleiben getrennt.
- Legacy Kiekert FTL: `Zusammenfassung`, Mat **4,8211**, Fert **1,3321**, HK **6,1532**, ABP/AP **9,0236**.
- V9 Brose FT: `SUMMARY`, I8 **5A69441**, Mat **5,9353**, Mfg **1,9610**, TPC **7,8963**, QBP **9,1084**, QP **8,0268** (`P28 = −1,0816` als Warnung, nicht Fehler).

---

## §16 Definition of Done

1. `SupplierPulse_QAF_Compare_V6.html` startet weiter als lokale Datei im Browser.
2. Die zwei BMW-G60-Dateien werden erfolgreich verglichen.
3. **112** BMW-G60-Positionen erkannt.
4. Alias `75_2_a`↔`75_2a` geht nicht verloren.
5. Legacy-QAFs mit `Zusammenfassung` erkannt.
6. V9-QAFs mit `SUMMARY` erkannt.
7. DE- und EN-UI funktionieren.
8. Vergleich funktioniert vollständig **ohne KI**.
9. KI ist nur optional und deaktivierbar.
10. Jede Kennzahl hat eine Quelle.
11. Jede Warnung ist sichtbar.
12. Jede unklare Zuordnung ist reviewpflichtig.
13. Alle Tests dokumentiert.
14. Abschlussbericht vorhanden (§17).

---

## §17 Geforderter technischer Abschlussbericht

Gib am Ende aus: geänderte Dateien · Architekturentscheidung · erkannte QAF-Templates · Parser-Version · Matcher-Version · Engine-Version · Testergebnisse je Referenzdatei (Soll/Ist gegen §15) · bekannte Einschränkungen · Sicherheitsbewertung (keine Makros, keine externen Requests, keine Kostendaten-Logs) · nächste Schritte für die SupplierPulse-Integration.
