#!/usr/bin/env python3
"""Parser für Shell-Tankbelege (PDF), aufgerufen von belegverarbeitung.py.

Diese Datei fehlte beim Bau im Projektordner (siehe Kopfkommentar in
pyscript/belegverarbeitung.py und README.md, Abschnitt "Bekannte Lücken").
Sie ist hier gegen echte Shell-eReceipts neu geschrieben und gegen deren
tatsächlich von pypdf extrahierten Text geprüft - nicht gegen eine
angenommene Struktur. Regressionstest mit zehn realen Belegen aus vier
Stationen liegt unter tests/test_shell_beleg_parser.py (Aufruf: python3
tests/test_shell_beleg_parser.py aus diesem Ordner). Die Belege
unterscheiden sich u. a. in der Markenzeile ("SHELL STATION" vs.
"Shell Station" vs. "Shell-Station") und der Rabattbezeichnung
("V-Power Smart Deal" vs. "ClubSmartRabatt") - deshalb ist die
Stations- und Rabatterkennung unten bewusst nicht an deren genauen
Wortlaut gebunden, sondern an feste Beleg-Struktur-Marker (Obj.-Nr.,
Betragsmuster vor "EUR #A").

Abhängigkeit: pypdf (reine Textextraktion, der Beleg ist ein normales
Text-PDF, kein Scan/Bild - kein OCR nötig). Läuft als eigener Prozess über
`python3 shell_beleg_parser.py <pfad>` (system-python3 im Container, nicht
die pyscript-Sandbox - siehe _parser_aufrufen() in belegverarbeitung.py),
deshalb reicht `pip install pypdf` einmalig im selben Python, das
`python3` in configuration.yaml/Docker auch sonst benutzt.

Kommandozeilen-Schnittstelle wie von belegverarbeitung.py angenommen:
Ein Pfad zu einer PDF-Datei als einziges Argument, JSON-Objekt auf stdout
bei Erfolg, Exit-Code ungleich 0 und Fehlertext auf stderr bei Misserfolg
(z. B. wenn der Beleg keiner der erkannten Vorlagen entspricht).

Aufbau eines Shell-eReceipts (aus dem echten Beispiel, Reihenfolge fix):
    SHELL STATION
    <Betreibername, z.B. "A. Zrenner GmbH">
    <Straße>
    <PLZ Ort>
    Obj.-Nr.: <Stations-ID> Tel. ...
    Beleg-Nr. <receipt_no> <TT.MM.JJ> <HH:MM>
    *<article_no> <Produktname>      <Listenbetrag> EUR #A*
    *Zp <Zapfsäule>  <Liter> l  <Listenpreis/l> EUR/l  # *
        [<Rabattname>  -<Rabattbetrag> EUR #A     -- optional, nur mit SmartDeal
         <Rabatt/l>  EUR/Liter                    -- optional]
        Gesamtbetrag  <Endbetrag> EUR
    Typ  Netto  Mwst  Brutto
    A:<Mwst-Satz>%  <Netto>  <Mwst>  <Brutto>
    ...
    Mobile Payment
    <TT.MM.JJJJ>  <HH:MM:SS>            -- Zahlungszeitstempel
    ...
    (Seite 2) POS:...-...-... TSE-Beleg-Nr: <tse_beleg_nr>
    Start/Ende: <TT.MM.JJJJ HH:MM:SS> / ...
"""

import datetime
import hashlib
import json
import re
import sys

try:
    import pypdf
except ImportError:
    print(
        "pypdf fehlt. Einmalig installieren mit: pip install pypdf "
        "(im selben Python, das diesen Parser ausführt, siehe Kopfkommentar).",
        file=sys.stderr,
    )
    sys.exit(1)


def _text_aus_pdf(pdf_pfad):
    leser = pypdf.PdfReader(pdf_pfad)
    return "\n".join(seite.extract_text() or "" for seite in leser.pages)


def _de_zahl(text):
    """"103,06" -> 103.06 / "2,399" -> 2.399. Kein Tausenderpunkt bei
    Tankbeträgen dieser Größenordnung zu erwarten, trotzdem robust: Punkte
    vor dem Umwandeln entfernen, nicht nur das Komma ersetzen."""
    if text is None:
        return None
    return float(text.strip().replace(".", "").replace(",", "."))


def _iso(tag, monat, jahr, stunde, minute, sekunde="0"):
    jahr = int(jahr)
    if jahr < 100:
        jahr += 2000
    return datetime.datetime(
        jahr, int(monat), int(tag), int(stunde), int(minute), int(sekunde)
    ).isoformat()


_ZAHL = r"[\d.,]+"

# Echte Geldangabe: Nachkommastellen sind Pflicht, damit im stationsunabhängigen
# Parser (_parsen_generisch) keine Artikel-/Belegnummer als Betrag durchgeht.
_GELD = r"\d{1,3}(?:[ .]\d{3})*,\d{2}"
_GESAMT_WOERTER = ("GESAMT", "ABSOLUT", "ENDBETRAG", "ZUZAHLEN", "TOTAL", "SUMME")
# Steuer- und Nettozeilen tragen dieselben Schlüsselwörter ("MWST GESAMT"),
# nennen aber nie den bezahlten Rechnungsbetrag.
_STEUER_WOERTER = ("MWST", "UST", "VAT", "STEUER", "NETTO")

# Kraftstoffmarken, wie sie im Belegkopf stehen. Bewusst nur dort gesucht (die
# ersten Zeilen), nicht im ganzen Text: "Total" wäre sonst z. B. auch als
# Summenzeile ein Treffer. Längere Namen zuerst, damit "TotalEnergies" nicht
# als "Total" endet. Die Schreibweise hier ist die, die angezeigt wird - auf
# Bons steht die Marke meist in Großbuchstaben ("SHELL STATION").
_MARKEN = (
    "TotalEnergies", "Total", "Aral", "Shell", "Eni", "Agip", "Esso", "OMV",
    "Avia", "Turmöl", "Turmoel", "Orlen", "Tamoil", "Westfalen", "Allguth",
    "Classic", "Sprint", "Raiffeisen", "BayWa", "Elan", "HEM", "Star", "JET",
    "bft", "BP", "Q1",
)
_STRASSE_WOERTER = r"(?:stra(?:ss|ß)e|str\.|gasse|weg|platz|allee|ring|damm|chaussee)"


def _marke(zeilen):
    """Markenname aus dem Belegkopf ("SHELL STATION" -> "Shell"). None, wenn
    keine bekannte Marke draufsteht - dann bleibt es beim Betreibernamen."""
    kopf = "\n".join(zeilen[:8])
    for marke in _MARKEN:
        if re.search(r"\b" + re.escape(marke) + r"\b", kopf, re.IGNORECASE):
            return marke
    return None


def _ist_strasse(zeile):
    """Straßenzeilen tragen eine Hausnummer oder ein Straßenwort - der
    Betreibername ("AUTO B. FRISCHMANN GMBH") tut beides nicht."""
    if not zeile:
        return False
    return bool(re.search(r"\d", zeile) or re.search(_STRASSE_WOERTER, zeile, re.IGNORECASE))


def _tankstelle(marke, strasse, ort, ersatzname=None):
    """Anzeigename der Tankstelle im Format "Marke, Straße, Ort" (§ Anzeige im
    Frontend, Feld station_name). Fehlende Teile fallen weg statt als leere
    Kommastelle stehen zu bleiben - nicht jeder Beleg nennt eine Straße. Ohne
    erkennbare Marke tritt der Betreibername an ihre Stelle."""
    teile = [t for t in (marke or ersatzname, strasse, ort) if t]
    return ", ".join(teile) if teile else None


def _parsen(text):
    def suchen(muster, *, pflicht=True, flags=re.MULTILINE):
        treffer = re.search(muster, text, flags)
        if treffer is None and pflicht:
            raise ValueError(f"Muster nicht gefunden: {muster!r}")
        return treffer

    # ---- Station ---------------------------------------------------------
    # Nicht auf den Wortlaut der Markenzeile selbst verankert ("SHELL
    # STATION" vs. "Shell-STATION" - beide an echten Belegen gesehen,
    # offenbar je nach Pächter/Kassensystem unterschiedlich geschrieben).
    # Stattdessen einfach die drei Zeilen direkt vor "Obj.-Nr." nehmen - die
    # Reihenfolge Betreiber/Straße/Ort direkt davor ist auf beiden echten
    # Belegen gleich.
    station = suchen(
        r"(?P<name>.+?)\s*\n\s*(?P<strasse>.+?)\s*\n\s*(?P<ort>.+?)\s*\n\s*Obj\.-Nr\."
    )
    station_id = suchen(r"Obj\.-Nr\.:\s*(\d+)").group(1)

    # ---- Beleg-Kopf (Beleg-Nr., Datum/Zeit auf dem Kassenbon) ----------
    kopf = suchen(
        r"Beleg-Nr\.\s*(?P<receipt_no>[\d/]+)\s+(?P<tag>\d{2})\.(?P<monat>\d{2})\.(?P<jahr>\d{2})\s+(?P<stunde>\d{2}):(?P<minute>\d{2})"
    )
    receipt_no = kopf.group("receipt_no")
    ts = _iso(kopf.group("tag"), kopf.group("monat"), kopf.group("jahr"),
              kopf.group("stunde"), kopf.group("minute"))

    # ---- Kraftstoff-Position --------------------------------------------
    position = suchen(
        r"\*(?P<article_no>\d+)\s+(?P<produkt>.+?)\s+" + _ZAHL + r"\s*EUR\s*#A\*"
    )
    article_no = position.group("article_no")
    product_name = position.group("produkt").strip()

    zapfsaeule = suchen(
        r"\*Zp\s*\d+\s+(?P<liter>" + _ZAHL + r")\s*l\s+(?P<preis>" + _ZAHL + r")\s*EUR/l"
    )
    liters = _de_zahl(zapfsaeule.group("liter"))
    list_price_per_l = _de_zahl(zapfsaeule.group("preis"))

    # ---- SmartDeal-Rabatt (optional - nicht jeder Tankvorgang hat einen) --
    rabatt_treffer = suchen(r"-(" + _ZAHL + r")\s*EUR\s*#A\s*$", pflicht=False)
    discount = _de_zahl(rabatt_treffer.group(1)) if rabatt_treffer else None
    rabatt_l_treffer = suchen(r"(" + _ZAHL + r")\s*EUR/Liter", pflicht=False)
    discount_per_l = _de_zahl(rabatt_l_treffer.group(1)) if rabatt_l_treffer else None

    # ---- Beträge ----------------------------------------------------------
    fuel_total_eur = _de_zahl(suchen(r"Gesamtbetrag\s+(" + _ZAHL + r")\s*EUR").group(1))
    steuerzeile = suchen(
        r"A:" + _ZAHL + r"%\s+(?P<netto>" + _ZAHL + r")\s+(?P<mwst>" + _ZAHL + r")\s+(?P<brutto>" + _ZAHL + r")"
    )
    net_eur = _de_zahl(steuerzeile.group("netto"))
    vat_eur = _de_zahl(steuerzeile.group("mwst"))
    receipt_total_eur = _de_zahl(steuerzeile.group("brutto"))

    # ---- Zahlungszeitstempel (Mobile Payment) - optional, falls anderes
    # Zahlungsverfahren auf dem Beleg steht ---------------------------------
    zahlung = suchen(
        r"Mobile Payment\s*\n+\s*(?P<tag>\d{2})\.(?P<monat>\d{2})\.(?P<jahr>\d{4})\s+(?P<stunde>\d{2}):(?P<minute>\d{2}):(?P<sekunde>\d{2})",
        pflicht=False,
    )
    ts_payment = (
        _iso(zahlung.group("tag"), zahlung.group("monat"), zahlung.group("jahr"),
             zahlung.group("stunde"), zahlung.group("minute"), zahlung.group("sekunde"))
        if zahlung else None
    )

    # ---- TSE-Signatur (Seite 2) - optional -------------------------------
    tse_nr = suchen(r"TSE-Beleg-Nr:\s*(\d+)", pflicht=False)
    tse_beleg_nr = tse_nr.group(1) if tse_nr else None
    tse_zeit = suchen(
        r"Start/Ende:\s*(?P<tag>\d{2})\.(?P<monat>\d{2})\.(?P<jahr>\d{4})\s+(?P<stunde>\d{2}):(?P<minute>\d{2}):(?P<sekunde>\d{2})",
        pflicht=False,
    )
    ts_tse = (
        _iso(tse_zeit.group("tag"), tse_zeit.group("monat"), tse_zeit.group("jahr"),
             tse_zeit.group("stunde"), tse_zeit.group("minute"), tse_zeit.group("sekunde"))
        if tse_zeit else None
    )

    # receipt_key: minutengenauer Deduplizierungs-Schlüssel (§6.4/§7.7 Regel 3)
    receipt_key = f"{station_id}_{receipt_no.replace('/', '-')}_{ts[:16]}"

    return {
        "receipt_key": receipt_key,
        "receipt_no": receipt_no,
        "tse_beleg_nr": tse_beleg_nr,
        "ts": ts,
        "ts_payment": ts_payment,
        "ts_tse": ts_tse,
        "station_id": station_id,
        # Anzeigename "Marke, Straße, Ort" (siehe _tankstelle). Der Ort steht
        # auf dem Bon als "85057 Ingolstadt" - die PLZ gehört nur in die
        # ausführliche station_address darunter, nicht in den Anzeigenamen.
        "station_name": _tankstelle(
            _marke(text.splitlines()),
            station.group("strasse").strip(),
            re.sub(r"^\d{4,5}\s+", "", station.group("ort").strip()),
            ersatzname=station.group("name").strip(),
        ),
        "station_address": f"{station.group('strasse').strip()}, {station.group('ort').strip()}",
        "article_no": article_no,
        "product_name": product_name,
        "fuel_type": product_name,
        "liters": liters,
        "list_price_per_l": list_price_per_l,
        "discount": discount,
        "discount_per_l": discount_per_l,
        "fuel_total_eur": fuel_total_eur,
        "receipt_total_eur": receipt_total_eur,
        "net_eur": net_eur,
        "vat_eur": vat_eur,
    }


