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>
220 lines
7.7 KiB
HTML
220 lines
7.7 KiB
HTML
{% extends "v2/base.html" %}
|
|
|
|
{% block title %}Quellen — GWÖ-Antragsprüfer{% endblock %}
|
|
|
|
{% set v2_active_nav = "" %}
|
|
|
|
{% block head_extra %}
|
|
<style>
|
|
.quellen-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: 12px;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.prog-card {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding: 14px;
|
|
background: var(--ecg-card-bg);
|
|
border: 1px solid var(--ecg-border);
|
|
border-radius: 6px;
|
|
}
|
|
.prog-card img {
|
|
width: 64px;
|
|
height: auto;
|
|
border: 1px solid var(--ecg-border);
|
|
border-radius: 3px;
|
|
flex-shrink: 0;
|
|
object-fit: cover;
|
|
}
|
|
.prog-meta { font-size: 11px; opacity: 0.6; margin-top: 2px; margin-bottom: 6px; }
|
|
.prog-badge {
|
|
display: inline-block;
|
|
padding: 1px 5px;
|
|
border-radius: 3px;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
margin-right: 4px;
|
|
font-family: var(--font-mono);
|
|
}
|
|
.badge-spd { background: #e3000f; color: #fff; }
|
|
.badge-cdu { background: #2c2c2c; color: #fff; }
|
|
.badge-gruene,.badge-grüne { background: #46962b; color: #fff; }
|
|
.badge-fdp { background: #ffed00; color: #222; }
|
|
.badge-afd { background: #009ee0; color: #fff; }
|
|
.badge-linke { background: #be3075; color: #fff; }
|
|
.badge-type-wp { background: var(--ecg-teal); color: #fff; }
|
|
.badge-type-pp { background: var(--ecg-green); color: #fff; }
|
|
.indexed-ok { color: var(--ecg-green); font-size: 11px; font-family: var(--font-mono); }
|
|
.indexed-no { color: var(--ecg-dark); opacity: 0.4; font-size: 11px; font-family: var(--font-mono); }
|
|
.stat-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
gap: 10px;
|
|
margin: 10px 0;
|
|
}
|
|
.stat-cell {
|
|
text-align: center;
|
|
padding: 12px;
|
|
background: var(--ecg-bg-subtle);
|
|
border-radius: 4px;
|
|
}
|
|
.stat-val { font-family: var(--font-display); font-size: 28px; color: var(--ecg-teal); font-weight: 900; }
|
|
.stat-lbl { font-size: 11px; opacity: 0.6; margin-top: 2px; }
|
|
.section-h2 {
|
|
font-family: var(--font-display);
|
|
font-size: 16px;
|
|
color: var(--ecg-teal);
|
|
margin: 1.5rem 0 0.75rem;
|
|
padding-bottom: 4px;
|
|
border-bottom: 2px solid var(--ecg-teal);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block main %}
|
|
<div style="padding:0 0 2rem;">
|
|
<h1 style="font-family:var(--font-display);font-size:22px;color:var(--ecg-teal);margin:0 0 4px;">Quellen & Referenzdokumente</h1>
|
|
<p style="font-size:12px;font-family:var(--font-mono);color:var(--ecg-dark);opacity:0.6;">
|
|
Wahl- und Grundsatzprogramme · semantisch indexiert
|
|
</p>
|
|
</div>
|
|
|
|
<div class="v2-kasten outline-blue" style="margin-bottom:1.5rem;">
|
|
<p>
|
|
Der GWÖ-Antragsprüfer vergleicht parlamentarische Anträge mit den Wahl- und Grundsatzprogrammen
|
|
der Parteien. Hier finden Sie alle verwendeten Originaldokumente zum Download.
|
|
</p>
|
|
<p style="font-size:11px;opacity:0.7;margin-top:6px;">
|
|
Die Programme werden semantisch indexiert, um relevante Passagen für jeden Antrag zu finden.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Indexierungsstatus -->
|
|
<div class="v2-kasten outline-green" style="margin-bottom:1.5rem;">
|
|
<h4>Indexierungsstatus</h4>
|
|
<div class="stat-grid">
|
|
<div class="stat-cell">
|
|
<div class="stat-val">{{ status.indexed }}</div>
|
|
<div class="stat-lbl">Indexiert</div>
|
|
</div>
|
|
<div class="stat-cell">
|
|
<div class="stat-val">{{ status.total }}</div>
|
|
<div class="stat-lbl">Gesamt</div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:8px;">
|
|
<button onclick="indexAll()"
|
|
style="font-family:var(--font-mono);font-size:11px;padding:5px 12px;background:var(--ecg-teal);color:#fff;border:none;border-radius:3px;cursor:pointer;">
|
|
Alle Programme indexieren (Admin)
|
|
</button>
|
|
<span id="index-status" style="margin-left:10px;font-size:11px;opacity:0.7;"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Wahlprogramme nach BL -->
|
|
{% for bl_name, bl_progs in wahlprogramme_grouped %}
|
|
<h2 class="section-h2">{{ bl_name }}</h2>
|
|
<div class="quellen-grid">
|
|
{% for prog in bl_progs %}
|
|
<div class="prog-card">
|
|
<a href="{{ prog.pdf_url }}" target="_blank" style="flex-shrink:0;">
|
|
<img src="/api/programme/thumbnail/{{ prog.id }}" alt="{{ prog.name }}"
|
|
loading="lazy" onerror="this.style.display='none'">
|
|
</a>
|
|
<div style="min-width:0;">
|
|
<div style="font-family:var(--font-display);font-size:13px;margin-bottom:3px;">
|
|
<span class="prog-badge badge-{{ prog.partei|lower|replace('ü','ue')|replace('ä','ae')|replace('ö','oe') }}">{{ prog.partei }}</span>
|
|
{{ prog.name }}
|
|
</div>
|
|
<div class="prog-meta">
|
|
<span class="prog-badge badge-type-wp">Wahlprogramm</span>
|
|
{% if prog.bundesland %}{{ prog.bundesland }}{% endif %}
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
<a href="{{ prog.pdf_url }}" target="_blank"
|
|
style="font-size:11px;font-family:var(--font-mono);color:var(--ecg-teal);">
|
|
PDF herunterladen
|
|
</a>
|
|
{% for s in status.programmes if s.id == prog.id %}
|
|
{% if s.indexed %}
|
|
<span class="indexed-ok">✓ {{ s.chunks }} Chunks</span>
|
|
{% else %}
|
|
<span class="indexed-no">○ Nicht indexiert</span>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Grundsatzprogramme -->
|
|
<h2 class="section-h2">Grundsatzprogramme (Bundesebene)</h2>
|
|
<div class="quellen-grid">
|
|
{% for prog in grundsatzprogramme %}
|
|
<div class="prog-card">
|
|
<a href="{{ prog.pdf_url }}" target="_blank" style="flex-shrink:0;">
|
|
<img src="/api/programme/thumbnail/{{ prog.id }}" alt="{{ prog.name }}"
|
|
loading="lazy" onerror="this.style.display='none'">
|
|
</a>
|
|
<div style="min-width:0;">
|
|
<div style="font-family:var(--font-display);font-size:13px;margin-bottom:3px;">
|
|
<span class="prog-badge badge-{{ prog.partei|lower|replace('ü','ue')|replace('ä','ae')|replace('ö','oe') }}">{{ prog.partei }}</span>
|
|
{{ prog.name }}
|
|
</div>
|
|
<div class="prog-meta">
|
|
<span class="prog-badge badge-type-pp">Grundsatzprogramm</span>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
<a href="{{ prog.pdf_url }}" target="_blank"
|
|
style="font-size:11px;font-family:var(--font-mono);color:var(--ecg-teal);">
|
|
PDF herunterladen
|
|
</a>
|
|
{% for s in status.programmes if s.id == prog.id %}
|
|
{% if s.indexed %}
|
|
<span class="indexed-ok">✓ {{ s.chunks }} Chunks</span>
|
|
{% else %}
|
|
<span class="indexed-no">○ Nicht indexiert</span>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="v2-kasten outline-green" style="margin-top:1rem;">
|
|
<h4>Hinweise zur Methodik</h4>
|
|
<ul style="margin:6px 0 0 1.2rem;font-size:12px;">
|
|
<li>Die Programme werden in semantische Chunks aufgeteilt (~400 Wörter)</li>
|
|
<li>Jeder Chunk wird mit einem Embedding-Modell vektorisiert</li>
|
|
<li>Bei der Analyse wird der Antrag ebenfalls vektorisiert</li>
|
|
<li>Die ähnlichsten Passagen werden als Kontext an das LLM übergeben</li>
|
|
<li>Das LLM zitiert nur, wenn eine Passage wirklich zur Argumentation passt</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block body_scripts %}
|
|
<script>
|
|
async function indexAll() {
|
|
const statusEl = document.getElementById('index-status');
|
|
statusEl.textContent = 'Indexierung gestartet …';
|
|
try {
|
|
const fd = new FormData();
|
|
fd.append('all_programmes', 'true');
|
|
const resp = await fetch('/api/programme/index', { method: 'POST', body: fd });
|
|
const data = await resp.json();
|
|
statusEl.textContent = 'Indexierung läuft für ' + data.programmes.length + ' Programme';
|
|
setTimeout(() => location.reload(), 30000);
|
|
} catch (e) {
|
|
statusEl.textContent = 'Fehler: ' + e.message;
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|