#!/usr/bin/env python3 """ generate_fixtures.py -------------------- Erzeugt deterministische Test-Eingabedaten für den HVB-ETL-Prozess (Skript include/02_etl_angebote_zuschlaege.py). Pro Quell-Ordner werden MINDESTENS 4 Excel-Dateien erzeugt, die gezielt folgende Fälle abdecken (vollständige Beschreibung in tests/README.md): taifun_export/ - dieselbe Angebotsnummer in mehreren Dateien (jüngster Export-Stand gewinnt), Standort- vs. Kundenadresse, Auslands-Angebot, Projektnummer-Status. pflege/ - UNION mehrerer Status-Historien, exakte Dubletten, Tie-Break per Status-Rang, Waisen-Status (ohne Angebot). archiv/ - Alt-Angebote ergänzen den Export; bereits im Export vorhandene Nummern werden ignoriert; Datei-Dubletten. zuschlaege/ - zwei Blätter (Detail/Kompakt) mit Kopfzeile in Zeile 4, Dedup je (Zuschlags-Nr, Flurstück), PLZ ohne Geo-Treffer. Aufruf: python tests/generate_fixtures.py """ from pathlib import Path import pandas as pd BASIS = Path(__file__).parent INPUT = BASIS / "fixtures" / "input" # Reale PLZ aus etl_cache/geo_plz_koordinaten.csv (haben Koordinaten): # 10115 Berlin | 20095 Hamburg | 50667 Köln | 80331 München # 04109 Leipzig (führende Null!) | 99999 = bewusst OHNE Geo-Treffer EXPORT_COLS = [ "Nummer", "Datum", "Kunde", "Debitor", "Beschreibung", "Bearbeiter", "Status", "Projekt", "Gültig bis", "Kunden-Adresse: PLZ, Ort", "Kunden-Adresse: Straße, Nr.", "Kunden-Adresse: Land (ISO-Ländercode)", "Kunden-Adresse: Telefon", "Kunden-Adresse: Mobiltelefon", "Standort-Adresse: PLZ, Ort", "Wiedervorlage: Datum", ] def _exp(nummer, datum, kunde, debitor, beschreibung, projekt="", kunden_plz_ort="", land="DE", standort_plz_ort=""): return { "Nummer": nummer, "Datum": datum, "Kunde": kunde, "Debitor": debitor, "Beschreibung": beschreibung, "Bearbeiter": "Schmidt", "Status": "", "Projekt": projekt, "Gültig bis": "2099-12-31", "Kunden-Adresse: PLZ, Ort": kunden_plz_ort, "Kunden-Adresse: Straße, Nr.": "Teststr. 1", "Kunden-Adresse: Land (ISO-Ländercode)": land, "Kunden-Adresse: Telefon": "030-123", "Kunden-Adresse: Mobiltelefon": "", "Standort-Adresse: PLZ, Ort": standort_plz_ort, "Wiedervorlage: Datum": "", } def schreibe_export(): ordner = INPUT / "taifun_export" ordner.mkdir(parents=True, exist_ok=True) # Datei 1 (ältester Stand, siehe _dateistand.csv): A-1001 v0 ("ALT") df1 = pd.DataFrame([ _exp("A-1001", "2023-12-01", "Müller ALT GmbH", 70001, "Altanlage 100 m³", kunden_plz_ort="10115 Berlin"), ]) # Datei 2: A-1001 v1, A-1002 (mit echter Projektnummer + Pflege -> Konflikt) df2 = pd.DataFrame([ _exp("A-1001", "2024-01-10", "Müller GmbH", 70001, "Pufferspeicher 800 m³", kunden_plz_ort="10115 Berlin"), _exp("A-1002", "2024-01-20", "Bauer AG", 70002, "Heizung 500 m³", projekt="P-12345", kunden_plz_ort="20095 Hamburg"), ]) # Datei 3: A-1003 (Pflege Auftrag), A-1004 (Projektnr ohne Pflege -> Auftrag) df3 = pd.DataFrame([ _exp("A-1003", "2024-02-05", "Klein KG", 70003, "Solaranlage", projekt="Auftrag verloren", kunden_plz_ort="50667 Köln"), _exp("A-1004", "2024-02-12", "Lang GmbH", 70004, "Anlage 2000 m³", projekt="P-67890", kunden_plz_ort="", standort_plz_ort="80331 München"), ]) # Datei 4 (jüngster Stand): A-1001 v2 (gewinnt!), A-1005 (gleicher Kunde # wie A-1001 -> Kunden-Dedup), A-1006 (Ausland + Abbruch-Projekttext) df4 = pd.DataFrame([ _exp("A-1001", "2024-03-02", "Müller GmbH", 70001, "Pufferspeicher 1000 m³ neu", kunden_plz_ort="10115 Berlin"), _exp("A-1005", "2024-03-15", "Müller GmbH", 70001, "Erweiterung 1200 m³", kunden_plz_ort="10115 Berlin"), _exp("A-1006", "2024-03-20", "Swiss AG", 70006, "Anlage CH", projekt="kein Interesse", kunden_plz_ort="1010 Wien", land="CH"), ]) for name, df in [ ("export_00_dup.xlsx", df1), ("export_2024_01.xlsx", df2), ("export_2024_02.xlsx", df3), ("export_2024_03.xlsx", df4), ]: df[EXPORT_COLS].to_excel(ordner / name, sheet_name="Tabelle 1", index=False) # _dateistand.csv: legt die Reihenfolge "jüngster Export gewinnt" fest. # (Im Echtbetrieb schreibt der DAG diese Datei aus den WebDAV-mtimes.) stand = pd.DataFrame([ {"datei": "export_00_dup.xlsx", "stand_epoch": 500.0}, {"datei": "export_2024_01.xlsx", "stand_epoch": 2000.0}, {"datei": "export_2024_02.xlsx", "stand_epoch": 3000.0}, {"datei": "export_2024_03.xlsx", "stand_epoch": 4000.0}, ]) stand.to_csv(INPUT / "_dateistand.csv", index=False) print(f"taifun_export: 4 Dateien + _dateistand.csv") def _pflege(nummer, datum, status, netto=None, kontaktart="Telefon", zustaendig="Meier"): return { "Nummer": nummer, "Status-Datum": datum, "Status": status, "Netto (EUR)": netto, "Kontaktart": kontaktart, "Zuständig": zustaendig, "Wettbewerber": "", "Abbruchgrund": "", "Wiedervorlage": "", "Bemerkungen": "", } def schreibe_pflege(): ordner = INPUT / "pflege" ordner.mkdir(parents=True, exist_ok=True) df1 = pd.DataFrame([ _pflege("A-1001", "2024-01-15", "Kontakt/Lead", 0), _pflege("A-1001", "2024-02-20", "Angebot", 50000), _pflege("A-1002", "2024-02-01", "Angebot", 30000), ]) df2 = pd.DataFrame([ _pflege("A-1001", "2024-03-05", "Verhandlung", 55000), _pflege("A-1003", "2024-02-10", "Auftrag", 80000), ]) # Datei 3: exakte Dublette von A-1003 (wird entfernt) + Tie-Break-Test: # A-1001 am selben Datum wie "Verhandlung", aber niedrigerer Rang -> verliert. df3 = pd.DataFrame([ _pflege("A-1003", "2024-02-10", "Auftrag", 80000), _pflege("A-1001", "2024-03-05", "Angebot", 52000), ]) # Datei 4: Waisen-Status ohne zugehöriges Angebot (nur in Historie sichtbar) df4 = pd.DataFrame([ _pflege("A-9999", "2024-01-01", "Angebot", 9999), ]) for name, df in [ ("pflege_2024_01.xlsx", df1), ("pflege_2024_02.xlsx", df2), ("pflege_2024_03_dup.xlsx", df3), ("pflege_2024_04_waise.xlsx", df4), ]: df.to_excel(ordner / name, sheet_name="Status_Historie", index=False) print("pflege: 4 Dateien") def _archiv(nummer, kunde, debitor, datum, plz_ort, beschreibung="Alt-Projekt"): return { "Nummer": nummer, "Kunde": kunde, "Debitor": debitor, "Datum": datum, "PLZ, Ort": plz_ort, "Beschreibung": beschreibung, "Telefon": "0341-9", "Mobil": "", "E-Mail": "alt@example.de", "Ansprechpartner": "Herr Alt", } def schreibe_archiv(): ordner = INPUT / "archiv" ordner.mkdir(parents=True, exist_ok=True) df1 = pd.DataFrame([ _archiv("B-9001", "Altkunde Nord", 80001, "2015-05-01", "10115 Berlin"), ]) df2 = pd.DataFrame([ _archiv("B-9002", "Altkunde Süd", 80002, "2016-06-01", "04109 Leipzig"), ]) # Datei 3: A-1002 ist bereits im Export -> wird ignoriert (Export gewinnt); # B-9001 ist Dublette aus Datei 1 -> wird entfernt. df3 = pd.DataFrame([ _archiv("A-1002", "Bauer AG ARCHIV", 70002, "2014-01-01", "20095 Hamburg"), _archiv("B-9001", "Altkunde Nord", 80001, "2015-05-01", "10115 Berlin"), ]) df4 = pd.DataFrame([ _archiv("B-9003", "Altkunde West", 80003, "2017-07-01", "50667 Köln"), ]) for name, df in [ ("archiv_1.xlsx", df1), ("archiv_2.xlsx", df2), ("archiv_3_dup.xlsx", df3), ("archiv_4.xlsx", df4), ]: df.to_excel(ordner / name, sheet_name="Archiv_Stammdaten", index=False) print("archiv: 4 Dateien") # Detail-Spalten werden vom ETL positionsbasiert umbenannt; Kopfzeile in Zeile 4 # (header=3). Spaltennamen hier nur informativ — "Postleitzahl" steuert dtype=str. DETAIL_COLS = ["Bieter", "Gebot-Nr", "Zuschlags-Nr", "Bundesland", "Landkreis", "Postleitzahl", "Gemeinde", "Gemarkung", "Flurstück"] def _detail(bieter, gebot, zuschlag, bundesland, kreis, plz, gemeinde, gemarkung, flurstueck): return dict(zip(DETAIL_COLS, [bieter, gebot, zuschlag, bundesland, kreis, plz, gemeinde, gemarkung, flurstueck])) def _schreibe_zuschlag_datei(pfad, detail_rows, kompakt_rows): detail = pd.DataFrame(detail_rows, columns=DETAIL_COLS) kompakt = pd.DataFrame(kompakt_rows, columns=["Zuschlags-Nr", "Gebotsmenge"]) with pd.ExcelWriter(pfad, engine="openpyxl") as xls: # startrow=3 -> Kopfzeile landet in Zeile 4 (read_excel header=3) detail.to_excel(xls, sheet_name="Zuschläge_Detailliert", index=False, startrow=3) kompakt.to_excel(xls, sheet_name="Zuschläge_Kompakt", index=False, startrow=3) def schreibe_zuschlaege(): ordner = INPUT / "zuschlaege" ordner.mkdir(parents=True, exist_ok=True) # Datei 1: Z-100 mit zwei Flurstücken (beide bleiben erhalten) _schreibe_zuschlag_datei( ordner / "zuschlaege_1.xlsx", [_detail("WindCo", "G-01", "Z-100", "Berlin", "Kreis A", "10115", "Berlin", "GemA", "F1"), _detail("WindCo", "G-01", "Z-100", "Berlin", "Kreis A", "10115", "Berlin", "GemA", "F2")], [["Z-100", 5000]], ) # Datei 2: Z-200 in München (PLZ hat eigenes Angebot -> Flag True) _schreibe_zuschlag_datei( ordner / "zuschlaege_2.xlsx", [_detail("SolarCo", "G-02", "Z-200", "Bayern", "Kreis B", "80331", "München", "GemB", "F3")], [["Z-200", 3000]], ) # Datei 3: exakte Dublette (Z-100, F1) -> wird per Dedup entfernt _schreibe_zuschlag_datei( ordner / "zuschlaege_3_dup.xlsx", [_detail("WindCo", "G-01", "Z-100", "Berlin", "Kreis A", "10115", "Berlin", "GemA", "F1")], [["Z-100", 5000]], ) # Datei 4: Z-300 mit Leipzig (führende Null!) + PLZ ohne Geo-Treffer (99999) _schreibe_zuschlag_datei( ordner / "zuschlaege_4.xlsx", [_detail("HydroCo", "G-03", "Z-300", "Sachsen", "Kreis C", "04109", "Leipzig", "GemC", "F4"), _detail("HydroCo", "G-03", "Z-300", "Sachsen", "Kreis C", "99999", "Nirgendwo", "GemD", "F5")], [["Z-300", 7000]], ) print("zuschlaege: 4 Dateien") def main(): if INPUT.exists(): import shutil shutil.rmtree(INPUT) INPUT.mkdir(parents=True) schreibe_export() schreibe_pflege() schreibe_archiv() schreibe_zuschlaege() print(f"\nFixtures erzeugt unter: {INPUT}") if __name__ == "__main__": main()