Skip to content

Smarter commodity-context defaults and Commitment commodity field#2272

Open
Flix6x wants to merge 166 commits into
mainfrom
feat/commodity-context-defaults
Open

Smarter commodity-context defaults and Commitment commodity field#2272
Flix6x wants to merge 166 commits into
mainfrom
feat/commodity-context-defaults

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 8, 2026

Copy link
Copy Markdown
Member

Description

New in-scope features for the multi-commodity flex-context (follow-up to #2172, stacked on #2271) ahead of the v1 release.

Commitment schema commodity field

  • CommitmentSchema gets an undocumented commodity field, defaulting to electricity, for internal bookkeeping — matching a commitment to the device(s) sharing that commodity. Not the documented way for API users to associate a commitment with a commodity; that remains the multi-commodity commodities list, one context per commodity.

Smarter defaults for commodity contexts

A commodity context that omits grid-connection fields now gets sensible defaults instead of raising or leaving the grid unconstrained, based on which fields are explicitly given:

  • Nothing given (e.g. bare {"commodity": "gas"}, no grid connection): zero site-consumption-capacity and zero site-production-capacity as soft constraints, unlimited site-power-capacity.
  • Only consumption-price given: unlimited site-power-capacity and site-consumption-capacity, 0 site-production-capacity.
  • Only production-price given: unlimited site-power-capacity and site-production-capacity, 0 site-consumption-capacity.
  • Only site-consumption-capacity given: unlimited site-power-capacity, that consumption capacity, 0 consumption-price, 0 site-production-capacity (and 0 production-price).
  • Only site-production-capacity given: unlimited site-power-capacity, that production capacity, 0 production-price, 0 site-consumption-capacity (and 0 consumption-price).
  • Only site-power-capacity given: hard constraint at that capacity; site-consumption/production-capacity equal to it; 0 consumption- and production-price.

A relax-constraints: False context that ends up with a smart-defaulted 0 hard capacity now logs a warning, since this can make the schedule infeasible.

Correctness fix (found while implementing the above)

  • A commodity context with no user-given price fields was stamped with a fallback "EUR" currency, which could spuriously trip cross-commodity currency-mismatch checks against a differently-currencied portfolio. Such price-free contexts are now tagged and excluded from the mismatch check; their fallback-currency fields get re-expressed under the portfolio's real currency once it's known (from a top-level price or a sibling commodity context).
  • StorageScheduler.convert_to_commitments now logs a warning when a commitment's commodity matches zero devices (aid for catching typos).

Regression tests

7 parametrized DB-free cases for the 6 smart-default cases (+ a multi-field combination); price-free-context currency handling (top-level price sets currency, sibling context sets currency, nothing given anywhere falls back to EUR, genuine mismatches still 422).

  • Added changelog item in documentation/changelog.rst

How to test

pytest flexmeasures/data/schemas/tests/test_scheduling.py flexmeasures/data/models/planning/tests/test_commitments.py

Related Items

Stacked on #2271; follow-up to #2172; prepares #1946 for release.

Flix6x and others added 30 commits December 5, 2025 16:50
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
…ontext

Signed-off-by: F.N. Claessen <felix@seita.nl>
…formulation

Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
…and map them to the respective group id

Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
…chemas

Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Flix6x added 13 commits July 8, 2026 07:53
An empty group could be created for a commodity that no device refers
to (e.g. the always-created electricity entry in an all-gas flex-model,
or a commodity context listed in the flex-context but unused by the
flex-model). Such groups demanded a consumption price and produced
empty-device commitments and EMS constraint groups, making the problem
unbounded. Skip them: they need no prices, commitments or constraints.

Also normalize inflexible_device_sensors to a list (tests bypassing the
schema may pass dict_values, which broke concatenation with the
per-commodity inflexible sensors), and fix the DataSource keyword
(type, not source_type) in the new per-commodity inflexible sensor
test.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…mantics

test_capacity, test_battery_power_capacity_as_sensor and
test_device_power_capacity_uses_directional_capacity_before_site_fallback
assert the hard constraint bounds (derivative min/max DataFrames) equal
the contracted capacities. With relax-constraints defaulting to True,
default breach prices are filled and the hard bound becomes the physical
capacity (contracted capacities are then only softly penalized), so
these tests must opt out explicitly.

Note the default breach prices (10000/kW site, 100/kW device, plus soc
prices) dwarf typical energy prices, so optimal schedules still respect
contracted capacities in ordinary price-arbitrage cases; the relaxation
only kicks in when the problem would otherwise be infeasible.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
The new FlexContextSchema.commodity field (single-dict form is
electricity-only) is deliberately not exposed in the UI, like the
relax-* developer fields.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Power sensors store consumption as negative values by default;
get_power_values flips the sign to the scheduler's consumption-positive
convention. The test wrote +8 kW beliefs, which entered the problem as
8 kW production, making the aggregate 1 - 8 = -7 kW instead of the
expected 1 + 8 = 9 kW. Record the load as -8 kW. The per-commodity
inflexible path in _prepare shares the exact same get_power_values loop
as the top-level path, so no production-code change is needed.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Wire an undocumented commodity field (defaulting to "electricity")
into CommitmentSchema, so a flex-context commitment only binds
devices of its own commodity. StorageScheduler.convert_to_commitments
already matched commitment and device commodities; this adds the
schema-level field so the commodity can actually be set on a
commitment spec.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
A commodities-list entry that omits some or all grid-connection
fields (consumption-price, production-price, site-consumption-
capacity, site-production-capacity, site-power-capacity) previously
either failed (a missing consumption-price crashes the scheduler) or
left ambiguous, error-prone combinations to fill in by hand. Add
CommodityFlexContextSchema.fill_grid_connection_defaults, a post-load
step that derives sensible defaults from which of those five fields
were explicitly given:

- none given: no grid connection (both site capacities default to 0,
  as soft constraints; site-power-capacity stays unlimited)
- only a price given: assume a grid connection in that direction
  (unlimited capacity there), 0 capacity in the other direction
- only a capacity given: 0 price in that direction, and the other
  direction defaults to a 0 capacity (and price)
- only site-power-capacity given: a hard constraint at that capacity
  for both directions, with 0 prices

Combinations of explicitly given fields apply the same rules per
direction independently. A 0 capacity filled in by this method is
enforced as a soft constraint (a default breach price is filled in
too), not a hard, potentially infeasible, one; explicit hard
constraints (e.g. via site-power-capacity alone) get no breach price.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
A commodity context with no user-given price fields (e.g. a bare
{"commodity": "gas"}) was stamped with a fallback "EUR" currency by
_try_to_convert_price_units, which then tripped the cross-context and
top-level/context currency checks against a differently-currencied
portfolio, even though the context had no real price constraint of its
own. Track when a context's shared_currency_unit is just a fallback,
skip such contexts in the currency comparisons, and let their
0-price/breach-price fills inherit the portfolio's real currency where
determinable. Also warn when a smart-defaulted 0-capacity soft
constraint ends up hard because relax-constraints was explicitly set
to False.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Helps catch a typo in a commitment's or device's commodity field; the
commitment is still silently skipped (no binding, no error), matching
the existing deferred-validation approach for commodity mismatches.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Flag explicitly that partially-specified commodity contexts used to
leave capacities unlimited and hard-error on a missing consumption
price, whereas they now get 0-capacity soft constraints and 0 prices;
and that price-free contexts no longer trip a spurious cross-currency
error against a differently-currencied portfolio.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Move the relax-constraints breaking-change entry to the top of its
section, and fold the PR #2271 hardening-fix summary into the existing
multi-commodity feature entry (appending the PR reference) instead of
listing it as a separate bugfix bullet.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…regate fields

Per self-review: these commented-out notes on aggregate-consumption
and aggregate-production are intentional placeholders for planned
future UI support, not dead code to remove.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
The field's comment previously read like it documented a supported way
to bind a commitment to a commodity. Clarify that it's internal
bookkeeping, and that the documented way to associate a commitment
with a commodity is to place it under the relevant entry of the
multi-commodity flex-context's commodities list.

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ext-defaults

# Conflicts:
#	documentation/changelog.rst
Signed-off-by: F.N. Claessen <claessen@seita.nl>

# Conflicts:
#	flexmeasures/data/models/planning/storage.py
#	flexmeasures/data/services/scheduling.py

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

Self-review.

Comment thread documentation/changelog.rst Outdated
Comment thread flexmeasures/data/schemas/scheduling/__init__.py Outdated
Comment thread flexmeasures/data/schemas/scheduling/__init__.py Outdated
Comment thread flexmeasures/data/schemas/scheduling/__init__.py Outdated
@Flix6x Flix6x self-assigned this Jul 8, 2026
@Flix6x Flix6x added this to the 1.0.0 milestone Jul 8, 2026
Copilot AI and others added 3 commits July 8, 2026 17:29
…2274)

Context:
- PR #2072 added scheduling_result output to StorageScheduler.compute()
  when return_multiple=True, and added _compute_unresolved_targets.
- In single-sensor mode (self.sensor is set), flex_model entries lack a
  "sensor" key, so _compute_unresolved_targets was skipping every device
  and returning empty unresolved/resolved lists.

Changes:
- storage.py: fall back to self.sensor in _compute_unresolved_targets
  when the flex_model entry has no "sensor" key (single-sensor mode)
- test_commitments.py: update schedule-count assertions (+1 for the new
  scheduling_result entry added by PR #2072)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
…nto feat/commodity-context-defaults

# Conflicts:
#	documentation/changelog.rst
Base automatically changed from fix/multi-commodity-hardening to main July 8, 2026 18:31
Flix6x added 3 commits July 8, 2026 20:39
…t-defaults

chore: update openapi-specs.json

Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
@Flix6x Flix6x requested a review from Ahmad-Wahid July 8, 2026 19:27
@Flix6x Flix6x added documentation Improvements or additions to documentation AI Infra labels Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Infra documentation Improvements or additions to documentation Scheduling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants