Skip to content

Regular (unscoped) commitments bind each device individually instead of the commodity aggregate (regression from #1946) #2379

Description

@Flix6x

Summary

A regular (non-scoped) flex-context commitment now binds each device individually against its baseline, instead of binding the aggregate flow of all the commodity's devices. This appears to be an unreleased regression introduced by #1946 (multi-commodity support), and it contradicts the original intent (commitments are electricity/commodity-scoped aggregate commitments).

Surfaced while reviewing #2295: #2295 (comment)

Evidence (git bisect of StorageScheduler.convert_to_commitments)

Before #1946 (003c145c5^) — one EMS-level commitment per spec, device defaulting to None, i.e. aggregated over all devices:

for commitment_spec in commitment_specs:
    # ... convert baseline / prices ...
    commitments.append(FlowCommitment(**commitment_spec))   # device=None -> EMS-level aggregate

After #1946 (current main) — one commitment per device:

for d, flex_model_d in enumerate(flex_model):
    device_commodity = flex_model_d.get("commodity", "electricity")
    if device_commodity != commitment_commodity:
        continue
    commitment = FlowCommitment(
        device=d,                        # <-- single device
        device_group=device_commodity,
        provenance="custom",
        **commitment_spec,
    )
    commitments.append(commitment)

convert_commitments_to_subcommitments then enumerates each of these as its own commitment c, whose deviation constraint sums ems_power over just {d} — so each device's own flow is held to the baseline, with its own deviation penalty.

Note that #1946's internal commitment does aggregate correctly:

FlowCommitment(name=f"{commodity} net energy", device=commodity_devices, device_group=commodity, ...)  # device = list of all commodity devices

So user commitments should follow the same shape.

Impact

  • A commitment baseline: 10 MW on a commodity with N devices currently penalises each device for deviating from 10 MW, rather than penalising the sum of the N devices deviating from 10 MW. These give very different schedules (and costs) once a commodity has more than one device.
  • Unreleased: Add multi‑commodity support to device scheduler #1946 is not in any tagged release (latest release v0.33.1 predates it), so this can be fixed on main/v1.0.0.dev without a released-behaviour migration.

Suggested fix

In convert_to_commitments, bind one FlowCommitment per (commitment, commodity) over all of that commodity's devices, mirroring the internal net-energy commitment:

device=<all device indices of commitment_commodity>,
device_group=commitment_commodity,

(Canonical indices from self.device_inventory, e.g. commodity_to_devices.) The current per-device warning-when-empty behaviour still applies when a commodity has no devices.

Test gap

test_commitment_commodity_does_not_bind_other_commodity_devices and test_commitments_in_commodity_contexts_are_converted only use one device per commodity, so they pass under both the per-device and the aggregate implementations. Add a case with two+ devices of the same commodity and assert the sum is bound (mirroring the aggregate assertion in #2295's test_sensor_scoped_commitment_binds_aggregate_of_selected_devices).

Relation to #2295

#2295 adds explicit scoped (subset) aggregate commitments. Fixing this regression makes an unscoped commitment aggregate over its whole commodity again; the sensors/group scopes remain the way to aggregate over a chosen subset.

🤖 Filed with Claude Code after a review question from @Flix6x on #2295.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions