feat(simulation): Live-Daten-Stream-Worker — Park am Historien-Ende mit Wall-Clock fortsetzen - #47
Conversation
…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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughDie 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. ChangesLive-Daten-Stream-Worker
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
Estimated code review effort🎯 4 (Komplex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
DEPLOY.md (1)
284-286: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueFenced 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 valueDocstring-Bezeichner an die Signatur angleichen.
Der Docstring spricht von „
pace/nowinjizierbar", der Parameter heißt aberpacer. 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
📒 Files selected for processing (7)
DEPLOY.mdGROUND_TRUTH.mdsrc/foreman/adapters/simulation/adapter.pysrc/foreman/adapters/simulation/live.pysrc/foreman/adapters/simulation/live_worker.pytests/integration/test_live_worker.pytests/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>
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.
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 (keinspeed); 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 nachanchor(kein Overlap; PK(data_point_id, time)kollidiert nie), lückenlos iminterval-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 beinow(geloggt, kein stilles Abschneiden) statt eines Boot-Storms; Default = lückenlos füllen.LiveParkAdapter(SourceAdapter) — bündelt je Park-Szenario einenSimulationAdapter, 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-Vordergrundprozesspython -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.ingest→copy_readings-Einzigkeit (§12.3) undnotify_changes/WS-Push (F5) je Commit.SimulationAdapterbekommt die geteilten Nähtetick_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
tick_readings(utc_time=…),live_tick_timesresolve_resume_anchor(max(time)) + striktanchor+k·intervalend_elapsed_skonstant; DriftNone→ Offset 0--interval-seconds(Default 60 s)IngestionService→notify_changesTests & Gates
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.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.ruff checkclean ·ruff formatok ·mypy --strict0 (135 Dateien) ·pytest791 passed, 2 skipped @ 94.34 % Coverage.Betrieb / Sicherheit
--max-ticks), Blick drauf, dann scharf. CC merged nicht selbst.🤖 Generated with Claude Code
Summary by CodeRabbit