Skip to content

Feat: make soc-min and soc-max canonical with sensor defaults#2267

Open
BelhsanHmida wants to merge 29 commits into
feat/retire-fallback-schedulerfrom
feat/dynamic-soc-bounds-defaults
Open

Feat: make soc-min and soc-max canonical with sensor defaults#2267
BelhsanHmida wants to merge 29 commits into
feat/retire-fallback-schedulerfrom
feat/dynamic-soc-bounds-defaults

Conversation

@BelhsanHmida

@BelhsanHmida BelhsanHmida commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Description

  • Make soc-min and soc-max the canonical fields for fixed and dynamic storage SoC boundaries.
  • Allow dynamic soc-min / soc-max values through sensor references and time-series specifications.
  • Add a generic default field to sensor references for filling missing sensor values.
  • Keep soc-minima and soc-maxima supported as deprecated legacy aliases.
  • Reject ambiguous combinations of canonical and legacy dynamic fields.
  • Preserve backwards compatibility for fixed SoC bounds and fixed-plus-legacy configurations.
  • Make scalar soc-min / soc-max follow the default SoC relaxation behavior.
  • Reject default: null.
  • Update the API, UI, scheduling documentation, changelog, and OpenAPI specifications.
  • Allow configuring sensor-reference fallbacks through the flex-model UI
  • Add changelog entry

Look & Feel

N/A

Example API flex-model shape:

{
  "soc-at-start": "12 kWh",
  "soc-min": {
    "sensor": 300,
    "default": "10 kWh"
  },
  "soc-max": {
    "sensor": 301,
    "default": "25 kWh"
  },
  "power-capacity": "5 kW"
}

How to test

  • pytest:
uv run pytest \
  flexmeasures/data/schemas/tests/test_sensor.py \
  flexmeasures/data/schemas/tests/test_scheduling.py \
  flexmeasures/data/models/planning/tests/test_storage.py \
  flexmeasures/data/models/planning/tests/test_utils_fresh_db.py \
  tests/documentation/test_openapi_spec.py
  • Manual API-style check:
    Use a storage flex-model where soc-min and soc-max are dynamic sensor references with defaults:
{
  "flex-model": {
    "soc-at-start": "12 kWh",
    "soc-min": {
      "sensor": 300,
      "default": "10 kWh"
    },
    "soc-max": {
      "sensor": 301,
      "default": "25 kWh"
    },
    "power-capacity": "5 kW"
  }
}

Expected behavior: soc-min and soc-max are treated as dynamic SoC boundaries, missing sensor values are filled with the provided default values, default: null is rejected, and scalar soc-min / soc-max are soft by default when SoC relaxation is active.

Further Improvements

Related Items


Sign-off

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on code under GPL or other license that is incompatible with FlexMeasures

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
@BelhsanHmida BelhsanHmida self-assigned this Jul 6, 2026
@BelhsanHmida
BelhsanHmida changed the base branch from main to feat/retire-fallback-scheduler July 6, 2026 01:18
@BelhsanHmida
BelhsanHmida requested a review from Copilot July 6, 2026 07:38

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

This PR updates FlexMeasures’ storage flex-model schema to make soc-min / soc-max the canonical fields for both fixed and dynamic SoC boundaries, adds a default fallback to sensor references for filling missing values, and keeps soc-minima / soc-maxima as deprecated legacy aliases (including OpenAPI + docs updates).

Changes:

  • Extend VariableQuantityField / SensorReference to support a default fallback quantity and apply it when fetching belief series
  • Route dynamic soc-min / soc-max payloads to the internal dynamic-boundary handling while rejecting ambiguous combinations with legacy aliases
  • Update tests, docs, and regenerated OpenAPI specs/examples to reflect the new canonical behavior and deprecations

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
flexmeasures/ui/static/openapi-specs.json Regenerated OpenAPI specs/examples for canonical soc-min / soc-max, default fallback, and deprecated aliases
flexmeasures/data/schemas/tests/test_sensor.py Add tests for deserializing/serializing sensor references with default
flexmeasures/data/schemas/tests/test_scheduling.py Add schema tests for soc-min / soc-max dynamic sensor refs with default, plus mutual exclusivity cases
flexmeasures/data/schemas/sensors.py Implement default handling in VariableQuantityField + SensorReference and update time series schema examples
flexmeasures/data/schemas/scheduling/storage.py Switch soc_min / soc_max to VariableQuantityField, deprecate legacy metadata, and map dynamic canonical fields to legacy dynamic keys post-load
flexmeasures/data/schemas/scheduling/metadata.py Update field descriptions/examples to document canonical dynamic behavior and mark legacy aliases deprecated
flexmeasures/data/schemas/scheduling/init.py Update UI schema text for SoC boundaries and legacy alias labeling
flexmeasures/data/models/planning/utils.py Apply SensorReference.default to fill missing values in queried belief series
flexmeasures/data/models/planning/tests/test_utils_fresh_db.py Add integration test ensuring default fills missing belief slots
flexmeasures/data/models/planning/tests/test_solver.py Extend solver tests to cover canonical dynamic soc-min / soc-max and time-series cases
flexmeasures/data/models/planning/storage.py Update internal comment to reflect new deserialization routing for dynamic soc-max
flexmeasures/api/v3_0/sensors.py Update API example payload to use canonical soc-min sensor reference with default
flexmeasures/api/v3_0/assets.py Update API example payload to use canonical soc-min for time-series specs
documentation/features/scheduling.rst Update scheduling docs to describe dynamic soc-min / soc-max relaxation behavior and legacy aliases
documentation/api/introduction.rst Update API introduction note on default relaxation behavior for dynamic SoC boundaries

Comment thread flexmeasures/data/schemas/sensors.py Outdated
Comment thread flexmeasures/data/schemas/sensors.py
Comment thread flexmeasures/data/schemas/sensors.py Outdated
Comment thread flexmeasures/data/schemas/sensors.py
Comment thread flexmeasures/data/schemas/scheduling/storage.py Outdated
Comment thread documentation/api/introduction.rst
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
@BelhsanHmida

Copy link
Copy Markdown
Contributor Author

A couple of follow-up questions:

  1. I implemented default on the generic sensor reference schema rather than special-casing it for soc-min / soc-max. This means other fields that accept sensor references could also use default now. Is that an acceptable API shape, or should fallback defaults be restricted to dynamic SoC bounds only?

  2. Fixed scalar soc-min / soc-max remain hard physical limits, while dynamic soc-min / soc-max are mapped to the existing relaxed soc-minima / soc-maxima scheduler path. Is that the distinction we want?

  3. I kept fixed soc-min / soc-max together with legacy soc-minima / soc-maxima valid for backwards compatibility. Should we keep allowing that, or should it eventually warn/reject?

  4. I treat default: null the same as omitting default. Is that acceptable?

  5. I marked soc-minima and soc-maxima as deprecated in docs/OpenAPI metadata, but did not add runtime deprecation warnings when users still send them. Is metadata-only deprecation enough for this PR, or should we also emit runtime warnings for legacy soc-minima / soc-maxima usage?

@Flix6x

Flix6x commented Jul 10, 2026

Copy link
Copy Markdown
Member

A couple of follow-up questions:

Here are my answers.

  1. I implemented default on the generic sensor reference schema rather than special-casing it for soc-min / soc-max. This means other fields that accept sensor references could also use default now. Is that an acceptable API shape, or should fallback defaults be restricted to dynamic SoC bounds only?

That's actually great. This will be very useful for other fields, too, and the PR title+description, changelog entry and docs should reflect that.

  1. Fixed scalar soc-min / soc-max remain hard physical limits, while dynamic soc-min / soc-max are mapped to the existing relaxed soc-minima / soc-maxima scheduler path. Is that the distinction we want?

Not quite. In v1 we are moving to relaxing constraints by default (this already landed on main). So also a scalar soc-min/max should be a soft limit, unless the user explicitly turns off the soc relaxation (by setting either relax-constraints or relax-soc-constraints to False).

(Effectively, before this PR the user had the possibility of setting both hard and soft constraints for SoC limits, and now they have to pick one.)

  1. I kept fixed soc-min / soc-max together with legacy soc-minima / soc-maxima valid for backwards compatibility. Should we keep allowing that, or should it eventually warn/reject?

Keep the support. No rejection, no warning, just a deprecation notice in the docs.

  1. I treat default: null the same as omitting default. Is that acceptable?

