Support intermediate power constraints on groups of devices (flex-model group field) - #2276
Conversation
Add a new `group` field to the storage flex-model, letting device entries reference a power sensor that represents a group of devices (e.g. a hybrid inverter shared by a battery and PV). The group sensor's own flex-model entry constrains the group's aggregate power: - power-capacity: hard constraint (both directions) - consumption-capacity / production-capacity: soft constraints, enforced via breach commitments with default breach prices (10000 <currency>/kW) The group's scheduled aggregate power is saved to the group sensor. Nested groups are supported (cycles rejected); groups require multi-device flex-models and members must share a commodity. Implemented on top of the existing device-group solver machinery (ems_constraint_groups from the multi-commodity work, and grouped FlowCommitments from PR #1934); no changes to the optimizer itself. Also fixes a latent device-index misalignment in MetaStorageScheduler._prepare, where per-device lists were built from the full flex-model list (including stock-only entries) instead of the filtered device models. Closes #2092 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX
Documentation build overview
51 files changed ·
|
Add a new `group` field to the storage flex-model, letting device entries reference a power sensor that represents a group of devices (e.g. a hybrid inverter shared by a battery and PV). The group sensor's own flex-model entry constrains the group's aggregate power: - power-capacity: hard constraint (both directions) - consumption-capacity / production-capacity: soft constraints, enforced via breach commitments with default breach prices (10000 <currency>/kW) The group's scheduled aggregate power is saved to the group sensor. Nested groups are supported (cycles rejected); groups require multi-device flex-models and members must share a commodity. Implemented on top of the existing device-group solver machinery (ems_constraint_groups from the multi-commodity work, and grouped FlowCommitments from PR #1934); no changes to the optimizer itself. Also fixes a latent device-index misalignment in MetaStorageScheduler._prepare, where per-device lists were built from the full flex-model list (including stock-only entries) instead of the filtered device models. Closes #2092 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX Signed-off-by: F.N. Claessen <claessen@seita.nl>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX Signed-off-by: F.N. Claessen <claessen@seita.nl>
The UI-DB flex-model schema parity test requires every DBStorageFlexModelSchema field to have UI support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ts' into fix/intermediate-power-constraints
… asset tree (#2277) Extend the storage flex-model's group field to also accept {"asset": <id>}, so a group can be identified by an asset-keyed flex-model entry - the form that DB-stored flex-models naturally produce. This makes intermediate power constraints fully definable via an asset tree with stored flex-models, with an empty flex-model in the scheduling trigger. - Asset-referenced group entries define no power sensor of their own; the group's scheduled aggregate power is saved via the entry's consumption and/or production output sensors, following the usual output-sensor conventions (full profile on a single sensor; clip-split when both given). - Sensor-referenced groups keep saving the aggregate to the group power sensor, and may now also define output sensors. - The UI asset flex-model editor recommends the parent asset when editing a child's group field (one-click suggestion), and hints in both the parent's and children's editors when the parent's flex-model defines power-capacity. - New tutorial: toy example for intermediate power constraints, driven entirely by DB-stored flex-models. Bug fixes uncovered along the way: - AssetTriggerSchema.check_flex_model_sensors raised KeyError on asset-only flex-model entries. - The freeze_server_now test fixture leaked its server_now monkeypatch into all subsequently run tests in the same process, causing order-dependent duplicate-key failures when scheduling jobs saved beliefs with identical (frozen) belief times. Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX Signed-off-by: F.N. Claessen <claessen@seita.nl> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Remove references to code modules, tests and GitHub issues from the tutorial; tutorials address FlexMeasures users, not developers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX Signed-off-by: F.N. Claessen <claessen@seita.nl>
Adapt the group feature to the typed DeviceInventory introduced in PR #2321: - Classify group entries in the inventory (new DeviceRole.GROUP), as the first classification step, so a group entry's own power sensor or output sensors cannot make it pass for a device. - Replace the scheduler's ad-hoc _group_models/_group_to_devices derivation with the inventory's group_entries field and group_to_devices resolution (transitive leaf-device resolution with cycle detection). - Drop this branch's own copy of the stock-only misalignment fix (device_sensor_id_to_index and the device_models zip fix), which main now solves by construction via the inventory's canonical device indices. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qxM7UZ5wHTz3ftz1Mf9yy Signed-off-by: F.N. Claessen <felix@seita.nl>
Reconcile the group-field intermediate power constraints with main's DeviceInventory/DeviceRegistry work (#2321/#2325): combine the devices.py imports (group_key_label + _resolve_stock_key) and keep both sides' new DeviceInventory tests. Regenerate openapi-specs.json (version 0.33.2 -> 1.0.0), addressing the review comment on the stale spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
|
Brought in line with Re the OpenAPI spec: regenerated it, so the |
There was a problem hiding this comment.
Pull request overview
This PR adds support for intermediate power constraints by introducing a group field in storage flex-model entries, enabling hard/soft aggregate power limits over groups of devices (e.g., hybrid inverter bottlenecks) without changing the optimizer, and wiring the feature through schema validation, scheduling logic, UI hints/editor behavior, tests, and documentation.
Changes:
- Add
group(sensor- or asset-referenced) to storage flex-model schemas and OpenAPI/metadata, including validation logic. - Extend the
StorageSchedulerpipeline to classify group entries, enforce group hard/soft constraints, and save group aggregate schedules (including via output sensors for asset-referenced groups). - Add UI hints + extensive test coverage and documentation/tutorial updates for DB-driven asset-tree configurations.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| flexmeasures/ui/views/assets/views.py | Passes parent/child + power-capacity presence flags to the asset properties template for UI hints |
| flexmeasures/ui/tests/test_asset_crud.py | Adds UI test coverage for the new group-field hint behavior |
| flexmeasures/ui/templates/assets/asset_properties.html | Adds hint banners and editor-side “use parent as group” behavior in the flex-model editor |
| flexmeasures/ui/static/openapi-specs.json | Updates generated OpenAPI specs to document the new group field/schema |
| flexmeasures/data/schemas/tests/test_scheduling.py | Adds schema tests for group sensor/asset variants and power-sensor validation |
| flexmeasures/data/schemas/scheduling/storage.py | Introduces GroupReferenceSchema and validates group references (power-sensor check, exactly-one-of sensor/asset) |
| flexmeasures/data/schemas/scheduling/metadata.py | Adds GROUP metadata used for docs/OpenAPI/UI schema rendering |
| flexmeasures/data/schemas/scheduling/init.py | Adds UI schema-spec entry for group and fixes sensor-checking for asset-only flex-model entries |
| flexmeasures/data/models/planning/tests/test_group_constraints.py | New planning-level test module covering hard/soft group constraints, nesting, output saving, and DB-tree E2E |
| flexmeasures/data/models/planning/tests/test_device_inventory.py | Adds tests for classifying group entries, resolving leaf membership, and cycle detection |
| flexmeasures/data/models/planning/storage.py | Implements group constraint enforcement and group schedule output handling in the scheduler |
| flexmeasures/data/models/planning/devices.py | Extends DeviceInventory to classify group entries and resolve transitive group membership |
| flexmeasures/conftest.py | Fixes freeze_server_now fixture leakage by restoring patched imports and server_now functions |
| flexmeasures/api/v3_0/tests/test_asset_schedules_fresh_db.py | Adds API-level fresh-db tests for group constraint enforcement and tree membership validation |
| documentation/tut/toy-example-group-constraints.rst | New tutorial showing a fully DB-driven asset-tree setup using group constraints |
| documentation/index.rst | Adds the new tutorial to the documentation index |
| documentation/features/scheduling.rst | Documents group field semantics and intermediate power constraints |
| documentation/concepts/commitments.rst | Updates commitments docs to reflect current group-related capability and roadmap |
| documentation/changelog.rst | Adds changelog entries for intermediate group constraints and asset-referenced groups |
| documentation/api/change_log.rst | Adds API changelog entries describing the new group field behavior |
- asset_properties.html: HTML-escape the parent asset name before it is inserted into innerHTML in the group hint (it is user-controlled -> XSS). - storage.py: attach the "group must reference a power sensor" ValidationError to the `group` field, not `sensor`. - GroupReferenceSchema now inherits from SharedSensorReferenceSchema instead of SensorReferenceSchema, so it accepts only `sensor`/`asset` (a group is a device-group identifier, not a belief-query reference) and no longer exposes the source-* filter fields; regenerate openapi-specs.json accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
|
Addressed the three review findings (commit 7686b39):
All green locally: 130 tests across |
Reword _add_group_schedules' docstring: the scheduling model assembles and returns each group's aggregate power schedule; persistence is the data service's responsibility. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
Resolve conflicts from PR #2276 (intermediate power constraints via flex-model 'group' field) against the CHP converter-coupling work. All conflicts were additive (both sides added distinct helpers/methods/ docstring notes in the same regions); kept both sides: - devices.py: DeviceRole docstring (converter ports + GROUP), the _is_zero_capacity/_resolve_coupling_coefficient helpers alongside the group-key helpers, and coupling_groups alongside group_to_devices. - storage.py: coupling_groups wiring alongside group entry classification and validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
Combine the operation-modes power-bands flex-model field with the new group-based intermediate power constraints from PR #2276. Conflicts were co-located additions in the storage flex-model schema, metadata descriptions, docs, and the generated OpenAPI spec; both features kept. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
…on-mode-fixed-cost Pull in the base branch's merge with main (group-based intermediate power constraints from PR #2276). Only the changelog conflicted (co-located additions); both entries kept. Fixed-cost operation-mode tests stay green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
feat/chp now carries origin/main's PR #2276 (intermediate power constraints via the flex-model 'group' field). Single conflict in storage.py, additive: kept both the balance_groups initialization (internal-commodity-balance) and the group-entry classification/validation block (from the feat/chp main-merge). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
Reconcile sensor-scoped commitments with main's group field (#2276) on the DeviceInventory/DeviceRegistry rewrite. CommitmentSchema keeps both the new `sensors` scoping field and main's `commodity` field; convert_to_commitments keeps the scoped-aggregate branch (via DeviceInventory.by_sensor_id) alongside main's per-commodity `bound_device_count` guard. Re-append the scoped-commitment test onto main's version of test_commitments.py (git had interleaved it with _shared_stock_scheduler on their shared StorageScheduler(...) kwargs). Regenerate openapi-specs.json. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B Signed-off-by: F.N. Claessen <claessen@seita.nl>
|
Thanks for building this. #2092 describes our situation exactly: a hybrid inverter whose battery and PV children can We solve it downstream today, on 0.32.0, with a Before we plan it, we'd like to check that we've understood the feature correctly. Below is Our understanding
How we would map our sites onto it Our tree is site → point of common coupling → distribution board → inverter → {battery, PV, One question that isn't about the code Do you have a sense of which release will carry this, and roughly when? We can see it's on Once we've confirmed the above we'll have a couple of follow-ups about our specific asset |
|
Fixes the oversight raised under point 5 of the #2276 discussion: inflexible devices from the flex-context could not be group members, so a group's intermediate power constraint ignored inflexible (measured) load behind the same node. Each inflexible-consumption/inflexible-production entry may now carry the same group field (sensor- or asset-referenced) as a flex-model device entry. The device inventory folds such inflexible devices into the group's leaf members, so the solver's group power constraint and the saved group aggregate account for their fixed power. GroupReferenceSchema moves to a shared groups.py to avoid an import cycle. 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_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl>
…consumption and inflexible-production (#2358) * feat: replace inflexible-device-sensors with sign-explicit flex-context fields Introduce the inflexible-consumption and inflexible-production flex-context fields (issue #2239): lists of sensor references (with optional source filters) whose key name makes explicit how the sign of each inflexible device's power data is read. Deprecate inflexible-device-sensors (still supported; sign read from each sensor's consumption_is_positive attribute), reject mixing the deprecated and newer keys in one flex-context, and treat the three keys as one field family when merging flex-contexts across sources (request over DB, asset-tree inheritance). Includes a behavior-preserving data migration splitting stored contexts by each sensor's consumption_is_positive attribute, scheduler support via the device inventory, sequential-scheduling injection routed by the same attribute, UI editor support, docs/tutorial updates and OpenAPI regeneration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl> * docs: fill in PR number in changelog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl> * fix: gather inflexible device sensors from nested commodities entries too Addresses Copilot review on #2358: get_inflexible_device_sensors only inspected the top-level flex-context keys, omitting inflexible devices configured within per-commodity contexts from sensor status/metadata. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl> * refactor: iterate migration result cursor directly Addresses Copilot review round 2 on #2358; matches the cb8df44ebda5 style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl> * feat: let inflexible devices join a group (flex-context group field) Fixes the oversight raised under point 5 of the #2276 discussion: inflexible devices from the flex-context could not be group members, so a group's intermediate power constraint ignored inflexible (measured) load behind the same node. Each inflexible-consumption/inflexible-production entry may now carry the same group field (sensor- or asset-referenced) as a flex-model device entry. The device inventory folds such inflexible devices into the group's leaf members, so the solver's group power constraint and the saved group aggregate account for their fixed power. GroupReferenceSchema moves to a shared groups.py to avoid an import cycle. 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_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl> * docs: advocate the asset form of the group field Both group-reference forms target v1.0.0 and neither is released yet. Lead the docs, the GROUP field description, and the schema docstring with the `{"asset": <id>}` form (the one that composes with the asset tree and multi-level hierarchies), and frame `{"sensor": <id>}` as the compact, API-passed alternative. Also point inflexible-device group membership at the asset form. No behavior change; both forms remain supported. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaMepbxRYzJxQKFtu6Hq4p Signed-off-by: F.N. Claessen <felix@seita.nl> --------- Signed-off-by: F.N. Claessen <felix@seita.nl> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Description
Adds support for intermediate power constraints: power limits on the aggregate flow of a group of devices, sitting between per-device power capacities and site-level (EMS) constraints. Closes #2092 (a hybrid inverter whose battery + PV children could jointly exceed the inverter's limit) and implements the flex-model-centric design from the discussion #1418 comment. Includes #2277.
Design
Device entries in a multi-device storage flex-model reference a group; the group's own flex-model entry constrains the group's aggregate power:
[ {"sensor": 1, "power-capacity": "2 kW", "group": {"sensor": 5}}, {"sensor": 2, "production-capacity": "2 kW", "consumption-capacity": "0 kW", "group": {"sensor": 5}}, {"sensor": 5, "power-capacity": "2.5 kW"} ]power-capacityon the group entry → hard constraint (both directions).consumption-capacity/production-capacity→ soft constraints, enforced via breach commitments with default breach prices (10000 currency/kW; no user-set group prices — consistent with the smart-defaults policy in Smarter commodity-context defaults and Commitment commodity field #2272).{"sensor": <id>}, shown above) or by asset ({"asset": <id>}). The asset variant is what DB-stored flex-models naturally produce, making the feature fully definable via an asset tree: store partial flex-models on the assets and trigger the parent with an empty flex-model.consumption/productionoutput sensors, following the standard conventions (consumption only → full profile consumption-positive; production only → full profile production-positive on save; both → clip-split into positive parts on each sensor). Either kind may define output sensors.Implementation
Built entirely on the existing device-group solver machinery — hard bounds via
ems_constraints/ems_constraint_groups(multi-commodity work, #1946) and soft bounds via groupedFlowCommitments (#1934). No optimizer changes.groupfield (sensor or asset reference, exactly one) onStorageFlexModelSchemaandDBStorageFlexModelSchema; sensor variant validated to reference a power sensor.MetaStorageScheduler._prepare: group entries split from device entries (validations: dangling refs, device-only fields on group entries, mixed commodities, cycles, single-sensor mode) and per-group hard/soft constraints built mirroring the site-level capacity/breach patterns.groupadded to the flex-model editor; the editor recommends the parent asset for a child'sgroupfield (one-click suggestion) and hints in both the parent's and children's editors when the parent's flex-model defines apower-capacity.Bug fixes along the way
_prepare: per-device lists were built from the full flex-model list (including stock-only entries) instead of the filtered device models.AssetTriggerSchema.check_flex_model_sensorsraisedKeyErroron asset-only flex-model entries.freeze_server_nowtest fixture leaked itsserver_nowmonkeypatch into all subsequently run tests in the same process, causing order-dependent duplicate-key failures when scheduling jobs saved beliefs with identical frozen belief times.Tests
test_group_constraints.py, 18 tests): the [FlexMeasures/Scheduler] Investigate Missing Parent-Child Physical Constraint Propagation #2092 inverter case with a control run proving the constraint binds, soft directional bounds + breach commitment costs, output-sensor saving in all three modes, nested/mixed-kind groups, cycle detection, validation errors, and a pure-DB-tree end-to-end test (all flex-models stored as asset attributes, scheduling triggered with an empty flex-model).🤖 Generated with Claude Code
https://claude.ai/code/session_01TAad46Ayg86DHpL6nY54sX