Repository navigation
fix: degradation watch regresses on the whole lookback, not the newest 40 sessions - #43
Merged
Merged
Conversation
…t 40 sessions fit_sessions kept only the newest 40 sessions, so the drift regression's baseline was whatever happened ~40 charges ago. On the live install a single week of low fits (Sep 14-20) at that edge read as +8.1 C of statistically confirmed drift and raised the alert on 10-14; over the 80 days since the verified anchor the same regression is flat (-0.04 C, 95% CI -5.9..5.8). fit_sessions now fits every session in the lookback and tags fits from the newest FORECAST_SESSIONS (40) with forecast=True. fit_history takes tau, rise and the current exponent from those only, so the forecast is unchanged: lifting the cap for it too was backtested and moved the in-run cold-start forecast 0.4-0.5 C optimistic. With the split, contrib/backtest_forecast.py output is byte-identical to main under both --truth fit and --truth last on the live history. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The history fit now covers the whole drift lookback and takes ~27 s on the live install (72 fits; it was 9-10 s for 30), while the amp controller polls /api/thermal with a 10 s timeout. The endpoint used to refit inline every 6 h, so that poll would time out and the controller would skip the tick. A stale fit (past THERMAL_REFIT_S, or a changed idle-offset model) is now served at once while a single background refit runs. The cache is warmed at app startup; only a cold cache or an explicit ?refit waits, and the cold wait is shielded so a caller giving up doesn't cancel the refit. A refit that finishes after a newer one doesn't overwrite it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The heat-rise degradation alert ("Handle heat rise increasing") was raised on 10-14 with a "statistically confirmed" +8.1 °C over 34 days (95% CI 2.8..13.3, n=29). The connector isn't degrading.
fit_sessionskept only the newest 40 sessions ([:40], there since the first thermal commit), so the drift regression's baseline was whatever happened ~40 charges ago. On this install that edge fell on one week of unusually low fits (Sep 14–20: median rise 28.9 °C against 35–37 °C before and after). The "climb" was the recovery from that week.Garage ambient isn't the explanation: the regression's ambient coefficient is +0.12 ± 0.16 °C/°C, and the garage was warmer in mid-September than during the recent warm days.
Code touched
wallmonitor/thermal.pyfit_sessionsfits every session in the lookback (default 120 days) and tags fits from the newestFORECAST_SESSIONS(40) withforecast: true. Older sessions are processed after the newest 40, so the newest 40's fits come out exactly as before. Thetau_priorused for window sizing grows in the same order.forecast_fits(fits)returns the tagged subset.fit_historytakes tau, rise and the current exponent fromforecast_fitsonly, and re-normalizes every fit'srise_ref_cwith that exponent, so the drift check's older fits share the forecast's current law.wallmonitor/web.py:/api/thermalserves a stale fit right away and refits in the background. The fit now covers the whole lookback and takes ~27 s on the live install, up from 9–10 s, and the amp controller polls this endpoint with a 10 s timeout. A fit is stale afterTHERMAL_REFIT_S(6 h, as before) or when the idle-offset model changes. Only one background refit runs at a time, and the cache is warmed at app startup. Only a cold cache or an explicit?refitwaits. The cold wait is shielded so a caller that gives up doesn't cancel the shared refit, and a refit that finishes late doesn't overwrite a newer one.contrib/backtest_forecast.py: leave-one-out params come fromforecast_fits, matching what the deployed forecast learns from.tests/test_wallmonitor.py: two new tests. The first checks that a stale fit is served without waiting and that repeated stale requests start only one refit. The second is a regression test. A flat history with a low stretch at the 40th-newest session must not alarm, and the old 40-session window on the same data must. It also pins the forecast to the newest 40.docs/thermal-model.md: the "What it compares" section now says the watch uses the whole lookback.Deliberately left alone: the forecast's 40-session window. Lifting it for the forecast too was backtested and rejected (see Verification). The constant's comment records why the cap is still there.
Risk
main. Slower hardware, such as a Raspberry Pi, or a heavier charging history will take longer. The poller runs it in a worker thread when a session ends./api/thermalno longer refits inline: onmain, the request that hit the 6 h refresh waited for the whole fit, which was already close to the amp controller's 10 s timeout. A stale fit is now served at once while the refit runs in the background.skip: cannot reach wallmonitorand leaves the current unchanged.mainhas the same window at 9–10 s.session_fitsin/api/thermalnow lists the whole lookback, not the newest 40 sessions, and each fit gains aforecastboolean. The dashboard's rise-vs-date chart shows more history. Nothing else consumes the list.Verification
uv run pytest -q: 182 passed. The new test fails onmain(assert 40 == 52: only 40 of 52 sessions fitted).contrib/backtest_forecast.pywas run against a snapshot of the live database (114 sessions), with--truth fitand--truth last:mainin both truth modes./api/thermal: the first request took 33.4 s (cold, waiting on the warm-up), and the next two took 0.005 s each. The stale-cache path is covered by the new test.ruffreports 3 errors that already exist onmainin test files; none are from this change.Deploy: update the package (or
git pulla checkout) on the host running wallmonitor, then restart the service, e.g.sudo systemctl restart wallmonitor.service. The stale drift alert clears at the next refit (the next session end, or/api/thermal?refit=1for the dashboard's view).