Fix device misalignment when flex-model entries are filtered from multi-device schedules#2319
Closed
Flix6x wants to merge 2 commits into
Closed
Fix device misalignment when flex-model entries are filtered from multi-device schedules#2319Flix6x wants to merge 2 commits into
Flix6x wants to merge 2 commits into
Conversation
…ti-device schedules Two bugs in StorageScheduler multi-device scheduling: 1. _prepare() zipped the (filtered) device sensors against the unfiltered self.flex_model when building the per-device asset list, misassigning assets whenever a stock-only entry was filtered out. This let a device inherit another device's power capacity via the asset.flex_model power-capacity fallback. Zip against device_models instead. 2. _deserialize_flex_model() left the top-level "sensor" as None for devices that reference their power sensor only through a nested "consumption" (or "production") output reference. Such devices were misclassified as stock-only and silently dropped, so their output sensor received no schedule. Resolve the power sensor from the output reference. Adds an end-to-end regression test covering both bugs. 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
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
|
Flix6x
added a commit
that referenced
this pull request
Jul 15, 2026
…2321) * test: lock multi-device flex-model alignment (currently broken) Add strict-xfail regression tests for four confirmed device-alignment bugs in multi-device storage scheduling: - A stock-only entry listed before device entries shifts every device's properties (power capacity, efficiencies) by one, because per-device properties are read from the unfiltered flex-model list. - A device without a state-of-charge sensor silently loses its own SoC parameters when a stock-only entry is present, because the synthetic stock keys diverge between _prepare and _build_stock_groups. - Flex-context commitments are assigned device indices by enumerating the unfiltered flex-model list, so stock-only entries receive commitments meant for devices. - A device referencing its power sensor only via a nested consumption/ production output reference is misclassified as stock-only and dropped (carried over from PR #2319, which this branch subsumes). The xfail markers are removed in the commits that fix each bug. Signed-off-by: F.N. Claessen <felix@seita.nl> * feat: add a typed device inventory for schedulers Introduce planning/devices.py: every flex-model entry is classified exactly once into a DeviceInventory, which becomes the single source of truth for - entry roles (device / stock-only, extensible to group entries and converter ports), - canonical solver device indices (flexible devices first, then top-level inflexible devices, then per-commodity-context inflexible devices), - stock groups and their SoC parameters (sharing one key space, so parameters can no longer be lost to diverging synthetic keys), and - sensor-id and commodity lookups. Power sensors are resolved from the top-level sensor field or, failing that, from a nested consumption/production output reference, so such devices are no longer misclassified as stock-only entries. The raw deserialized flex-model dicts are kept as-is on each FlexDevice, so downstream code and new flex-model fields need no dataclass changes. Not yet wired into the StorageScheduler; see subsequent commits. Signed-off-by: F.N. Claessen <felix@seita.nl> * test: give the alignment regression tests their own parent site Scheduling on the shared module-scoped building fixture is fragile: earlier tests create sensors on it whose legacy attributes (capacity_in_mw, max_soc_in_mwh, ...) get moved into the building's flex-model, which collect_flex_config then injects into the test's flex-model as an extra asset-only device entry, changing the optimization problem. Each alignment test now schedules on its own fresh parent site. Signed-off-by: F.N. Claessen <felix@seita.nl> * fix: track devices via the inventory in the StorageScheduler Build the DeviceInventory once after flex-config deserialization (with a lazy rebuild for tests that bypass deserialization), and replace _prepare()'s ad-hoc entry classification with it. The local flex_model variable now holds (copies of) the classified device flex-models instead of reverting to the unfiltered flex-model list. This fixes, by construction: - Devices no longer inherit a neighboring entry's properties (power capacity, efficiencies, output sensors) when a stock-only entry precedes them in the flex-model list: all per-device property lists, including the sensors/assets pair, now derive from the same filtered, index-aligned device list. - Devices without a state-of-charge sensor no longer lose their own SoC parameters when a stock-only entry is present (the synthetic stock keys of stock parameters and stock groups now come from one counter). - Flex-context commitments now bind the scheduled devices instead of enumerating the unfiltered flex-model list. - Devices referencing their power sensor only via a nested consumption/ production output reference are scheduled (previously misclassified as stock-only and silently dropped) — subsumes PR #2319. - The dead (and misaligned) stock-groups build in _deserialize_flex_model and the duplicated stock-only detection are gone. The strict-xfail alignment regression tests now pass; their markers are removed. _prepare()'s return value and device_scheduler's interface are unchanged. Signed-off-by: F.N. Claessen <felix@seita.nl> * refactor: one source of truth for the commodity device enumeration Both _prepare() and the compute()-side result mapping now take the commodity -> device indices mapping (flexible devices, then top-level inflexible devices, then per-commodity-context inflexible devices) from the device inventory, instead of maintaining two mirrored ~40-line enumerations that had to stay in sync byte for byte. The device constraints for inflexible devices are likewise sized and filled from the inventory's canonical order. _reconstruct_commodity_to_devices keeps a fallback for bare schedulers (e.g. in tests) that classifies the stored device models on the fly. Signed-off-by: F.N. Claessen <felix@seita.nl> * fix: a state of charge given as a value or time series forms its own stock Only a sensor reference can link devices into a shared stock. A state-of-charge field holding timed values (a list of specs) is not a stock identity: classify such devices under their own synthetic stock key, keeping their SoC parameters. On main, this shape crashed _prepare (unhashable stock_models key) if it carried SoC parameters; it only went unnoticed because such flex-models were exercised up to deserialization, never scheduling. Signed-off-by: F.N. Claessen <felix@seita.nl> * fix: tolerate unflushed assets when collecting the flex config Two latent order-dependence bugs, surfaced by running the planning test modules in a new order: - GenericAsset.get_flex_context crashed (TypeError) when an ancestor's flex_context was still None, e.g. a pending asset created without one, before its column default is applied on flush. - The charging station test fixture never flushed, so its assets had no IDs unless an earlier test happened to trigger a flush; scheduler config collection then silently failed to match the flex-model to its asset. The affected test (test_deserialize_storage_soc_at_start_from_ state_of_charge_time_series) failed when run in isolation. Both are now fixed; the test passes in any order. Signed-off-by: F.N. Claessen <felix@seita.nl> * docs: changelog entries and _build_stock_groups deprecation note Signed-off-by: F.N. Claessen <felix@seita.nl> * docs: shorten the changelog reference The fixed alignment bugs only affect flex-model shapes introduced during v1 development (entries without a top-level sensor were rejected outright in v0.33.1), so no released behavior regressed: reference the PR from the multiple-feeders feature entry instead of a separate bugfix entry. Signed-off-by: F.N. Claessen <felix@seita.nl> * fix: fail fast on conflicting stock parameters (review) Address review comments on PR #2321: - Raise a ValidationError when more than one flex-model entry defines state-of-charge parameters for the same stock, instead of silently letting the last entry win (stricter than the historical behavior). - Reflow attribute comments to natural clause breaks, clarify that the flex_model None check is a precaution for inflexible devices, and note why inflexible devices always have a power sensor. - Word choice: scheduling features (not code sites) re-derived entry kinds; pass the sensor name as a keyword argument in the test helper. Signed-off-by: F.N. Claessen <felix@seita.nl> --------- Signed-off-by: F.N. Claessen <felix@seita.nl>
Member
Author
|
Subsumed by #2321. |
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
Fixes two confirmed bugs in
StorageSchedulermulti-device scheduling that surface whenever a flex-model entry is filtered from the device list. Both were reproduced in a community simulation with a heat pump and a child battery: the heat pump inherited the battery's ±2.5 kW power capacity, and the battery was never scheduled.Bug 1 — misaligned zip in
_prepare()(zip truncation since #1946 × #1429)device_modelsis a filtered subset ofself.flex_model(stock-only entries are dropped). When building the per-device asset list, the code zippedsensors(built fromdevice_models) against the unfilteredself.flex_model. Whenever anything was filtered out, this misassigned assets. Combined with_get_device_power_capacity'sasset.flex_model.get("power-capacity")fallback, a device could inherit another device's power capacity. The latent assumption dates to #1429; #1946 broke it by adding the stock-only filtering.Fix: zip against
device_models(the same listsensorsis built from).Bug 2 — devices with only a nested output reference silently dropped
A flex-model entry that references its power sensor only via a nested
"consumption": {"sensor": N}(or"production") and has a"state-of-charge"sensor ends up withsensor = Noneafter deserialization. It is then classified as stock-only and removed fromdevice_models— the device is never scheduled and its consumption/production output sensor receives zero beliefs.Fix: in
_deserialize_flex_model(list branch), when a device entry has no top-level"sensor", resolve it from theconsumption/productionoutput reference. Genuine stock-only entries (no sensor and no output reference) are still filtered.Test
Adds
test_multi_device_flex_model_alignment, which schedules two devices under a common parent:consumption, its own state-of-charge, and a larger (9 kW) capacity.Asserts (i) device A respects its own 1 kW capacity (fails before Bug 1 fix — A charged 4 kW) and (ii) device B's consumption output sensor receives a schedule (fails before Bug 2 fix). Verified to fail on unfixed code and pass with the fixes. Existing
test_storage.pyoutput-sensor tests still pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01LQFkeGxDwErydUV5ZmHmki