"""Report generation for HTML and PDF output.""" import subprocess from pathlib import Path from typing import Optional from jinja2 import Environment, FileSystemLoader from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG from .bundeslaender import BUNDESLAENDER # ECOnGOOD Colors COLORS = { "darkgray": "#5a5a5a", "green": "#889e33", "blue": "#009da5", "lightgray": "#bfbfbf", "orange": "#F7941D", "red": "#d00000", } def get_score_color(score: float) -> str: """Get color for a score value.""" if score >= 7: return COLORS["blue"] if score >= 4: return COLORS["green"] if score >= 2: return "#FFC20E" if score >= 1: return COLORS["orange"] return COLORS["red"] def get_rating_symbol(rating: int) -> str: """Convert numeric rating to symbol.""" if rating >= 2: return "++" if rating == 1: return "+" if rating == 0: return "○" if rating == -1: return "−" return "−−" def format_redline_html(text: str) -> str: """Convert redline markup to HTML.""" import re # **text** → green bold (inserted) text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) # ~~text~~ → red strikethrough (deleted) text = re.sub(r'~~([^~]+)~~', r'\1', text) return text def build_matrix_html(assessment: Assessment) -> str: """Build HTML matrix table.""" rating_map = {e.field: e for e in assessment.gwoe_matrix} rows = ["A", "B", "C", "D", "E"] row_labels = { "A": "Lieferant:innen", "B": "Finanzen", "C": "Führung/Verwaltung", "D": "Bürger:innen", "E": "Gesellschaft/Natur", } html = [''] html.append('') html.append('') for col in range(1, 6): html.append(f'') html.append('') html.append('') for row in rows: html.append(f'') for col in range(1, 6): field = f"{row}{col}" entry = rating_map.get(field) if entry: symbol = get_rating_symbol(entry.rating) css_class = "positive" if entry.rating > 0 else ("negative" if entry.rating < 0 else "neutral") html.append(f'') else: html.append('') html.append('') html.append('
{col}
{row}: {row_labels[row]}{symbol}
') return '\n'.join(html) async def generate_html_report( assessment: Assessment, output_path: Path, bundesland: Optional[str] = None, ) -> None: """Generate HTML report. ``bundesland`` is the optional state code (e.g. ``"NRW"``, ``"LSA"``). When set and known in ``BUNDESLAENDER``, the resulting report carries the parlament name in its header so the source parliament is always visible — important since assessments from multiple bundesländer share the same Drucksachen-ID space. """ empf_config = EMPFEHLUNG_CONFIG.get(assessment.empfehlung.value, {}) parlament_name = "" if bundesland and bundesland in BUNDESLAENDER: parlament_name = BUNDESLAENDER[bundesland].parlament_name html = f""" GWÖ-Antragsprüfung: {assessment.title}
GEMEINWOHL-ÖKONOMIE | ANTRAGSBEWERTUNG

{assessment.title}

{f'
{parlament_name}
' if parlament_name else ''}
Drucksache: {assessment.drucksache}  |  Datum: {assessment.datum}  |  Fraktion(en): {', '.join(assessment.fraktionen)}  |  GWÖ-Score: {assessment.gwoe_score}/10
{empf_config.get('symbol', '[?]')} Empfehlung: {assessment.empfehlung.value}

Der Antrag im Überblick

{assessment.antrag_zusammenfassung or 'Keine Zusammenfassung verfügbar.'}

{('') if assessment.antrag_kernpunkte else ''}

GWÖ-Treue

Score: {assessment.gwoe_score}/10

Begründung: {assessment.gwoe_begruendung}

Schwerpunkte: {', '.join(assessment.gwoe_schwerpunkt)}

Matrix-Zuordnung (Matrix 2.0 für Gemeinden)

{build_matrix_html(assessment)}

Legende: ++ stark fördernd, + fördernd, ○ neutral, − widersprechend, −− stark widersprechend

Berührte Themenfelder

Programmtreue

{''.join(f'''

{s.fraktion} {' (Antragsteller)' if s.ist_antragsteller else ''}{' (Regierung)' if s.ist_regierung else ''}

Wahlprogramm: {s.wahlprogramm.score}/10 — {s.wahlprogramm.begruendung}

Parteiprogramm: {s.parteiprogramm.score}/10 — {s.parteiprogramm.begruendung}

''' for s in assessment.wahlprogramm_scores)}

Verbesserungsvorschläge

{''.join(f'''
Original:
{v.original}
Vorschlag:
{format_redline_html(v.vorschlag)}
{v.begruendung}
''' for v in assessment.verbesserungen) or '

Keine Verbesserungsvorschläge.

'}

Zusammenfassung

Stärken

Schwächen

""" output_path.write_text(html) async def generate_pdf_report( assessment: Assessment, output_path: Path, bundesland: Optional[str] = None, ) -> None: """Generate PDF report using WeasyPrint. ``bundesland`` is forwarded to ``generate_html_report`` so the source parlament name appears in the report header. """ # First generate HTML html_path = output_path.with_suffix('.tmp.html') await generate_html_report(assessment, html_path, bundesland=bundesland) try: from weasyprint import HTML HTML(filename=str(html_path)).write_pdf(str(output_path)) finally: html_path.unlink(missing_ok=True)