---
name: project-08-07-stueckliste-pipeline
description: Klartext-Stücklisten je Auftrag aus den eingebetteten PDFs der .plax ziehen — wiederholbare Pipeline für die kommenden ~2000 Aufträge
metadata: 
  node_type: memory
  type: project
  originSessionId: 9605d7dd-3c40-4f92-b05f-540606518879
---

Die Bauteil-Klartexte (Bezeichnung + Würth-Artikelnummer) stehen **nur in der eingebetteten PDF** jeder `.plax` (Auftragsbestätigung/Angebot), nicht in der XML/JSON — dort nur `modul_id` + Preise. Siehe [[reference-extracted-xml-data]], [[project-08-07-datenmodell]].

**Fundort in der .plax (ZIP):** `auftragsbestaetigung_*.pdf`. Stücklisten-Zeile: `Pos | Artikelnummer | Abmessungen | Menge | Nettowert`, darunter die Bezeichnungszeile, die mit dem Kurzzeichen (= `modul_id`) beginnt. Bei mehrsprachig-beschreibenden `modul_id` (z. B. `KlesknaggWürth4kroker`) ist die Zeile selbst die Bezeichnung mit Trennzeichen (`Klesknagg, Würth, 4 kroker`).

**Wiederholbare Pipeline (bei neuen .plax / für die ~2000 Aufträge):** `import.py` löscht `wuerth.db` und baut aus `schema.sql` **komplett neu** — danach müssen ALLE Folge-Loader wieder laufen, sonst sind Stückliste UND Veredelung leer. Reihenfolge:
1. Neue `.plax` nach `data/Auftragsplanungen/` legen; aktualisierten `data/extracted_xml_data.json` nach `data/`.
2. `python3 pipeline/import.py` — kunden/auftraege/module/positionen.
3. `python3 pipeline/extract_stueckliste.py` — PDFs → `module.klartext` + `module.artikelnummer`, Beleg `pipeline/stueckliste.json`.
4. `python3 pipeline/enrich_currency.py` — echte Währung + Wechselkurs (siehe unten) → `auftraege.waehrung/wechselkurs/kurs_datum`, Cache `pipeline/fx_cache.json`.
5. `python3 pipeline/enrich_load.py` — Firmendaten (`kunde_firmendaten`, `web_cache`) aus `pipeline/enrichment/*.json` (offline-Cache).
6. `python3 pipeline/enrich_fzg_load.py` — Fahrzeug (`auftrag_fzg`) aus `pipeline/fzg_enrichment/*.json`.
7. App gegen die DB; Popup je Auftrag rendert die Stückliste (`list_positionen`) inkl. EUR-Spalte + Kurs-Fußzeile.

Schritte 5/6 sind idempotent (leeren ihre Tabellen vorab) und laufen offline aus dem JSON-Cache — kein Web-Call.

**Währung/Wechselkurs (Schritt 4):** `extracted_xml_data.json`-Feld `currency` ist unbrauchbar (konstant `EUR` für alle). Echte Landeswährung nur im .plax-XML: `VARNAME:CURRENCY:USER:EINSTELLUNGEN_LAND` (EUR/NOK/CHF/PLN), bestätigt durch Label-Übersetzung `EUR→<Währung>`. Verteilung Stand 191: EUR 170, NOK 15, CHF 5, PLN 1. Kurs = EZB-Referenz zum Auftragsdatum (Abschlusstag aus dem Export-Key) via `api.frankfurter.dev` (User-Agent-Header nötig, sonst 403; wählt bei Wochenende/Feiertag den letzten Handelstag). `wechselkurs` = Einheiten je EUR (EUR=1), EUR-Wert = Betrag ÷ Kurs. `v_kunde_360.umsatz_netto` und `list_auftraege.umsatz_eur` sind darüber EUR-normiert (sonst NOK+EUR gemischt).

**Matching-Logik (extract_stueckliste.py):** Positionszeile per Split an ≥2 Leerzeichen (Spalten sauber trennen, sonst laufen Artikelnummer/Abmessung zusammen). Bezeichnung→`modul_id` über (a) erstes Token == modul_id, (b) normalisierter Präfix (Sonderzeichen/Leerzeichen strippen, lower) — fängt die ~10 % beschreibenden IDs, Abdeckung nahe 100 %. Kanonische Bezeichnung je Modul = häufigste, Deutsch bevorzugt (dieselbe modul_id hat je Auftrag DE/NO/IT-Varianten). Fallback `klartext = modul_id`, wenn ein Modul in keiner PDF vorkommt.

**Gemessen (Stand 213 .plax / 804 module):** 7.138 Positionszeilen, 90 % direkt per Kurzzeichen gemappt; Rest über Normalisierung.
