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>
This commit is contained in:
parent
8ae2b92313
commit
79e7937d51
@ -14,11 +14,21 @@ from html import escape as _e
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG
|
from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG
|
||||||
from .bundeslaender import BUNDESLAENDER
|
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
|
# ECOnGOOD Colors
|
||||||
COLORS = {
|
COLORS = {
|
||||||
"darkgray": "#5a5a5a",
|
"darkgray": "#5a5a5a",
|
||||||
@ -512,6 +522,90 @@ async def generate_html_report(
|
|||||||
output_path.write_text(html)
|
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(
|
async def generate_pdf_report(
|
||||||
assessment: Assessment,
|
assessment: Assessment,
|
||||||
output_path: Path,
|
output_path: Path,
|
||||||
@ -535,9 +629,10 @@ async def generate_pdf_report(
|
|||||||
``bundesland`` is forwarded to ``generate_html_report`` so the source
|
``bundesland`` is forwarded to ``generate_html_report`` so the source
|
||||||
parlament name appears in the report header.
|
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')
|
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:
|
try:
|
||||||
from weasyprint import HTML
|
from weasyprint import HTML
|
||||||
|
|||||||
@ -180,6 +180,16 @@
|
|||||||
<p id="alt-views-sample" style="font-family:var(--font-mono);font-size:12px;line-height:1.8;">
|
<p id="alt-views-sample" style="font-family:var(--font-mono);font-size:12px;line-height:1.8;">
|
||||||
Lade aktuelles Beispiel …
|
Lade aktuelles Beispiel …
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3 class="v2-h3" style="margin-top:24px;">Design-Werkstätten</h3>
|
||||||
|
<p style="font-family:var(--font-mono);font-size:12px;opacity:0.75;line-height:1.6;">
|
||||||
|
Live-Editoren für Layout-Iteration ohne Server-Redeploy:
|
||||||
|
</p>
|
||||||
|
<ul style="font-family:var(--font-mono);font-size:12px;line-height:1.8;">
|
||||||
|
<li><a href="/v2/scorecard-werkstatt">Scorecard-Werkstatt</a> —
|
||||||
|
Live-Vorschau aller Card-Formate (Portrait / Square / OG) mit
|
||||||
|
CSS-Editor und Embed-Link-Generator.</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -484,8 +484,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# ── Share-Block (analog v1) ───────────────────────────────────── #}
|
{# ── Share-Block (analog v1) — nur fuer angemeldete User sichtbar,
|
||||||
<div style="margin-top:20px;">
|
wird in initAuth() ein-/ausgeblendet (display:none default). ── #}
|
||||||
|
<div id="v2-share-block" style="margin-top:20px;display:none;">
|
||||||
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Teilen</div>
|
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Teilen</div>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||||
<button onclick="v2DetailShareCopy()"
|
<button onclick="v2DetailShareCopy()"
|
||||||
@ -725,14 +726,17 @@ window.v2ShowMatrixFieldInfo = function(field) {
|
|||||||
currentUser = (data && data.authenticated) ? data : null;
|
currentUser = (data && data.authenticated) ? data : null;
|
||||||
} catch (_) { currentUser = null; }
|
} catch (_) { currentUser = null; }
|
||||||
|
|
||||||
var form = document.getElementById('v2-comment-form');
|
var form = document.getElementById('v2-comment-form');
|
||||||
var loginHint = document.getElementById('v2-comment-login-hint');
|
var loginHint = document.getElementById('v2-comment-login-hint');
|
||||||
|
var shareBlock = document.getElementById('v2-share-block');
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
if (form) form.style.display = 'block';
|
if (form) form.style.display = 'block';
|
||||||
if (loginHint) loginHint.style.display = 'none';
|
if (loginHint) loginHint.style.display = 'none';
|
||||||
|
if (shareBlock) shareBlock.style.display = ''; /* default-display, nicht 'block' (v3-rest-block ist flex/grid je nach Klasse) */
|
||||||
} else {
|
} else {
|
||||||
if (form) form.style.display = 'none';
|
if (form) form.style.display = 'none';
|
||||||
if (loginHint) loginHint.style.display = 'block';
|
if (loginHint) loginHint.style.display = 'block';
|
||||||
|
if (shareBlock) shareBlock.style.display = 'none';
|
||||||
}
|
}
|
||||||
loadComments();
|
loadComments();
|
||||||
loadVotes();
|
loadVotes();
|
||||||
|
|||||||
756
app/templates/v3/pdf/antrag_pdf.html
Normal file
756
app/templates/v3/pdf/antrag_pdf.html
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
{# ─────────────────────────────────────────────────────────────────────
|
||||||
|
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 & 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">Solidarität</div>
|
||||||
|
<div class="col-label">Nachhaltigkeit</div>
|
||||||
|
<div class="col-label">Gerechtigkeit</div>
|
||||||
|
<div class="col-label">Transparenz</div>
|
||||||
|
|
||||||
|
{% set rows = [
|
||||||
|
('A', 'Lieferant:innen'),
|
||||||
|
('B', 'Finanzen'),
|
||||||
|
('C', 'Verwaltung'),
|
||||||
|
('D', 'Bürger:innen'),
|
||||||
|
('E', 'Gesellschaft & 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>
|
||||||
@ -365,8 +365,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Teilen #}
|
{# Teilen — nur fuer angemeldete User; initAuth() blendet via #v2-share-block ein. #}
|
||||||
<div class="v3-rest-block v3-rest-divider-top">
|
<div id="v2-share-block" class="v3-rest-block v3-rest-divider-top" style="display:none;">
|
||||||
<h3 class="v3-h3">Teilen</h3>
|
<h3 class="v3-h3">Teilen</h3>
|
||||||
<div class="v3-share-buttons">
|
<div class="v3-share-buttons">
|
||||||
<button onclick="v2DetailShareCopy()" class="v3-action-btn">📋 Kopieren</button>
|
<button onclick="v2DetailShareCopy()" class="v3-action-btn">📋 Kopieren</button>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user