feat(#6 Phase 11): Bundesrats-Drucksachen erkennen + markieren + ablehnen
DIP-Drucksachen mit `herausgeber: 'BR'` (Bundesrat) haben Bundesländer als Antragsteller (z.B. SN, HE) statt Fraktionen. Variante b — explizite Behandlung statt nur ausschließen: - Drucksache-dataclass: neue Felder `is_bundesrat: bool`, `urheber_bundeslaender: list[str]`. Existierende Pfade unberührt. - BundestagAdapter._doc_to_drucksache: liest herausgeber + urheber-Liste, setzt Bundesländer-Codes (bezeichnung wie "SN") in urheber_bundeslaender. fraktionen bleibt leer fuer BR — verhindert dass Stimmverhalten-Aggregate verwirrt werden. - /api/search-landtag liefert is_bundesrat + urheber_bundeslaender im Response. - /api/analyze-drucksache (POST) lehnt BR-Drucksachen mit HTTP 400 + klarer Meldung ab statt crashen. - v2-Search-UI rendert grayen Bundesrat-Sticker mit BL-Codes statt Fraktionen, "Analysieren"-Button durch "nicht unterstuetzt" ersetzt. is_bundesrat_drucksache() in drucksache_typen.py als Format-Helper (N/M/JJ-Pattern) bleibt fuer Cases wo nur die Drucksache-ID ohne Adapter-Metadaten verfuegbar ist. Refs: #6 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
03948038c4
commit
5667259bff
@ -128,3 +128,25 @@ def likely_kleine_anfrage_titel(title: str) -> bool:
|
||||
if t.rstrip().endswith("?"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# Bundesrats-Drucksachen-Format erkennen.
|
||||
# Bundestag: "21/9954" (Wahlperiode/Nummer, 2 Komponenten).
|
||||
# Bundesrat: "186/3/26", "186/2/26" (Nummer/Sub/Jahr, 3 Komponenten).
|
||||
import re as _re
|
||||
|
||||
_BUNDESRAT_PATTERN = _re.compile(r"^\d+/\d+/\d+$")
|
||||
|
||||
|
||||
def is_bundesrat_drucksache(drucksache: str) -> bool:
|
||||
"""Erkennt Bundesrats-Drucksachen am 3-Komponenten-Format ``N/M/JJ``.
|
||||
|
||||
Diese Drucksachen finden sich über die DIP-API (Bundestags-Backend
|
||||
indexiert sie mit), haben aber **keine Fraktionen** als Antragsteller —
|
||||
sondern Bundesländer. Der GWÖ-Analyzer erwartet Fraktionen und schlägt
|
||||
bei Bundesrats-Drucksachen fehl. Folge: separate Behandlung im Such-
|
||||
+ Analyse-Pfad.
|
||||
"""
|
||||
if not drucksache:
|
||||
return False
|
||||
return bool(_BUNDESRAT_PATTERN.match(drucksache.strip()))
|
||||
|
||||
16
app/main.py
16
app/main.py
@ -1416,6 +1416,8 @@ async def search_landtag(
|
||||
"bundesland": bundesland,
|
||||
"typ": doc.typ,
|
||||
"typ_normiert": doc.typ_normiert,
|
||||
"is_bundesrat": doc.is_bundesrat,
|
||||
"urheber_bundeslaender": doc.urheber_bundeslaender,
|
||||
"gwoeScore": None,
|
||||
"status": "unchecked",
|
||||
})
|
||||
@ -1572,6 +1574,20 @@ async def analyze_drucksache(
|
||||
# Get document metadata
|
||||
doc = await adapter.get_document(drucksache)
|
||||
|
||||
# Bundesrats-Drucksachen ablehnen — Antragsteller sind Bundesländer,
|
||||
# GWÖ-Wahlprogramm-Bewertung greift nicht (kein Partei-Bezug).
|
||||
if doc and getattr(doc, "is_bundesrat", False):
|
||||
bls = ", ".join(doc.urheber_bundeslaender or []) or "—"
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
f"Bundesrats-Drucksache (Antragsteller: {bls}) wird derzeit "
|
||||
f"nicht unterstützt. Die GWÖ-Bewertung erwartet eine "
|
||||
f"antragstellende Fraktion, Bundesländer-Anträge aus dem "
|
||||
f"Bundesrat haben aber keine Wahlprogramm-Verbindung."
|
||||
),
|
||||
)
|
||||
|
||||
# #127: Typ-Check — nur abstimmbare Drucksachen analysieren.
|
||||
# Falls der Adapter den Typ nicht richtig setzt (NRW: "Drucksache"),
|
||||
# versuche den Typ aus dem Dokument-Text zu erkennen.
|
||||
|
||||
@ -23,11 +23,18 @@ class Drucksache:
|
||||
bundesland: str
|
||||
typ: str = "Antrag" # Original-Typ vom Landtag (z.B. "Kleine Anfrage", "Gesetzentwurf")
|
||||
typ_normiert: str = "" # Normierter Typ (wird automatisch gesetzt)
|
||||
# Bundesrats-Drucksachen (DIP herausgeber=BR): Antragsteller sind
|
||||
# Bundesländer (z.B. ['SN','HE']) statt Fraktionen. fraktionen bleibt
|
||||
# leer, damit Stimmverhalten-Aggregate sich nicht verwirren.
|
||||
is_bundesrat: bool = False
|
||||
urheber_bundeslaender: list[str] = None # type: ignore[assignment]
|
||||
|
||||
def __post_init__(self):
|
||||
from .drucksache_typen import normalize_typ
|
||||
if not self.typ_normiert:
|
||||
self.typ_normiert = normalize_typ(self.typ)
|
||||
if self.urheber_bundeslaender is None:
|
||||
self.urheber_bundeslaender = []
|
||||
|
||||
|
||||
class ParlamentAdapter(ABC):
|
||||
@ -2928,15 +2935,30 @@ class BundestagAdapter(ParlamentAdapter):
|
||||
if not pdf_url:
|
||||
return None
|
||||
|
||||
# Fraktionen aus urheber-Liste extrahieren. DIP listet sie als
|
||||
# "Fraktion der AfD" o.ä. — extract_fraktionen kennt das Pattern
|
||||
# bereits aus den Landtags-Adaptern.
|
||||
# Bundesrats-Drucksachen erkennen am DIP-Feld `herausgeber: 'BR'`.
|
||||
# Antragsteller sind dort Bundesländer (z.B. Sachsen, Hessen),
|
||||
# nicht Bundestags-Fraktionen.
|
||||
is_bundesrat = (doc.get("herausgeber") or "").upper() == "BR"
|
||||
|
||||
urheber_strs: list[str] = []
|
||||
urheber_bundeslaender: list[str] = []
|
||||
for u in (doc.get("urheber") or []):
|
||||
if isinstance(u, dict):
|
||||
urheber_strs.append(u.get("titel") or u.get("bezeichnung") or "")
|
||||
urheber_combined = ", ".join(filter(None, urheber_strs))
|
||||
fraktionen = extract_fraktionen(urheber_combined, bundesland=self.bundesland)
|
||||
# Bei Bundesrat: bezeichnung ist der BL-Code (SN, HE, NW, ...)
|
||||
if is_bundesrat:
|
||||
code = (u.get("bezeichnung") or "").strip().upper()
|
||||
if code and len(code) <= 4:
|
||||
urheber_bundeslaender.append(code)
|
||||
|
||||
if is_bundesrat:
|
||||
# Fraktionen leer lassen — Bundesländer-Antragsteller sind
|
||||
# in urheber_bundeslaender. Stimmverhalten-Aggregate ignorieren
|
||||
# diese Drucksachen automatisch (kein fraktionen-Match).
|
||||
fraktionen: list[str] = []
|
||||
else:
|
||||
urheber_combined = ", ".join(filter(None, urheber_strs))
|
||||
fraktionen = extract_fraktionen(urheber_combined, bundesland=self.bundesland)
|
||||
|
||||
return Drucksache(
|
||||
drucksache=nummer,
|
||||
@ -2946,6 +2968,8 @@ class BundestagAdapter(ParlamentAdapter):
|
||||
link=pdf_url,
|
||||
bundesland=self.bundesland,
|
||||
typ=doc.get("drucksachetyp", "Antrag"),
|
||||
is_bundesrat=is_bundesrat,
|
||||
urheber_bundeslaender=urheber_bundeslaender,
|
||||
)
|
||||
|
||||
async def _fetch_page(
|
||||
|
||||
@ -273,29 +273,41 @@ function renderRow(item, bl) {
|
||||
var url = item.url || item.link || '';
|
||||
var done = lsCheckedIds.has(ds);
|
||||
var fraktionen = Array.isArray(item.fraktionen) ? item.fraktionen : [];
|
||||
var isBundesrat = !!item.is_bundesrat;
|
||||
var bundeslaender = Array.isArray(item.urheber_bundeslaender) ? item.urheber_bundeslaender : [];
|
||||
|
||||
var titleHtml = url
|
||||
? '<a href="' + escHtml(url) + '" target="_blank" rel="noopener">' + title + '</a>'
|
||||
: title;
|
||||
|
||||
var fraktionenHtml = fraktionen.length
|
||||
? '<div class="ls-fraktionen">'
|
||||
var fraktionenHtml = '';
|
||||
if (isBundesrat) {
|
||||
var blsList = bundeslaender.length
|
||||
? bundeslaender.map(function(c){ return escHtml(c); }).join(', ')
|
||||
: '—';
|
||||
fraktionenHtml = '<div class="ls-fraktionen" style="opacity:0.85;">'
|
||||
+ '<span class="ls-fraktion" style="background:rgba(247,148,29,0.18);color:#bf6c10;border-color:rgba(247,148,29,0.4);" title="Bundesrats-Drucksache: Antragsteller sind Bundesländer, nicht Fraktionen. Wird derzeit nicht für die GWÖ-Bewertung unterstützt.">Bundesrat · ' + blsList + '</span>'
|
||||
+ '</div>';
|
||||
} else if (fraktionen.length) {
|
||||
fraktionenHtml = '<div class="ls-fraktionen">'
|
||||
+ fraktionen.map(function (f) {
|
||||
return '<span class="ls-fraktion">' + escHtml(f) + '</span>';
|
||||
}).join('')
|
||||
+ '</div>'
|
||||
: '';
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
var actionHtml;
|
||||
if (done) {
|
||||
actionHtml = '<span class="ls-badge-done">Bewertet → <a href="/antrag/' + encodeURIComponent(ds) + '" style="color:inherit;">Ansehen</a></span>';
|
||||
} else if (isBundesrat) {
|
||||
actionHtml = '<span style="font-family:var(--font-mono);font-size:10px;opacity:0.55;" title="Bundesrats-Drucksachen werden derzeit nicht analysiert.">nicht unterstützt</span>';
|
||||
} else if (lsIsAuth) {
|
||||
actionHtml = '<button class="ls-btn-analyse" onclick="lsAnalyse(this,\'' + escAttr(ds) + '\',\'' + escAttr(bl) + '\')">Analysieren</button>';
|
||||
} else {
|
||||
actionHtml = '<span style="font-family:var(--font-mono);font-size:10px;opacity:0.5;">Anmeldung nötig</span>';
|
||||
}
|
||||
|
||||
return '<div class="ls-row">'
|
||||
return '<div class="ls-row"' + (isBundesrat ? ' style="opacity:0.7;"' : '') + '>'
|
||||
+ '<div class="ls-drucksache">' + escHtml(ds) + '</div>'
|
||||
+ '<div class="ls-main">'
|
||||
+ '<div class="ls-title">' + titleHtml + '</div>'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user