docs: explain the Sud forecast's static-vs-dynamic duration gap

Documents why a brew commonly takes ~250-270 min against a ~160 min
static estimate: the PID cascade needs real time to spin up and settle
within the tight 0.2°C "reached" tolerance, a roughly per-step-constant
lag that accumulates across every ramp/hold transition. Found and
confirmed by a live measured run on the real server.
This commit is contained in:
2026-06-21 14:38:31 +02:00
parent c27a3b06d4
commit 54422f6d27
+29
View File
@@ -189,6 +189,35 @@ step. With no schedule loaded (or all its steps run), it just sits idle -
manual `theta_soll`/`heatrate_soll` control via the GUI works the same as
ever.
### Forecast vs. actual duration
The GUI's Automatic tab shows two different time estimates, and they can
diverge substantially:
- Before a run starts, it shows a *static* estimate: walk the schedule
assuming every ramp instantly achieves and holds its declared `rate`
(`abs(delta)/rate`) and every hold lasts exactly its declared `duration`.
- Once running, it switches to a *dynamic* one: the already-elapsed part is
the actual measured trace, and only the remaining steps are re-projected
from the live temperature/step/`hold_remaining` each tick.
In practice the dynamic total tends to run well above the static one - a
schedule estimated at ~160 min commonly finishes closer to ~250-270 min.
The static model assumes the plant instantly tracks the declared rate and
that "reached" is instant once the math says so; the real PID cascade
(`theta_err -> pid_hold -> heatrate_soll -> pid_heat -> heater power ->
actual heat rate`) needs real time to spin up (constrained by the plant's
thermal mass and the Smith predictor's transport-delay compensation) and
then more time to settle within the tight 0.2°C "reached" tolerance
(`tasks/sud.py`'s `TEMP_REACHED_TOLERANCE`) once nominally at target. That
settling lag is roughly constant per step rather than proportional to the
step's nominal duration, so it eats a larger fraction of short ramps and
keeps accumulating across every ramp/hold transition in the schedule -
many small (~20-90%) per-step overruns compounding into one large gap by
the end, rather than one big error anywhere in particular. The dynamic
re-anchoring is what corrects for this as a brew actually progresses; the
upfront static number has no settling-time data to work with yet.
## Logging & analysis
`tracer.py` (and `tasks/tracer.py`) periodically dump traced signals