docs: update Forecast vs. actual duration for the new server-side simulated estimate

This commit is contained in:
2026-06-21 16:47:35 +02:00
parent 567ab80c9c
commit c9d38e50db
+28 -23
View File
@@ -191,32 +191,37 @@ ever.
### Forecast vs. actual duration ### Forecast vs. actual duration
The GUI's Automatic tab shows two different time estimates, and they can The GUI's Automatic tab shows up to three time estimates for a schedule:
diverge substantially:
- Before a run starts, it shows a *static* estimate: walk the schedule - Immediately on load, a quick *naive* preview: walk the schedule assuming
assuming every ramp instantly achieves and holds its declared `rate` every ramp instantly achieves and holds its declared `rate`
(`abs(delta)/rate`) and every hold lasts exactly its declared `duration`. (`abs(delta)/rate`) and every hold lasts exactly its declared `duration`.
- Once running, it switches to a *dynamic* one: the already-elapsed part is Deliberately approximate - it's just a placeholder until the next one
the actual measured trace, and only the remaining steps are re-projected arrives a moment later.
from the live temperature/step/`hold_remaining` each tick. - A *simulated* estimate, computed server-side (`components/sud_forecast.py`'s
`SudForecastEstimator`) by actually running the schedule through a
throwaway `Sud`/`Pot`/temperature-controller trio built from the same
params (`pid_type`, `TempCtrl` gains, plant params, ambient, heater max
power) the real server uses - a multi-hour brew simulates in well under a
second since it's pure CPU-bound iteration, no real time/IO involved. This
replaces the naive preview a moment after a schedule is loaded.
- Once running, 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 The naive estimate is structurally optimistic - it assumes the plant
schedule estimated at ~160 min commonly finishes closer to ~250-270 min. instantly tracks the declared rate and that "reached" is instant once the
The static model assumes the plant instantly tracks the declared rate and math says so, when the real PID cascade (`theta_err -> pid_hold ->
that "reached" is instant once the math says so; the real PID cascade heatrate_soll -> pid_heat -> heater power -> actual heat rate`) needs real
(`theta_err -> pid_hold -> heatrate_soll -> pid_heat -> heater power -> time to spin up and settle within the tight 0.2°C "reached" tolerance
actual heat rate`) needs real time to spin up (constrained by the plant's (`tasks/sud.py`'s `TEMP_REACHED_TOLERANCE`). The simulated estimate accounts
thermal mass and the Smith predictor's transport-delay compensation) and for that (it's driven by the real controller dynamics), so it's normally
then more time to settle within the tight 0.2°C "reached" tolerance within a few percent of how the dynamic one settles once a run actually
(`tasks/sud.py`'s `TEMP_REACHED_TOLERANCE`) once nominally at target. That finishes - if the two diverge by far more than that, suspect a real control
settling lag is roughly constant per step rather than proportional to the issue rather than a forecasting one (see the `HoldCool` threshold note in
step's nominal duration, so it eats a larger fraction of short ramps and `components/pid/temp_controller_base.py` - too tight a threshold there once
keeps accumulating across every ramp/hold transition in the schedule - caused exactly this kind of large, otherwise-unexplained gap, by making the
many small (~20-90%) per-step overruns compounding into one large gap by controller chatter in and out of `COOL` and never actually converge).
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 ## Logging & analysis