Skip to content

feat(simulation): Live-Daten-Stream-Worker — Park am Historien-Ende mit Wall-Clock fortsetzen - #47

Merged
patricznr1 merged 2 commits into
mainfrom
feat/live-data-stream
Jun 25, 2026
Merged

feat(simulation): Live-Daten-Stream-Worker — Park am Historien-Ende mit Wall-Clock fortsetzen#47
patricznr1 merged 2 commits into
mainfrom
feat/live-data-stream

Conversation

@patricznr1

@patricznr1 patricznr1 commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Worum es geht

Das Dashboard soll leben: neue Readings ticken fortlaufend mit aktuellen Zeitstempeln rein, statt dass die 21-Tage-Historie altert (löst zugleich anchor-now). Dieser PR baut den fehlenden Live-Produzenten, der den signalbasierten Twin-Park-Generator am Ende der Backfill-Historie ansetzt und mit Wall-Clock-Stempeln (echte Jetzt-Zeit, nie Szenario-Sim-Zeit) weitertickt.

Verfassung: Das ist die Eingangs-Simulation (Zwilling als Datenquelle) — echte Generierung, „ist das live?" → Ja. Strikt getrennt vom FOREMAN-internen Reasoning-Simulieren (bleibt inaktiv).

Abgrenzung zu runner.py/park.py --mode live (WallClockPacer): jenes spielt das Szenario ab Tag 0 mit Sim-Stempeln im Echtzeit-Takt ab (für eine frische DB). Es macht bewusst das Falsche zum Fortsetzen einer bestehenden Historie — daher dieser eigene Bau.

Was drin ist

adapters/simulation/live.py (neu)

  • RealTimePacer — Echtzeit 1:1 (kein speed); wartet bis zum Tick-Stempel, kein Warten in der Vergangenheit (Aufhol-Phase nach Backfill/Neustart).
  • live_tick_times(anchor, interval, max_ticks) — reine Tick-Achse: strikt nach anchor (kein Overlap; PK (data_point_id, time) kollidiert nie), lückenlos im interval-Takt (kein Gap), monoton.
  • resolve_resume_anchor(...) — Anker = max(time) der Park-Datenpunkte → neustart-fest (frisch aus der DB; kein Doppel, keine Lücke). Ohne Historie → now − interval.
  • cap_resume_anchor(...) — optionaler Aufhol-Deckel (--max-catchup-ticks): kappt eine sehr große Lücke bei now (geloggt, kein stilles Abschneiden) statt eines Boot-Storms; Default = lückenlos füllen.
  • LiveParkAdapter (SourceAdapter) — bündelt je Park-Szenario einen SimulationAdapter, tickt sie gemeinsam auf einer Wall-Clock-Achse. Drift als Plateau (elapsed_s = end_elapsed_s, kein Weglaufen); gesunde Maschinen bleiben gesund (kein erfundenes Signal, Ehrlichkeitslinie); events() leer (erfindet keine Alarme).

adapters/simulation/live_worker.py (neu) — Dauer-Vordergrundprozess python -m foreman.adapters.simulation.live_worker [--interval-seconds 60] [--seed --batch-size --max-ticks --max-catchup-ticks --db-url].

Reuse statt Neubau: schreibt über den unveränderten IngestionService.ingestcopy_readings-Einzigkeit (§12.3) und notify_changes/WS-Push (F5) je Commit. SimulationAdapter bekommt die geteilten Nähte tick_readings/new_rngs/end_elapsed_s/local_timezone; readings() (Backfill) nutzt dieselben — verhaltensneutraler Refactor.

Doku: GROUND_TRUTH §12.6 (+ §12.5-Klarstellung), DEPLOY.md Etappe 3 (eigener Railway-Worker-Service, Trockenlauf → scharf).

Leitplanken ↔ Umsetzung

