gwoe-antragspruefer/app/templates/v3/pdf/antrag_pdf.html
Dotty Dotter 332453b940 fix: Programm-Treue-Block bei BUND-/Antraegen mit komplett fehlenden Programmen ausblenden
User: '?? Hinweis: Für folgende Parteien lag kein Wahl-/Parteiprogramm
vor — keine Treue-Bewertung möglich: CDU, CSU, AfD, SPD, GRÜNE, LINKE,
BSW, FDP.' Vorher zeigte die UI den Disclaimer + trotzdem die LLM-
halluzinierten Programm-Treue-Scores aller 8 BT-Parteien — schlechte UX.

WAHLPROGRAMME['BUND'] ist aktuell leer (keine Bundestags-Wahlprogramme
indiziert), daher wird check_missing_programmes alle BT-Fraktionen als
'fehlend' markieren. Bisher wurden trotzdem die LLM-Scores rausgespielt.

Fix in v3/screens/antrag_detail.html und v3/pdf/antrag_pdf.html:
- Wenn _all_missing (alle Fraktionen fehlen) → ganze Programm-Treue-
  Sektion ausblenden, nur klare Disclaimer-Box zeigen ('Programm-Treue
  nicht verfuegbar — fuer dieses Parlament sind aktuell keine Programme
  indiziert').
- Wenn nur einzelne Fraktionen fehlen → die einzelnen Karten via
  {% if fs.fraktion not in _missing_set %} skippen, Disclaimer fuer
  die wegfallenden Fraktionen zeigen ('werden hier nicht aufgefuehrt').

Damit keine LLM-halluzinierten Scores mehr gezeigt werden, wo es keine
Quelle gibt. Issue #186 (historische Programme indizieren) ist die
langfristige Loesung — diese Aenderung macht die UI bis dahin ehrlich.

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

778 lines
26 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;
}
</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 }}</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>