feat: fit how rise scales with current, and restore straight to the sustainable rate - #31
Merged
Merged
Conversation
…ustainable rate The first live calibration probe held 32 A for 40 min and then released to 48 A on a day the idle forecast had already said full rate would trip. The controller re-capped 3.5 min later: 32 -> 48 -> 42 A, and those minutes at 48 A pushed the handle from 48.8 to 54.4 C. Two things were wrong, one in the model and one in the controller. The model assumed rise scales with I^2. It does not on this install: the 32 A probe settled 4 C above the I^2 prediction, and the two 39.6 A fits read 42.7 and 43.3 C "at 48 A" against ~36 for every 48 A window. Part of the handle's heat is current-independent, so plateaus fall off more gently than I^2 as current drops. The exponent is now fitted per install by log-log regression over free-running fits (>= 4 fits spanning >= 6 A, else the I^2 prior stands), every fit's rise_ref_c is re-normalized with it, and every forecast at an off-reference current goes through it. On the production database: n = 1.20 +/- 0.08 from 13 fits; the 39.6 A fits re-read as 36.7 and 37.2; the drift regression's current coefficient drops from -0.80 to -0.06 C/A, because there is no normalization error left for it to absorb. Verdict unchanged. The controller restored to normal_amps on probe completion, and climbed 2 A per confirmed-clear cycle everywhere else. Each rung resets the trajectory window, so a climb from 32 A took half an hour to find a number the model could name at once. The server now reports sustainable_max_a on every forecast - the highest current whose plateau stays under the trip point at today's ambient - and the controller restores straight to it: on probe completion, and on the first confirmed-clear restore of a session. After a quick reversal the model has already been wrong about today and the climb falls back to single steps; against a server without the field the ladder is unchanged. Which ambient feeds that number matters. Replaying the probe's end on production data, the trajectory-implied ambient (n = 1.2 extrapolated to 32 A over-reads the rise by 2 C) named 47 A, which would have tripped; the LAN sensor's 30.25 C named 44 A, which is what held. So the sustainable current is worked from a measured ambient whenever a sensor reports - interpolation inside the fitted data rather than a round trip to 32 A and back - and from the implied ambient only without one. Today's session under this code: 32 A probe, then 44 A in one move. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 15, 2026
Closed
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 first live calibration probe (2026-09-14) held 32 A for 40 min, then released to 48 A on a day the idle forecast had already said full rate would trip (
safe_ambient_max_c28.7 at 28.9 °C ambient). The controller re-capped 3.5 min later — 32 → 48 → 42 A — and those minutes at 48 A pushed the handle from 48.8 to 54.4 °C.Two defects behind it:
normal_amps; every other restore climbed 2 A per confirmed-clear cycle. Each rung resets the trajectory window, so a climb from 32 A took ~30 min to find a number the model could name at once.Code touched
wallmonitor/thermal.pyThermalParamsgainscurrent_exp(+ fits used, SE) withrise_at()/current_for_rise(); every(I/48)**2site now goes through them.as_dict()exposes the three fields._fit_current_exponent(): log-log OLS over free-running fits; needs ≥ 4 fits spanning ≥ 6 A, clamped to [1.0, 2.5], else the I² prior withcurrent_exp_fits: 0. Regulated fits excluded — they cluster at high current and would bend n down for the wrong reason.fit_sessions()records rawrise_cper fit;fit_history()fits n first and re-normalizes every fit'srise_ref_cin place, so the API, the drift watch and the median all share one current law. No schema change — fits are recomputed, not stored.sustainable_max_current(): highest current under trip − margin, capped at 48;suggest_max_current()becomes a thin wrapper (None when ≥ 48).predict()reportssustainable_max_aon every forecast (charging + idle) plussustainable_ambient_source. Measured LAN ambient is preferred over the trajectory-implied one — see Verification for why.contrib/derate_amp_control.pymin(normal, max(ladder step, sustainable)). Trusted once per session —restore_attempts > 0falls back to single steps. Field absent → old ladder, byte-for-byte.last_step_up_ts, so a re-cap is a quick reversal and backs off); hold at probe current if sustainable is no higher; full restore if it says 48 or the field is absent.--restore-step-ahelp updated. Left alone: cap path, confidence guard, backoff,suggested_max_asemantics.wallmonitor/static/app.js: model note saysHeat rise scales as I^n here (fitted across N free-running sessions)when fitted; charging "no derate" line shows the sustainable rate when it's under 48.docs/thermal-model.md,docs/amp-control.md: current-law bullet, restore-target paragraph, probe release wording, I² mentions.Risk
max_restore_attempts. A wrong jump is a quick reversal → ladder for the rest of the session.--forecast-confidence-k 4, a fresh trajectory window's wide SE may trim 2 A right after a jump, then the ladder returns — worst case one extra round, self-resolving.rise_ref_cvalues on the API/scatter shift slightly for off-reference fits (the point of the change); 48 A fits move < 0.7 °C.Verification
uv run pytest: 151 passed. New: exponent recovered from an n = 1.5 seed across 32/40/48 A (±0.15, every fit re-normalized to ~36); prior stands with one current;sustainable_max_currentfollows n (38 vs 35 A at 40 °C ambient) and saturates at 48;predictreports the field on trajectory basis with and without a predicted trip; controller: jump, jump-to-48 lifts fully, never below a ladder step, ladder after a quick reversal, probe → sustainable / hold / full restore.Deploy: pull +
sudo systemctl restart wallmonitor.service. The amp-control oneshot re-reads its script every 30 s; no unit change.🤖 Generated with Claude Code