Skip to content

fix(mpc): replay every plan and represent a true zero PV cap - #971

Open
frahlg wants to merge 3 commits into
cursor/ev-pv-beside-battery-grid-charge-8f15from
885-planning-physics-kernel
Open

fix(mpc): replay every plan and represent a true zero PV cap#971
frahlg wants to merge 3 commits into
cursor/ev-pv-beside-battery-grid-charge-8f15from
885-planning-physics-kernel

Conversation

@frahlg

@frahlg frahlg commented Aug 22, 2026

Copy link
Copy Markdown
Member

Accepted text proposal

Issue or Discussion: #885 (replay / sentinel / engine alignment). Stacked on #957.

Maintainer comment that accepted this scope: plan-mode refactor of planning-model physics: one energy step, ValidatePlan on every published plan, distinct zero PV cap, PV-bonus mode gate.

What changed

  • Go DP and ValidatePlan share loadpoint.BatteryEnergyDeltaWh. Forward simulation starts from real SoC instead of snapping onto the DP grid, so reported energy replays.
  • runReplan runs ValidatePlan on Go fallback, not only on the Python optimizer. A trajectory that cannot be reconstructed is dropped; the previous plan stays.
  • Aggregate Go DP plans without per-storage maps replay as one battery.
  • Actions carry pv_curtail_active. Active with pv_limit_w = 0 is a true zero cap. Inactive with 0 remains release. Python CVXPY, HiGHS, recourse, progressive and multistage all serialize through one helper.
  • Python applies pv_charge_bonus_ore_kwh only in passive_arbitrage, matching Go DP.
  • Surplus-only leftover PV and battery-to-EV residual are constrained per scenario, not only the base forecast.
  • Shared physics table: go/internal/loadpoint/testdata/site_physics.json.

Why

Go fallback could publish a plan that would fail independent replay. Full PV curtailment was serialized as pv_limit_w = 0, which Core treated as no cap and then rejected a physically valid solve. The same bonus setting changed the Python objective in modes where Go ignored it.

Boundaries and safety

Open PRs on overlapping files:

Verification

go test ./internal/mpc ./internal/loadpoint ./internal/control -count=1
uv run --directory optimizer --extra test pytest -q
make verify

Local pre-commit ran make verify. Local pre-push ran make verify-all (including linux/arm64, linux/amd64, windows/amd64). Optimizer suite: 172 passed.

No UI changed.

Checklist

  • The diff implements one accepted scope and does not add follow-on work.
  • I checked open pull requests that touch the same files.
  • Tests cover the changed behaviour and its failure path.
  • A human reviewed every changed web/UI view in a browser, or no UI changed.
  • A Changeset is included, or the change is exempt.
  • Every commit has a DCO sign-off.

Note

Medium Risk
Changes the plan-publish gate and shared energy/grid identities used by both Go DP and the Python optimizer. Dispatch hardware is untouched, but invalid or unreplayable trajectories are now dropped instead of going live.

Overview
Rejects any plan that cannot be reconstructed from the request before it becomes live, including Go DP fallback. Shared helpers (BatteryEnergyDeltaWh, EffectivePVW) now drive both forward simulation and ValidatePlan.

Go DP integrates from the actual initial SoC (policy lookup stays on the grid) and clips actions that would worsen operating-band recovery. Aggregate DP trajectories without per-storage maps replay as one battery.

pv_curtail_active separates a true zero PV cap from “no cap”. Python solvers serialize through _pv_curtail_output. PV charge bonus applies only in passive_arbitrage, matching Go. Battery-to-EV residual is constrained per scenario. Fuse-limit checks allow sub-watt solver residue only.

A shared site_physics.json table locks Go and Python identities together.

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

@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: f4cb4119a7

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

Comment thread go/internal/mpc/mpc.go Outdated
Comment on lines +975 to +978
soc := p.InitialSoCPct
if soc < p.SoCMinPct {
soc = p.SoCMinPct
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replay from the actual out-of-band SoC

When live telemetry is below SoCMinPct (or above SoCMaxPct), this replaces the real starting SoC with the configured boundary before generating every reported SoCPct. ValidatePlan instead replays from the unchanged p.InitialSoCPct, so the first action differs by the entire boundary violation and runReplan rejects every Go fallback plan, retaining an old or nil plan precisely while the battery needs recovery. Keep the actual energy for forward replay and restrict only the policy lookup index, or explicitly model a recovery action.

AGENTS.md reference: AGENTS.md:L19-L21

Useful? React with 👍 / 👎.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f4cb411. Configure here.

Comment thread go/internal/mpc/mpc.go Outdated

@cursor cursor 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.

Stale comment

Left a non-blocking comment and did not approve: Cursor Bugbot skipped and reported one unresolved high-severity finding on Go fallback plan replay. Reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor
cursor Bot requested review from Leitet and erikarenhill August 22, 2026 18:16

@cursor cursor 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.

Stale comment

Left a non-blocking comment and did not approve: Cursor Bugbot skipped on this head and still has an unresolved high-severity finding on Go fallback plan replay. No reviewers were assigned; two are already requested.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

frahlg added 3 commits August 23, 2026 18:23
Go fallback used to skip ValidatePlan, and forward simulation snapped
SoC onto the DP grid, so reported energy did not replay. Python
serialized full curtailment as pv_limit_w=0, the same sentinel as no
cap, and applied the PV-charge bonus in every mode.

Share BatteryEnergyDeltaWh, replay aggregate Go DP trajectories, start
forward sim from real SoC, and add pv_curtail_active so a zero cap is
a distinct contract. Gate the PV-charge bonus to passive_arbitrage.
Leftover-PV and battery-to-EV residual constraints run per scenario.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Policy lookup stays on the SoC grid. Forward simulation now integrates
from the actual initial SoC, including starts outside the configured
band, so ValidatePlan and the published trajectory match. An action
that would worsen recovery is clipped to the remaining headroom instead
of being replaced with idle or snapped after the fact.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg force-pushed the 885-planning-physics-kernel branch from 4a91854 to 1f00ebe Compare August 23, 2026 16:28

@cursor cursor 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.

Approved. Cursor Bugbot passed on this head and the prior finding is resolved; no remaining automated-review issues need human attention. No reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

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