planner_cheap and planner_self are superseded but still shipping. Both mode
namespaces still carry them, and every surface that touches modes has to know
about all four planner variants when only two are ever offered.
What is already true
mpc.ModeCheapCharge says so itself, in go/internal/mpc/mpc.go:
Superseded by ModePassiveArbitrage as of v0.82 — the merged mode covers the
same use case (charge from cheapest available source, never export battery)
and removes the operator-facing choice between "smart self-consumption" and
"cheap charge" that the planner is already capable of making on its own.
Kept here for existing-config compatibility.
And ModeCatalog() has already moved planner_self and planner_cheap to
TierHidden, so neither is reachable from the dashboard. What is left is the
code, the config surface and the golden corpus.
Why it is worth finishing
Two namespaces map 1:1 for the planner modes — control.ModePlannerCheap →
mpc.ModeCheapCharge, and so on through PlannerMPCMode(). That indirection
earns its keep for the six non-planner modes, which have no MPC counterpart at
all, but for the planner ones it is four names for two behaviours.
It also leaks outward. Anything that renders or validates modes carries the
full list, and the FTW webapp now derives its choices from ModeCatalog()
over the wire — so a mode that exists only for config compatibility still costs
a row in every client that speaks to a box.
Roughly where it lives
rg 'planner_cheap|ModePlannerCheap|cheap_charge|ModeCheapCharge|planner_self|ModePlannerSelf' finds it in:
go/internal/control/dispatch.go — the enum, AllModes(), PlannerMPCMode(), IsPlannerMode()
go/internal/mpc/mpc.go, service.go, shadow_evaluator.go
go/internal/config/config.go — the config surface that is the stated reason for keeping them
go/internal/control/modes_catalog.go
optimizer/ftw_optimizer/ — model.py, multistage.py, recourse.py, direct_highs.py, backtest.py
web/plan.js, web/settings/tabs/planner.js, web/settings/tabs/control.js
- the golden corpus in
go/internal/control/testdata/golden/ — several hundred references
What removing them needs
- A migration for existing configs. This is the whole reason they are still
here. A site on planner_cheap should land on planner_passive_arbitrage;
planner_self needs a decision, since passive arbitrage may grid-charge and
planner_self explicitly may not. That difference is real for anyone who
chose it deliberately, and it should be checked against how many sites
actually run it before being collapsed.
- Regenerating the golden corpus, which is where most of the diff will be.
- Deciding whether
mpc.Mode stays a separate type at all once the planner
modes are 1:1. Probably yes — the six non-planner modes justify it — but
worth stating rather than inheriting.
What this is not
Not urgent, and not a bug. Nothing is broken; there is dead surface area that
grows a little every time something new has to enumerate modes.
Raised while building the FTW webapp, which now reads ModeCatalog() from the
box rather than hard-coding a list — the same pattern web/app.js already
follows.
planner_cheapandplanner_selfare superseded but still shipping. Both modenamespaces still carry them, and every surface that touches modes has to know
about all four planner variants when only two are ever offered.
What is already true
mpc.ModeCheapChargesays so itself, ingo/internal/mpc/mpc.go:And
ModeCatalog()has already movedplanner_selfandplanner_cheaptoTierHidden, so neither is reachable from the dashboard. What is left is thecode, the config surface and the golden corpus.
Why it is worth finishing
Two namespaces map 1:1 for the planner modes —
control.ModePlannerCheap→mpc.ModeCheapCharge, and so on throughPlannerMPCMode(). That indirectionearns its keep for the six non-planner modes, which have no MPC counterpart at
all, but for the planner ones it is four names for two behaviours.
It also leaks outward. Anything that renders or validates modes carries the
full list, and the FTW webapp now derives its choices from
ModeCatalog()over the wire — so a mode that exists only for config compatibility still costs
a row in every client that speaks to a box.
Roughly where it lives
rg 'planner_cheap|ModePlannerCheap|cheap_charge|ModeCheapCharge|planner_self|ModePlannerSelf'finds it in:go/internal/control/dispatch.go— the enum,AllModes(),PlannerMPCMode(),IsPlannerMode()go/internal/mpc/mpc.go,service.go,shadow_evaluator.gogo/internal/config/config.go— the config surface that is the stated reason for keeping themgo/internal/control/modes_catalog.gooptimizer/ftw_optimizer/—model.py,multistage.py,recourse.py,direct_highs.py,backtest.pyweb/plan.js,web/settings/tabs/planner.js,web/settings/tabs/control.jsgo/internal/control/testdata/golden/— several hundred referencesWhat removing them needs
here. A site on
planner_cheapshould land onplanner_passive_arbitrage;planner_selfneeds a decision, since passive arbitrage may grid-charge andplanner_selfexplicitly may not. That difference is real for anyone whochose it deliberately, and it should be checked against how many sites
actually run it before being collapsed.
mpc.Modestays a separate type at all once the plannermodes are 1:1. Probably yes — the six non-planner modes justify it — but
worth stating rather than inheriting.
What this is not
Not urgent, and not a bug. Nothing is broken; there is dead surface area that
grows a little every time something new has to enumerate modes.
Raised while building the FTW webapp, which now reads
ModeCatalog()from thebox rather than hard-coding a list — the same pattern
web/app.jsalreadyfollows.