Model inflexible devices as assets; drop the unreleased flex-context group field - #2374
Conversation
…t group field An inflexible (unschedulable) device can now be declared in a flex-model entry via a single inflexible-consumption/inflexible-production sensor reference (its own asset). Such a device joins a group through the ordinary flex-model 'group' field, exactly like a flexible member, so its fixed load counts towards the group's intermediate power constraint -- and it is gathered upward with the rest of the flex-model/asset tree, so no downward flex-context traversal is needed. This replaces the unreleased approach from #2358, which let a flex-context inflexible entry carry a 'group' field (removed here, together with the SensorReference.group carrier and the inventory plumbing behind it). The flat flex-context inflexible-consumption/production lists remain for site base load. InflexibleDeviceSchema moves to schemas/sensors.py so both the flex-context and the flex-model can use it without an import cycle. group_to_devices already folds inflexible members in, and FlexDevice.group_key reverts to reading the flex-model 'group' -- so a flex-model inflexible device's membership uses the normal path. 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>
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>
Documentation build overview
10 files changed ·
|
There was a problem hiding this comment.
🟡 Not ready to approve
The OpenAPI schema/examples for the new flex-model inflexible fields currently document an array example for fields that accept a single object, which can mislead API/UI users.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR updates FlexMeasures’ scheduling model to treat inflexible (unschedulable) devices as assets declared in the flex-model (via a single inflexible-consumption or inflexible-production sensor reference), and removes the previously introduced but unreleased flex-context inflexible group sub-feature. This unifies group membership semantics (everything joins groups via the normal flex-model group field) and simplifies device-inventory classification and solver ordering.
Changes:
- Add flex-model support for inflexible devices (
inflexible-consumption/inflexible-production) with schema-level validation to prevent ambiguous/invalid entries. - Update device inventory classification/enumeration so flex-model inflexible entries are indexed after flexible devices and before flat flex-context inflexibles, and so only flex-model entries can participate in groups.
- Update docs/tests/OpenAPI to reflect the new modeling approach and the removal of the flex-context inflexible
groupcarrier.
File summaries
| File | Description |
|---|---|
| flexmeasures/ui/static/openapi-specs.json | Updates generated OpenAPI schema for removed flex-context group support and added flex-model inflexible fields (but currently contains misleading examples for these new fields). |
| flexmeasures/data/schemas/tests/test_scheduling.py | Replaces flex-context-group validation test with flex-model inflexible-field validation tests. |
| flexmeasures/data/schemas/sensors.py | Moves InflexibleDeviceSchema into a shared location and removes SensorReference.group. |
| flexmeasures/data/schemas/scheduling/storage.py | Adds flex-model fields for inflexible devices plus schema validation to enforce correct/unambiguous declarations. |
| flexmeasures/data/schemas/scheduling/metadata.py | Updates descriptions of inflexible fields to reflect “list in flex-context vs single ref in flex-model entry”. |
| flexmeasures/data/schemas/scheduling/init.py | Removes old scheduling-local InflexibleDeviceSchema (with group) and updates UI schema metadata for inflexible fields. |
| flexmeasures/data/models/planning/tests/test_group_constraints.py | Updates group-constraint test to model an inflexible load as its own flex-model entry/asset and join group via normal group. |
| flexmeasures/data/models/planning/tests/test_device_inventory.py | Updates inventory tests to cover new inflexible classification, ordering, and group membership semantics. |
| flexmeasures/data/models/planning/devices.py | Implements flex-model inflexible-entry classification and updates group membership resolution (flex-model only). |
| documentation/features/scheduling.rst | Documents flex-model inflexible fields and explains new approach for inflexible devices participating in groups. |
| documentation/changelog.rst | Adds changelog entry for modeling inflexible devices as assets and removes mention of unreleased flex-context group-on-inflexible feature. |
Review details
Suppressed comments (3)
flexmeasures/data/schemas/scheduling/storage.py:381
- The
inflexible-productionfield on a flex-model entry is a single nested reference, but it reusesmetadata.INFLEXIBLE_PRODUCTIONwhoseexampleis a list; this makes the generated OpenAPI/Docs misleading (array vs. object).
inflexible_production = fields.Nested(
InflexibleDeviceSchema,
data_key="inflexible-production",
required=False,
metadata=metadata.INFLEXIBLE_PRODUCTION.to_dict(),
flexmeasures/data/schemas/scheduling/storage.py:664
- Same issue as above for DB-stored flex-model entries:
inflexible-productionis a single nested object, but the reused metadata example is a list, which misdocuments the expected input shape.
inflexible_production = fields.Nested(
InflexibleDeviceSchema,
data_key="inflexible-production",
required=False,
metadata=metadata.INFLEXIBLE_PRODUCTION.to_dict(),
flexmeasures/ui/static/openapi-specs.json:6870
- In the generated OpenAPI schema for
StorageFlexModelSchemaOpenAPI,inflexible-productionis a single object ($reftoInflexibleDevice), but the example is an array. This is inconsistent with the actual schema and may mislead API/UI users.
"example": [
{
"sensor": 3
},
{
- Files reviewed: 11/11 changed files
- Comments generated: 3
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| inflexible_consumption = fields.Nested( | ||
| InflexibleDeviceSchema, | ||
| data_key="inflexible-consumption", | ||
| required=False, | ||
| metadata=metadata.INFLEXIBLE_CONSUMPTION.to_dict(), | ||
| ) |
There was a problem hiding this comment.
Fixed in c4de6dd: the flex-model inflexible-consumption/inflexible-production fields now override the shared metadata's list example with a single object ({"sensor": 3}), so the generated OpenAPI documents an object ($ref InflexibleDevice), not an array. The flex-context list fields keep their array example.
| inflexible_consumption = fields.Nested( | ||
| InflexibleDeviceSchema, | ||
| data_key="inflexible-consumption", | ||
| required=False, | ||
| metadata=metadata.INFLEXIBLE_CONSUMPTION.to_dict(), | ||
| ) |
There was a problem hiding this comment.
Fixed in c4de6dd: the flex-model inflexible-consumption/inflexible-production fields now override the shared metadata's list example with a single object ({"sensor": 3}), so the generated OpenAPI documents an object ($ref InflexibleDevice), not an array. The flex-context list fields keep their array example.
The flex-model inflexible-consumption/inflexible-production fields are a single sensor reference, but reused the shared metadata whose example is a list (that form fits the flex-context list fields). Override the example to a single object on the flex-model declarations so the generated OpenAPI no longer shows an array for an object-valued field. Addresses Copilot's review on #2374. 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>
There was a problem hiding this comment.
🟡 Not ready to approve
The new validation intended to forbid schedulable-device fields on inflexible flex-model entries currently misses at least soc-unit, allowing contradictory inputs to pass.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
flexmeasures/data/schemas/scheduling/storage.py:57
validate_inflexible_flex_model_entryis meant to reject any schedulable-device fields on an inflexible flex-model entry, but_SCHEDULABLE_DEVICE_DATA_KEYScurrently omitssoc-unit. This allows inputs like{ "inflexible-consumption": {…}, "soc-unit": "kWh" }to pass validation even though the entry is still treated as inflexible, which contradicts the stated validation rules and can lead to silently ignored fields.
"soc-maxima",
"soc-targets",
"soc-gain",
"soc-usage",
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
…ation The schedulable-field check used a hand-maintained blacklist that missed at least soc-unit (Copilot review on #2374), so a contradictory entry like {inflexible-consumption, soc-unit} slipped through. Replace it with a whitelist of the keys allowed alongside an inflexible declaration (identity/group/commodity and the defaulted activation preferences), rejecting every other declared field. This stays complete as new device fields are added. Adds a soc-unit test guard. 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>
|
Addressed Copilot's second-round finding in |
There was a problem hiding this comment.
🟡 Not ready to approve
A small docstring-formatting fix is needed and the new “source-filtered inflexible device in flex-model” path lacks a dedicated regression test.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
flexmeasures/data/schemas/sensors.py:1060
- The new
InflexibleDeviceSchemadocstring introduces line breaks mid-sentence (e.g. breaking after “in a”), which conflicts with the repository docstring/comment convention to break lines only after punctuation for readability and easier searching.
"""One inflexible device: a sensor reference with optional source filters.
Used both in the flex-context (as a list, for site-level inflexible load) and in a
flex-model entry (as a single reference, when an inflexible device is modelled as
its own asset). Deserializes to a plain :class:`Sensor` when no source filters are
given (a backward-compatible shape downstream) and to a :class:`SensorReference`
otherwise.
"""
flexmeasures/data/models/planning/devices.py:343
- Support for source-filtered inflexible devices in the flex-model is implemented by accepting SensorReference-like objects (
entry_ref.sensor) here, but there is no test covering that path. Adding a regression test would ensure source filters are preserved onFlexDevice.sensor_referencefor flex-model-declared inflexible entries (not just the flat flex-context list).
# Tolerate SensorReference-like objects (with a `.sensor`) and plain Sensors.
reference_sensor = getattr(entry_ref, "sensor", None)
if reference_sensor is not None:
sensor, sensor_reference = reference_sensor, entry_ref
else:
sensor, sensor_reference = entry_ref, None
return FlexDevice(
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
…ltered flex-model inflexible Addresses Copilot's third-round review on #2374: - Reflow the new docstrings (InflexibleDeviceSchema and the inflexible-device helpers/validator) to break only after punctuation, matching the repo docstring convention and the surrounding code. - Add a device-inventory regression test that a flex-model inflexible entry given as a source-filtered SensorReference keeps the reference on FlexDevice.sensor_reference (so source filters reach the solver's power lookup). 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>
|
Addressed round 3 in the latest commit: reflowed the new docstrings to break only after punctuation (matching the repo convention / surrounding code), and added a device-inventory regression test that a flex-model inflexible entry given as a source-filtered |
There was a problem hiding this comment.
🟡 Not ready to approve
A single-sensor flex-model dict containing inflexible-consumption/inflexible-production can be accepted by schema deserialization but will not be classified as an inflexible entry in DeviceInventory, risking silent misconfiguration.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
flexmeasures/data/models/planning/devices.py:448
- In single-sensor mode (
flex_modelis a dict), flex-model-declared inflexible devices are not classified (_register_flex_model_inflexibleis a no-op), but the schema layer can still deserialize a dict that only containsinflexible-consumption/inflexible-production. That would currently be treated as a schedulable DEVICE entry (using the targetsensor) rather than being rejected, which can silently accept an invalid configuration.
# Collect the group keys referenced by flex-model entries' "group" fields; the
# entries whose own sensor/asset matches a referenced key are classified as group
# entries below. Flex-model-sourced inflexible devices carry their "group" the
# same way, so this one scan covers them too.
inventory.referenced_group_keys = _collect_referenced_group_keys(
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Addresses Copilot's fourth-round review on #2374: a single-sensor flex-model dict containing inflexible-consumption/inflexible-production was a no-op in classification, so it would be silently scheduled as a normal device. Now raise 'only supported in multi-device flex-models' (mirroring the group field), and add a regression test. 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>
|
Addressed round 4: a single-sensor flex-model declaring |
There was a problem hiding this comment.
🟡 Human review recommended
It changes core scheduling/device-inventory classification and grouping semantics, so a final human review should confirm no subtle regressions beyond the updated tests and docs.
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
|
||
| * New ``inflexible-consumption`` and ``inflexible-production`` flex-context fields make explicit how the sign of each inflexible device's power data should be read (positive values denote consumption resp. production), accepting sensor references with optional source filters; they replace the now-deprecated ``inflexible-device-sensors`` field (bare sensor IDs, sign read from each sensor's ``consumption_is_positive`` attribute), which remains supported. Each inflexible device may also carry a ``group`` field, so that inflexible (measured) load counts towards the intermediate power constraint of the group it belongs to [see `PR #2358 <https://www.github.com/FlexMeasures/flexmeasures/pull/2358>`_] | ||
| * New ``inflexible-consumption`` and ``inflexible-production`` flex-context fields make explicit how the sign of each inflexible device's power data should be read (positive values denote consumption resp. production), accepting sensor references with optional source filters; they replace the now-deprecated ``inflexible-device-sensors`` field (bare sensor IDs, sign read from each sensor's ``consumption_is_positive`` attribute), which remains supported [see `PR #2358 <https://www.github.com/FlexMeasures/flexmeasures/pull/2358>`_] | ||
| * An inflexible (unschedulable) device can be modelled as its own asset by giving its flex-model entry a single ``inflexible-consumption`` or ``inflexible-production`` sensor reference; such a device joins a ``group`` like any other member, so its fixed (measured) load counts towards the group's intermediate power constraint [see `PR #2374 <https://www.github.com/FlexMeasures/flexmeasures/pull/2374>`_] |
There was a problem hiding this comment.
| * An inflexible (unschedulable) device can be modelled as its own asset by giving its flex-model entry a single ``inflexible-consumption`` or ``inflexible-production`` sensor reference; such a device joins a ``group`` like any other member, so its fixed (measured) load counts towards the group's intermediate power constraint [see `PR #2374 <https://www.github.com/FlexMeasures/flexmeasures/pull/2374>`_] | |
| * An inflexible (unschedulable) device can be modelled as its own asset by giving its flex-model entry a single ``inflexible-consumption`` or ``inflexible-production`` sensor reference; such a device can be assigned to a ``group`` like any other member, so its fixed (measured) load counts towards the group's intermediate power constraint [see `PR #2374 <https://www.github.com/FlexMeasures/flexmeasures/pull/2374>`_] |
…e, and it may set a commodity Addresses review questions on #2374: without a group, a flex-model inflexible device is simply accounted for under the grid connection (like the flex-context list, only declared on the asset); with a group it additionally counts towards that group's constraint. It may also set a commodity like any device entry. 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>
…vice The flex-model inflexible tests all used the default (electricity) commodity; only the flat flex-context lists had per-commodity coverage. Add a test that a flex-model inflexible entry with commodity=gas is classified with that commodity and joins the gas device group (not electricity). 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>
…mmitments Brings in #2374 (inflexible devices as flex-model assets); clean auto-merge. 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>
Brings in #2295 (sensor/group-scoped commitments), #2358 (inflexible-consumption / inflexible-production replacing inflexible-device-sensors), #2374 (inflexible devices as assets), #2278 (operation-mode power bands) and #2306 (rate limiting). Conflict resolutions of note: * schemas/scheduling/storage.py -- main moved GroupReferenceSchema into the new schemas/scheduling/groups.py and storage.py now imports it, so the branch's local copy is dropped rather than merged. Kept _validate_coupling_name (still used by both flex-model schemas) alongside main's new validate_inflexible_flex_model_entry. * linear_optimization.py -- coupling groups (this branch) and operation-mode power bands (main) are independent features; both kept. * devices.py -- FlexDevice gains main's inflexible-device fields next to this branch's coupling fields. * storage.py -- the device_scheduler call passes both coupling_groups and device_power_bands. flexmeasures/data/models/planning + flexmeasures/data/schemas: 579 passed, 3 xfailed. black and flake8 clean. Note: #2306 imports `limits`, which is resolved via uv.lock but not declared in pyproject.toml, so existing venvs need `uv sync --all-groups`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEvWAj45zXaod5WjniF81D Signed-off-by: F.N. Claessen <felix@seita.nl>
Picks up feat/chp's merge of origin/main (#2295 sensor/group-scoped commitments, #2358 inflexible-consumption/-production, #2374 inflexible devices as assets, #2278 operation-mode power bands, #2306 rate limiting). Two conflicts, both additive: this branch's internal-node balance groups and feat/chp's operation-mode power bands are independent features. The device_scheduler call now passes all three feature arguments side by side: coupling_groups (converters), balance_groups (internal commodity nodes) and device_power_bands (operation modes). flexmeasures/data/models/planning + flexmeasures/data/schemas: 586 passed, 3 xfailed. black and flake8 clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEvWAj45zXaod5WjniF81D Signed-off-by: F.N. Claessen <felix@seita.nl>
Description
Changes how inflexible (unschedulable) devices are modelled, and removes an unreleased sub-feature of #2358.
An inflexible device can now be declared in a flex-model entry (as its own asset) by giving it a single
inflexible-consumptionorinflexible-productionsensor reference (the field name sets the sign; source filters are supported). Such an entry carries no schedulable-device fields; it simply declares a fixed device whose power is accounted for. It joins a group through the ordinary flex-modelgroupfield, exactly like a flexible member — so:The flat
inflexible-consumption/inflexible-productionlists in the flex-context remain, for plain site base load (no group).Removed (unreleased, from #2358)
groupfield on flex-context inflexible entries, theSensorReference.groupcarrier, and the device-inventory plumbing behind it (_collect_inflexible_referenced_group_keys,FlexDevice.group_reference, the inflexible branch ofgroup_key).group_to_devicesstill folds inflexible members in, andby_indexstays — both are reused by the new model.Notable
InflexibleDeviceSchemamoves toschemas/sensors.pyso both the flex-context and the flex-model can use it without an import cycle.consumption_is_positivecontradicts the field, and co-existing schedulable-device fields.How to test
pytest flexmeasures/data/models/planning/tests/test_device_inventory.py(classification + ordering + group membership)pytest flexmeasures/data/models/planning/tests/test_group_constraints.py -k inflexible(an inflexible load in a group caps the battery end-to-end)pytest flexmeasures/data/schemas/tests/test_scheduling.py -k "inflexible or storage_flex_model"(schema validation)Note:
test_battery_storage_with_time_series_in_flex_modelfails on this branch and on cleanmainin this environment (a price-fixture/shared-DB artifact,UnknownPricesException); unrelated to this change.Related
group-on-inflexible sub-feature of Replace flex-context field inflexible-device-sensors with inflexible-consumption and inflexible-production #2358 (unreleased). Part of the Support intermediate power constraints on groups of devices (flex-model group field) #2276 group work (point 5 of that discussion).🤖 Generated with Claude Code