Leitplanke Wo
Wall-Clock-Stempel, nie Sim-Zeit tick_readings(utc_time=…), live_tick_times
Am Historien-Ende, kein Doppel/keine Lücke resolve_resume_anchor (max(time)) + strikt anchor+k·interval
Drift-Plateau, kein erfundenes Signal end_elapsed_s konstant; Drift None → Offset 0
Neustart-fest Anker je Start frisch aus DB
Tick-Intervall konfigurierbar --interval-seconds (Default 60 s)
Push über bestehende WS-Infra IngestionServicenotify_changes

Tests & Gates

  • 16 Unit (tests/unit/test_live_stream.py): Pacer (Vergangenheit/Zukunft/naive-UTC), Tick-Achse (Gap/Overlap/max_ticks), Plateau, Ehrlichkeitslinie, Aufhol-Deckel, geteilte Nähte/Determinismus.
  • 4 Integration (tests/integration/test_live_worker.py, braucht DB): Anschluss am Ende ohne Gap/Overlap + Historie unangetastet; neustart-fest ohne Doppeln; genau ein NOTIFY je Commit; Default lädt alle 12 Park-Szenarien.
  • Gate grün: ruff check clean · ruff format ok · mypy --strict 0 (135 Dateien) · pytest 791 passed, 2 skipped @ 94.34 % Coverage.
  • CLI-Smoke gegen Test-DB: leere DB → Now-Anker (heutige Stempel); zweiter Lauf → Resume aus DB, 0 Doppel, lückenlose Fortsetzung.

Betrieb / Sicherheit

  • Nicht ungeprüft auf die öffentliche Live-DB: Live-Betrieb ist ein dokumentierter, manueller Schritt (DEPLOY.md Etappe 3) — erst Trockenlauf (--max-ticks), Blick drauf, dann scharf. CC merged nicht selbst.
  • Genau eine Worker-Instanz (PK schützt vor Dubletten, zwei Schreiber kollidieren beim COPY).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Neue Funktionen
    • Live-Daten-Stream für Simulationen ergänzt: kontinuierliche, zeitnahe Messwert-Erzeugung mit Intervall-Pacing und nachvollziehbarer Zeitachsen-Logik.
    • Neuer Live-Worker mit konfigurierbaren Startoptionen (u. a. Intervall, Seeds, Tick-Limits, Capture-Cap) und zuverlässigem Neustartverhalten.
  • Bug Fixes
    • Verhindert Lücken oder doppelte Einträge nach Unterbrechungen und begrenzt große Aufholphasen zur Entlastung.
    • Schutz vor ungewollten Mehrfach-Worker-Läufen.
  • Dokumentation
    • Bereitstellungsanleitung um Live-Stream-Schritte inkl. Verifikations-Checkliste erweitert.
    • Live-Worker-Verhalten in den Simulationseinsätzen präzisiert.
  • Tests
    • Neue Unit- und Integrationstests für Pacing, Ankerbildung, Lückenfreiheit und Neustart-Fortsetzung.

…it Wall-Clock fortsetzen

Setzt den signalbasierten Twin-Park-Generator am ENDE der Backfill-Historie an
und tickt mit Wall-Clock-Stempeln (aktuelle Zeit, nie Szenario-Sim-Zeit)
fortlaufend weiter — das Dashboard lebt, statt dass die Historie altert.
Eingangs-Simulation (Zwilling als Datenquelle); strikt getrennt vom FOREMAN-
internen Reasoning-Simulieren (bleibt inaktiv).

- LiveParkAdapter (SourceAdapter): bündelt je Park-Szenario einen
  SimulationAdapter und tickt sie gemeinsam auf EINER Wall-Clock-Achse (ein
  Tick = ein Stempel über alle Maschinen → ein gebündeltes NOTIFY je Commit).
  Drift als Plateau (konstantes elapsed_s = end_elapsed_s, kein Weglaufen);
  gesunde Maschinen bleiben gesund (kein erfundenes Signal, Ehrlichkeitslinie).
- RealTimePacer (Echtzeit 1:1, kein speed): wartet bis zum Tick-Stempel, kein
  Warten in der Vergangenheit (Aufhol-Phase). resolve_resume_anchor liest
  max(time) der Park-Datenpunkte → neustart-fest, kein Doppel/keine Lücke;
  optionaler --max-catchup-ticks-Deckel (geloggte Lücke statt Boot-Storm).
- Reuse statt Neubau: schreibt über den UNVERÄNDERTEN IngestionService.ingest
  (copy_readings-Einzigkeit + notify_changes/WS-Push je Commit). Geteilte
  Nähte in SimulationAdapter (tick_readings/new_rngs/end_elapsed_s/
  local_timezone); readings() (Backfill) nutzt dieselben — verhaltensneutral.
- Worker-Entrypoint live_worker.py (dünner Dauer-Vordergrundprozess); eigener
  Railway-Worker-Service + Scharfschaltung dokumentiert (DEPLOY.md Etappe 3),
  CC merged nicht selbst.
- Tests: 16 Unit (Pacer, Tick-Achse ohne Gap/Overlap, Plateau, Ehrlichkeit,
  Aufhol-Deckel) + 4 Integration (Anschluss am Ende, neustart-fest ohne
  Doppeln, ein NOTIFY je Commit, Historie unangetastet). GROUND_TRUTH §12.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d0429288-5594-4502-b247-058a60e99086

📥 Commits

Reviewing files that changed from the base of the PR and between 3ded6bb and d91ebd2.

📒 Files selected for processing (1)
  • tests/unit/test_live_stream.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/test_live_stream.py

📝 Walkthrough

Walkthrough

Die PR fügt einen wall-clock-getakteten Live-Datenstrom für Simulationen hinzu. Ein Live-Adapter ermittelt den Resume-Anker aus der Datenbank, ein separater Worker schreibt Readings über den bestehenden Ingestion-Weg, und Deployment-, Integrations- sowie Unit-Tests wurden ergänzt.

Changes

Live-Daten-Stream-Worker

Layer / File(s) Summary
Wall-Clock und Resume-Anker
src/foreman/adapters/simulation/live.py, tests/unit/test_live_stream.py
RealTimePacer, live_tick_times, cap_resume_anchor und resolve_resume_anchor bestimmen Ticks und Wiederaufnahme; Unit-Tests prüfen Warten, Monotonie, max_ticks und Catch-up-Kappung.
Per-Tick-Readings
src/foreman/adapters/simulation/adapter.py, tests/unit/test_live_stream.py
SimulationAdapter extrahiert RNG-Erzeugung, lokale Zeitzone, end_elapsed_s und tick_readings(...); die Unit-Tests prüfen deterministische Werte, Zeitstempel, Drift und Zeitzonenverhalten.
Live-Worker-Wiring
src/foreman/adapters/simulation/live.py, src/foreman/adapters/simulation/live_worker.py, DEPLOY.md, GROUND_TRUTH.md, tests/integration/test_live_worker.py
LiveParkAdapter und run_live_worker() verbinden Live-Ticks, DB-Anker, CLI-Start und Ingestion; Deployment- und Integrationstests prüfen Restart, NOTIFY und fortlaufende Zeitreihen.

Sequence Diagram(s)

sequenceDiagram
  participant amain
  participant run_live_worker
  participant IngestionService
  participant LiveParkAdapter
  participant AsyncSession
  participant RealTimePacer

  amain->>run_live_worker: scenarios, interval, seed, max_ticks
  run_live_worker->>IngestionService: ingest(adapter, pace=RealTimePacer)
  IngestionService->>LiveParkAdapter: seed_topology()
  LiveParkAdapter->>AsyncSession: resolve_resume_anchor(max(time))
  AsyncSession-->>LiveParkAdapter: latest reading time
  IngestionService->>RealTimePacer: wait(target tick)
  IngestionService->>AsyncSession: write readings + notify
Loading

Estimated code review effort

🎯 4 (Komplex) | ⏱️ ~60 minutes

Possibly related PRs

  • patricznr1/foreman#3: Refactors SimulationAdapter.readings() and introduces the tick_readings/timezone contract used here.
  • patricznr1/foreman#18: Introduces the ingestion notification plumbing and realtime WS stack used by the live worker’s notify path.
  • patricznr1/foreman#41: Introduces the --anchor-now / end_anchor time-window shift that this live-worker flow builds on.

Poem

Ich hopp’ im Takt der Wall-Clock, ganz sacht und fein,
die Ticks springen weiter, kein Loch darf sein.
Mit Anker und Worker durch die Nacht,
bleibt jede Reading-Zeit wohl bedacht.
🐇✨ Hopp-hopp, der Live-Stream ist erwacht!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Der Titel beschreibt den Hauptumfang der Änderung klar: Live-Stream-Worker für Simulation mit Fortsetzung am Historienende per Wall-Clock.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/live-data-stream

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
DEPLOY.md (1)

284-286: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fenced Code Block ohne Sprachangabe (MD040).

Der Block beim Start-Command hat keine Sprachkennzeichnung; markdownlint meldet MD040.

Vorschlag
-   ```
+   ```bash
    python -m foreman.adapters.simulation.live_worker --interval-seconds 60
    ```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DEPLOY.md` around lines 284 - 286, The start-command example in DEPLOY.md is
an unlabeled fenced code block, which triggers MD040. Update the fenced block
around the live_worker command to include the appropriate language identifier
(for example, using the markdown code fence in that section) so the block is
properly annotated and matches the surrounding docs style.

Source: Linters/SAST tools

src/foreman/adapters/simulation/live_worker.py (1)

69-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Docstring-Bezeichner an die Signatur angleichen.

Der Docstring spricht von „pace/now injizierbar", der Parameter heißt aber pacer. Kleinigkeit, hilft aber beim Lesen.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/foreman/adapters/simulation/live_worker.py` around lines 69 - 74, The
docstring in live_worker.py is out of sync with the callable signature because
it mentions an injectible “pace” parameter while the actual argument is named
pacer. Update the docstring for the Live-Adapter/ingestion helper to use the
same identifier as the function signature, keeping any other referenced symbols
like scenarios, max_ticks, now, and IngestionService.ingest consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@DEPLOY.md`:
- Around line 284-286: The start-command example in DEPLOY.md is an unlabeled
fenced code block, which triggers MD040. Update the fenced block around the
live_worker command to include the appropriate language identifier (for example,
using the markdown code fence in that section) so the block is properly
annotated and matches the surrounding docs style.

In `@src/foreman/adapters/simulation/live_worker.py`:
- Around line 69-74: The docstring in live_worker.py is out of sync with the
callable signature because it mentions an injectible “pace” parameter while the
actual argument is named pacer. Update the docstring for the
Live-Adapter/ingestion helper to use the same identifier as the function
signature, keeping any other referenced symbols like scenarios, max_ticks, now,
and IngestionService.ingest consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a6e05c26-1cf5-4d77-8378-bcc94f196f97

📥 Commits

Reviewing files that changed from the base of the PR and between 081e992 and 3ded6bb.

📒 Files selected for processing (7)
  • DEPLOY.md
  • GROUND_TRUTH.md
  • src/foreman/adapters/simulation/adapter.py
  • src/foreman/adapters/simulation/live.py
  • src/foreman/adapters/simulation/live_worker.py
  • tests/integration/test_live_worker.py
  • tests/unit/test_live_stream.py

… umbrechen

CI-Gate `ruff format --check` schlug an test_live_stream.py an (eine zu lange
assert-Zeile in test_cap_resume_anchor_ohne_deckel_fuellt_die_luecke). Reiner
Formatter-Umbruch, identischer AST. Lokal das volle CI-Gate repliziert:
mypy 0 / ruff check clean / ruff format --check clean / pytest 791 passed @ 94.34%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@patricznr1
patricznr1 merged commit 8855173 into main Jun 25, 2026
3 checks passed
@patricznr1
patricznr1 deleted the feat/live-data-stream branch June 25, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant