Lage.Bonn
Abstrahierte Entity-Relationship-Konstellation über einem Radar-Pin auf Bonn

Architektur
& Datenmodell

Statische Übersicht des Lagebild-Systems auf nostromo (Produktion): die Container-Pipeline von der externen Quelle bis zum Browser und das auf der Platte liegende Datenmodell. Das Datenmodell unten wird aus store.py und models.py generiert — es kann nicht still vom Code abweichen.

01 Architektur

Container-Pipeline C4 · Level 2
C4 Level 2 Container-Diagramm: Collectors → Kafka → Consumers → PostgreSQL → api + web → Ingress
Tragende Entscheidungen
EntscheidungWahlBegründung
DatenbankPostgreSQL (CNPG)CloudNativePG statt SQLite: shared zwischen Deployments, HA-fähig, LVM-Storage.
Collector-Splitfeed-collector · nina · allrisSeparate Deployments: unabhängige Skalierung, Restart-Isolation, je eigener ServiceMonitor.
Nachrichten-BusKafka (Strimzi KRaft)Entkoppelt Collector von Consumer; Append-Only-Buffer; Ordering-Invariante per Single-Partition.
Push an BrowserSSE über mtime-PollingKeine inotify-Abhängigkeit; portabel.
DedupIn-Memory Fuzzy-ClusterDasselbe Ereignis aus 3 Quellen kollabiert.
Signal-WatchersSidecars im api-PodGemeinsamer prometheus_multiproc-Dir; metrics-exporter-Sidecar aggregiert alle Watchers auf :9200.
Web-Skalierung2 Replicas, RollingUpdateStateless SSR, kein geteilter State; Zero-Downtime-Deploy.

02 Datenmodell

Vier Tabellen in PostgreSQL (CNPG). Beziehungen sind dokumentiert (das Schema ist für günstige Abfragen denormalisiert; Fremdschlüssel sind nicht deklariert).

Tabelle 5 Spalten

sources

Feed registry. Seeded from sources.yaml, then DB-owned. Full pydantic Source JSON lives in `config`; hot columns are denormalized out.

id PKVARCHAR
kind VARCHAR
enabled INTEGER
poll_minutes INTEGER
config TEXT
Tabelle 21 Spalten

items

Every seen item. Dedup + Atom source of truth. `fingerprint` is a stable SHA-256 over guid/link.

fingerprint PKVARCHAR
source_id VARCHAR
source_title VARCHAR
category VARCHAR
guid VARCHAR?
title VARCHAR
summary VARCHAR?
link VARCHAR?
source_published_at VARCHAR?
collected_at VARCHAR
event_at VARCHAR?
first_seen VARCHAR
ortsteil VARCHAR?
stadtbezirk VARCHAR?
lat FLOAT?
lon FLOAT?
geo_source VARCHAR?
cluster_id VARCHAR?
severity TEXT?
expires TEXT?
onset TEXT?
Indizes stadtbezirkcluster_idcollected_atsource_published_at
  • source_idsources.id emitted by · many-to-one
  • cluster_iditems.cluster_id clustered with · self (dedup)
Tabelle 7 Spalten

fetch_state

Per-source fetch cadence and health. Drives the Quellenstatus panel.

source_id PKVARCHAR
last_fetch VARCHAR
last_status VARCHAR?
last_error VARCHAR?
last_ok VARCHAR?
fail_count INTEGER
last_items INTEGER?
  • source_idsources.id tracks · one-to-one
Tabelle 8 Spalten

signals

Append-only state log per watcher. One row on each state change (`source` is a watcher name, not a feed source). Drives the Signals panel and /api/signals; severity is derived by the API when absent.

id PKINTEGER
source VARCHAR
state VARCHAR
previous_state VARCHAR?
emitted_at VARCHAR
severity VARCHAR?
detail TEXT?
info_url VARCHAR?
Indizes source, emitted_at
Quellen-Modell Union über kind

sources.config speichert das vollständige Pydantic-Source-JSON. Die Variante wird über das Feld kind diskriminiert.

rss RSSSource

A standard RSS/Atom feed.

idstr
poll_minutes?int
enabled?bool
kind?'rss'
titlestr
category'blaulicht' | 'news' | 'amtlich' | 'wissenschaft' | 'events' | 'verkehr' | 'wetter'
urlstr
stadtbezirk?str | None
bonn_only?bool
user_agent?str | None
autobahn AutobahnSource

Autobahn roadworks/warning JSON API for a set of roads near Bonn.

idstr
poll_minutes?int
enabled?bool
kind?'autobahn'
title?str
roads?list
dwd DWDSource

DWD municipality-level weather warnings (WFS).

idstr
poll_minutes?int
enabled?bool
kind?'dwd'
title?str
gemeinde?str
radiobonn RadioBonnSource

Radio Bonn news via listing-page allowlist + monthly sitemap for dates. No RSS feed exists. Strategy: scrape the local-news listing page to get Bonn-area article IDs, then cross-reference with the monthly sitemap for lastmod timestamps. /dpa/ URLs skipped per robots.txt.

idstr
poll_minutes?int
enabled?bool
kind?'radiobonn'
title?str
category?'blaulicht' | 'news' | 'amtlich' | 'wissenschaft' | 'events' | 'verkehr' | 'wetter'
listing_url?str
sitemap_base?str
bonn_only?bool
swb-bus-und-bahn SWBBusUndBahnSource

SWB Bus und Bahn (Stadtwerke Bonn) live ÖPNV disruptions. Collected from the BONNmobil disruption API (``bc-drawings-extra-data``) — the same structured, real-time feed the official disturbances map renders. Each entry carries a headline, affected lines, validity window, and a full description. Drives the ÖPNV health signal. The endpoint is configurable.

idstr
poll_minutes?int
enabled?bool
kind?'swb-bus-und-bahn'
title?str
category?'blaulicht' | 'news' | 'amtlich' | 'wissenschaft' | 'events' | 'verkehr' | 'wetter'
url?str
stadtbezirk?str | None
bonn_only?bool
nina NINASource

BBK NINA civil-protection warnings (Kafka-decoupled collector).

idstr
poll_minutes?int
enabled?bool
kind?'nina'
title?str
category?'blaulicht' | 'news' | 'amtlich' | 'wissenschaft' | 'events' | 'verkehr' | 'wetter'
url?str
allris AllrisSource

Bonn ALLRIS council-information system feed (Kafka-decoupled collector).

idstr
poll_minutes?int
enabled?bool
kind?'allris'
titlestr
category?'blaulicht' | 'news' | 'amtlich' | 'wissenschaft' | 'events' | 'verkehr' | 'wetter'
urlstr
Kategorien Vokabular ↗
blaulichtnewsamtlichwissenschafteventsverkehrwetter

scripts/gen-datenmodell.py (just update-datenmodell)