diff --git a/app/report.py b/app/report.py index 72a3587..b8d3570 100644 --- a/app/report.py +++ b/app/report.py @@ -14,11 +14,21 @@ from html import escape as _e from pathlib import Path from typing import Optional +from jinja2 import Environment, FileSystemLoader, select_autoescape + logger = logging.getLogger(__name__) from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG from .bundeslaender import BUNDESLAENDER +# Eigene Jinja-Env fuer PDF-Templates (separat von Starlette templates, +# weil report.py auch von Hintergrund-Jobs ohne FastAPI-Request laufen muss). +_TEMPLATE_DIR = Path(__file__).parent / "templates" +_pdf_jinja = Environment( + loader=FileSystemLoader(str(_TEMPLATE_DIR)), + autoescape=select_autoescape(["html"]), +) + # ECOnGOOD Colors COLORS = { "darkgray": "#5a5a5a", @@ -512,6 +522,90 @@ async def generate_html_report( output_path.write_text(html) +async def generate_html_report_v3( + assessment: Assessment, + output_path: Path, + bundesland: Optional[str] = None, +) -> None: + """Render Antrags-PDF im neuen v3-Layout (single column, A4 portrait). + + Reuses die v3-Layout-Logik (Score-Hero, Matrix mit Achsen-Labels, + Programm-Treue, Verbesserungen) und ergaenzt sie um die im PDF + notwendigen Adaptionen: + + - Kein interaktiver Matrix-Klick → "Schwerpunkte erklaert"-Sektion + listet die Top-3 positiven und Top-3 negativen Felder mit ihren + LLM-generierten label/aspect-Texten unter der Matrix. + - Plenum-Votes werden best-effort geladen, inkl. Konsistenz-Hinweis + (Mehrheit deckt sich / gegen GWOE-Empfehlung). + - Online-Elemente (Share, Vote-treffend, Kommentare, News, Modals) + sind im Template gar nicht erst angelegt. + + Template: app/templates/v3/pdf/antrag_pdf.html + """ + matrix_lookup = {e.field: {"rating": e.rating} for e in assessment.gwoe_matrix} + + # Schwerpunkt-Felder mit Erklaerung: Top + Bottom Ratings. + sorted_matrix = sorted( + assessment.gwoe_matrix, key=lambda e: e.rating, reverse=True + ) + matrix_top = [ + {"field": e.field, "label": e.label, "aspect": e.aspect, "rating": e.rating} + for e in sorted_matrix if e.rating > 0 + ][:4] + matrix_bottom = [ + {"field": e.field, "label": e.label, "aspect": e.aspect, "rating": e.rating} + for e in sorted(sorted_matrix, key=lambda e: e.rating) if e.rating < 0 + ][:4] + + # Score-Color (gleich wie Scorecard) + s = assessment.gwoe_score + if s >= 8: score_color = "#1a7f37" + elif s >= 5: score_color = "#bf6c10" + else: score_color = "#9a2a2a" + + parlament_name = "" + if bundesland and bundesland in BUNDESLAENDER: + parlament_name = BUNDESLAENDER[bundesland].parlament_name + + # Plenum-Votes best-effort (Hintergrund-Job kann ohne DB-Pfad laufen, + # in dem Fall einfach keine Votes anzeigen). + plenum_votes: list[dict] = [] + konsistenz_state: Optional[str] = None + konsistenz_decisive: Optional[str] = None + try: + from .database import get_plenum_votes + plenum_votes = await get_plenum_votes( + bundesland or "NRW", assessment.drucksache, + ) + if plenum_votes: + from .marker import consistency_state, decisive_outcome + konsistenz_state = consistency_state( + assessment.empfehlung.value, plenum_votes, + ) + konsistenz_decisive = decisive_outcome(plenum_votes) + except Exception as exc: + logger.warning( + "Plenum-Votes fuer PDF nicht ladbar (drucksache=%s): %s", + assessment.drucksache, exc, + ) + + template = _pdf_jinja.get_template("v3/pdf/antrag_pdf.html") + html = template.render( + assessment=assessment, + matrix_lookup=matrix_lookup, + matrix_top=matrix_top, + matrix_bottom=matrix_bottom, + score_color=score_color, + parlament_name=parlament_name, + bundesland=bundesland or "", + plenum_votes=plenum_votes, + konsistenz_state=konsistenz_state, + konsistenz_decisive=konsistenz_decisive, + ) + output_path.write_text(html) + + async def generate_pdf_report( assessment: Assessment, output_path: Path, @@ -535,9 +629,10 @@ async def generate_pdf_report( ``bundesland`` is forwarded to ``generate_html_report`` so the source parlament name appears in the report header. """ - # Step 1 — render the report itself + # Step 1 — render the report itself, neues v3-Layout (single column, + # Score-Hero, Matrix mit Achsen-Labels, Schwerpunkte-erklaert). html_path = output_path.with_suffix('.tmp.html') - await generate_html_report(assessment, html_path, bundesland=bundesland) + await generate_html_report_v3(assessment, html_path, bundesland=bundesland) try: from weasyprint import HTML diff --git a/app/templates/v2/screens/admin_stand.html b/app/templates/v2/screens/admin_stand.html index 3a908fd..c3fcb98 100644 --- a/app/templates/v2/screens/admin_stand.html +++ b/app/templates/v2/screens/admin_stand.html @@ -180,6 +180,16 @@

Lade aktuelles Beispiel …

+ +

Design-Werkstätten

+

+ Live-Editoren für Layout-Iteration ohne Server-Redeploy: +

+ {% endblock %} diff --git a/app/templates/v2/screens/antrag_detail.html b/app/templates/v2/screens/antrag_detail.html index bd6138a..ec0a649 100644 --- a/app/templates/v2/screens/antrag_detail.html +++ b/app/templates/v2/screens/antrag_detail.html @@ -484,8 +484,9 @@ - {# ── Share-Block (analog v1) ───────────────────────────────────── #} -
+ {# ── Share-Block (analog v1) — nur fuer angemeldete User sichtbar, + wird in initAuth() ein-/ausgeblendet (display:none default). ── #} + - {# Teilen #} -
+ {# Teilen — nur fuer angemeldete User; initAuth() blendet via #v2-share-block ein. #} +