gwoe-antragspruefer/docs/reference/adapter-capabilities.md
Dotty Dotter f0f1c39911 Docs: Feld-Mapping-Tabelle pro Adapter + ADR 0005 + Auth-Tests
Adapter-Capabilities-Matrix (#93) erweitert um detailliertes Feld-
Mapping: Pro Adapter welches API-/HTML-/JSON-Feld zu welchem
Drucksache-Feld wird (title, datum, fraktionen, drucksache, link, typ)
mit konkreten Beispielwerten. 12 Adapter-Sektionen.

ADR 0005: Keycloak SSO mit Dev-Bypass — dokumentiert die Entscheidung
für Read/Write-Trennung (GET offen, POST mit JWT) und den Dev-Modus
(Auth deaktiviert wenn KEYCLOAK_URL nicht gesetzt).

Auth-Tests: 7 neue Tests für Token-Extraction, Auth-Enabled-Detection,
_pick_best_title (letztere skipped wenn slowapi nicht installiert).

201 passed, 5 skipped.
2026-04-10 16:29:28 +02:00

222 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Adapter-Capabilities: Vergleichsmatrix
Stand: 2026-04-10. Automatisch aus `parlamente.py` + `bundeslaender.py` extrahiert.
## Übersicht
16 Bundesländer + Bundestag, alle aktiv.
| BL | Adapter | WP | Suche | Fuzzy | Typ-Filter | API |
|---|---|---|---|---|---|---|
| **BUND** | BundestagAdapter | 21 | Server (DIP REST) + Title-Filter | Nein | Server (`f.drucksachetyp`) | REST JSON |
| **NRW** | NRWAdapter | 18 | Server (HTML-Form) + AND-Filter | Nein | Server (`dokTyp`) | HTML POST |
| **LSA** | PortalaAdapter | 8 | Server (eUI) + Title-Filter | Nein | Server (`ETYPF=Antrag`) | eUI 2-Step |
| **BE** | PortalaAdapter | 19 | Server (eUI, 730d-Fenster) + Title-Filter | Nein | Client-only | eUI 2-Step |
| **BB** | PortalaAdapter | 8 | Server (eUI) + Title-Filter | Nein | Server (`ETYPF=Antrag`) | eUI 2-Step |
| **RP** | PortalaAdapter | 18 | Server (eUI) + Title-Filter | Nein | Server (`ETYPF=Antrag`) | eUI 2-Step |
| **MV** | ParLDokAdapter | 8 | Server (ParlDok JSON) + Typ-Filter | Nein | Client (`type=="Antrag"`) | REST JSON |
| **HH** | ParLDokAdapter | 23 | Server (ParlDok JSON) + Typ-Filter | Nein | Client (`type=="Antrag"`) | REST JSON |
| **TH** | ParLDokAdapter | 8 | Server (ParlDok JSON) + Typ-Filter | Nein | Client (Substring `"Antrag"` in type) | REST JSON |
| **SH** | StarFinderCGI | 20 | Server (CGI, `dtyp=antrag`) + Title-Filter | Nein | Server (`dtyp`) | Legacy CGI |
| **HE** | StarWebHEAdapter | 21 | Server (eUI, VTDRS) + Title+Typ-Filter | Nein | Client (`"antrag"` in typ) | eUI 2-Step |
| **HB** | PARiSHBAdapter | 21 | Server (PARiS-Servlet) + Typ-Filter | Nein (Thesaurus only) | Client (`"antrag"` in typ) | HTML POST |
| **BW** | PARLISAdapter | 17 | Server (eUI, async Polling) + Title-Filter | Nein | Server (`lines.l4=Antrag`) | eUI async |
| **BY** | BayernAdapter | 19 | **Server-Volltext (Solr)** + Typ-Filter | **Ja (Solr)** | Client (`typ=="antrag"`) | HTML Scraping |
| **SL** | SaarlandAdapter | 17 | **Server-Volltext (Umbraco)** + Typ-Filter | Umbraco-Relevanz | Client (`DocumentType`) | REST JSON |
| **SN** | SNEdasXmlAdapter | 8 | Statisch (manueller XML-Export) + Title-Filter | Nein | N/A | XML Import |
## Such-Architekturen
### Volltext-Suche server-seitig
Nur **BY** (TYPO3-Solr) und **SL** (Umbraco) bieten echte Volltextsuche
über den gesamten Dokumentkorpus. Alle anderen filtern entweder nur den
Titel client-seitig oder nutzen eingeschränkte Server-Filter (WP + Datum).
### Client-seitige Title-Filterung
Die Mehrheit (portala/eUI, ParlDok, StarFinder, BUND) holt einen breiten
Satz von Treffern nach WP/Datum/Typ und filtert dann im Python-Code per
`all(t in title.lower() for t in query_terms)`. Das bedeutet:
- **Nur Titel-Matches** — Inhalte der Drucksachen werden nicht durchsucht
- **AND-Logik** — alle Suchbegriffe müssen im Titel vorkommen
- **Keine Fuzzy-Toleranz** — Tippfehler oder Synonym-Drift werden nicht abgefangen
- **Performance ok** — max. 1000-1500 Treffer pro Request (via chunksize)
### Sonderfälle
| BL | Besonderheit |
|---|---|
| **SN** | Kein Scraping möglich (`robots.txt`); manueller wöchentlicher XML-Export aus EDAS |
| **BE** | `document_type=None` weil Berlin's ETYPF andere Values nutzt; 730-Tage-Fenster als Workaround |
| **TH** | Anträge heißen "Antrag gemäß § 79 GO" → Substring-Match statt Exact-Match; `kinds=["Drucksache", "Vorlage"]` |
| **BW** | Async 2-Step mit Search-ID-Polling (2-15 Versuche × 2s Pause), JSON-in-HTML-Comments |
| **HE** | Perl-Data::Dumper in HTML-Comments, Hex-Escape-Decoding (`\x{e9}` → `é`) |
| **NI** | ❌ **Nicht implementiert** — NILAS-StarWeb braucht Session-Cookie, HAR-Capture nötig |
## Feld-Mapping: Woher kommen die Daten?
Pro Adapter: welches API-Feld wird zu welchem Drucksache-Feld, mit Beispiel.
### NRW (NRWAdapter — OPAL HTML)
| Feld | Quelle | Beispiel |
|---|---|---|
| title | HTML `.e-document-result-item__title` `<a>` Text | "Kostenloses Parken für E-Fahrzeuge aufheben" |
| datum | `<time>` Element, DD.MM.YYYY → ISO | "07.04.2026" → "2026-04-07" |
| fraktionen | `<p>` mit "Urheber:" → `extract_fraktionen()` | "Urheber: SPD, GRÜNE" → `["SPD", "GRÜNE"]` |
| drucksache | Regex aus href `MMD18-12345.pdf` → "18/12345" | "18/18085" |
| link | `https://www.landtag.nrw.de{href}` | `https://www.landtag.nrw.de/.../MMD18-18085.pdf` |
| typ | HTML `.e-document-result-item__category` | "Antrag", "Kleine Anfrage" |
### LSA / BB / RP (PortalaAdapter — eUI Perl-Dump)
| Feld | Quelle (Perl-Dump-Feld) | Beispiel |
|---|---|---|
| title | `WEV06[0].main` | "Energiewende und Klimaschutz" |
| datum | `WEV32[0].main` → Regex, DD.MM.YYYY → ISO | "01.10.2024" → "2024-10-01" |
| fraktionen | `WEV32[0].main` Urheber-Text → `extract_fraktionen()` | "CDU, SPD" → `["CDU", "SPD"]` |
| drucksache | `WEV32[0].main` → Regex | "8/6645" |
| link | `{base_url}{pdf_url_prefix}{WEV32[0].5}` | `https://padoka.landtag.sachsen-anhalt.de/files/drs/8/6645.pdf` |
| typ | Fest: "Antrag" (server-side gefiltert via `ETYPF`) | "Antrag" |
### BE (PortalaAdapter — eUI HTML-Cards)
| Feld | Quelle (HTML-Element) | Beispiel |
|---|---|---|
| title | `<h3 class="h5">` Text | "Kinder- und Familienförderung" |
| datum | `<h6>` Metadata-Zeile → Regex DD.MM.YYYY → ISO | "31.03.2026" → "2026-03-31" |
| fraktionen | `<h6>` Doctype-Zeile → `extract_fraktionen()` | "Antrag CDU, SPD" → `["CDU", "SPD"]` |
| drucksache | `<h6>` → Regex | "19/3104" |
| link | `<a href="…pdf">` (http→https, relativ→absolut) | `https://pardok.parlament-berlin.de/...` |
| typ | `<h6>` vor Fraktionsnamen | "Antrag", "Vorlage" |
### MV / HH / TH (ParLDokAdapter — JSON-API)
| Feld | Quelle (JSON-Feld) | Beispiel |
|---|---|---|
| title | `title` | "Weiterbildungsförderung" |
| datum | `date`, DD.MM.YYYY → ISO | "15.03.2026" → "2026-03-15" |
| fraktionen | `authorhtml``extract_fraktionen()` | "Klaus Meyer (CDU)" → `["CDU"]` |
| drucksache | `f"{lp}/{number}"` | lp=8, number=1594 → "8/1594" |
| link | `{base_url}{prefix}{link}` (Fragment `#navpanes=0` gestrippt) | `https://dokumentation.landtag-mv.de/parldok/dokument/8` |
| typ | `type` (TH: Substring-Match auf "Antrag") | "Antrag", "Antrag gemäß § 79 GO" |
### SH (StarFinderCGIAdapter — Legacy CGI)
| Feld | Quelle (HTML-Regex) | Beispiel |
|---|---|---|
| title | `<b>…</b>` in Tabellenzeile | "Energiewende vorantreiben" |
| datum | Nach `</b>`, DD.MM.YYYY → ISO | "07.04.2026" → "2026-04-07" |
| fraktionen | Urheber-Text → `extract_fraktionen()` | "Christian Dirschauer (SSW)" → `["SSW"]` |
| drucksache | `<a>` Text | "20/5136" |
| link | `<a href="…">` direkt | `http://lissh.lvn.parlanet.de/.../20-5136.pdf` |
| typ | Fest: "Antrag" (server-side `dtyp=antrag`) | "Antrag" |
### HE (StarWebHEAdapter — eUI Perl-Dump)
| Feld | Quelle (Perl-Dump-Feld) | Beispiel |
|---|---|---|
| title | `WEV01[0].main` (Hex-Escape-Decoding `\x{e9}``é`) | "Schulinfrastruktur modernisieren" |
| datum | `WEV02[0].main`, DD.MM.YYYY → ISO | "29.02.2026" → "2026-02-29" |
| fraktionen | `WEV12[0].main``extract_fraktionen()` | "Klaus Dieter (GRÜNE)" → `["GRÜNE"]` |
| drucksache | `WEV08[0].main` | "21/8532" |
| link | `WEV07[0].main` (http→https) | `https://starweb.hessen.de/.../21_8532.pdf` |
| typ | `WEV03[0].main` | "Antrag", "Antrag mit Änderung" |
### HB (PARiSHBAdapter — Java-Servlet HTML)
| Feld | Quelle (HTML-Regex) | Beispiel |
|---|---|---|
| title | `<h2><a>…</a></h2>` | "Bremenpass für Kultur" |
| datum | Nach Drucksache, DD.MM.YYYY → ISO | "23.02.2026" → "2026-02-23" |
| fraktionen | Nach Datum → `extract_fraktionen()` | "SPD, BÜNDNIS 90/DIE GRÜNEN, Die Linke" → `["SPD", "GRÜNE", "LINKE"]` |
| drucksache | `Drs <b>21/730</b>` + optionaler Suffix (S/L) | "21/730", "21/730S" |
| link | `<a href="…pdf" target="new">` | `https://www.bremische-buergerschaft.de/.../21/730.pdf` |
| typ | Regex vor Datum | "Antrag", "Änderungsantrag" |
### BW (PARLISAdapter — eUI async Polling, JSON-in-HTML)
| Feld | Quelle (Perl/JSON-Feld) | Beispiel |
|---|---|---|
| title | `WMV33` (Schlagworte, `<i>` gestrippt) / Fallback `EWBV23` | "Energiewirtschaft; Stromversorgung" |
| datum | `EWBV23` → Regex DD.MM.YYYY → ISO | "16.03.2026" → "2026-03-16" |
| fraktionen | `WMV30` (Kurz-Urheber) → `extract_fraktionen()` | "Felix Herkens (GRÜNE) u. a." → `["GRÜNE"]` |
| drucksache | `EWBV22` oder `EWBD01` → Regex | "17/10323" |
| link | `EWBD05[0].main` (direkte PDF-URL) | `https://parlis.landtag-bw.de/.../17_10323.pdf` |
| typ | Fest: `document_typ="Antrag"` (in `lines.l4`) | "Antrag" |
### BY (BayernAdapter — TYPO3-Solr HTML)
| Feld | Quelle (HTML-Regex) | Beispiel |
|---|---|---|
| title | `<h5><strong>…</strong></h5>` | "Kostenloses Parken für E-Fahrzeuge aufheben" |
| datum | `<h4>` "Drucksache Nr. 19/11407 vom 08.04.2026" DD.MM.YYYY → ISO | "08.04.2026" → "2026-04-08" |
| fraktionen | `<p>` "Antrag AfD" → `extract_fraktionen()` | "Antrag CSU, FREIE WÄHLER" → `["CSU", "FW-BAYERN"]` |
| drucksache | `<h4>` Regex | "19/11407" |
| link | `<a href="…pdf">` (absolute URL) | `https://www.bayern.landtag.de/.../0000009107.pdf` |
| typ | Erstes Wort aus `<p>` | "Antrag" |
### SL (SaarlandAdapter — Umbraco JSON)
| Feld | Quelle (JSON-Feld) | Beispiel |
|---|---|---|
| title | `Title` | "Schule als Lern- und Bildungsort weiter stärken" |
| datum | `PublicDate` ISO-Format, erste 10 Zeichen | "2022-05-12T00:00:00" → "2022-05-12" |
| fraktionen | `Publisher` + `DocumentAuthor``extract_fraktionen()` | Publisher "CDU" → `["CDU"]` |
| drucksache | `DocumentNumber` | "17/11" |
| link | `FilePath` mit `/file.ashx``/Downloadfile.ashx` Rewrite | `https://www.landtag-saar.de/Downloadfile.ashx?FileId=14230&FileName=Ag17_0011.pdf` |
| typ | `DocumentType` | "Antrag", "Anfrage", "Gesetzentwurf" |
### SN (SNEdasXmlAdapter — Manueller XML-Export)
| Feld | Quelle (XML-Element) | Beispiel |
|---|---|---|
| title | `<Titel>` (CDATA) | "Geschäftsordnung des Sächsischen Landtags" |
| datum | `<Fundstelle>` → Regex "datum"-Gruppe, DD.MM.YYYY → ISO | "01.10.2024" → "2024-10-01" |
| fraktionen | `<Fundstelle>` → Regex "urheber"-Gruppe → `extract_fraktionen()` | "CDU, BSW, SPD" → `["CDU", "BSW", "SPD"]` |
| drucksache | `f"{Wahlperiode}/{Dokumentennummer}"` | "8/2" |
| link | Konstruiert: `.../viewer.aspx?dok_nr={nr}&dok_art=Drs&leg_per={wp}` (PDF wird on-demand aus Iframe gelöst) | `https://edas.landtag.sachsen.de/.../viewer.aspx?dok_nr=2&dok_art=Drs&leg_per=8` |
| typ | Fest: "Antrag" | "Antrag" |
### BUND (BundestagAdapter — DIP REST-API)
| Feld | Quelle (JSON-Feld) | Beispiel |
|---|---|---|
| title | `titel` | "Förderung von Genossenschaften im sozialen Bereich" |
| datum | `datum` (bereits ISO YYYY-MM-DD) | "2026-04-08" |
| fraktionen | `urheber[*].titel``extract_fraktionen()` | "Fraktion der AfD" → `["AfD"]` |
| drucksache | `dokumentnummer` | "21/5136" |
| link | `fundstelle.pdf_url` | `https://dip.bundestag.de/documents/btd/21/051/2105136.pdf` |
| typ | `drucksachetyp` (server-side gefiltert) | "Antrag" |
## Gemeinsames Pattern
Alle Adapter normalisieren Fraktionen über den zentralen `parteien.extract_fraktionen(text, bundesland=...)` Mapper (#55). Alle konvertieren DE-Datumsformat (DD.MM.YYYY) nach ISO (YYYY-MM-DD), außer BUND (schon ISO) und SL (schon ISO mit T-Suffix).
## Historien-Tiefe (ältere WPs)
Alle Adapter sind aktuell auf **eine feste Wahlperiode** konfiguriert.
Keiner unterstützt Queries über mehrere WPs hinweg. Ältere Drucksachen
aus früheren WPs sind nicht abrufbar ohne Adapter-Rekonfiguration.
Für eine künftige Multi-WP-Suche müsste pro Adapter geprüft werden, ob
das Backend WP-übergreifende Queries unterstützt:
| API-Typ | Multi-WP möglich? |
|---|---|
| portala/eUI | Ja (WP-Term aus dem JSON-Tree weglassen) |
| ParlDok | Ja (`facet_lp` weglassen) |
| StarFinder/PARiS | Ja (WP-Param weglassen) |
| TYPO3-Solr (BY) | Ja (`wahlperiodeid[]` weglassen) |
| DIP-API (BUND) | Ja (`f.wahlperiode` weglassen) |
| Umbraco (SL) | Ja (`Filter.Periods=[]`) |
| EDAS-XML (SN) | Nur innerhalb des exportierten Datenbestands |
## Fehlende Adapter
| BL | Status | Blocker |
|---|---|---|
| **NI** (Niedersachsen) | Issue #22 offen | NILAS-StarWeb erfordert Session-Cookie, HAR-Capture vom User nötig |