Fix: couple stock constraints for multi-device batteries whose SoC comes from a sensor#2322
Merged
Conversation
…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
Documentation build overview
4 files changed± changelog.html± genindex.html± _autosummary/flexmeasures.data.models.planning.storage.html± api/v3_0.html |
- 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>
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.
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-chargesensor (or time series) — with no explicitsoc-at-start— was scheduled without stock constraints. Itssoc_at_startstayedNone, 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-chargesensor, and providessoc-min/soc-maxbut notsoc-at-start.Root cause: in single-sensor mode,
ensure_soc_at_start()resolvessoc-at-startfrom thestate-of-chargefield during deserialization, but the multi-device (list) branch of_deserialize_flex_model()never does. The per-stock assignment in_prepare()then readsstock_model.get("soc_at_start")and getsNone, soadd_storage_constraintsis skipped.Fix
Resolve a stock's
soc-at-startfrom its (deserialized)state-of-chargesensor 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'sensure_soc_at_start()(#2026), a state of charge that is given but cannot be resolved fails the schedule with aValueError, 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 astate-of-chargesensor 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)
ensure_soc_at_startcall 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.soc-max(the percent-conversion helper expects the pre-deserialization flex-model shape), with a regression test.🤖 Generated with Claude Code
https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki