diff --git a/app/main.py b/app/main.py index 349357a..6b1a1b3 100644 --- a/app/main.py +++ b/app/main.py @@ -3363,6 +3363,106 @@ async def og_template(request: Request, drucksache: str = ""): }) +@app.get("/v2/scorecard") +async def scorecard_template( + request: Request, drucksache: str, bundesland: str = "NRW", + format: str = "og", +): + """Internes Render-Template für Scorecards (#179). + + `format=og` → 1200×630 (LinkedIn/Twitter-OG) + `format=square` → 1080×1080 (Instagram) + """ + from .config import settings as _settings + drucksache = validate_drucksache(drucksache) + row = await get_assessment(drucksache) + if not row: + raise HTTPException(status_code=404, detail="Antrag nicht gefunden") + + from .models import Assessment + assessment = Assessment.model_validate(row) + matrix_lookup = {e.field: {"rating": e.rating} for e in assessment.gwoe_matrix} + + fraktionen = list(row.get("fraktionen", []) or []) + if isinstance(fraktionen, str): + try: + import json as _json + fraktionen = _json.loads(fraktionen) or [] + except Exception: + fraktionen = [] + + score = assessment.gwoe_score + if score >= 8: score_color = "#1a7f37" + elif score >= 5: score_color = "#bf6c10" + elif score >= 3: score_color = "#9a2a2a" + else: score_color = "#9a2a2a" + + dimensions = {"og": (1200, 630), "square": (1080, 1080)} + width, height = dimensions.get(format, dimensions["og"]) + + return templates.TemplateResponse("v2/screens/scorecard.html", { + "request": request, + "assessment": assessment, + "bundesland": bundesland, + "matrix_lookup": matrix_lookup, + "fraktionen": fraktionen[:4], + "datum": (row.get("datum") or "")[:10], + "score_color": score_color, + "width": width, + "height": height, + }) + + +@app.get("/api/assessment/scorecard.png") +async def api_scorecard_png( + drucksache: str, bundesland: str = "NRW", format: str = "og", +): + """Liefert die Scorecard als PNG via Playwright-Render (#179). + + `format=og` → 1200×630, `format=square` → 1080×1080. + """ + drucksache = validate_drucksache(drucksache) + row = await get_assessment(drucksache) + if not row: + raise HTTPException(status_code=404, detail="Antrag nicht gefunden") + + if format not in ("og", "square"): + raise HTTPException(status_code=400, detail="format muss 'og' oder 'square' sein") + width, height = ((1200, 630) if format == "og" else (1080, 1080)) + + try: + from playwright.sync_api import sync_playwright + import urllib.parse as _up + url = ( + f"http://127.0.0.1:{settings.port}/v2/scorecard" + f"?drucksache={_up.quote(drucksache, safe='')}" + f"&bundesland={_up.quote(bundesland)}" + f"&format={format}" + ) + with sync_playwright() as pw: + browser = pw.chromium.launch(args=["--no-sandbox"]) + page = browser.new_page(viewport={"width": width, "height": height}) + page.goto(url, wait_until="networkidle", timeout=15000) + png = page.screenshot( + clip={"x": 0, "y": 0, "width": width, "height": height}, type="png", + ) + browser.close() + except Exception as e: + logger.exception("Scorecard-Render fehlgeschlagen") + raise HTTPException(status_code=500, detail=f"Render-Fehler: {e}") + + safe_name = drucksache.replace("/", "-") + return Response( + content=png, media_type="image/png", + headers={ + "Content-Disposition": ( + f'inline; filename="scorecard-{safe_name}-{format}.png"' + ), + "Cache-Control": "public, max-age=600", + }, + ) + + @app.get("/api/og/{drucksache_encoded}.png") async def api_og_card(drucksache_encoded: str, request: Request): """Liefert die Open-Graph-PNG-Karte für einen Antrag (#141). diff --git a/app/templates/v2/screens/scorecard.html b/app/templates/v2/screens/scorecard.html new file mode 100644 index 0000000..3d0e08e --- /dev/null +++ b/app/templates/v2/screens/scorecard.html @@ -0,0 +1,191 @@ + + + + +Scorecard — {{ assessment.title }} + + + +
+
+
GWÖ-Bewertung · {{ bundesland }} · {{ assessment.drucksache }}
+
{{ datum }}
+
+
+
+

{{ assessment.title|truncate(100, end="…") }}

+ {% if fraktionen %} +
+ {% for f in fraktionen %}{{ f }}{% endfor %} +
+ {% endif %} +
{{ assessment.empfehlung.value }}
+
{{ assessment.gwoe_begruendung|truncate(420, end="…") }}
+
+
+
{{ "%.1f"|format(assessment.gwoe_score) }}
+
GWÖ-Score · 0–10
+
+ {% for r in ['A','B','C','D','E'] %} + {% for c in ['1','2','3','4','5'] %} + {% set cell = matrix_lookup.get(r ~ c, {}) %} + {% set rt = cell.get('rating', 0) %} +
+ {% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}−−{% else %}−{% endif %} +
+ {% endfor %} + {% endfor %} +
+
+
+ +
+ +