// Canonical QAF field registry — data (KAR-892 / P1.1 fundament).
//
// This is the versioned, code-config field catalogue the backlog item calls
// for ("Feldregistry zunächst als versionierte TS-Datenstruktur ... generisch
// für beliebige Module"). Pure data — canonical-model.ts holds the access
// functions (findByAlias/byModule/byCanonicalId/validateRegistry).
//
// ── Coverage per module (read this before trusting a field's completeness) ──
//
//   SUMMARY        FULL for the 13 identity fields (QafSummary) + 20 money
//                  metrics (SummaryMetricKey) Kadi-v2 now parses — labels/
//                  aliases lifted straight from rule-engine.ts SUMMARY_FIELD_
//                  LABELS and summary-metrics.ts SYNONYMS (already verified
//                  against real files). PLUS a CORE (not yet parsed by
//                  Kadi-v2 — no QafSummary/SummaryMetricKey slot exists)
//                  block for the REMAINING "Allgemeine Prämissen" /
//                  "Prämissen Lieferant" / "Sonstige Daten" fields (Leitfaden
//                  S.10-12), included because they belong to the same
//                  already-parsed SUMMARY sheet and the Leitfaden tabulates
//                  them completely — flagged `notes: 'not parsed by Kadi-v2
//                  today'` per entry. KAR-910 (fehlerreport-analyse.md §5
//                  Fälle #10/#12/#14/#16/#23) promoted 4 of these premise
//                  fields (sum_peak_volume_year/sum_production_start_sop/
//                  sum_delivery_site/sum_shifts_per_week — QafSummary went
//                  from 9 to 13 fields) plus 1 net-new, Leitfaden-undocumented
//                  money metric (sum_cost_breakdown_aw1 — SUMMARY_METRIC_KEYS
//                  went from 19 to 20) from registry-only to actually parsed;
//                  see each entry's `notes` for the exact evidence.
//   MANUFACTURING  FULL — all 22 QAFFieldKey values, DE+EN labels taken
//                  verbatim from qaf-parser.ts HEADER_TO_KEY (both label
//                  variants are already shipped/verified against real BMW // allow-customer-string
//                  files, not a translation guess).
//   G60            CORE — the 5 real-file-verified header-row anchors
//                  (structure-guard.ts TAB_HEADER_ANCHORS, verified against 2
//                  real G60 workbooks 2026-07-09) + the 8 INPUT!B22-B29
//                  SG&A/profit rate-card labels (also real-file-verified, see
//                  structure-guard.ts module header). Deliberately NOT the
//                  full ~14-column fixed-coordinate extraction (parser.ts
//                  extractFullTab) — those columns have no independently
//                  verified label text yet (P0.3 module header: "keine echten
//                  BMW-G60-Testdateien im Repo" for most of them); adding // allow-customer-string
//                  unverified labels here would misrepresent them as
//                  Leitfaden-sourced when they are not. G60 is a separate
//                  BMW-internal detail-costing tool, not a sheet documented // allow-customer-string
//                  in the general QAF-Leitfaden — its evidence is code
//                  (parser.ts/structure-guard.ts), not Leitfaden pages.
//   MATERIAL       CORE, but Leitfaden S.18-20 tabulates the sheet's fields
//                  completely, so this is effectively near-full for the
//                  fields BMW documents (28 Leitfaden entries + 1 // allow-customer-string
//                  Fehlerreport-only aggregate field added KAR-910,
//                  mat_packaging_cost_offer, see that entry's `notes`).
//                  Requirement (mandatory/conditional/optional) is NOT // allow-customer-string
//                  explicitly Pflicht-tagged per field in the Leitfaden for
//                  this module (unlike SBM, which has an explicit Pflicht
//                  column) — every MATERIAL entry below defaults to
//                  'optional' unless the Leitfaden text explicitly says
//                  "nur Rohmaterial"/"bedingt" (-> 'conditional'). Do not
//                  read 'optional' here as "BMW says this is optional" — // allow-customer-string
//                  read it as "not yet Fachlich klassifiziert", pending
//                  P1.6.
//   SBM            CORE (35 Leitfaden entries + 2 added KAR-910 —
//                  sbm_anzahl_stufen_takte_aktiv/_leer, splitting the
//                  Aktiv/Leer sub-columns the Leitfaden's own screenshot [38]
//                  already shows under the combined sbm_anzahl_stufen_takte
//                  header group, see that entry's `notes`), same near-full
//                  situation as MATERIAL — Leitfaden S.34-37 DOES have an
//                  explicit Pflicht/optional/bedingt column, so `requirement`
//                  below is a genuine Leitfaden-sourced classification for
//                  this module.
//   LOGISTICS, RMR, LC_CN, CO2E, WAF, LAF, LEK
//                  CORE representative subsets (12-17 entries each) of
//                  modules Kadi-v2 does not parse at all today (P1.6/P1.7/
//                  P2.x territory) — chosen to cover each module's identity/
//                  header fields plus its 1-2 defining cost/result fields,
//                  not every column the Leitfaden tabulates. `requirement`
//                  defaults to 'optional' (no Pflicht column documented for
//                  these modules in the extracted Leitfaden sections) unless
//                  the Leitfaden text says "bedingt".
//
// ── EN-label verification status (be honest about this, KAR-892 instruction) ─
//
//   SUMMARY + MANUFACTURING: EN labels are code-verified (HEADER_TO_KEY,
//   already matched against real BMW QAF v8/v9 EN customer files). // allow-customer-string
//   Every other module: the Leitfaden extraction used for this PR only
//   tabulates GERMAN field names (the source document's field tables are
//   DE-only outside the two already-shipped dictionaries) — EN labels below
//   are a working English translation of the DE label/description, NOT
//   verified against a real shipped English-language QAF template for that
//   module. templateVersions for those modules stays scoped to the DE
//   template tag only (no 'V8_EN'/'V9_EN' claim) to make this explicit.
//
// ── Documented BMW source-data typos/duplicates (Master-Prompt §6 requirement) ─ // allow-customer-string
//
//   - "manufactering" (sheet-name typo, MANUFACTURING module) — already
//     handled by qaf-parser.ts's sheet-name matcher (`n.includes('manufactering
//     cost')`), recorded as a sheet-name alias (not a field alias, it is a
//     sheet name not a field label) in the centralized DE/EN sheet-name
//     registry: ./module-sheet-names.ts's MODULE_SHEET_NAME_ALIASES
//     (KAR-905/P3.1 — every module's isXxxSheetName evidence lives there now,
//     this file used to carry its own single-module copy before P3.1).
//   - "sucharge" (G60 column Y typo for "surcharge") — alias on
//     g60_raw_material_surcharge_y, sourced to structure-guard.ts.
//   - "Teilebennung" (typo for "Teilebenennung", MATERIAL + duplicated 2x per
//     fehlerreport-analyse §3.3) — alias on mat_part_designation.
//   - "Verrechungsform" (typo for "Verrechnungsform", SBM) — alias on
//     sbm_verrechnungsform.
//   - "Produdktionsstart" (typo for "Produktionsstart", SUMMARY premises) —
//     alias on sum_production_start_sop.
//   - Real duplicate "Mengeneinheit" label in MATERIAL (fehlerreport-analyse
//     §3.3/§4.2 R6: row #31 is the genuine "Mengeneinheit"/Unit-of-Measure
//     field; row #36 is a label-copy error where the REAL field is
//     "Materialgemeinkosten MGK" but the BMW file's own column literally // allow-customer-string
//     reads "Mengeneinheit" too) — modeled as an intentional collision via
//     `collisionGroup: 'material_mengeneinheit_label_duplicate'` on both
//     mat_unit_of_measure and mat_overhead_cost, exactly the case KAR-892
//     asks findByAlias()/validateRegistry() to support without forbidding.
//
// tdd-guard:skip — pure constant data, same category as summary-metrics.ts's
// SYNONYMS/TEMPLATE_CONFIG and rule-engine.ts's STEP_FIELD_LABELS. Structural
// invariants (uniqueness, evidence, DE+EN on mandatory fields, backward-
// mapping completeness) are exercised by __tests__/canonical-model.test.ts.

import type { QAFFieldKey } from "@/lib/qaf-parser"
import type { SummaryMetricKey } from "./summary-metrics"
import type { MaterialFieldKey } from "./material-parser"
import type { SbmFieldKey } from "./sbm-parser"
import type { RmrFieldKey } from "./rmr-parser"
import type { LogisticsFieldKey } from "./logistics-parser"
import type { LccnFieldKey } from "./lccn-parser"
import type { Co2eFieldKey } from "./co2e-parser"
import type {
  CanonicalClassification,
  CanonicalDataType,
  CanonicalField,
  CanonicalFieldEvidence,
  CanonicalLevel,
  CanonicalRequirement,
  QafModule,
} from "./canonical-fields.types"

// ── Evidence-reference helpers (compact citations, see header) ──────────────

function L1(page: number, note?: string): CanonicalFieldEvidence {
  return note
    ? { source: `leitfaden-teil1:${page}`, note }
    : { source: `leitfaden-teil1:${page}` }
}
function L2(page: number, note?: string): CanonicalFieldEvidence {
  return note
    ? { source: `leitfaden-teil2:${page}`, note }
    : { source: `leitfaden-teil2:${page}` }
}
function L3(page: number, note?: string): CanonicalFieldEvidence {
  return note
    ? { source: `leitfaden-teil3:${page}`, note }
    : { source: `leitfaden-teil3:${page}` }
}
function CODE(path: string, note?: string): CanonicalFieldEvidence {
  return note ? { source: `code:${path}`, note } : { source: `code:${path}` }
}
function FEHLER(section: string, note?: string): CanonicalFieldEvidence {
  return note
    ? { source: `fehlerreport-analyse:${section}`, note }
    : { source: `fehlerreport-analyse:${section}` }
}
// KAR-915: V8.8 Template-Generation (Vorgaenger des V9-Leitfadens) header-
// label evidence. Extracted from BOTH real V8.8 NCAR-Korpus files (server-
// only, /root/aria/work/qaf-compare-kar824/input/...) via a header-row-only
// script (never printed/committed any row below the header) — every alias
// cited with this helper is a verbatim column-header LABEL TEXT (a template
// constant), never a supplier value/name/price. `detail` names which of the
// two files (or both) the label was read from, for reviewer traceability.
function V88(detail: string): CanonicalFieldEvidence {
  return { source: 'v88-template-generation', note: `V8.8-Template, real-file-verified (server-only NCAR-Korpus) — ${detail}` }
}

// ── Template-version tags ────────────────────────────────────────────────────

const TV_SUMMARY = ["QAF_LEGACY_DE_SUMMARY", "QAF_V9_SUMMARY"] // matches summary-metrics.ts SummaryTemplateType, code-verified
const TV_MFG = ["V9_DE_TEMPLATE", "V8_EN"] // matches qaf-parser.ts HEADER_TO_KEY, code-verified against real files
const TV_G60 = ["G60_DETAIL_QAF"] // BMW-internal detail-costing workbook, no Leitfaden version tag — see structure-guard.ts // allow-customer-string
const TV_DE_ONLY = ["V9_DE_TEMPLATE"] // Leitfaden-DE-sourced only, EN label is an unverified working translation (see header)

// ── Field-spec helper (defaults aliases to []) ───────────────────────────────

interface FieldSpec {
  id: string
  module: QafModule
  section: string
  labelDe: string
  labelEn: string
  aliases?: string[]
  dataType: CanonicalDataType
  unit?: string
  classification: CanonicalClassification
  requirement: CanonicalRequirement
  level: CanonicalLevel
  templateVersions: string[]
  evidence: CanonicalFieldEvidence[]
  collisionGroup?: string
  notes?: string
}

function f(spec: FieldSpec): CanonicalField {
  return { aliases: [], ...spec }
}

// ── SUMMARY — identity fields (QafSummary, 9, FULL, parsed) ─────────────────

const SUMMARY_IDENTITY_FIELDS: CanonicalField[] = [
  f({
    id: "sum_part_number",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "BMW Sachnummer", // allow-customer-string
    labelEn: "BMW part number", // allow-customer-string
    dataType: "text",
    classification: "protected",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
    notes: "Primary comparison identity (compare.ts). // allow-customer-string",
  }), // allow-customer-string
  f({
    id: "sum_quotation_date",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "Angebotsdatum",
    labelEn: "Quotation date",
    dataType: "date",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
  }),
  f({
    id: "sum_supplier",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "Anbieter/Lieferant",
    labelEn: "Supplier",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
    notes:
      "Deliberately not in MANDATORY_SUMMARY_FIELDS (rule-engine.ts) today — frequently unset in existing fixtures.",
  }),
  f({
    id: "sum_part_name",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "Teilebenennung",
    labelEn: "Part designation",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
  }),
  f({
    id: "sum_variant",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "Variante",
    labelEn: "Variant",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [
      L1(10, 'explicitly "optional" in Leitfaden text'),
      CODE("rule-engine.ts:SUMMARY_FIELD_LABELS"),
    ],
  }),
  f({
    id: "sum_project",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "BMW Projekt", // allow-customer-string
    labelEn: "BMW project", // allow-customer-string
    dataType: "text",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
  }), // allow-customer-string
  f({
    id: "sum_request_version",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "Anfragenummer/Version",
    labelEn: "Request number/version",
    dataType: "text",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
  }),
  f({
    id: "sum_change_index",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "Änderungsindex (AI)",
    labelEn: "Change index (AI)",
    dataType: "text",
    unit: "Zähler bis 99",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
  }),
  f({
    id: "sum_supplier_number",
    module: "SUMMARY",
    section: "Allgemeine Produktdaten",
    labelDe: "BMW Lieferanten-Nr.", // allow-customer-string
    labelEn: "BMW supplier number", // allow-customer-string
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(10), CODE("rule-engine.ts:SUMMARY_FIELD_LABELS")],
    notes:
      "Deliberately not in MANDATORY_SUMMARY_FIELDS today, see rule-engine.ts comment.",
  }), // allow-customer-string
]

// ── SUMMARY — 19 money metrics (SummaryMetricKey, FULL, parsed) ─────────────

const SUMMARY_METRIC_FIELDS: CanonicalField[] = [
  f({
    id: "sum_material_costs",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Materialkosten",
    labelEn: "Material costs",
    aliases: ["Material cost"],
    dataType: "currency",
    classification: "calculated",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_manufacturing_costs",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Fertigungskosten",
    labelEn: "Manufacturing costs",
    aliases: ["Manufacturing cost"],
    dataType: "currency",
    classification: "calculated",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_total_production_costs",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "SUMME HERSTELLKOSTEN",
    labelEn: "Total production costs",
    aliases: ["Summe Herstellkosten", "Herstellkosten"],
    dataType: "currency",
    classification: "calculated",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
    notes: "Formula: Materialkosten + Fertigungskosten (Leitfaden S.12).",
  }),
  f({
    id: "sum_packaging_transport_included",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Enthaltene Verpackung und Transport",
    labelEn: "Included packaging and transport",
    aliases: ["Verpackung und Transport", "Packaging and transport"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_customs_included",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Enthaltene Zölle",
    labelEn: "Customs included",
    aliases: ["Included customs"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_devices_and_tools",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Vorrichtungen und Folgewerkzeuge",
    labelEn: "Devices and tools",
    aliases: ["Devices and follow-up tools"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_scrap_material",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Ausschusskosten Material",
    labelEn: "Scrap costs material",
    aliases: ["Ausschusskosten", "Scrap costs"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_scrap_manufacturing",
    module: "SUMMARY",
    section: "Detaillierung Gesamtkosten",
    labelDe: "Ausschusskosten Fertigung",
    labelEn: "Scrap costs manufacturing",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(12), CODE("summary-metrics.ts:parseSummaryMetrics")],
    notes:
      "No own label anchor — read from the row below scrapMaterial (V11 port).",
  }),
  f({
    id: "sum_total_costs",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "GESAMTKOSTEN",
    labelEn: "Total costs",
    aliases: ["Gesamtkosten", "TOTAL COSTS"],
    dataType: "currency",
    classification: "calculated",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_other_surcharges",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "Sonstige Zuschläge",
    labelEn: "Other surcharges",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
    notes:
      '"Als fixer Wert einzutragen" — not a formula field despite living in the calculated block.',
  }),
  f({
    id: "sum_quotation_base_price",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "ANGEBOTSBASISPREIS",
    labelEn: "Quotation base price",
    aliases: ["Angebotsbasispreis", "QUOTATION BASE PRICE"],
    dataType: "currency",
    classification: "calculated",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
    notes: "Formula: Gesamtkosten + Sonstige Zuschläge (Leitfaden S.13).",
  }),
  f({
    id: "sum_raw_material_price_share_material",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "Rohstoff Preisanteil Material",
    labelEn: "Raw material price share material",
    aliases: ["Rohstoff Preisanteil", "Raw material price share"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_raw_material_price_share_energy",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "Rohstoff Preisanteil Energie",
    labelEn: "Raw material price share energy",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: ["QAF_V9_SUMMARY"],
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
    notes:
      "Absent on QAF_LEGACY_DE_SUMMARY (no row prior for it, see TEMPLATE_CONFIG).",
  }),
  f({
    id: "sum_customs_supplier_to_bmw",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "Zölle Lieferant - BMW", // allow-customer-string
    labelEn: "Customs supplier - BMW", // allow-customer-string
    aliases: ["Customs Supplier - BMW"], // allow-customer-string
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }), // allow-customer-string
  f({
    id: "sum_transport_supplier_to_bmw",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "Transportkosten Lieferant - BMW", // allow-customer-string
    labelEn: "Transport costs supplier - BMW", // allow-customer-string
    aliases: ["Transport costs Supplier - BMW", "Transport supplier BMW"], // allow-customer-string
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }), // allow-customer-string
  f({
    id: "sum_quotation_price",
    module: "SUMMARY",
    section: "Detaillierung Angebotspreis",
    labelDe: "ANGEBOTSPREIS",
    labelEn: "Quotation price",
    aliases: ["Angebotspreis", "QUOTATION PRICE", "Offer price"],
    dataType: "currency",
    classification: "calculated",
    requirement: "mandatory",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
    notes:
      "Formula: Angebotsbasispreis + Rohmaterial Preisgleitklausel + Zoll/CBAM + Logistikkosten (Leitfaden S.13).",
  }),
  f({
    id: "sum_one_time_development",
    module: "SUMMARY",
    section: "Detaillierung Einmalaufwendungen",
    labelDe: "Entwicklungskosten",
    labelEn: "Development costs",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_one_time_tools",
    module: "SUMMARY",
    section: "Detaillierung Einmalaufwendungen",
    labelDe: "Sonderbetriebsmittel",
    labelEn: "Special operating resources",
    aliases: ["Special tooling"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_total_one_time_payment",
    module: "SUMMARY",
    section: "Detaillierung Einmalaufwendungen",
    labelDe: "GESAMTEINMALZAHLUNG",
    labelEn: "Total one-time payment",
    aliases: ["Gesamteinmalzahlung", "TOTAL ONE-TIME PAYMENT"],
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [L1(13), CODE("summary-metrics.ts:SYNONYMS")],
  }),
  f({
    id: "sum_cost_breakdown_aw1",
    module: "SUMMARY",
    section: "Kostenaufschlüsselung (Fehlerreport-only)",
    labelDe: "Cost Breakdown AW1",
    labelEn: "Cost Breakdown AW1",
    dataType: "currency",
    classification: "input",
    // KAR-910/fehlerreport-analyse.md §5 Fall #23: NOT documented anywhere
    // in the Leitfaden's exhaustive SUMMARY cost-block enumeration
    // (leitfaden-teil1.md [10]-[14], the "Kostenpositionen 1-10"/
    // "Detaillierung Angebotspreis"/"Detaillierung Einmalaufwendungen"
    // tables list every SUMMARY money field BMW documents, and this label is // allow-customer-string
    // not among them) — the only evidence is the real Fehlerreport row
    // itself (§2 row #23, notwendig=Ja/Yes on both DE/EN). Since the exact
    // sheet row is unverified (no Leitfaden coverage, no real-file cell
    // coordinate confirmed), classifying this 'mandatory' would let R2
    // (Pflichtfeld-Blockade) fire/block on a field the engine can only
    // locate opportunistically — kept 'optional' per task instruction ("im
    // Zweifel optional mit Kommentar+Quelle") until a real file or Leitfaden
    // update confirms its actual sheet position and Pflicht status.
    requirement: "optional",
    level: "summary",
    templateVersions: TV_SUMMARY,
    evidence: [
      FEHLER("§5 Fall #23", "no Leitfaden coverage at all for this field — Fehlerreport-only evidence"),
      CODE("summary-metrics.ts:LABEL_COL", "located via the same column-G label-anchor convention as the other 19 SUMMARY metrics, but WITHOUT a row prior (locateRowUnanchored) — see summary-metrics.ts module comment"),
    ],
    notes:
      "Parsed since KAR-910 (SUMMARY_METRIC_KEYS.costBreakdownAw1) via an unanchored full-column-G label scan — no expected-row prior exists (unlike every other SUMMARY metric). classifyFieldState does NOT flag a negative value as provided_invalid today (Fehlerreport's EN 'inkorrekt' reason was negative values) — that would need a new business-rule check, deliberately out of scope for this registry-extension PR (task instruction: 'NICHT über das Ziel hinaus').",
  }),
]

// ── SUMMARY — Allgemeine Prämissen / Prämissen Lieferant / Sonstige Daten ────
// (CORE, not parsed by Kadi-v2 today — see module header)

const SUMMARY_PREMISES_FIELDS: CanonicalField[] = [
  f({
    id: "sum_series_runtime_years",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Serienlaufzeit [Jahre]",
    labelEn: "Series runtime [years]",
    dataType: "number",
    unit: "Jahre",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_total_volume_requested",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Gesamtvolumen gem. Anfrage [Teile]",
    labelEn: "Total volume per request [parts]",
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_peak_volume_year",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Peakvolumen Jahr [Teile / Jahr]",
    labelEn: "Peak volume per year [parts/year]",
    // "Peak Volume" is the real EN Fehlerreport column text (fehlerreport-
    // analyse §2 row #10, openpyxl-read from the actual EN Fehlerreport
    // sheet, not a translation guess) — added as an alias so
    // summary-parser.ts's label-scan matches both the Leitfaden-DE and the
    // real-file EN wording.
    aliases: ["Peak Volume"],
    dataType: "number",
    unit: "Stück/Jahr",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11), FEHLER("§5 Fall #10", "resolved KAR-910 — parsed via summary-parser.ts label-scan, no fixed A1 anchor known")],
    notes:
      "Parsed since KAR-910 via summary-parser.ts label-scan (types.ts QafSummary.peakVolumeYear) — was 'not parsed by Kadi-v2 today' before this PR, see fehlerreport-analyse.md §5 Fall #10.",
  }),
  f({
    id: "sum_planned_capacity",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Plankapazität [Teile / Jahr]",
    labelEn: "Planned capacity [parts/year]",
    dataType: "number",
    unit: "Stück/Jahr",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L1(11),
      { source: "reports/qaf-value-stream-corpus-evidence.md", note: "QVS-P1 corpus scan: 221/227 dev-split files carry this label (97%) — justified the QVS-P4 parser extension" },
    ],
    notes:
      "Parsed since QVS-P4/KAR-973 via summary-parser.ts label-scan (types.ts QafSummary.plannedCapacity) — was 'not parsed by Kadi-v2 today' before this PR. File-level only (no per-step equivalent); used for QVS Takt-/Kapazitäts-Kontext (lib/qaf-value-stream), never mapped onto a VsmNode step field.",
  }),
  f({
    id: "sum_lot_size",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Fertigungslosgröße [Teile]",
    labelEn: "Production lot size [parts]",
    // "Manufacturing lot size [parts]" is the real-file EN wording
    // qaf-value-stream-corpus-evidence.md's scan matched (distinct from this
    // entry's own Leitfaden-derived labelEn working translation) — added as
    // an alias, same "Leitfaden label stays primary" precedent as
    // sum_peak_volume_year's "Peak Volume" / sum_delivery_site's
    // "Delivery Location" aliases above.
    aliases: ["Manufacturing lot size [parts]"],
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L1(11),
      { source: "reports/qaf-value-stream-corpus-evidence.md", note: "QVS-P1 corpus scan: 220/227 dev-split files carry this label (97%) — justified the QVS-P4 parser extension" },
    ],
    notes:
      "Parsed since QVS-P4/KAR-973 via summary-parser.ts label-scan (types.ts QafSummary.lotSize) — was 'not parsed by Kadi-v2 today' before this PR. File-level only (no per-step equivalent); used for QVS Takt-/Kapazitäts-Kontext (lib/qaf-value-stream), never mapped onto a VsmNode step field.",
  }),
  f({
    id: "sum_production_start_sop",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Produktionsstart (SOP) [mm.jjjj]",
    labelEn: "Start of production (SOP) [mm.yyyy]",
    // "Produdktionsstart" is the documented BMW typo (§3.3); "Production // allow-customer-string
    // Start (SOP)" is the real EN Fehlerreport column text (§2 row #12).
    aliases: ["Produdktionsstart", "Production Start (SOP)"],
    dataType: "month_year",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L1(11),
      FEHLER("3.3", 'documented typo "Produdktionsstart"'),
      FEHLER("§5 Fall #12", "resolved KAR-910 — parsed via summary-parser.ts label-scan, no fixed A1 anchor known"),
    ],
    notes:
      "Parsed since KAR-910 via summary-parser.ts label-scan (types.ts QafSummary.productionStartSop) — was 'not parsed by Kadi-v2 today' before this PR, see fehlerreport-analyse.md §5 Fall #12.",
  }),
  f({
    id: "sum_remarks_open",
    module: "SUMMARY",
    section: "Allgemeine Prämissen",
    labelDe: "Offenes Bemerkungsfeld",
    labelEn: "Open remarks field",
    dataType: "freetext",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    collisionGroup: "summary_offenes_bemerkungsfeld_duplicate",
    notes:
      'not parsed by Kadi-v2 today. "Offenes Bemerkungsfeld" is a genuine, real Leitfaden duplicate label — it names a DIFFERENT remarks box in each of two SUMMARY sub-sections (Allgemeine Prämissen S.11 vs. Sonstige Daten S.12), not a copy error.',
  }),
  f({
    id: "sum_manufacturing_site",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "Fertigungsstandort (Land/Ort)",
    labelEn: "Manufacturing site (country/city)",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_delivery_site",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "Auslieferungsstandort (Land)",
    labelEn: "Delivery site (country)",
    // "Delivery Location" is the real EN Fehlerreport column text (§2 row
    // #14) — differs from the Leitfaden's working translation "Delivery
    // site", added as an alias rather than replacing labelEn (Leitfaden
    // wording stays the primary label, real-file wording is the alias, same
    // pattern as sum_peak_volume_year above).
    aliases: ["Delivery Location"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11), FEHLER("§5 Fall #14", "resolved KAR-910 — parsed via summary-parser.ts label-scan, no fixed A1 anchor known")],
    notes:
      "Parsed since KAR-910 via summary-parser.ts label-scan (types.ts QafSummary.deliverySite) — was 'not parsed by Kadi-v2 today' before this PR, see fehlerreport-analyse.md §5 Fall #14.",
  }),
  f({
    id: "sum_working_hours_per_year",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "Arbeitsstunden / Jahr [h]",
    labelEn: "Working hours / year [h]",
    dataType: "number",
    unit: "h",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_shifts_per_week",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "Schichten / Woche",
    labelEn: "Shifts / week",
    // "Shifts" is the real EN Fehlerreport column text (§2 row #16).
    aliases: ["Shifts"],
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11), FEHLER("§5 Fall #16", "resolved KAR-910 — parsed via summary-parser.ts label-scan, no fixed A1 anchor known")],
    notes:
      "Parsed since KAR-910 via summary-parser.ts label-scan (types.ts QafSummary.shiftsPerWeek) — was 'not parsed by Kadi-v2 today' before this PR, see fehlerreport-analyse.md §5 Fall #16.",
  }),
  f({
    id: "sum_quotation_currency_1",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "1. (Bestell-)Währung [AW1]",
    labelEn: "1st (order) currency [AW1]",
    dataType: "dropdown",
    unit: "ISO 4217",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_quotation_currency_2_3",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "2./3. Angebotswährung [AW2]/[AW3]",
    labelEn: "2nd/3rd quotation currency [AW2]/[AW3]",
    dataType: "dropdown",
    unit: "ISO 4217",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today. Optional multi-currency activation.",
  }),
  f({
    id: "sum_lta",
    module: "SUMMARY",
    section: "Prämissen Lieferant",
    labelDe: "LTA (Langzeitvereinbarung)",
    labelEn: "LTA (long-term agreement)",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(11)],
    notes: "not parsed by Kadi-v2 today. Years + reduction factor.",
  }),
  f({
    id: "sum_remarks_other",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "Offenes Bemerkungsfeld",
    labelEn: "Open remarks field",
    dataType: "freetext",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    collisionGroup: "summary_offenes_bemerkungsfeld_duplicate",
    notes:
      "not parsed by Kadi-v2 today. See sum_remarks_open — same genuine Leitfaden duplicate label, different sub-section.",
  }),
  f({
    id: "sum_hts_code_component",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "HTS-Code Komponente",
    labelEn: "HTS code component",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_part_dimensions",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "Teileabmessungen (LxBxH) [mm]",
    labelEn: "Part dimensions (LxWxH) [mm]",
    dataType: "text",
    unit: "mm",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_part_weight_unpacked",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "Teilegewicht unverpackt [kg]",
    labelEn: "Part weight unpacked [kg]",
    dataType: "number",
    unit: "kg",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    notes: "not parsed by Kadi-v2 today",
  }),
  f({
    id: "sum_total_weight",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "Gesamtgewicht [kg]",
    labelEn: "Total weight [kg]",
    dataType: "number",
    unit: "kg",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    notes: "not parsed by Kadi-v2 today. Part + packaging weight (gross).",
  }),
  f({
    id: "sum_delivery_terms",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "Lieferbedingungen",
    labelEn: "Delivery terms",
    dataType: "dropdown",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    notes:
      "not parsed by Kadi-v2 today. Auto-transferred from LOGISTICS&CUSTOM sheet.",
  }),
  f({
    id: "sum_supply_type_kovp",
    module: "SUMMARY",
    section: "Sonstige Daten",
    labelDe: "Versorgungsart/KOVP Abrufstatus",
    labelEn: "Supply type/KOVP call-off status",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(12)],
    notes: "not parsed by Kadi-v2 today. E.g. JIT, JIS-5000, JIS-5300.",
  }),
]

// ── MANUFACTURING — 22 QAFFieldKey values (FULL, parsed) ─────────────────────

const MANUFACTURING_FIELDS: CanonicalField[] = [
  f({
    id: "mfg_position_number",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Positionsnummer Fertigungsschritt",
    labelEn: "Item number Manufacturing step",
    dataType: "text",
    classification: "input",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_part_designation",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Teilebenennung",
    labelEn: "Parts designation",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_process_designation",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Prozessbezeichnung",
    labelEn: "Process designation",
    dataType: "text",
    classification: "input",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_facility_designation",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Bezeichnung Anlage/Maschine/Typ",
    labelEn: "Designation Facility/machine/type",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_site",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Standort",
    labelEn: "Site",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_procurement_currency",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Beschaffungswährung BW",
    labelEn: "Procurement currency [BW]",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_cycle_time",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Zykluszeit [s]",
    labelEn: "Cycle time [s]",
    dataType: "number",
    unit: "s",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_parts_per_cycle",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Teile pro Zyklus",
    labelEn: "Parts per cycle",
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_direct_employee_count",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Anzahl direkte Mitarbeiter",
    labelEn: "Number of direct employees",
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_direct_labor_cost",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Kalkulatorisch angesetzte direkte Lohnkosten [BW/h]",
    labelEn: "Imputed direct labor costs [BW/h]",
    dataType: "number",
    unit: "BW/h",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_social_overhead_rate",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Kalkulatorisch angesetzte Lohn-zuschlagssätze SGK [%]",
    labelEn: "Imputed social overhead costs (SGK) [%]",
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_machine_hour_rate",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Maschinenstundensatz MSS [BW/h]",
    labelEn: "Machine-hour rate (MSS) [BW/h]",
    dataType: "number",
    unit: "BW/h",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_setup_cost_per_unit",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Rüstkosten pro Stück [BW]",
    labelEn: "Setup costs per unit [BW]",
    dataType: "number",
    unit: "BW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_direct_manufacturing_cost",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Fertigungseinzelkosten FEK [BW]",
    labelEn: "Direct manufacturing costs (FEK) [BW]",
    dataType: "number",
    unit: "BW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
    notes:
      "FEK = [Anzahl MA x Lohn x (1+SGK) + MSS] x Zykluszeit / Teile pro Zyklus / 3600 + Rüstkosten.",
  }),
  f({
    id: "mfg_remaining_overhead_rate",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 1",
    labelDe: "Restfertigungsgemeinkosten (RFGK) Stundensatz [BW/h]",
    labelEn: "Remaining manufacturing overhead costs (RFGK) Hourly rate [BW/h]",
    dataType: "number",
    unit: "BW/h",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(30)],
  }),
  f({
    id: "mfg_manufacturing_cost_bw",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Fertigungskosten FK [BW]",
    labelEn: "Manufacturing costs (FK) [BW]",
    dataType: "number",
    unit: "BW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
    notes:
      "FK [BW] = FEK + RFGK Stundensatz x Zykluszeit / Teile pro Zyklus / 3600.",
  }),
  f({
    id: "mfg_quotation_currency",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Angebotswährung AW",
    labelEn: "Quotation currency [AW]",
    dataType: "currency",
    classification: "input",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
  }),
  f({
    id: "mfg_exchange_rate",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Wechselkurs [AW/BW]",
    labelEn: "Exchange rate [AW/BW]",
    dataType: "number",
    unit: "AW/BW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
  }),
  f({
    id: "mfg_quantity_per_quoted_part",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Anzahl pro Angebotsteil",
    labelEn: "Number per quotation part",
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
  }),
  f({
    id: "mfg_manufacturing_cost_aw",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Fertigungskosten FK [AW]",
    labelEn: "Manufacturing costs FK [AW]",
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
    notes: "The one MANDATORY_STEP_NUMERIC_FIELD in rule-engine.ts today.",
  }),
  f({
    id: "mfg_scrap_rate",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Ausschuss pro Prozessschritt [%]",
    labelEn: "Scrap per process step [%]",
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
  }),
  f({
    id: "mfg_scrap_cost",
    module: "MANUFACTURING",
    section: "Feldtabelle Teil 2",
    labelDe: "Ausschusskosten Fertigung [AW]",
    labelEn: "Scrap costs Manufacturing [AW]",
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_MFG,
    evidence: [CODE("qaf-parser.ts:HEADER_TO_KEY"), L1(31)],
  }),
]

// Known sheet-NAME variants/typos per module (distinct from field-label
// aliases above) moved to ./module-sheet-names.ts's MODULE_SHEET_NAME_ALIASES
// (KAR-905/P3.1) — that module is the single source every isXxxSheetName
// function across material-parser.ts/sbm-parser.ts/logistics-parser.ts/
// rmr-parser.ts/lccn-parser.ts/co2e-parser.ts/workbook-adapter.ts now reads
// from, covering every module (not just MANUFACTURING, as this file's
// pre-P3.1 copy did).

// ── G60 — detail-costing workbook (CORE, real-file-verified) ────────────────

const G60_FIELDS: CanonicalField[] = [
  f({
    id: "g60_material_cost_w",
    module: "G60",
    section: "Cost tab header row (col W)",
    labelDe: "Materialkosten",
    labelEn: "Material costs",
    dataType: "number",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_G60,
    evidence: [
      CODE(
        "g60/structure-guard.ts:TAB_HEADER_ANCHORS",
        "verified 2026-07-09 against 2 real G60 files"
      ),
    ],
    notes:
      "Row-15..38 component rows AND the row-41 aggregate share this column.",
  }),
  f({
    id: "g60_raw_material_surcharge_y",
    module: "G60",
    section: "Cost tab header row (col Y)",
    labelDe: "Rohstoffzuschlag RoZ0",
    labelEn: "Raw material surcharge RoZ0",
    aliases: ["Raw material sucharge"],
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_G60,
    evidence: [
      CODE(
        "g60/structure-guard.ts:TAB_HEADER_ANCHORS",
        "verified 2026-07-09 against 2 real G60 files"
      ),
    ],
    notes:
      'BOTH verified real files carry BMW\'s own template typo "sucharge" (not "surcharge") — that IS the template spelling, kept as an alias, not corrected away.', // allow-customer-string
  }), // allow-customer-string
  f({
    id: "g60_cycle_time_ao",
    module: "G60",
    section: "Cost tab header row (col AO)",
    labelDe: "Zykluszeit",
    labelEn: "Cycle time",
    dataType: "number",
    unit: "s",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_G60,
    evidence: [
      CODE(
        "g60/structure-guard.ts:TAB_HEADER_ANCHORS",
        "verified 2026-07-09 against 2 real G60 files"
      ),
    ],
  }),
  f({
    id: "g60_employee_count_aq",
    module: "G60",
    section: "Cost tab header row (col AQ)",
    labelDe: "Anzahl Mitarbeiter",
    labelEn: "Number of direct employees",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_G60,
    evidence: [
      CODE(
        "g60/structure-guard.ts:TAB_HEADER_ANCHORS",
        "verified 2026-07-09 against 2 real G60 files"
      ),
    ],
  }),
  f({
    id: "g60_machine_hour_rate_at",
    module: "G60",
    section: "Cost tab header row (col AT)",
    labelDe: "Maschinenstundensatz",
    labelEn: "Machine-hour rate",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_G60,
    evidence: [
      CODE(
        "g60/structure-guard.ts:TAB_HEADER_ANCHORS",
        "verified 2026-07-09 against 2 real G60 files"
      ),
    ],
    notes:
      'Same column AT means "Machine cost aggregate" (MCOL.Mach) at row 41 — one column, two semantics depending on row, documented in parser.ts.',
  }),
  f({
    id: "g60_overhead_fk_direct",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Overhead FK (direkt)",
    labelEn: "Overhead FK (direct)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.ovFK_d"),
      CODE(
        "g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS",
        "label presence verified 2026-07-09, row B22"
      ),
    ],
  }),
  f({
    id: "g60_overhead_mat_direct",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Overhead MAT (direkt)",
    labelEn: "Overhead MAT (direct)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.ovMAT_d"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B23"),
    ],
  }),
  f({
    id: "g60_profit_fk_direct",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Profit FK (direkt)",
    labelEn: "Profit FK (direct)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.pfFK_d"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B24"),
    ],
  }),
  f({
    id: "g60_profit_mat_direct",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Profit MAT (direkt)",
    labelEn: "Profit MAT (direct)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.pfMAT_d"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B25"),
    ],
  }),
  f({
    id: "g60_overhead_fk_sourced",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Overhead FK (sourced)",
    labelEn: "Overhead FK (sourced)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.ovFK_s"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B26"),
    ],
  }),
  f({
    id: "g60_overhead_mat_sourced",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Overhead MAT (sourced)",
    labelEn: "Overhead MAT (sourced)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.ovMAT_s"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B27"),
    ],
  }),
  f({
    id: "g60_profit_fk_sourced",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Profit FK (sourced)",
    labelEn: "Profit FK (sourced)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.pfFK_s"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B28"),
    ],
  }),
  f({
    id: "g60_profit_mat_sourced",
    module: "G60",
    section: "INPUT!B22-B29 rate card",
    labelDe: "Profit MAT (sourced)",
    labelEn: "Profit MAT (sourced)",
    dataType: "percent",
    classification: "protected",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_G60,
    evidence: [
      CODE("g60/parser.ts:G60Rates.pfMAT_s"),
      CODE("g60/structure-guard.ts:INPUT_RATE_LABEL_ROWS", "row B29"),
    ],
  }),
]

// ── MATERIAL — CORE (near-full per Leitfaden S.18-20, not parsed by Kadi-v2) ─

const MATERIAL_FIELDS: CanonicalField[] = [
  f({
    id: "mat_position_number",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Positionsnummer Fertigungsschritt",
    labelEn: "Item number manufacturing step",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18)],
    notes:
      "Split rows (RMR, S.17) share the same Positionsnummer by design — not a duplicate.",
  }),
  f({
    id: "mat_part_designation",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Teilebenennung",
    labelEn: "Part designation",
    // "Parts designation" (plural) is the real V8.8 EN column text — differs
    // from the V9 Leitfaden's singular labelEn.
    aliases: ["Teilebennung", "Parts designation"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L1(18),
      FEHLER("3.3", 'documented typo "Teilebennung", appears 2x'),
      V88("EN Material row 11 col C"),
    ],
  }),
  f({
    id: "mat_raw_material_or_purchased_part_designation",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Benennung Rohmaterial / Kaufteil",
    labelEn: "Raw material / purchased part designation",
    // KAR-915 CORE FIELD FIX: the real V8.8 EN column text is "Designation
    // Raw material / purchased part" — "Designation" moved to the FRONT
    // instead of the Leitfaden's trailing "... designation". This field is
    // one of MATERIAL's 3 CORE_MATERIAL_FIELD_KEYS (materialDesignation) —
    // the word-order mismatch was the root cause of coreFieldsFound:false /
    // rows:0 on the V8.8 EN real file (DE side already matched exactly).
    aliases: ["Designation Raw material / purchased part"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("EN Material row 11 col D, CORE field")],
  }),
  f({
    id: "mat_supplier",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Lieferant",
    labelEn: "Supplier",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18)],
  }),
  f({
    id: "mat_technical_function",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Technische Funktion des Bauteiles",
    labelEn: "Technical function of the component",
    // Real V8.8 DE column text drops the genitive "-es" ("Bauteils" not
    // "Bauteiles") — a genuine spelling variant, not a typo per se.
    aliases: ["Technische Funktion des Bauteils"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE Material row 11 col F (EN col F matches labelEn exactly)")],
  }),
  f({
    id: "mat_country_of_origin",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Ursprungsland",
    labelEn: "Country of origin",
    // Real V8.8 header carries an explicit "(commercial law)"/"(handelsrechtlich)"
    // suffix on both DE and EN, absent from the Leitfaden's shorter label.
    aliases: ["Ursprungsland (handelsrechtlich)", "Country of origin (commercial law)"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE+EN Material row 11 col G")],
  }),
  f({
    id: "mat_hts_code",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "HTS-Code",
    labelEn: "HTS code",
    // Real V8.8 header adds a "(6-stellig)"/"(6 digits)" suffix, and the EN
    // side keeps the hyphen ("HTS-Code", not the Leitfaden's "HTS code").
    aliases: ["HTS-Code (6-stellig)", "HTS-Code (6 digits)"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE+EN Material row 11 col H")],
  }),
  f({
    id: "mat_unit_of_measure",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Mengeneinheit",
    labelEn: "Unit of measure",
    // "Unit" (bare) is the real V8.8 EN column text — a short form, not the
    // Leitfaden's fuller "Unit of measure". Exact-string match only (no
    // substring risk): still scoped to the MATERIAL module registry.
    aliases: ["Unit"],
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L1(18),
      FEHLER("3.3", 'row #31, correct/canonical use of "Mengeneinheit"'),
      V88("DE Material row 11 col I (exact); EN col I = 'Unit'"),
    ],
    collisionGroup: "material_mengeneinheit_label_duplicate",
    notes:
      "Selection drives row accounting logic (purchased part / raw material / raw-material-risk / premium), Leitfaden S.15.",
  }),
  f({
    id: "mat_procurement_currency",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Beschaffungswährung BW",
    labelEn: "Procurement currency [BW]",
    // Real V8.8 DE header brackets the unit ("[BW]") instead of the
    // Leitfaden's trailing bare "BW"; the real V8.8 EN header uses
    // "Purchasing" instead of "Procurement".
    aliases: ["Beschaffungswährung [BW]", "Purchasing currency [BW]"],
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE Material row 11 col J; EN col J = 'Purchasing currency [BW]'")],
  }),
  f({
    id: "mat_calc_material_cost_per_unit_bw",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Kalkulatorisch angesetzte Materialkosten pro Mengeneinheit [BW]",
    labelEn: "Imputed material costs per unit of measure [BW]",
    // Real V8.8 EN header drops "material" and says "quantity unit" instead
    // of "unit of measure".
    aliases: ["Imputed costs per quantity unit [BW]"],
    dataType: "number",
    unit: "BW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE Material row 11 col K (exact); EN col K = 'Imputed costs per quantity unit [BW]'")],
  }),
  f({
    id: "mat_quotation_currency",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Angebotswährung AW",
    labelEn: "Quotation currency [AW]",
    // Real V8.8 DE header brackets the unit ("[AW]") instead of the
    // Leitfaden's trailing bare "AW".
    aliases: ["Angebotswährung [AW]"],
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE Material row 11 col L")],
  }),
  f({
    id: "mat_exchange_rate",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Wechselkurs [AW/BW]",
    labelEn: "Exchange rate [AW/BW]",
    dataType: "number",
    unit: "AW/BW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18)],
    notes: '"1" required when AW = BW.',
  }),
  f({
    id: "mat_packaging_cost_per_unit",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Verpackungskosten je Mengeneinheit [AW]",
    labelEn: "Packaging costs per unit of measure [AW]",
    // Real V8.8 EN header drops "of measure".
    aliases: ["Packaging costs per unit [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE Material row 11 col N (exact); EN col N = 'Packaging costs per unit [AW]'")],
  }),
  f({
    id: "mat_transport_cost_per_unit",
    module: "MATERIAL",
    section: "Feldtabelle Teil 1",
    labelDe: "Transportkosten je Mengeneinheit [AW]",
    labelEn: "Transport costs per unit of measure [AW]",
    // Real V8.8 EN header says "per quantity unit" instead of "per unit of measure".
    aliases: ["Transport costs per quantity unit [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(18), V88("DE Material row 11 col O (exact); EN col O = 'Transport costs per quantity unit [AW]'")],
  }),
  f({
    id: "mat_customs_cbam_cost_per_unit",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Zollkosten, CBAM-Kosten je Mengeneinheit [AW]",
    labelEn: "Customs costs, CBAM costs per unit of measure [AW]",
    // Real V8.8 header is shorter on both sides: DE drops ", CBAM-Kosten";
    // EN says "Customs duties" (not "Customs costs, CBAM costs") and "per
    // quantity unit" (not "per unit of measure").
    aliases: ["Zollkosten je Mengeneinheit [AW]", "Customs duties per quantity unit [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE+EN Material row 11 col P")],
    notes:
      "Only if no rebate and not already covered via raw-material-risk participation.",
  }),
  f({
    id: "mat_overhead_cost",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Materialgemeinkosten MGK",
    labelEn: "Material overhead costs (MGK)",
    // Real V8.8 header on BOTH sides appends "[%]" (absent from the
    // Leitfaden/V9 label) — see the collisionGroup note below for why
    // "Mengeneinheit" (the OTHER genuine duplicate-label case) is also here.
    aliases: ["Mengeneinheit", "Materialgemeinkosten MGK [%]", "Material overhead costs (MGK) [%]"],
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L1(19),
      FEHLER(
        "3.3",
        'row #36 — real BMW file mislabels this column "Mengeneinheit" (label-copy error from row #31), should read "MGK je Mengeneinheit"' // allow-customer-string
      ),
      V88("DE+EN Material row 11 col Q"),
    ],
    collisionGroup: "material_mengeneinheit_label_duplicate",
    notes: "MGK-Anteil entered as absolute value, Leitfaden S.16.",
  }), // allow-customer-string
  f({
    id: "mat_calc_material_cost_per_unit_aw",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Kalkulatorische Materialkosten pro Mengeneinheit [AW]",
    labelEn: "Calculated material costs per unit of measure [AW]",
    // Real V8.8 header is shorter on both sides: DE drops "Kalkulatorische";
    // EN says "Material costs" (not "Calculated material costs") and "per
    // quantity unit" (not "per unit of measure").
    aliases: ["Materialkosten pro Mengeneinheit [AW]", "Material costs per quantity unit [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE+EN Material row 11 col R")],
    notes:
      "= Kosten pro ME x Wechselkurs + Verpackungskosten + Transportkosten + Zollkosten + MGK.",
  }),
  f({
    id: "mat_reference_quantity",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Bezugsmenge [Einheiten] (nur Rohmaterial)",
    labelEn: "Reference quantity [units] (raw material only)",
    // Real V8.8 header uses "Einsatzmenge"/"Use amount" instead of
    // "Bezugsmenge"/"Reference quantity", and EN reorders the "(only raw
    // materials)" suffix.
    aliases: ["Einsatzmenge [Einheiten] (nur Rohmaterial)", "Use amount [units] (only raw materials)"],
    dataType: "number",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE+EN Material row 11 col S")],
  }),
  f({
    id: "mat_net_quantity",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Nettomenge [Einheiten] (nur Rohmaterial)",
    labelEn: "Net quantity [units] (raw material only)",
    // Real V8.8 EN header says "Net amount" instead of "Net quantity" and
    // reorders the "(only raw materials)" suffix.
    aliases: ["Net amount [units] (only raw materials)"],
    dataType: "number",
    classification: "informational",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col T (exact); EN col T = 'Net amount [units] (only raw materials)'")],
    notes: '"nur Info" — informational, not comparison-relevant per Leitfaden.',
  }),
  f({
    id: "mat_calc_rebate",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Kalkulatorisch angesetzte Rückvergütung [AW] (nur Rohmaterial)",
    labelEn: "Imputed rebate [AW] (raw material only)",
    // Real V8.8 EN header says "Imputed reimbursement" (not "Imputed
    // rebate") and reorders the "(only raw materials)" suffix.
    aliases: ["Imputed reimbursement [AW] (only raw materials)"],
    dataType: "number",
    unit: "AW",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col U (exact); EN col U = 'Imputed reimbursement [AW] (only raw materials)'")],
  }),
  f({
    id: "mat_quantity_per_quoted_part",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Anzahl pro Angebotsteil",
    labelEn: "Quantity per quoted part",
    // Real V8.8 EN header says "Number per quotation part" (not "Quantity
    // per quoted part").
    aliases: ["Number per quotation part"],
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col V (exact); EN col V = 'Number per quotation part'")],
  }),
  f({
    id: "mat_calc_material_cost",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Kalkulatorische Materialkosten [AW]",
    labelEn: "Calculated material costs [AW]",
    // KAR-915 CORE FIELD FIX: the real V8.8 header on BOTH sides drops the
    // "Kalkulatorisch(e)"/"Calculated" adjective ("Materialkosten [AW]" /
    // "Material costs [AW]"). This field is one of MATERIAL's 3
    // CORE_MATERIAL_FIELD_KEYS (materialCost) — this mismatch was the root
    // cause of coreFieldsFound:false / rows:0 on BOTH V8.8 real files (DE
    // and EN alike), unlike materialDesignation above which only failed on
    // the EN side.
    aliases: ["Materialkosten [AW]", "Material costs [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE+EN Material row 11 col W, CORE field")],
    notes:
      "Kaufteil: Kosten/ME x Verwendungen. Rohmaterial: [(Kosten/ME x Einsatzmenge) - Rückvergütung] x Verwendungen.",
  }),
  f({
    id: "mat_packaging_cost_offer",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2 (Fehlerreport-only)",
    labelDe: "Verpackungskosten (Angebot)",
    labelEn: "Packaging Costs (Offer)",
    // Real V8.8 header text — "Verpackungskosten [AW] Angebot" — appears
    // VERBATIM on both the DE and the EN V8.8 file (BMW did not translate // allow-customer-string
    // this trailing "Angebot"-aggregate column in the EN template variant;
    // see KAR-915 PR body for the same observation on 3 sibling columns
    // that have no canonical field yet).
    aliases: ["Verpackungskosten [AW] Angebot"],
    dataType: "number",
    unit: "AW",
    // KAR-910/fehlerreport-analyse.md §5 Fall #42: an AGGREGATED per-position
    // total, distinct from mat_packaging_cost_per_unit (row #33, the
    // Leitfaden-documented Je-Mengeneinheit/per-unit value). NOT in the
    // Leitfaden's own exhaustive MATERIAL column-layout screenshot
    // enumeration (leitfaden-teil1.md [21], "Vollständiges Spaltenlayout ...
    // bestätigt" lists every column left-to-right and stops at "Ausschusskosten
    // Material [AW]" — no "(Angebot)"-suffixed aggregate columns appear
    // anywhere in that list) — the only evidence is the real Fehlerreport row
    // itself (§2 row #42). Classified 'optional' (not 'mandatory') for the
    // same reason as sum_cost_breakdown_aw1: unverified sheet position, task
    // instruction "im Zweifel optional mit Kommentar+Quelle".
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      FEHLER("§5 Fall #42", "no Leitfaden coverage — Fehlerreport-only aggregate field, distinct from mat_packaging_cost_per_unit's Leitfaden-documented per-unit value"),
      V88("DE+EN Material row 11 col AF (identical text both sides)"),
    ],
    notes:
      "Parsed since KAR-910 (material-parser.ts MaterialRowValues.packagingCostOffer) via the standard registry label-anchor match (matchHeaderColumnSync) — same mechanism as every other MATERIAL column, just a new registry entry, no parser-anchor change needed.",
  }),
  f({
    id: "mat_scrap_rate",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Materialausschuss [%]",
    labelEn: "Material scrap rate [%]",
    // Real V8.8 EN header drops "rate".
    aliases: ["Material scrap [%]"],
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col X (exact); EN col X = 'Material scrap [%]'")],
  }),
  f({
    id: "mat_scrap_cost",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Ausschusskosten Material [AW]",
    labelEn: "Scrap costs material [AW]",
    // Real V8.8 EN header reorders to "Material scrap costs [AW]".
    aliases: ["Material scrap costs [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col Y (exact); EN col Y = 'Material scrap costs [AW]'")],
  }),
  f({
    id: "mat_raw_material_designation",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Rohstoffbezeichnung",
    labelEn: "Raw material designation",
    // Real V8.8 EN header says "description" instead of "designation".
    aliases: ["Raw material description"],
    dataType: "dropdown",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col AA (exact); EN col AA = 'Raw material description'")],
  }),
  f({
    id: "mat_reference_weight",
    module: "MATERIAL",
    section: "Feldtabelle Teil 2",
    labelDe: "Bezugsgewicht [kg]",
    labelEn: "Reference weight [kg]",
    // Real V8.8 EN header says "Base weight" instead of "Reference weight".
    aliases: ["Base weight [kg]"],
    dataType: "number",
    unit: "kg",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(19), V88("DE Material row 11 col AB (exact); EN col AB = 'Base weight [kg]'")],
  }),
  f({
    id: "mat_raw_material_quotation",
    module: "MATERIAL",
    section: "Feldtabelle Teil 3",
    labelDe: "Rohstoffnotierung Ro [AW/kg]",
    labelEn: "Raw material quotation Ro [AW/kg]",
    // Real V8.8 EN header says "annotation" instead of "quotation".
    aliases: ["Raw material annotation Ro [AW/kg]"],
    dataType: "number",
    unit: "AW/kg",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(20), V88("DE Material row 11 col AC (exact); EN col AC = 'Raw material annotation Ro [AW/kg]'")],
  }),
  f({
    id: "mat_raw_material_surcharge",
    module: "MATERIAL",
    section: "Feldtabelle Teil 3",
    labelDe: "Rohstoffzuschlag RoZ0 [AW]",
    labelEn: "Raw material surcharge RoZ0 [AW]",
    // Real V8.8 DE header carries a triple-f typo ("Rohstofffzuschlag"); the
    // real V8.8 EN header carries the SAME "sucharge" (missing r) typo
    // already documented on g60_raw_material_surcharge_y — this is the same
    // BMW-template-wide typo, now confirmed on a second module. // allow-customer-string
    aliases: ["Rohstofffzuschlag RoZ0 [AW]", "Raw material sucharge RoZ0 [AW]"], // allow-customer-string
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L1(20), V88("DE+EN Material row 11 col AD")],
    notes: "Applied to all materials with raw-material-risk participation.",
  }),
]

// ── SBM-DEVICES-FWZ — CORE (near-full per Leitfaden S.34-37, not parsed) ────

