feat(planner): add EV SoC economics sensor comparing cost across target SoC and deadlines - #905
Open
woopstar wants to merge 2 commits into
Open
feat(planner): add EV SoC economics sensor comparing cost across target SoC and deadlines#905woopstar wants to merge 2 commits into
woopstar wants to merge 2 commits into
Conversation
…et SoC and deadlines - Fixes #903 Adds a diagnostic sensor per EV that shows the real-money cost of charging to each of 50/60/70/80/100% SoC by the next occurrence of 08:00 and 17:00, by re-running the existing pure run_planner() on a cloned copy of the coordinator's last-used PlannerInput with only that EV's target-SoC and deadline fields overridden. No planner/MILP semantics change. - New pure module planner/ev_soc_economics.py (next_time_of_day(), EVSoCEconomicsPoint/Result, compute_ev_soc_economics()). - New coordinator mixin coordinator_ev_soc_economics.py, wired into coordinator_cycle.py right after _run_planner_phase(), throttled to at most every 30 minutes, both EVs solved in one executor job, with the same stale-update-cycle guard as the main planner run. - Two new diagnostic sensors (ev_soc_economics_sensor.py, ev_second_soc_economics_sensor.py) mirroring the existing EV optimal charging plan sensors, gated on the existing EV enabled config flags. - Conditional markdown-table dashboard cards per EV, mirrored into docs/dashboard-setup.md. - Docs: new "EV SoC economics sensors" subsection in docs/sensors-reference.md. - Tests: tests/planner/test_ev_soc_economics.py, coordinator throttle/gating/stale-cycle tests in tests/test_coordinator.py, and sensor tests in tests/sensors/test_ev_soc_economics_sensor.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an "EV SoC Economics" diagnostic sensor per EV that answers "what would it
cost to charge to X% by deadline Y?" — the real-money cost of charging to each
of 50/60/70/80/100% SoC by the next occurrence of 08:00 and by the next
occurrence of 17:00.
Each combination is computed by cloning the coordinator's last-used
PlannerInput, overriding only that EV's target-SoC/deadline fields(
ev_planned_load_target_soc_pct/ev_planned_load_deadline, or theev_second_*equivalents), and re-running the existing purerun_planner(PlannerInput) -> PlannerOutput.PlannerOutput.plan_cost.total_costis read back per combination. No planner/MILP semantics change — this
calls the existing pure engine unmodified with different inputs.
No auto-recommended target is exposed — the raw cost/delta numbers are
surfaced and the user decides.
What changed
Planner (pure, no HA imports):
custom_components/hsem/planner/ev_soc_economics.py—next_time_of_day()helper,
EVSoCEconomicsPoint/EVSoCEconomicsResultdataclasses, andcompute_ev_soc_economics(). Guard clauses mirrorev_planner.build_ev_charging_plan()'s early-outs (notenabled/connected/smart-charging-disabled/zero capacity). Targets at or
below the EV's current SoC cost
0.0and skip solving entirely. Feasibilityis a simple charger-power-based check independent of price.
Coordinator:
custom_components/hsem/coordinator_ev_soc_economics.py— new mixin, calledfrom
coordinator_cycle.pyright after_run_planner_phase()returns.Throttled to at most every 30 minutes
(
EV_SOC_ECONOMICS_RECOMPUTE_MIN_SECONDS), requiresself._last_planner_inputto be set, solves both EVs in a singlehass.async_add_executor_job()call, and re-checks the update-generationguard afterwards (same stale-cycle pattern as the main
run_planner()call).CoordinatorData/CoordinatorSharedState:ev_soc_economics,ev_second_soc_economics.Sensors:
custom_sensors/ev_soc_economics_sensor.py(HSEMEVSoCEconomicsSensor) andcustom_sensors/ev_second_soc_economics_sensor.py(
HSEMEVSecondSoCEconomicsSensor) — mirror the existingev_optimal_charging_plansensors exactly (DIAGNOSTIC,RestoreEntity,ENUM device class). State is one of
ready/not_connected/smart_charging_disabled/unavailable; the full cost/delta table livesin
extra_state_attributesviaEVSoCEconomicsResult.as_attributes().utils/sensornames/ev.py, registered insensor.pyunder the existing
hsem_ev_planned_load_enabled/hsem_ev_second_planned_load_enabledgates.translations/en.jsonandtranslations/da.json.Dashboard:
custom_components/hsem/dashboards/dashboard_en.yaml's "EV Charging"section, rendering the
pointsattribute grouped bydeadline_labelviaJinja's
groupbyfilter (notcustom:apexcharts-card— this is a smallcategorical table, not a time series). Mirrored into
docs/dashboard-setup.md's "Full dashboard YAML" block; card-count tableand "EV Charging tiles" reference table updated.
Docs:
docs/sensors-reference.mdunder "EV charging plan sensors".
docs/planner-spec.mdis untouched — noplanner semantics changed.
Tests
tests/planner/test_ev_soc_economics.py—next_time_of_day(), guardstates (no
run_planner()call), monotonic cost per deadline column,zero-cost/no-solve for already-met targets, price-independent feasibility,
delta fields, second-EV field isolation (17 tests).
tests/test_coordinator.py— throttle window, first-call compute,no-op without
_last_planner_input, single executor job for both EVs,stale-generation discard (6 tests).
tests/sensors/test_ev_soc_economics_sensor.py— state/attributes/availability/restore for both sensors (25 tests).
tests/test_ha_mock_integration.pybare-coordinatorhelpers/mocks that didn't know about the new coordinator fields/executor
call.
Test and lint results
All four quality gates pass:
Acceptance criteria (from #903)
compute_ev_soc_economics()returns a monotonically non-decreasing costper target within each deadline column, for a fixed price/PV forecast.
0.0and do not triggeran extra
run_planner()solve.feasibleisFalsewhen the charger's max power cannot physicallyreach the target by the deadline, independent of price.
not_connected,smart_charging_disabled,unavailable) short-circuit without callingrun_planner()at all.hsem_ev_second_planned_load_enabledis set.docs/sensors-reference.mddocuments both new sensors.mirrored into
docs/dashboard-setup.md../scripts/quality.sh allpasses.Fixes #903