Skip to content

Plan and dispatch physical battery and EV fleets with the compiled worker - #1118

Merged
frahlg merged 6 commits into
masterfrom
fix/storage-topology
Sep 7, 2026
Merged

Plan and dispatch physical battery and EV fleets with the compiled worker#1118
frahlg merged 6 commits into
masterfrom
fix/storage-topology

Conversation

@frahlg

@frahlg frahlg commented Sep 7, 2026

Copy link
Copy Markdown
Member

Sites without a home battery, or with several batteries or EVs, could receive an unsupported-topology error from the compiled planner. This change lets Core use the bundled Energyplan 0.3.0 worker for those sites and preserves each device's identity, physical limits and energy target from the request through validation and dispatch.

Core checks the raw response before translating it, including complete device maps, slot times, EV deadlines and PV command bounds. Multi-battery dispatch tracks each battery's measured delivery while retaining the existing site limit, state-of-charge, slew and safety checks. If Core DP cannot represent the site after a worker failure, Core keeps the old plan for diagnosis and withholds its execution directive instead of dropping devices.

PV control credit requires verified generation-control semantics. This first adapter supports one site PV control domain with the reviewed Ferroamp Lua bytes and an effective positive release setting. Export-only limits, other unverified drivers and multiple independent PV control domains get no such credit; their measured/forecast PV remains in the model. The proof comes from the loaded bytes and effective init and changes with driver generation. Restored diagnostics with physical device maps or PV dependence remain archives until a new plan has current physical inputs and runtime permission. This prevents restore from turning per-battery budgets into aggregate dispatch. Loss of proof or usable telemetry stops battery, EV and legacy plan execution and marks the archived plan outdated. Manual and protective PV limits retain precedence.

Planning stays outside the control tick. Small requests use at most 500 ms. Larger requests use at most 5 s when slots * (3 * physical batteries + 2 * active EVs) >= 193 * 6, or when PV control/risk requires it. A real aggregate battery counts as one; a batteryless site counts as zero. The outer worker timeout is 7 s, and replacement/cancellation still stops the process.

Validation:

  • Full make verify passed with the final worker pin on the master fix from test(ftwdb): match the live sidecar I/O budget #1119, including native processes, Go suites, compose/container/release checks, vet and build.
  • make native-solver-test passed with the pinned package, including the real process budget and Core plan replay.
  • Independent Go race checks passed for the actual Ferroamp Lua-to-MQTT cap/release, generation/config/health revocation across EV and legacy paths, complete PV domain, and manual/protective precedence.
  • Independent native/race restore and shadow tests passed: a current plan stays executable when comparison data arrives, a restored physical archive stays inactive after a late shadow, and a new validated replan restores execution.
  • The exact bundled ARM64 worker passed 27/27 physical Pi runs: nine synthetic 193-slot cases repeated three times, up to four batteries and three EVs. A separate replay verified all 5,211 actions, device limits, EV deadlines, scenario grid limits and costs/CVaR, with negative controls. Maximum wall time was 5,076.102 ms; maximum cumulative child RSS was 20,352 KiB. Runs paused for cooling, excluded from worker time. Three repeats do not establish tail percentiles or worst-case bounds.
  • The 4-battery/3-EV and risk-plus-PV cases returned feasible plans with unknown lower bounds. No optimality claim is made for those cases. Core and three drivers remained healthy, and FTWDB error/drop counters did not increase. No physical commands, box installation or beta dispatch occurred here.

Private paired worker PR: https://github.com/srcfl/energyplan/pull/6. Only compiled workers, licenses/notices and public bundle metadata are included here. The bundle pins source 3b9c59baecb71a91a8bc01208b4b8d5b9a5bfa60; Linux ARM64 SHA256 fde573c1330e7b9adcaddc42893296fdd375e0efcc8298ea947a155780780545 (1,155,360 bytes).

PR #971 retains its stack order and ownership of its Python, true-zero-PV and broader shared-physics changes. PR #1036 keeps its evaluator, benchmark and report semantics, including its existing review findings. This PR only takes the physical-device boundary, raw-response validation and verifiable PV control; it does not supersede either PR.

PR #888 retains right of way on its export-guard work (related #885). The small dispatch hooks here preserve the existing guard, residual allocation, minimum command and release paths, as coordinated; that draft's implementation is not pulled into this change. Includes a minor changeset for the new supported site configurations.


Note

High Risk
Changes real-time battery/EV dispatch, MPC plan execution gates, and verified PV curtailment—paths that directly affect grid and device control if validation or proof logic is wrong.

Overview
Extends Energyplan 0.3.0 so MPC can plan sites with no home battery, multiple batteries, and multiple EVs, each with its own limits and energy targets. buildMPC no longer requires aggregate battery capacity; worker requests/responses are validated for complete device maps, slot timing, EV deadlines, and PV cap bounds before plans are accepted.

Dispatch carries per-battery slot energy (StorageEnergyWh) through slot directives and uses distributePlannedStorages so multi-battery sites honor individual budgets (blocked batteries do not have their share reassigned) while still respecting site clamps and existing safety paths.

PV generation curtailment from plans is gated on a runtime proof tied to loaded, reviewed Ferroamp Lua bytes, effective init, driver generation, and fresh telemetry—not config flags alone. Revoked or stale proof stops slot directives, EV/battery plan execution, and planned PV caps; manual/protective ceilings still win. Restored diagnostics with physical maps or PV control stay archives until a new solve validates current inputs.

When the primary worker fails and Core DP cannot model the topology, Core keeps the previous plan for diagnosis but withholds execution instead of falling back to a reduced aggregate model. Solve budgets scale (500 ms vs 5 s) for large fleets and PV/risk cases; transport timeout rises to 7 s.

Reviewed by Cursor Bugbot for commit c78eb7a. Bugbot is set up for automated code reviews on this repo. Configure here.

@frahlg
frahlg force-pushed the fix/storage-topology branch from 0750442 to 260848e Compare September 7, 2026 20:57
@frahlg
frahlg marked this pull request as ready for review September 7, 2026 21:12
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 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-09-07T21:18:13.654248Z c78eb7a Draft marked ready
ℹ️ 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.

@cursor

cursor Bot commented Sep 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e7d2748d-bbe9-402b-8e9a-2cc48b6ad63c)

@frahlg
frahlg merged commit bf1b60c into master Sep 7, 2026
16 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c78eb7abf9

ℹ️ 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".

}
if totalCapacityWh <= 0 {
if totalCapacityWh < 0 {
return ReasonNoBatteryCapacity

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep batteryless sites unavailable when Core DP is selected

When a batteryless site explicitly selects planner.engine: core—or uses the stable/development default—this condition now reports the planner as available and buildMPC constructs it, but OptimizeContext immediately rejects the same parameters through coreDPModelError because CapacityWh == 0. Every replan therefore fails validation and any EV on the site receives no schedule; admission should depend on selecting a planner that supports the zero-storage topology, or Core should remain unavailable for it.

AGENTS.md reference: AGENTS.md:L19-L25

Useful? React with 👍 / 👎.

Comment on lines +34 to +35
if len(slots)*assets >= 193*6 || p.PVCurtailment.MinW > 0 || p.PVUncertaintyW > 0 || p.PVRelativeUncertainty > 0 {
return 5 * time.Second

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve risk inputs until the time budget is chosen

For Energyplan requests whose only reason for the larger budget is configured PV uncertainty, this risk branch is unreachable: EnergyplanOptimizer.Optimize clears PVUncertaintyW and PVRelativeUncertainty before ExternalOptimizer.buildRequest invokes energyplanTimeBudget. Such requests consequently receive 500 ms rather than the documented 5 s unless they independently cross the fleet threshold or have PV-control proof, increasing budget errors or prematurely accepted feasible plans on the risk-adjusted horizon.

Useful? React with 👍 / 👎.

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