# QAF Template-Gate-Audit — KAR-957 (Master-Prompt §8+§9+§10)

Repo: `/home/aria/work/kadi-v2` (main, read-only, nichts geändert/committed). Scope: `lib/qaf-differences/**` (Diff-Engine, KAR-799) + `app/qaf-differences/**` + `components/qaf-differences/**` (UI/Actions), plus Cross-Sweep über den Rest des Repos. `.claude/worktrees/**` ausgeschlossen (stale Duplikate).

Klassifikation nach Master-Prompt §0: **BLOCKIEREND** = Template-/Klassifikations-Label verhindert die Verarbeitung tatsächlich vorhandener Workbook-Daten. **LEGITIM** = echter Capability-Check (Daten nach vollständiger Suche wirklich nicht da / echte Provenienz-/Ambiguitäts-Guard). **HINT** = reine Parser-Steuerung/Deskription, keine Suppression.

---

## 1. Baseline — Modul-Karte (§8)

### 1.1 Extraktion / Detektion (`lib/qaf-differences/internal/`)

| Modul | Zweck | Input | Output |
|---|---|---|---|
| `workbook-adapter.ts` | ExcelJS↔Engine-Brücke: Zellauflösung (Formel/Merge/Richtext), Grid-Konvertierung, `parseQafFile` (kombiniert Summary+Fertigungskosten) | ExcelJS `Workbook`/Buffer | Grids, `QafSummary`, `SummaryMetricsParse` |
| `co2e-parser.ts` | CO2e-Summary-Panel + CO2e-Material-Zeilen | Grid | `Co2eSummaryParseResult`, `Co2eMaterialParseResult` |
| `lccn-parser.ts` | LC-CN-Summary-Sheet (Panel, Einzelrecord) | Grid | `LccnParseResult` |
| `logistics-parser.ts` | LOGISTICS&CUSTOM-Detailsheet | Grid | `LogisticsParseResult` |
| `material-parser.ts` | MATERIAL/BOM-Detailsheet | Grid, Registry | `MaterialParseResult` |
| `rmr-parser.ts` | RAW MATERIAL RISKS, 1+ Blöcke | Grid, Registry | `RmrParseResult` |
| `sbm-parser.ts` | SBM-DEVICES-FWZ Werkzeug/Vorrichtungskosten | Grid, Registry | `SbmParseResult` |
| `summary-parser.ts` | Identitäts-/Stammdaten aus SUMMARY-Sheet | Grid | `QafSummary` |
| `qaf-type-detector.ts` | Multi-QAF-Detektion (4 gewichtete Signale S1–S4) | `MultiQafDetectionInput` | `MultiQafDetectionResult` |
| `template-fingerprint.ts` | Struktur-Fingerprint (Facet-Coverage) vs. bekannte Profile | geparste Facets | `TemplateFingerprintResult` |
| `foreign-form-detection.ts` | WAF/LAF/LEK (Fremdformular)-Erkennung | Sheetnamen+Header | `ForeignFormDetection\|null` |
| `candidate-sheet-plausibility.ts` | Plausibilitäts-Flag für nicht gewählte Kandidaten-Sheets | Worksheet | `IgnoredCandidateSheetEntry[]` |
| `module-sheet-names.ts` | DE/EN-Sheetnamen-Alias-Registry | Tab-Name | boolean-Match |
| `module-degradation.ts` | UI-Lesemodell: welche Module `coreFieldsFound:false` hatten | `g60_meta` | `DegradedModule[]` |
| `language-detection.ts` | DE/EN/mixed-Spracherkennung | Sheetnamen | `QafFileLanguageResult` |
| `normalizer.ts` | Zahl/Blank/Prozessname-Normalisierung | Rohwerte | normalisierte Werte |

### 1.2 Kanonisches Modell / Vergleich / Business-Logik

| Modul | Zweck | Input | Output |
|---|---|---|---|
| `canonical-model.ts` / `canonical-fields.ts` / `.types.ts` | Statische Registry ~200 kanonischer Felder (0 if/switch, reine Daten) | Label-String | `CanonicalField[]` |
| `differ.ts` | Feld-Delta (abs/%/Status-Band) | zwei `QAFRow` | `FieldDiff[]` |
| `compare.ts` | Orchestrator: match→diff→reconcile→business-rules→plausibility→root-cause | zwei `QafFileParsed` | `QafComparisonResult` |
| `comparison-mode.ts` | Dispatch-Registry je `comparison_mode` (Datei-Rolle/Export/Pins/Swap) | `comparison_mode` | `ComparisonModeRule` |
| `matcher.ts` | 5-Stufen-Kaskade Prozessschritt-Matching | ALT/NEU `QAFRow[]` | `StepMatch[]` |
| `business-rules.ts` | Zeilenweise Formel-Rekonstruktion (Leitfaden) | Steps + Tri-State-Detailzeilen | `BusinessRuleResult[]` |
| `rule-engine.ts` | R1–R6 Pflichtfeld-/Zustandsvalidierung | Summary+Steps+Detail | `RuleViolation[]` |
| `reconciliation.ts` | Summary↔Detail-Summen-Abgleich | Steps+Metrics+Detail | `ReconciliationResult[]` |
| `root-cause.ts` | Kostentreiber-Ranking + Management-Summary | Step-Diffs | `RootCauseResult` |
| `plausibility.ts` | Cross-File-Sanity (Identität, Negativwerte, Währung) | Summaries+Steps | `PlausibilityIssue[]` |
| `projection.ts` / `levers.ts` / `movers.ts` | Hochrechnung / Hebel / Tornado-Chart | Diffs | `Projection`/`Lever[]`/`Movers` |
| `summary-metrics.ts` | Label-verankerte Suche der 19–20 SUMMARY-Geldmetriken | Grid | `SummaryMetricsParse` |
| `summary-view.ts` | View-Model (KPI, Buckets, Bridge) | persistierte Diff-Zeilen | View-Model-Typen |
| `engine-config.ts` | Versionierte Engine-Konfiguration | `EngineConfig`/Version | `DEFAULT_ENGINE_CONFIG` |
| `field-mapping-override.ts` | User-Header-Mapping-Korrekturen | `QAFRow[]`+Overrides | gepatchte `QAFRow[]` |
| `explain-provenance.ts` | Zelle→Ergebnis-Provenance | Modul-Zeilen | `ExplainAttributes` |
| `persistence-mapper.ts` / `rehydrate.ts` | DB-Row-Mapping / Rekonstruktion aus DB | `QafComparisonResult` ↔ DB-Rows | DB-Rows ↔ `QafFileParsed` |
| `baseline.ts` / `batch.ts` | ALT/NEU-Auswahl / Teilenummer-Gruppierung | Dateipaare/Files | `BaselineSelection`/`BatchGroupResult[]` |
| `formula-engine.ts` | Formel-Extraktion/-Vergleich (Manipulationserkennung) | Formula-Provenance | `FormulaComparisonResult` |
| `workbook-safety.ts` | Zip-Bomb/Makro/External-Link-Hardening | Rohbuffer | `WorkbookSafetyResult` |
| `export.ts` | 8-Sheet-Referenz-Export | `QafComparisonResult` | xlsx `Buffer` |
| `product-line-tags.ts` | Derivat-Tags aus Dateinamen | Dateinamen | `string[]` |

### 1.3 Multi-QAF (`internal/multi-qaf/`, KAR-929/930)

| Modul | Zweck | Input | Output |
|---|---|---|---|
| `types.ts` | Domänen-Typen Multi-QAF-Container | — | Typen |
| `template-fingerprint.ts` | Familien-Klassifikation (known/modified/unknown) | Detection+Header | `MultiQafTemplateFingerprintResult` |
| `header-parser.ts` | Varianten-Header-Block-Parsing (Dimensionen, Slot-Index, Volumen) | Grid | `HeaderParserResult` |
| `identity.ts` | Kanonischer Schlüssel je Variante, Active/Inactive-State | Dimensionen | `compositeCanonicalKey` |
| `column-classifier.ts` | 10-Kind-Spaltenklassifikation | Facts | `ColumnClassification` |
| `variant-matcher.ts` | 4-Stufen ALT/NEU-Varianten-Matching | zwei `VariantDefinition[]` | `VariantMatchResult[]` |
| `variant-reconciliation.ts` | 14 Konsistenzchecks je Variante, fail-closed | `MultiQafContainer` | `VariantReconciliationResult[]` |
| `variant-vs-standard.ts` | Bridged EINE aktive Variante in Standard-Engine | Container+Variante | `VariantVsStandardComparisonResult` |
| `bridge.ts` | `VirtualQafVariant`→`Partial<QafFileParsed>` | Variante | Partial-QAF |
| `compare-flow.ts` | End-to-End MQ↔MQ-Orchestrator | zwei Container | `MultiQafComparisonResult` |
| `container-differ.ts` / `material-differ.ts` / `profile-differ.ts` / `summary-totals-differ.ts` | Struktur-/Wert-Diffs | Container-Paar | jeweilige DiffResult |
| `aggregate-impact.ts` | Summierter Impact über Variantenpopulation | alle Diff-Results | `AggregateImpactResult` |
| `profile-parser.ts` / `material-matrix-parser.ts` | Shared-Profile / Material-Matrix strukturell erkannt | Grid+Variantenrefs | `SharedCostProfile[]` / `MaterialMatrixParseResult` |
| `container-assembly.ts` | Orchestriert 6 Parser → `MultiQafContainer` | Worksheets+Detection | Container + `VirtualQafVariant[]` |
| `export.ts` / `serialization.ts` / `formula-lineage.ts` | Export/JSON-Persistenz/Formel-Lineage | Container/Comparison | xlsx / JSON / Lineage-Graph |

