feat(#149): BB-Parser produktiv — Brandenburger Plenarprotokolle (Status-Only)
URL-Pattern verifiziert WP8 Sitzung 22: https://www.parlamentsdokumentation.brandenburg.de/starweb/LBB/ELVIS/parladoku/w8/plpr/{n}.pdf **Wichtig:** parladoku-PDF-URL liefert 403 ohne Cookie-Session. Erst GET auf portal/browse.tt.html?wp=8 zur Cookie-Akquise, dann mit gesetztem Cookie die PDF-URL aufrufen. Ingest-Cron implementiert diesen Flow per http.cookiejar.CookieJar in Python. Anchor-Pattern (NRW-aehnlich): - "Damit ist [Subj] (mehrheitlich|einstimmig)? (angenommen|abgelehnt|ueberwiesen)" - Drucksachen-Lookup: Drucksache 8/N rueckwaerts vom Anchor Vote-Style: Handzeichen-only (kein Fraktionen-Listing). Daher Vote-Listen leer; einstimmig=True setzt JA=alle WP8-Fraktionen (SPD, AfD, CDU, BSW, GRÜNE). Tests: 14 BB-Tests, Verifikation S22 → 26 Vote-Anchors extrahiert. Stand: 10 produktive Parser (NRW, BUND, BE, HH, TH, HE, SH, HB, SL, BB). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d0f7b9217c
commit
33bb564ed1
@ -37,6 +37,7 @@ from .he import parse_protocol as _parse_he
|
|||||||
from .sh import parse_protocol as _parse_sh
|
from .sh import parse_protocol as _parse_sh
|
||||||
from .hb import parse_protocol as _parse_hb
|
from .hb import parse_protocol as _parse_hb
|
||||||
from .sl import parse_protocol as _parse_sl
|
from .sl import parse_protocol as _parse_sl
|
||||||
|
from .bb import parse_protocol as _parse_bb
|
||||||
|
|
||||||
# Typ-Alias fuer Lesbarkeit; Parser-Signatur ist bewusst minimal.
|
# Typ-Alias fuer Lesbarkeit; Parser-Signatur ist bewusst minimal.
|
||||||
ProtokollParser = Callable[[str], list[dict]]
|
ProtokollParser = Callable[[str], list[dict]]
|
||||||
@ -51,6 +52,7 @@ PROTOKOLL_PARSERS: dict[str, ProtokollParser] = {
|
|||||||
"SH": _parse_sh,
|
"SH": _parse_sh,
|
||||||
"HB": _parse_hb,
|
"HB": _parse_hb,
|
||||||
"SL": _parse_sl,
|
"SL": _parse_sl,
|
||||||
|
"BB": _parse_bb,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,47 +1,118 @@
|
|||||||
"""Brandenburg (BB) — Plenarprotokoll-Parser STUB (#106 Folge, ADR 0009).
|
"""Brandenburg (BB) — Plenarprotokoll-Parser (#106 / #149, ADR 0009).
|
||||||
|
|
||||||
**Status: noch nicht implementiert.** Dieser Modul-Stub enthaelt
|
URL-Pattern (verifiziert WP8 Sitzung 22):
|
||||||
Recherche-Findings vom 2026-04-28, sodass die Implementer-Session
|
``https://www.parlamentsdokumentation.brandenburg.de/starweb/LBB/ELVIS/parladoku/w8/plpr/{n}.pdf``
|
||||||
direkt produktiv loslegen kann. Der Stub wird **nicht** in
|
|
||||||
``app.protokoll_parsers.PROTOKOLL_PARSERS`` registriert — der
|
|
||||||
Auto-Ingest-Cron ueberspringt BB solange.
|
|
||||||
|
|
||||||
## Recherche
|
**Wichtig:** parladoku-PDF-URL braucht Cookie-Session vom Portal. Erst
|
||||||
|
GET auf ``portal/browse.tt.html?wp=8`` zur Cookie-Akquise, dann mit
|
||||||
|
gesetztem Cookie die PDF-URL aufrufen. Im Auto-Ingest-Cron deshalb
|
||||||
|
ein eigener Block, der den BB-Cookie-Flow durchlaeuft.
|
||||||
|
|
||||||
| Feld | Wert |
|
## Anchor-Sprache (verifiziert WP8 Sitzung 22)
|
||||||
|---|---|
|
|
||||||
| **Doku-System** | portala |
|
|
||||||
| **Base-URL** | https://www.parlamentsdokumentation.brandenburg.de |
|
|
||||||
| **Familie** | RP/HE-Familie |
|
|
||||||
| **Format** | PDF (Vote-Tabellen erwartet); BB-Adapter PortalaAdapter |
|
|
||||||
|
|
||||||
## URL-Discovery
|
```
|
||||||
|
Wer dem zustimmt, den bitte ich um das Handzeichen. – Ich bitte um die
|
||||||
|
Gegenprobe. – Stimmenthaltungen? – Damit ist der Antrag mehrheitlich
|
||||||
|
abgelehnt; es gab keine Enthaltungen.
|
||||||
|
```
|
||||||
|
|
||||||
https://www.parlamentsdokumentation.brandenburg.de/parladoku/w8/plpr/PlPr8-{n}.pdf (HTTP 403 ohne Referer)
|
Pattern (NRW-aehnlich):
|
||||||
|
- **Result-Anchor:** ``Damit ist [Subjekt] (mehrheitlich|einstimmig)? (angenommen|abgelehnt|überwiesen)``
|
||||||
|
- **Vote-Block:** Q+A im Reden-Stil (Handzeichen-only, ohne Fraktionen-Listing)
|
||||||
|
- "Wer dem zustimmt, ... Handzeichen"
|
||||||
|
- "Gegenprobe"
|
||||||
|
- "Enthaltungen?"
|
||||||
|
- **Drucksachen-Lookup:** ``Drucksache 8/N`` rueckwaerts vom Anchor
|
||||||
|
|
||||||
## Bezug
|
**Limitierung:** BB-Plenarprotokolle nennen die Fraktionen nicht
|
||||||
|
explizit — Vote-Listen bleiben leer. ``einstimmig=True`` setzt
|
||||||
|
JA=alle WP8-Fraktionen als Approximation.
|
||||||
|
|
||||||
- Architektur: ADR 0009 (Plenarprotokoll-Parser-Registry)
|
## Fraktions-Mapping WP8 (ab 2024)
|
||||||
- Roadmap: ``docs/protokoll-parser-roadmap.md``
|
|
||||||
- Referenz-Implementation: ``app/protokoll_parsers/nrw.py``
|
|
||||||
(38 Tests, 19/19-Fixture-Garantie)
|
|
||||||
- Folge-Issue: https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/149 (Titel: "protokoll-parser: BB (Brandenburg)")
|
|
||||||
|
|
||||||
## Aufwand
|
WP8 Konstellation (2024-Wahl): SPD + BSW (Koalition), AfD + CDU + GRÜNE
|
||||||
|
(Opposition).
|
||||||
|
|
||||||
Geschaetzt 1-3 Tage konzentrierte Arbeit:
|
- ``SPD``, ``AfD``, ``CDU``, ``BSW``, ``GRÜNE``
|
||||||
- 2-4h URL-Discovery + Format-Inspektion (Sample-Protokoll inhaltlich anschauen)
|
|
||||||
- 4-8h Anchor-Phrasen-Reverse-Engineering + Parser-Implementierung
|
|
||||||
- 4h Tests mit Fixture-Pinning
|
|
||||||
- 1h Eintrag in PROTOKOLL_PARSERS + auto-ingest-protocols.sh
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
def parse_protocol(path: str) -> list[dict]:
|
try:
|
||||||
"""STUB — siehe Modul-Docstring."""
|
import fitz
|
||||||
raise NotImplementedError(
|
except ImportError:
|
||||||
"BB-Plenarprotokoll-Parser ist noch nicht implementiert. "
|
fitz = None
|
||||||
"Siehe app/protokoll_parsers/bb.py-Docstring fuer Recherche-Findings "
|
|
||||||
"und docs/protokoll-parser-roadmap.md."
|
|
||||||
|
ALLE_FRAKTIONEN_BB = ["SPD", "AfD", "CDU", "BSW", "GRÜNE"]
|
||||||
|
|
||||||
|
|
||||||
|
# Result-Anchor: "Damit ist/sind [Subjekt] (modus)? (ergebnis)"
|
||||||
|
RESULT_ANCHOR_RE = re.compile(
|
||||||
|
r"Damit\s+(?:ist|sind)\s+(?:der|die|das|dieser?|dieses|beide|alle|auch)?\s*"
|
||||||
|
r"(?P<subject>Antrag|Alternativantrag|Änderungsantrag|Gesetzentwurf|"
|
||||||
|
r"Entschließungsantrag|Beschlussempfehlung|Tagesordnungspunkt|Anträge)?"
|
||||||
|
r"[^.]{0,200}?(?P<modus>einstimmig|mehrheitlich|mit\s+(?:großer\s+)?Mehrheit)?\s*"
|
||||||
|
r"(?P<ergebnis>angenommen|abgelehnt|überwiesen)",
|
||||||
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DS_RE_BB = re.compile(r"Drucksache\s+8/(\d{1,5})")
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_drucksache_bb(text: str, anchor_start: int) -> Optional[str]:
|
||||||
|
window_start = max(0, anchor_start - 1500)
|
||||||
|
window = text[window_start:anchor_start]
|
||||||
|
matches = list(DS_RE_BB.finditer(window))
|
||||||
|
if matches:
|
||||||
|
return f"8/{matches[-1].group(1)}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_text(text: str) -> str:
|
||||||
|
text = re.sub(r"(?<=[a-zäöüß])-\s+(?=[a-zäöüß])", "", text)
|
||||||
|
return re.sub(r"\s+", " ", text)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_protocol(pdf_path: str) -> list[dict]:
|
||||||
|
if fitz is None:
|
||||||
|
raise ImportError("PyMuPDF (fitz) ist erforderlich fuer den BB-Parser")
|
||||||
|
|
||||||
|
doc = fitz.open(pdf_path)
|
||||||
|
full = "".join(p.get_text() for p in doc)
|
||||||
|
doc.close()
|
||||||
|
full = _normalize_text(full)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for m in RESULT_ANCHOR_RE.finditer(full):
|
||||||
|
modus = (m.group("modus") or "").lower()
|
||||||
|
ergebnis = m.group("ergebnis")
|
||||||
|
ds = _resolve_drucksache_bb(full, m.start())
|
||||||
|
if not ds:
|
||||||
|
continue
|
||||||
|
|
||||||
|
einstimmig = "einstimmig" in modus
|
||||||
|
votes = {"ja": [], "nein": [], "enthaltung": []}
|
||||||
|
if einstimmig:
|
||||||
|
votes["ja"] = list(ALLE_FRAKTIONEN_BB)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"drucksache": ds,
|
||||||
|
"ergebnis": ergebnis,
|
||||||
|
"einstimmig": einstimmig,
|
||||||
|
"kind": "direct",
|
||||||
|
"votes": votes,
|
||||||
|
"anchor_pos": m.start(),
|
||||||
|
})
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
deduped = []
|
||||||
|
for r in results:
|
||||||
|
key = (r["drucksache"], r["anchor_pos"])
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
deduped.append(r)
|
||||||
|
return deduped
|
||||||
|
|||||||
@ -19,7 +19,7 @@ Body und der Eintrag wird in `PROTOKOLL_PARSERS` ergaenzt.
|
|||||||
| **BUND** | `bund.py` | [#148](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/148) | ✅ produktiv (112 Votes, 39 Protokolle WP20) |
|
| **BUND** | `bund.py` | [#148](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/148) | ✅ produktiv (112 Votes, 39 Protokolle WP20) |
|
||||||
| **BE** | `be.py` | [#150](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/150) | ✅ produktiv (200 Votes, 63 Protokolle WP19) |
|
| **BE** | `be.py` | [#150](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/150) | ✅ produktiv (200 Votes, 63 Protokolle WP19) |
|
||||||
| **HH** | `hh.py` | [#155](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/155) | ✅ produktiv (18+ Votes WP23, Cron via Index-Scrape) |
|
| **HH** | `hh.py` | [#155](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/155) | ✅ produktiv (18+ Votes WP23, Cron via Index-Scrape) |
|
||||||
| BB | `bb.py` | [#149](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/149) | 📋 Stub (Portala 403 ohne Cookie-Session) |
|
| **BB** | `bb.py` | [#149](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/149) | ✅ produktiv (Status-Only Handzeichen, Cookie-URL-Flow, WP8) |
|
||||||
| BW | `bw.py` | [#151](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/151) | ⚠ Stub (Datenmodell-Inkompatibilitaet) |
|
| BW | `bw.py` | [#151](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/151) | ⚠ Stub (Datenmodell-Inkompatibilitaet) |
|
||||||
| BY | `by.py` | [#152](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/152) | 📋 Stub |
|
| BY | `by.py` | [#152](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/152) | 📋 Stub |
|
||||||
| **HB** | `hb.py` | [#153](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/153) | ✅ produktiv (Status-Only, 402 Votes 33 Protokolle WP21 Land, URL-Pattern direkt) |
|
| **HB** | `hb.py` | [#153](https://repo.toppyr.de/tobias/gwoe-antragspruefer/issues/153) | ✅ produktiv (Status-Only, 402 Votes 33 Protokolle WP21 Land, URL-Pattern direkt) |
|
||||||
|
|||||||
@ -290,6 +290,91 @@ for pid, url in matches:
|
|||||||
print(f" SL: {new_count} neue Sitzungen ingestet")
|
print(f" SL: {new_count} neue Sitzungen ingestet")
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# ─── BB: Cookie-basierter URL-Flow ────────────────────────────────────
|
||||||
|
# parladoku-PDF-URL braucht Cookie-Session. Erst GET portal/browse zur
|
||||||
|
# Cookie-Akquise, dann PDF-Probing mit gesetztem Cookie.
|
||||||
|
# URL-Pattern: starweb/LBB/ELVIS/parladoku/w8/plpr/{n}.pdf
|
||||||
|
echo "--- BB WP8 (Cookie-URL-Probing) ---"
|
||||||
|
docker exec -i "$CONTAINER" python <<'EOF'
|
||||||
|
import re, sys, http.cookiejar
|
||||||
|
import urllib.request
|
||||||
|
import sqlite3
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# Cookie-Akquise: Portal-Browse-Page aufrufen
|
||||||
|
cj = http.cookiejar.CookieJar()
|
||||||
|
opener = urllib.request.build_opener(
|
||||||
|
urllib.request.HTTPCookieProcessor(cj)
|
||||||
|
)
|
||||||
|
opener.addheaders = [("User-Agent", "Mozilla/5.0 GWOeAntragspruefer")]
|
||||||
|
|
||||||
|
try:
|
||||||
|
opener.open(
|
||||||
|
"https://www.parlamentsdokumentation.brandenburg.de/portal/browse.tt.html?wp=8",
|
||||||
|
timeout=20,
|
||||||
|
).read()
|
||||||
|
print(f" Cookies erworben: {len(cj)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Cookie-Akquise fehlgeschlagen: {e}")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
db = sqlite3.connect("/app/data/gwoe-antraege.db")
|
||||||
|
existing_max = db.execute(
|
||||||
|
"SELECT COALESCE(MAX(CAST(SUBSTR(quelle_protokoll, 6) AS INTEGER)), 0) "
|
||||||
|
"FROM plenum_vote_results WHERE bundesland='BB' AND quelle_protokoll LIKE 'BB8-%'"
|
||||||
|
).fetchone()[0]
|
||||||
|
|
||||||
|
from app.ingest_votes import ingest_pdf
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
start_n = max(1, existing_max + 1)
|
||||||
|
print(f" Letztes ingestes BB8-: {existing_max}, probiere ab {start_n}")
|
||||||
|
|
||||||
|
new_count = 0
|
||||||
|
consec_404 = 0
|
||||||
|
for n in range(start_n, start_n + 50):
|
||||||
|
pid = f"BB8-{n}"
|
||||||
|
url = f"https://www.parlamentsdokumentation.brandenburg.de/starweb/LBB/ELVIS/parladoku/w8/plpr/{n}.pdf"
|
||||||
|
try:
|
||||||
|
with opener.open(url, timeout=20) as resp:
|
||||||
|
data = resp.read()
|
||||||
|
if data[:4] != b"%PDF":
|
||||||
|
consec_404 += 1
|
||||||
|
if consec_404 >= 3:
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
consec_404 = 0
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code == 404 or e.code == 403:
|
||||||
|
consec_404 += 1
|
||||||
|
if consec_404 >= 3:
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(f" {pid}: HTTP {e.code}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {pid}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f" → ingest {pid}")
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
|
||||||
|
tmp_path = Path(tmp.name)
|
||||||
|
tmp_path.write_bytes(data)
|
||||||
|
try:
|
||||||
|
stats = asyncio.run(ingest_pdf(
|
||||||
|
tmp_path, bundesland="BB", protokoll_id=pid, quelle_url=url,
|
||||||
|
))
|
||||||
|
print(f" parsed: {stats['parsed']}, written: {stats['written']}")
|
||||||
|
new_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Fehler: {e}")
|
||||||
|
finally:
|
||||||
|
tmp_path.unlink(missing_ok=True)
|
||||||
|
print(f" BB: {new_count} neue Protokolle ingestet")
|
||||||
|
EOF
|
||||||
|
|
||||||
for entry in "${PROTO_TARGETS[@]}"; do
|
for entry in "${PROTO_TARGETS[@]}"; do
|
||||||
IFS='|' read -r bl wp prefix pattern <<< "$entry"
|
IFS='|' read -r bl wp prefix pattern <<< "$entry"
|
||||||
echo "--- ${bl} WP${wp} (prefix=${prefix}) ---"
|
echo "--- ${bl} WP${wp} (prefix=${prefix}) ---"
|
||||||
|
|||||||
83
tests/test_protokoll_parsers_bb.py
Normal file
83
tests/test_protokoll_parsers_bb.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""Tests fuer app/protokoll_parsers/bb.py — BB Plenarprotokoll-Parser (#149).
|
||||||
|
|
||||||
|
Stichprobe-getestet gegen WP8 Sitzung 22 (Brandenburg).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.protokoll_parsers.bb import (
|
||||||
|
_normalize_text,
|
||||||
|
_resolve_drucksache_bb,
|
||||||
|
RESULT_ANCHOR_RE,
|
||||||
|
DS_RE_BB,
|
||||||
|
ALLE_FRAKTIONEN_BB,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormalizeText:
|
||||||
|
def test_collapses_whitespace(self):
|
||||||
|
assert _normalize_text("a b\n\tc") == "a b c"
|
||||||
|
|
||||||
|
def test_repairs_soft_hyphenation(self):
|
||||||
|
assert _normalize_text("zustim- mt") == "zustimmt"
|
||||||
|
|
||||||
|
|
||||||
|
class TestResultAnchorRegex:
|
||||||
|
def test_matches_mehrheitlich_abgelehnt(self):
|
||||||
|
m = RESULT_ANCHOR_RE.search("Damit ist der Antrag mehrheitlich abgelehnt.")
|
||||||
|
assert m
|
||||||
|
assert m.group("modus") == "mehrheitlich"
|
||||||
|
assert m.group("ergebnis") == "abgelehnt"
|
||||||
|
|
||||||
|
def test_matches_einstimmig_angenommen(self):
|
||||||
|
m = RESULT_ANCHOR_RE.search("Damit ist der Antrag einstimmig angenommen.")
|
||||||
|
assert m
|
||||||
|
assert m.group("modus") == "einstimmig"
|
||||||
|
|
||||||
|
def test_matches_entschliessungsantrag(self):
|
||||||
|
m = RESULT_ANCHOR_RE.search(
|
||||||
|
"Damit ist der Entschließungsantrag mehrheitlich abgelehnt."
|
||||||
|
)
|
||||||
|
assert m and m.group("subject") == "Entschließungsantrag"
|
||||||
|
|
||||||
|
def test_matches_beschlussempfehlung(self):
|
||||||
|
m = RESULT_ANCHOR_RE.search(
|
||||||
|
"Damit sind die Beschlussempfehlung mit Enthaltungen angenommen worden."
|
||||||
|
)
|
||||||
|
assert m and m.group("subject") == "Beschlussempfehlung"
|
||||||
|
|
||||||
|
def test_no_match_random(self):
|
||||||
|
m = RESULT_ANCHOR_RE.search("Der Antrag wurde abgelehnt.")
|
||||||
|
assert m is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestDrucksacheRegex:
|
||||||
|
def test_matches_drucksache_8(self):
|
||||||
|
m = DS_RE_BB.search("Drucksache 8/2054")
|
||||||
|
assert m and m.group(1) == "2054"
|
||||||
|
|
||||||
|
def test_only_wp8_matches(self):
|
||||||
|
# Drucksache 7/123 sollte nicht matchen (anderer WP)
|
||||||
|
assert DS_RE_BB.search("Drucksache 7/123") is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestResolveDrucksacheBb:
|
||||||
|
def test_finds_drucksache_before_anchor(self):
|
||||||
|
text = "Drucksache 8/2054 ... Damit ist der Antrag abgelehnt."
|
||||||
|
anchor = text.index("Damit")
|
||||||
|
assert _resolve_drucksache_bb(text, anchor) == "8/2054"
|
||||||
|
|
||||||
|
def test_picks_most_recent_drucksache(self):
|
||||||
|
text = "Drucksache 8/1000 ... Drucksache 8/2000 ... Damit ist abgelehnt."
|
||||||
|
anchor = text.index("Damit")
|
||||||
|
assert _resolve_drucksache_bb(text, anchor) == "8/2000"
|
||||||
|
|
||||||
|
def test_returns_none_when_no_ds(self):
|
||||||
|
assert _resolve_drucksache_bb("Damit ist abgelehnt.", 0) is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestConstants:
|
||||||
|
def test_all_fraktionen_set(self):
|
||||||
|
# WP8-BB Konstellation: SPD-BSW Koalition + AfD/CDU/GRÜNE Opposition
|
||||||
|
assert set(ALLE_FRAKTIONEN_BB) == {"SPD", "AfD", "CDU", "BSW", "GRÜNE"}
|
||||||
@ -20,8 +20,8 @@ import pytest
|
|||||||
from app.protokoll_parsers import PROTOKOLL_PARSERS, supported_bundeslaender
|
from app.protokoll_parsers import PROTOKOLL_PARSERS, supported_bundeslaender
|
||||||
|
|
||||||
STUB_BL_CODES = [
|
STUB_BL_CODES = [
|
||||||
# BUND/BE/HH/TH/HE/SH/HB/SL raus, weil seit 2026-04-28/29 produktive Parser
|
# BUND/BE/HH/TH/HE/SH/HB/SL/BB raus, weil seit 2026-04-28/29 produktive Parser
|
||||||
"BB", "BW", "BY",
|
"BW", "BY",
|
||||||
"LSA", "MV", "NI", "RP", "SN",
|
"LSA", "MV", "NI", "RP", "SN",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -76,8 +76,10 @@ class TestRegistryDiscipline:
|
|||||||
|
|
||||||
def test_stubs_not_in_registry(self):
|
def test_stubs_not_in_registry(self):
|
||||||
registered = set(supported_bundeslaender())
|
registered = set(supported_bundeslaender())
|
||||||
# Aktuell: NRW + BUND + BE + HH + TH + HE + SH + HB + SL produktiv
|
# Aktuell: NRW + BUND + BE + HH + TH + HE + SH + HB + SL + BB produktiv
|
||||||
assert registered == {"NRW", "BUND", "BE", "HH", "TH", "HE", "SH", "HB", "SL"}, (
|
assert registered == {
|
||||||
|
"NRW", "BUND", "BE", "HH", "TH", "HE", "SH", "HB", "SL", "BB",
|
||||||
|
}, (
|
||||||
"Unerwartete Registry-Eintraege. Wenn neue BL implementiert sind, "
|
"Unerwartete Registry-Eintraege. Wenn neue BL implementiert sind, "
|
||||||
"diesen Test anpassen UND den Stub durch echten Parser ersetzen."
|
"diesen Test anpassen UND den Stub durch echten Parser ersetzen."
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user