gwoe-antragspruefer/app/templates/v3/pdf/antrag_pdf.html
Dotty Dotter c7eab5a695 feat(pdf): Heuchelei-/Opportunismus-Marker im Vote-Block (#175)
Web-Detail zeigt diese Marker bereits — pro NEIN-Fraktion einen ⚠ wenn
der eigene Wahlprogramm-Score ≥ 7/10 ist (Heuchelei: stimmt gegen die
eigenen Versprechen), pro JA-Fraktion einen ! wenn der Wahlprogramm-
Score < 3/10 (Opportunismus: stimmt zu obwohl Antrag inhaltlich nicht
zum eigenen Programm passt). Im PDF fehlten sie bisher.

Daten-Pfad: report.py rechnet die Marker einmal vor (heuchelei_score /
opportunismus_score aus app/marker.py, gefüttert mit umgemappten
fraktions_scores aus assessment.wahlprogramm_scores) und reicht zwei
Maps fraktion → score ans Template. Template macht nur noch Lookup:
``opportunismus_by_fraktion.get(f)`` neben jeder JA-Fraktion,
``heuchelei_by_fraktion.get(f)`` neben jeder NEIN-Fraktion. Plus
kompakte Legende unter dem Vote-Block, falls überhaupt Marker
vorkommen.

Stimmverhalten und Programm-Treue-Begründungen sind im PDF schon da
(verifiziert bei der Code-Inspektion). Damit ist die "PDF auf Augenhöhe
mit Web-Detail"-Liste aus #175 bis auf News-Match abgehakt; News-Match
explizit out-of-scope nach User-Entscheidung.
2026-05-09 02:21:12 +02:00

799 lines
27 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>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
@page {
size: A4;
margin: 18mm 16mm 22mm 16mm;
@bottom-left {
content: "{{ assessment.drucksache }}";
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 8pt;
color: #888;
}
@bottom-center {
content: "gwoe.toppyr.de · GWÖ-Antragsprüfer · automatische Gemeinwohl-Bilanzierung";
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 8pt;
color: #888;
}
@bottom-right {
content: "Seite " counter(page) " von " counter(pages);
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 8pt;
color: #888;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { font-family: 'Inter', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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 {
/* kompakt: Zellen 28pt hoch (war 60pt), Höhe gesamt halbiert ohne
dass die Symbole unleserlich werden. Cells leicht breiter (52pt)
als hoch — die Symbole bleiben mittig. */
display: grid;
grid-template-columns: 76pt repeat(5, 52pt);
grid-template-rows: 18pt repeat(5, 28pt);
gap: 1.5pt;
}
.pdf-matrix-grid .col-label {
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 6.5pt;
font-weight: 700;
color: #009DA5;
text-transform: uppercase;
letter-spacing: 0.03em;
display: flex;
align-items: end;
justify-content: center;
padding-bottom: 2pt;
text-align: center;
line-height: 1.05;
}
.pdf-matrix-grid .row-label {
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 7pt;
font-weight: 700;
color: #009DA5;
text-transform: uppercase;
letter-spacing: 0.02em;
display: flex;
align-items: center;
padding-right: 4pt;
line-height: 1.1;
}
.pdf-matrix-grid .cell {
border-radius: 1pt;
display: flex;
align-items: center;
justify-content: center;
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 11pt;
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: 5pt;
display: flex;
flex-wrap: wrap;
gap: 3pt 12pt;
font-family: 'JetBrains Mono', 'Source Code Pro', monospace;
font-size: 7.5pt;
color: #444;
}
.pdf-matrix-legend .legend-item {
display: inline-flex;
align-items: center;
gap: 3pt;
}
.pdf-matrix-legend .swatch {
width: 9pt; height: 9pt; 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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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: 'JetBrains Mono', '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;
}
.pdf-marker {
font-weight: 700;
font-size: 9pt;
margin-left: 2pt;
}
.pdf-marker.heuchelei { color: #cf222e; }
.pdf-marker.opportunismus { color: #bf6c10; font-style: italic; }
.pdf-marker-legend {
font-size: 7.5pt;
color: #57606a;
margin-top: 4pt;
padding-top: 3pt;
border-top: 1pt dashed #d0d7de;
}
</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 %}
{% set _missing_set = (assessment.fehlende_programme or []) | map('lower') | list %}
{% set _all_missing = assessment.fehlende_programme and (assessment.wahlprogramm_scores | length == assessment.fehlende_programme | length) %}
{% if _all_missing %}
<div class="pdf-section">
<h2 class="pdf-h2">Programm-Treue pro Fraktion</h2>
<p style="font-size:9pt;color:#555;line-height:1.55;padding:6pt 10pt;background:rgba(247,148,29,0.08);border-left:3pt solid #bf6c10;border-radius:1pt;">
<strong>Programm-Treue nicht verfügbar.</strong>
Für dieses Parlament ({{ bundesland }}) sind aktuell keine Wahl- und
Parteiprogramme indiziert. Eine inhaltliche Bewertung gegen die einzelnen
Programme ist damit nicht möglich; um halluzinierte Scores zu vermeiden,
wird die Programm-Treue-Sektion hier nicht ausgewiesen.
</p>
</div>
{% else %}
<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 — sie werden hier nicht aufgeführt:
{{ assessment.fehlende_programme | join(", ") }}.
</p>
{% endif %}
{% for fs in assessment.wahlprogramm_scores %}
{% if fs.fraktion | lower not in _missing_set %}
<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>
{% endif %}{# fraktion not in fehlende_programme #}
{% endfor %}
</div>
{% endif %}{# _all_missing-else #}
{% endif %}{# wahlprogramm_scores #}
<!-- ─── 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 }}{% if opportunismus_by_fraktion.get(f) is not none %}<span class="pdf-marker opportunismus" title="Opportunismus: {{ f }} stimmt zu trotz Wahlprogramm-Score {{ '%.0f' | format(opportunismus_by_fraktion[f]) }}/10">!</span>{% endif %}</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 }}{% if heuchelei_by_fraktion.get(f) is not none %}<span class="pdf-marker heuchelei" title="Heuchelei: {{ f }} stimmt mit Nein trotz Wahlprogramm-Score {{ '%.0f' | format(heuchelei_by_fraktion[f]) }}/10"></span>{% endif %}</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>
{% if heuchelei_by_fraktion or opportunismus_by_fraktion %}
<div class="pdf-marker-legend">
{% if heuchelei_by_fraktion %}<span class="pdf-marker heuchelei"></span> Heuchelei (Nein trotz Wahlprogramm-Match ≥7/10){% endif %}
{% if heuchelei_by_fraktion and opportunismus_by_fraktion %} · {% endif %}
{% if opportunismus_by_fraktion %}<span class="pdf-marker opportunismus">!</span> Opportunismus (Ja trotz Wahlprogramm-Mismatch &lt;3/10){% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</body>
</html>