Architektur
Ein Scraper für das Oldenburger Ratsinformationssystem speist eine SQLite-Datenbank. Ein Web-Frontend (FastAPI + Next.js) sitzt auf denselben Daten und liefert personalisierte Benachrichtigungen per E-Mail und Web-Push.
flowchart TD
RIS["Oldenburger Ratsinformationssystem
(SessionNet)"]
SCRAPER["Scraper (council/)
Sitzungen · Protokolle · Vorlagen · Anlagen"]
DB[("SQLite
Ratsdaten · Konten & Themen")]
LLM["LLM via OpenRouter
zusammenfassen · matchen · klassifizieren"]
API["FastAPI-Backend"]
WEB["Next.js-Frontend
ratslotse.de · iOS-App"]
NOTIFY["Benachrichtigungen
E-Mail (Resend) · Web-Push (APNs/FCM)"]
RIS -->|"HTML & PDFs — ohne LLM"| SCRAPER
SCRAPER --> DB
DB <--> LLM
DB --> API
API --> WEB
DB --> NOTIFY
Welche Dokumente im Einzelnen ausgewertet werden und was daraus entsteht, zeigt die Übersichtsgrafik unter Ratsdokumente & Beschlüsse.
Komponenten
Abschnitt betitelt „Komponenten“Stadtrat-Scraper (council/)
Abschnitt betitelt „Stadtrat-Scraper (council/)“Das Ratsinformationssystem (buergerinfo.oldenburg.de) läuft auf SessionNet
(Somacos GmbH). Es gibt keine öffentliche API — die Seiten werden mit
BeautifulSoup4 gescrapt:
- die Kalenderseite liefert alle Sitzungen eines Monats,
- die Sitzungs-Detailseite Kopfdaten (Datum/Zeit/Ort) und die
Tagesordnungstabelle (TOPs mit
Ösind öffentlich,Nnicht-öffentlich), - die Vorlagen-Seite Vorlagen- und Anlagen-PDFs (
council/vorlagen.py, ausgewertet ganz ohne LLM).
Der Store merkt sich, welche Sitzungen bereits gesehen und welche Alerts schon versendet wurden — das verhindert Doppel-Benachrichtigungen.
KI-Klassifikation
Abschnitt betitelt „KI-Klassifikation“Die Klassifikation läuft über OpenRouter (OpenAI-SDK) und ist bewusst zustandslos — kein Fine-Tuning, strukturierte Prompts mit JSON-Mode. Die Modelle sind je Aufgabe über Env-Variablen konfigurierbar; Defaults greifen ohne Konfiguration.
Details: siehe KI-Pipeline.
Benachrichtigungen
Abschnitt betitelt „Benachrichtigungen“Jeder Nutzer wählt pro Konto einen Zustellkanal (email / push / both —
oder off für gar nicht) und zusätzlich, wofür er Post bekommt — sechs
Anlässe, einzeln abschaltbar.
Zustellung: E-Mail via Resend, Web-Push auf die registrierten
App-Geräte via APNs/FCM. Fehlt das Secret eines Kanals, wird dieser still
übersprungen.
Kein Anlass sendet selbst: Alle reihen in notification_queue ein, und
nwz.notify.zustellen() liefert zentral aus — unter zwei harten Grenzen
(höchstens zwei Zustellungen pro Person und Tag, nichts zwischen 21 und 7 Uhr).
Termingebundene Anlässe — die Vorabend-Erinnerung — zählen dabei in einem
eigenen Topf, sonst kämen sie am Sitzungstag statt am Vortag.
Details in App & Konten.
Datenbankschema
Abschnitt betitelt „Datenbankschema“Zwei SQLite-Dateien: eine für Konten & Themen, eine für die Ratsdaten (siehe ADR 0005).
Die Trennlinie ist: alles Nutzerbezogene liegt in nwz.sqlite, alle
Ratsdaten in council.sqlite. Deshalb liegen z. B. Themen-Treffer und
Quiz-Antworten in der Konten-DB, obwohl sie auf Beschlüsse bzw. Fragen zeigen —
ein Join über beide Dateien ist nicht möglich, solche Zeilen denormalisieren die
nötigen Felder.
Konten & Nutzerdaten (nwz.sqlite)
web_users— Konten (E-Mail, Passwort-Hash, Rolle, Status,display_name,delivery_channel,badges,onboarding,apple_sub,token_version)topics,committee_subscriptions— Themen-Watchlist und Ausschuss-Aboscouncil_topic_matches,topic_hits_seen— Beschluss-Treffer je Thema und was davon schon gesehen wurde („Neu“-Zähler)council_agenda_matches,council_agenda_classified— Tagesordnungs-Treffer und bereits klassifizierte Agendenpush_tokens,email_verification_tokens,password_reset_tokens— Push und E-Mail-/Passwort-Flowsquiz_answers,quiz_ratings,quiz_daily,user_quiz_questions— Quiz: Antworten/Punkte, 👍/👎, tägliche Challenge, eigene Fragenuser_activity,llm_usage— Aktivitätssignale und LLM-Kostenerfassungprompts— live editierbare Prompt-Overrides (Admin-UI)
Ratsdaten (council.sqlite)
council_sessions,council_agenda_items,council_scheduled_sessions— Sitzungen, Tagesordnungen und terminierte Sitzungen ohne Tagesordnungcouncil_alerts_sent,committee_notifications— Deduplizierung der Benachrichtigungencommittee_summaries— gecachte Agenda-Zusammenfassungencouncil_protocols,council_decisions,council_attendance,council_vorlagen,council_anlagen,council_beratungen— Dokument-Auswertung und Beratungsfolge (siehe Beschlüsse)council_persons,council_memberships— Mandatsträger:innen und Gremiencouncil_entities(+_links,_meta,_obs,_scanned) — erkannte Orte und Projekte samt Geokoordinaten für die Stadtkartecouncil_embeddings,council_similar— Vektoren und vorberechnete Nachbarn („Ähnliche Beschlüsse“, siehe ADR 0003)council_field_recaps,council_goal_links— Themenfeld-Rückblicke und Ziele-Trackingcouncil_fundstuecke— kuratierte „Fundstück des Tages“-Karten (siehe Bewertungs-Scores)council_quiz_questions,council_haushalt— Quizfragen und Haushaltszahlencouncil_decisions_fts— FTS5-Volltextindex über die Beschlüsse
Geplante Jobs
Abschnitt betitelt „Geplante Jobs“Alle Auswertungen laufen als geplante Jobs auf dem Server — jeder Lauf ist idempotent und meldet Fehler per E-Mail an die Betreiber-Adresse:
| Rhythmus | Aufgabe |
|---|---|
| täglich | Datenbank-Backup (rotierend, optional off-site gespiegelt) |
| täglich | Ausschuss-Tagesordnungen zusammenfassen |
| mehrmals täglich | Sitzungen auf Themen-Treffer prüfen (inkl. geänderter Tagesordnungen) |
| täglich | neue Protokolle parsen — und in einem Rutsch: Vorlagen & Anlagen nachladen, Beschlüsse klassifizieren, Beträge erkennen, „Lotti erklärt’s einfach“ schreiben, Tragweite und Gesprächswert bewerten, Wichtigkeit neu berechnen |
| wöchentlich | Backfills als Auffangnetz: Entitäten & Beschreibungen, Geocoding, Embeddings/Ähnliche, Themen↔Beschlüsse, Rückblicke, Stammdaten, Scores in Tranchen, Quizfragen, Fundstücke |
Der tägliche Protokoll-Lauf deckt neue Beschlüsse tagesaktuell ab (mit Obergrenzen je Lauf), der wöchentliche Lauf holt in Tranchen den Bestand nach. Details und Zeitpläne: Betrieb.
Deployment
Abschnitt betitelt „Deployment“- Hosting: ein App-Server hinter einer Edge-VM; Caddy auf der Edge terminiert TLS und proxyt auf das Next.js-Frontend (siehe ADR 0004).
- Prozesse: Backend (FastAPI/uvicorn) und Frontend (Next.js) laufen als systemd-Services.
- CI/CD: GitHub Actions. Nur ein gemergter Pull Request nach
mainlöst den Deploy aus (rsync + Service-Restart + Frontend-Build) — ein direkter Push aufmainläuft nur durch die Tests (siehe ADR 0008). - Secrets:
.envliegt nur auf dem Server, nie im Git; der Deploy-Key ist ein GitHub-Actions-Secret.