"""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