"""Wahlprogramm-Referenzsystem mit Zitaten und Seitenreferenzen.""" import json import re from pathlib import Path from typing import Optional # Wahlprogramm-Metadaten WAHLPROGRAMME = { "CDU": { "file": "cdu-nrw-2022.pdf", "titel": "Machen, worauf es ankommt", "partei": "CDU NRW", "jahr": 2022, "seiten": 109, }, "SPD": { "file": "spd-nrw-2022.pdf", "titel": "Unser Land von morgen", "partei": "SPD NRW", "jahr": 2022, "seiten": 116, }, "GRÜNE": { "file": "gruene-nrw-2022.pdf", "titel": "Von hier an Zukunft", "partei": "BÜNDNIS 90/DIE GRÜNEN NRW", "jahr": 2022, "seiten": 100, }, "FDP": { "file": "fdp-nrw-2022.pdf", "titel": "Nie gab es mehr zu tun", "partei": "FDP NRW", "jahr": 2022, "seiten": 96, }, "AfD": { "file": "afd-nrw-2022.pdf", "titel": "Wer sonst.", "partei": "AfD NRW", "jahr": 2022, "seiten": 68, }, } # Basis-Pfad für Referenzdokumente REFERENZEN_PATH = Path(__file__).parent / "static" / "referenzen" KONTEXT_PATH = Path(__file__).parent / "kontext" def load_wahlprogramm_text(partei: str) -> dict[int, str]: """Lädt Wahlprogramm-Text mit Seitenzuordnung. Returns: Dict mit Seitennummer -> Text """ if partei not in WAHLPROGRAMME: return {} # Versuche paged-Textdatei zu laden paged_file = KONTEXT_PATH / f"{WAHLPROGRAMME[partei]['file'].replace('.pdf', '-paged.txt')}" if not paged_file.exists(): # Fallback: Normale Textdatei txt_file = KONTEXT_PATH / f"{WAHLPROGRAMME[partei]['file'].replace('.pdf', '.txt')}" if txt_file.exists(): return {1: txt_file.read_text()} return {} text = paged_file.read_text() pages = {} current_page = 1 current_text = [] for line in text.split('\n'): if line.startswith('--- PAGE '): # Speichere vorherige Seite if current_text: pages[current_page] = '\n'.join(current_text) # Extrahiere neue Seitenzahl match = re.search(r'PAGE (\d+)', line) if match: current_page = int(match.group(1)) current_text = [] else: current_text.append(line) # Letzte Seite speichern if current_text: pages[current_page] = '\n'.join(current_text) return pages def search_wahlprogramm(partei: str, keywords: list[str], max_results: int = 3) -> list[dict]: """Sucht relevante Passagen in einem Wahlprogramm. Args: partei: Partei-Kürzel (CDU, SPD, GRÜNE, FDP, AfD) keywords: Suchbegriffe max_results: Maximale Anzahl Ergebnisse Returns: Liste von {seite, text, score, url} """ pages = load_wahlprogramm_text(partei) if not pages: return [] results = [] keywords_lower = [k.lower() for k in keywords] for page_num, text in pages.items(): text_lower = text.lower() # Zähle Keyword-Treffer score = sum(1 for kw in keywords_lower if kw in text_lower) if score > 0: # Finde relevante Absätze (mit Keyword) paragraphs = text.split('\n\n') relevant_paragraphs = [] for para in paragraphs: para_clean = para.strip() if len(para_clean) < 50: continue para_lower = para_clean.lower() if any(kw in para_lower for kw in keywords_lower): relevant_paragraphs.append(para_clean) if relevant_paragraphs: # Nimm den relevantesten Absatz (mit meisten Keywords) best_para = max(relevant_paragraphs, key=lambda p: sum(1 for kw in keywords_lower if kw in p.lower())) # Kürze auf ~300 Zeichen if len(best_para) > 300: best_para = best_para[:297] + "..." results.append({ "partei": partei, "seite": page_num, "text": best_para, "score": score, "url": f"/static/referenzen/{WAHLPROGRAMME[partei]['file']}#page={page_num}", "quelle": f"{WAHLPROGRAMME[partei]['partei']} Wahlprogramm {WAHLPROGRAMME[partei]['jahr']}, S. {page_num}" }) # Sortiere nach Score, nimm Top-Ergebnisse results.sort(key=lambda x: x['score'], reverse=True) return results[:max_results] def find_relevant_quotes(antrag_text: str, fraktionen: list[str]) -> dict[str, list[dict]]: """Findet relevante Zitate aus Wahlprogrammen für einen Antrag. Args: antrag_text: Volltext des Antrags fraktionen: Liste der Fraktionen (Antragsteller + Regierung) Returns: Dict mit Partei -> Liste von Zitaten """ # Extrahiere Keywords aus Antrag (einfache Heuristik) # Entferne Stoppwörter und kurze Wörter stopwords = {'der', 'die', 'das', 'und', 'oder', 'für', 'mit', 'von', 'zu', 'auf', 'ist', 'sind', 'wird', 'werden', 'hat', 'haben', 'ein', 'eine', 'einer', 'den', 'dem', 'des', 'im', 'in', 'an', 'bei', 'nach', 'über', 'unter', 'durch', 'als', 'auch', 'nur', 'noch', 'aber', 'wenn', 'dass', 'sich', 'nicht', 'wie', 'so', 'aus', 'zum', 'zur', 'vom', 'beim', 'seit', 'bis'} words = re.findall(r'\b[A-Za-zäöüÄÖÜß]{4,}\b', antrag_text) keywords = [w for w in words if w.lower() not in stopwords] # Zähle Worthäufigkeit word_freq = {} for w in keywords: w_lower = w.lower() word_freq[w_lower] = word_freq.get(w_lower, 0) + 1 # Top-Keywords (häufigste) top_keywords = sorted(word_freq.keys(), key=lambda x: word_freq[x], reverse=True)[:15] # Suche in relevanten Wahlprogrammen quotes = {} # Immer Regierungsfraktionen einbeziehen parteien_to_search = set(fraktionen) | {"CDU", "GRÜNE"} for partei in parteien_to_search: if partei in WAHLPROGRAMME: found = search_wahlprogramm(partei, top_keywords, max_results=2) if found: quotes[partei] = found return quotes def format_quote_for_prompt(quotes: dict[str, list[dict]]) -> str: """Formatiert Zitate für den LLM-Prompt.""" if not quotes: return "" lines = ["\n## Relevante Passagen aus Wahlprogrammen\n"] lines.append("Nutze diese Originalzitate als Belege in deiner Bewertung:\n") for partei, zitate in quotes.items(): for z in zitate: lines.append(f"### {z['quelle']}") lines.append(f'> "{z["text"]}"') lines.append("") return "\n".join(lines)