gwoe-antragspruefer/app/drucksache_typen.py
Dotty Dotter ad1db2a924 feat: 16 BL-Adapter, Drucksache-Typen, Mail-Digest, Clustering, Redline-Parser
- 16 aktive BL-Adapter + BUND (parlamente.py 3397 LOC)
- drucksache_typen.py: BL-spezifische Typ-Normalisierung (#127)
- mail.py: SMTP + Daily-Digest (#124)
- clustering.py: Embedding-Naehe-Graph + Bubble-Chart (#105)
- redline_utils.py: §INS§/§DEL§-Parser + PDF-Cite-URL-Builder
- embeddings v3->v4 Migration (#123, ADR 0006)
- chart.js + d3.v7 als statische Assets fuer Auswertungen-Cluster

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:54:50 +02:00

89 lines
2.8 KiB
Python

"""Drucksache-Typ-Normalisierung (#127).
Jeder Landtag hat eigene Bezeichnungen für Dokumenttypen. Dieses Modul
normalisiert sie auf einheitliche Kategorien und bestimmt ob eine
Drucksache abstimmbar ist (= GWÖ-Bewertung sinnvoll).
"""
# Normierte Kategorien
ANTRAG = "antrag"
GESETZENTWURF = "gesetzentwurf"
AENDERUNGSANTRAG = "aenderungsantrag"
DRINGLICHKEITSANTRAG = "dringlichkeitsantrag"
ENTSCHLIESSUNGSANTRAG = "entschliessungsantrag"
BESCHLUSSEMPFEHLUNG = "beschlussempfehlung"
KLEINE_ANFRAGE = "kleine_anfrage"
GROSSE_ANFRAGE = "grosse_anfrage"
UNTERRICHTUNG = "unterrichtung"
PETITION = "petition"
WAHLVORSCHLAG = "wahlvorschlag"
BERICHT = "bericht"
SONSTIGE = "sonstige"
ABSTIMMBARE_TYPEN = {
ANTRAG,
GESETZENTWURF,
AENDERUNGSANTRAG,
DRINGLICHKEITSANTRAG,
ENTSCHLIESSUNGSANTRAG,
}
# Übersetzungstabelle: Original-Typ (lowercase) → normierter Typ.
# Keys werden case-insensitive + substring-matched.
# Reihenfolge: spezifischere zuerst (z.B. "kleine anfrage" vor "anfrage").
_TYP_MAP = [
# Abstimmbar
("gesetzentwurf", GESETZENTWURF),
("änderungsantrag", AENDERUNGSANTRAG),
("aenderungsantrag", AENDERUNGSANTRAG),
("dringlichkeitsantrag", DRINGLICHKEITSANTRAG),
("entschließungsantrag", ENTSCHLIESSUNGSANTRAG),
("entschliessungsantrag", ENTSCHLIESSUNGSANTRAG),
("antrag gemäß", ANTRAG),
("antrag", ANTRAG),
# Nicht abstimmbar
("kleine anfrage", KLEINE_ANFRAGE),
("große anfrage", GROSSE_ANFRAGE),
("grosse anfrage", GROSSE_ANFRAGE),
("anfrage", KLEINE_ANFRAGE),
("beschlussempfehlung", BESCHLUSSEMPFEHLUNG),
("unterrichtung", UNTERRICHTUNG),
("bericht", BERICHT),
("mitteilung", UNTERRICHTUNG),
("vorlage", UNTERRICHTUNG),
("petition", PETITION),
("wahlvorschlag", WAHLVORSCHLAG),
("stellungnahme", SONSTIGE),
("drucksache", SONSTIGE),
]
def normalize_typ(original: str) -> str:
"""Normalisiert einen BL-spezifischen Typ-String auf eine Kategorie.
Case-insensitiv, Substring-Match, spezifischere Patterns zuerst.
"""
if not original:
return SONSTIGE
low = original.lower().strip()
for pattern, norm in _TYP_MAP:
if pattern in low:
return norm
return SONSTIGE
def ist_abstimmbar(typ_normiert: str) -> bool:
"""Prüft ob ein normierter Typ zur Abstimmung steht.
``sonstige`` wird durchgelassen (benefit of the doubt) — wenn der
Adapter den Typ nicht bestimmen kann (z.B. NRW liefert nur
"Drucksache"), wird der echte Check erst beim Analysieren gemacht
(aus dem Dokument-Text).
"""
return typ_normiert in ABSTIMMBARE_TYPEN or typ_normiert == SONSTIGE
def ist_abstimmbar_original(original: str) -> bool:
"""Convenience: prüft direkt am Original-Typ-String."""
return ist_abstimmbar(normalize_typ(original))