Fix: NRW-Titel + Regierungsfraktionen-Pflicht im LLM-Prompt
Bug 1 — NRW-Titel "Drucksache XX/YYYYY": NRW's get_document machte nur HEAD-Request auf die PDF-URL und gab title="Drucksache 18/18085" zurück — keinen echten Titel. Fix: nutzt jetzt search(drucksache) um den echten Eintrag von OPAL zu holen. Fallback: leerer Titel statt generischer, damit der LLM-Titel nicht überschrieben wird. Plus _pick_best_title Helper: doc.title nur übernehmen wenn es ein echter Titel ist (nicht "Drucksache XX"). Bug 2 — Nur Antragsteller im Passungsprofil, keine Regierungsfraktionen: Der LLM ignorierte die "UND Regierungsfraktionen"-Anweisung im Prompt. Fix: explizite PFLICHT-FRAKTIONEN-Zeile im User-Prompt: "Du MUSST folgende Fraktionen in wahlprogrammScores bewerten: SPD, CDU, GRÜNE" (dedupliziert aus fraktionen + regierungsfraktionen). Tests: 194/194 grün. Batch-Re-Analyse muss nochmal laufen mit den Fixes (21 bereits fertig, 15 noch offen — werden alle erneut benötigt weil die Titel/Fraktionen in den neuen Assessments falsch sind).
This commit is contained in:
parent
303b30f6dd
commit
038ebd6447
@ -282,10 +282,13 @@ async def analyze_antrag(text: str, bundesland: str = "NRW", model: str = "qwen-
|
||||
{text}
|
||||
</antrag>
|
||||
|
||||
**PFLICHT-FRAKTIONEN:** Du MUSST folgende Fraktionen in `wahlprogrammScores` bewerten:
|
||||
{', '.join(dict.fromkeys(fraktionen + BUNDESLAENDER[bundesland].regierungsfraktionen))}
|
||||
|
||||
Bewerte nach GWÖ-Matrix 2.0 für Gemeinden:
|
||||
1. GWÖ-Treue (0-10) mit Matrix-Zuordnung und Symbolen (++/+/○/−/−−)
|
||||
2. Wahlprogrammtreue der einreichenden Fraktion(en) UND Regierungsfraktionen (0-10)
|
||||
3. Parteiprogrammtreue der einreichenden Fraktion(en) UND Regierungsfraktionen (0-10)
|
||||
2. Wahlprogrammtreue JEDER der oben genannten Pflicht-Fraktionen (0-10)
|
||||
3. Parteiprogrammtreue JEDER der oben genannten Pflicht-Fraktionen (0-10)
|
||||
4. Bis zu 3 Verbesserungsvorschläge in Redline-Syntax
|
||||
5. Themen-Tags für Kategorisierung
|
||||
|
||||
|
||||
25
app/main.py
25
app/main.py
@ -40,6 +40,25 @@ from .parlamente import get_adapter, ADAPTERS
|
||||
from .bundeslaender import alle_bundeslaender
|
||||
from .analyzer import analyze_antrag
|
||||
from .auth import get_current_user, require_auth, keycloak_login_url, _is_auth_enabled
|
||||
|
||||
|
||||
def _pick_best_title(llm_title: str, doc_title: Optional[str], drucksache: str) -> str:
|
||||
"""Wähle den besten Titel aus LLM-Output und Adapter-Metadata.
|
||||
|
||||
Priorität:
|
||||
1. doc_title, wenn ein echter Titel (nicht "Drucksache XX")
|
||||
2. llm_title, wenn nicht leer und nicht generisch
|
||||
3. Generischer Fallback "Drucksache XX"
|
||||
"""
|
||||
generic_prefix = f"Drucksache {drucksache.split('/')[0]}"
|
||||
# doc_title gut? (nicht generisch, nicht leer)
|
||||
if doc_title and not doc_title.startswith("Drucksache ") and len(doc_title) > 5:
|
||||
return doc_title
|
||||
# LLM-Titel gut? (nicht generisch)
|
||||
if llm_title and not llm_title.startswith("Drucksache ") and len(llm_title) > 5:
|
||||
return llm_title
|
||||
# doc_title als Fallback (auch wenn generisch)
|
||||
return doc_title or llm_title or f"Drucksache {drucksache}"
|
||||
from .report import generate_html_report, generate_pdf_report
|
||||
from .embeddings import (
|
||||
init_embeddings_db, get_programme_info, get_indexing_status,
|
||||
@ -536,7 +555,11 @@ async def run_drucksache_analysis(
|
||||
# Prepare data for DB
|
||||
assessment_data = {
|
||||
"drucksache": drucksache,
|
||||
"title": assessment.title or (doc.title if doc else f"Drucksache {drucksache}"),
|
||||
# Titel-Priorität: LLM-generierter Titel > doc.title,
|
||||
# ABER nur wenn doc.title ein echter Titel ist (nicht "Drucksache XX",
|
||||
# wie NRW's get_document zurückgibt). Sonst überschreibt der
|
||||
# generische doc.title den echten LLM-Titel.
|
||||
"title": _pick_best_title(assessment.title, doc.title if doc else None, drucksache),
|
||||
"fraktionen": assessment.fraktionen,
|
||||
"datum": assessment.datum or (doc.datum if doc else ""),
|
||||
"link": doc.link if doc else "",
|
||||
|
||||
@ -255,23 +255,31 @@ class NRWAdapter(ParlamentAdapter):
|
||||
return results
|
||||
|
||||
async def get_document(self, drucksache: str) -> Optional[Drucksache]:
|
||||
"""Get document metadata by drucksache ID (e.g. '18/8125')."""
|
||||
# Parse legislatur and number
|
||||
"""Get document metadata by drucksache ID (e.g. '18/8125').
|
||||
|
||||
Nutzt ``search(drucksache)`` um den echten Titel und die
|
||||
Fraktionen von OPAL zu bekommen. Fallback auf generischen
|
||||
PDF-Link wenn die Suche nichts findet.
|
||||
"""
|
||||
# Versuch 1: über die Suche den echten Eintrag finden
|
||||
results = await self.search(drucksache, limit=10)
|
||||
for doc in results:
|
||||
if doc.drucksache == drucksache:
|
||||
return doc
|
||||
|
||||
# Fallback: PDF-Link konstruieren ohne Titel/Fraktionen
|
||||
match = re.match(r"(\d+)/(\d+)", drucksache)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
legislatur, nummer = match.groups()
|
||||
pdf_url = f"https://www.landtag.nrw.de/portal/WWW/dokumentenarchiv/Dokument/MMD{legislatur}-{nummer}.pdf"
|
||||
|
||||
# Try to fetch and extract basic info
|
||||
async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client:
|
||||
try:
|
||||
resp = await client.head(pdf_url)
|
||||
if resp.status_code == 200:
|
||||
return Drucksache(
|
||||
drucksache=drucksache,
|
||||
title=f"Drucksache {drucksache}",
|
||||
title="", # Leer statt generisch, damit LLM-Titel nicht überschrieben wird
|
||||
fraktionen=[],
|
||||
datum="",
|
||||
link=pdf_url,
|
||||
@ -279,7 +287,6 @@ class NRWAdapter(ParlamentAdapter):
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
async def download_text(self, drucksache: str) -> Optional[str]:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user