const SBM_FIELDS: CanonicalField[] = [
  f({
    id: "sbm_position_number",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Positionsnummer Fertigungsschritt",
    labelEn: "Item number manufacturing step",
    // Real V8.8 EN header carries the SAME "Manufactering" (missing "u")
    // typo already documented as a SHEET-NAME variant (module-sheet-
    // names.ts MANUFACTURING alias) — here it also appears inside a COLUMN
    // label on the SBM sheet, a second, independent occurrence.
    aliases: ["Item number Manufactering step"], // allow-customer-string
    dataType: "text",
    classification: "input",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("EN SBM-devices-FWZ row 12 col B")],
  }),
  f({
    id: "sbm_verrechnungsform",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Verrechnungsform",
    labelEn: "Charging method",
    // Real V8.8 EN header uses "Billing method" instead of "Charging method".
    aliases: ["Verrechungsform", "Billing method"],
    dataType: "dropdown",
    classification: "input",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L2(34),
      FEHLER("3.3", 'documented typo "Verrechungsform"'),
      V88("DE SBM-Vorrichtungen-FWZ row 12 col C (exact); EN col C = 'Billing method'"),
    ],
    notes:
      "SBM / Vorrichtung / Folgeeinsatz / Folgewerkzeug / Werkzeuginstandhaltung.",
  }),
  f({
    id: "sbm_werkzeug_vorrichtungsart",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Werkzeug-/ Vorrichtungsart",
    labelEn: "Tool/fixture type",
    // KAR-915 CORE FIELD FIX: real V8.8 EN header is "Tool / device type"
    // (spaced slash, "device" not "fixture") — this field is one of SBM's 3
    // CORE_SBM_FIELD_KEYS (toolFixtureType); this mismatch was the EN-side
    // root cause of coreFieldsFound:false / rows:0 (DE side already matched
    // exactly).
    aliases: ["Tool / device type"],
    dataType: "dropdown",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("EN SBM-devices-FWZ row 12 col D, CORE field")],
    notes:
      "Pflicht sobald Verrechnungsform gesetzt — turns red in the sheet until filled.",
  }),
  f({
    id: "sbm_bauteilbezeichnung",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Bauteilbezeichnung",
    labelEn: "Component designation",
    // Real V8.8 EN header is the bare word "Name" (real-file-verified, not a
    // guess — exact-string match only, no substring risk, scoped to SBM).
    aliases: ["Name"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE SBM-Vorrichtungen-FWZ row 12 col E (exact); EN col E = 'Name'")],
  }),
  f({
    id: "sbm_auslegung_kavitaeten",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Auslegung / Kavitäten / Teile pro Hub [x-fach]",
    labelEn: "Design / cavities / parts per stroke",
    // Real V8.8 EN header appends "[x times]" (absent from the Leitfaden
    // label).
    aliases: ["Design / cavities / parts per stroke [x times]"],
    dataType: "dropdown",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE SBM-Vorrichtungen-FWZ row 12 col M (exact); EN col M = 'Design / cavities / parts per stroke [x times]'")],
  }),
  f({
    id: "sbm_anzahl_stufen_takte",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Anzahl Stufen / Takte / Komponenten",
    labelEn: "Number of stages / cycles / components",
    // Real V8.8 EN header says "stations" instead of "stages". Note (fragile,
    // documented for KAR-915 PR body): on BOTH real V8.8 files this header
    // text is IDENTICAL on cols 14 AND 15 (the Aktiv/Leer split lives on a
    // SEPARATE row — row 13, " aktiv"/" leer" resp. " active"/" idle" — not
    // on the single header row this single-row-header-detection parser
    // reads) — col 14 maps here, col 15 is (correctly, not incorrectly)
    // left unmapped by the existing used-key dedup guard. sbm_anzahl_
    // stufen_takte_aktiv/_leer therefore cannot be populated from a V8.8
    // file with the current parser architecture; fixing that needs
    // multi-row header stitching, out of this alias-only PR's scope.
    aliases: ["Number of stations / cycles / components"],
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE+EN SBM-Vorrichtungen-FWZ row 12 col N (identical text on col N and col O both files)")],
    notes:
      "The combined field the Leitfaden's field-description table [34] names. KAR-910 adds sbm_anzahl_stufen_takte_aktiv/_leer alongside this one (NOT a replacement) — the Leitfaden's own screenshot [38] shows the sheet column group as '(Aktiv/Leer)', i.e. 2 sub-columns under this one header group, matching the Fehlerreport's 2 separately-scored rows (#74/#75). This combined id is kept for backward-compat (sbm-parser.ts's existing stageCount field/parser wiring is untouched).",
  }),
  f({
    id: "sbm_anzahl_stufen_takte_aktiv",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Anzahl Stufen/Takte: Aktiv",
    labelEn: "Number of Stages: Active",
    // KAR-910/fehlerreport-analyse.md §5 Fall #74. Leitfaden evidence: [34]'s
    // field description text explicitly distinguishes "aktiven und leeren
    // Stufen/Takten/Komponenten"; [38]'s screenshot of the real sheet layout
    // names the column group "Anzahl Stufen/Takte/Komponenten (Aktiv/Leer)" —
    // i.e. 2 adjacent sub-columns, not one merged cell, confirming the
    // Fehlerreport's 2-row split is a real sheet structure, not a Fehlerreport
    // artifact.
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L2(34, 'field description: "unterschieden nach aktiven und leeren Stufen/Takten/Komponenten"'),
      L2(38, 'screenshot column group "Anzahl Stufen/Takte/Komponenten (Aktiv/Leer)"'),
      FEHLER("§5 Fall #74"),
    ],
    notes:
      "Parsed since KAR-910 (sbm-parser.ts SbmRowValues.stageCountActive) via the standard registry label-anchor match — same mechanism as every other SBM column.",
  }),
  f({
    id: "sbm_anzahl_stufen_takte_leer",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Anzahl Stufen/Takte: Leer",
    labelEn: "Number of Stages: Empty",
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L2(34, 'field description: "unterschieden nach aktiven und leeren Stufen/Takten/Komponenten"'),
      L2(38, 'screenshot column group "Anzahl Stufen/Takte/Komponenten (Aktiv/Leer)"'),
      FEHLER("§5 Fall #75"),
    ],
    notes:
      "Parsed since KAR-910 (sbm-parser.ts SbmRowValues.stageCountEmpty) via the standard registry label-anchor match — same mechanism as every other SBM column.",
  }),
  f({
    id: "sbm_standzeit",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Standzeit [Zyklen]",
    labelEn: "Service life [cycles]",
    // Real V8.8 EN header says "Lifetime" instead of "Service life".
    aliases: ["Lifetime [cycles]"],
    dataType: "number",
    unit: "Zyklen",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE SBM-Vorrichtungen-FWZ row 12 col P (exact); EN col P = 'Lifetime [cycles]'")],
  }),
  f({
    id: "sbm_einsatztermin_werkzeug",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Einsatztermin Werkzeug [MM/JJJJ]",
    labelEn: "Tool deployment date [MM/YYYY]",
    dataType: "month_year",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34)],
  }),
  f({
    id: "sbm_auftragszeit",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Auftragszeit [Wochen]",
    labelEn: "Order lead time [weeks]",
    // Real V8.8 EN header drops "lead".
    aliases: ["Order time [weeks]"],
    dataType: "number",
    unit: "Wochen",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE SBM-Vorrichtungen-FWZ row 12 col Q (exact); EN col Q = 'Order time [weeks]'")],
  }),
  f({
    id: "sbm_standort_hersteller",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Standort des Werkzeug-/Vorrichtungsherstellers",
    labelEn: "Location of the tool/fixture manufacturer",
    // Real V8.8 EN header uses completely different wording.
    aliases: ["toolshop / device manufacturer location"],
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE SBM-Vorrichtungen-FWZ row 12 col AI (exact); EN col AI = 'toolshop / device manufacturer location'")],
  }),
  f({
    id: "sbm_procurement_currency",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Beschaffungswährung BW",
    labelEn: "Procurement currency [BW]",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34)],
  }),
  f({
    id: "sbm_calc_tool_fixture_cost_bw",
    module: "SBM",
    section: "Feldbeschreibungen Teil 1",
    labelDe: "Kalkulatorisch angesetzte Werkzeug- und Vorrichtungskosten [BW]",
    labelEn: "Imputed tool and fixture costs [BW]",
    // Real V8.8 EN header carries a "Inputed" (missing "m") typo and says
    // "tool / device costs" instead of "tool and fixture costs".
    aliases: ["Inputed tool / device costs [BW]"], // allow-customer-string
    dataType: "number",
    unit: "BW",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(34), V88("DE SBM-Vorrichtungen-FWZ row 12 col AK (exact); EN col AK = 'Inputed tool / device costs [BW]'")],
  }),
  f({
    id: "sbm_bmw_part_number",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "BMW Sachnummer", // allow-customer-string
    labelEn: "BMW part number", // allow-customer-string
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35)],
    notes: "optional (falls vorhanden). // allow-customer-string",
  }), // allow-customer-string
  f({
    id: "sbm_index",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "Index",
    labelEn: "Index",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35)],
  }),
  f({
    id: "sbm_length",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "Länge [mm]",
    labelEn: "Length [mm]",
    dataType: "number",
    unit: "mm",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35)],
  }),
  f({
    id: "sbm_width",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "Breite [mm]",
    labelEn: "Width [mm]",
    dataType: "number",
    unit: "mm",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35)],
  }),
  f({
    id: "sbm_height",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "Höhe [mm]",
    labelEn: "Height [mm]",
    dataType: "number",
    unit: "mm",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35)],
  }),
  f({
    id: "sbm_sheet_thickness",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "Blechdicke [mm]",
    labelEn: "Sheet thickness [mm]",
    dataType: "number",
    unit: "mm",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35)],
    notes: "bedingt (falls technologisch erforderlich).",
  }),
  f({
    id: "sbm_component_surface",
    module: "SBM",
    section: "Bauteilspezifikationen",
    labelDe: "Bauteiloberfläche",
    labelEn: "Component surface",
    // Real V8.8 EN header says "Part surface" instead of "Component surface".
    aliases: ["Part surface"],
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(35), V88("DE SBM-Vorrichtungen-FWZ row 12 col L (exact); EN col L = 'Part surface'")],
  }),
  f({
    id: "sbm_tool_dimensions",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Werkzeugabmessungen",
    labelEn: "Tool dimensions",
    dataType: "text",
    unit: "mm",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36)],
  }),
  f({
    id: "sbm_tool_weight",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Werkzeuggewicht [kg]",
    labelEn: "Tool weight [kg]",
    dataType: "number",
    unit: "kg",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36)],
  }),
  f({
    id: "sbm_tool_concept",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Werkzeugkonzept",
    labelEn: "Tool concept",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36)],
  }),
  f({
    id: "sbm_connection_concept",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Anbindungskonzept / Sonderelemente",
    labelEn: "Connection concept / special elements",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36)],
  }),
  f({
    id: "sbm_sliders_general",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Schieber allg. / Verfahr-Elemente",
    labelEn: "General sliders / travel elements",
    // Real V8.8 DE header drops the hyphen ("Verfahrelemente" not "Verfahr-
    // Elemente"); real V8.8 EN header uses completely different wording.
    aliases: ["Schieber allg. / Verfahrelemente", "Slider general / movement element"],
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36), V88("DE+EN SBM-Vorrichtungen-FWZ row 12 col Y")],
  }),
  f({
    id: "sbm_sliders_hydraulic",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Schieber hydraulisch",
    labelEn: "Hydraulic sliders",
    // "Schieber hyrd." is the pre-existing documented typo. "Schieber
    // hydr." (abbreviated, correctly spelled) is the DISTINCT real V8.8
    // header text; "Slider hydraulic" is the real V8.8 EN header
    // (word-order flipped vs. the Leitfaden's "Hydraulic sliders").
    aliases: ["Schieber hyrd.", "Schieber hydr.", "Slider hydraulic"],
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L2(36),
      FEHLER("3.3", 'documented typo "Schieber hyrd." for "hydr."'),
      V88("DE+EN SBM-Vorrichtungen-FWZ row 12 col AA"),
    ],
  }),
  f({
    id: "sbm_angled_sliders",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Schrägschieber / Halteelemente",
    labelEn: "Angled sliders / retaining elements",
    // Real V8.8 EN header uses completely different wording ("Angular
    // slider / clamping element").
    aliases: ["Angular slider / clamping element"],
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36), V88("DE SBM-Vorrichtungen-FWZ row 12 col AC (exact); EN col AC = 'Angular slider / clamping element'")],
  }),
  f({
    id: "sbm_jaw_sliders",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Backenschieber",
    labelEn: "Jaw sliders",
    // Real V8.8 EN header uses "Block slider" instead of "Jaw sliders".
    aliases: ["Block slider"],
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36), V88("DE SBM-Vorrichtungen-FWZ row 12 col AE (exact); EN col AE = 'Block slider'")],
  }),
  f({
    id: "sbm_sliders_total",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Schieber gesamt",
    labelEn: "Total sliders",
    // "Cam (total)" is the real V8.8 EN header at the SAME row/column
    // position as the DE side's "Schieber gesamt" (col AG on both real
    // files). Previously flagged as resting on positional evidence only
    // ("nur positional belegt") — since verified lexically too (KAR-915
    // review rider, PR #290): the DE twin file carries the label "Schieber
    // gesamt" at the IDENTICAL cell position (row 12, col 33/AG), directly
    // behind the 4-Schieber column block (the same "total of 4 slider
    // columns" aggregate the EN "Cam (total)" column also sits behind) — so
    // the DE side's own label independently confirms this is the "Schieber
    // gesamt" aggregate, not merely "same column index across two files".
    // "Cam" and "Slider" remain distinct tooling concepts in English in
    // general; this specific alias is justified by the confirmed DE-side
    // label at that exact position, not by lexical similarity to "Slider".
    aliases: ["Cam (total)"],
    dataType: "number",
    unit: "Stück",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36), V88("DE SBM-Vorrichtungen-FWZ row 12 col AG (exact) vs. EN col AG = 'Cam (total)', same column position")],
  }),
  f({
    id: "sbm_remarks",
    module: "SBM",
    section: "Werkzeugspezifikationen",
    labelDe: "Bemerkungen / Details",
    labelEn: "Remarks / details",
    // Real V8.8 EN header uses "Comments" instead of "Remarks".
    aliases: ["Comments / details"],
    dataType: "freetext",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(36), V88("DE SBM-Vorrichtungen-FWZ row 12 col AH (exact); EN col AH = 'Comments / details'")],
  }),
  f({
    id: "sbm_quotation_currency",
    module: "SBM",
    section: "Angebotswährung/Summen",
    labelDe: "Angebotswährung AW",
    labelEn: "Quotation currency [AW]",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(37)],
  }),
  f({
    id: "sbm_exchange_rate",
    module: "SBM",
    section: "Angebotswährung/Summen",
    labelDe: "Wechselkurs [AW/BW]",
    labelEn: "Exchange rate [AW/BW]",
    dataType: "number",
    unit: "AW/BW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(37)],
  }),
  f({
    id: "sbm_tool_fixture_count",
    module: "SBM",
    section: "Angebotswährung/Summen",
    labelDe: "Anzahl Werkzeuge / Vorrichtungen",
    labelEn: "Number of tools / fixtures",
    // Real V8.8 EN header carries a "tolls" (transposed letters) typo for
    // "tools", and says "devices" instead of "fixtures".
    aliases: ["Number of tolls / devices"], // allow-customer-string
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(37), V88("DE SBM-Vorrichtungen-FWZ row 12 col AN (exact); EN col AN = 'Number of tolls / devices'")],
  }),
  f({
    id: "sbm_total_tool_fixture_cost_aw",
    module: "SBM",
    section: "Angebotswährung/Summen",
    labelDe: "Summe Werkzeug-/Vorrichtungskosten [AW]",
    labelEn: "Total tool/fixture costs [AW]",
    // KAR-915 CORE FIELD FIX: this field is one of SBM's 3
    // CORE_SBM_FIELD_KEYS (totalToolFixtureCostAw). Real V8.8 DE header adds
    // spaces around the hyphen-slash ("Werkzeug- / Vorrichtungskosten", not
    // "Werkzeug-/Vorrichtungskosten") — a whitespace-only difference, but
    // normalizeHeaderCell only collapses \s+, it does not strip the hyphen,
    // so "Werkzeug- /" (space after hyphen) still differs from
    // "Werkzeug-/" (no space) after normalization. Real V8.8 EN header uses
    // completely different wording ("Sum tools / device costs [AW]"). Both
    // mismatches were root causes of coreFieldsFound:false / rows:0.
    aliases: ["Summe Werkzeug- / Vorrichtungskosten [AW]", "Sum tools / device costs [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(37), V88("DE+EN SBM-Vorrichtungen-FWZ row 12 col AO, CORE field")],
  }),
  f({
    id: "sbm_total_sbm_tool_cost",
    module: "SBM",
    section: "Angebotswährung/Summen",
    labelDe: "Summe Werkzeugkosten SBM [AW]",
    labelEn: "Total SBM tool costs [AW]",
    // Real V8.8 EN header carries a "too" (missing "l") typo for "tool" and
    // reorders to "Sum too costs (SBM) [AW]".
    aliases: ["Sum too costs (SBM) [AW]"], // allow-customer-string
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(37), V88("DE SBM-Vorrichtungen-FWZ row 12 col AP (exact); EN col AP = 'Sum too costs (SBM) [AW]'")],
    notes: "Only filled when Verrechnungsform = SBM.",
  }),
  f({
    id: "sbm_devices_followup_maintenance_per_unit",
    module: "SBM",
    section: "Angebotswährung/Summen",
    labelDe:
      "Vorrichtungen + Folgewerkzeuge/-einsätze + Werkzeuginstandhaltung pro Stück [AW]",
    labelEn:
      "Fixtures + follow-up tools/inserts + tool maintenance per unit [AW]",
    // Real V8.8 EN header uses completely different wording ("Devices +
    // subsequent dies / -inserts + tool maintenance per unit [AW]").
    aliases: ["Devices + subsequent dies / -inserts + tool maintenance per unit [AW]"],
    dataType: "number",
    unit: "AW",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(37), V88("DE SBM-Vorrichtungen-FWZ row 12 col AQ (exact); EN col AQ full text")],
  }),
]

