Skip to content

Advisory: integration branch for the multi-commodity + commitments PR family#2301

Draft
Flix6x wants to merge 302 commits into
mainfrom
dev/multi-commodity-integration
Draft

Advisory: integration branch for the multi-commodity + commitments PR family#2301
Flix6x wants to merge 302 commits into
mainfrom
dev/multi-commodity-integration

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 12, 2026

Copy link
Copy Markdown
Member

Purpose — do not merge

This is a mock-main integration branch combining all open PRs of the multi-commodity/commitments family, opened to advise on merge order and cross-PR interactions. The branch: #2218 + #2289 + #2272 + #2295 + #2281 + #2284 + #2287 + #2285, plus two interaction fixes. The commitments planning suite passes on the combination (21/21).

Recommended merge order

  1. Combined Heat and Power (CHP) #2218 (coupling primitive) → Balance internal commodity nodes (heat/steam networks) via first-class balance groups #2289 (balance groups; stacked on Combined Heat and Power (CHP) #2218)
  2. Smarter commodity-context defaults and Commitment commodity field #2272 (commodity-context smart defaults) — independent of 1
  3. Commitments can be scoped to specific sensors, binding their aggregate flow #2295 (sensor-scoped commitments) — independent, applies cleanly to main
  4. UI stack: refactor(ui): drive flex-context field types from UI_FLEX_CONTEXT_SCHEMA #2281feat(ui): edit per-commodity flex-contexts through a commodity tab bar #2284feat(ui): edit flex-context commitments in the context modal #2287 (each stacked on the previous)
  5. fix: namespace user commitment names with a custom: prefix #2285 (namespace user commitment names) — independent, but see interaction (b)

Interactions found while integrating

a) #2272 × #2289: #2272's smart defaults fill in zero prices for bare commodity contexts, which defeats #2289's internal-node detection (priceless commodity → hard flow balance). Fixed here by recording a prices_are_defaulted flag in fill_grid_connection_defaults and consulting it in _prepare (commit c706b9d). Whichever lands second needs this as a follow-up.

b) #2285 × #2287: #2285 prefixes user commitment names with custom:; #2287's commodity-matching test asserts unprefixed names. One-line test adaptation needed in whichever lands second (last commit here).

c) #2289 base: the balance rule must sum plain ems_power, not stock-side terms (commit 892d6b0, already part of #2289).

d) Squash-merging stacked branches conflicts spuriously (#2289 re-applies #2218's hunks; #2287 carries #2284/#2281). Merging the stack tips (#2289, #2287) after their bases, or rebasing each on main after its base merges, avoids this.

Not included

🤖 Generated with Claude Code

https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B

Ahmad-Wahid and others added 30 commits March 4, 2026 23:30
…multi-feed-stock

# Conflicts:
#	flexmeasures/data/models/planning/tests/test_commitments.py
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>
Introduce stock_groups mapping to link multiple devices to a shared SOC.

Aggregate stock delta across devices sharing the same battery.

Update stock change calculation to use combined device flows.

Add device-to-group and group-to-devices lookup for efficient shared stock computation.

Signed-off-by: Ahmad-Wahid <ahmedwahid16101@gmail.com>
…er and discharge later, rather than on the EPEX price transition, as the inline test documentation advertised

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…ckCommitment to prefer being full

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

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>
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>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
…itment, using a tiny price slope to prefer a fuller SoC sooner rather than later, by lowering penalties later

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>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
This reverts commit bf16e63.

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

Signed-off-by: F.N. Claessen <claessen@seita.nl>
…eat/multi-commodity

# Conflicts:
#	documentation/concepts/commitments.rst
#	flexmeasures/data/models/planning/__init__.py
#	flexmeasures/data/models/planning/storage.py
Flix6x and others added 29 commits July 8, 2026 08:30
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
…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
…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>
…nstraint analysis #2072, CI/agents #2275)

Conflict resolution policy:
- Schema layer: main's design wins wholesale (commodities list nesting,
  commodity default 'electricity', uniqueness + shared-currency validators).
- Engine: main's #1946 multi-commodity device_scheduler plus this branch's
  coupling_groups primitive (coupling_alpha variables and per-device
  flow-coupling constraints), keeping the branch's group_to_devices mapping
  which supports overlapping stock-group membership.
- storage.py: main's _prepare/_build_soc_schedule structure wins, with the
  branch's coupling-group construction re-added; main's strict price-presence
  validation is restored (the branch had it commented out).
- Tests: kept both sides' distinct tests (branch: CHP coupling + factory
  dispatch; main: gas-only flex-model, per-commodity inflexible sensors,
  directional-capacity fallback).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
The sign of a coupling coefficient is inferred from directional capacities,
and a device with both directions open (or blocked) was silently treated as
an input. Reject such flex-models with a validation error instead.

Also promote the coupling field descriptions to MetaData constants and
document both fields in the storage flex-model table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
Adds a balance_groups argument to device_scheduler: each group lists the
devices of an internal commodity node (e.g. a heat or steam network without
a grid connection) whose stock-side flows must sum to zero at every time
step. This replaces the reference-device min=max=0 stock-group workaround
used by the factory scenario, which is now tested in both modes.

The StorageScheduler derives balance groups from the flex-config: a
non-electricity commodity without energy prices becomes an internal node
(previously this raised 'Missing consumption price'). Together with
coupling groups (one flex-model entry per converter port), this makes the
factory scenario (CHP + gas boiler + e-heater meeting a fixed steam demand)
schedulable end-to-end through StorageScheduler.compute().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
…t-defaults

# Conflicts:
#	.claude/agents/api-backward-compatibility-specialist.md
#	.claude/agents/architecture-domain-specialist.md
#	.claude/agents/coordinator.md
#	.claude/agents/data-time-semantics-specialist.md
#	.claude/agents/documentation-developer-experience-specialist.md
#	.claude/agents/performance-scalability-specialist.md
#	.claude/agents/test-specialist.md
#	.claude/agents/tooling-ci-specialist.md
#	.claude/agents/ui-specialist.md
- Give this PR its own concise changelog entry (pointing at the new
  commodity_context_defaults docs section) instead of only tagging the
  multi-commodity entry.
- Reword the smart-defaults docs per review: prefer 'zero' over '0',
  clarify that zero prices refer to the usage (energy) prices, adopt the
  suggested 'Then, ...' phrasing in the per-case bullets, and state that
  giving all capacity fields is valid (directional capacities soft within
  the hard site-power-capacity limit).
- Drop the redundant test-name prefix from a test docstring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
…l combo branch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
…efaults

The smart defaults (#2272) fill a zero consumption-price into price-free
commodity contexts, which defeated the priceless-commodity internal-node
detection (#2279). Record durably on the context whether any price field
was user-given (prices_are_defaulted), and treat a context whose prices
were all defaulted as an internal node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MLCUiSdXDqDBmg8GbYp1B
…terms

A device can sit in both a commodity balance group (via its commodity) and
a shared-stock group (via its state-of-charge sensor), e.g. a steamer that
discharges a heat buffer to produce steam. Its derivative efficiencies and
stock delta (e.g. the buffer's soc-usage losses assigned to it) describe
the stock-side conversion and must not leak into the commodity balance:
what crosses the node is the device's power flow (ems_power).

Found while running a realistic factory scenario, where the heat buffer's
soc-usage drain was distorting the steam balance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants