diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 716382f..e4df87a 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -44,6 +44,7 @@ Karte Fristen Fraktionen + Anleitung @@ -80,6 +81,7 @@ menuOpen = false} class="block text-gray-600 hover:text-gray-900 hover:bg-gray-50 px-3 py-3 rounded-md text-base font-medium">Karte menuOpen = false} class="block text-gray-600 hover:text-gray-900 hover:bg-gray-50 px-3 py-3 rounded-md text-base font-medium">Fristen menuOpen = false} class="block text-gray-600 hover:text-gray-900 hover:bg-gray-50 px-3 py-3 rounded-md text-base font-medium">Fraktionen + menuOpen = false} class="block text-gray-600 hover:text-gray-900 hover:bg-gray-50 px-3 py-3 rounded-md text-base font-medium">Anleitung {/if} diff --git a/frontend/src/routes/anleitung/+page.svelte b/frontend/src/routes/anleitung/+page.svelte new file mode 100644 index 0000000..5992aa3 --- /dev/null +++ b/frontend/src/routes/anleitung/+page.svelte @@ -0,0 +1,381 @@ + + + + Anleitung — Antragstracker Hagen + + +
+

Anleitung

+

Wie der Antragstracker funktioniert — und wie du ihn nutzt.

+ + + + + +
+

Was ist der Antragstracker?

+
+

+ Der Antragstracker ist ein kommunalpolitisches Kontroll-Instrument für den Rat der Stadt Hagen. + Er verfolgt den Weg von Anträgen, Anfragen und Beschlüssen durch die Gremien — und macht sichtbar, + was beschlossen wurde und was davon tatsächlich umgesetzt wird. +

+

+ Daten werden aus dem Ratsinformationssystem (Allris) importiert. Eine KI analysiert den aktuellen + Stand jedes Vorgangs und bewertet, ob beschlossene Maßnahmen umgesetzt wurden. +

+
+
+ + +
+

Die vier Verfahrensstränge

+

+ Jeder Vorgang im Rat folgt einem von vier Verfahrenssträngen. Jeder Strang hat eigene Schritte, + eine eigene Ampel — und eine eigene Kontrollfrage. +

