From 54422f6d270530eb75451ed661293cabe6daa2a7 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 21 Jun 2026 14:38:31 +0200 Subject: [PATCH] docs: explain the Sud forecast's static-vs-dynamic duration gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 6f85c25..9c268f6 100644 --- a/README.md +++ b/README.md @@ -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