gwoe-antragspruefer/app/templates/v3/pdf/antrag_pdf.html
Dotty Dotter 79e7937d51 feat: PDF-Generierung auf v3-Layout, Werkstatt-Link im Admin, Share nur bei Login
Drei Aufgaben in einem Schwung:

1. Werkstatt-Link im Admin
   admin_stand bekommt eine Sektion 'Design-Werkstaetten' mit Link auf
   /v2/scorecard-werkstatt — damit Admins den Live-Editor finden ohne
   die URL kennen zu muessen.

2. Share-Block nur fuer angemeldete User
   Der ganze Share-Block (Kopieren, Threads, Mastodon, LinkedIn,
   Instagram, E-Mail, Scorecard, Stock-Bild) bekommt id=v2-share-block
   und wird per initAuth() display:none/block geschaltet — analog zum
   Comment-Form. Default im Markup: display:none, damit Gaeste ihn
   nicht waehrend des Auth-Roundtrips kurz sehen. Funktioniert in v2
   und v3 (gleicher JS-Handler via super-Inheritance).

3. PDF-Layout = v3-Layout
   Neues Template v3/pdf/antrag_pdf.html (single column, A4) reused die
   Visuallogik aus der Online-Detailseite:
   - Score-Hero-Block mit Farb-Tint
   - Matrix 5×5 mit Achsen-Labels (Werte oben, Berührungsgruppen links)
   - Programm-Treue pro Fraktion mit Begruendung + Zitaten + Fallback-
     Hinweis bei fehlenden Zitaten
   - Verbesserungsvorschlaege mit Redline-Format
   - Abstimmungsergebnis (best-effort via get_plenum_votes) inkl.
     Konsistenz-Hinweis

   Online-spezifisches gestrichen: Merken-Button, Vote-treffend, Share,
   Kommentare, News-Box, Reanalyze, Historie, Modals.

   NEU im PDF: 'Schwerpunkte erklaert'-Sektion direkt unter der Matrix.
   Listet die Top-4 positiven und Top-4 negativen Matrix-Felder mit
   ihrem LLM-generierten label + aspect — Ersatz fuer den interaktiven
   Klick, der im PDF nicht funktioniert.

   report.generate_html_report_v3() neu, generate_pdf_report() ruft
   diese statt der alten Inline-HTML-Variante. Alte generate_html_report
   bleibt als Fallback erhalten.

   WeasyPrint rendert mit @page A4, Footer mit Drucksache + Branding +
   Seitenzahl 'Seite X von Y'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:48:19 +02:00

