Commit Graph

2 Commits

Author SHA1 Message Date
Dotty Dotter
19e5fe4691 Phase J: SN EDAS-XML-Adapter (#26/#38) — Sachsen aktiv via XML-Export
Reaktiviert die in Phase J vertagte Adapter-Implementation: statt
ASP.NET-Postbacks zu simulieren (blockt durch __VIEWSTATE-Komplexität
plus robots.txt: Disallow: /), liest die neue ``SNEdasXmlAdapter``-
Klasse einen wöchentlich manuell aus EDAS exportierten XML-Dump.

Workflow:

1. User exportiert in der EDAS-Suchmaske mit Filter "Dokumententyp =
   Antr" einen XML-Dump (bis zu 2500 Treffer/Export, sortiert
   newest-first nach Datum)
2. Datei wird unter ``data/sn-edas-export.xml`` abgelegt (ins
   persistent volume des prod-containers)
3. ``search()``/``get_document()`` lesen die XML-Datei lokal — keine
   Server-Calls gegen edas.landtag.sachsen.de
4. ``download_text()`` resolved die echte PDF-URL on-demand über einen
   einzelnen GET gegen ``viewer_navigation.aspx`` (single GET, kein
   Postback) und holt dann das PDF von ``ws.landtag.sachsen.de/images``

XML-Schema (ISO-8859-1):

- ``<ID>`` interne EDAS-Doc-ID
- ``<Wahlperiode>``, ``<Dokumentenart>``, ``<Dokumentennummer>``
- ``<Fundstelle>`` z.B. ``"Antr CDU, BSW, SPD 01.10.2024 Drs 8/2"`` —
  enthält Typ, Urheber und Datum, parsen via Regex
- ``<Titel>`` Volltext-Titel

PDF-URL-Schema (extrahiert aus dem viewer_navigation.aspx onLoad-
Handler): ``ws.landtag.sachsen.de/images/{wp}_Drs_{nr}_{...}.pdf``
mit variablen Suffix-Komponenten — wir machen die Resolution lazy.

Mapper-Erweiterung:

- ``parteien.PARTEIEN``-Tabelle um ``BÜNDNISGRÜNE``/``Bündnisgrüne``
  ergänzt — der Sachsen-spezifische zusammengeschriebene Eigenname der
  GRÜNEN-Fraktion (sonst wären 8/2100 etc. mit leerer Fraktionen-Liste
  rausgekommen)

BL-Eintrag:

- ``SN.aktiv = True``
- ``doku_system="EDAS-XML-Export"`` (klare Klassifikation, dass es
  KEIN normaler Webcrawler ist)
- Test ``test_sn_is_eigensystem_not_parldok`` umbenannt in
  ``test_sn_uses_xml_export_not_parldok``

Live-Probe lokal:

```
search('Klima', limit=5):
  8/2100 2025-03-17 | [GRÜNE]              | Fahrradoffensive Sachsen ...
  7/192  2019-10-11 | [LINKE]              | Erste Schritte zur Klimager...
  7/2067 2020-03-19 | [CDU, SPD, GRÜNE]    | Sächsische Waldbesitzer ...
```

176 Unit-Tests grün. Container braucht beim Deploy einen XML-Upload
ins data/-Volume — separater scp-Schritt.

Refs: #26, #38, #59 (Phase J revived)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:39:03 +02:00
Dotty Dotter
eb045d0ed3 Phase B: Parteinamen-Mapper #55 (Roadmap #59)
Zentrale `app/parteien.py` als Single Source of Truth für die Partei-
Auflösung:

- `PARTEIEN`-Tabelle mit kanonischem Key, langem Display-Namen, allen
  bekannten Aliasen, optionalem `bundesland_scope` und Government-
  Marker. 14 Einträge (CDU, CSU, SPD, GRÜNE, FDP, LINKE, AfD, BSW, SSW,
  BiW + die Freie-Wähler-Familie BVB-FW, FW-BAYERN, FW-SL und der
  generische FREIE WÄHLER-Eintrag).
- `normalize_partei(raw, *, bundesland=None)` für Single-String-Lookups
  mit Government-Vorrang und FW-Familien-Disambiguierung
- `extract_fraktionen(text, *, bundesland=None)` als Funnel für die
  vier alten Adapter-Helper. Kommagetrennte Listen, MdL-mit-Klammer-
  partei, HTML-Reste — alles fließt durch eine Stelle, mit BL-Scope-
  Filter (SSW nur in SH, BVB-FW nur in BB, etc.).
- `display_name(canonical, *, long=False)` für UI/PDF — kurze Form
  bleibt der kanonische Key, lange Form ist "BÜNDNIS 90/DIE GRÜNEN"
  statt "GRÜNE" etc.

Adapter-Migration in `app/parlamente.py`:

- Vier nahezu identische `_normalize_fraktion()`-Methoden in
  PortalaAdapter, ParLDokAdapter, StarFinderCGIAdapter, PARLISAdapter
  durch einen einzeiligen Shim ersetzt, der `extract_fraktionen` mit
  `self.bundesland` aufruft. ~120 Zeilen Duplikation entfernt.
- `@staticmethod` aufgehoben, weil wir jetzt `self.bundesland` brauchen
  für die FW-Disambiguierung — alle Aufrufer waren bereits `self._...`,
  also keine Call-Site-Änderung nötig.

`app/embeddings.py:496` Workaround-Hack entfernt:

- `partei.upper() if partei != "GRÜNE" else "GRÜNE"` durch zentralen
  `normalize_partei()`-Aufruf ersetzt — der Hack war ein Kommentarzeichen
  dafür, dass die Partei-Schreibweise irgendwo zwischen Adapter und
  Embedding-Lookup driften konnte. Mit dem Mapper ist die Schreibweise
  überall garantiert kanonisch.

Tests:

- Neue `tests/test_parteien.py` mit 52 Cases — Single-Lookup, FW-
  Disambiguierung (BVB/Bayern/Saarland/RP), Volltext-Extraktion,
  Government-Marker, Tabellen-Konsistenz
- `tests/test_parlamente.py` Test-Klasse umgeschrieben: statt der 6
  statischen `PortalaAdapter._normalize_fraktion(...)`-Tests jetzt 4
  Roundtrip-Tests über echte Adapter-Instanzen, inkl. expliziter
  BB→BVB-FW vs. RP→FREIE WÄHLER-Verifikation

157 Unit-Tests grün (105 alt + 52 neu). Backwards-kompatibel — die
kanonischen Keys sind exakt die in der DB stehenden Strings, kein
Migrations-Schritt nötig.

Refs: #55, #59 (Phase B)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:22:13 +02:00