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
120 lines
5.0 KiB
Markdown
120 lines
5.0 KiB
Markdown
# 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.
|