gwoe-antragspruefer/app/drucksache_typen.py

131 lines
4.3 KiB
Python
Raw Permalink Normal View History

"""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))
# Frage-Präfixe die typisch für Kleine Anfragen sind. Wird genutzt wenn der
# Adapter (z.B. NRW) den Typ nur als "Drucksache" liefert — wir versuchen
# anhand des Titels eine bessere Klassifikation, damit Search-Ergebnisse
# nicht voll mit nicht-abstimmbaren Anfragen sind.
_FRAGE_PRAEFIXE = (
"welche ", "wie viele ", "wieviel", "wie viel ", "wie hoch ", "wie ",
"wann ", "warum ", "weshalb ", "wo ", "wer ", "wie steht ", "wie weit ",
"ist es ", "ist der ", "ist die ", "ist das ", "sind ",
"trifft es ", "kann ", "wird ", "wieso ", "was ",
"hat ", "hat der ", "hat die ", "hat das ",
"haben ", "war ", "waren ",
)
def likely_kleine_anfrage_titel(title: str) -> bool:
"""Heuristik: erkennt Kleine Anfragen am Titel-Format.
Wenn der Titel mit einem typischen Frage-Präfix beginnt oder mit "?" endet,
behandeln wir die Drucksache als Kleine Anfrage. NRW-OPAL klassifiziert
alle Drucksachen als "Drucksache" ohne diese Heuristik landen Anfragen
in den Search-Ergebnissen, was den User verwirrt (#149 Folge).
Args:
title: Drucksachen-Titel inkl. evtl. Nummer-Präfix wie "1Welche...".
Returns:
True wenn der Titel wie eine Kleine Anfrage aussieht.
"""
if not title:
return False
t = title.strip()
# Manche Adapter prefixen mit Nummerierung wie "1Welche..." — strippen
while t and (t[0].isdigit() or t[0] in " .-"):
t = t[1:]
t_low = t.lower()
if t_low.startswith(_FRAGE_PRAEFIXE):
return True
if t.rstrip().endswith("?"):
return True
return False