def _parsen_generisch(text):
    """Stationsunabhängiger Fallback für Belege, die keinem der oben fest an
    Shells Beleg-Struktur verankerten Muster entsprechen (_parsen() wirft in
    dem Fall ValueError - siehe main()). Arbeitet nach demselben Verfahren,
    das an einem echten Beleg einer bislang unbekannten Tankstelle von Hand
    nachvollzogen wurde: Adresse suchen, Gesamtbetrag suchen ("Gesamt.../
    Absolut..."), Literangabe suchen ("Menge"/"Amount" oder eine an "l"/
    "Liter" hängende Zahl), daraus den tatsächlich bezahlten Preis/Liter
    errechnen (Gesamtbetrag / Liter). Steht daneben auch ein aufgedruckter
    Preis/Liter auf dem Beleg und weicht er vom errechneten ab, wurde ein
    Rabatt gewährt - auf dem Beleg meist als eigener Betrag mit einem Minus
    davor erkennbar, danach wird zuerst gesucht, sonst aus der Preisdifferenz
    hergeleitet. Ob ein so gefundener Rabatt als "SmartDeal" behandelt wird,
    entscheidet einzig station_name (Shell-Logo/-Label im Frontend, siehe
    frontend_veroeffentlichung.py) - kein Sonderfall hier nötig, die Frontend-
    Anzeige hängt nur an einem gesetzten discount, nicht an einem eigenen
    Kennzeichen.

    Liefert deutlich weniger Felder als _parsen() (kein Bon-Kleingedrucktes
    wie TSE-Nummer/Zahlungszeitstempel) - unproblematisch, denn
    belegverarbeitung.py liest nur receipt_key als echtes Pflichtfeld,
    alles andere per .get()."""

    def suchen(muster, flags=re.IGNORECASE | re.MULTILINE):
        return re.search(muster, text, flags)

    zeilen = [z.rstrip() for z in text.splitlines()]

    # ---- Adresse -----------------------------------------------------------
    # Die PLZ-Ort-Angabe steht nicht zuverlässig auf einer eigenen Zeile (ein
    # österreichischer Beleg führt sie als "SHELL TANKSTELLE, 6450 SÖLDEN"
    # zusammen mit der Filialbezeichnung) und PLZ sind vier- *oder*
    # fünfstellig (AT/DE). Deshalb nicht mehr drei feste Zeilen hintereinander
    # verlangen, sondern im Belegkopf die erste Zeile mit PLZ+Ort suchen; der
    # Name ist die erste Textzeile darüber.
    plz_treffer = None
    plz_zeile = None
    for i, zeile in enumerate(zeilen[:12]):
        treffer = re.search(r"\b\d{4,5}\s+([A-Za-zÄÖÜäöüß][^\n]*)$", zeile)
        if treffer:
            plz_treffer, plz_zeile = treffer, i
            break

    ort = None
    strasse = None
    betreiber = None
    station_address = None
    if plz_treffer is not None:
        ort = plz_treffer.group(1).strip(" ,.-")
        station_address = zeilen[plz_zeile].strip()
        # Die Straße steht entweder vor der PLZ auf derselben Zeile
        # ("Musterstr. 5, 80331 München") oder auf der Zeile darüber.
        vor_plz = zeilen[plz_zeile][: plz_treffer.start()].strip(" ,.-")
        if _ist_strasse(vor_plz):
            strasse = vor_plz
        for zeile in reversed(zeilen[:plz_zeile]):
            if not zeile.strip():
                continue
            if strasse is None and _ist_strasse(zeile.strip()):
                strasse = zeile.strip()
                continue
            betreiber = zeile.strip()
            break
    if betreiber is None:
        for zeile in zeilen[:5]:
            if zeile.strip():
                betreiber = zeile.strip()
                break
    if station_address and strasse and strasse not in station_address:
        station_address = f"{strasse}, {station_address}"
    station_name = _tankstelle(_marke(zeilen), strasse, ort, ersatzname=betreiber)

    # ---- Gesamtbetrag ------------------------------------------------------
    # Zeilenweise statt über den ganzen Text, aus zwei Gründen, die beide an
    # einem echten Beleg aufgefallen sind: Bondrucker sperren Überschriften
    # gern buchstabenweise ("G E S A M T  BETRAG EUR: 92,60"), und ein frei
    # laufendes Muster fand vorher die Spaltenüberschrift "SUMME-EUR" und
    # las die Artikelnummer der Folgezeile als Betrag. Deshalb: Schlüsselwort
    # auf der leerzeichenfreien Zeile prüfen, Betrag nur als echte Geldangabe
    # (mit Nachkommastellen) akzeptieren und Steuerzeilen ausschließen.
    kandidaten = []
    for zeile in zeilen:
        kompakt = re.sub(r"\s+", "", zeile).upper()
        if not any(wort in kompakt for wort in _GESAMT_WOERTER):
            continue
        if any(wort in kompakt for wort in _STEUER_WOERTER):
            continue
        betraege = re.findall(_GELD, zeile)
        if betraege:
            kandidaten.append(_de_zahl(betraege[-1]))
    if not kandidaten:
        raise ValueError("Kein Gesamtbetrag gefunden (weder 'Gesamt' noch 'Absolut')")
    # Mehrere Treffer (z. B. Summenzeile und Kartenzahlungszeile): der
    # Rechnungsbetrag ist der größte - Teilbeträge sind nie größer.
    fuel_total_eur = max(kandidaten)

    # ---- Menge in Litern ----------------------------------------------------
    menge = suchen(r"(?:Menge|Amount)\D{0,15}?(" + _ZAHL + r")\s*(?:l\b|L\b|Liter|Ltr\.?)")
    if not menge:
        menge = suchen(r"(" + _ZAHL + r")\s*(?:Liter|Ltr\.?|l)\b")
    if not menge:
        raise ValueError("Keine Literangabe gefunden (weder 'Menge'/'Amount' noch 'X l')")
    liters = _de_zahl(menge.group(1))
    if not liters:
        raise ValueError("Literangabe ist 0 oder unlesbar")

    berechneter_preis_l = round(fuel_total_eur / liters, 3)

    # ---- Aufgedruckter Preis/Liter - Abweichung verrät einen Rabatt -------
    # "Preis/L" (ohne ausgeschriebenes "Liter") kommt auf echten Belegen vor,
    # deshalb ist alles nach dem L optional.
    aufgedruckt = suchen(
        r"(?:Preis\s*/?\s*(?:je\s*)?L(?:iter|tr\.?)?|Price\s*/?\s*L(?:iter)?|€\s*/\s*l|EUR\s*/\s*l)\D{0,10}?(" + _ZAHL + r")"
    )
    list_price_per_l = _de_zahl(aufgedruckt.group(1)) if aufgedruckt else berechneter_preis_l

    discount = None
    discount_per_l = None
    if aufgedruckt and abs(list_price_per_l - berechneter_preis_l) > 0.005:
        minus = suchen(r"-\s*(" + _ZAHL + r")\s*(?:€|EUR)")
        if minus:
            discount = _de_zahl(minus.group(1))
            discount_per_l = round(discount / liters, 3)
        else:
            discount_per_l = round(list_price_per_l - berechneter_preis_l, 3)
            discount = round(discount_per_l * liters, 2)

    # ---- Zeitstempel: irgendein Datum+Uhrzeit auf dem Beleg ----------------
    zeit = suchen(r"(\d{2})\.(\d{2})\.(\d{2,4})\D{0,10}?(\d{2}):(\d{2})")
    ts = _iso(zeit.group(1), zeit.group(2), zeit.group(3), zeit.group(4), zeit.group(5)) if zeit else None

    # ---- Belegnummer, sonst deterministischer Ersatzschlüssel --------------
    # Wichtig: kein eingebautes hash() (pro Prozess zufällig gesalzen, siehe
    # PYTHONHASHSEED) - der Parser läuft für jeden Beleg als eigener Prozess
    # (Kopfkommentar oben), ein instabiler Schlüssel würde den §7.7-Regel-3-
    # Dedup-Vergleich in belegverarbeitung.py unbrauchbar machen.
    beleg_nr = suchen(
        r"(?:Beleg-?Nr\.?|Bon-?Nr\.?|Rechnungs-?Nr\.?|Receipt\s*No\.?|Trans(?:aktions)?-?Nr\.?)\D{0,5}?([\dA-Za-z/\-]+)"
    )
    receipt_no = beleg_nr.group(1) if beleg_nr else None
    schluessel_basis = "|".join(
        str(teil) for teil in (receipt_no, ts, station_address, fuel_total_eur, liters) if teil
    ) or text[:80]
    receipt_key = "generisch_" + hashlib.sha1(schluessel_basis.encode("utf-8")).hexdigest()[:16]

    return {
        "receipt_key": receipt_key,
        "receipt_no": receipt_no,
        "tse_beleg_nr": None,
        "ts": ts,
        "ts_payment": None,
        "ts_tse": None,
        "station_id": None,
        "station_name": station_name,
        "station_address": station_address,
        "article_no": None,
        "product_name": None,
        "fuel_type": None,
        "liters": liters,
        "list_price_per_l": list_price_per_l,
        "discount": discount,
        "discount_per_l": discount_per_l,
        "fuel_total_eur": fuel_total_eur,
        "receipt_total_eur": fuel_total_eur,
        "net_eur": None,
        "vat_eur": None,
    }


