"""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'{col} | ')
html.append('
')
html.append('')
for row in rows:
html.append(f'| {row}: {row_labels[row]} | ')
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'{symbol} | ')
else:
html.append(' | ')
html.append('
')
html.append('
')
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}
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.'}
{('' + ''.join(f'- {k}
' for k in assessment.antrag_kernpunkte) + '
') 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
{''.join(f'- {e.field}: {e.aspect} [{get_rating_symbol(e.rating)}]
' for e in assessment.gwoe_matrix)}
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
{''.join(f'- {s}
' for s in assessment.staerken) or '- (keine)
'}
Schwächen
{''.join(f'- {s}
' for s in assessment.schwaechen) or '- (keine)
'}
"""
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)