// ── LOGISTICS&CUSTOM — CORE (not parsed) ─────────────────────────────────────

const LOGISTICS_FIELDS: CanonicalField[] = [
  f({
    id: "log_position_number",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Positionsnummer",
    labelEn: "Item number",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_delivery_site",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Anlieferstandort",
    labelEn: "Delivery site",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_total_volume",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Gesamtvolumen",
    labelEn: "Total volume",
    dataType: "number",
    unit: "Stück",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_quotation_currency",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Angebotswährung AW",
    labelEn: "Quotation currency [AW]",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_transport_cost_per_part",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Transportkosten pro Bauteil Lieferant",
    labelEn: "Transport costs per part, supplier",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_delivery_terms",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Lieferbedingungen",
    labelEn: "Delivery terms (Incoterm)",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
    notes: "FCA / DAP / DDP.",
  }),
  f({
    id: "log_packaging_cost_per_part",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Verpackungskosten pro Bauteil Lieferant",
    labelEn: "Packaging costs per part, supplier",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_surcharge_key_packaging",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Zuschlagsschlüssel (Verpackungskosten)",
    labelEn: "Surcharge key (packaging costs)",
    dataType: "text",
    classification: "protected",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
    notes: 'Auto-set to "VERP" — protected/automatic.',
  }),
  f({
    id: "log_pre_packaging_cost_per_part",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Vorverpackungskosten pro Bauteil Lieferant",
    labelEn: "Pre-packaging costs per part, supplier",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_surcharge_key_pre_packaging",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Zuschlagsschlüssel (Vorverpackungskosten)",
    labelEn: "Surcharge key (pre-packaging costs)",
    dataType: "text",
    classification: "protected",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
    notes: 'Auto-set to "LVVV" — protected/automatic.',
  }),
  f({
    id: "log_logistics_cost_per_delivery_site",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Logistikkosten je Anlieferstandort",
    labelEn: "Logistics costs per delivery site",
    dataType: "number",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
    notes:
      "= Transportkosten + Verpackungskosten + Vorverpackungskosten (je Bauteil).",
  }),
  f({
    id: "log_customs_cost_per_part",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Zollkosten pro Bauteil je Anlieferstandort",
    labelEn: "Customs costs per part per delivery site",
    dataType: "number",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
    notes: "= Zollwert x Zollsatz, ggf. + CBAM-Kosten.",
  }),
  f({
    id: "log_customs_tariff_number",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Zolltarifnummer",
    labelEn: "Customs tariff number",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
  f({
    id: "log_customs_cost_per_delivery_site",
    module: "LOGISTICS",
    section: "Feldbeschreibungen",
    labelDe: "Zollkosten je Anlieferstandort",
    labelEn: "Customs costs per delivery site",
    dataType: "number",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(40)],
  }),
]

// ── RAW MATERIAL RISKS — CORE (not parsed) ───────────────────────────────────

const RMR_FIELDS: CanonicalField[] = [
  f({
    id: "rmr_position_number",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Positionsnummer",
    labelEn: "Item number",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43)],
  }),
  f({
    id: "rmr_raw_material_designation",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Rohstoffbezeichnung",
    labelEn: "Raw material designation",
    // KAR-915 CORE FIELD FIX: this field is one of RMR's 3
    // CORE_RMR_FIELD_KEYS (rawMaterialDesignation). Real V8.8 EN header
    // says "description" instead of "designation" (the same variant already
    // added on mat_raw_material_designation for the MATERIAL sheet's own
    // "Rohstoffbezeichnung" column) — this mismatch was a root cause of
    // coreFieldsFound:false / rows:0 on the RMR/Rohstoffrisiken sheet.
    aliases: ["Raw material description"],
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE Rohstoffrisiken row 11 col C (exact); EN col C = 'Raw material description', CORE field")],
  }),
  f({
    id: "rmr_reference_weight",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Bezugsgewicht [kg]",
    labelEn: "Reference weight [kg]",
    // KAR-902 follow-up (adversarial-review finding, confidence 85): the RMR
    // sheet's "Rohstoff Preisanteil Energie" block (Leitfaden [44]/[45])
    // reuses this same quantity-column ROLE but names it "Energieverbrauch
    // pro h [kWh]" with a kWh unit instead of Bezugsgewicht's kg (Abbildung 26
    // worked examples, S.45: "Energieverbrauch 34,18 kWh/Angebotsteil" /
    // "2,78 kWh") — added as an alias (not a separate canonical field) so
    // rmr-parser.ts's multi-block scan can map the Energie block's quantity
    // column onto the SAME RmrFieldKey (referenceWeight) the RoZ0 formula
    // already multiplies against, letting the one validated RoZ0 = Ro x
    // Menge check run unchanged for both block types (see rmr-parser.ts
    // module header, "Block-uebergreifende Rohstoffzuschlag-Validierung").
    // "Referenced weight [kg]" is the real V8.8 EN header ("Referenced"
    // instead of "Reference").
    aliases: ["Energieverbrauch pro h [kWh]", "Energieverbrauch [kWh]", "Referenced weight [kg]"],
    dataType: "number",
    unit: "kg",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L2(43),
      L2(45, "Energieverbrauch-Alias aus Abbildung 26 Energie-Block"),
      V88("DE Rohstoffrisiken row 11 col D (exact); EN col D = 'Referenced weight [kg]'"),
    ],
    notes:
      "Aggregated/cumulated automatically from repeated MATERIAL-sheet rows sharing the same raw-material designation. Energie-Block: gleiche Spaltenrolle, andere Einheit (kWh statt kg) — s. aliases.",
  }),
  f({
    id: "rmr_raw_material_key",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Rohstoffschlüssel",
    labelEn: "Raw material key",
    dataType: "text",
    classification: "protected",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43)],
    notes:
      "BMW-internal key, auto-derived from Rohstoffbezeichnung. // allow-customer-string",
  }), // allow-customer-string
  f({
    id: "rmr_raw_material_quotation",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Rohstoffnotierung Ro [AW/kg]",
    labelEn: "Raw material quotation Ro [AW/kg]",
    // KAR-902 follow-up (adversarial-review finding, confidence 85): the
    // Energie block prices its quotation per kWh, not per kg (Abbildung 26,
    // S.45: "Rohstoffnotierung Ro 0,030 AW/kWh" / "0,090 AW/kWh") — same
    // reasoning as rmr_reference_weight's aliases above (unit brackets are
    // deliberately NOT stripped by normalizeCanonicalLabel, so the kWh
    // variant needs its own alias entry to match).
    // KAR-915 CORE FIELD FIX: this field is one of RMR's 3
    // CORE_RMR_FIELD_KEYS (rawMaterialQuotation). Real V8.8 DE header spells
    // it "Rohstoffnotierung R0 [AW/kg]" — digit "0" instead of letter "o" in
    // "Ro" (a real BMW-template character substitution, not a case/ // allow-customer-string
    // whitespace variant normalizeCanonicalLabel already folds). Real V8.8
    // EN header says "annotation" instead of "quotation". BOTH mismatches
    // were root causes of coreFieldsFound:false / rows:0 on the RMR sheet
    // (DE and EN alike).
    aliases: [
      "Rohstoffnotierung Ro [AW/kWh]",
      "Raw material quotation Ro [AW/kWh]",
      "Rohstoffnotierung R0 [AW/kg]",
      "Raw material annotation Ro [AW/kg]",
    ],
    dataType: "number",
    unit: "AW/kg",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [
      L2(43),
      L2(45, "AW/kWh-Alias aus Abbildung 26 Energie-Block"),
      V88("DE+EN Rohstoffrisiken row 11 col F, CORE field"),
    ],
  }),
  f({
    id: "rmr_raw_material_surcharge",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Rohstoffzuschlag [AW] RoZ0",
    labelEn: "Raw material surcharge [AW] RoZ0",
    // Real V8.8 DE header adds a hyphen ("Rohstoff-Zuschlag" not
    // "Rohstoffzuschlag"); the real V8.8 EN header already matches
    // labelEn exactly (verified against both real files).
    aliases: ["Rohstoff-Zuschlag [AW] RoZ0"],
    dataType: "number",
    unit: "AW",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE Rohstoffrisiken row 11 col G = 'Rohstoff-Zuschlag [AW] RoZ0'; EN col G matches labelEn exactly")],
  }),
  f({
    id: "rmr_settlement_model",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Abwicklungsmodell",
    labelEn: "Settlement model",
    // Real V8.8 header adds a "(siehe Anfrageunterlagen)"/"(see RFQ
    // documents)" suffix on both sides; the real V8.8 EN header uses
    // "Handling model" instead of "Settlement model".
    aliases: ["Abwicklungsmodell (siehe Anfrageunterlagen)", "Handling model (see RFQ documents)"],
    dataType: "dropdown",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE+EN Rohstoffrisiken row 11 col H")],
  }),
  f({
    id: "rmr_index_designation",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Indexbezeichnung (siehe Anfrage)",
    labelEn: "Index designation (per request documents)",
    // Real V8.8 DE header suffix is fuller ("siehe Anfrageunterlagen" not
    // "siehe Anfrage"); the real V8.8 EN header uses "Index name" instead
    // of "Index designation" and "(see RFQ documents)" instead of "(per
    // request documents)".
    aliases: ["Indexbezeichnung (siehe Anfrageunterlagen)", "Index name (see RFQ documents)"],
    dataType: "text",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE+EN Rohstoffrisiken row 11 col I")],
  }),
  f({
    id: "rmr_index_quotation",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Indexnotierung RoI0 [AW/kg] im Durchschnittszeitraum",
    labelEn: "Index quotation RoI0 [AW/kg], averaging period",
    // Real V8.8 DE header drops the "s" in "Durchschnittszeitraum" (real
    // BMW-template typo, "Durchschnittzeitraum"). Real V8.8 EN header says // allow-customer-string
    // "annotation" instead of "quotation" and rephrases the averaging-period
    // clause entirely.
    aliases: [
      "Indexnotierung RoI0 [AW/kg] im Durchschnittzeitraum",
      "Index annotation RoI0 [AW/kg] in specified average period",
    ],
    dataType: "number",
    unit: "AW/kg",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE+EN Rohstoffrisiken row 11 col J")],
  }),
  f({
    id: "rmr_bmw_participation_rate",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "BMW Beteiligungsquote BQ [in %]", // allow-customer-string
    labelEn: "BMW participation rate BQ [%]", // allow-customer-string
    // Real V8.8 header drops the internal space before "%" ("[in%]" not
    // "[in %]" — normalizeCanonicalLabel does not strip internal spaces
    // inside unit brackets, so this needs its own alias) and adds a "(siehe
    // Anfrageunterlagen)"/"(see RFQ documents)" suffix on both sides; the
    // real V8.8 EN header uses "Sharing Quote" instead of "participation
    // rate".
    aliases: [
      "BMW Beteiligungsquote BQ [in%] (siehe Anfrageunterlagen)", // allow-customer-string
      "BMW Sharing Quote BQ [in%] (see RFQ documents)", // allow-customer-string
    ],
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE+EN Rohstoffrisiken row 11 col K")],
  }), // allow-customer-string
  f({
    id: "rmr_threshold",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Schwellwert SW [in %]",
    labelEn: "Threshold SW [%]",
    // Real V8.8 header drops the internal space before "%" (same pattern as
    // rmr_bmw_participation_rate above) and adds a "(siehe
    // Anfrageunterlagen)"/"(see RFQ documents)" suffix on both sides.
    aliases: [
      "Schwellwert SW [in%] (siehe Anfrageunterlagen)",
      "Threshold SW [in%] (see RFQ documents)",
    ],
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE+EN Rohstoffrisiken row 11 col L")],
    notes:
      "Price adjustment only applies once the threshold is exceeded since the last adjustment.",
  }),
  f({
    id: "rmr_remark",
    module: "RMR",
    section: "Rohstoffdetaillierung",
    labelDe: "Bemerkung",
    labelEn: "Remark",
    // Real V8.8 EN header uses "Comments" (plural) instead of "Remark".
    aliases: ["Comments"],
    dataType: "freetext",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(43), V88("DE Rohstoffrisiken row 11 col S (exact); EN col S = 'Comments'")],
  }),
]

// ── LOCAL CONTENT CHINA (LC-CN) — CORE (not parsed) ──────────────────────────

