feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
{% extends "v2/base.html" %}
{% block title %}Cluster — GWÖ-Antragsprüfer{% endblock %}
{% set v2_active_nav = "cluster" %}
{% block head_extra %}
2026-05-06 18:21:15 +02:00
< script src = "/static/d3.v7.min.js" defer > < / script >
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< style >
2026-05-06 18:21:15 +02:00
#cluster-graph {
margin: 1rem 0;
border: 1px solid var(--hairline);
border-radius: 6px;
background: var(--ecg-card-bg);
overflow: hidden;
}
#cluster-graph svg { display: block; width: 100%; height: 420px; }
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
.cluster-toolbar {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 1rem;
padding: 10px 12px;
background: var(--ecg-bg-subtle);
border-radius: 4px;
font-size: 12px;
font-family: var(--font-mono);
}
.cluster-toolbar select {
font-family: var(--font-mono);
font-size: 11px;
padding: 4px 8px;
border: 1px solid var(--ecg-border);
border-radius: 3px;
background: var(--ecg-card-bg);
color: var(--ecg-dark);
}
.cluster-card {
background: var(--ecg-card-bg);
border: 1px solid var(--ecg-border);
border-radius: 6px;
padding: 14px 16px;
margin-bottom: 10px;
cursor: pointer;
transition: border-color 0.1s;
}
.cluster-card:hover { border-color: var(--ecg-teal); }
.cluster-card h3 {
font-family: var(--font-display);
font-size: 14px;
color: var(--ecg-teal);
margin: 0 0 4px;
}
.cluster-meta {
display: flex;
gap: 10px;
font-size: 11px;
font-family: var(--font-mono);
opacity: 0.65;
margin-bottom: 6px;
flex-wrap: wrap;
}
.cluster-score {
font-weight: 700;
color: var(--ecg-green);
}
.cluster-fraktionen {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-top: 6px;
}
.fraktion-bar {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 10px;
font-family: var(--font-mono);
padding: 2px 6px;
border-radius: 3px;
background: var(--ecg-bg-subtle);
border: 1px solid var(--ecg-border);
}
/* Cluster detail panel */
#cluster-detail {
display: none;
margin-top: 1rem;
}
#cluster-detail-back {
font-family: var(--font-mono);
font-size: 11px;
color: var(--ecg-teal);
cursor: pointer;
margin-bottom: 10px;
display: inline-block;
}
#cluster-detail-back:hover { text-decoration: underline; }
< / style >
{% endblock %}
{% block main %}
< div style = "padding:0 0 1.5rem;" >
< h1 style = "font-family:var(--font-display);font-size:22px;color:var(--ecg-teal);margin:0 0 4px;" > Cluster< / h1 >
< p style = "font-size:12px;font-family:var(--font-mono);color:var(--ecg-dark);opacity:0.6;" >
Thematisch ähnliche Anträge · Cosine-Similarity über Embeddings
< / p >
< / div >
<!-- Toolbar -->
< div class = "cluster-toolbar" >
< label for = "cl-bl" > Bundesland:< / label >
< select id = "cl-bl" onchange = "loadClusters()" >
< option value = "" > Bundesweit< / option >
{% for code in bl_codes %}
< option value = "{{ code }}" > {{ code }}< / option >
{% endfor %}
< / select >
< label for = "cl-thr" style = "margin-left:8px;" > Schwellenwert:< / label >
< input type = "range" id = "cl-thr" min = "0.40" max = "0.80" step = "0.05" value = "0.55"
style="width:100px;"
oninput="document.getElementById('cl-thr-val').textContent = parseFloat(this.value).toFixed(2)"
onchange="loadClusters()">
< span id = "cl-thr-val" style = "min-width:30px;" > 0.55< / span >
< button onclick = "loadClusters()"
style="font-family:var(--font-mono);font-size:11px;padding:4px 10px;
background:var(--ecg-teal);color:#fff;border:none;border-radius:3px;cursor:pointer;
margin-left:8px;">
Aktualisieren
< / button >
< / div >
<!-- Main list view -->
< div id = "cluster-list" >
< div style = "font-family:var(--font-mono);font-size:12px;opacity:0.5;" > Lade Cluster …< / div >
< / div >
<!-- Detail view (shown when cluster is clicked) -->
< div id = "cluster-detail" >
< span id = "cluster-detail-back" onclick = "showList()" > ← Zurück zur Übersicht< / span >
< div id = "cluster-detail-content" > < / div >
< / div >
<!-- Link to classic Force - Graph -->
< div style = "margin-top:1.5rem;font-size:11px;font-family:var(--font-mono);opacity:0.6;" >
Vollständige Force-Graph-Visualisierung:
< a href = "/classic?mode=clusters" style = "color:var(--ecg-teal);" > Klassische Ansicht →< / a >
< / div >
{% endblock %}
{% block body_scripts %}
< script >
let _clusters = [];
async function loadClusters() {
const listEl = document.getElementById('cluster-list');
const bl = document.getElementById('cl-bl').value;
const thr = document.getElementById('cl-thr').value;
listEl.innerHTML = '< div style = "font-family:var(--font-mono);font-size:12px;opacity:0.5;" > Lade Cluster …< / div > ';
document.getElementById('cluster-detail').style.display = 'none';
listEl.style.display = '';
let url = '/api/clusters?threshold=' + thr;
if (bl) url += '& bundesland=' + encodeURIComponent(bl);
try {
const resp = await fetch(url);
const data = await resp.json();
_clusters = data.clusters || [];
if (!_clusters.length) {
listEl.innerHTML = '< div class = "v2-kasten outline-green" > < h4 > Keine Cluster gefunden< / h4 > < p > Mit diesem Schwellenwert entstehen keine Gruppen. Versuchen Sie einen niedrigeren Wert.< / p > < / div > ';
return;
}
// Sort by size descending
_clusters.sort((a, b) => (b.members || []).length - (a.members || []).length);
// Top-10 list
const top = _clusters.slice(0, 10);
listEl.innerHTML = top.map((cl, idx) => renderClusterCard(cl, idx)).join('');
} catch (e) {
listEl.innerHTML = '< div style = "color:var(--ecg-dark);font-family:var(--font-mono);font-size:12px;" > Fehler: ' + e.message + '< / div > ';
}
}
function renderClusterCard(cl, idx) {
2026-05-06 17:52:36 +02:00
// Backend liefert drucksachen+avg_gwoe_score; alte UI-Variante hatte members+avg_score.
const members = cl.drucksachen || cl.members || [];
const avgScoreRaw = cl.avg_gwoe_score != null ? cl.avg_gwoe_score : cl.avg_score;
const avgScore = avgScoreRaw != null ? parseFloat(avgScoreRaw).toFixed(1) : '—';
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
const label = cl.label || cl.title || ('Cluster ' + (idx + 1));
2026-05-06 17:52:36 +02:00
const dom = cl.dominant_fraktion || '';
const size = cl.size != null ? cl.size : members.length;
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
2026-05-06 17:52:36 +02:00
const fraktionen = cl.fraktionen || (dom ? {[dom]: size} : {});
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
const frakBars = Object.entries(fraktionen)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([f, n]) => `< span class = "fraktion-bar" > ${f} < strong > ${n}< / strong > < / span > `)
.join('');
return `< div class = "cluster-card" onclick = "showCluster(${idx})" >
< h3 > ${label}< / h3 >
< div class = "cluster-meta" >
2026-05-06 17:52:36 +02:00
< span > ${size} Antrag${size !== 1 ? 'e' : ''}< / span >
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< span class = "cluster-score" > Ø ${avgScore}< / span >
< / div >
${frakBars ? `< div class = "cluster-fraktionen" > ${frakBars}< / div > ` : ''}
< / div > `;
}
function showCluster(idx) {
const cl = _clusters[idx];
if (!cl) return;
document.getElementById('cluster-list').style.display = 'none';
const detail = document.getElementById('cluster-detail');
const content = document.getElementById('cluster-detail-content');
2026-05-06 18:32:28 +02:00
detail.style.display = 'block';
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
2026-05-06 17:52:36 +02:00
const members = cl.drucksachen || cl.members || [];
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
const label = cl.label || cl.title || ('Cluster ' + (idx + 1));
2026-05-06 17:52:36 +02:00
const avgScoreRaw = cl.avg_gwoe_score != null ? cl.avg_gwoe_score : cl.avg_score;
const avgScore = avgScoreRaw != null ? parseFloat(avgScoreRaw).toFixed(1) : '—';
const size = cl.size != null ? cl.size : members.length;
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
content.innerHTML = `
< div style = "margin-bottom:1rem;" >
< h2 style = "font-family:var(--font-display);font-size:18px;color:var(--ecg-teal);" > ${label}< / h2 >
< p style = "font-family:var(--font-mono);font-size:12px;opacity:0.65;" >
2026-05-06 17:52:36 +02:00
${size} Anträge · Ø Score ${avgScore}
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< / p >
< / div >
2026-05-06 18:21:15 +02:00
< div id = "cluster-graph" > < / div >
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< div role = "list" >
2026-05-06 17:52:36 +02:00
${members.map(m => {
// m kann String (Drucksache-ID) oder Objekt sein
const isObj = typeof m === 'object' & & m !== null;
const ds = isObj ? (m.drucksache || '') : m;
const bl = isObj ? (m.bundesland || '') : '';
const titel = isObj ? (m.titel || ds) : ds;
const score = isObj & & m.gwoe_score != null ? parseFloat(m.gwoe_score).toFixed(1) : null;
const url = `/antrag/${encodeURIComponent(ds)}` + (bl ? `?bundesland=${encodeURIComponent(bl)}` : '');
return `
< a href = "${url}" class = "v2-result-row" style = "display:block;text-decoration:none;" >
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< div class = "v2-result-meta" >
2026-05-06 17:52:36 +02:00
${bl ? `< span class = "v2-chip" style = "font-size:10px;" > ${bl}< / span > ` : ''}
< span style = "font-size:11px;opacity:0.6;font-family:var(--font-mono);" > ${ds}< / span >
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< / div >
2026-05-06 17:52:36 +02:00
< div class = "v2-result-title" > ${titel}< / div >
${score ? `< div style = "font-family:var(--font-mono);font-size:12px;color:var(--ecg-teal);font-weight:700;" > Score ${score}< / div > ` : ''}
< / a > `;
}).join('')}
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
< / div > `;
2026-05-06 18:21:15 +02:00
// Force-Graph rendern (wenn d3 + nodes/edges da sind)
renderClusterGraph(cl);
}
function renderClusterGraph(cl) {
const container = document.getElementById('cluster-graph');
if (!container || typeof d3 === 'undefined') return;
const rawNodes = cl.nodes || [];
const rawEdges = cl.edges || [];
if (!rawNodes.length) {
container.innerHTML = '< div style = "padding:14px;font-family:var(--font-mono);font-size:11px;opacity:0.55;" > Keine Graph-Daten verfügbar.< / div > ';
return;
}
container.innerHTML = '';
const width = container.clientWidth || 720;
const height = 420;
const fraktionColor = (f) => {
const map = {
'CDU': '#000', 'CSU': '#0a3', 'SPD': '#e30613',
'GRÜNE': '#1faf38', 'FDP': '#ffd400', 'AfD': '#0089cf',
'LINKE': '#be3075', 'BSW': '#7b2d8e', 'FW': '#f57f17',
};
return map[f] || '#888';
};
2026-05-06 22:11:16 +02:00
// Edges sind Index-basiert (a/b sind Positionen in rawNodes).
// d3.forceLink mappt per id-Lookup → wir nutzen den Index als id-String.
const nodes = rawNodes.map((n, i) => ({
id: String(i),
2026-05-06 18:21:15 +02:00
drucksache: n.drucksache,
title: n.title || n.titel || n.drucksache,
bundesland: n.bundesland || '',
fraktion: (n.fraktionen & & n.fraktionen[0]) || '',
score: n.gwoe_score != null ? parseFloat(n.gwoe_score) : 5,
}));
2026-05-06 22:11:16 +02:00
const links = rawEdges.map(e => ({
source: String(e.a),
target: String(e.b),
sim: e.sim || 0.5,
}));
2026-05-06 18:21:15 +02:00
const svg = d3.select(container).append('svg')
.attr('width', width).attr('height', height)
.attr('viewBox', `0 0 ${width} ${height}`);
const sim = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.id)
.distance(l => (1 - (l.sim || 0)) * 220 + 50)
.strength(l => l.sim || 0.5))
.force('charge', d3.forceManyBody().strength(-380))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide().radius(d => Math.sqrt(Math.max(1, d.score)) * 6 + 6));
const link = svg.append('g')
.attr('stroke', '#999').attr('stroke-opacity', 0.4)
.selectAll('line').data(links).join('line')
.attr('stroke-width', d => Math.max(0.5, (d.sim || 0.5) * 3));
const node = svg.append('g').selectAll('g')
.data(nodes).join('g')
.style('cursor', 'pointer')
.call(d3.drag()
.on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
.on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; }))
.on('click', (e, d) => {
const url = `/antrag/${encodeURIComponent(d.drucksache)}` + (d.bundesland ? `?bundesland=${encodeURIComponent(d.bundesland)}` : '');
window.location.href = url;
});
node.append('circle')
.attr('r', d => Math.sqrt(Math.max(1, d.score)) * 5 + 4)
.attr('fill', d => fraktionColor(d.fraktion))
.attr('fill-opacity', 0.85)
.attr('stroke', '#fff').attr('stroke-width', 1.5);
node.append('title')
.text(d => `${d.title}\n${d.bundesland} · ${d.fraktion || '?'} · Score ${d.score.toFixed(1)}/10\nKlick öffnet Detail`);
node.append('text')
.attr('x', 0)
.attr('y', d => Math.sqrt(Math.max(1, d.score)) * 5 + 14)
.attr('text-anchor', 'middle')
.attr('font-size', '9px').attr('fill', '#333')
.text(d => (d.title || d.drucksache).substring(0, 32));
sim.on('tick', () => {
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
node.attr('transform', d => `translate(${d.x},${d.y})`);
});
feat(#139,#129,#138,#141): v2-Frontend (ECOnGOOD-CD), Login-Modal, Auto-DL, OG-Cards
v2-Frontend (#139, ECOnGOOD CD Manual Juni 2024):
- app/static/v2/: tokens.css, fonts.css, v2.css, Nunito-Sans woff2, Phosphor-Icons (21 SVGs)
- app/templates/v2/: base.html + 11 Screens + 8 Component-Macros
- AppShell mit Sidebar (Lesen/Pruefen/Daten/Admin), v2-Detail mit allen Features
(ScoreHero, MatrixMini, QuoteCard, Redline, Fraktions-Scores)
- v2 ist jetzt Default unter / — classic unter /classic
- Login-Modal in v2-Topbar mit Tabs Anmelden/Registrieren (#129)
- Phosphor-Icons in Sidebar + Topbar mit dynamischem Theme-Toggle
- Keyboard-Shortcuts (j/k/Enter/Esc/?/path), Landtag-Suche, Antrag-Historie,
Sort-Dropdown, Matrix-Feld-Info-Modal, Bookmarks/Comments/Voting/Share/Re-Analyze
Backend-Erweiterungen:
- main.py: ~30 neue Routes (/v2/*, /antrag/{ds}, /api/auth/{login,refresh,logout},
/api/me/merkliste/*, /api/admin/*, /v2/admin/*, OG-Cards, etc.)
- og_card.py + og_template: Open-Graph-Bilder via Playwright (#141)
- wahlprogramm_fetch.py + wahlprogramm-links.yaml: SHA-Gate Auto-DL (#138)
- auswertungen.py: BL-Filter + get_wahlperioden Helper (#137)
- auth.py: Direct-Access-Grant + Refresh-Token-Cookie
Classic-Updates:
- Header-DRY via _header.html, Auswertungen redirected, Batch-Inline raus
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:55:57 +02:00
}
function showList() {
document.getElementById('cluster-list').style.display = '';
document.getElementById('cluster-detail').style.display = 'none';
}
// Initial load
loadClusters();
< / script >
{% endblock %}