# KAR-958/P1 — Real-Korpus-Validierung

Repo: `/home/aria/work/kadi-v2`, Branch `kar-958-parse-decoupling`. Validiert:
`parseQafFile` (`lib/qaf-differences/internal/workbook-adapter.ts`) — die
Promise.allSettled-Entkopplung aus Paket 1.

Werkzeug: `tools/p1-validate.mjs` (lokal, NICHT committet). Läuft gegen die
37 „0-Steps"-Dateien aus `batch-results.json` (jede Datei mit einem
nicht-leeren `errors`-Array — 35× `manufacturing-parse: ...`, 2× `load: ...`
für die beiden Legacy-`.xls`-Dateien). Aggregierte Zahlen nur — keine
Dateinamen, keine Zellwerte, per Vertraulichkeitsdisziplin.

Methodik: zweimal `npx tsx ../qaf-corpus/tools/p1-validate.mjs` von
`kadi-v2` aus ausgeführt — einmal gegen den `git stash`-ten (Vorher-)Stand,
einmal gegen den gefixten (Nachher-)Arbeitsbaum.

## Ergebnis

| Metrik | Vorher | Nachher |
|---|---|---|
| Zieldateien (37 "0-Steps") gefunden auf Disk | 37/37 | 37/37 |
| `parseQafFile` wirft komplett (Summary + Steps verloren) | **37/37** | **2/37** |
| Summary erfolgreich extrahiert (`partNumber.value` gesetzt) | 0/37 | 17/37 |
| Steps extrahiert (`steps.length > 0`) | 0/37 | 0/37 |
| Strukturiertes `manufacturingDegradation`-Finding statt Crash | 0/37 | 35/37 |
| davon Reason `PARSE_FAILED` | — | 35/35 |

## Einordnung

- **Kernfix bestätigt:** vorher riss JEDE der 37 Dateien beim
  `parseQafFile`-Aufruf die komplette Promise.all — auch die Summary-Daten
  (Teilenummer, Teilebenennung, Zusammenfassungs-Metriken) gingen verloren,
  obwohl deren Parse unabhängig erfolgreich gewesen wäre. Nachher bleiben nur
  die 2 tatsächlich unloadbaren Legacy-`.xls`-Dateien (`QAF_FBK_G20-G22
  20P_Quote 3_20131002.xls` / `...40P LH_Quote 3_20131002.xls`, siehe
  gate-audit.md — außerhalb des KAR-958-Scopes, ExcelJS ist zip-only) als
  Crash übrig.
- **Steps bleiben bei 0/37** — erwartet und korrekt: Paket 1 entkoppelt die
  Facetten, es repariert NICHT die zugrundeliegende Fertigungskosten-Header-
  Erkennung selbst (das wäre ein anderer Gate, außerhalb des B14-Scopes).
  Der Gewinn ist Summary-Erhalt + sichtbare Degradations-Ursache statt eines
  stillen/kompletten Verlusts.
- **Summary nur bei 17/37 tatsächlich befüllt** (die übrigen 18 bleiben
  strukturell leer): 3× `confirmed_multi_qaf` und 2× `ambiguous`
  (Nicht-QAF-JIT-Matrizen) unter den 37 haben strukturell kein Standard-
  SUMMARY-Sheet, weitere Dateien haben ein SUMMARY-Sheet, dessen BMW-
  Sachnummer-Label der `summary-parser.ts`-Labelscan nicht findet
  (unverändertes, aus dem Scope dieses PRs ausgeschlossenes Verhalten) —
  wichtig: diese 18 sind nach dem Fix nicht SCHLECHTER als vorher (vorher
  0/37 hatten überhaupt eine Summary), nur eben nicht ALLE 37 besser.
- **Package 2 (die 6 Detail-Parser: material/sbm/rmr/logistics/lccn/co2e)**
  ist NICHT Teil dieser 37-Dateien-Metrik — deren coreFieldsFound-Resilienz
  wirkt auf ein anderes Symptom (Teilextraktion statt Alles-oder-nichts bei
  degradierten Detail-Sheets, gate-audit B5-B11) und wird durch die
  Unit-Test-Suite (`lib/qaf-differences/internal/__tests__/{material,sbm,rmr,
  logistics,lccn,co2e}-parser.test.ts`) verankert, nicht durch dieses
  Real-Korpus-Skript.

## Ehrlicher Risiko-Hinweis (Stand vor PR #325 Review-Fix — inzwischen behoben, siehe unten)

`parseQafFile` (der hier gefixte Pfad) war im echten Produktions-Ingest
(`app/qaf-differences/actions.ts`) NICHT der tatsächlich genutzte Code-Pfad —
`actions.ts` duplizierte die Summary-/Manufacturing-Parse-Logik inline
(`parseSummarySheetFromWorkbook` gefolgt von einem UNGEFANGENEN
`await parseQAFTemplate(...)`, Zeile ~917) und hatte damit ein strukturell
ähnliches (in der Praxis: das komplette Ingest bricht ohne DB-Insert ab)
Kopplungsproblem, das dieser PR ursprünglich NICHT behoben hatte. Ein
nachfolgendes High-Effort-Review (PR #325, Finding #1, CONFIRMED) hat genau
das bestätigt: die oben dokumentierten 37/37→2/37-Zahlen wurden nur über den
ungenutzten `parseQafFile`-Standalone-Pfad gemessen, nicht über den echten
Ingest. **Fix (KAR-958/P3, selbes PR):** `actions.ts` ~916-943 verdrahtet
jetzt dieselbe Promise.allSettled-artige Isolation DIREKT im Ingest-Fluss
(try/catch um `parseQAFTemplate`, degradiert `steps` auf ein leeres,
Confidence-0-`QAFParseResult` statt die Exception propagieren zu lassen) —
bewusst NICHT durch Umstellen auf `parseQafFile` selbst, weil das einen
zweiten, redundanten ExcelJS-Load des bereits in `actions.ts` geladenen
Workbooks eingeführt hätte (verletzt die dort etablierte "ONE ExcelJS load
per file"-Disziplin) und `ingestQafUpload`s Kompensations-/Persistenz-Logik
unangetastet bleiben sollte.

## Ingest-Pfad-Validierung (KAR-958/P3, PR #325 Finding #1 Fix)

Werkzeug: `tools/p1-validate-ingest.mjs` (lokal, NICHT committet, wie
`p1-validate.mjs`). Repliziert die EXAKTE Sequenz, die `actions.ts`
`ingestQafUpload` jetzt an der realen Call-Site ausführt — EIN
`loadExcelWorkbook`, dann synchron `parseSummarySheetFromWorkbook`, dann der
neue try/catch um `parseQAFTemplate` — statt den ungenutzten `parseQafFile`
zu rufen. Ein echter Aufruf von `ingestQafUpload` selbst ist ohne Live-DB
nicht sinnvoll möglich (DB-gebundenes Integrations-Glue, siehe die
`tdd-guard:skip`-Modulkopfzeile der Funktion) — dies ist die nächstmögliche
Ingest-nahe Validierung des echten Fixes ohne Live-Supabase.

Methodik: dieselbe Sequenz einmal OHNE (Vorher, den alten ungefangenen
`await parseQAFTemplate(...)` simulierend) und einmal MIT (Nachher, aktueller
Arbeitsbaum) dem neuen try/catch gegen dieselben 37 Dateien ausgeführt.

| Metrik | Vorher (Ingest-Pfad, ungefangen) | Nachher (Ingest-Pfad, mit Fix) |
|---|---|---|
| Zieldateien (37 "0-Steps") gefunden auf Disk | 37/37 | 37/37 |
| Gesamter Ingest-Fluss wirft (Summary + Steps verloren, kein DB-Insert) | **37/37** | **2/37** |
| Manufacturing-Facet wirft, Ingest-Fluss überlebt (Fix greift) | 0/37 | **35/37** |
| Steps extrahiert (`steps.length > 0`) | 0/37 | 0/37 (erwartet, wie oben — B14 repariert nicht die Header-Erkennung selbst) |

**Einordnung:** identisch zur ursprünglichen (aber falsch-adressierten)
37/37→2/37-Zahl oben — jetzt aber tatsächlich am Ingest-Call-Site gemessen,
nicht am unverdrahteten `parseQafFile`. Die verbleibenden 2/37 sind dieselben
2 strukturell unloadbaren Legacy-`.xls`-Dateien (außerhalb des KAR-958-Scopes,
ExcelJS ist zip-only) wie oben. Die 35 vormals komplett abbrechenden Ingests
persistieren jetzt Summary + alle anderen Facetten (MATERIAL/SBM/RMR/
LOGISTICS/LC-CN/CO2e) statt gar nichts.
