Skip to content

fix(ev): stop valuing a charge-only EV as storage - #210

Draft
nberlee wants to merge 3 commits into
jsimonetti:mainfrom
nberlee:fix/charge-only-ev-soc-credit
Draft

nberlee wants to merge 3 commits into
jsimonetti:mainfrom
nberlee:fix/charge-only-ev-soc-credit

Conversation

@nberlee

@nberlee nberlee commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #209.

Problem

An EV charger without V2H (empty discharge_segments) was treated as storage:

  • _compute_soc_credit credited its SOC gain at the average import price, with an implicit discharge efficiency of 1.0 (the empty-segment fallback of _avg_discharge_efficiency), so a charge-only car was valued above a battery.
  • EvDevice.terminal_soc_var returned soc[T-1] for any plugged-in EV, so the objective rewarded charging beyond the target whenever the price was below the horizon average.

The car can never return that energy to the house, so for the household it is consumption, like a deferrable load.

Changes

As agreed in the issue:

  1. _compute_soc_credit skips an EV charger with no discharge_segments, as it already skips an unplugged EV.
  2. EvDevice.terminal_soc_var returns None for a charge-only charger. Its docstring now covers both cases that return None, and states that energy beyond the target is charged only when that lowers the cost on its own, for example at a negative import price.

Two follow-on changes keep the rest of the code base consistent with the fix:

  1. The reporter's household view (_discharge_path in mimirheim_helpers/reporter/reporter/household.py) mirrors _compute_soc_credit to value energy left in storage per day. It now leaves a charge-only EV out too; otherwise its daily savings would disagree with the solver.
  2. The SolveResult.soc_credit_eur docstring no longer says the credit covers all EVs.

The fairer naive baseline (car charging at full power from plug-in, deferrable load starting at the start of its window) is left for a separate issue, as discussed.

Tests

  • test_soc_credit_skips_a_charge_only_ev, next to test_soc_credit_skips_an_unavailable_ev.
  • test_charge_only_ev_is_charged_to_its_target_and_not_credited: the reproduction from the issue through build_and_solve. Before the fix the car ended at 42 kWh (22 kWh charged); now it charges exactly the 10 kWh it needs in the cheap hours, optimised_cost_eur is 1.40 and soc_credit_eur is 0.
  • test_ev_charge_only_has_no_terminal_soc_var and test_ev_v2h_has_terminal_soc_var.
  • The reporter's solver cross-check (test_household_against_solver.py) now includes a charge-only EV with a charging target, and asserts that the van gains SOC so the exclusion is actually exercised.

No scenario golden file changes.

Behaviour change

A plugged-in charge-only EV without a target_soc_kwh no longer charges at all, including from PV surplus (unless the import price is negative). Before, it charged whenever the price was below the horizon average, because the terminal value made stored energy look profitable. Users who relied on that opportunistic charging should set a target.

Still draft, so I can see if this really solves my issue in the comming days

A charger without discharge_segments can never return energy to the
house, so what it charges is consumption, like a deferrable load. It was
nevertheless credited in soc_credit_eur at the average import price,
with an implicit discharge efficiency of 1.0, and its terminal SOC was
valued in the objective, which drove the solver to charge beyond the
target whenever the price was below the horizon average.

_compute_soc_credit now skips a charge-only EV, and
EvDevice.terminal_soc_var returns None for one, so the target SOC alone
decides how much is charged. No scenario golden file changes.

Fixes jsimonetti#209
…rgy value

The household view mirrors _compute_soc_credit to value energy left in
storage per day. It still credited a charge-only EV at an implicit
discharge efficiency of 1.0, so its per-day savings disagreed with the
solver after the previous commit. The cross-check against the solver now
includes a charge-only EV with a charging target.

Refs jsimonetti#209
SolveResult.soc_credit_eur still described a credit across all EVs, and
the terminal_soc_var docstring claimed the target alone decides how much
is charged, which a negative import price contradicts.

Refs jsimonetti#209

This branch has not been deployed

No deployments
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.

A charge-only EV is counted as storage in soc_credit_eur and the objective

1 participant