Skip to content

Commitments can be scoped to specific sensors, binding their aggregate flow - #2295

Merged
Flix6x merged 12 commits into
mainfrom
feat/sensor-scoped-commitments
Aug 1, 2026
Merged

Commitments can be scoped to specific sensors, binding their aggregate flow#2295
Flix6x merged 12 commits into
mainfrom
feat/sensor-scoped-commitments

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 11, 2026

Copy link
Copy Markdown
Member

Description

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 (using the existing device_group machinery of FlowCommitment).

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

…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

@Flix6x Flix6x left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only wish to clear up some documentation. And one clarification request.

The test is correct, but would be slightly stronger if the statement about 0 consumption being preferred without the extra commitment would be proven.

Comment thread flexmeasures/data/models/planning/storage.py Outdated
Comment thread flexmeasures/data/models/planning/tests/test_commitments.py
@Flix6x

Flix6x commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

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.

Flix6x and others added 7 commits July 16, 2026 09:19
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>
@Flix6x

Flix6x commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

Pushed the recommended follow-ups (on top of a merge with current main, which includes #2374):

  • Optional group scope on a commitment, alongside the existing sensors list — at most one of the two (schema-validated). A group reference reuses the group's resolved membership (group_to_devices), so you don't restate the member sensors; a sensors list stays the general cherry-pick form that can span electrical groups. No multi-group membership is introduced — the two are different relationships (topology vs. market selection).
  • Inflexible-inclusion semantics documented: a sensors scope binds the listed flexible devices only (by_sensor_id), while a group scope includes the group's inflexible members (the node's total flow). Spelled out on the schema field and _resolve_commitment_scope.
  • Commodity-share validation: a scoped set spanning more than one commodity is rejected (a commitment binds the aggregate flow of a single commodity).
  • Robustness: scoped commitments now carry provenance='custom' (was missing, so they weren't tagged for cost reporting), and each gets a unique device_group label so two same-named scoped commitments can't merge.

Tests: group-scope binding (end-to-end), the sensors/group mutual-exclusion, plus the existing 22 commitment tests still green. Changelog updated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 sensors and group as optional, mutually exclusive commitment scope selectors.
  • Update StorageScheduler.convert_to_commitments to build a single aggregate-flow FlowCommitment when 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 thread flexmeasures/data/models/planning/storage.py
Comment thread flexmeasures/data/models/planning/tests/test_commitments.py Outdated
Comment thread flexmeasures/data/models/planning/storage.py
Comment thread flexmeasures/data/schemas/scheduling/__init__.py Outdated
… + 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Flix6x commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

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 .github/instructions/docstrings.instructions.md (commit 4e36ef5). Separately following up on the suppression itself so these surface in future reviews.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Comment thread documentation/changelog.rst Outdated
…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>
@Flix6x
Flix6x merged commit fcbf579 into main Aug 1, 2026
12 of 13 checks passed
@Flix6x
Flix6x deleted the feat/sensor-scoped-commitments branch August 1, 2026 18:28
Flix6x added a commit that referenced this pull request Aug 2, 2026
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>
Flix6x added a commit that referenced this pull request Aug 2, 2026
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>
@Flix6x Flix6x self-assigned this Aug 3, 2026
@Flix6x Flix6x added this to the 1.0.0 milestone Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants