Skip to content

Fix: couple stock constraints for multi-device batteries whose SoC comes from a sensor#2322

Merged
Flix6x merged 3 commits into
mainfrom
fix/inventory-stock-coupling
Jul 15, 2026
Merged

Fix: couple stock constraints for multi-device batteries whose SoC comes from a sensor#2322
Flix6x merged 3 commits into
mainfrom
fix/inventory-stock-coupling

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 15, 2026

Copy link
Copy Markdown
Member

Problem

When scheduling a subtree with multiple storage devices (multi-device / list flex-model mode), a battery whose starting state of charge is provided only by a state-of-charge sensor (or time series) — with no explicit soc-at-start — was scheduled without stock constraints. Its soc_at_start stayed None, so the solver never coupled the device's energy flow to its stored energy, and it could discharge far more energy than its store held.

This is the production shape of a battery under an apartment: the child asset's DB flex-model references the battery's power sensor via a nested output reference and its SoC via a state-of-charge sensor, and provides soc-min/soc-max but not soc-at-start.

Root cause: in single-sensor mode, ensure_soc_at_start() resolves soc-at-start from the state-of-charge field during deserialization, but the multi-device (list) branch of _deserialize_flex_model() never does. The per-stock assignment in _prepare() then reads stock_model.get("soc_at_start") and gets None, so add_storage_constraints is skipped.

Fix

Resolve a stock's soc-at-start from its (deserialized) state-of-charge sensor or time series in _prepare()'s per-stock loop — on the stock-owning entry only, so shared-stock device entries are unaffected and cannot be mistaken for stock owners. In line with single-sensor mode's ensure_soc_at_start() (#2026), a state of charge that is given but cannot be resolved fails the schedule with a ValueError, rather than silently scheduling the device without stock constraints.

Test

Adds test_multi_device_battery_couples_stock_from_soc_sensor: a battery in a multi-device flex-model, whose only SoC input is a state-of-charge sensor holding a small 0.05 kWh reading, with charging forbidden. It must not discharge more than the ~0.05 kWh it holds. Before the fix it discharged the full power capacity every step (10 kWh over the window); after the fix it respects the stored energy. The existing #2321 alignment / inventory / shared-stock tests continue to pass.

Cleanup (follow-up commits)

  • This turned out to be a regression from v1 development: Feat/dynamic soc at start #2026 added a per-entry ensure_soc_at_start call to the multi-device deserialization branch, which Add multi‑commodity support to device scheduler #1946 dropped (v0.33.1 still resolved soc-at-start there). The changelog entry is therefore an appended reference on the multiple-feeders feature entry, rather than a Bugfixes entry.
  • Percent-based state-of-charge sensors are now converted using the entry's own soc-max (the percent-conversion helper expects the pre-deserialization flex-model shape), with a regression test.
  • A given but unresolvable state of charge fails the schedule (see above), with a regression test.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki

…mes from a sensor

In multi-device (list) flex-model mode, soc-at-start is not resolved during
deserialization (unlike single-sensor mode's ensure_soc_at_start()). A battery
whose only SoC input is a state-of-charge sensor (no explicit soc-at-start) - the
production shape of a battery under an apartment - therefore had soc_at_start=None,
so the scheduler applied no stock constraints and the device could discharge far
more energy than its store held.

Resolve a stock's soc-at-start from its (deserialized) state-of-charge sensor or
time series in _prepare()'s per-stock loop, on the stock-owning entry only, so
shared-stock device entries are unaffected. Resolution is defensive: if no recent
SoC value exists, the stock simply keeps no stock constraints (as before) instead
of failing the schedule.

Signed-off-by: F.N. Claessen <felix@seita.nl>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki
@read-the-docs-community

read-the-docs-community Bot commented Jul 15, 2026

Copy link
Copy Markdown

Documentation build overview

📚 flexmeasures | 🛠️ Build #33604860 | 📁 Comparing 5d688ab against latest (97a5ef1)

  🔍 Preview build  

4 files changed
± changelog.html
± genindex.html
± _autosummary/flexmeasures.data.models.planning.storage.html
± api/v3_0.html

@Flix6x Flix6x self-assigned this Jul 15, 2026
@Flix6x Flix6x added bug Something isn't working Scheduling labels Jul 15, 2026
@Flix6x Flix6x added this to the 1.0.0 milestone Jul 15, 2026
Flix6x added 2 commits July 15, 2026 20:02
- The fixed behavior only regressed during v1 development (PR #1946
  dropped the per-entry ensure_soc_at_start call that PR #2026 had added
  to the multi-device branch; v0.33.1 still resolved soc-at-start there),
  so reference this PR from the multiple-feeders feature entry instead of
  adding a Bugfixes entry.
- Pass the stock entry's soc-max to the percent-conversion helper in the
  hyphenated pre-deserialization shape it expects; previously a
  state-of-charge sensor recording percentages could not be converted
  using the entry's own soc-max (new regression test).
- Warn when a stock's starting state of charge cannot be resolved, since
  the stock is then scheduled without stock constraints.
- Reflow comments and docstrings at natural clause boundaries.

Signed-off-by: F.N. Claessen <felix@seita.nl>
In line with single-sensor mode's ensure_soc_at_start() (PR #2026),
a stock whose state of charge is given but unresolvable
(e.g. a state-of-charge sensor without a recent value)
now fails the schedule with a ValueError,
instead of silently scheduling the device without stock constraints.

Also reflow docstrings and comments to break lines only after punctuation,
per .github/instructions/docstrings.instructions.md.

Signed-off-by: F.N. Claessen <felix@seita.nl>
@Flix6x
Flix6x merged commit 721bbb2 into main Jul 15, 2026
12 of 13 checks passed
@Flix6x
Flix6x deleted the fix/inventory-stock-coupling branch July 15, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Scheduling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant