Skip to content

feat(ev): Start becomes "charge now → target", releasing back to the plan - #1005

Closed
frahlg wants to merge 1 commit into
1002-ev-visibilityfrom
1002-ev-charge-now
Closed

feat(ev): Start becomes "charge now → target", releasing back to the plan#1005
frahlg wants to merge 1 commit into
1002-ev-visibilityfrom
1002-ev-charge-now

Conversation

@frahlg

@frahlg frahlg commented Aug 30, 2026

Copy link
Copy Markdown
Member

Part 2 of #1002. Stacked on #1004 — retarget to master after #1004 merges; only the last commit is this PR's diff.

Problem

The modal's Start button installs a persistent manual hold that pins the wallbox at a fixed amperage until unplug — the planner is silently dead for the whole session. On 2026-08-29 that meant an 11 kW offer held all night against a car that was already at its own charge limit, with the per-phase fuse clamp fighting the hold the entire time (worst phase measured 23 A on a 16 A main fuse).

What this does

  • loadpoint.ManualHold gains ReleaseAtSoC (0–1): once the loadpoint's inferred (or BMS-anchored) SoC reaches it, the controller clears the hold and the same tick falls through to automatic surplus/plan dispatch. Persisted with the hold across restarts. A hold without a target keeps the legacy pin-until-Stop-or-unplug contract; unplug and the vehicle-declined debounce still release as before.
  • POST /api/loadpoints/{id}/manual_hold accepts release_at_soc_pct (0–100, validated); GET mirrors it; loadpoint state carries manual_release_soc.
  • The Start button becomes "Charge now → 80 %" (the schedule's target when set, 80 % otherwise). The manual tab and the feat(ev): say why the charger is (not) charging and when it will #1004 plan strip both explain the hand-back: "Charging now at 11.0 kW → returns to plan at 80 %."

Verified in a browser (running dev stack)

Pressed "Charge now → 80 %" in the rendered modal → hold installed with release_at_soc_pct: 80 → corrected SoC to 85 % via /soc → controller logged loadpoint manual hold released — charge-now target reached (soc=0.85, release_at_soc=0.8) and the very next dispatch was the plan's, not the hold's.

Tests

  • TestChargeNowHoldReleasesAtTargetSoC — holds below target, releases at target, same-tick plan dispatch
  • TestLegacyHoldWithoutTargetNeverSoCReleases — legacy contract untouched
  • web/ev-charge-now.test.mjs — button contract, POST body, both status texts (445 web tests green)

🤖 Generated with Claude Code

https://claude.ai/code/session_01C1d4uknzs7QUv7nE9rW4fi


Note

Medium Risk
Changes live EV dispatch when operators use Start—release timing depends on inferred SoC—but legacy holds are unchanged and behavior is covered by new tests.

Overview
Start is a bounded “charge now” boost, not a session-long planner override. The EV modal relabels the action to “Charge now → {target} %” (schedule target SoC, else 80 %), posts release_at_soc_pct with the manual hold, and updates plan/manual status copy to describe hand-back to the plan.

Backend: ManualHold gains ReleaseAtSoC; the loadpoint controller clears the hold when estimated/BMS SoC reaches the target and dispatches the MPC plan on the same tick. Holds with no release target keep pin until Stop/unplug. POST/GET /api/loadpoints/{id}/manual_hold accept and echo release_at_soc_pct (0–100); loadpoint state exposes manual_release_soc.

Tests: controller release-at-target + legacy hold behavior; web test asserts UI POST/labels.

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

…plan

Field report (#1002): a Start hold pins the wallbox at a fixed amperage
until unplug, silently disabling the planner for the whole session —
observed as an 11 kW offer held all night against a car that was
already full, with the fuse clamp fighting it the whole way.

ManualHold gains ReleaseAtSoC (0–1): once the loadpoint's inferred (or
BMS-anchored) SoC reaches it, the controller clears the hold and the
same tick falls through to automatic surplus/plan dispatch. The target
persists with the hold across restarts; a hold without one keeps the
legacy pin-until-Stop-or-unplug contract, and the existing releases
(unplug, vehicle-declined debounce) still apply.

POST /api/loadpoints/{id}/manual_hold accepts release_at_soc_pct
(0–100), GET mirrors it, and the loadpoint state carries
manual_release_soc so the UI can say where the boost stops. The modal's
Start button becomes "Charge now → 80 %" (schedule target when set,
80 % otherwise) and both the manual tab and the plan strip explain the
hand-back.

Verified in the running dev stack: pressed the button, hold installed
with release_at_soc_pct 80; corrected SoC to 85 %; controller logged
"charge-now target reached" and released to plan dispatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1d4uknzs7QUv7nE9rW4fi
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 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-08-30T07:54:14.990289Z d47879c 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.

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

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

// surplus/plan dispatch instead of holding the wallbox at a fixed
// amperage the rest of the session.
if hold, held := c.GetManualHold(lpCfg.ID, now); held && hold.ReleaseAtSoC > 0 {
if st, ok := c.manager.State(lpCfg.ID); ok && st.CurrentSoC >= hold.ReleaseAtSoC {

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 Honor charge-now targets when capacity is omitted

When a loadpoint has neither vehicle_capacity_wh nor a paired vehicle BMS, this condition can never become true: configuration permits omitting capacity, while estimateSoC in loadpoint.go returns only the fixed plug-in anchor whenever capacity is zero, regardless of delivered session energy. The UI now installs every Charge-now hold with an 80% or scheduled release target, but on these sites the hold continues past that target until Stop, unplug, or a supported vehicle-declined signal—recreating the persistent high-current behavior this change is meant to prevent. Apply the documented capacity fallback to SoC inference or otherwise ensure target-based holds can terminate without configured capacity.

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 d47879c. Configure here.

slog.Info("loadpoint manual hold released — charge-now target reached",
"lp", lpCfg.ID, "soc", st.CurrentSoC, "release_at_soc", hold.ReleaseAtSoC)
c.ClearManualHold(lpCfg.ID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Charge now uses latched session SoC

Medium Severity

Charge-now release compares ReleaseAtSoC to CurrentSoC after Observe, which pins CurrentSoC to targetSoC once sessionComplete latches. The UI sends the schedule target as the release point, so a hold clears on the first tick even when the car is still below that target — typically after it declined at its own charge limit. Charge now then cannot override a completed session without an unplug.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d47879c. Configure here.

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

Left a non-blocking comment and did not approve: Cursor Bugbot completed as skipped, so the required automated-review signal is not usable. Assigned reviewers for a human look at the charge-now hold/release path.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor
cursor Bot requested a review from erikarenhill August 30, 2026 07:57
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