### 1.4 G60 (`internal/g60/`)

| Modul | Zweck | Input | Output |
|---|---|---|---|
| `parser.ts` | Fixkoordinaten-Extraktion (`detectG60`, INPUT-Raten, Kostenreiter Zeile 15–38/41) | `G60Workbook` | `G60ParseResult` |
| `structure-guard.ts` | Label-Anker-Validierung, Spalten-Relokation, Tab-Exklusion | `G60Workbook` | `G60StructureReport`, `G60GuardedParseResult` |
| `bridge.ts` | ExcelJS→`G60Workbook` | Workbook/Buffer | `G60Workbook` |
| `analyze.ts` / `calculator.ts` / `scenario.ts` / `view.ts` | Vergleichsaggregation / Preis-Kalkulator / Szenario-Editor / UI-View-Model | `G60ParseResult`(-Paar) | `G60Analysis` / `UnitPriceResult` / `G60ScenarioModel` / View-Typen |
| `export.ts` / `persistence.ts` / `rehydrate.ts` / `stopwatch-map.ts` | Export / DB-Mapping / Rehydrate / Stoppuhr-Zuordnung | jeweils | jeweils |

### 1.5 App/UI/Actions

| Modul | Zweck | Input | Output |
|---|---|---|---|
| `app/qaf-differences/actions.ts` (4228 Zeilen) | Server Actions: Ingest-Routing (Multi-QAF→G60→Summary), Persistenz, Export-Dispatch, Recompare | Upload/DB | DB-Writes, `IngestResult` |
| `app/qaf-differences/page.tsx` / `[id]/page.tsx` / `[id]/kalkulator/page.tsx` | Liste / Detailansicht / Preis-Kalkulator-Seite | DB-Query | React-Rendering |
| `app/api/qaf-differences/route.ts` | schlank (11 Zeilen), kein eigenes Gating | — | — |
| `app/api/qaf-template/route.ts` | Blanko-Fertigungskosten-Template-Download (Legacy-Tool) | — | xlsx |
| `components/qaf-differences/qaf-section-registry.ts` | Zentrale 14-Sektionen-Struktur + statische Placeholder-Texte | — | `QAF_SECTION_TITLES`, `SUMMARY_PLACEHOLDERS`, `G60_PLACEHOLDERS` |
| `components/qaf-differences/qaf-comparison-detail.tsx` | Summary-Modus-Detailansicht (14 Sektionen) | `QafComparisonResult` | Rendering |
| `components/qaf-differences/qaf-g60-detail.tsx` | G60-Modus-Detailansicht | `G60Analysis` | Rendering |
| `components/qaf-differences/qaf-multi-qaf-detail.tsx` | Multi-QAF-Detailansicht | `MultiQafComparisonResult` | Rendering |
| `components/qaf-differences/qaf-differences-client.tsx` | Listenansicht, Mode-Badges | DB-Rows | Rendering |
| ~30 weitere `qaf-*.tsx/.ts` in `components/qaf-differences/` | Charts, Export-Buttons, Provenance-Panel etc. | jeweils | Rendering |

**Legacy-Parallelpfad (außerhalb Kernscope, kurz geprüft):** `app/project/[id]/qaf`, `app/lsc-workshop/[id]/qaf`, `components/qaf/*`, `lib/qaf/comparison.ts`, `lib/qaf/column-groups.ts`, `lib/qaf-parser.ts` sind das ältere „QAF Process Comparison Board" (KAR-341) — separate DB-Tabelle `qaf_uploads`, eigenes UI, kein `template_family`/`isG60`-Gating. Einzige echte Kopplung zum neuen Engine: `lib/qaf-parser.ts` wird von `workbook-adapter.ts` für den Fertigungskosten-Parse wiederverwendet (siehe Gate #14 unten).

---

## 2. Gate-Inventar (§9)

Format: Datei:Zeile | Bedingung | Was wird unterdrückt/verzweigt | Kategorie | Einstufung + Begründung.

### 2.1 BLOCKIEREND (17 Fundstellen)

