Commitments can be scoped to specific sensors, binding their aggregate flow - #2295
Commitments can be scoped to specific sensors, binding their aggregate flow#2295Flix6x wants to merge 9 commits into
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 ·
|
| commitment_commodity = commitment_spec.get("commodity", "electricity") | ||
|
|
||
| # A commitment scoped to specific sensors binds the *aggregate* flow | ||
| # of those devices as one commitment, rather than each device separately. |
There was a problem hiding this comment.
Is the regular commitment not aggregating over all devices (of one grid-connected commodity), rather than applied to each device individually?
| """ | ||
| heater_type = get_or_create_model(GenericAssetType, name="e-heater") | ||
| site = GenericAsset( | ||
| name="Band site (scoped commitment test)", generic_asset_type=heater_type |
There was a problem hiding this comment.
Explain the term "band" in the docstring.
|
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. |
| commodities = { | ||
| self.device_inventory.by_index(d).commodity for d in scoped_devices | ||
| } | ||
| if len(commodities) > 1: | ||
| raise ValueError( | ||
| f"Commitment '{commitment_spec.get('name')}' is scoped to devices of" | ||
| f" more than one commodity ({sorted(commodities)}); a commitment binds" | ||
| " the aggregate flow of a single commodity." | ||
| ) |
There was a problem hiding this comment.
Fixed in c35820a: _build_scoped_commitment now pins the commitment's commodity to the (single) commodity of its scoped devices, overriding the schema's electricity default, so cost attribution matches the scope. Added test_scoped_commitment_pins_commodity_to_scoped_devices.
| """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, while an unscoped battery stays unaffected. | ||
| """ |
There was a problem hiding this comment.
Fixed: removed the stray "unscoped battery" clause — that test only models the two heaters.
| if not scoped_devices: | ||
| current_app.logger.warning( | ||
| f"Commitment '{commitment_spec.get('name')}' is scoped to" | ||
| f" {scope_description}, none of which appear in the flex-model." | ||
| " This commitment will not bind any device." |
There was a problem hiding this comment.
Added test_scoped_commitment_with_no_matching_devices_warns_and_binds_nothing, which asserts the warning is logged and the scheduler binds nothing (returns no commitment) rather than failing.
| # Optional scoping: bind this commitment to the aggregate flow of a subset of | ||
| # devices, rather than binding each device of the commodity separately. Give either | ||
| # a list of power `sensors` (a cherry-pick that may span electrical groups, e.g. an | ||
| # aFRR band on a site's e-heaters) or a `group` reference (the members of an | ||
| # electrical group). At most one of the two. |
There was a problem hiding this comment.
Reflowed the comment to break only after punctuation, per the repo convention.
… + 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.
"""
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