#!/usr/bin/env python3 """Phase 2 als Ganzes: alle Module in fester Reihenfolge. python3 phase2.py --backfill python3 phase2.py --slot 2026-09-07T08:00 python3 phase2.py --neueste # der juengste Slot in raw_items python3 phase2.py --aufholen # jeder Slot, dem eine Kodierung fehlt Das ist der Einstiegspunkt im Betrieb; die Module lassen sich weiter einzeln aufrufen. Die Reihenfolge ist nicht beliebig: die regelbasierten Module sind in Millisekunden fertig, das Embedding braucht Modell und Rechenzeit. Bricht es ab, stehen die uebrigen Ergebnisse trotzdem. Jedes Modul haelt seine eigene coder_version. Ein Fehlschlag in einem Modul laesst die anderen unberuehrt - dafuer ist die Schichtung in codings da. """ import argparse import sys MODULE = ["dubletten", "revisionen", "embeddings"] def slots_ohne_kodierung(conn): """Slots, in denen Items liegen, die noch keine Dublettenzeile haben. Absichtlich am ersten Modul festgemacht und nicht an allen: laeuft das Embedding einmal nicht durch, soll der Aufholvorgang nicht dauerhaft dieselben Slots wiederholen. Fehlende Vektoren holt ein Backfill. """ return [r[0] for r in conn.execute( """select distinct r.slot from raw_items r where not exists (select 1 from codings c where c.raw_item_id = r.id and c.ebene = 'dublette') order by 1""")] def fuehre_aus(name, argumente): modul = __import__(name) print(f"\n--- {name} {' '.join(argumente)}") try: rueck = modul.main(argumente) except Exception as fehler: # noqa: BLE001 # Ein Modul darf die uebrigen nicht mitreissen. Der Fehler steht im # Protokoll und im Rueckgabewert, nicht in einem abgebrochenen Lauf. print(f" FEHLER in {name}: {fehler.__class__.__name__}: {fehler}", file=sys.stderr) return 1 return rueck or 0 def main(argv=None): p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) gruppe = p.add_mutually_exclusive_group(required=True) gruppe.add_argument("--backfill", action="store_true", help="Gesamtbestand") gruppe.add_argument("--slot", help="ein Slot, z.B. 2026-09-07T08:00") gruppe.add_argument("--neueste", action="store_true", help="der juengste Slot in raw_items") gruppe.add_argument("--aufholen", action="store_true", help="jeder Slot, dem eine Kodierung fehlt") p.add_argument("--ohne", action="append", default=[], metavar="MODUL", help="Modul auslassen, mehrfach angebbar") p.add_argument("--trocken", action="store_true", help="rechnen, nichts schreiben") a = p.parse_args(argv) module = [m for m in MODULE if m not in a.ohne] zusatz = ["--trocken"] if a.trocken else [] if a.backfill: laeufe = [["--backfill"]] elif a.slot: laeufe = [["--slot", a.slot]] else: from db import verbindung with verbindung() as conn: if a.neueste: zeile = conn.execute("select max(slot) from raw_items").fetchone() slots = [zeile[0]] if zeile and zeile[0] else [] else: slots = slots_ohne_kodierung(conn) if not slots: print("kein offener Slot") return 0 print(f"{len(slots)} Slot(s): {slots[0]} bis {slots[-1]}") laeufe = [["--slot", s.isoformat()] for s in slots] schlecht = 0 for lauf in laeufe: for name in module: schlecht += fuehre_aus(name, lauf + zusatz) if schlecht: print(f"\n{schlecht} Modullauf/-laeufe mit Fehler", file=sys.stderr) return 1 if schlecht else 0 if __name__ == "__main__": sys.exit(main())