| # | Datei:Zeile | Bedingung | Unterdrückt | Kategorie | Begründung |
|---|---|---|---|---|---|
| B1 | `lib/qaf-differences/internal/g60/parser.ts:126-129` (`detectG60`) | `if (wb.sheet('INPUT')===null \|\| detailTabs(wb).length===0) return false` + Zusammenfassung-Sheet-Ausschluss + Ratenlabel-Check | Ganze Datei wird nicht als G60 geroutet → verliert den gesamten detaillierten G60-Pfad, obwohl Fertigungskosten/BOM-Detail-Daten äquivalenter Tiefe unter anderem Sheetnamen vorliegen können | Extraktion | Exakt das im Master-Prompt §0.2 verbotene Muster: fixer Sheetname `INPUT` + `N_2`-Namenskonvention entscheidet über den gesamten Detailpfad |
| B2 | `g60/parser.ts:89` (`detailTabs`) | `.filter(s => /^\d+_2/.test(s.trim()) && !MASTERS.has(s))` | Jeder Kostenreiter, der nicht exakt `N_2` heißt, ist für `detectG60`/`parseG60Workbook`/`structure-guard.ts` unsichtbar | Extraktion | Hartkodierte „numbered sheets"-Annahme aus §0.2 Punkt 2 |
| B3 | `g60/structure-guard.ts:153,393` (`finalizeFinding`) | `hard = mismatches.length >= hardMismatchThreshold(2 von 5 Ankern)` | Ganzer Tab (alle 11 Zeile-41-Metriken + Prozessschritte) verworfen, obwohl ~9 weitere Fixspalten nie geprüft werden | Extraktion | Verwirft mehr als die 2 tatsächlich unsicheren Spalten rechtfertigen |
| B4 | `g60/structure-guard.ts:495-497` (`parseG60WorkbookGuarded`) | `if (report.input.confidence !== 0) { … }` sonst leere `.tabs` | **Jeder** Tab der Datei (Material/Sales/Production/HK/TC) verworfen, nur weil INPUT-Ratenkarte ≥2/8 Labels nicht fand — auch Felder, die gar nicht von Raten abhängen | Extraktion | Ratenkarten-Check greift auf ratenunabhängige Felder durch |
| B5 | `material-parser.ts:501-509` | `coreFieldsFound = 3 Kern-Keys.every(...)`; `if(!coreFieldsFound) return []` | Alle 28 kanonischen MATERIAL-Spalten/-Zeilen verworfen wegen 1 fehlendem Kern-Label, auch wenn andere Spalten erfolgreich gemappt wurden | Extraktion | All-or-nothing-Gate; eigene KAR-927-Kommentare dokumentieren exakt dieses Fehlerbild für einen Schwesterpfad, aber der Hauptparse-Pfad wurde nie mitgefixt |
| B6 | `sbm-parser.ts:486-494` | identisches Muster (3 Kern-Keys) | Alle 35 SBM-Felder verworfen | Extraktion | wie B5 |
| B7 | `rmr-parser.ts:500,624-632,686-694` | Block-weise `coreFieldsFound`, Scan bricht beim 1. degradierten Block ab | Degradierter Block + alle folgenden Blöcke verloren | Extraktion | wie B5, zusätzlich verliert Scan-Abbruch auch spätere intakte Blöcke |
| B8 | `logistics-parser.ts:453-460` | identisches Muster | Alle LOGISTICS&CUSTOM-Zeilen verworfen | Extraktion | wie B5 |
| B9 | `lccn-parser.ts:414-421` | identisches Muster (Einzelrecord) | Alle 15 LC-CN-Felder verworfen | Extraktion | wie B5 |
| B10 | `co2e-parser.ts:400-406` | identisches Muster (2 Kern-Keys) | CO2e-Summary-Panel verworfen | Extraktion | wie B5 |
| B11 | `co2e-parser.ts:486-494` | identisches Muster | CO2e-Material-Zeilen verworfen | Extraktion | wie B5 |
| B12 | `summary-metrics.ts:399-401` | `TEMPLATE_CONFIG.QAF_LEGACY_DE_SUMMARY.rows` hat keinen Eintrag für `rawMaterialPriceShareEnergy` (V9 hat einen) | Metrik wird auf als „Legacy" klassifizierten Sheets nie gesucht, bleibt permanent `ABSENT` — auch wenn das Label tatsächlich vorhanden wäre | Extraktion | Template-Klassifikation (nicht Datenlage) entscheidet, ob überhaupt gesucht wird; andere Metriken haben einen unverankerten Fallback, diese nicht |
| B13 | `multi-qaf/aggregate-impact.ts:629-701,780-787` | `IDENTITY_GUARD_NICHT_PRUEFBAR_REASONS` (fingerprint_unavailable/unmatched_variant) | Bereits berechnete Material-/Profil-Impact-Werte einer Variante werden aus den **Pro-Varianten-Tabellen** entfernt, nicht nur aus der Summe (Kommentar im Code behauptet nur Summenausschluss) | Übernahme-Feature/Report | Implementierung geht über dokumentierten Scope hinaus — Daten sind vorhanden, werden aber nicht angezeigt |
| B14 | `lib/qaf-differences/internal/workbook-adapter.ts:283-312` (`parseQafFile`) i.V.m. `lib/qaf-parser.ts:713-720` | `Promise.all([parseSummarySheet, parseQAFTemplate])` ohne try/catch; `parseQAFTemplate` wirft bei fehlenden `CORE_MANUFACTURING_FIELD_KEYS` | Wenn NUR das Fertigungskosten-Sheet die 3 Kernspalten nicht matched, reißt die GESAMTE `parseQafFile`-Promise — auch die bereits erfolgreich geparsten Summary-Daten gehen verloren | Extraktion (Kopplungs-Bug) | Ein legitimer Einzelmodul-Check (B18/lokal LEGITIM) wird durch `Promise.all`-Kopplung zum globalen Blocker für ein unabhängiges Datenmodul — genau die „ein fehlendes Feld sperrt ein ganzes Modul"-Anti-Pattern aus §0.3 Punkt 6 |
| B15 | `components/qaf-differences/qaf-comparison-detail.tsx:769-770` | `<PlaceholderSection title={T.prod} reason={SUMMARY_PLACEHOLDERS.prod}/>` — **unbedingt** gerendert im Summary-Modus, kein Check ob das jeweilige File tatsächlich Zykluszeit/MSS/Personal-Daten im Fertigungskosten-Sheet hat | Sektion 6 „Produktionssicht" für JEDEN Summary-Vergleich pauschal als „gibt's nur bei G60" ausgeblendet | UI-Anzeige | Textbuch-BLOCKIEREND aus §0.3 Punkt 4: „Returning empty production data because the workbook is not classified as G60" — Fertigungskosten-Zeilen (inkl. evtl. vorhandener Raten-/Zeit-Spalten) werden durch `movers`/`steps` bereits geparst, aber nie in dieser Sektion genutzt |
| B16 | `qaf-comparison-detail.tsx:770,863` | analog: `T.anomalies` (SUMMARY_PLACEHOLDERS.anomalies, „kein INPUT-Blatt mit Master-Sätzen") + `T.scenario` (SUMMARY_PLACEHOLDERS.scenario) unbedingt gerendert | Sektion 7 (Anomalien/Treiber) und 11 (Szenario-Editor) für jeden Summary-Vergleich kategorisch deaktiviert | UI-Anzeige | wie B15 — Modus statt Datenlage entscheidet |
| B17 | `qaf-g60-detail.tsx:215,318,367,368` | `<PlaceholderSection … reason={G60_PLACEHOLDERS.form\|negotiation\|onetime\|fazit}/>` unbedingt im G60-Modus | Sektionen 2, 9, 13, 14 für jeden G60-Vergleich kategorisch deaktiviert | UI-Anzeige | Spiegelbild von B15/B16, geringeres Risiko (G60-Dateien haben strukturell selten ein Zusammenfassungs-Formular) aber gleiches Architekturmuster — sollte durch Datenverfügbarkeits-Check ersetzt werden |

### 2.2 LEGITIM (27 Fundstellen, Auswahl mit Beleg — vollständige Liste in Anhang, s.u.)

| Datei:Zeile | Bedingung | Kategorie | Begründung |
|---|---|---|---|
| `g60/parser.ts:37` (`MASTERS`) | 4 bekannte Nicht-Kosten-Sheets ausgeschlossen | Extraktion | Gezielter False-Positive-Fix, keine Kapazitätssperre |
| `g60/structure-guard.ts:284-297` (`locateAnchor`) | Spalten-Relokation ±1/±2 → genutzt, nicht gesperrt | Normalisierung | Generalisierung weg von starrer Koordinate |
| `material-parser.ts:458` / `sbm-parser.ts:453` (`headerIdx===null`) | Kein Header-Score ≥5 in 20 Zeilen gefunden | Extraktion | Echte „keine plausible Struktur"-Prüfung |
| `qaf-type-detector.ts:391-400` (`confirmed_multi_qaf`) | Multi-QAF-Upload wird komplett abgelehnt statt geparst | Übernahme-Feature | Dokumentierte Capability-Grenze: Single-Value-Parser kann Mehrspalten-Varianten strukturell nicht disambiguieren; **Caveat:** sobald P1+ Multi-QAF-Parsing produktiv ist, wird dies zu BLOCKIEREND, da die Daten dann tatsächlich vorhanden UND extrahierbar sind |
| `foreign-form-detection.ts:414-422` | WAF/LAF/LEK-Signaturtreffer → Ablehnung | Übernahme-Feature | Echter Formatunterschied, kein QAF |
| `summary-parser.ts:132-153` (`labelScan`) | `hits.length !== 1` → `null` | Extraktion | Ambiguitäts-Guard (2+ Kandidatenzellen), kein Raten |
| `comparison-mode.ts:80-151` | `exportSupported/pinsSupported/swapSupported: false` je Modus | Export/Übernahme-Feature | Jeder ausgeschlossene Modus hat einen eigenen dedizierten Export (`exportQafG60ComparisonXlsx` etc.) — keine Daten verloren, nur Routing |
| `comparison-mode.ts:82-131` (`expectedKind`) | Replace-Upload-Kind-Mismatch abgelehnt | Extraktion | Input-Validierung für bereits etablierte Vergleichsrolle |
| `rule-engine.ts:763-776`+`compare.ts:378,397-406` (`blockedStepFields`) | Feld als „blockiert" markiert bei R2-Pflichtfeldverletzung | Normalisierung | Datenlage-basiert, nicht Template; zudem aktuell inaktiv (`ruleEnforcement` Default = `'warn'`) |
| `reconciliation.ts:601-604,727-730` | Sheet degradiert → `nicht_pruefbar` statt „bestanden" | Normalisierung | Verhindert False-Positive „sauber", nicht Datenunterdrückung |
| `business-rules.ts` (alle `evaluate*Check`) | `actual===null → return null` | Normalisierung | Zelle wirklich leer, kein Label-/Typ-Gate |
| `explain-provenance.ts:295-312` | `row===null` → alle Attribute `not_applicable` | UI-Anzeige | Strukturelle Tatsache (kein Match), kein Label |
| `summary-view.ts:86-89,192-194,261-264` | KPI/Bucket/Bridge nur bei vorhandenen Metrikwerten gerendert | UI-Anzeige | Datenlage-basiert |
| `field-mapping-override.ts:132-134` | Identitäts-Anker nicht override-fähig | Übernahme-Feature | Anti-Poisoning-Schutz |
| `workbook-safety.ts` (Zip-Ratio-Guard) | Zip-Bomb/Übergröße → Ablehnung vor Extraktion | Sicherheit | Kein Template-Bezug |
| `multi-qaf/variant-reconciliation.ts:509-521` | Fingerprint fehlt/mismatch → `nicht_pruefbar` | Normalisierung | Mitgliedschafts-/Provenienz-Guard gegen Fehlmatches |
| `multi-qaf/container-assembly.ts:825-834` | Identität aus MATERIAL-Fallback statt SUMMARY → Profile-Binding übersprungen | Extraktion | Echte Cross-Sheet-Vorbedingung, dokumentiert als P2.2-Folgearbeit, gewarnt statt still verworfen |
| `multi-qaf/variant-vs-standard.ts` (`DEGRADED_MODULES`, 12 von 14) | Virtuelle Variante hat keine Prozesszeilen-Äquivalenz | Übernahme-Feature | Echte Strukturlücke, nicht Klassifikations-Flag |
| `multi-qaf/*-differ.ts` (ambiguous/split/merge) | Variantenpaar mit unsicherem Match ausgeschlossen | Extraktion | KAR-936-Matching-Unsicherheit, surfaced statt versteckt |
| `lib/qaf-parser.ts:669-673,677-681,713-720` | Near-empty-Sheet / Header-Score / Kernfelder → Exception | Extraktion | Isoliert betrachtet legitime Zeilenidentitäts-Anforderung (siehe aber B14 für den Kopplungsfehler) |
| `app/qaf-differences/actions.ts:2039,2186` | `comparison_mode !== 'g60'/'multi_qaf'` → Export-Route verweigert | Export | Leitet korrekt zum modusspezifischen Export, keine Datensuppression |

### 2.3 HINT (21 Fundstellen, Auswahl)

| Datei:Zeile | Bedingung | Begründung |
|---|---|---|
| `template-fingerprint.ts:614-618` (`facetStatus`) | known/modified/unknown nur für `PlausibilityIssue` (Severity `pruefen`), gate-frei | reine Deskription |
| `template-fingerprint.ts:657-687` (G60-Facet) | dito | reine Deskription |
| `candidate-sheet-plausibility.ts:143-166` | nur `ignoredCandidateSheets[].plausibleData`-Flag, nie extraktionswirksam | UI-Hinweis, KAR-927-Fix |
| `material-parser.ts:222-225` u.ä. (`isXSheetName`) | reine Sheet-Routing-Disambiguierung MATERIAL vs. RMR | keine Suppression |
| `module-degradation.ts:83-92` | surfaced nur, was bereits (in B5-B11) verworfen wurde | Konsument, nicht Erzeuger des Gates |
| `qaf-type-detector.ts:185-193` (Schwellenwerte) | Realkorpus-kalibriert, dokumentiert, konservativ Richtung `standard_qaf` | |
| `summary-metrics.ts:352-354` (`pickTemplate`) | setzt nur Such-Zentrum eines ±3-Zeilen-Fensters | Label muss trotzdem matchen |
| `comparison-mode.ts:159-162` (`normalizeComparisonMode`) | unbekannter Modus → `'summary'` (permissivster Default) | nicht suppressiv |
| `rule-engine.ts` (R3 hartcodiert `'warn'`) | dokumentierte Business-Entscheidung zur Severity, keine Datenunterdrückung | |
| `multi-qaf/template-fingerprint.ts:435-478` | `unknown_multi_qaf` nur Report-Feld | kein Downstream-Konsument sperrt darauf |
| `qaf-differences-client.tsx` (Mode-Badges) | reine Anzeige-Icons/Labels | |
| `qaf-provenance.tsx:265` (`classification==='known'`) | nur Anzeige-Kurzform | |
| `qaf-g60-detail.tsx:76-77`/`qaf-comparison-detail.tsx:1202-1203` (Badge-Farben) | reine Farbcodierung | |

---

## 3. Root-Cause-Klassen für leere Felder (§10) — Bezug zu `batch-report.md`

| Symptomklasse (Batch-Befund) | Betroffene Dateien | Root-Cause-Gate(s) |
|---|---|---|
| 37 Dateien, 0 Fertigungskosten-Zeilen ("Keine passenden Spalten gefunden") | 37 (30× `standard_summary`, 3× `confirmed_multi_qaf`, 2× `ambiguous`) | **B14** (`Promise.all`-Kopplung in `workbook-adapter.ts`) verstärkt durch **legitim isolierten** Header-Kernfeld-Check in `lib/qaf-parser.ts:713-720` — sobald `Prozessbezeichnung`/`FK`/`Positionsnummer`-Label abweicht, reißt der GESAMTE Parse inkl. Summary-Daten. Zusätzlich verschluckt `module-degradation.ts` das Ergebnis nur als Boolean statt Teildaten zu erhalten (Analogie zu B5-B11-Muster) |
| BMW G70/I20 (2024/2025, 5× `.xlsm`): `unknown`-Fingerprint, kein G60, kein Multi-QAF-Signal | 5 | **B1/B2** (`detectG60`/`detailTabs` — neue Template-Generation nutzt vermutlich andere Sheet-/Tab-Namen als `INPUT`/`N_2`) → routet als `standard_summary` → trifft dann zusätzlich **B14** (Fertigungskosten-Header nicht erkannt) → 0 Steps, keine Detailtiefe sichtbar |
| RMR-Facet-Coverage <0.5 bei 345/354 Dateien | 345 | **B7**-Musterverwandt: `rmr-parser.ts` Block-Kernfeld-Gate + wahrscheinlich unvollständige Sheetnamen-Alias-Liste in `module-sheet-names.ts` (Kalibrierungslücke, nicht im Detail geprüft — Empfehlung: RMR-Sheetnamen-Aliase gegen Realkorpus nachziehen) |
| SUMMARY-Facet nie `known` (`knownCoverageThreshold=1` unerreichbar, 356/356 in `modified`) | 356 | **HINT**, kein Datenverlust — `template-fingerprint.ts:614-618` klassifiziert nur, blockiert nichts (verifiziert: kein Downstream-Konsument sperrt auf `known`/`modified`). Der Schaden ist rein diagnostisch (Signal „modified" ist entwertet), keine unterdrückten Felder. Trotzdem KAR-würdig für Kalibrierung, aber NICHT Teil der BLOCKIEREND-Migration |
| Legacy-`.xls` (BIFF) nicht ladbar (2 Dateien) | 2 | Kein Gate — ExcelJS ist strukturell zip-only, echte technische Grenze, nicht Teil dieses Audits (niedrige Priorität laut `batch-report.md`) |
| Summary-Vergleiche: Sektionen „Produktionssicht"/„Anomalien"/„Szenario-Editor" immer leer, auch wenn Fertigungskosten-Sheet Zeitraten enthält | potenziell alle 358 `standard_summary`-Dateien | **B15/B16** (`qaf-comparison-detail.tsx` unbedingte Placeholder) — unabhängig vom tatsächlichen Dateninhalt der bereits erfolgreich geparsten `steps` |

---

## 4. Ersetzungs-Reihenfolge-Empfehlung

Ziel: maximaler Feld-Gewinn pro Aufwand, ohne die 0-False-Intercepts-Regression zu reißen (bestätigtes Verhalten laut Testsuite: `qaf-type-detector.test.ts` S1–S4-Entscheidungstabelle, `template-fingerprint.test.ts:634` „never blocks", `parser-degradation.test.ts` Degradations-statt-Crash-Pfad, `comparison-mode.test.ts` Export-Routing-Verträge — diese Tests MÜSSEN nach jeder Änderung weiterhin grün bleiben; zusätzlich ist `qaf-candidate-visibility.real-files.test.ts` env-gated und sollte für die Validierung explizit mit vorhandenem Input-Verzeichnis aktiviert werden).

1. **B14 (Promise.all-Kopplung, `workbook-adapter.ts:283-312`)** — höchster Hebel, geringstes Risiko: Summary- und Fertigungskosten-Parse entkoppeln (`Promise.allSettled` oder try/catch je Zweig), damit ein Fertigungskosten-Fehler nicht mehr die bereits erfolgreichen Summary-Daten mitreißt. Betrifft potenziell alle 37 „0-Steps"-Dateien plus jede zukünftige Datei mit abweichendem Fertigungskosten-Header. Kein bestehender Test verlangt das aktuelle Alles-oder-nichts-Verhalten (Lücke im Testnetz selbst dokumentiert von der Cross-Sweep-Analyse).
2. **B5–B11 (`coreFieldsFound`-All-or-nothing in material-/sbm-/rmr-/logistics-/lccn-/co2e-parser.ts)** — identisches, 6× repliziertes Muster; ein einziger Fix (Rückgabe der bereits gemappten Teilspalten statt `[]`, analog zum bereits reparierten `candidate-sheet-plausibility.ts`-Vorbild) hebt alle sechs gleichzeitig. Zweithöchster Hebel, mittleres Risiko (Downstream-Konsumenten von `coreFieldsFound:false` — u.a. `module-degradation.ts` — müssen weiterhin korrekt „degradiert" statt „vollständig" anzeigen).
3. **B15–B17 (UI-Placeholder in `qaf-comparison-detail.tsx`/`qaf-g60-detail.tsx`)** — sichtbarster Nutzerimpact, aber isoliert und risikoarm: `PlaceholderSection` durch echten Datenverfügbarkeits-Check ersetzen (z.B. „zeige Sektion 6 nur leer, wenn `steps` wirklich keine Zeitraten-Spalten enthält" statt „wenn `comparison_mode !== 'g60'`"). Kann unabhängig von 1–2 ausgerollt werden, sobald B14/B5-B11 mehr Rohdaten liefern.
4. **B1–B4 (G60-Detektion/Structure-Guard)** — höchster Aufwand, höchstes Risiko: `detectG60`/`detailTabs` von starren Namen auf Capability-Signale umstellen (z.B. „hat die Datei Kostenreiter-artige Sheets mit Zeile-15-38-Struktur, unabhängig vom Namen") und `structure-guard.ts`s Tab-/File-weite Exklusion auf die tatsächlich betroffenen Spalten/Felder verengen. Dies berührt die BMW-G70/I20-Neugeneration (5 Dateien) am direktesten, erfordert aber neue Referenzdateien zur Kalibrierung — als letztes einplanen, mit eigenem Holdout-Test gegen `g60_meta.excludedTabs`-Semantik (siehe `template-fingerprint.test.ts:321`, das bestehende Verhalten explizit pinnt).
5. **B12 (`summary-metrics.ts` LEGACY-Template fehlender Fallback)** und **B13 (`aggregate-impact.ts` Pro-Varianten-Tabellen)** — kleine, chirurgische Einzel-Fixes, jederzeit einschiebbar, kein Reihenfolge-Zwang gegenüber 1–4.

Nicht in die Migration aufnehmen: alle als LEGITIM/HINT eingestuften Gates — insbesondere `comparison-mode.ts`s Export/Pins/Swap-Matrix (dediziertes Routing, kein Datenverlust) und `qaf-type-detector.ts`s Multi-QAF-Volltextablehnung (echte Capability-Grenze bis P1+ Varianten-Parsing produktiv ist — danach neu bewerten).
