Repository navigation
fix(mpc): deadline reserve waits for the latest start (#1718) - #1896
Conversation
Without an accepted plan, a saved goal reserved full charger power whatever the slack, so a restart or one failed replan started the car and the next plan paused it. The reserve now acts only once the rest of the goal at full power plus one hourly slot reaches the deadline; a running reserve keeps a second slot so a SoC step does not cycle it. DeadlineNeed checks the passed minW, so a loadpoint with min_charge_w 0 and steps gets the reserve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 551b44da50
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| latestStart := st.TargetTime.Add(-time.Duration(need / peak * float64(time.Hour))).Add(-margin) | ||
| if now.Before(latestStart) { | ||
| continue |
There was a problem hiding this comment.
Account for shared fuse capacity before delaying charging
When multiple cars share a fuse budget and no executable plan is available, each car's latest start assumes it can independently receive its full peak. For example, two cars each needing 33.3 kWh, rated at 11 kW and due in 10 hours both wait until roughly four hours before departure. However, freezeFuseBudget in go/internal/loadpoint/controller_fuse_budget.go splits the site's total EV allowance between them: with an 11 kW shared allowance, only about 44 kWh can then be delivered against their combined 66.6 kWh need. Both goals were feasible before this delay, and the one-hour margin cannot cover the lost time. Compute the start threshold using joint remaining demand and available site capacity, or retain a conservative fallback when independent peaks cannot be supplied simultaneously.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 49ce18d. A goal's latest start now sums need/peak over every saved goal due at or before its deadline. The fuse budget serves the earliest deadline first, so the cars due by that deadline can finish one after another. TestDeadlineReserveSharedAllowance covers two cars on a shared 11 kW allowance; with the per-car rule three of its four cases missed the goal.
…1718) Each car's latest start assumed it alone got its full peak. Two goal cars on one EV fuse allowance with no plan then both waited until about need/peak + 1 h, and the fuse budget, which serves the earliest deadline first, could not finish both. A goal's latest start now counts the hours at full power of every saved goal due at or before its deadline. With one car the rule is unchanged; with cars on separate fuse room they only start earlier, as before #1718. Adds TestDeadlineReserveSharedAllowance (two cars, shared 11 kW, earliest deadline first and even split) and TestDeadlineReserveZeroMinWithSteps, which covers the DeadlineNeed minW change at directive and controller level. Both fail without their fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item D6 of the stable plan. Fixes #1718.
Problem
When no plan is in force, a saved car goal falls back to the deadline reserve (
SlotDirectiveAt, service.go:667-675). This happens with no plan, a failed replan, a restored archive, or a plan the contract rejects.DeadlineNeednever looks at the deadline, so the reserve orders full power whatever the slack.Example: a car at 30 % of 60 kWh with a goal of 80 % in 10 h needs about 33.3 kWh, or about 3 h at 11 kW. That leaves about 7 h of slack. On master the loadpoint still orders 11,000 W ("EV deadline reserve") in all three fault cases. The next plan then pauses the car, as in the #1718 log (10,350 W at startup, then 0 W after the replan).
A second, smaller fault:
DeadlineNeedrefused a loadpoint withmin_charge_w: 0, which config allows, even when its steps give a legal minimum.Change
withDeadlineReservereserves a goal only from its latest start:now >= TargetTime - hours - 1 h, wherehourssumsneed/peakover every saved goal due at or before this goal's deadline. One hour is one slot at the planner's longest (hourly) price slot.freezeFuseBudgetgives that allowance earliest deadline first, so from a goal's latest start the cars due by its deadline can finish one after another. With one car this isneed/peak. With cars on separate fuse room they only start earlier, as on master. Before that the loadpoint gets no reserve, and the controller orders 0 W (no_plan_budget), as it does for any car without a plan budget.need/peakby about 4 min. Without the wider margin, the car would stop and start again a few minutes later.DeadlineNeedchecks theminWit is given, notst.MinChargeW.Tests
TestDeadlineReserveWaitsForLatestStart, copied from the audit's repro (TestReproDeadlineReserveIgnoresSlack). It fails on master with 11000 W for no plan, failed replacement and restored archive, and passes here.TestDeadlineReserveLatestStartcovers the start boundary and the running margin.TestDeadlineReserveSharedAllowance: two 60 kWh cars, each 30 % to 80 %, share 11 kW with no plan, minute by minute, SoC read in whole percent. Both reach 80 % with the same deadline (earliest deadline first, and even split), with b due one hour later, and with b due much later. With a per-car latest start, three of the four cases miss (SoC 0.46 to 0.63).TestDeadlineReserveZeroMinWithSteps: amin_charge_w: 0loadpoint with steps gets the reserve, and the controller orders power. It fails with master'sdeadline_need.go.cars/reserve_test.go). What they check is unchanged.go test -race -count=1for./internal/mpc/ ./internal/loadpoint/ ./internal/cars/ ./internal/control/ ./internal/api/ ./cmd/ftw/: all ok.go vet ./...andgo build ./...: ok../internal/drivers/fails in this worktree only becausedrivers/*.luais not checked out (TestBundledDriversMeetStartupContractcannot read the files). This change does not touch drivers.Risk
Low. Without a plan, a goal now starts later than before. The start uses each charger's peak and assumes cars that share an allowance get it in deadline order, as
freezeFuseBudgetgives it. If the fuse room, house load or a one-phase car delivers less than the peak, only the one-hour margin covers the gap: a single car held to 75 % of its peak just makes 80 %, and at 50 % it ends near 63 %. That needs no plan for the whole window, for example a long price outage with no kept plan (W2-13). The reserve still needs a known fuse cap, and that check is unchanged. #1865 touches the manual branch ofwithDeadlineReserve. Its hunks do not overlap this change.🤖 Generated with Claude Code