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>
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