docs: document per-logger elapsed timelines and the log_latest.* files

Covers the last two commits: tick-counted t/_elapsed() semantics and why
PlantParams must be stamped with each logger's own (not Sud.elapsed
universally), plus the log_latest.json/log_latest_sud.json fixed-name
copies and the caveat that the two aren't guaranteed to be in sync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JFDeoTKBDkXRhPfnyDEoBt
This commit is contained in:
2026-07-01 20:55:49 +02:00
co-authored by Claude Sonnet 5
parent 0382bb01aa
commit 452d8ddd4b
+27 -2
View File
@@ -732,19 +732,44 @@ timestamp, plus any plant-param changes along the way (see
`apply_plant_params()`) - written as JSON via `write()`, always the whole
accumulated sample list rewritten to the same file, never appended.
Each sample's simulated-elapsed-seconds (`'t'`) is tick-counted, never
derived from wall-clock time (see `_elapsed()` in `tasks/server_log.py`) -
`SudLogTask`'s is `Sud.elapsed` itself (so it lines up with
`SudForecastEstimator`'s own tick-counted timeline for forecast
comparison), while `ServerLogTask`'s is its own independent counter since
server startup, on a different timeline (the server usually starts well
before any Sud does). `PlantParams` entries are always stamped with
*that same logger's* `_elapsed()` too, for exactly this reason - using the
wrong one (e.g. `Sud.elapsed` in a `ServerLogTask`) would put
`PlantParams`' timestamps on a different scale than that log's own
`Samples`, breaking anything (like `utils/replay_sim.py`) that correlates
the two by `t`.
`tasks/server_log.py`'s `ServerLogTask` records continuously for the whole
server session, regardless of Sud state - useful for verifying controller
and heater behaviour outside of a scheduled brew. It starts on construction
and is written to `logs/log_<date>T<time>.json` on shutdown, and again
every `log_interval` seconds (`config.json`, default 300) in between so a
hard crash or power loss only loses up to that much data.
hard crash or power loss only loses up to that much data. Every write also
overwrites a fixed-name `logs/log_latest.json` with the same content, so a
dashboard/tail-style consumer can point at one unchanging path instead of
tracking the current session's own timestamped filename.
`tasks/sud_log.py`'s `SudLogTask` subclasses `ServerLogTask` to reuse the
same sample format and `log_interval` checkpointing, but only records while
a Sud run is actually active. A fresh run starts recording on a genuine
`Start` from `IDLE`/`DONE` (a `Pause`→resume `Start` is a no-op - it's still
the same run) and is written out on `Stop` or natural completion (`DONE`),
to `logs/log_<date>T<time>_<sud-name>.json`.
to `logs/log_<date>T<time>_<sud-name>.json` - and, the same way, also to a
fixed `logs/log_latest_sud.json`.
`logs/log_latest.json` and `logs/log_latest_sud.json` are updated on
independent schedules (`ServerLogTask`'s own periodic checkpoint is
completely decoupled from any particular Sud run starting or stopping), so
at any given moment they can reflect different points in the same overall
session - don't assume they're a matched pair unless you've just watched a
run finish (`log_latest_sud.json` is only ever current *immediately* after
a `Stop`/`DONE`).
`server/brewpi.py` also mirrors everything it prints (every component's
`print()`-based status/debug output) to a plain text log,