const LC_CN_FIELDS: CanonicalField[] = [
  f({
    id: "lccn_am_cif_share",
    module: "LC_CN",
    section: "Gruppierung/Formeln",
    labelDe: "AM (CIF-Anteil, Cost Insurance Freight je Komponente)",
    labelEn: "AM (CIF share, cost insurance freight per component)",
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(47)],
    notes: "Required when Mengeneinheit = Unit_(purchased_part).",
  }),
  f({
    id: "lccn_am_process_clustering",
    module: "LC_CN",
    section: "Gruppierung/Formeln",
    labelDe: "AM (Prozessclusterung, Fertigungskosten)",
    labelEn: "AM (process clustering, manufacturing costs)",
    dataType: "dropdown",
    classification: "input",
    requirement: "mandatory",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(47)],
    notes:
      "Substantial manufacturing vs. Other manufacturing, per Betriebsmittelkategorie-Zuordnungstabelle (Leitfaden S.85-90, out of this PR's extraction scope).",
  }),
  f({
    id: "lccn_local_material_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Kosten lokales Material",
    labelEn: "Local material costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_local_material_cost_excl_mgk",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Kosten lokales Material (ohne MGK)",
    labelEn: "Local material costs (excl. MGK)",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_cif_imported_material_subsupplier",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "CIF-Kosten importiertes Material vom Unterlieferant",
    labelEn: "CIF costs imported material from sub-supplier",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
    notes:
      "Only when the sub-supplier's LC rate <= 40% (40/40 rule, Leitfaden S.46).",
  }),
  f({
    id: "lccn_imported_material_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Kosten importiertes Material",
    labelEn: "Imported material costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_cif_imported_material",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "CIF-Kosten importiertes Material",
    labelEn: "CIF costs imported material",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_raw_material_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Rohmaterialkosten",
    labelEn: "Raw material costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_raw_material_cost_excl_mgk",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Rohmaterialkosten (ohne MGK)",
    labelEn: "Raw material costs (excl. MGK)",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_substantial_manufacturing_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Substanzielle Fertigungskosten",
    labelEn: "Substantial manufacturing costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_other_manufacturing_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Andere Fertigungskosten",
    labelEn: "Other manufacturing costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_total_production_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Summe Herstellkosten",
    labelEn: "Total production costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_devices_substantial",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe:
      "Vorrichtungen und Folgewerkzeuge für substanziellen Fertigungsprozess",
    labelEn:
      "Devices and follow-up tools for substantial manufacturing process",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_devices_other",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Vorrichtungen und Folgewerkzeuge für anderen Fertigungsprozess",
    labelEn: "Devices and follow-up tools for other manufacturing process",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_total_imported_value_cif",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Gesamt importierter Wert (CIF)",
    labelEn: "Total imported value (CIF)",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_total_lc_manufacturing_cost",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "Gesamtkosten für LC Fertigung",
    labelEn: "Total costs for LC manufacturing",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
  }),
  f({
    id: "lccn_lc_rate",
    module: "LC_CN",
    section: "Feldbeschreibungen (Zusammenfassung)",
    labelDe: "LC Rate in %",
    labelEn: "LC rate [%]",
    dataType: "percent",
    unit: "%",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(48)],
    notes:
      "Leitfaden has two formulations for this (S.47 vs. S.48) that appear contradictory — documented as [unklar] in the source Leitfaden extraction itself, not resolved here.",
  }),
]

// ── CARBON FOOTPRINT (CO2e) — CORE (not parsed) ──────────────────────────────

const CO2E_FIELDS: CanonicalField[] = [
  f({
    id: "co2e_pcf",
    module: "CO2E",
    section: "Zusammenfassung",
    labelDe: "PCF",
    labelEn: "PCF (Product Carbon Footprint)",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(52)],
    notes: "Per Catena-X Product Carbon Footprint Rulebook.",
  }),
  f({
    id: "co2e_pcf_unit",
    module: "CO2E",
    section: "Zusammenfassung",
    labelDe: "PCF-Einheit",
    labelEn: "PCF unit",
    dataType: "dropdown",
    unit: "kg CO2e / t CO2e",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(52)],
  }),
  f({
    id: "co2e_reference_unit",
    module: "CO2E",
    section: "Zusammenfassung",
    labelDe: "Referenzeinheit",
    labelEn: "Reference unit",
    dataType: "text",
    unit: "Stück/kg/t",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(52)],
  }),
  f({
    id: "co2e_cbam_specific_direct_emissions",
    module: "CO2E",
    section: "Zusammenfassung",
    labelDe: "CBAM Specific Direct Embedded Emissions",
    labelEn: "CBAM specific direct embedded emissions",
    dataType: "number",
    unit: "t CO2e / t CBAM-Ware",
    classification: "input",
    requirement: "conditional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(52)],
  }),
  f({
    id: "co2e_emission_factor",
    module: "CO2E",
    section: "Material Feldbeschreibungen",
    labelDe: "Emissionsfaktor [kg CO2e / Mengeneinheit]",
    labelEn: "Emission factor [kg CO2e / unit of measure]",
    dataType: "number",
    unit: "kg CO2e/ME",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(55)],
  }),
  f({
    id: "co2e_emission_factor_source",
    module: "CO2E",
    section: "Material Feldbeschreibungen",
    labelDe: "Quelle des Emissionsfaktors",
    labelEn: "Source of the emission factor",
    dataType: "freetext",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(55)],
  }),
  f({
    id: "co2e_upstream_packaging_emissions",
    module: "CO2E",
    section: "Material Feldbeschreibungen",
    labelDe:
      "Upstream Emissionen der Materialverpackung [kg CO2e / Mengeneinheit]",
    labelEn:
      "Upstream emissions of material packaging [kg CO2e / unit of measure]",
    dataType: "number",
    unit: "kg CO2e/ME",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(55)],
  }),
  f({
    id: "co2e_upstream_transport_emissions",
    module: "CO2E",
    section: "Material Feldbeschreibungen",
    labelDe: "Upstream Emissionen Transport [kg CO2e / Mengeneinheit]",
    labelEn: "Upstream transport emissions [kg CO2e / unit of measure]",
    dataType: "number",
    unit: "kg CO2e/ME",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(55)],
  }),
  f({
    id: "co2e_total_distance",
    module: "CO2E",
    section: "Material Feldbeschreibungen",
    labelDe: "Gesamtentfernung [km]",
    labelEn: "Total distance [km]",
    dataType: "number",
    unit: "km",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(55)],
  }),
  f({
    id: "co2e_transport_mode",
    module: "CO2E",
    section: "Material Feldbeschreibungen",
    labelDe: "Transportmittel (Mehrfachnennung möglich)",
    labelEn: "Mode of transport (multiple selection possible)",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(55)],
  }),
  f({
    id: "co2e_material_emissions",
    module: "CO2E",
    section: "Material Feldbeschreibungen Teil 2",
    labelDe: "Emissionen Material [kg CO2e / Mengeneinheit]",
    labelEn: "Material emissions [kg CO2e / unit of measure]",
    dataType: "number",
    unit: "kg CO2e/ME",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(56)],
  }),
  f({
    id: "co2e_total_material_emissions",
    module: "CO2E",
    section: "Material Feldbeschreibungen Teil 2",
    labelDe: "Gesamtemissionen [kg CO2e]",
    labelEn: "Total emissions [kg CO2e]",
    dataType: "number",
    unit: "kg CO2e",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(56)],
  }),
  f({
    id: "co2e_recycled_content_recq",
    module: "CO2E",
    section: "Material Feldbeschreibungen Teil 2",
    labelDe: "Rezyklatgehalt (Rec-Q)",
    labelEn: "Recycled content (Rec-Q)",
    dataType: "percent",
    unit: "% pro Mengeneinheit",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(56)],
    notes: "Rec-Q = PrC + PoC.",
  }),
  f({
    id: "co2e_cn_code",
    module: "CO2E",
    section: "Material Feldbeschreibungen Teil 2 (CBAM)",
    labelDe: "CN-Code",
    labelEn: "CN code",
    dataType: "text",
    classification: "input",
    requirement: "conditional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(56)],
    notes: "8-stelliger CN-Code, CBAM-relevant.",
  }),
]

// ── WAF (Werkzeuganalyseformular) — CORE (not parsed, external form) ────────

const WAF_FIELDS: CanonicalField[] = [
  f({
    id: "waf_supplier_company",
    module: "WAF",
    section: "Ansprechpartner",
    labelDe: "Firma",
    labelEn: "Company",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
    notes: "Tool supplier company name.",
  }),
  f({
    id: "waf_country",
    module: "WAF",
    section: "Ansprechpartner",
    labelDe: "Land",
    labelEn: "Country",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
  }),
  f({
    id: "waf_contact_name",
    module: "WAF",
    section: "Ansprechpartner",
    labelDe: "Name des Ansprechpartners",
    labelEn: "Contact person name",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
  }),
  f({
    id: "waf_exchange_rate_to_euro",
    module: "WAF",
    section: "Währungsprämissen",
    labelDe: "Wechselkurs zum Euro",
    labelEn: "Exchange rate to Euro",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
  }),
  f({
    id: "waf_currency",
    module: "WAF",
    section: "Währungsprämissen",
    labelDe: "Währung",
    labelEn: "Currency",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
  }),
  f({
    id: "waf_charging_method",
    module: "WAF",
    section: "Verrechnungsform",
    labelDe: "Verrechnungsform",
    labelEn: "Charging method",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
    notes:
      'SBM or Vorrichtungen — must be set before "Werkzeugart" is selectable.',
  }),
  f({
    id: "waf_tool_type",
    module: "WAF",
    section: "Verrechnungsform",
    labelDe: "Werkzeugart",
    labelEn: "Tool type",
    dataType: "dropdown",
    classification: "input",
    requirement: "conditional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(81)],
  }),
  f({
    id: "waf_component_designation",
    module: "WAF",
    section: "Stammdaten Bauteil",
    labelDe: "Benennung",
    labelEn: "Component designation",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(82)],
  }),
  f({
    id: "waf_bmw_part_number",
    module: "WAF",
    section: "Stammdaten Bauteil",
    labelDe: "BMW Sachnummer", // allow-customer-string
    labelEn: "BMW part number", // allow-customer-string
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(82)],
    notes: "// allow-customer-string",
  }), // allow-customer-string
  f({
    id: "waf_material_cost_total",
    module: "WAF",
    section: "Kostenzusammenfassung",
    labelDe: "Materialkosten",
    labelEn: "Material costs",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(83)],
    notes: "Sum of all material cost lines. = Menge x WE/unit.",
  }),
  f({
    id: "waf_manufacturing_cost_engineering",
    module: "WAF",
    section: "Fertigungskosten",
    labelDe: "Engineering",
    labelEn: "Engineering",
    dataType: "number",
    unit: "Stunden",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(82)],
    notes: "Hours x Vollkostenstundensatz (VKSS).",
  }),
  f({
    id: "waf_logistics_transport",
    module: "WAF",
    section: "Logistik",
    labelDe: "Transport",
    labelEn: "Transport",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(83)],
  }),
  f({
    id: "waf_surcharge_percent",
    module: "WAF",
    section: "Kostenzusammenfassung",
    labelDe: "Zuschläge in %",
    labelEn: "Surcharges [%]",
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(83)],
  }),
  f({
    id: "waf_quotation_price",
    module: "WAF",
    section: "Angebotspreis",
    labelDe: "Angebotspreis",
    labelEn: "Quotation price",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(83)],
    notes:
      "= (Σ Materialkosten + Σ Fertigungskosten) x (1 + Zuschläge[%]) + Logistikkosten. Zuschläge NOT applied to logistics costs.",
  }),
]

// ── LAF (Logistikkostenanalyseformular) — CORE (not parsed, external form) ──

const LAF_FIELDS: CanonicalField[] = [
  f({
    id: "laf_supplier",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Lieferant",
    labelEn: "Supplier",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_contact_person",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Kontaktperson",
    labelEn: "Contact person",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_project",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Projekt",
    labelEn: "Project",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_vehicles_per_day_avg",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Fahrzeuge pro Tag (Durchschnitt)",
    labelEn: "Vehicles per day (average)",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_vehicles_per_day_peak",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Fahrzeuge pro Tag (Peak)",
    labelEn: "Vehicles per day (peak)",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_production_days_per_year",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Produktionstage pro Jahr",
    labelEn: "Production days per year",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_lifecycle_years",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Lebenszyklus [Jahre]",
    labelEn: "Lifecycle [years]",
    dataType: "number",
    unit: "Jahre",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
    notes: "SOP to EOP.",
  }),
  f({
    id: "laf_quotation_currency",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Angebotswährung",
    labelEn: "Quotation currency",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_distance_unit",
    module: "LAF",
    section: "Allgemeine Logistikdaten",
    labelDe: "Entfernungseinheit [km/miles]",
    labelEn: "Distance unit [km/miles]",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(64)],
  }),
  f({
    id: "laf_part_designation",
    module: "LAF",
    section: "Blatt Verpackung",
    labelDe: "Teilebenennung",
    labelEn: "Part designation",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(65)],
  }),
  f({
    id: "laf_parts_per_vehicle",
    module: "LAF",
    section: "Blatt Verpackung",
    labelDe: "Bauteile pro Fahrzeug",
    labelEn: "Parts per vehicle",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(65)],
  }),
  f({
    id: "laf_installation_rate",
    module: "LAF",
    section: "Blatt Verpackung",
    labelDe: "Verbaurate [%]",
    labelEn: "Installation rate [%]",
    dataType: "percent",
    unit: "%",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(65)],
  }),
  f({
    id: "laf_packaging_type",
    module: "LAF",
    section: "Blatt Verpackung",
    labelDe: "Verpackungsart",
    labelEn: "Packaging type",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(65)],
    notes: "Oneway vs. Returnables.",
  }),
  f({
    id: "laf_total_packaging_cost_per_part",
    module: "LAF",
    section: "Blatt Verpackung Teil 2",
    labelDe: "Summe der Verpackungskosten umgelegt auf ein Bauteil [WE]",
    labelEn: "Total packaging costs allocated to one part [currency unit]",
    dataType: "currency",
    classification: "calculated",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L2(66)],
  }),
]

// ── LEK (Lieferantenentwicklungskosten) — CORE (not parsed) ─────────────────

const LEK_FIELDS: CanonicalField[] = [
  f({
    id: "lek_award_scope",
    module: "LEK",
    section: "Zahlungsplan Allgemein",
    labelDe: "Vergabeumfang",
    labelEn: "Award scope",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(75)],
  }),
  f({
    id: "lek_product_line",
    module: "LEK",
    section: "Zahlungsplan Allgemein",
    labelDe: "Produktlinie",
    labelEn: "Product line",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(75)],
  }),
  f({
    id: "lek_supplier",
    module: "LEK",
    section: "Zahlungsplan Allgemein",
    labelDe: "Lieferant",
    labelEn: "Supplier",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(75)],
  }),
  f({
    id: "lek_supplier_number",
    module: "LEK",
    section: "Zahlungsplan Allgemein",
    labelDe: "Lieferanten-Nr. / Zähladresse",
    labelEn: "Supplier number / count address",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(75)],
  }),
  f({
    id: "lek_currency",
    module: "LEK",
    section: "Zahlungsplan Allgemein",
    labelDe: "Währung",
    labelEn: "Currency",
    dataType: "dropdown",
    classification: "input",
    requirement: "optional",
    level: "summary",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(75)],
    notes: "Default AW = EUR.",
  }),
  f({
    id: "lek_one_time_payment",
    module: "LEK",
    section: "Zahlungsplan Allgemein",
    labelDe: "LEK",
    labelEn: "LEK one-time payment",
    dataType: "currency",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(75)],
    notes: "One amount per year per Erstverwender/Folgeverwender.",
  }),
  f({
    id: "lek_qualification",
    module: "LEK",
    section: "Cost Break-Down",
    labelDe: "Qualifikation",
    labelEn: "Qualification",
    dataType: "text",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(77)],
  }),
  f({
    id: "lek_hourly_rate",
    module: "LEK",
    section: "Cost Break-Down",
    labelDe: "Währung / h",
    labelEn: "Currency / h (hourly rate)",
    dataType: "currency",
    unit: "Betrag/h",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(77)],
  }),
  f({
    id: "lek_effort_hours",
    module: "LEK",
    section: "Cost Break-Down",
    labelDe: "h (für alle Zeitblöcke)",
    labelEn: "Effort hours (all time blocks)",
    dataType: "number",
    unit: "Stunden",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(77)],
  }),
  f({
    id: "lek_repetition_count",
    module: "LEK",
    section: "Cost Break-Down",
    labelDe: "Anzahl Wiederholungen",
    labelEn: "Number of repetitions",
    dataType: "number",
    classification: "input",
    requirement: "optional",
    level: "row",
    templateVersions: TV_DE_ONLY,
    evidence: [L3(77)],
  }),
]

