gwoe-antragspruefer/docs/adr/0002-adapter-architecture.md
Dotty Dotter 45379a2639 #62 Phase 1+3: ADRs + Doku-Struktur in webapp/docs/
Architektur-Entscheidung aus Issue #62: Diátaxis-Framework für Doku-
Pflege ohne Drift. Pflege im Repo, ADRs immutable, Stale-Snapshots
explizit als Archiv markiert.

Phase 1 — Architecture Decision Records:

- docs/README.md — Diátaxis-Index, Erklärung was wo dokumentiert wird
- docs/adr/README.md — ADR-Workflow + Index
- docs/adr/template.md — Vorlage für neue ADRs
- docs/adr/0001-llm-citation-binding.md — Issue #60 Doppel-Fix-Story
  (A=ENUM-Anker, B=server-seitige Rekonstruktion, warum Option C verworfen)
- docs/adr/0002-adapter-architecture.md — ParlamentAdapter-Basisklasse
  + Registry, Klassen vs. Strategy vs. Modul-pro-Adapter
- docs/adr/0003-citation-property-tests.md — Sub-D Strategie, warum
  Property-Test gegen echte PDFs statt Schema-Tests oder Online-Verify
- docs/adr/0004-deployment-workflow.md — Docker-Compose + Volumes
  Standard-Workflow + SN-XML-Sonderpfad + Container-UTC-Gotcha

Phase 3 — Stale Doku archiviert:

- DOKUMENTATION.md (24.März, Skript-Architektur vor Webapp-Migrate)
  → docs/archive/DOKUMENTATION-2026-03-24.md
- STATUS-2026-03-28.md (Tagesstand-Snapshot)
  → docs/archive/STATUS-2026-03-28.md
- README.md (28.März, listet nur NRW-Adapter, vor 16 weiteren BLs)
  → docs/archive/README-2026-03-28.md
- docs/archive/README.md erklärt warum die Files da sind und warum
  niemand sie überschreiben oder ersetzen sollte

Plus neue Top-Level-README.md im Project-Root (außerhalb git, da
project-root kein Repo ist) als Folder-Index für den User.

CLAUDE.md ergänzt um Doku-Sektion mit Verweis auf docs/adr/.

Phase 2 (mkdocs Setup) folgt separat — braucht eine Docker-Image-
Erweiterung, die ich nicht autark einrollen will ohne Decision.

Tests: 194/194 grün (keine Code-Änderung).

Refs: #62
2026-04-10 01:38:03 +02:00

5.0 KiB

0002 — Adapter-Pattern mit ParlamentAdapter-Basisklasse + Registry

Status accepted
Datum 2026-04-10
Refs Issues #2, #3, #4, #19, #21, #23, #24, #26, #56, parlamente.py

Kontext

Der GWÖ-Antragsprüfer soll Anträge aus 16 Bundesländern + dem Bundestag analysieren können. Jedes Parlament hat ein eigenes Doku-System mit unterschiedlichen Endpoints, Schemas, Auth-Anforderungen und Quirks:

Familie Mitglieder Charakter
portala/eUI LSA, BE, BB, RP JSON-Tree-Search mit parsed-Strings, HTML-Hits in efxRecordRepeater
StarWeb (eUI v2) HE browse.tt.json 2-step + Perl-Dump-Comments
PARiS (Java-Servlet) HB älterer StarWeb-Vorgänger, eigenes Hit-Format
ParlDok 8.x MV, HH, TH SPA mit Fulltext/Search + Resultpage Pagination
StarFinder-CGI SH lissh.lvn.parlanet.de/cgi-bin/starfinder/0
OPAL native NRW älteres Eigensystem
PARLIS (eUI-Variante) BW polling, JSON-in-HTML-Comments
Eigensysteme BY (TYPO3-Solr), SL (Umbraco/.NET), SN (ASP.NET-Webforms), BUND (DIP-API) jeweils komplett unique

Optionen

Option A — Eine Mega-Klasse mit Strategy-Pattern

Eine ParlamentAdapter-Klasse mit Strategy-Object pro Doku-System, gewählt über Konfiguration.

Vorteile: zentral, ein Eintrittspunkt für gemeinsame Logik wie Fraktion-Normalisierung.

Nachteile: kompliziert sich schnell, wenn jede Strategy ihre eigenen Quirks hat (z.B. BB hat Datum-vor-Drucksachennummer im h6, BE hat 730d-Window-Hack, HE hat Perl-Hex-Decoder). Die Trennung wäre "Strategy mit Subclasses" — und damit sind wir bei B.

Option B — Abstract Base + Eine konkrete Klasse pro Familie

ParlamentAdapter(ABC) mit search/get_document/download_text-Methoden. Pro Doku-System eine konkrete Klasse: PortalaAdapter, ParLDokAdapter, StarFinderCGIAdapter, StarWebHEAdapter, PARiSHBAdapter, PARLISAdapter, BayernAdapter, SaarlandAdapter, BundestagAdapter, SNEdasXmlAdapter, NRWAdapter.

Wo möglich, wird die Klasse via Konstruktor-Parameter parametrisiert (PortalaAdapter nimmt 6 BLs auf, ParLDokAdapter 3) statt jede Wiederverwendung als Subklasse.

Registriert in einem ADAPTERS: dict[str, ParlamentAdapter] am Datei-Ende, indexiert per BL-Code ("NRW", "BUND", …).

Vorteile:

  • Klare Verantwortlichkeit: ein Test-Lauf gegen einen LT trifft genau eine Klasse.
  • Reverse-Engineering-Findings können als Class-Docstring dokumentiert werden, wo sie hingehören.
  • Parametrisierung erlaubt Wiederverwendung ohne Vererbungs-Hierarchie.
  • get_adapter(bundesland) ist O(1).

Nachteile:

  • Cross-Cutting-Concerns (Fraktion-Normalisierung) müssen separat gelöst werden — siehe ADR-Folge zur parteien-Zentralisierung in #55.

Option C — Separates Modul pro Adapter

Ein Python-File pro Adapter (z.B. app/adapters/lsa.py).

Vorteile: maximale Datei-Granularität.

Nachteile: Import-Overhead, der Registry-Eintrag muss in einer dritten Datei liegen, kein nennenswerter Vorteil gegenüber B solange die Adapter-Klassen unter ~500 Zeilen bleiben (PortalaAdapter ist mit ~520 Zeilen der größte).

Entscheidung

Option B. Eine parlamente.py mit einer Klasse pro Doku-System, parametrisierbar wo sinnvoll, registriert in ADAPTERS. Cross-Cutting-Logik wie Fraktion-Normalisierung wandert in separate Module (parteien.py, bundeslaender.py) und wird per Import oder Funktions-Argument injiziert.

Konsequenzen

Positiv

  • 17 Adapter sauber separierbar in ~3000 Zeilen parlamente.py. Jeder Adapter kommt mit seinem Reverse-Engineering-Kommentar als Docstring, was bei Re-Reads nach Wochen die Wiedereinarbeit auf Minuten bringt.
  • Tests parametrisieren über ADAPTERS-Registry: ein einzelner Test-Helper iteriert alle aktiven Adapter und prüft die Akzeptanz- Kriterien (search liefert ≥3 Hits, get_document funktioniert, download_text extrahiert Text).
  • Neue Bundesländer kommen als 100-300-Zeilen-Klasse + 1 Eintrag in ADAPTERS rein, ohne Berührung anderer Adapter.

Negativ

  • parlamente.py ist groß (~3000 Zeilen, 17 Klassen). Code-Navigation via Grep statt Auto-Imports.
  • Reverse-Engineering-Findings sind in Kommentaren, nicht in einer reference-Sektion. Wenn sich das System ändert, kann der Kommentar veralten — Mitigation: jede Reverse-Engineering-Session legt einen HAR-Trace in TEMP/ und referenziert ihn im Class-Docstring.
  • Adapter-Bugs leak nicht in andere Adapter, aber gemeinsame Fraktion-Disambiguation muss manuell konsistent gehalten werden (siehe #55-Refactor und seine Regression in Issue #60-Side-Befund: ein Refactor-Rest in embeddings.py:528 wurde durch ein anderes Modul verbreitet).

Folgen für andere ADRs

  • ADR 0001 (LLM-Citation-Binding) ist davon unabhängig — der Postprocess läuft adapter-agnostisch nach jedem analyze_antrag.
  • Eine künftige Module-Splitter-Entscheidung (adapters/ als Folder) würde den ADR superseden, wenn parlamente.py über ~5000 Zeilen wächst.