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 %}Neuer Antrag — GWÖ-Antragsprüfer{% endblock %}
|
|
|
|
|
|
|
|
|
|
|
|
{% set v2_active_nav = "neu" %}
|
|
|
|
|
|
|
|
|
|
|
|
{% block head_extra %}
|
|
|
|
|
|
<style>
|
|
|
|
|
|
.neu-form {
|
|
|
|
|
|
max-width: 560px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-form label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-family: var(--font-mono);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-form input[type="text"],
|
|
|
|
|
|
.neu-form select {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
font-family: var(--font-mono);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
|
border: 1px solid var(--ecg-border);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background: var(--ecg-card-bg);
|
|
|
|
|
|
color: var(--ecg-dark);
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-form input[type="text"]:focus,
|
|
|
|
|
|
.neu-form select:focus {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
border-color: var(--ecg-teal);
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-submit {
|
|
|
|
|
|
font-family: var(--font-display);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
padding: 10px 24px;
|
|
|
|
|
|
background: var(--ecg-teal);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
letter-spacing: 0.04em;
|
|
|
|
|
|
transition: opacity 0.1s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-submit:hover { opacity: 0.85; }
|
|
|
|
|
|
.neu-submit:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
|
|
|
|
#neu-status {
|
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
|
font-family: var(--font-mono);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-progress {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
color: var(--ecg-teal);
|
|
|
|
|
|
}
|
|
|
|
|
|
.neu-spinner {
|
|
|
|
|
|
width: 14px; height: 14px;
|
|
|
|
|
|
border: 2px solid var(--ecg-teal);
|
|
|
|
|
|
border-top-color: transparent;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 0.7s linear infinite;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
|
|
.neu-error { color: #c00; }
|
|
|
|
|
|
</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;">Neuer Antrag prüfen</h1>
|
|
|
|
|
|
<p style="font-size:12px;font-family:var(--font-mono);color:var(--ecg-dark);opacity:0.6;">
|
|
|
|
|
|
Drucksachen-Nr. eingeben · Analyse startet sofort
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="v2-kasten outline-blue" style="max-width:560px;margin-bottom:1.5rem;">
|
|
|
|
|
|
<p style="font-size:12px;">
|
|
|
|
|
|
Der Prüfer holt den Antragstext aus dem Landtags-Portal, bewertet ihn nach der GWÖ-Matrix 2.0
|
|
|
|
|
|
und zeigt Wahlprogramm-Treue sowie Verbesserungsvorschläge. Die Analyse dauert 30–90 Sekunden.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<form class="neu-form" onsubmit="startAnalyse(event)">
|
|
|
|
|
|
|
|
|
|
|
|
<label for="neu-drucksache">Drucksachen-Nummer</label>
|
|
|
|
|
|
<input type="text" id="neu-drucksache" name="drucksache"
|
|
|
|
|
|
placeholder="z. B. 18/12345 oder NRW-18/12345"
|
|
|
|
|
|
required autocomplete="off">
|
|
|
|
|
|
|
|
|
|
|
|
<label for="neu-bl">Bundesland</label>
|
|
|
|
|
|
<select id="neu-bl" name="bundesland">
|
|
|
|
|
|
{% for bl in bundeslaender %}
|
|
|
|
|
|
<option value="{{ bl.code }}"{% if bl.code == 'NRW' %} selected{% endif %}>{{ bl.name }} ({{ bl.code }})</option>
|
|
|
|
|
|
{% endfor %}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
<label for="neu-model">Modell</label>
|
|
|
|
|
|
<select id="neu-model" name="model">
|
|
|
|
|
|
<option value="">Standard ({{ default_model }})</option>
|
|
|
|
|
|
<option value="qwen-plus">qwen-plus</option>
|
|
|
|
|
|
<option value="qwen-max">qwen-max</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
<button type="submit" class="neu-submit" id="neu-btn">Analyse starten</button>
|
|
|
|
|
|
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="neu-status">
|
|
|
|
|
|
<div id="neu-progress" class="neu-progress" style="display:none;">
|
|
|
|
|
|
<div class="neu-spinner"></div>
|
|
|
|
|
|
<span id="neu-progress-text">Analyse läuft …</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="neu-error" class="neu-error" style="display:none;"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
|
|
{% block body_scripts %}
|
|
|
|
|
|
<script>
|
|
|
|
|
|
async function startAnalyse(e) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const btn = document.getElementById('neu-btn');
|
|
|
|
|
|
const statusEl = document.getElementById('neu-status');
|
|
|
|
|
|
const progEl = document.getElementById('neu-progress');
|
|
|
|
|
|
const progText = document.getElementById('neu-progress-text');
|
|
|
|
|
|
const errEl = document.getElementById('neu-error');
|
|
|
|
|
|
|
|
|
|
|
|
const drucksache = document.getElementById('neu-drucksache').value.trim();
|
|
|
|
|
|
const bundesland = document.getElementById('neu-bl').value;
|
|
|
|
|
|
const model = document.getElementById('neu-model').value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!drucksache) return;
|
|
|
|
|
|
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
statusEl.style.display = '';
|
|
|
|
|
|
progEl.style.display = '';
|
|
|
|
|
|
errEl.style.display = 'none';
|
|
|
|
|
|
progText.textContent = 'Analyse läuft … (30–90 s)';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fd = new FormData();
|
|
|
|
|
|
fd.append('drucksache', drucksache);
|
|
|
|
|
|
fd.append('bundesland', bundesland);
|
|
|
|
|
|
if (model) fd.append('model', model);
|
|
|
|
|
|
|
|
|
|
|
|
const resp = await fetch('/api/analyze-drucksache', { method: 'POST', body: fd });
|
2026-04-25 21:35:55 +02:00
|
|
|
|
if (resp.status === 401 || resp.status === 403) {
|
|
|
|
|
|
progEl.style.display = 'none';
|
|
|
|
|
|
errEl.style.display = '';
|
|
|
|
|
|
errEl.textContent = 'Sitzung abgelaufen — bitte erneut anmelden.';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
if (typeof window.v2AuthModalOpen === 'function') window.v2AuthModalOpen();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
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
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
|
const err = await resp.json().catch(() => ({ detail: resp.statusText }));
|
|
|
|
|
|
throw new Error(err.detail || ('HTTP ' + resp.status));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
|
const ds = data.drucksache || drucksache;
|
2026-04-25 21:35:55 +02:00
|
|
|
|
|
|
|
|
|
|
if (data.status === 'already_checked') {
|
|
|
|
|
|
progText.textContent = 'Bereits bewertet. Weiterleitung …';
|
|
|
|
|
|
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 400);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (data.status === 'skipped') {
|
|
|
|
|
|
progEl.style.display = 'none';
|
|
|
|
|
|
errEl.style.display = '';
|
|
|
|
|
|
errEl.textContent = 'Antrag-Typ "' + (data.typ || 'unbekannt') + '" ist nicht abstimmbar — keine GWÖ-Bewertung sinnvoll.';
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Job-Polling bis Abschluss, dann redirect
|
|
|
|
|
|
const jobId = data.job_id;
|
|
|
|
|
|
if (!jobId) { window.location.href = '/antrag/' + encodeURIComponent(ds); return; }
|
|
|
|
|
|
let attempts = 0;
|
|
|
|
|
|
const maxAttempts = 90;
|
|
|
|
|
|
while (attempts < maxAttempts) {
|
|
|
|
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
|
|
|
|
attempts++;
|
|
|
|
|
|
const st = await fetch('/status/' + jobId).then(r => r.json()).catch(() => null);
|
|
|
|
|
|
if (!st) continue;
|
|
|
|
|
|
progText.textContent = 'Analyse läuft … (' + (st.status || '?') + ', ~' + (attempts * 2) + 's)';
|
|
|
|
|
|
if (st.status === 'completed') {
|
|
|
|
|
|
progText.textContent = 'Fertig. Weiterleitung …';
|
|
|
|
|
|
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 400);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (st.status === 'failed' || st.status === 'rejected') {
|
|
|
|
|
|
throw new Error('Analyse fehlgeschlagen: ' + (st.error || 'unbekannt'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
throw new Error('Analyse-Timeout (>3 min)');
|
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
|
|
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
progEl.style.display = 'none';
|
|
|
|
|
|
errEl.style.display = '';
|
|
|
|
|
|
errEl.textContent = 'Fehler: ' + err.message;
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
{% endblock %}
|