+ + {#if error} +
{error}
+ {/if} + +
+ {#each STRANG_ORDER as key} + {@const farben = STRANG_FARBEN[key]} + {@const mock = MOCK_AMPELN[key]} + {@const ampelData = buildMockAmpel(key)} +
+

{strangLabel(key)}

+

{mock.beschreibung}

+ + {#if ampelData} +
+ +
+ {/if} + + {#if mock.kontrollfrage} +

+ Kontrollfrage: {mock.kontrollfrage} +

+ {/if} + +

+ Ablauf: {mock.ablauf} +

+
+ {/each} +
+
+ + +
+

Die Ampel verstehen

+
+

+ Jeder Vorgang hat eine Ampel, die seinen Fortschritt zeigt. Die Schritte werden + von links nach rechts durchlaufen. +

+
+ +
+
+
+
+

Offen (weißer Kreis)

+

Dieser Schritt wurde noch nicht erreicht.

+
+
+
+
+
+

Durchlaufen (grau)

+

Dieser Schritt liegt in der Vergangenheit — er wurde bereits passiert.

+
+
+
+
+
+

Aktuell — wartet (gelb)

+

Der Vorgang befindet sich gerade in diesem Schritt. Es passiert etwas — oder es wird darauf gewartet.

+
+
+
+
+
+

Erledigt (grün)

+

Dieser Schritt ist abgeschlossen — der aktive Schritt leuchtet grün, wenn er positiv abgeschlossen ist.

+
+
+
+
+
+

Gescheitert (rot)

+

Hier ist etwas schiefgegangen — abgelehnt, gescheitert oder nicht umgesetzt.

+
+
+
+ +

Abzweigungen

+

+ Manchmal nimmt ein Vorgang nicht den normalen Weg. In diesen Fällen zeigt die Ampel eine Abzweigung: +

+ +
+
+

Versandet

+

+ Ein Antrag wurde beschlossen, aber nie umgesetzt. Die Verwaltung hat ihn schlicht nicht weiterverfolgt. +

+ +
+ +
+

Abgelehnt

+

+ Der Vorgang wurde in der Beratung oder Abstimmung abgelehnt und nicht weiterverfolgt. +

+ +
+
+
+ + +
+

Die KI-Bewertung

+
+

+ Jeder Vorgang wird von einer KI analysiert. Sie liest die zugehörigen Dokumente aus dem + Ratsinformationssystem und erstellt: +

+
    +
  • Eine Zusammenfassung — worum geht es, was wurde beschlossen?
  • +
  • Einen Umsetzungs-Score — wie weit ist die Umsetzung fortgeschritten (0–100%)?
  • +
+ +

Was bedeutet der Prozentwert?

+
    +
  • 0% — Keine erkennbare Umsetzung
  • +
  • 1–49% — Teilweise umgesetzt, wesentliche Teile fehlen
  • +
  • 50–79% — Überwiegend umgesetzt, aber nicht vollständig
  • +
  • 80–100% — Weitgehend bis vollständig umgesetzt
  • +
+ +

Neubewertung

+

+ Bewertungen können neu angefordert werden — etwa wenn neue Informationen vorliegen oder eine + Einschätzung fragwürdig erscheint. Dabei kann eine Anmerkung mitgegeben werden, die der KI + als zusätzlichen Kontext dient. +

+ +

Versionierung

+

+ Jede Bewertung wird versioniert. Alte Bewertungen bleiben erhalten und sind nachvollziehbar. + So lässt sich verfolgen, wie sich die Einschätzung über die Zeit verändert hat. +

+
+
+ + +
+

Konkrete Szenarien

+
+
+

„Welche beschlossenen Anträge wurden nie umgesetzt?"

+

+ → Explorer öffnen, nach Status „versandet" filtern. + Zeigt alle Vorgänge, bei denen die Verwaltung beschlossene Maßnahmen nicht weiterverfolgt hat. +

+
+
+

„Hat die Verwaltung meine Anfrage wirklich beantwortet?"

+

+ → Anfrage im Explorer suchen, KI-Bewertung lesen. Falls unbefriedigend: Neubewertung mit + eigener Anmerkung anfordern. +

+
+
+

„Welche Fristen laufen demnächst ab?"

+

+ → Fristen öffnen. + Zeigt alle anstehenden und überfälligen Termine aus Beschlusstexten. +

+
+
+

„Wie stimmt meine Fraktion im Vergleich zu anderen ab?"

+

+ → Abstimmungen öffnen. + Vergleicht das Abstimmungsverhalten aller Fraktionen. +

+
+
+
+ + +
+

Fristen-Tracking

+
+

+ In vielen Beschlüssen stecken konkrete Termine: „bis Ende Q2 umsetzen", „Bericht bis März vorlegen". + Der Antragstracker erkennt solche Fristen automatisch per KI und macht sie sichtbar. +

+
    +
  • Automatische Erkennung — Die KI extrahiert Termine aus Beschlusstexten
  • +
  • Manuelle Ergänzung — Fristen können auch von Hand hinzugefügt werden
  • +
  • Farbkodierung — Überfällige Fristen werden rot markiert, anstehende gelb
  • +
+

+ Alle Fristen sind unter /fristen einsehbar, gefiltert nach Status und Zeitraum. +

+
+
+
diff --git a/scripts/check-background-jobs.sh b/scripts/check-background-jobs.sh new file mode 100755 index 0000000..5ea31d1 --- /dev/null +++ b/scripts/check-background-jobs.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Prüft Background-Jobs anhand ihrer Heartbeat-Dateien. +# Per Cron alle 5 Minuten aufrufen: +# */5 * * * * /path/to/antragstracker/scripts/check-background-jobs.sh >> /path/to/antragstracker/data/job-checker.log 2>&1 + +set -euo pipefail +cd "$(dirname "$0")/.." + +DATA_DIR="data" +MAX_STALE_SECONDS=1800 # 30 Minuten ohne Update = stale + +now=$(date +%s) + +check_job() { + local name="$1" + local heartbeat_file="$2" + local restart_cmd="$3" + + if [ ! -f "$heartbeat_file" ]; then + return 0 # Kein Heartbeat = Job nicht aktiv, OK + fi + + local status=$(python3 -c "import json; d=json.load(open('$heartbeat_file')); print(d.get('status','unknown'))" 2>/dev/null || echo "error") + + if [ "$status" = "completed" ]; then + echo "[$(date)] ✅ $name: fertig" + return 0 + fi + + # PID prüfen + local pid=$(python3 -c "import json; d=json.load(open('$heartbeat_file')); print(d.get('pid',''))" 2>/dev/null || echo "") + if [ -n "$pid" ] && [ "$pid" != "null" ] && [ "$pid" != "None" ]; then + if ! kill -0 "$pid" 2>/dev/null; then + echo "[$(date)] ❌ $name: Prozess $pid tot → Neustart" + eval "$restart_cmd" + return 0 + fi + fi + + # Timestamp prüfen + local last_batch=$(python3 -c " +import json, datetime +d = json.load(open('$heartbeat_file')) +ts = d.get('last_batch_at') or d.get('started_at') or '' +if ts: + dt = datetime.datetime.fromisoformat(ts.replace('Z', '+00:00')) + print(int(dt.timestamp())) +else: + print(0) +" 2>/dev/null || echo "0") + + if [ "$last_batch" != "0" ]; then + local age=$((now - last_batch)) + if [ "$age" -gt "$MAX_STALE_SECONDS" ]; then + echo "[$(date)] ⚠️ $name: Heartbeat ${age}s alt (max ${MAX_STALE_SECONDS}s) → Neustart" + # Kill alten Prozess + [ -n "$pid" ] && [ "$pid" != "null" ] && kill "$pid" 2>/dev/null || true + sleep 2 + eval "$restart_cmd" + return 0 + fi + fi + + # Alles gut + local progress=$(python3 -c " +import json +d = json.load(open('$heartbeat_file')) +done = d.get('total_geocoded', d.get('total_done', '?')) +pending = d.get('total_pending', '?') +print(f'{done} done, {pending} pending') +" 2>/dev/null || echo "?") + echo "[$(date)] ✓ $name: läuft (PID $pid, $progress)" +} + +# === Jobs registrieren === + +PROJ_DIR="$(pwd)" +check_job "Geocoding" \ + "$DATA_DIR/geocode-heartbeat.json" \ + "cd '$PROJ_DIR' && nohup bash scripts/geocode_background.sh >> data/geocode.log 2>&1 &" + +# Weitere Jobs hier eintragen: +# check_job "OParl-Sync" "$DATA_DIR/sync-heartbeat.json" "..." diff --git a/scripts/geocode_background.sh b/scripts/geocode_background.sh new file mode 100755 index 0000000..ad24c97 --- /dev/null +++ b/scripts/geocode_background.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Background Geocoding mit Heartbeat alle 20 Minuten +# Schreibt Status nach data/geocode-heartbeat.json + +set -euo pipefail +cd "$(dirname "$0")/.." +HEARTBEAT_FILE="data/geocode-heartbeat.json" +BATCH_SIZE=1200 # ~20 Min bei 1/s + +while true; do + START=$(date +%s) + START_ISO=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + # Run batch + OUTPUT=$(python3 scripts/geocode_pending.py --limit $BATCH_SIZE 2>&1) || true + + END=$(date +%s) + DURATION=$((END - START)) + + # Parse output for stats + GEOCODED=$(echo "$OUTPUT" | grep -oP 'geocoded: \K\d+' 2>/dev/null || echo "0") + FAILED=$(echo "$OUTPUT" | grep -oP 'failed: \K\d+' 2>/dev/null || echo "0") + REMAINING=$(python3 -c " +import sqlite3 +conn = sqlite3.connect('data/tracker.db') +r = conn.execute('SELECT COUNT(*) FROM orte WHERE lat IS NULL').fetchone()[0] +t = conn.execute('SELECT COUNT(*) FROM orte WHERE lat IS NOT NULL').fetchone()[0] +print(f'{r}|{t}') +conn.close() +" 2>/dev/null || echo "0|0") + + PENDING=$(echo "$REMAINING" | cut -d'|' -f1) + DONE=$(echo "$REMAINING" | cut -d'|' -f2) + + # Write heartbeat + cat > "$HEARTBEAT_FILE" << EOF +{ + "status": "running", + "last_batch_at": "$START_ISO", + "duration_seconds": $DURATION, + "batch_geocoded": $GEOCODED, + "batch_failed": $FAILED, + "total_geocoded": $DONE, + "total_pending": $PENDING, + "pid": $$ +} +EOF + + echo "[$(date)] Batch done: +$GEOCODED geocoded, $FAILED failed, $PENDING remaining" + + # Done? + if [ "$PENDING" = "0" ]; then + cat > "$HEARTBEAT_FILE" << EOF +{ + "status": "completed", + "completed_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", + "total_geocoded": $DONE, + "total_pending": 0, + "pid": null +} +EOF + echo "✅ Geocoding fertig!" + exit 0 + fi + + # Kleine Pause zwischen Batches + sleep 5 +done