// ── Registry aggregate ────────────────────────────────────────────────────────

export const CANONICAL_FIELDS: CanonicalField[] = [
  ...SUMMARY_IDENTITY_FIELDS,
  ...SUMMARY_METRIC_FIELDS,
  ...SUMMARY_PREMISES_FIELDS,
  ...MANUFACTURING_FIELDS,
  ...G60_FIELDS,
  ...MATERIAL_FIELDS,
  ...SBM_FIELDS,
  ...LOGISTICS_FIELDS,
  ...RMR_FIELDS,
  ...LC_CN_FIELDS,
  ...CO2E_FIELDS,
  ...WAF_FIELDS,
  ...LAF_FIELDS,
  ...LEK_FIELDS,
]

// ── Backward mapping: existing production keys -> canonical id ──────────────
//
// Additive only (KAR-892 scope discipline, see canonical-model.ts header):
// the parsers that actually PRODUCE QAFFieldKey/SummaryMetricKey values
// (qaf-parser.ts, summary-metrics.ts) are untouched by this PR. These two
// tables are the "already exists, give it a canonical name" side of the
// registry — P1.2 is the (separate, future) PR that rewires the parsers
// themselves to look field metadata up here instead of their own local
// dictionaries.

export const QAF_FIELD_KEY_TO_CANONICAL: Record<QAFFieldKey, string> = {
  positionsnummer: "mfg_position_number",
  teilebenennung: "mfg_part_designation",
  prozessbezeichnung: "mfg_process_designation",
  bezeichnungAnlage: "mfg_facility_designation",
  standort: "mfg_site",
  beschaffungswaehrung: "mfg_procurement_currency",
  zykluszeit: "mfg_cycle_time",
  teileProZyklus: "mfg_parts_per_cycle",
  anzahlMA: "mfg_direct_employee_count",
  lohnkosten: "mfg_direct_labor_cost",
  lohnzuschlagssaetze: "mfg_social_overhead_rate",
  mss: "mfg_machine_hour_rate",
  ruestkosten: "mfg_setup_cost_per_unit",
  fek: "mfg_direct_manufacturing_cost",
  rfgk: "mfg_remaining_overhead_rate",
  fk: "mfg_manufacturing_cost_bw",
  angebotswaehrung: "mfg_quotation_currency",
  wechselkurs: "mfg_exchange_rate",
  anzahlProAngebotsteil: "mfg_quantity_per_quoted_part",
  fkAW: "mfg_manufacturing_cost_aw",
  ausschuss: "mfg_scrap_rate",
  ausschusskosten: "mfg_scrap_cost",
}

export const SUMMARY_METRIC_KEY_TO_CANONICAL: Record<SummaryMetricKey, string> =
  {
    materialCosts: "sum_material_costs",
    manufacturingCosts: "sum_manufacturing_costs",
    totalProductionCosts: "sum_total_production_costs",
    packagingTransportIncluded: "sum_packaging_transport_included",
    customsIncluded: "sum_customs_included",
    devicesAndTools: "sum_devices_and_tools",
    scrapMaterial: "sum_scrap_material",
    scrapManufacturing: "sum_scrap_manufacturing",
    totalCosts: "sum_total_costs",
    otherSurcharges: "sum_other_surcharges",
    quotationBasePrice: "sum_quotation_base_price",
    rawMaterialPriceShareMaterial: "sum_raw_material_price_share_material",
    rawMaterialPriceShareEnergy: "sum_raw_material_price_share_energy",
    customsSupplierToBMW: "sum_customs_supplier_to_bmw",
    transportSupplierToBMW: "sum_transport_supplier_to_bmw",
    quotationPrice: "sum_quotation_price",
    oneTimeDevelopment: "sum_one_time_development",
    oneTimeTools: "sum_one_time_tools",
    totalOneTimePayment: "sum_total_one_time_payment",
    // KAR-910
    costBreakdownAw1: "sum_cost_breakdown_aw1",
  }

// MATERIAL backward mapping (KAR-897/P1.6) — same additive relationship as
// QAF_FIELD_KEY_TO_CANONICAL/SUMMARY_METRIC_KEY_TO_CANONICAL above, just for
// the newly-shipped material-parser.ts MaterialFieldKey values instead of an
// already-existing production key set (MATERIAL had no parser before this PR
// — this table is minted alongside the parser itself, not retrofitted onto
// a pre-existing dictionary).
export const MATERIAL_FIELD_KEY_TO_CANONICAL: Record<MaterialFieldKey, string> = {
  positionNumber: "mat_position_number",
  partDesignation: "mat_part_designation",
  materialDesignation: "mat_raw_material_or_purchased_part_designation",
  supplier: "mat_supplier",
  technicalFunction: "mat_technical_function",
  countryOfOrigin: "mat_country_of_origin",
  htsCode: "mat_hts_code",
  unitOfMeasure: "mat_unit_of_measure",
  procurementCurrency: "mat_procurement_currency",
  costPerUnitBw: "mat_calc_material_cost_per_unit_bw",
  quotationCurrency: "mat_quotation_currency",
  exchangeRate: "mat_exchange_rate",
  packagingCostPerUnit: "mat_packaging_cost_per_unit",
  transportCostPerUnit: "mat_transport_cost_per_unit",
  customsCbamCostPerUnit: "mat_customs_cbam_cost_per_unit",
  overheadCost: "mat_overhead_cost",
  costPerUnitAw: "mat_calc_material_cost_per_unit_aw",
  referenceQuantity: "mat_reference_quantity",
  netQuantity: "mat_net_quantity",
  rebate: "mat_calc_rebate",
  quantityPerQuotedPart: "mat_quantity_per_quoted_part",
  materialCost: "mat_calc_material_cost",
  scrapRate: "mat_scrap_rate",
  scrapCost: "mat_scrap_cost",
  rawMaterialDesignation: "mat_raw_material_designation",
  referenceWeight: "mat_reference_weight",
  rawMaterialQuotation: "mat_raw_material_quotation",
  rawMaterialSurcharge: "mat_raw_material_surcharge",
  // KAR-910
  packagingCostOffer: "mat_packaging_cost_offer",
}

// SBM backward mapping (KAR-898/P1.7) — same additive relationship as
// MATERIAL_FIELD_KEY_TO_CANONICAL above, minted alongside the newly-shipped
// sbm-parser.ts SbmFieldKey values (SBM had no parser before this PR).
export const SBM_FIELD_KEY_TO_CANONICAL: Record<SbmFieldKey, string> = {
  positionNumber: "sbm_position_number",
  verrechnungsform: "sbm_verrechnungsform",
  toolFixtureType: "sbm_werkzeug_vorrichtungsart",
  componentDesignation: "sbm_bauteilbezeichnung",
  cavityConfiguration: "sbm_auslegung_kavitaeten",
  stageCount: "sbm_anzahl_stufen_takte",
  serviceLifeCycles: "sbm_standzeit",
  toolDeploymentDate: "sbm_einsatztermin_werkzeug",
  orderLeadTimeWeeks: "sbm_auftragszeit",
  manufacturerLocation: "sbm_standort_hersteller",
  procurementCurrency: "sbm_procurement_currency",
  toolFixtureCostBw: "sbm_calc_tool_fixture_cost_bw",
  bmwPartNumber: "sbm_bmw_part_number",
  componentIndex: "sbm_index",
  length: "sbm_length",
  width: "sbm_width",
  height: "sbm_height",
  sheetThickness: "sbm_sheet_thickness",
  componentSurface: "sbm_component_surface",
  toolDimensions: "sbm_tool_dimensions",
  toolWeight: "sbm_tool_weight",
  toolConcept: "sbm_tool_concept",
  connectionConcept: "sbm_connection_concept",
  slidersGeneral: "sbm_sliders_general",
  slidersHydraulic: "sbm_sliders_hydraulic",
  slidersAngled: "sbm_angled_sliders",
  slidersJaw: "sbm_jaw_sliders",
  slidersTotal: "sbm_sliders_total",
  remarks: "sbm_remarks",
  quotationCurrency: "sbm_quotation_currency",
  exchangeRate: "sbm_exchange_rate",
  toolFixtureCount: "sbm_tool_fixture_count",
  totalToolFixtureCostAw: "sbm_total_tool_fixture_cost_aw",
  totalSbmToolCostAw: "sbm_total_sbm_tool_cost",
  devicesFollowupMaintenancePerUnit: "sbm_devices_followup_maintenance_per_unit",
  // KAR-910
  stageCountActive: "sbm_anzahl_stufen_takte_aktiv",
  stageCountEmpty: "sbm_anzahl_stufen_takte_leer",
}

// RMR (RAW MATERIAL RISKS) backward mapping (KAR-902/P2.3) — same additive
// relationship as MATERIAL_FIELD_KEY_TO_CANONICAL/SBM_FIELD_KEY_TO_CANONICAL
// above, minted alongside the newly-shipped rmr-parser.ts RmrFieldKey values
// (RMR had no parser before this PR — the 12 canonical rmr_* ids themselves
// already existed, RMR_FIELDS, KAR-892/P1.1).
export const RMR_FIELD_KEY_TO_CANONICAL: Record<RmrFieldKey, string> = {
  positionNumber: "rmr_position_number",
  rawMaterialDesignation: "rmr_raw_material_designation",
  referenceWeight: "rmr_reference_weight",
  rawMaterialKey: "rmr_raw_material_key",
  rawMaterialQuotation: "rmr_raw_material_quotation",
  rawMaterialSurcharge: "rmr_raw_material_surcharge",
  settlementModel: "rmr_settlement_model",
  indexDesignation: "rmr_index_designation",
  indexQuotation: "rmr_index_quotation",
  bmwParticipationRate: "rmr_bmw_participation_rate",
  threshold: "rmr_threshold",
  remark: "rmr_remark",
}

// LOGISTICS&CUSTOM backward mapping (KAR-903/P2.4) — same additive
// relationship as MATERIAL_FIELD_KEY_TO_CANONICAL/SBM_FIELD_KEY_TO_CANONICAL/
// RMR_FIELD_KEY_TO_CANONICAL above, minted alongside the newly-shipped
// logistics-parser.ts LogisticsFieldKey values (LOGISTICS had no parser
// before this PR — the 14 canonical log_* ids themselves already existed,
// LOGISTICS_FIELDS, KAR-892/P1.1).
export const LOG_FIELD_KEY_TO_CANONICAL: Record<LogisticsFieldKey, string> = {
  positionNumber: "log_position_number",
  deliverySite: "log_delivery_site",
  totalVolume: "log_total_volume",
  quotationCurrency: "log_quotation_currency",
  transportCostPerPart: "log_transport_cost_per_part",
  deliveryTerms: "log_delivery_terms",
  packagingCostPerPart: "log_packaging_cost_per_part",
  surchargeKeyPackaging: "log_surcharge_key_packaging",
  prePackagingCostPerPart: "log_pre_packaging_cost_per_part",
  surchargeKeyPrePackaging: "log_surcharge_key_pre_packaging",
  logisticsCostPerDeliverySite: "log_logistics_cost_per_delivery_site",
  customsCostPerPart: "log_customs_cost_per_part",
  customsTariffNumber: "log_customs_tariff_number",
  customsCostPerDeliverySite: "log_customs_cost_per_delivery_site",
}

// LC-CN (LOCAL CONTENT CHINA) backward mapping (KAR-904/P2.5) — same additive
// relationship as MATERIAL_FIELD_KEY_TO_CANONICAL/.../LOG_FIELD_KEY_TO_CANONICAL
// above, minted alongside the newly-shipped lccn-parser.ts LccnFieldKey values
// (LC-CN had no parser before this PR). Covers 15 of the 17 registered
// lccn_* canonical ids — the remaining 2 (lccn_am_cif_share,
// lccn_am_process_clustering) are level:'row' MATERIAL/MANUFACTURING COSTS
// "Gruppierung" columns, out of lccn-parser.ts's sheet scope (see that
// module's own header "Field-Scope"), so they stay registered but
// deliberately unmapped here.
export const LCCN_FIELD_KEY_TO_CANONICAL: Record<LccnFieldKey, string> = {
  localMaterialCost: "lccn_local_material_cost",
  localMaterialCostExclMgk: "lccn_local_material_cost_excl_mgk",
  cifImportedMaterialSubsupplier: "lccn_cif_imported_material_subsupplier",
  importedMaterialCost: "lccn_imported_material_cost",
  cifImportedMaterial: "lccn_cif_imported_material",
  rawMaterialCost: "lccn_raw_material_cost",
  rawMaterialCostExclMgk: "lccn_raw_material_cost_excl_mgk",
  substantialManufacturingCost: "lccn_substantial_manufacturing_cost",
  otherManufacturingCost: "lccn_other_manufacturing_cost",
  totalProductionCost: "lccn_total_production_cost",
  devicesSubstantial: "lccn_devices_substantial",
  devicesOther: "lccn_devices_other",
  totalImportedValueCif: "lccn_total_imported_value_cif",
  totalLcManufacturingCost: "lccn_total_lc_manufacturing_cost",
  lcRate: "lccn_lc_rate",
}

// CO2e (CARBON FOOTPRINT) backward mapping (KAR-904/P2.5) — same additive
// relationship as LCCN_FIELD_KEY_TO_CANONICAL above, minted alongside the
// newly-shipped co2e-parser.ts Co2eFieldKey values (CO2E had no parser before
// this PR). Covers all 14 registered co2e_* canonical ids.
export const CO2E_FIELD_KEY_TO_CANONICAL: Record<Co2eFieldKey, string> = {
  pcf: "co2e_pcf",
  pcfUnit: "co2e_pcf_unit",
  referenceUnit: "co2e_reference_unit",
  cbamSpecificDirectEmissions: "co2e_cbam_specific_direct_emissions",
  emissionFactor: "co2e_emission_factor",
  emissionFactorSource: "co2e_emission_factor_source",
  upstreamPackagingEmissions: "co2e_upstream_packaging_emissions",
  upstreamTransportEmissions: "co2e_upstream_transport_emissions",
  totalDistance: "co2e_total_distance",
  transportMode: "co2e_transport_mode",
  materialEmissions: "co2e_material_emissions",
  totalMaterialEmissions: "co2e_total_material_emissions",
  recycledContentRecq: "co2e_recycled_content_recq",
  cnCode: "co2e_cn_code",
}
