Repository navigation
fix(control): a cut on its way covers the overage it was sent for - #1802
Merged
Merged
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Easee Cloud applies a cut after 30-120 s, but the EV fuse cap took each cut from the highest offer of the last 20 s. Once that window passed, the meter still showed the old draw and Core cut again: in the field matrix 10,400 -> 6,900 W, then 0 W after a 6 A kettle step. The same window missed a raise still on its way, so a cut could leave the phase over the limit once the raise landed. The cut now counts our own orders that cannot have shown yet, both ways, from the per-charger, per-phase log of #1776/#1793. A cut on its way covers the overage it was sent for until its charger can have applied it (max(20 s, declared power_max_age_s), 180 s undeclared); Core cuts only what it does not cover. A raise on its way adds to the cut. A cut and every order after it count as not shown; of the orders before it, a raise counts from the lowest. An offer found in force at a restart, which Core did not send in this run, counts as shown. Once a phase's filtered current passes 1.25 x In, nothing on its way holds back a cut. Over the fuse the cap never rises above the latest offers. Rises, starts and the hour's import ceiling are unchanged. Field matrix: 93 of 99 netting rows pass. The other six are cut by the split, which floors 7,600 W to 6,900 W (7600/690*690 = 7599.999...); Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of the first commit found three ways a cut on its way could cover more than it removes: - A cut that landed covered a later step on the same phase for the charger's whole response time: 180 s for a fast charger that declares nothing, such as OCPP. A cut now covers only within the run over the limit it was sent in. The run ends once every phase has read under the working limit for evSettle; a short dip, as battery relief gives, does not end it. Probe: 2 min 35 s over the limit becomes 22 s, as master. - A car that draws less than its order retried each cut after 180 s (10 min 36 s over the limit in the review's case). Once the run's first cut has outlasted its charger's response time, cuts cover for evSettle, as before: 5 min 16 s. - A change of phases read as a cut in amps per phase: 15 A on one phase to 6 A on three covered 9 A on every phase. Orders now carry their phase count; across a change the latest order counts in full as a raise. A single-phase cut covers no phase when other chargers share the pool. Without phase currents Core cannot see the hottest phase, so there a cut covers for evSettle, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FTW_FIELD_KNOWN=write removes 99 over-cut netting, 1 limit netting and 4 over-cut D rows and adds none. 71 of 338 cases still fail, all listed on master: energy 40 (39 D, 1 A), idle 25 (export, readback, hold3p), own-raise 6 (relief). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg
force-pushed
the
fix/ev-fuse-cut-netting
branch
from
October 22, 2026 09:44
a096b0b to
6bdad95
Compare
frahlg
added a commit
that referenced
this pull request
Oct 22, 2026
On master with #1802, the 39 one-phase car energy rows (D) and the one energy row labelled A pass with this branch; their lines and labels go. No row is new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg
added a commit
that referenced
this pull request
Oct 22, 2026
…nned and ordered as one (#1801) * fix(loadpoint): a one-phase car on a three-phase charger is seen, planned and ordered as one A one-phase car draws a three-phase order's current per phase on one phase. On the field table the planner got [0, 4800], the car took 1,600 W of a 4,800 W order, and the feedback said not following. The loadpoint now tests each rise of its three-phase order on the site meter: one phase rising by about the order's rise, the other two still, for 30 s within the charger's response time, while the charger's own reading shows that draw. That marks the session's car one-phase (State.CarPhases, the field a car profile can set later); a rise on all three phases clears it, and unplugging resets it. A house load does not follow the orders, and a two-phase car moves two phases, so neither is marked. Marked, the planner gets the car's one-phase levels, 6 A up to the charger's current per phase. The charger stays on three phases and the order is the plan's power times three, so its current per phase is what the car should take. The command carries expected_w, a third of the order, and command evidence compares the car with that. The verdict is Limited, "Charging on one phase, at most 3.7 kW". The fuse limit is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(loadpoint): one-phase car tests on fast and undeclared chargers; field rows The field matrix showed two gaps. A charger that applies in 2 s has already taken a raise by the next reading, and a fuse ramp climbs 1 A at a time, so a test's baseline must be the meter read before the first raise since the order it measures from. A charger that declares no response time is now given 3 minutes, as the fuse limit counts it, not one. Field matrix on #1799: 55 D rows, 28 #1795 rows and one A row turn green. Every one-phase-car row now meets its energy, verdict and one-phase invariants. 25 of them still cut twice when a house step on the car's phase meets the second cut of the netting class; they are labelled netting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(loadpoint): one-phase car check compares changes, guards a clear, and survives a restart Review findings on #1801: M1: the check compared the charger's whole draw with the meter's rise, so only a rise from about 0 A could mark a car. It now compares the change in the charger's draw since the baseline. M2: a three-phase verdict had no draw check, so a heat pump starting at 2 A a phase during a 2 A rise cleared a correct mark. It now needs the charger's draw to have risen by about the three phases' sum. M3: the first reading of a session had a zero baseline, and the mark lives in memory, so after a Core restart with the car charging it was never marked again. A reading with no baseline is no longer a test, and an unmarked car whose charger's whole draw shows on one phase while the other two carry less than a third of it, for 30 s, is marked. No three- or two-phase car can show that. Each review probe is a test, and each fails on the previous head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(api): judge a one-phase car after limits, setpoint and waiting "Charging on one phase" ran before the limit guard, so a fuse limit, import limit, fuse cooldown, stale site meter, changed setpoint or a reading older than the order all read as limited/one_phase. The branch now runs after the waiting check; those verdicts come first, and a draw beyond tolerance is judged as for any car. The field matrix judged the car's draw against the full order and let only one_phase excuse a third of it. It now judges the draw against the command's expected_w, as Core does, so a marked car under a fuse limit reads fuse_limit without failing the verdict check. The known list does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(control): rewrite the field matrix's known list on #1802 On master with #1802, the 39 one-phase car energy rows (D) and the one energy row labelled A pass with this branch; their lines and labels go. No row is new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Part of v0.155.0-beta.2. Fredrik approved this fix for beta.2 and accepted its one trade-off, N1 below. Rebased on master 5ae590a, with #1799 and #1795.
Problem
stepEVMeterLimittook each fuse cut frompeakW, the highest offer of the lastevSettle(20 s). Easee Cloud applies a cut after 30–120 s. Once the 20 s had passed, the meter still showed the old draw and Core cut again. In the field matrix (#1800, labelnetting) the car went 10,400 → 6,900 W, then to 0 W 25 s later after a 6 A kettle step; 43 of 95 double cuts on master ended at 0 W. The same window missed a raise still on its way, so a cut could leave the phase over the limit once the raise landed (homebox/16A/empty/none/cloud60s/car3p/balanced/pause).Rule
Fuse protection counts the site meter's current on each phase plus our own orders that cannot have shown yet, both ways. It reuses the per-charger, per-phase log from #1776 and #1793, with T = max(20 s, declared
power_max_age_s), 180 s when the driver declares nothing.evSettle; a short dip, as battery relief gives, does not end it.evSettle, as before.Details:
evSettle, as before.The ambiguity, and which way this resolves it
The meter cannot tell whether a cut has landed. A reading over the limit after a cut fits two stories:
This PR takes story 1 while the run over the limit lasts, for at most T from the cut. Master took story 2 after 20 s, and that is the double cut on every slow charger. The meter ends story 1 early: once every phase has read under the limit for 20 s, the cut has landed or the overage has gone, and a cut sent before covers nothing more.
Why story 1 is safe when story 2 is true:
sitesim. A miniature circuit breaker does not trip at 1.13 × In within an hour and trips within an hour at 1.45 × In.The cost, shown by three probes against master:
A driver that declares
power_max_age_sshortens the first two to its own response time.Known limit (N1), accepted for beta.2
Every charger that declares no
power_max_age_sgets T = 180 s: OCPP in Core, the three CTEK drivers and tesla_wall_connector. Only easee_cloud declares. The reviewer's probe: a 5 s charger takes an instant cut that lands; 10 s later a second load takes L1 to 33 A on a 25 A fuse. Core cuts 2 of the 9 A over and leaves L1 at 31 A (1.24 × In) for 2 min 55 s. Master took 15 s, and the same charger declaring 30 s takes 25 s. L1 stays inside the heat bound above. Dropping the credit above a 1.2 × In reading is not the fix: it brings back 31over-cutrows. The fix is a measured response time per charger, from the device chain, and until then OCPP in Core declaring one (follow-up, not in this PR).Not changed
Rises, starts,
EVFusePhaseRoomA, the split between cars,fuse_heat.go,go/internal/loadpoint, and the hour's import ceiling cut. That cut still takes the overage from the highest offer of the last 20 s, aspendingEVCutWand its battery relief expect. No new setting.Review
An independent review of the first commit found four things; the second commit handles the first three:
evSettlethere.evSettleonce its first cut has outlasted T: 5 min 16 s. Scoping cover to the run also brought the fast-charger probe from 2 min 35 s to 22 s when the second load comes after the run ended.Open drafts on the same file
#1782, #1789 and #1792 (drafts) also change
ev_fuse_budget.go. #1782 touches comments. #1792's two-phase orders will needofferPhaseAto return 2 as the phase count. #1789 has its own cut netting, per run over a limit and at mostevSettle; this rule replaces that part, so #1789 needs a rebase onto it.Field matrix (
TestSiteSimFieldMatrix, 338 cases)over-cutlimitcut,one-phase,verdictfuse,stale,home-useenergyidleown-raiseFTW_FIELD_KNOWN=writeremoved 104 known lines and added none: 99over-cut netting, 1limit netting, 4over-cut D. Thenettinglabel is gone. The 71 cases left were all listed on master:energy39 D and 1 A,idle13 export, 10 readback and 2 hold3p,own-raise6 relief.Before the rebase, on master 7202ace, this change alone took the matrix from 249 to 173 failing cases (
over-cut120 → 6). The six left there were the split's float floor (creditA × 690= 7,599.999… W → 6,900 W step), which #1799 removed.Probe cases (
ev_fuse_cut_netting_test.go)Tests
go test ./internal/control/ ./internal/loadpoint/ ./internal/sitesim/: pass; the fuse tests andTestSiteSimScenariosalso pass under-race.make verify: vet, tests and build clean before the rebase (a096b0b).go vet ./...andgo test ./internal/... ./cmd/ftw/...pass (60 packages).Follow-ups, not in this PR:
phaseRoomCapWmultipliescreditAback in floats (7,600 W → 7,599.999… W), andfloorChargeWthen drops a step. fix(loadpoint): planned charging runs on three phases, and room never cuts a running order #1799 hides it on the same phases; a small tolerance would remove it on a change of phases.unshowncounts 15 A on one phase to 6 A on three as no rise, though two phases gain 6 A. The split's owncreditAalready gives a phase change no credit.🤖 Generated with Claude Code