Commitments can be scoped to specific sensors, binding their aggregate flow - #2295
Conversation
…gregate flow A flex-context commitment gains an optional 'sensors' field: instead of binding each device of the matching commodity separately, the commitment binds the aggregate flow of the devices whose power sensors are listed, as one grouped commitment (device_group machinery). Useful to commit a band on a subset of devices, e.g. an aFRR upward-regulation band on a site's e-heaters (aggregate consumption >= band, deviation penalized). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
Documentation build overview
87 files changed ·
|
|
I think this would also close #2179, as this essentially adds support for an activation price per device, if you model the commitment with a zero baseline, a positive upwards deviation price and a negative downward deviation price. But if that is the case, then I suggest an additional test (making sure that the problem remains convex) and extending the documentation accordingly. |
Adapt the sensor-scoped commitment branch to the typed DeviceInventory introduced in PR #2321: resolve the scoped devices' canonical solver indices via inventory.by_sensor_id() instead of re-enumerating the raw flex-model entry list. 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>
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 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>
…mmitments Resolves conflicts in CommitmentSchema (keep the new `sensors` field alongside main's `name` length validation and updated commodity comment) and test_commitments.py (keep both new tests). 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 Integrates #2358 (now on main), whose CommitmentSchema/scheduling changes auto-merge cleanly with the sensor-scoped commitment work; only changelog.rst needed resolving (keep both entries). 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>
Follow-up on #2295. A commitment's scope can now be given as a 'group' reference (the members of an electrical group, reusing group_to_devices) in addition to the raw 'sensors' list; at most one of the two (schema-validated). A group scope includes the group's inflexible members (total node flow), whereas a sensors scope binds the listed flexible devices only -- documented on the schema field and the resolver. Also: validate that a scoped set shares one commodity, tag scoped commitments with provenance='custom' (was missing), and give each a unique device_group label so same-named commitments never 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>
|
Pushed the recommended follow-ups (on top of a merge with current main, which includes #2374):
Tests: group-scope binding (end-to-end), the |
There was a problem hiding this comment.
Pull request overview
Adds optional scoping for flex-context commitments so they can bind the aggregate flow of a selected subset of devices (via a sensors list or a group reference), reusing existing FlowCommitment.device_group aggregation behavior in the planning solver.
Changes:
- Extend the scheduling Commitment schema and OpenAPI to accept
sensorsandgroupas optional, mutually exclusive commitment scope selectors. - Update
StorageScheduler.convert_to_commitmentsto build a single aggregate-flowFlowCommitmentwhen a scope is provided, with warning-and-skip behavior when the scope matches no devices. - Add tests covering sensor-scoped and group-scoped aggregate-flow commitments, plus schema validation for mutual exclusivity.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| flexmeasures/ui/static/openapi-specs.json | Documents new sensors / group commitment fields and (re)positions GroupReference schema. |
| flexmeasures/data/schemas/scheduling/init.py | Adds sensors and group fields to CommitmentSchema and validates mutual exclusivity. |
| flexmeasures/data/models/planning/tests/test_commitments.py | Adds unit tests asserting aggregate binding behavior for sensor- and group-scoped commitments. |
| flexmeasures/data/models/planning/storage.py | Implements scoped commitment resolution and construction of aggregate-flow FlowCommitments. |
| documentation/changelog.rst | Adds a main changelog entry describing the new scoped-commitment capability. |
… + comment tidy Addresses Copilot's review on #2295: - A scoped commitment now takes its commodity from the (single) commodity of its scoped devices, overriding the schema's electricity default, so its cost is not misattributed to the wrong commodity. - Add tests for the commodity pinning and for the 'scope matches no device -> warn and bind nothing' path. - Fix the sensor-scoped test docstring (it mentioned a battery the test never had). - Reflow the CommitmentSchema scoping comment to break only after punctuation. 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.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (6)
flexmeasures/data/models/planning/tests/test_commitments.py:2201
- This new test docstring breaks lines mid-sentence (e.g. after "that"), which goes against the repo convention to only break lines after punctuation (see .github/instructions/docstrings.instructions.md:38). Please rewrap so each line ends at punctuation.
"""A commitment scoped to a ``group`` reference binds the aggregate flow of that
group's members, reusing the group's resolved membership -- the same band effect as
listing the members' sensors, but pointing at the group instead."""
flexmeasures/data/models/planning/tests/test_commitments.py:2276
- This new test docstring introduces mid-sentence line breaks, which conflicts with the repo's docstring/comment wrapping convention (line breaks only after punctuation; see .github/instructions/docstrings.instructions.md:38). Rewrap the docstring so each line ends with punctuation.
"""A scoped commitment's commodity follows its scoped devices, overriding the
schema's electricity default -- so its cost is attributed to the right commodity."""
flexmeasures/data/models/planning/tests/test_commitments.py:2323
- This new test docstring breaks a sentence across lines without punctuation at the line break, which conflicts with the repo convention (see .github/instructions/docstrings.instructions.md:38). Rewrap so each line break happens after punctuation.
"""A scope that matches no device in the flex-model logs a warning and binds
nothing, rather than failing the whole schedule."""
flexmeasures/data/models/planning/storage.py:1698
- The new scoped-commitment comment block introduces mid-sentence line breaks, which conflicts with the repo convention to only break lines after punctuation (see .github/instructions/docstrings.instructions.md:38). Rewrapping improves searchability and keeps diffs stable.
# A commitment scoped to a subset of devices binds the *aggregate* flow of
# those devices as one commitment, rather than each device separately. The
# scope is given either as a raw list of power `sensors` (a cherry-pick that
# may span electrical groups, e.g. an aFRR band on a site's e-heaters) or as
# a `group` reference (the members of an electrical group, reusing its
flexmeasures/data/models/planning/storage.py:1559
- This docstring paragraph has several mid-sentence line breaks (e.g. after "the"), which goes against the repo's docstring/comment wrapping convention (line breaks only after punctuation; see .github/instructions/docstrings.instructions.md:38). Rewrap these sentences so each line ends with punctuation.
This issue also appears on line 1694 of the same file.
A ``group`` scope yields the group's (leaf) members, which -- unlike the
by-sensor scope -- include any inflexible members, so the aggregate covers the
node's total flow (fixed load included). A ``sensors`` scope yields the listed
*flexible* devices (``by_sensor_id`` returns schedulable devices only, so
inflexible devices are not bound by a sensor scope). Canonical indices always
flexmeasures/data/models/planning/tests/test_commitments.py:2093
- This new test docstring uses mid-sentence line breaks, which conflicts with the repo convention to only break lines after punctuation (see .github/instructions/docstrings.instructions.md:38). Rewrapping into full sentences per line will make future diffs and text search cleaner.
This issue also appears in the following locations of the same file:
- line 2199
- line 2275
- line 2322
"""A commitment scoped to specific sensors (here: two e-heaters) binds their
aggregate flow as one commitment: a baseline of 10 MW with a steep penalty on
downward deviation keeps their combined consumption at 10 MW even though a
cheaper allocation (0 MW) exists.
"""
…ent net signed flow A commitment's 'sensors' scope now resolves through a new inventory helper, scheduled_devices_by_sensor_id, that includes inflexible (fixed-power) devices -- not just flexible ones (by_sensor_id stays flexible-only for its other uses). So a sensor scope and a group scope handle inflexible members the same way, and listing a group's member sensors binds the same set as scoping by that group. Also document that a scoped commitment binds the net *signed* aggregate flow (consumption positive, production negative), so consumers add, producers subtract, and any fixed member contributes its fixed signed power -- on the schema field, the resolver docstring and the changelog. 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.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (4)
flexmeasures/data/models/planning/storage.py:1704
- Inline comment wrapping breaks lines mid-phrase (e.g., after "of", "The", "that"), which conflicts with the repo guideline to only break comment/docstring lines after punctuation for readability and searchability. Please reflow this comment to wrap only after punctuation (preferably after full sentences).
# A commitment scoped to a subset of devices binds the *aggregate* flow of
# those devices as one commitment, rather than each device separately. The
# scope is given either as a raw list of power `sensors` (a cherry-pick that
# may span electrical groups, e.g. an aFRR band on a site's e-heaters) or as
# a `group` reference (the members of an electrical group, reusing its
flexmeasures/data/models/planning/tests/test_device_inventory.py:215
- This docstring is wrapped mid-sentence (line break after "whereas"), which goes against the repository guideline to only break docstring/comment lines after punctuation. Reflowing this makes the docstring easier to read and keeps future diffs smaller.
"""scheduled_devices_by_sensor_id returns flexible *and* inflexible devices, whereas
by_sensor_id returns flexible devices only."""
flexmeasures/data/models/planning/devices.py:632
- The new method docstring’s summary line is very long and the continuation line is wrapped mid-phrase (after "a"). This conflicts with the repo guideline to only break lines after punctuation in docstrings/comments; consider splitting into a short summary sentence plus a separate paragraph.
"""Return all devices in the optimization -- flexible *and* inflexible -- whose power sensor has the given id.
Unlike :meth:`by_sensor_id`, this includes inflexible (fixed-power) devices, so a
commitment scoped to a sensor list can bind an inflexible device's flow too.
"""
flexmeasures/data/models/planning/tests/test_commitments.py:2093
- This test docstring is wrapped mid-sentence (e.g., after "binds their" / "on"), which conflicts with the repo guideline to only break docstring/comment lines after punctuation. Reflowing into complete sentences improves readability and searchability.
"""A commitment scoped to specific sensors (here: two e-heaters) binds their
aggregate flow as one commitment: a baseline of 10 MW with a steep penalty on
downward deviation keeps their combined consumption at 10 MW even though a
cheaper allocation (0 MW) exists.
"""
… after punctuation Follows the repo docstring/comment convention (.github/instructions/docstrings.instructions.md): line breaks only after punctuation. Addresses the four docstring/comment findings Copilot suppressed on #2295, and the other mid-phrase breaks introduced alongside them. 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 the four docstring/comment findings from the last review that were suppressed (they don't appear as inline threads, only in the review's collapsed 'Suppressed comments'): reflowed all of them — plus the other mid-phrase breaks introduced alongside them — to break only after punctuation, per |
…hangelog Addresses two review comments on #2295: - Define 'band' (a committed power level the aggregate is held to by penalising deviation; here a floor, since only downward deviation is priced) in the sensor-scoped test docstring. - Reword the changelog to describe the final feature (a commitment binds the net signed aggregate of the scoped devices, flexible and inflexible alike), rather than the dev story of how the two scopes treat inflexible members. 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
A flex-context commitment gains an optional
sensorsfield: instead of binding each device of the matching commodity separately, the commitment binds the aggregate flow of the devices whose power sensors are listed, as one grouped commitment (using the existingdevice_groupmachinery ofFlowCommitment).Example — reserve an upward-regulation band on a site's e-heaters (their combined consumption must stay at/above the band, with downward deviation penalized):
{ "commitments": [ { "name": "reserved band", "sensors": [123, 124], "baseline": "10 MW", "down-price": "-10000 EUR/MWh" } ] }If none of the listed sensors appear in the flex-model, a warning is logged and the commitment binds nothing (rather than failing the whole schedule).
Look & Feel
New unit test
test_sensor_scoped_commitment_binds_aggregate_of_selected_devices: two 8 MW heaters that would otherwise stay off (they only cost money) are held at a combined 10 MW — a level neither device could carry alone — while the rest of the planning problem is unaffected.How to test
pytest flexmeasures/data/models/planning/tests/test_commitments.py -k scoped🤖 Generated with Claude Code
https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B