Repository navigation
feat: a settle-on-target amp controller and a closed-loop backtest to judge it (experimental, not wired in) - #38
Merged
Conversation
… judge it (experimental, not wired in) 2026-10-02 (session 101): the amp controller moved 49 -> 40 -> 46 -> 44 -> 46 -> 48 -> 45 -> 41 A in eighteen minutes, then the quick-reversal back-off pinned the session at 41 A when ~44 A was sustainable. Step-ups are decided on the plateau at the present current, never the target's; caps trust a two-minute-old trajectory fit; and a 2-3 A step stays inside predict()'s 10% window band, so the next fit mixes two currents. contrib/settle_amp_control.py is a candidate replacement for the derate decision. It tracks one number per session - the effective ambient, plateau minus the model's rise - fused from the ambient prior and one reading per steady-current stretch, and targets the largest whole amp whose plateau stays a soak margin under the trip. It front-loads at full rate while the handle is cold, makes one planned drop, and afterwards steps down on whichever is higher of the fused estimate and the live stretch's own reading, with a handle-driven cut near the trip that ignores the estimate. Not wired into the daemon. contrib/backtest_settle_amp_control.py closes the loop the existing backtest leaves open: it replays a session's real samples through the real predict() until the simulated command diverges from what the car drew, then hands over to a two-time-constant plant fitted to that session, and reruns each session against plants 1-2 C hotter than the fit. Whole history, 82 sessions with a usable plant (15 short, 4 poor fits skipped), fitted plant, deployed vs settle at a 1.5 C soak margin: trips 0 / 0, worst peak 64.5 / 63.7 C, amp changes 432 / 288, 3542 / 3561 Ah. On the 54 sessions where full rate is not sustainable: median changes 7 / 5, mean reversals 3.1 / 0.9, sessions with no reversal 3 / 31. No trips for either on any of the four hotter plants. A 2.0 C margin delivers less than the deployed controller and is no safer. Two drafts failed this backtest before this one passed, both pinned by tests: counting every tick of a long stretch as a new reading (held 48 A into a 64.3 C peak on 09-30), and averaging the live reading with earlier, cooler ones (seven trips on hot days across the history). Known limits: the plant is fitted to the day it is judged on; the forecast still under-reads the plateau until the cable has soaked, which is why hot days take several 1 A steps down rather than one move; 09-06 is worse than the deployed controller (12 changes, 6 reversals against 10 and 4). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merged
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
On 2026-10-02 (session 101) the amp controller moved 49 -> 40 -> 46 -> 44 -> 46 -> 48 -> 45 -> 41 A in eighteen minutes. The quick-reversal back-off then pinned the session at 41 A when about 44 A was sustainable. Three things in the deployed decision produce this:
predict()'s 10% window band, so the next fit mixes two currents.The existing backtest cannot judge a replacement either: it is open-loop, so once a candidate commands a different current from the one the car actually drew, the recorded samples no longer describe what would have happened.
Code touched
contrib/settle_amp_control.py(new): a candidate replacement for the derate decision. It tracks one number per session, the effective ambient (plateau minus the model's rise), fused from the ambient prior and one reading per steady-current stretch. It targets the largest whole amp whose plateau stays a soak margin under the trip. It front-loads at full rate while the handle is cold and makes one planned drop. After that it steps down on whichever is higher of the fused estimate and the live stretch's own reading, with a handle-driven cut near the trip that ignores the estimate.contrib/backtest_settle_amp_control.py(new): closes the loop. It replays a session's real samples through the realpredict()until the simulated command diverges from what the car drew, then hands over to a two-time-constant plant fitted to that session. Each session is rerun against plants 1-2 C hotter than the fit.tests/test_settle_amp_control.py(new): 9 tests, including the two failed drafts described under Verification.contrib/derate_amp_control.py, the daemon, the installer and the docs. Nothing imports the new controller.Risk
contrib/and nothing is wired in. The deployed controller behaves exactly as before.Verification
uv run pytest: 176 passed (9 new). Ruff: no findings on the three new files.Deploy: nothing to deploy.