Skip to content

fix(loadpoint): plan a one-phase car above the charger's minimum, not at it - #1806

Merged
frahlg merged 2 commits into
masterfrom
fix/ev-one-phase-car-plan-floor
Oct 22, 2026
Merged

frahlg merged 2 commits into
masterfrom
fix/ev-one-phase-car-plan-floor

Conversation

@frahlg

@frahlg frahlg commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Release blocker from #1801. Found by the device-simulator rig on 2e84710, scenario s14-one-phase-car-25A.

Problem

In s14, a one-phase car charges from a three-phase charger set up as the settings page does it: minimum 4,140 W, maximum 11,040 W. About a minute in, #1801 marks the car as one-phase. On the next replan the plan has no charging for the car: commanded_w 0, plan_next_wh null, and the goal is missed. The rig saw this both on a reactive-load replan and on a loadpoint_soc_changed replan. Before #1801 the car drew 3.1 kW for the whole run.

The cause is in the planner's input. Once the car is marked, main.go gives the planner the car's one-phase levels, 1,380–3,680 W, together with LimitPower's minimum. That minimum is the charger's own, 4,140 W. mpc's normalizedSteps drops every level below the minimum, so only 0 was left.

Change

loadpoint.PlanningLimits now holds the planner's range and steps for a session. main.go (planningLimitsForLoadpoint) and the field matrix both call it.

A one-phase car takes a third of each three-phase order. Its levels are the charger's three-phase planning steps divided by three, up to its one-phase maximum, so each level is a third of an order that the charger's table and minimum allow. The minimum is the lowest level and the maximum the highest.

Charger Levels
No table, minimum 4,140 W 1,380 W in 230 W steps
No table, minimum 4,800 W from 1,610 W
Table [0, 4,140, 6,900, 11,040] 1,380, 2,300 and 3,680 W
Help-report Easee table 1,600–3,467 W, the thirds of its three-phase steps

This applies only when the charger can run three phases (threePhaseCapable, which also requires a three-phase site).

Core's side was already right. It multiplies the plan by three before snapping to the charger's steps, so every level goes out as the step it came from.

Why the field matrix missed it

The matrix replans every 15 minutes, but its fieldPlanningSteps called PlanningSteps directly:

  • It never took the CarPhases branch in main.go, so a marked car was planned at three-phase levels, which Core then multiplied by three.
  • It also ignored the minimum below which the planner drops levels.

It now calls PlanningLimits and drops levels outside its minimum and maximum, as mpc does. Without the fix this fails 24 existing sweep rows on energy: one-phase car, planned, a charger with a 4,140 W minimum. For example, sweep/25A/auto/none/easee40s/car1p/balanced/planned delivered 0.9 of 8.8 kWh.

Two new rows, help1022/20A/auto/own/{fast2s,easee40s}/car1p/2A/planned+need1000W, ask a small goal on the help report's table, so the plan picks the lowest level. Both were idle with room on the previous head, 5fda29b.

Evidence

One-phase car, charger 4,140–11,040 W, 25 A master (8ac2750) this PR
Planner range after the mark 4,140–11,040 W, levels 1,380–3,680 W 1,380–3,680 W
Levels the planner keeps 0 11
Field matrix 24 rows fail on energy once it plans as main.go does pass
Device-simulator rig, s14-25 car 128 W over the last 10 min car 3,657 W over the last 10 min, 0 not-following samples
  • TestOnePhaseCarLevelsAreOrders (loadpoint) ticks the controller at every level the planner keeps, with the plan's peak, for five chargers and two fuses. Every level must go out as an order within the charger and the fuse, and the car must draw within 1 % of the plan.
    • On 5fda29b the field table sent 0 W at 1,380 W.
    • On 5fda29b the coarse tables drew up to 30 % less than the plan.
  • TestOnePhaseCarPlanningLimits (loadpoint) checks the range and the number of kept levels for five chargers. It used to lock in the third-of-minimum rule for the field table; it now expects the table's thirds.
  • TestOnePhaseCarPlanChargesAtALevelCoreCanOrder (cmd/ftw) runs mpc.Optimize on the input main.go gives the planner. At least one slot must charge at one of the car's levels, and three times that level must be within the charger's range. With master's limits it fails: "no slot charges the car at a level Core can order (limits 4140-11000 W, ...)".
  • s14 in the rig: this charger's levels are identical before and after the review fix (1,380–3,680 W), so the 5fda29b rerun still holds.
  • The known list is unchanged. The matrix has 340 cases and 29 failures, all listed: export 13, readback 10, relief 6. go vet is clean, and go test ./internal/... ./cmd/ftw/... passes in 60 packages.

Behaviour that changes beyond the fix

  • Surplus-only with a marked car. On master the plan had no entry for such a car, so nothing capped its surplus order. Now the plan has one, and it caps the order at three times the plan, as it does for an unmarked car. With 9 kW of surplus, master ordered 8,970 W; this PR orders 4,140–8,970 W, depending on the plan's level.

Limits

  • The deadline fallback (mpc/deadline_directive.go) still sizes the order from the charger's own steps. For a marked car the controller multiplies that by three, within the charger's maximum, so with no plan the car draws its full current instead of the level the goal needs.
  • One case fails outright, and it predates this PR. With no plan, a car whose known CarMaxW is below the charger's minimum, on a charger without a table, gets 0 W. Example: a plug-in hybrid with max 3,680 W against a 4,140 W minimum. deadline_directive.go:42 calls SnapChargeW with a maximum below the minimum, and DeadlineNeed has no "keep one step" fallback without a table. This is left for a follow-up.
  • Draft fix(charging): carry two-phase cars through plans, current limits and Easee #1792 changes ev_planning_limits.go, so it needs a rebase.
  • This does not touch the fuse budget, which is where the one-phase fuse-cap finding is being investigated.

Review findings

Finding Fix
1 A third of the charger's minimum gave a 1,380 W level on the field table that Core cannot order (0 W) levels are thirds of the charger's three-phase steps
2 Marked-car levels ignored the step table, so plan and draw differed by up to −30 % on coarse tables same
3 Surplus-only is not unchanged for a marked car stated above
4 The fallback statement in Limits was wrong for a car below the charger's minimum without a table stated above, follow-up
5 PlanningLimits should need a three-phase site; the planner test did not assert what it meant threePhaseCapable; test renamed and asserts an orderable slot

🤖 Generated with Claude Code

… at it

Once a car was marked one-phase, the planner got its one-phase levels
(1,380-3,680 W) with the charger's own minimum, 4,140 W on a charger
set up for three phases. The planner drops every level below its
minimum, so it had only 0 and the car stopped charging: device-simulator
rig s14 on 2e84710, 0 W for the last 15 min with the goal not reached.

The order for a one-phase car is three times the plan, so the plan's
minimum is a third of the charger's: 6 A on one phase. Its maximum is
the top one-phase level. loadpoint.PlanningLimits holds this, and
main.go and the field matrix both use it.

The field matrix planned with PlanningSteps alone: it never took the
one-phase branch in main.go and ignored the minimum the planner drops
levels below. It now uses PlanningLimits and drops levels outside the
minimum and maximum as mpc does; without the fix 24 sweep rows (one-phase
car, planned, a charger with a 4,140 W minimum) fail on energy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T11:38:09.377270Z 5fda29b PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…three-phase steps

Review of #1806: a third of the charger's minimum gave the planner a
1,380 W level on the field table, whose minimum (1,600 W) is a one-phase
one. Three times it, 4,140 W, is not on the table, so a plan slot at that
level with the plan's peak went out as 0 W; on coarse tables the car drew
up to 30 % less than the plan counted.

A marked one-phase car's levels are now the charger's three-phase
planning steps divided by three, up to its one-phase maximum, so each is
a third of an order the table and minimum allow. Without a table nothing
changes: 4,140 W gives 1,380 W, 4,800 W gives 1,610 W. PlanningLimits
also needs a charger that can run three phases (threePhaseCapable, which
includes a three-phase site).

TestOnePhaseCarLevelsAreOrders ticks the controller at every level the
planner keeps, for five chargers and two fuses. Two field-matrix rows ask
a small goal of the one-phase car on the help report's table; both were
idle with room on the previous head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit b0cb7d4 into master Oct 22, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant