No description
  • Python 68.8%
  • Svelte 14.1%
  • TypeScript 11.4%
  • Shell 2.4%
  • CSS 1.5%
  • Other 1.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Christoph Görn 4665e7c5dc
feat: add multimodal transport-network graph with resilience routing
Fact-based multimodal graph (data/transport-network.gexf, GEXF 1.3) for the
Bonn / Rhein-Sieg region, generated by scripts/build_transport_network.py from
authoritative sources — nothing hand-invented (see data/AGENTS.md):

- Fernverkehr (ICE/IC): gtfs.de de_fv feed (DELFI, CC-BY 4.0), clipped to the
  region — real line numbers and the Flughafen Köln/Bonn ICE halt
- Regionalbahn + tram/Stadtbahn: VRS GTFS-static (rail_bonn/tram_bonn)
- Autobahn A-junctions + B/L crossings: OpenStreetMap via Overpass
- Waterways: WSV Bundeswasserstraßen

Roads model real Anschlussstelle chains, so a closure (e.g. the A555 at
Bornheim) severs one segment and forces a detour — the graph can answer
"if the Autobahn is blocked, how do I get from A to B?" across all modes.
Served copy at apps/web/static/geo for the /_transport route.

just update-transport-{fernverkehr,vrs,osm,osm-bl} refresh each layer.
2026-07-23 20:51:06 +02:00
.agents/skills chore: 🔧 install i-have-adhd project skill 2026-07-20 12:57:25 +02:00
.beads chore: 🔥 remove spacetimedb from nostromo environment 2026-07-23 00:12:06 +02:00
.castra chore: 🔧 sync uv.lock to lage 0.1.115 2026-07-02 17:45:30 +02:00
.claude feat: mount sources.yaml from ConfigMap in api Deployment (lage-fttg) (#183) 2026-07-21 15:35:59 +02:00
.config fix: 🐛 correct stale web pre-start hook path to apps/web 2026-07-11 10:30:50 +02:00
.direnv refactor: 🏗️ split single pod into separate api and web deployments 2026-06-06 12:50:41 +02:00
.tekton fix: 🔒️ pin lage-api image by digest, not just mutable tag 2026-07-20 17:34:33 +02:00
apps feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
bin feat: rename castra persona-post to post-run, add help and URL fallback 2026-06-28 07:40:30 +02:00
contrib/systemd-unit-files refactor: 🏗️ move systemd-unit-files into contrib/ 2026-06-05 17:18:39 +02:00
data feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
docs chore: 🔧 merge origin/main into add-community-resolution-feedback 2026-07-22 23:52:25 +02:00
karte refactor: 🏗️ move web/ to apps/web/, alongside the other frontend apps 2026-07-07 13:11:06 +02:00
manifest chore: 🔧 bump lage-web to codeberg.org/machdenstaat/lage-web:v0.1.83-58154d5 [skip ci] 2026-07-23 12:45:25 +00:00
openspec docs: 📝 archive add-community-resolution-feedback, sync specs into canon 2026-07-23 00:12:07 +02:00
sandbox feat: per-mode transport impact report for one event geometry (lage-02ri) 2026-07-22 21:50:09 +02:00
scripts feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
src/lage chore: 🔧 merge origin/main into add-community-resolution-feedback 2026-07-22 23:52:25 +02:00
tests chore: 🔧 merge origin/main into add-community-resolution-feedback 2026-07-22 23:52:25 +02:00
.containerignore fix: 🐛 re-include apps/monitor in .containerignore so lage-monitor builds 2026-06-16 09:04:06 +02:00
.envrc.example chore: 🔧 add Justfile, split image versions, ignore .envrc secrets 2026-06-06 11:34:07 +02:00
.gitignore feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
.gitleaks.toml feat: curator review flag, web env ConfigMap, sealed token 2026-06-18 19:53:40 +02:00
.pre-commit-config.yaml feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
.python-version chore: 🔧 pin Python 3.14 via .python-version 2026-07-09 12:52:44 +02:00
.sops.yaml feat: central Postgres (cnpg) shared state store 2026-06-07 11:27:16 +02:00
AGENTS.md docs: 📝 always merge with wt merge --no-squash --no-commit (lage-xh9q) 2026-07-22 17:43:06 +02:00
ARCHITECTURE.md docs: 📝 update architecture to reflect nostromo production topology 2026-06-14 18:20:37 +02:00
CLAUDE.md docs: 📝 consolidate CLAUDE.md into AGENTS.md via symlink 2026-07-05 18:38:30 +02:00
Containerfile feat: expose /metrics on api (FastAPI) and web (SvelteKit) 2026-06-06 14:59:32 +02:00
DEVELOPER.md feat: lage bahn — Bahn-Wetter Lagebild im Terminal (lage-e36g) 2026-07-22 08:53:29 +02:00
Justfile feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
logo.svg feat: add project logo and replace default Svelte favicon 2026-06-06 15:27:38 +02:00
openapi.json chore: 🔧 merge origin/main into add-community-resolution-feedback 2026-07-22 23:52:25 +02:00
OWNERS OWNERS aktualisiert 2026-07-04 16:28:57 +02:00
podman-compose.yaml refactor: 🏗️ move web/ to apps/web/, alongside the other frontend apps 2026-07-07 13:11:06 +02:00
pyproject.toml feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
README.md feat: add project logo and replace default Svelte favicon 2026-06-06 15:27:38 +02:00
renovate.json5 chore: 🔧 cap renovate typescript updates to <7 (svelte-check incompat) 2026-07-12 13:28:15 +02:00
skills-lock.json chore: beads and skill update 2026-07-20 11:34:21 +02:00
sources.yaml fix: 🐛 make bonn_only test resolved coordinates, not geo_source hits 2026-07-21 18:19:43 +02:00
sqi.jsonl docs: 📝 archive add-community-resolution-feedback, sync specs into canon 2026-07-23 00:12:07 +02:00
uv.lock feat: add multimodal transport-network graph with resilience routing 2026-07-23 20:51:06 +02:00
VISION.de.md docs: 📝 add affectedness journey + abgeleitet level to VISION (lage-inb4) 2026-07-22 09:26:36 +02:00
VISION.md docs: 📝 add affectedness journey + abgeleitet level to VISION (lage-inb4) 2026-07-22 09:26:36 +02:00

Lage Bonn

Lage Bonn logo

Live: https://lage.bonn.machdenstaat.de/

A Lagebild for Bonn: aggregates local RSS/Atom feeds and open government APIs into one geo-tagged, deduplicated Atom stream. Deep at the local (Ortsteil) level rather than thin and broad.

Approach (a): deterministic curation — fetch, geo-tag, dedup, emit. No LLM, no rewriting of source text. LLM synthesis and a scheduled Morgenlage digest are later phases.

Pipeline

SQLite registry ─▶ fetch ─▶ geo-tag ─▶ dedup ─▶ SQLite ─▶ Atom (out/feed.atom)
   feeds (DB)       RSS+JSON   gazetteer  cluster  items      georss:point

Both the feed registry and all items live in SQLite, with pydantic as the single schema authority (LageItem + a discriminated Source union). sources.yaml is a git-tracked seed: lage feed import validates it through pydantic and upserts it into the DB (also auto-seeded on first collect when the registry is empty). After that the DB is authoritative — edit feeds with lage feed create/delete.

  • fetch — RSS via feedparser; Autobahn + DWD via JSON/WFS (no auth).
  • geo-tag — Bonn gazetteer (4 Stadtbezirke, ~50 Ortsteile). Ortsteil names matched in item text (Bonn-Röttgen → Röttgen/Bonn); Autobahn/DWD use native coordinates; GA district feeds tag at fetch time. Rhein-Sieg neighbor Gemeinden (Alfter, Siegburg, …) are detected so the bare word "Bonn" cannot mis-claim them → "Umland (Rhein-Sieg)". No-place items → "Bonn allgemein". Region-wide feeds can set bonn_only: true to drop items that resolve to no Bonn/Umland place.
  • dedup — exact (guid/link hash, SQLite PK) + fuzzy (normalized-title similarity within 36h) so the same event from Polizei + GA + Rundschau collapses into one Atom entry that cites all sources.
  • out — Atom 1.0 with georss:point, geo/category/source categories.

Sources (verified live, auth-free)

Category Sources
Blaulicht Polizei Bonn, Feuerwehr & Rettungsdienst Bonn, Bundespolizei Dir. Sankt Augustin (NRW-wide, bonn_only-filtered)
News General-Anzeiger (Stadt + Bonn/Bad Godesberg/Beuel/Hardtberg), Bonner Rundschau
Amtlich Bundesstadt Bonn Presse
Wissenschaft Universität Bonn
Events Bonn Veranstaltungskalender
Verkehr Autobahn API (A565/A555/A59 roadworks + warnings)
Wetter DWD weather warnings (Stadt Bonn)

Deferred (need work/auth): Bonn OParl RIS (HTTP 500 at build time), WDR Lokalzeit (no working feed), SWB/VRS disruptions (scrape or API key), Amtsblatt (PDF only).

Usage

uv run lage feed collect          # one poll cycle, respects per-source cadence
uv run lage feed collect --force  # ignore cadence, fetch everything now
uv run lage feed collect -vv      # DEBUG: per-item geo + dedup scores
uv run lage feed collect --window-hours 48   # Atom output time window
uv run lage                       # bare alias for `feed collect` (back-compat)

Output: out/feed.atom. State: data/lage.db (SQLite).

Status

uv run lage status                # data-store stats + per-source fetch health

Reports store size, item counts (total / last 24h / 72h), dedup effectiveness, the covered time span, breakdowns by category / Stadtbezirk / geo-source, and per source the last fetch outcome (ok/fail), consecutive failure count, last item count, and an overdue flag when a source has missed twice its cadence.

Manage sources

uv run lage feed import           # seed/refresh the DB registry from sources.yaml
uv run lage feed list             # show configured sources (from the DB)
uv run lage feed show polizei-bonn   # one source: config + live stats (DB)
uv run lage feed create --id radio-bonn --title "Radio Bonn" \
    --url https://example.org/feed.xml --category news --poll-minutes 30
uv run lage feed create --id bpol --title "Bundespolizei" \
    --url https://.../feed.rss --category blaulicht --bonn-only
uv run lage feed delete --id radio-bonn
uv run lage feed delete --all     # wipe the registry (re-seed with `feed import`)

create/delete write the SQLite registry directly (DB authority); import seeds/refreshes it from the git-tracked sources.yaml. Pydantic validates every source — a bad --category is rejected before it reaches the DB. Flags: --stadtbezirk (district scope), --bonn-only (drop non-Bonn items), --user-agent browser (for hosts that block bots).

Serve the feed

uv run lage serve                       # http://127.0.0.1:8080/
uv run lage serve --host 0.0.0.0 --port 8099

Serves a human-readable HTML view at / and /feed, and the raw out/feed.atom as application/atom+xml at /feed.atom and /atom (GET + HEAD). Dev/local convenience — it does not run the pipeline; keep fetching on the timer/cron. For production, front the file with nginx or any static server.

Google Maps traffic panel (optional)

The dashboard at / can show a live traffic map of Bonn powered by the Google Maps JavaScript API. It is hidden when GOOGLE_MAPS_API_KEY is unset.

One-time setup:

  1. Open Google Cloud Console → APIs & Services → Library.
  2. Search for Maps JavaScript API and click Enable.
  3. Go to APIs & Services → Credentials → Create Credentials → API key.
  4. Copy the key, then restrict it:
    • Application restrictions: HTTP referrers → add http://localhost:8080/* (and your production domain if applicable).
    • API restrictions: restrict to Maps JavaScript API only.
  5. Add the key to .envrc:
export GOOGLE_MAPS_API_KEY=AIza...your_key_here
  1. Reload the env (direnv allow if you use direnv, or source .envrc), then restart lage serve. The Verkehrslage panel appears automatically.

Deployment (continuous stream)

Deterministic, crash-safe — a timer wakes the app every 5 min; the per-source poll_minutes in the SQLite registry decides what actually gets fetched.

mkdir -p ~/.config/systemd/user
cp contrib/systemd-unit-files/lage.{service,timer} ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now lage.timer

Or cron:

*/5 * * * * cd ~/Source/lage && uv run lage feed collect

Serve out/feed.atom with any static file server; feed readers consume it.

Roadmap

  • (b) LLM synthesis: cluster → one written Lage paragraph per event.
  • Scheduled Morgenlage digest (06:00) as a second Atom feed / email.
  • OSM street-level gazetteer (Overpass) for sub-Ortsteil precision.
  • Recover OParl (council decisions), add SWB/VRS disruptions.