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.
This commit is contained in:
parent
ad73c824d3
commit
c7eab5a695
@ -590,6 +590,39 @@ async def generate_html_report_v3(
|
|||||||
assessment.drucksache, exc,
|
assessment.drucksache, exc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pre-compute Heuchelei-/Opportunismus-Marker pro Fraktion.
|
||||||
|
# Jinja-Globals sind im Web ``heuchelei_score`` und ``opportunismus_score``;
|
||||||
|
# sie erwarten Dict-Listen (fraktions_scores aus _row_to_detail). Das
|
||||||
|
# Pydantic-Assessment.wahlprogramm_scores hat dieselben Daten, aber als
|
||||||
|
# Pydantic-Objekte. Wir mappen einmal um und rechnen die Marker für
|
||||||
|
# alle abstimmenden Fraktionen, damit das Template nur noch Lookup
|
||||||
|
# statt Logik macht.
|
||||||
|
fraktions_scores_dict = []
|
||||||
|
for fs in (assessment.wahlprogramm_scores or []):
|
||||||
|
fraktions_scores_dict.append({
|
||||||
|
"fraktion": fs.fraktion,
|
||||||
|
"wahlprogramm": {"score": fs.wahlprogramm.score if fs.wahlprogramm else None},
|
||||||
|
})
|
||||||
|
|
||||||
|
from .marker import heuchelei_score as _h, opportunismus_score as _o
|
||||||
|
heuchelei_by_fraktion: dict[str, float] = {}
|
||||||
|
opportunismus_by_fraktion: dict[str, float] = {}
|
||||||
|
if plenum_votes and fraktions_scores_dict:
|
||||||
|
seen: set[str] = set()
|
||||||
|
for v in plenum_votes:
|
||||||
|
for f in (v.get("fraktionen_nein") or []):
|
||||||
|
if f in seen:
|
||||||
|
continue
|
||||||
|
hv = _h(f, fraktions_scores_dict)
|
||||||
|
if hv is not None:
|
||||||
|
heuchelei_by_fraktion[f] = hv
|
||||||
|
for f in (v.get("fraktionen_ja") or []):
|
||||||
|
if f in seen:
|
||||||
|
continue
|
||||||
|
ov = _o(f, fraktions_scores_dict)
|
||||||
|
if ov is not None:
|
||||||
|
opportunismus_by_fraktion[f] = ov
|
||||||
|
|
||||||
template = _pdf_jinja.get_template("v3/pdf/antrag_pdf.html")
|
template = _pdf_jinja.get_template("v3/pdf/antrag_pdf.html")
|
||||||
html = template.render(
|
html = template.render(
|
||||||
assessment=assessment,
|
assessment=assessment,
|
||||||
@ -602,6 +635,8 @@ async def generate_html_report_v3(
|
|||||||
plenum_votes=plenum_votes,
|
plenum_votes=plenum_votes,
|
||||||
konsistenz_state=konsistenz_state,
|
konsistenz_state=konsistenz_state,
|
||||||
konsistenz_decisive=konsistenz_decisive,
|
konsistenz_decisive=konsistenz_decisive,
|
||||||
|
heuchelei_by_fraktion=heuchelei_by_fraktion,
|
||||||
|
opportunismus_by_fraktion=opportunismus_by_fraktion,
|
||||||
)
|
)
|
||||||
output_path.write_text(html)
|
output_path.write_text(html)
|
||||||
|
|
||||||
|
|||||||
@ -504,6 +504,20 @@
|
|||||||
background: rgba(45, 164, 78, 0.07);
|
background: rgba(45, 164, 78, 0.07);
|
||||||
border-left-color: #2da44e;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -754,12 +768,12 @@
|
|||||||
<div class="pdf-vote-pills">
|
<div class="pdf-vote-pills">
|
||||||
{% if v.fraktionen_ja %}
|
{% if v.fraktionen_ja %}
|
||||||
<div><span class="pdf-vote-side" style="color:#1a7f37;">Ja:</span>
|
<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 %}
|
{% 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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if v.fraktionen_nein %}
|
{% if v.fraktionen_nein %}
|
||||||
<div><span class="pdf-vote-side" style="color:#a40e26;">Nein:</span>
|
<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 %}
|
{% 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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if v.fraktionen_enthaltung %}
|
{% if v.fraktionen_enthaltung %}
|
||||||
@ -768,6 +782,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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 <3/10){% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user