757 lines
24 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{# ─────────────────────────────────────────────────────────────────────
v3/pdf/antrag_pdf.html — GWÖ-Antragsprüfung als druckbares PDF.
Wiederverwendet die v3-Layout-Logik (Single Column, Sektions-Reihen-
folge), kappt aber alle Online-Elemente:
raus: Merken, Vote-treffend, Share-Block, News-Box, Kommentare,
Modals, Reanalyze, Historie, Cluster
drin: Header, Titel, Themen, Zusammenfassung+Kernpunkte, Bewertung,
Stärken/Schwächen, Matrix mit Achsen-Labels, NEU: Erklärung
der Schwerpunkt-Felder, Programm-Treue, Verbesserungen,
Abstimmungsergebnis.
Wird von report.py via Jinja gerendert; WeasyPrint produziert A4 PDF.
───────────────────────────────────────────────────────────────────── #}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>GWÖ-Antragsprüfung — {{ assessment.title }}</title>
<style>
@page {
size: A4;
margin: 18mm 16mm 22mm 16mm;
@bottom-left {
content: "{{ assessment.drucksache }}";
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
color: #888;
}
@bottom-center {
content: "gwoe.toppyr.de · GWÖ-Antragsprüfer · automatische Gemeinwohl-Bilanzierung";
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
color: #888;
}
@bottom-right {
content: "Seite " counter(page) " von " counter(pages);
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
color: #888;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { font-family: 'Avenir Next', 'Avenir', 'Helvetica Neue', Arial, sans-serif; }
body {
color: #1f1f1f;
font-size: 10pt;
line-height: 1.5;
}
/* ── Header ── */
.pdf-header {
border-bottom: 2px solid #009DA5;
padding-bottom: 8pt;
margin-bottom: 12pt;
}
.pdf-kicker {
font-family: 'Source Code Pro', monospace;
font-size: 8.5pt;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #009DA5;
margin-bottom: 4pt;
}
.pdf-title {
font-size: 18pt;
font-weight: 700;
line-height: 1.18;
color: #1f1f1f;
margin: 0 0 6pt;
}
.pdf-meta {
font-family: 'Source Code Pro', monospace;
font-size: 9pt;
color: #555;
line-height: 1.5;
}
.pdf-meta .pill {
display: inline-block;
padding: 1pt 6pt;
background: rgba(136, 158, 51, 0.18);
color: #44570a;
border-radius: 2pt;
font-weight: 700;
margin-left: 4pt;
}
.pdf-themen {
margin-top: 6pt;
}
.pdf-theme-chip {
display: inline-block;
padding: 1pt 6pt;
border: 0.5pt solid #cccfb8;
border-radius: 2pt;
background: #fff;
color: #444;
font-family: 'Source Code Pro', monospace;
font-size: 8.5pt;
margin-right: 4pt;
}
/* ── Sektionen ── */
.pdf-section {
margin: 14pt 0 0;
page-break-inside: avoid;
}
.pdf-h2 {
font-family: 'Source Code Pro', monospace;
font-size: 9pt;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #009DA5;
margin: 0 0 6pt;
}
p { margin: 0 0 6pt; }
/* ── Zusammenfassung + Kernpunkte ── */
.pdf-prose {
font-size: 10pt;
line-height: 1.55;
}
.pdf-kernpunkte {
margin-top: 8pt;
padding-top: 6pt;
border-top: 0.5pt dashed #ccc;
}
.pdf-kernpunkte-label {
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #009DA5;
margin-bottom: 4pt;
font-weight: 700;
}
.pdf-kernpunkte ul {
margin: 0;
padding-left: 14pt;
}
.pdf-kernpunkte li {
margin-bottom: 2pt;
}
/* ── Bewertungs-Block ── */
.pdf-bewertung {
background:
{% if score_color == '#1a7f37' %}#e8eed1
{% elif score_color == '#bf6c10' %}#f4e6cf
{% else %}#f1dcda{% endif %};
border-left: 4pt solid {{ score_color }};
border-radius: 2pt;
padding: 12pt 14pt;
display: flex;
align-items: center;
gap: 16pt;
}
.pdf-score-num {
font-family: 'Source Code Pro', monospace;
font-size: 36pt;
font-weight: 700;
line-height: 1;
color: {{ score_color }};
flex-shrink: 0;
}
.pdf-score-num .slash { font-size: 16pt; opacity: 0.5; }
.pdf-score-side { flex: 1; }
.pdf-score-label {
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #555;
margin-bottom: 3pt;
}
.pdf-verdict {
font-size: 14pt;
font-weight: 700;
color: {{ score_color }};
line-height: 1.15;
}
.pdf-bewertung-body {
font-size: 9.5pt;
line-height: 1.55;
color: #333;
margin-top: 8pt;
}
/* ── Stärken / Schwächen ── */
.pdf-werte-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10pt;
}
.pdf-werte-card {
border-left: 3pt solid;
padding: 8pt 10pt;
border-radius: 2pt;
}
.pdf-werte-card.staerke { border-left-color: #889E33; background: rgba(136, 158, 51, 0.08); }
.pdf-werte-card.schwaeche { border-left-color: #009DA5; background: rgba(0, 157, 165, 0.08); }
.pdf-werte-card h4 {
font-size: 9pt;
margin-bottom: 4pt;
color: #1f1f1f;
font-weight: 700;
}
.pdf-werte-card p {
font-size: 9.5pt;
margin: 0;
line-height: 1.5;
}
.pdf-werte-card ul {
margin: 0;
padding-left: 14pt;
font-size: 9.5pt;
}
/* ── Matrix mit Achsen ── */
.pdf-matrix-block {
page-break-inside: avoid;
}
.pdf-matrix-grid {
display: grid;
grid-template-columns: 90pt repeat(5, 60pt);
grid-template-rows: 24pt repeat(5, 60pt);
gap: 2pt;
}
.pdf-matrix-grid .col-label {
font-family: 'Source Code Pro', monospace;
font-size: 7pt;
font-weight: 700;
color: #009DA5;
text-transform: uppercase;
letter-spacing: 0.04em;
display: flex;
align-items: end;
justify-content: center;
padding-bottom: 3pt;
text-align: center;
line-height: 1.1;
}
.pdf-matrix-grid .row-label {
font-family: 'Source Code Pro', monospace;
font-size: 7.5pt;
font-weight: 700;
color: #009DA5;
text-transform: uppercase;
letter-spacing: 0.03em;
display: flex;
align-items: center;
padding-right: 5pt;
line-height: 1.15;
}
.pdf-matrix-grid .cell {
border-radius: 1pt;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Source Code Pro', monospace;
font-size: 13pt;
font-weight: 700;
}
.cell.r-pp { background: #889E33; color: #fff; }
.cell.r-p { background: #cddaa1; color: #44570a; }
.cell.r-0 { background: #b8b8b2; color: #4a4a44; }
.cell.r-n { background: #efc9c3; color: #931515; }
.cell.r-nn { background: #9A2A2A; color: #fff; }
.pdf-matrix-legend {
margin-top: 6pt;
display: flex;
flex-wrap: wrap;
gap: 4pt 14pt;
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
color: #444;
}
.pdf-matrix-legend .legend-item {
display: inline-flex;
align-items: center;
gap: 4pt;
}
.pdf-matrix-legend .swatch {
width: 12pt; height: 12pt; border-radius: 1pt; flex-shrink: 0;
}
/* ── Schwerpunkt-Felder-Erklaerung (Ersatz fuer Klick im PDF) ── */
.pdf-schwerpunkt-list {
margin-top: 8pt;
}
.pdf-schwerpunkt-item {
border-left: 2pt solid;
padding: 6pt 10pt;
margin-bottom: 5pt;
background: #fafaf7;
border-radius: 1pt;
}
.pdf-schwerpunkt-item.pos { border-left-color: #889E33; }
.pdf-schwerpunkt-item.neg { border-left-color: #9A2A2A; }
.pdf-schwerpunkt-item.neu { border-left-color: #b8b8b2; }
.pdf-schwerpunkt-head {
display: flex;
align-items: baseline;
gap: 8pt;
margin-bottom: 3pt;
}
.pdf-schwerpunkt-code {
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
font-weight: 700;
padding: 1pt 5pt;
border-radius: 2pt;
color: #fff;
background: #009DA5;
}
.pdf-schwerpunkt-rating {
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
font-weight: 700;
color: #555;
}
.pdf-schwerpunkt-label {
font-size: 9.5pt;
font-weight: 700;
color: #1f1f1f;
}
.pdf-schwerpunkt-aspect {
font-size: 9pt;
line-height: 1.5;
color: #333;
}
/* ── Programm-Treue ── */
.pdf-fraktion {
border: 0.5pt solid #ddd;
border-radius: 2pt;
padding: 8pt 10pt;
margin-bottom: 8pt;
page-break-inside: avoid;
background: #fafaf7;
}
.pdf-fraktion-head {
display: flex;
align-items: baseline;
gap: 6pt;
flex-wrap: wrap;
margin-bottom: 5pt;
}
.pdf-fraktion-name {
font-size: 11pt;
font-weight: 700;
}
.pdf-pill {
font-family: 'Source Code Pro', monospace;
font-size: 7pt;
text-transform: uppercase;
padding: 1pt 5pt;
border-radius: 1pt;
}
.pdf-pill.antrag { background: rgba(247, 148, 29, 0.18); color: #bf6c10; }
.pdf-pill.reg { background: rgba(0, 157, 165, 0.18); color: #1e6a90; }
.pdf-prog {
margin-top: 5pt;
padding-top: 5pt;
border-top: 0.5pt dashed #ccc;
}
.pdf-prog:first-of-type { border-top: none; padding-top: 0; }
.pdf-prog-row {
display: flex;
align-items: baseline;
gap: 8pt;
margin-bottom: 3pt;
}
.pdf-prog-label {
font-family: 'Source Code Pro', monospace;
font-size: 8.5pt;
font-weight: 700;
text-transform: uppercase;
color: #0d6f76;
letter-spacing: 0.04em;
}
.pdf-prog-spacer { flex: 1; }
.pdf-prog-score {
font-family: 'Source Code Pro', monospace;
font-size: 8.5pt;
font-weight: 700;
padding: 1pt 5pt;
border-radius: 2pt;
}
.pdf-prog-score.good { background: #cddaa1; color: #44570a; }
.pdf-prog-score.mid { background: #fff3cd; color: #7d5a00; }
.pdf-prog-score.low { background: #efc9c3; color: #a00000; }
.pdf-prog-text {
font-size: 9.5pt;
line-height: 1.5;
color: #333;
margin-bottom: 3pt;
}
.pdf-prog-zitat {
border-left: 2pt solid #009DA5;
background: #f3f8f8;
padding: 4pt 8pt;
margin: 3pt 0;
font-size: 9pt;
line-height: 1.4;
color: #333;
}
.pdf-prog-zitat .src {
display: block;
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
color: #555;
margin-top: 2pt;
}
.pdf-no-zitate {
font-size: 8.5pt;
line-height: 1.5;
color: #777;
font-style: italic;
background: #f5f5f5;
border-left: 2pt dashed #ccc;
padding: 4pt 8pt;
border-radius: 0 2pt 2pt 0;
}
/* ── Verbesserungsvorschlaege (Redline) ── */
.pdf-verbesserung {
margin-bottom: 10pt;
padding: 6pt 0;
page-break-inside: avoid;
}
.pdf-verbesserung-num {
font-family: 'Source Code Pro', monospace;
font-size: 8pt;
color: #555;
margin-bottom: 4pt;
}
.pdf-redline {
font-size: 9.5pt;
line-height: 1.5;
background: #fafaf7;
padding: 6pt 8pt;
border-radius: 2pt;
}
.pdf-redline .ins { background: #cddaa1; padding: 0 2pt; }
.pdf-redline .del { background: #efc9c3; text-decoration: line-through; padding: 0 2pt; }
.pdf-redline-original { font-size: 9pt; color: #555; margin-bottom: 4pt; }
.pdf-verbesserung-begr {
font-size: 8.5pt;
color: #555;
line-height: 1.5;
margin-top: 4pt;
font-style: italic;
}
/* ── Abstimmungsergebnis ── */
.pdf-vote-card {
border: 0.5pt solid #ddd;
border-radius: 2pt;
padding: 6pt 8pt;
margin-bottom: 5pt;
background: #fafaf7;
page-break-inside: avoid;
}
.pdf-vote-head {
font-size: 10pt;
font-weight: 700;
margin-bottom: 4pt;
}
.pdf-vote-pills {
display: flex;
flex-wrap: wrap;
gap: 6pt 12pt;
font-family: 'Source Code Pro', monospace;
font-size: 8.5pt;
}
.pdf-vote-side { font-weight: 700; }
.pdf-vote-pill {
display: inline-block;
padding: 1pt 5pt;
border-radius: 1pt;
margin-right: 2pt;
}
.pdf-vote-pill.ja { background: rgba(45, 164, 78, 0.15); color: #1a7f37; }
.pdf-vote-pill.nein { background: rgba(207, 34, 46, 0.15); color: #a40e26; }
.pdf-vote-pill.enth { background: rgba(110, 119, 129, 0.15); color: #57606a; }
.pdf-konsistenz {
font-size: 9pt;
padding: 5pt 8pt;
margin-bottom: 5pt;
border-left: 3pt solid;
border-radius: 2pt;
}
.pdf-konsistenz.conflict {
background: rgba(207, 34, 46, 0.07);
border-left-color: #cf222e;
}
.pdf-konsistenz.match {
background: rgba(45, 164, 78, 0.07);
border-left-color: #2da44e;
}
</style>
</head>
<body>
<!-- ─── Header + Title ─────────────────────────────────────────────── -->
<div class="pdf-header">
<div class="pdf-kicker">GWÖ-Antragsprüfung
{% if parlament_name %} · {{ parlament_name }}{% endif %}
</div>
<h1 class="pdf-title">{{ assessment.title }}</h1>
<div class="pdf-meta">
Drucksache <strong>{{ assessment.drucksache }}</strong>
{% if assessment.datum %} · eingebracht {{ assessment.datum }}{% endif %}
{% if assessment.fraktionen %}
— Antragsteller:
{% for f in assessment.fraktionen %}<span class="pill">{{ f }}</span>{% endfor %}
{% endif %}
</div>
{% if assessment.themen %}
<div class="pdf-themen">
{% for t in assessment.themen %}<span class="pdf-theme-chip">{{ t }}</span>{% endfor %}
</div>
{% endif %}
</div>
<!-- ─── Zusammenfassung + Kernpunkte ────────────────────────────────── -->
{% if assessment.antrag_zusammenfassung or assessment.antrag_kernpunkte %}
<div class="pdf-section">
<h2 class="pdf-h2">Zusammenfassung</h2>
{% if assessment.antrag_zusammenfassung %}
<p class="pdf-prose">{{ assessment.antrag_zusammenfassung }}</p>
{% endif %}
{% if assessment.antrag_kernpunkte %}
<div class="pdf-kernpunkte">
<div class="pdf-kernpunkte-label">Kernforderungen</div>
<ul>
{% for kp in assessment.antrag_kernpunkte %}<li>{{ kp }}</li>{% endfor %}
</ul>
</div>
{% endif %}
</div>
{% endif %}
<!-- ─── Bewertungs-Block ────────────────────────────────────────────── -->
<div class="pdf-section">
<h2 class="pdf-h2">Bewertung</h2>
<div class="pdf-bewertung">
<div class="pdf-score-num">{{ "%.1f"|format(assessment.gwoe_score) }}<span class="slash">/10</span></div>
<div class="pdf-score-side">
<div class="pdf-score-label">Gemeinwohl-Score</div>
<div class="pdf-verdict">{{ assessment.empfehlung.value }}</div>
</div>
</div>
{% if assessment.gwoe_begruendung %}
<p class="pdf-bewertung-body">{{ assessment.gwoe_begruendung }}</p>
{% endif %}
</div>
<!-- ─── Stärken / Schwächen ────────────────────────────────────────── -->
{% if assessment.staerken or assessment.schwaechen %}
<div class="pdf-section">
<h2 class="pdf-h2">Stärken &amp; Schwächen</h2>
<div class="pdf-werte-grid">
{% if assessment.staerken %}
<div class="pdf-werte-card staerke">
<h4>Stärken</h4>
<ul>{% for s in assessment.staerken %}<li>{{ s }}</li>{% endfor %}</ul>
</div>
{% endif %}
{% if assessment.schwaechen %}
<div class="pdf-werte-card schwaeche">
<h4>Schwächen</h4>
<ul>{% for s in assessment.schwaechen %}<li>{{ s }}</li>{% endfor %}</ul>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- ─── Matrix 5×5 mit Achsen-Labels ───────────────────────────────── -->
<div class="pdf-section pdf-matrix-block">
<h2 class="pdf-h2">GWÖ-Matrix 5×5</h2>
<div class="pdf-matrix-grid">
<div></div>
<div class="col-label">Würde</div>
<div class="col-label">Solida­rität</div>
<div class="col-label">Nach­haltig­keit</div>
<div class="col-label">Gerech­tigkeit</div>
<div class="col-label">Trans­parenz</div>
{% set rows = [
('A', 'Lieferant:­innen'),
('B', 'Finanzen'),
('C', 'Verwal­tung'),
('D', 'Bürger:­innen'),
('E', 'Gesell­schaft & Natur'),
] %}
{% for r, r_label in rows %}
<div class="row-label">{{ r }} · {{ r_label }}</div>
{% for c in ['1','2','3','4','5'] %}
{% set cell = matrix_lookup.get(r ~ c, {}) %}
{% set rt = cell.get('rating', 0) %}
<div class="cell {% if rt >= 4 %}r-pp{% elif rt >= 1 %}r-p{% elif rt == 0 %}r-0{% elif rt <= -4 %}r-nn{% else %}r-n{% endif %}">{% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}{% else %}{% endif %}</div>
{% endfor %}
{% endfor %}
</div>
<div class="pdf-matrix-legend">
<span class="legend-item"><span class="swatch" style="background:#889E33;"></span>++ stark fördernd</span>
<span class="legend-item"><span class="swatch" style="background:#cddaa1;"></span>+ fördernd</span>
<span class="legend-item"><span class="swatch" style="background:#b8b8b2;"></span>○ neutral</span>
<span class="legend-item"><span class="swatch" style="background:#efc9c3;"></span> widersprechend</span>
<span class="legend-item"><span class="swatch" style="background:#9A2A2A;"></span> stark widersprechend</span>
</div>
</div>
<!-- ─── Schwerpunkt-Felder erklärt (Ersatz für Matrix-Klick im PDF) ── -->
{% if matrix_top or matrix_bottom %}
<div class="pdf-section">
<h2 class="pdf-h2">Schwerpunkte erklärt</h2>
<p style="font-size:9pt;color:#555;margin-bottom:6pt;">
Die wichtigsten positiv und negativ wirkenden Bewertungsfelder mit der jeweiligen Begründung.
</p>
<div class="pdf-schwerpunkt-list">
{% for cell in matrix_top %}
<div class="pdf-schwerpunkt-item pos">
<div class="pdf-schwerpunkt-head">
<span class="pdf-schwerpunkt-code">{{ cell.field }}</span>
<span class="pdf-schwerpunkt-label">{{ cell.label }}</span>
<span class="pdf-schwerpunkt-rating">Bewertung: {% if cell.rating > 0 %}+{% endif %}{{ cell.rating }}</span>
</div>
{% if cell.aspect %}<div class="pdf-schwerpunkt-aspect">{{ cell.aspect }}</div>{% endif %}
</div>
{% endfor %}
{% for cell in matrix_bottom %}
<div class="pdf-schwerpunkt-item neg">
<div class="pdf-schwerpunkt-head">
<span class="pdf-schwerpunkt-code">{{ cell.field }}</span>
<span class="pdf-schwerpunkt-label">{{ cell.label }}</span>
<span class="pdf-schwerpunkt-rating">Bewertung: {{ cell.rating }}</span>
</div>
{% if cell.aspect %}<div class="pdf-schwerpunkt-aspect">{{ cell.aspect }}</div>{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- ─── Programm-Treue pro Fraktion ─────────────────────────────────── -->
{% if assessment.wahlprogramm_scores %}
<div class="pdf-section">
<h2 class="pdf-h2">Programm-Treue pro Fraktion</h2>
{% if assessment.fehlende_programme %}
<p style="font-size:9pt;color:#555;margin-bottom:6pt;">
<strong>Hinweis:</strong> Für folgende Parteien lag kein Wahl-/Parteiprogramm vor:
{{ assessment.fehlende_programme | join(", ") }}.
</p>
{% endif %}
{% for fs in assessment.wahlprogramm_scores %}
<div class="pdf-fraktion">
<div class="pdf-fraktion-head">
<span class="pdf-fraktion-name">{{ fs.fraktion }}</span>
{% if fs.ist_antragsteller %}<span class="pdf-pill antrag">Antragsteller:in</span>{% endif %}
{% if fs.ist_regierung %}<span class="pdf-pill reg">Regierungsfraktion</span>{% endif %}
</div>
{% for prog_key, prog_label in [("wahlprogramm","Wahlprogramm"), ("parteiprogramm","Parteiprogramm")] %}
{% set p = fs[prog_key] %}
{% set p_score = p.score | float %}
<div class="pdf-prog">
<div class="pdf-prog-row">
<span class="pdf-prog-label">{{ prog_label }}</span>
<span class="pdf-prog-spacer"></span>
<span class="pdf-prog-score {% if p_score >= 7 %}good{% elif p_score >= 4 %}mid{% else %}low{% endif %}">{{ "%.0f"|format(p_score) }}/10</span>
</div>
{% if p.begruendung %}<p class="pdf-prog-text">{{ p.begruendung }}</p>{% endif %}
{% if p.zitate %}
{% for z in p.zitate %}
<div class="pdf-prog-zitat">
„{{ z.text }}"
<span class="src">{{ z.quelle }}</span>
</div>
{% endfor %}
{% else %}
<div class="pdf-no-zitate">
Keine wörtlich passenden Stellen im {{ prog_label }} gefunden — Bewertung beruht auf inhaltlicher Auslegung.
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
<!-- ─── Verbesserungsvorschläge (Redline-Diff) ─────────────────────── -->
{% if assessment.verbesserungen %}
<div class="pdf-section">
<h2 class="pdf-h2">Verbesserungsvorschläge</h2>
{% for v in assessment.verbesserungen %}
<div class="pdf-verbesserung">
{% if assessment.verbesserungen | length > 1 %}
<div class="pdf-verbesserung-num">Vorschlag {{ loop.index }} von {{ assessment.verbesserungen | length }}</div>
{% endif %}
{% if v.original %}
<div class="pdf-redline-original"><em>Original:</em> {{ v.original }}</div>
{% endif %}
{% if v.vorschlag %}
<div class="pdf-redline">{{ v.vorschlag | safe }}</div>
{% endif %}
{% if v.begruendung %}
<div class="pdf-verbesserung-begr">Begründung: {{ v.begruendung }}</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
<!-- ─── Abstimmungsergebnis (falls vorhanden) ──────────────────────── -->
{% if plenum_votes %}
<div class="pdf-section">
<h2 class="pdf-h2">Abstimmungsergebnis</h2>
{% if konsistenz_state %}
<div class="pdf-konsistenz {{ konsistenz_state }}">
<strong>{% if konsistenz_state == 'conflict' %}Mehrheit gegen GWÖ-Empfehlung{% else %}Mehrheit deckt sich mit GWÖ-Empfehlung{% endif %}</strong>
— Empfohlen: <em>{{ assessment.empfehlung.value }}</em>; Beschluss: <em>{{ konsistenz_decisive | capitalize }}</em>.
</div>
{% endif %}
{% for v in plenum_votes %}
<div class="pdf-vote-card">
<div class="pdf-vote-head">{{ v.ergebnis | capitalize }}{% if v.einstimmig %} · einstimmig{% endif %}{% if v.quelle_protokoll %} · {{ v.quelle_protokoll }}{% endif %}</div>
<div class="pdf-vote-pills">
{% if v.fraktionen_ja %}
<div><span class="pdf-vote-side" style="color:#1a7f37;">Ja:</span>
{% for f in v.fraktionen_ja %}<span class="pdf-vote-pill ja">{{ f }}</span>{% endfor %}
</div>
{% endif %}
{% if v.fraktionen_nein %}
<div><span class="pdf-vote-side" style="color:#a40e26;">Nein:</span>
{% for f in v.fraktionen_nein %}<span class="pdf-vote-pill nein">{{ f }}</span>{% endfor %}
</div>
{% endif %}
{% if v.fraktionen_enthaltung %}
<div><span class="pdf-vote-side" style="color:#57606a;">Enth.:</span>
{% for f in v.fraktionen_enthaltung %}<span class="pdf-vote-pill enth">{{ f }}</span>{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
</body>
</html>