Bundesland filter & transparency: stringent split + visible source (#8)

Brings the Bundesland-Dropdown from a cosmetic header widget to a real
filter that propagates through every layer (Listing, internal search,
statistics, party/tag filters, upload mode), and at the same time makes
the source parliament visible in every place where assessments from
multiple bundesländer can be mixed.

Backend
- database.get_all_assessments(bundesland=None) — new optional filter,
  "ALL" treated as None.
- database.search_assessments — bug fix: previous `if bundesland:`
  branch incorrectly added a `WHERE bundesland='ALL'` clause; now
  guarded with `bundesland and bundesland != "ALL"`.
- main.list_assessments — accepts ?bundesland= query param, includes the
  bundesland field in the response so the frontend can render badges.
- main.get_single_assessment — also includes bundesland in the response
  so the detail header can show the source parlament.
- main.search_landtag — early HTTP 400 when bundesland is missing or
  "ALL"; the live Landtag adapter cannot serve a synthetic Bundesweit
  request.
- main.index() and main.list_bundeslaender — synthetic "🌍 Bundesweit"
  entry prepended to the bundesländer list (kept out of bundeslaender.py
  on purpose — ALL is not a real state). Both endpoints additionally
  expose a parlament_names map so the frontend can render the source
  parliament without an extra round-trip.

Report (PDF + HTML)
- generate_html_report / generate_pdf_report — new optional bundesland
  parameter. When set, the report header carries the parliament name
  ("Landtag von Sachsen-Anhalt", "Landtag Nordrhein-Westfalen", …)
  beside the title. Three call sites updated: run_analysis,
  run_drucksache_analysis, download_assessment_pdf.

Frontend (templates/index.html)
- Header dropdown gets the synthetic ALL entry as first option;
  initial currentBundesland is now 'ALL' (was 'NRW').
- localStorage persistence: changeBundesland writes, DOMContentLoaded
  reads and validates against the visible options.
- changeBundesland resets the score / party / tag filter state, syncs
  the upload-mode bundesland select, disables the Landtag-Suche button
  + tooltip when ALL, and toggles a data-mode attribute on
  .list-content (used by CSS to show/hide the per-item bundesland
  badge).
- loadAssessments now sends ?bundesland=… so the API does the actual
  filtering. updateStats renders an additional per-bundesland average
  block (Ø NRW: x · Ø LSA: y) when in ALL mode and the loaded list
  spans more than one bundesland.
- renderList prepends a small "bl-badge" beside the Drucksachen-Nummer.
  Hidden in single-bundesland mode via CSS selector to avoid clutter.
- showDetail header now shows the parliament name as its own line
  (.detail-parlament).
- searchLandtag has an early-out alert if currentBundesland === 'ALL',
  saving a network round-trip.
- Upload-Mode bundesland select now starts with a "— Bundesland wählen
  —" placeholder (no auto-default), and startAnalysis validates that a
  concrete bundesland was chosen.

CSS
- .bl-badge plus the .list-content[data-mode="single"] hide rule.
- .detail-parlament for the detail header line.
- .header-parlament for the PDF report header line.

Resolves #8.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dotty Dotter 2026-04-07 23:00:39 +02:00
parent 87874a7a14
commit f1867d463c
4 changed files with 267 additions and 49 deletions

View File

@ -200,14 +200,24 @@ async def get_assessment(drucksache: str) -> Optional[dict]:
return None return None
async def get_all_assessments() -> list[dict]: async def get_all_assessments(bundesland: str = None) -> list[dict]:
"""Get all assessments from database.""" """Get all assessments from database, optionally filtered by Bundesland.
The special value ``"ALL"`` and ``None`` mean no filter both behave
identically and return every row. Any other value becomes a strict
``WHERE bundesland = ?`` match.
"""
import json import json
sql = "SELECT * FROM assessments"
params: list = []
if bundesland and bundesland != "ALL":
sql += " WHERE bundesland = ?"
params.append(bundesland)
sql += " ORDER BY gwoe_score DESC"
async with aiosqlite.connect(settings.db_path) as db: async with aiosqlite.connect(settings.db_path) as db:
db.row_factory = aiosqlite.Row db.row_factory = aiosqlite.Row
cursor = await db.execute( cursor = await db.execute(sql, params)
"SELECT * FROM assessments ORDER BY gwoe_score DESC"
)
rows = await cursor.fetchall() rows = await cursor.fetchall()
results = [] results = []
for row in rows: for row in rows:
@ -293,7 +303,7 @@ async def search_assessments(query: str, bundesland: str = None, limit: int = 50
""" """
params = [f"%{first_term}%"] * 4 params = [f"%{first_term}%"] * 4
if bundesland: if bundesland and bundesland != "ALL":
sql += " AND bundesland = ?" sql += " AND bundesland = ?"
params.append(bundesland) params.append(bundesland)

View File

@ -84,13 +84,24 @@ async def startup():
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def index(request: Request): async def index(request: Request):
"""Landing page with upload form.""" """Landing page with upload form."""
# Frontend-Liste: synthetischer "ALL"-Eintrag (Bundesweit) zuerst, dann
# die echten Bundesländer aus der Konfig. Der "ALL"-Code ist eine reine
# Frontend/API-Konvention, kein Eintrag in bundeslaender.py.
bl_list = [{"code": "ALL", "name": "🌍 Bundesweit", "active": True}]
bl_list.extend(
{"code": bl.code, "name": bl.name, "active": bl.aktiv}
for bl in alle_bundeslaender()
)
# Map code → parlament_name, damit das Frontend ohne extra Backend-Call
# für jeden Antrag den Parlamentsnamen anzeigen kann.
parlament_names = {
bl.code: bl.parlament_name for bl in alle_bundeslaender()
}
return templates.TemplateResponse("index.html", { return templates.TemplateResponse("index.html", {
"request": request, "request": request,
"app_name": settings.app_name, "app_name": settings.app_name,
"bundeslaender": [ "bundeslaender": bl_list,
{"code": bl.code, "name": bl.name, "active": bl.aktiv} "parlament_names": parlament_names,
for bl in alle_bundeslaender()
],
}) })
@ -138,8 +149,8 @@ async def run_analysis(job_id: str, text: str, bundesland: str, model: str):
html_path = settings.reports_dir / f"{job_id}.html" html_path = settings.reports_dir / f"{job_id}.html"
pdf_path = settings.reports_dir / f"{job_id}.pdf" pdf_path = settings.reports_dir / f"{job_id}.pdf"
await generate_html_report(assessment, html_path) await generate_html_report(assessment, html_path, bundesland=bundesland)
await generate_pdf_report(assessment, pdf_path) await generate_pdf_report(assessment, pdf_path, bundesland=bundesland)
await update_job( await update_job(
job_id, job_id,
@ -203,9 +214,12 @@ async def get_pdf(job_id: str):
# API: Load assessments from database # API: Load assessments from database
@app.get("/api/assessments") @app.get("/api/assessments")
async def list_assessments(): async def list_assessments(bundesland: Optional[str] = None):
"""Return all assessments from database.""" """Return assessments from database, optionally filtered by Bundesland.
rows = await get_all_assessments()
``bundesland="ALL"`` and missing parameter both mean "no filter".
"""
rows = await get_all_assessments(bundesland)
# Convert DB format to frontend format # Convert DB format to frontend format
assessments = [] assessments = []
@ -216,6 +230,7 @@ async def list_assessments():
"fraktionen": row.get("fraktionen", []), "fraktionen": row.get("fraktionen", []),
"datum": row.get("datum"), "datum": row.get("datum"),
"link": row.get("link"), "link": row.get("link"),
"bundesland": row.get("bundesland"),
"gwoeScore": row.get("gwoe_score"), "gwoeScore": row.get("gwoe_score"),
"gwoeBegründung": row.get("gwoe_begruendung"), "gwoeBegründung": row.get("gwoe_begruendung"),
"gwoeMatrix": row.get("gwoe_matrix", []), "gwoeMatrix": row.get("gwoe_matrix", []),
@ -249,6 +264,7 @@ async def get_single_assessment(drucksache: str):
"fraktionen": row.get("fraktionen", []), "fraktionen": row.get("fraktionen", []),
"datum": row.get("datum"), "datum": row.get("datum"),
"link": row.get("link"), "link": row.get("link"),
"bundesland": row.get("bundesland"),
"gwoeScore": row.get("gwoe_score"), "gwoeScore": row.get("gwoe_score"),
"gwoeBegründung": row.get("gwoe_begruendung"), "gwoeBegründung": row.get("gwoe_begruendung"),
"gwoeMatrix": row.get("gwoe_matrix", []), "gwoeMatrix": row.get("gwoe_matrix", []),
@ -306,7 +322,11 @@ async def download_assessment_pdf(drucksache: str):
try: try:
assessment = Assessment(**assessment_data) assessment = Assessment(**assessment_data)
await generate_pdf_report(assessment, pdf_path) await generate_pdf_report(
assessment,
pdf_path,
bundesland=row.get("bundesland"),
)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"PDF-Generierung fehlgeschlagen: {e}") raise HTTPException(status_code=500, detail=f"PDF-Generierung fehlgeschlagen: {e}")
@ -356,7 +376,15 @@ async def search_landtag(
""" """
Search external parliament portal (e.g., NRW OPAL). Search external parliament portal (e.g., NRW OPAL).
Returns results that can be analyzed with "Jetzt prüfen". Returns results that can be analyzed with "Jetzt prüfen".
Requires a concrete Bundesland the special "ALL" / Bundesweit mode
cannot pick a single Landtag adapter and is rejected with HTTP 400.
""" """
if not bundesland or bundesland == "ALL":
raise HTTPException(
status_code=400,
detail="Landtag-Suche benötigt ein konkretes Bundesland",
)
adapter = get_adapter(bundesland) adapter = get_adapter(bundesland)
if not adapter: if not adapter:
return {"error": f"Bundesland {bundesland} noch nicht unterstützt"} return {"error": f"Bundesland {bundesland} noch nicht unterstützt"}
@ -472,8 +500,8 @@ async def run_drucksache_analysis(
html_path = settings.reports_dir / f"{job_id}.html" html_path = settings.reports_dir / f"{job_id}.html"
pdf_path = settings.reports_dir / f"{job_id}.pdf" pdf_path = settings.reports_dir / f"{job_id}.pdf"
await generate_html_report(assessment, html_path) await generate_html_report(assessment, html_path, bundesland=bundesland)
await generate_pdf_report(assessment, pdf_path) await generate_pdf_report(assessment, pdf_path, bundesland=bundesland)
await update_job( await update_job(
job_id, job_id,
@ -492,11 +520,26 @@ async def run_drucksache_analysis(
# API: List available Bundesländer # API: List available Bundesländer
@app.get("/api/bundeslaender") @app.get("/api/bundeslaender")
async def list_bundeslaender(): async def list_bundeslaender():
"""List available bundesländer with their status.""" """List available bundesländer with their status.
return [
{"code": bl.code, "name": bl.name, "active": bl.aktiv} Includes the synthetic "ALL" / Bundesweit entry as the first item so
for bl in alle_bundeslaender() that the frontend can render it directly. ``parlament_name`` is added
] so the detail view can show the source parliament without an extra
backend round-trip.
"""
out = [{
"code": "ALL",
"name": "🌍 Bundesweit",
"parlament_name": None,
"active": True,
}]
out.extend({
"code": bl.code,
"name": bl.name,
"parlament_name": bl.parlament_name,
"active": bl.aktiv,
} for bl in alle_bundeslaender())
return out
# === Quellen / Programme === # === Quellen / Programme ===

View File

@ -7,6 +7,7 @@ from typing import Optional
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG
from .bundeslaender import BUNDESLAENDER
# ECOnGOOD Colors # ECOnGOOD Colors
COLORS = { COLORS = {
@ -93,11 +94,26 @@ def build_matrix_html(assessment: Assessment) -> str:
return '\n'.join(html) return '\n'.join(html)
async def generate_html_report(assessment: Assessment, output_path: Path) -> None: async def generate_html_report(
"""Generate HTML report.""" assessment: Assessment,
output_path: Path,
bundesland: Optional[str] = None,
) -> None:
"""Generate HTML report.
``bundesland`` is the optional state code (e.g. ``"NRW"``, ``"LSA"``).
When set and known in ``BUNDESLAENDER``, the resulting report carries
the parlament name in its header so the source parliament is always
visible important since assessments from multiple bundesländer share
the same Drucksachen-ID space.
"""
empf_config = EMPFEHLUNG_CONFIG.get(assessment.empfehlung.value, {}) empf_config = EMPFEHLUNG_CONFIG.get(assessment.empfehlung.value, {})
parlament_name = ""
if bundesland and bundesland in BUNDESLAENDER:
parlament_name = BUNDESLAENDER[bundesland].parlament_name
html = f"""<!DOCTYPE html> html = f"""<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@ -142,6 +158,14 @@ async def generate_html_report(assessment: Assessment, output_path: Path) -> Non
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
}} }}
.header-parlament {{
font-size: 9pt;
color: var(--color-blue);
font-weight: bold;
margin-top: 0.4rem;
letter-spacing: 0.3px;
}}
h1 {{ h1 {{
color: var(--color-darkgray); color: var(--color-darkgray);
font-size: 14pt; font-size: 14pt;
@ -327,6 +351,7 @@ async def generate_html_report(assessment: Assessment, output_path: Path) -> Non
<div class="header"> <div class="header">
<div class="header-label">GEMEINWOHL-ÖKONOMIE | ANTRAGSBEWERTUNG</div> <div class="header-label">GEMEINWOHL-ÖKONOMIE | ANTRAGSBEWERTUNG</div>
<h1>{assessment.title}</h1> <h1>{assessment.title}</h1>
{f'<div class="header-parlament">{parlament_name}</div>' if parlament_name else ''}
</div> </div>
<div class="meta-box"> <div class="meta-box">
@ -414,11 +439,19 @@ async def generate_html_report(assessment: Assessment, output_path: Path) -> Non
output_path.write_text(html) output_path.write_text(html)
async def generate_pdf_report(assessment: Assessment, output_path: Path) -> None: async def generate_pdf_report(
"""Generate PDF report using WeasyPrint.""" assessment: Assessment,
output_path: Path,
bundesland: Optional[str] = None,
) -> None:
"""Generate PDF report using WeasyPrint.
``bundesland`` is forwarded to ``generate_html_report`` so the source
parlament name appears in the report header.
"""
# First generate HTML # First generate HTML
html_path = output_path.with_suffix('.tmp.html') html_path = output_path.with_suffix('.tmp.html')
await generate_html_report(assessment, html_path) await generate_html_report(assessment, html_path, bundesland=bundesland)
try: try:
from weasyprint import HTML from weasyprint import HTML

View File

@ -171,6 +171,31 @@
color: var(--color-blue); color: var(--color-blue);
} }
/* Bundesland-Badge: Im Listen-Item links neben der Drucksachen-Nummer.
Im Bundesland-spezifischen Modus per data-mode="single" am Container
ausgeblendet (redundant, da alle Einträge demselben Land zugehören). */
.bl-badge {
display: inline-block;
padding: 1px 6px;
margin-right: 0.4rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--color-blue);
border: 1px solid var(--color-blue);
border-radius: 3px;
vertical-align: middle;
}
.list-content[data-mode="single"] .bl-badge { display: none; }
/* Detail-Header: Parlament-Name unter dem Titel, vor der Drucksache-Zeile */
.detail-parlament {
font-size: 0.85rem;
color: var(--color-blue);
font-weight: 600;
margin: 0.2rem 0 0.1rem;
}
.list-item-score { .list-item-score {
font-size: 0.9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
@ -698,6 +723,7 @@
</div> </div>
<div class="stats-bar" style="padding: 0.5rem 1rem; gap: 1rem; flex-wrap: wrap; align-items: center;"> <div class="stats-bar" style="padding: 0.5rem 1rem; gap: 1rem; flex-wrap: wrap; align-items: center;">
<span style="font-size: 0.8rem;"><strong id="stat-total">0</strong> geprüft · <strong id="stat-high">0</strong> vorbildlich · Ø <strong id="stat-avg">0</strong></span> <span style="font-size: 0.8rem;"><strong id="stat-total">0</strong> geprüft · <strong id="stat-high">0</strong> vorbildlich · Ø <strong id="stat-avg">0</strong></span>
<span id="bundesland-stats" style="font-size: 0.8rem; color: var(--color-darkgray); display: none; gap: 0.6rem; flex-wrap: wrap;"></span>
<span style="color: var(--color-lightgray);">|</span> <span style="color: var(--color-lightgray);">|</span>
<span id="partei-stats" style="font-size: 0.8rem; display: flex; gap: 0.75rem; flex-wrap: wrap;"></span> <span id="partei-stats" style="font-size: 0.8rem; display: flex; gap: 0.75rem; flex-wrap: wrap;"></span>
</div> </div>
@ -770,8 +796,9 @@
<div style="margin-top: 1rem;"> <div style="margin-top: 1rem;">
<label>Bundesland:</label> <label>Bundesland:</label>
<select id="bundesland" style="padding: 0.5rem; margin-left: 0.5rem;"> <select id="bundesland" style="padding: 0.5rem; margin-left: 0.5rem;">
{% for bl in bundeslaender %} <option value="" disabled selected>— Bundesland wählen —</option>
<option value="{{ bl.code }}" {% if bl.active %}selected{% endif %} {% if not bl.active %}disabled{% endif %}> {% for bl in bundeslaender if bl.code != 'ALL' %}
<option value="{{ bl.code }}" {% if not bl.active %}disabled{% endif %}>
{{ bl.name }}{% if not bl.active %} (bald){% endif %} {{ bl.name }}{% if not bl.active %} (bald){% endif %}
</option> </option>
{% endfor %} {% endfor %}
@ -798,14 +825,39 @@
let allAssessments = []; let allAssessments = [];
let currentScoreFilter = 'all'; let currentScoreFilter = 'all';
let currentParteiFilter = ''; let currentParteiFilter = '';
let currentBundesland = 'NRW'; let currentBundesland = 'ALL';
let searchTimeout = null; let searchTimeout = null;
let isSearching = false; let isSearching = false;
let selectedTags = new Set(); let selectedTags = new Set();
let allTags = {}; let allTags = {};
// Load assessments on page load // Map code → parlament_name, vom Backend mit dem Initial-Render geliefert.
// Wird im Detail-Header und im Listen-Item-Badge-Tooltip verwendet.
const PARLAMENT_NAMES = {{ parlament_names | tojson }};
// Load assessments on page load — localStorage-Auswahl wiederherstellen
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const saved = localStorage.getItem('selectedBundesland');
const select = document.getElementById('bundesland-select');
if (saved) {
// Validieren: existiert die Option?
const exists = Array.from(select.options).some(
o => o.value === saved && !o.disabled
);
if (exists) {
currentBundesland = saved;
select.value = saved;
}
}
// Modus-Klasse für CSS (Badges aus/an)
document.getElementById('list-content').dataset.mode =
(currentBundesland === 'ALL') ? 'all' : 'single';
// Landtag-Button-State für Initial-Auswahl
const btnLandtag = document.getElementById('btn-landtag');
if (currentBundesland === 'ALL') {
btnLandtag.disabled = true;
btnLandtag.title = 'Landtag-Suche nur mit konkretem Bundesland möglich';
}
loadAssessments(); loadAssessments();
}); });
@ -944,7 +996,8 @@
async function loadAssessments() { async function loadAssessments() {
try { try {
const resp = await fetch('/api/assessments'); const url = `/api/assessments?bundesland=${encodeURIComponent(currentBundesland)}`;
const resp = await fetch(url);
allAssessments = await resp.json(); allAssessments = await resp.json();
updateStats(); updateStats();
renderList(allAssessments); renderList(allAssessments);
@ -965,6 +1018,35 @@
document.getElementById('stat-high').textContent = high; document.getElementById('stat-high').textContent = high;
document.getElementById('stat-avg').textContent = avg; document.getElementById('stat-avg').textContent = avg;
// Pro-Bundesland-Aufschlüsselung — nur im Bundesweit-Modus, und nur
// wenn tatsächlich mehr als ein Bundesland in der Liste vorkommt.
const blContainer = document.getElementById('bundesland-stats');
if (currentBundesland === 'ALL') {
const blStats = {};
allAssessments.forEach(a => {
if (a.gwoeScore == null || !a.bundesland) return;
if (!blStats[a.bundesland]) blStats[a.bundesland] = { sum: 0, count: 0 };
blStats[a.bundesland].sum += a.gwoeScore;
blStats[a.bundesland].count += 1;
});
const codes = Object.keys(blStats);
if (codes.length > 1) {
const sortedBl = codes
.map(c => ({ code: c, avg: blStats[c].sum / blStats[c].count, count: blStats[c].count }))
.sort((a, b) => b.avg - a.avg);
blContainer.innerHTML = sortedBl.map(b =>
`<span title="${PARLAMENT_NAMES[b.code] || b.code}">Ø <strong>${b.code}</strong> ${b.avg.toFixed(1)} <span style="color:#888">(n=${b.count})</span></span>`
).join(' · ');
blContainer.style.display = 'inline-flex';
} else {
blContainer.style.display = 'none';
blContainer.innerHTML = '';
}
} else {
blContainer.style.display = 'none';
blContainer.innerHTML = '';
}
// Partei-Durchschnitte berechnen // Partei-Durchschnitte berechnen
const parteiStats = {}; const parteiStats = {};
allAssessments.forEach(a => { allAssessments.forEach(a => {
@ -1006,10 +1088,13 @@
const themen = (item.themen || []).slice(0, 3); const themen = (item.themen || []).slice(0, 3);
const scoreText = isUnchecked ? '⏳' : `${item.gwoeScore}/10`; const scoreText = isUnchecked ? '⏳' : `${item.gwoeScore}/10`;
const blBadge = item.bundesland
? `<span class="bl-badge" title="${PARLAMENT_NAMES[item.bundesland] || item.bundesland}">${item.bundesland}</span>`
: '';
return ` return `
<div class="list-item ${isUnchecked ? 'unchecked' : ''}" data-drucksache="${item.drucksache}" onclick="${isUnchecked ? '' : `showDetail('${item.drucksache}')`}"> <div class="list-item ${isUnchecked ? 'unchecked' : ''}" data-drucksache="${item.drucksache}" onclick="${isUnchecked ? '' : `showDetail('${item.drucksache}')`}">
<div class="list-item-header"> <div class="list-item-header">
<span class="list-item-id">${item.drucksache}</span> <span class="list-item-id">${blBadge}${item.drucksache}</span>
<span class="list-item-score ${scoreClass}">${scoreText}</span> <span class="list-item-score ${scoreClass}">${scoreText}</span>
</div> </div>
<div class="list-item-title">${item.title || 'Ohne Titel'}</div> <div class="list-item-title">${item.title || 'Ohne Titel'}</div>
@ -1133,11 +1218,53 @@
function changeBundesland(code) { function changeBundesland(code) {
currentBundesland = code; currentBundesland = code;
localStorage.setItem('selectedBundesland', code);
// Filter zurücksetzen — Parteien & Tags pro Bundesland unterschiedlich,
// ein "LINKE"-Filter aus LSA würde in NRW eine leere Liste zeigen.
currentScoreFilter = 'all';
currentParteiFilter = '';
selectedTags.clear();
document.getElementById('search-input').value = ''; document.getElementById('search-input').value = '';
document.querySelectorAll('.filter-btn').forEach(b => {
b.classList.toggle('active', b.dataset.filter === 'all');
});
const parteiSelect = document.getElementById('partei-filter');
if (parteiSelect) parteiSelect.value = '';
// Upload-Mode-Dropdown synchronisieren. Bei "ALL" KEIN automatischer
// Default — der User soll im Upload-Form bewusst ein Bundesland wählen.
const uploadDropdown = document.getElementById('bundesland');
if (uploadDropdown) {
if (code === 'ALL') {
uploadDropdown.value = '';
} else {
uploadDropdown.value = code;
}
}
// Landtag-Suche-Button im Bundesweit-Modus deaktivieren
const btnLandtag = document.getElementById('btn-landtag');
if (code === 'ALL') {
btnLandtag.disabled = true;
btnLandtag.title = 'Landtag-Suche nur mit konkretem Bundesland möglich';
} else {
btnLandtag.disabled = false;
btnLandtag.title = '';
}
// Modus-Klasse für CSS (Badges aus/an im Single-Modus)
document.getElementById('list-content').dataset.mode =
(code === 'ALL') ? 'all' : 'single';
loadAssessments(); loadAssessments();
} }
async function searchLandtag() { async function searchLandtag() {
if (currentBundesland === 'ALL') {
alert('Landtag-Suche ist nur mit Auswahl eines konkreten Bundeslands möglich.\nBitte oben ein Bundesland auswählen.');
return;
}
const query = document.getElementById('search-input').value.trim(); const query = document.getElementById('search-input').value.trim();
if (query.length < 2) { if (query.length < 2) {
alert('Bitte mindestens 2 Zeichen eingeben'); alert('Bitte mindestens 2 Zeichen eingeben');
@ -1378,6 +1505,7 @@
<div class="detail-header"> <div class="detail-header">
<div> <div>
<div class="detail-title">${item.title || 'Ohne Titel'}</div> <div class="detail-title">${item.title || 'Ohne Titel'}</div>
${item.bundesland && PARLAMENT_NAMES[item.bundesland] ? `<div class="detail-parlament">${PARLAMENT_NAMES[item.bundesland]}</div>` : ''}
<div class="detail-id">${item.drucksache} · ${(item.fraktionen || []).join(', ')} · ${item.datum || ''}</div> <div class="detail-id">${item.drucksache} · ${(item.fraktionen || []).join(', ')} · ${item.datum || ''}</div>
</div> </div>
<div class="score-display"> <div class="score-display">
@ -1502,6 +1630,10 @@
alert('Bitte Text eingeben oder PDF hochladen'); alert('Bitte Text eingeben oder PDF hochladen');
return; return;
} }
if (!bundesland) {
alert('Bitte ein Bundesland wählen.');
return;
}
btn.disabled = true; btn.disabled = true;
statusDiv.style.display = 'block'; statusDiv.style.display = 'block';