Probably cleaner to use Marshmallow's defaults here, which are required=False, allow_none=False. Do you have a reason to explicitly allow null values?

  1. I marked soc-minima and soc-maxima as deprecated in docs/OpenAPI metadata, but did not add runtime deprecation warnings when users still send them. Is metadata-only deprecation enough for this PR, or should we also emit runtime warnings for legacy soc-minima / soc-maxima usage?

It's enough. No runtime warnings needed right now.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
@BelhsanHmida BelhsanHmida changed the title Feat: dynamic soc bounds defaults Feat: make soc-min and soc-max canonical with sensor defaults Jul 10, 2026
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
@BelhsanHmida

Copy link
Copy Markdown
Contributor Author

@Flix6x Thanks for the clarification. I addressed all five points:

  • Kept default on the generic sensor-reference schema and updated the PR description, changelog, docs, OpenAPI, and UI accordingly.
  • Scalar soc-min / soc-max now follow the default SoC relaxation behavior.
  • Kept fixed bounds together with legacy soc-minima / soc-maxima for backwards compatibility.
  • default: null is now rejected with required=False and allow_none=False.
  • Legacy aliases remain supported with documentation/OpenAPI deprecation only; no runtime warnings were added.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
…into feat/dynamic-soc-bounds-defaults

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

# Conflicts:
#	documentation/changelog.rst
#	documentation/tut/flex-model-v2g.rst
#	flexmeasures/data/models/planning/storage.py
#	flexmeasures/data/models/planning/tests/test_storage.py
#	flexmeasures/data/schemas/sensors.py
#	flexmeasures/data/schemas/tests/test_scheduling.py
#	flexmeasures/ui/static/openapi-specs.json
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
BelhsanHmida added a commit that referenced this pull request Jul 17, 2026
Both relax-soc-constraints and relax-constraints default to True, so the
former's default swallowed an explicit opt-out through the umbrella flag.
Now an explicit relax-soc-constraints wins and otherwise the umbrella
relax-constraints decides, per the semantics discussed in PR #2267:
setting either flag to False keeps SoC minima/maxima hard.

Also document why DBFlexContextSchema turns the relaxation defaults off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BelhsanHmida and others added 2 commits July 18, 2026 18:04
…ounds-defaults

Reconciles this branch with the updated base, which now includes main
(notably the off-tick SoC projection of PR #2194, which restructured the
soc-minima/maxima commitments flow) and the base's review fixes:

- Drop this branch's pre-projection commitments blocks (main moved that
  logic into the projection-scoped section) and fold the scalar
  soc-min/soc-max relaxation into the new location instead, after
  off-tick projection and gated on the same per-stock scoping. When
  relaxation was auto-enabled purely for off-tick projection, scalar
  bounds stay hard, respecting the user's explicit opt-out.
- Keep the base's opt-out precedence (an explicit relax-soc-constraints
  wins over relax-constraints; otherwise the umbrella decides). The
  off-tick projection machinery relies on this precedence, as it injects
  an explicit relax-soc-constraints=true.
- Update the off-tick projection test: with a breach price, the global
  soc-min now folds into the soft commitments instead of remaining hard.
- Drop the flooring of SoC field datetimes, per the base's off-tick
  design (soc-min/soc-max event datetimes are projected, not floored).
- Guard the %-SoC-sensor capacity lookup against raw dicts/lists from
  the DB flex-model (clean ValueError instead of a pint crash).
- Revert unrelated whitespace changes to repo tooling files.
- Document that a sensor-reference default densifies sparse sensors.
- Regenerate the OpenAPI specs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous merge commit accidentally captured a stale index, missing the
actual reconciliation work:

- Remove the broken flooring_resolution loop from StorageFlexModelSchema
  (main's off-tick design projects SoC datetimes instead of flooring them).
- Excise the pre-projection soc-minima/maxima commitments blocks that the
  merge resurrected; fold scalar soc-min/soc-max into the projection-scoped
  relaxation path instead (hard bounds are kept when relaxation was
  auto-enabled purely for off-tick projection).
- Restore the explicit-specific-wins opt-out precedence in check_prices,
  which the off-tick projection machinery relies on.
- Guard the %-SoC-sensor capacity lookup against raw dicts/lists.
- Update tests and the OpenAPI specs accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Make soc-minima and soc-maxima the default, simplifying usage

3 participants