Quellen anbinden¶
mandari liest kommunale Ratsinformationen aus drei Arten von Quellen. Alle landen in derselben Pipeline: Verarbeitung, Speicherung, Texterkennung, Suche, Tombstones.
| Art | source_type | Beschreibung |
|---|---|---|
| OParl-Schnittstelle | oparl (Standard) | Das Ratsinformationssystem der Kommune bietet selbst OParl an. Der einfachste Fall. |
| Scraper-Adapter | scraper:sessionnet | Für Systeme ohne OParl, aktuell SessionNet. Der Adapter im Ingestor erzeugt aus den HTML-Seiten OParl-1.1-JSON. |
| Externe Bridge | bridge:allris | Für ALLRIS: Ein eigener Container übersetzt in OParl, mandari konsumiert dessen Ausgabe als normale Quelle. |
Die Art steht in der Sync-Konfiguration der Quelle (sync_config["source_type"]). Im Admin unter OParl-Quellen sind Art, Parse-Quote und robots.txt-Status je Quelle sichtbar, der Filter „Quellen-Art“ trennt die drei.
OParl-Schnittstelle anbinden¶
- System-Endpunkt der Kommune ermitteln, zum Beispiel
https://ratsinfo.example.org/oparl/v1/system. Ein Blick in die JSON-Antwort zeigt Name und Body-Liste. - Admin → Insight → OParl-Quellen → Hinzufügen: Name und URL des System-Endpunkts eintragen, aktiv setzen.
- Der Ingestor synchronisiert im nächsten Zyklus (Standard alle 15 Minuten, Voll-Sync nachts). Alternativ die Admin-Aktion „Vollständiger Sync“ auslösen.
- Fortschritt im Betriebsmonitor verfolgen. Beim Onboarding wächst die OCR-Warteschlange sprunghaft, daher Kommunen einzeln aufschalten.
SessionNet-Kommune anbinden¶
SessionNet-Bürgerinfo-Portale sind Standard-Templates. Der Adapter unterstützt die klassische .asp-Variante und die neuere .php-Variante mit identischem Markup.
Schritt 1: Basis-URL ermitteln¶
Die Basis-URL ist das Verzeichnis, in dem die SessionNet-Seiten liegen, erkennbar an Seiten wie si0040.asp (Sitzungskalender), vo0050.asp (Vorlage) und gr0040.asp (Gremien). Typische Formen:
https://buergerinfo.example.org/(direkt im Root,.asp)https://rat.example.org/bi/(Unterverzeichnis,.php)https://sessionnet.hoster.example/kommune/bi/(Hoster mit Mandantenpfad)
Kurz prüfen: <basis-url>si0040.asp (oder .php) muss den Sitzungskalender liefern und der Seitentitel mit „SessionNet“ beginnen.
Schritt 2: robots.txt prüfen¶
Der Ingestor prüft robots.txt automatisch vor jedem Crawl und überspringt die Quelle bei einem Disallow. Der Status wird im Admin angezeigt. Vorab manuell nachsehen schadet nicht: https://<host>/robots.txt.
Schritt 3: Quelle anlegen¶
Admin → Insight → OParl-Quellen → Hinzufügen:
- Name: zum Beispiel „Stadt Musterstadt (SessionNet)“
- URL: die Basis-URL, sie dient als eindeutiger Schlüssel
- Sync config:
{
"source_type": "scraper:sessionnet",
"scraper": {
"base_url": "https://rat.example.org/bi/",
"body_name": "Stadt Musterstadt",
"variant": "php",
"rate_limit_seconds": 2.0,
"calendar_window_days": [-60, 210],
"full_window_days": [-365, 210]
}
}
Alle Schlüssel unter scraper außer base_url sind optional:
| Schlüssel | Standard | Bedeutung |
|---|---|---|
body_name | „Unbekannte Kommune“ | Anzeigename der Kommune |
variant | automatisch | "asp" oder "php" |
rate_limit_seconds | 2.0 | Mindestabstand zwischen Anfragen je Host |
max_concurrent | 1 | immer 1, Anfragen laufen nacheinander |
calendar_window_days | [-60, 210] | Kalenderfenster inkrementeller Läufe |
full_window_days | [-365, 210] | Fenster des Voll-Crawls (Historie) |
max_detail_pages | unbegrenzt | Obergrenze Detailseiten je Lauf, für Probe-Crawls |
members_on_full_only | true | Gremienmitglieder nur im Voll-Crawl laden |
Schritt 4: Probe-Crawl mit Limit¶
Beim Onboarding zunächst mit strengem Limit fahren ("max_detail_pages": 20), das Ergebnis im Admin prüfen (Parse-Quote, gespeicherte Objekte) und Stichproben gegen die Live-Seiten vergleichen. Danach das Limit entfernen und einen Voll-Sync auslösen.
Was der Adapter extrahiert¶
| SessionNet-Seite | OParl-Objekt |
|---|---|
| Basis-URL | Body |
Sitzungsdetail (si0057) | Meeting mit Tagesordnungspunkten, öffentlich und nicht-öffentlich gekennzeichnet, mit Beschlüssen |
Sitzungsdokumente (si0050) | Einladung, Niederschrift als File |
Vorlage (vo0050) | Paper mit Betreff, Nummer, Art und Anlagen |
Gremienliste (gr0040) | Organizations |
Gremiendetail (kp0040) | Persons und Memberships inklusive Stimmrecht |
| Punkt ↔ Vorlage | Consultation |
Anlagen laufen automatisch durch die Texterkennung und werden in der Suche indexiert.
Änderungserkennung und Löschungen¶
- Listen-Diffing: Je Kalendermonat wird ein Hash der Sitzungsliste gehalten, unveränderte Monate überspringt der inkrementelle Lauf.
- Inhalts-Hash je Objekt: Aktualisiert wird nur bei tatsächlicher Änderung.
modifiedist die Crawl-Zeit des letzten echten Updates, damit bleibt die eigene OParl-Ausgabe inkrementell konsumierbar. - Verschwinden ist nicht Löschen: Objekte, die in drei aufeinanderfolgenden Voll-Crawls nicht mehr gesehen wurden (
SCRAPER_TOMBSTONE_FULL_CRAWLS), werden als gelöscht markiert, nie physisch gelöscht. Taucht ein Objekt wieder auf, wird die Markierung automatisch aufgehoben.
Überwachung¶
Fällt die Parse-Quote eines Laufs unter 80 Prozent (SCRAPER_PARSE_QUOTA_WARN), wird gewarnt und der Lauf als fehlerhaft protokolliert. Das ist das typische Symptom eines Frontend-Redesigns der Instanz. Für Prometheus stehen Zähler für geladene Seiten, Parse-Fehler je Seitentyp und die Parse-Quote je Quelle bereit.
ALLRIS über oparl-bridge anbinden¶
ALLRIS rendert sein Frontend per Ajax, klassisches HTML-Scraping reicht dort nicht. Das externe Open-Source-Projekt oparl-bridge (MIT-Lizenz) liest eine ALLRIS-Instanz aus und publiziert die Daten als OParl-1.1-API. mandari konsumiert diese API als normale OParl-Quelle.
Empfohlen ist eine gepinnte Version der Bridge. Kein offizielles Docker-Image, daher ein eigenes Dockerfile. Beispieldateien:
# Image einmalig aus der gepinnten Upstream-Version bauen
docker build -f oparl-bridge.Dockerfile \
-t mandari/oparl-bridge:v0.2.0 \
https://github.com/Aeroid/oparl-bridge.git#v0.2.0
# Auszug: ein Bridge-Container je ALLRIS-Kommune
services:
oparl-bridge-musterstadt:
image: mandari/oparl-bridge:v0.2.0
environment:
OPARL_ALLRIS_BASE_URL: "https://ratsinfo.musterstadt.example/allris"
OPARL_API_BASE_URL: "https://oparl-musterstadt.intern.example"
OPARL_BODY_NAME: "Stadt Musterstadt"
OPARL_SCRAPER_DELAY_MS: "2000"
volumes:
- oparl_bridge_musterstadt:/data
restart: unless-stopped
Anbindung in mandari¶
- Bridge-Container starten, den initialen Sync abwarten (je nach Kommune 10 bis 40 Minuten) und die OParl-Ausgabe prüfen:
curl https://<bridge-host>/liefert das System-Objekt. - Quelle im Admin anlegen mit der URL des Bridge-System-Endpunkts und der Sync-Konfiguration
{"source_type": "bridge:allris"}. - Sync auslösen. Die Bridge liefert Listen ohne Pagination und ohne
modified_since-Filter. Das ist unkritisch, der Ingestor vergleicht Objekte clientseitig.
Ein Bridge-Container bedient genau eine ALLRIS-Instanz. PDFs werden von der Bridge bei Bedarf durchgereicht, nicht gespeichert.
Rücksicht auf die Quellen¶
Für alle Quellen gelten die gleichen Regeln: begrenzte Anfragerate, Respekt vor robots.txt, ehrlicher User-Agent, keine Umgehung von Logins oder Captchas, automatische Schonung bei Störungen. Wie Kommunen Drosselung oder Entfernung verlangen können, steht unter Crawler und Opt-out.