def beleg_lesen(pdf_pfad):
    """Liest einen Tankbeleg und gibt die Felder als dict zurück.

    Der eine Einstiegspunkt für Aufrufer im selben Prozess. Die Integration
    (belege.py) ruft genau diese Funktion im Executor auf - früher lief der
    Parser als eigener Unterprozess über main() unten, weil pyscript keine
    fremden Pakete laden konnte und pypdf deshalb von Hand ins Container-
    Python installiert werden musste. Als echte Integration steht pypdf über
    manifest.json bereit; der Unterprozess und der Handinstallations-Schritt
    entfallen damit ersatzlos.

    Wirft eine Exception, wenn weder das Shell-Format noch der
    stationsunabhängige Rückfall greifen - der Aufrufer entscheidet, was er
    dem Nutzer davon zeigt."""
    text = _text_aus_pdf(pdf_pfad)
    try:
        return _parsen(text)
    except Exception:
        # Kein Shell-Beleg im bekannten Format - Versuch mit dem
        # stationsunabhängigen Fallback (_parsen_generisch() oben), bevor
        # endgültig aufgegeben wird.
        return _parsen_generisch(text)


def main():
    """Kommandozeilen-Aufruf, weiterhin nutzbar zum Nachschauen von Hand:
    `python3 shell_beleg_parser.py <pfad-zur-pdf>`."""
    if len(sys.argv) != 2:
        print("Aufruf: python3 shell_beleg_parser.py <pfad-zur-pdf>", file=sys.stderr)
        sys.exit(1)
    try:
        ergebnis = beleg_lesen(sys.argv[1])
    except Exception as exc:
        print(f"Beleg konnte nicht gelesen werden ({type(exc).__name__}): {exc}", file=sys.stderr)
        sys.exit(1)
    print(json.dumps(ergebnis, ensure_ascii=False))


if __name__ == "__main__":
    main()
