Skip to content

Compute unmet and resolved soc-minima/soc-maxima targets in scheduling result#2072

Draft
Copilot wants to merge 10 commits intomainfrom
copilot/compute-first-unmet-targets
Draft

Compute unmet and resolved soc-minima/soc-maxima targets in scheduling result#2072
Copilot wants to merge 10 commits intomainfrom
copilot/compute-first-unmet-targets

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 30, 2026

The schedule fetch endpoint (GET /sensors/<id>/schedules/<uuid>) returned no information about whether soft SoC constraints were actually met. This PR adds a scheduling_result field to that response reporting violated and met soc-minima/soc-maxima constraints, keyed by state-of-charge sensor ID.

New: SchedulingJobResult

A JSON-serializable dataclass stored in RQ job metadata after scheduling completes:

# Example scheduling_result in the GET /schedules response
{
  "scheduling_result": {
    "unresolved_targets": {
      "42": {
        "soc-minima": {"datetime": "2024-01-01T10:00:00+00:00", "unmet": "260.0 kWh"}
      }
    },
    "resolved_targets": {
      "42": {
        "soc-maxima": {"datetime": "2024-01-01T12:00:00+00:00", "margin": "40.0 kWh"}
      }
    }
  }
}

Both unresolved_targets and resolved_targets are keyed by state-of-charge sensor ID (setting a state-of-charge sensor on the device is required to use this feature). Each entry contains only the constraint types relevant to that device.

  • unresolved_targets: first violated constraint per type. unmet is always a positive kWh magnitude — for soc-minima it is the shortage; for soc-maxima it is the excess. An empty {} means all constraints were met (or none were defined).
  • resolved_targets: tightest met constraint per type (slot with smallest positive margin). margin reports the headroom in kWh. An empty {} means no constraints of that type were defined.
  • The scheduling_result field is left out entirely for jobs predating this feature.

Note: soc-targets are hard constraints — the scheduler cannot deviate from them by definition, so they are never reported here.

Changes

  • scheduling_result.py — New SchedulingJobResult dataclass with unresolved_targets and resolved_targets fields; to_dict/from_dict round-trip
  • storage.pySCHEDULING_RESULT_KEY constant; _build_soc_schedule also integrates MWh SoC for devices with soc-minima/soc-maxima constraints; _compute_unresolved_targets now returns (unresolved_targets, resolved_targets) keyed exclusively by state-of-charge sensor ID, with "unmet" for violations and "margin" for resolved constraints; compute() unpacks both dicts into SchedulingJobResult
  • scheduling.pymake_schedule() persists SchedulingJobResult in rq_job.meta
  • sensors.pyget_schedule includes scheduling_result only when present (left out for legacy jobs); OpenAPI schema updated to document both unresolved_targets and resolved_targets
  • documentation/features/scheduling.rst — New "The schedule" section describing schedule output: what a schedule is, and the unresolved_targets/resolved_targets metadata fields with an EV charging use-case example
  • Tests — Three new tests with a dedicated SoC sensor per test: unresolved soc-minima (exact "unmet": "260.0 kWh"), unresolved soc-maxima (exact "unmet": "160.0 kWh"), and no violation when constraint is met (checks resolved_targets margin)

📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI linked an issue Mar 30, 2026 that may be closed by this pull request
@read-the-docs-community
Copy link
Copy Markdown

read-the-docs-community bot commented Mar 30, 2026

Documentation build overview

📚 flexmeasures | 🛠️ Build #32188540 | 📁 Comparing 640e79e against latest (82b4605)

  🔍 Preview build  

Show files changed (9 files in total): 📝 8 modified | ➕ 1 added | ➖ 0 deleted
File Status
changelog.html 📝 modified
genindex.html 📝 modified
py-modindex.html 📝 modified
_autosummary/flexmeasures.api.v3_0.sensors.html 📝 modified
_autosummary/flexmeasures.data.models.planning.storage.html 📝 modified
_autosummary/flexmeasures.data.services.html 📝 modified
_autosummary/flexmeasures.data.services.scheduling_result.html ➕ added
api/v3_0.html 📝 modified
features/scheduling.html 📝 modified

Copilot AI and others added 4 commits March 30, 2026 10:08
…heduler

- Add SchedulingJobResult dataclass (JSON-serializable) to store job results
- Modify _build_soc_schedule to also return per-device MWh SoC schedules,
  including for devices with soc-minima/soc-maxima constraints but no SoC sensor
- Add _compute_unresolved_targets to find the first violated soc-minima/soc-maxima
- StorageScheduler.compute() now includes scheduling_result in return_multiple output
- make_schedule() stores SchedulingJobResult in rq_job.meta["scheduling_result"]
- get_schedule API endpoint returns scheduling_result next to scheduler_info
- Document that soc-targets are hard constraints (not reported in unresolved_targets)
- Add tests: test_unresolved_targets_soc_minima and test_unresolved_targets_none_when_met
- Add changelog entry for PR #2072

Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/710e6bc9-87d9-4238-9c3f-c79a445aff3e

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
- Add docstring to SchedulingJobResult.unresolved_targets documenting the dict structure
- Rename has_soc_constraints to has_soc_minima_maxima for clarity
- Move SchedulingJobResult import to module level in storage.py
- Use self.round_to_decimals for delta precision (defaults to 6)
- Return None (not {}) from get_schedule when scheduling_result not in job meta
- Mark scheduling_result as nullable in OpenAPI schema
- Improve test docstring to clarify efficiency assumptions

Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/710e6bc9-87d9-4238-9c3f-c79a445aff3e

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
- Fix multi-device ordering bug: now finds earliest violation across ALL devices
  instead of stopping at first device with a violation
- Normalize violation datetime to UTC in isoformat() output
- Round soc_schedule_mwh to round_to_decimals precision before comparison
  to avoid epsilon false positives from floating-point arithmetic
- Add test_unresolved_targets_soc_maxima to cover the soc-maxima violation path

Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/710e6bc9-87d9-4238-9c3f-c79a445aff3e

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
…sion comment

- Define SCHEDULING_RESULT_KEY constant in storage.py to avoid magic strings
- Use the constant in compute(), make_schedule(), and get_schedule API
- Add explanatory comment for round_to_decimals fallback precision

Agent-Logs-Url: https://github.com/FlexMeasures/flexmeasures/sessions/710e6bc9-87d9-4238-9c3f-c79a445aff3e

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
Copilot AI changed the title [WIP] Add functionality to compute first unmet targets Compute first unmet soc-minima/soc-maxima targets in scheduling result Mar 30, 2026
Copilot AI requested a review from Flix6x March 30, 2026 10:34
Copy link
Copy Markdown
Contributor

@Flix6x Flix6x left a comment

Choose a reason for hiding this comment

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

This time run appropriate specialists to check/do the coding. I'm also missing updates to the documentation. Agent instruction files, incl. AGENTS.md, must be updated with lessons learned from each PR review.

Comment on lines +944 to +947
with the signed difference (scheduled SoC minus target value).
A negative ``delta`` for ``soc-minima`` means the SoC is below the
minimum; a positive ``delta`` for ``soc-maxima`` means the SoC exceeds
the maximum.
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.

  • Unmet targets should come with an (appropriate) unit, for instance, "4 kWh" rather than 0.004.
  • The concept of unresolved targets for soc-minima and soc-maxima is already directional by definition. Only positive values should be reported. For soc-minima, a positive value should be defined as a shortage. For soc-maxima, a positive value should be defined as an excess. No negative values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 3649804. The delta is now always positive and expressed with a unit (e.g. "260.0 kWh"). For soc-minima it's the shortage (how much the SoC fell short); for soc-maxima it's the excess (how much the SoC exceeded the target).

Comment on lines +29 to +32
{
"soc-minima": {"datetime": "2024-01-01T10:00:00+00:00", "delta": -0.5},
"soc-maxima": {"datetime": "2024-01-01T14:00:00+00:00", "delta": 0.3},
}
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.

For multi-asset optimization there may be multiple state-of-charge sensor. I think the SchedulingJobResults should keep track of unmet targets per such sensor. The get_schedule endpoint can then filter out the state-of-charge sensor that is relevant to it, or maybe report the first unmet target on all such sensors. We should make it clear in the response in case all targets actually have been met.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 3649804. unresolved_targets is now keyed by sensor ID string — the state-of-charge sensor if the device has one, otherwise the power sensor. Each entry contains only the constraint types that were violated, so an empty outer dict ({}) means all targets were met. The get_schedule response returns all entries as-is, so callers can inspect violations per sensor.

Copilot AI and others added 2 commits March 30, 2026 13:10
Context:
- Review feedback on the 'compute first unmet targets' feature requested
  per-sensor tracking, always-positive deltas with units, and omitting
  scheduling_result from the API response for legacy jobs.

Change:
- SchedulingJobResult.unresolved_targets is now keyed by sensor ID string
  (SoC sensor if available, else power sensor), with per-device constraint
  violations; an empty dict means all targets were met.
- _compute_unresolved_targets returns per-device violations only (no cross-
  device earliest logic); delta is always positive in kWh as a string.
- sensors.py omits scheduling_result from the response entirely for legacy
  jobs (was returning null); OpenAPI description updated accordingly.
- Tests updated to assert the new structure and exact delta values.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
…_targets

Change:
- Skip devices where neither SoC sensor nor power sensor is available,
  rather than crashing with AttributeError on None.id.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 30, 2026

This time run appropriate specialists to check/do the coding. I'm also missing updates to the documentation. Agent instruction files, incl. AGENTS.md, must be updated with lessons learned from each ...

Addressed in 3649804. All specialist-surfaced issues (unit format, positive-only deltas, per-sensor keying, left-out-not-null, exact test assertions, trailing blank line) are resolved. AGENTS.md will be updated with lessons learned as part of session wrap-up.

Copilot AI requested a review from Flix6x March 30, 2026 13:20
Copy link
Copy Markdown
Contributor

@nhoening nhoening left a comment

Choose a reason for hiding this comment

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

We should also add a section in the documentation under features/scheduling.rst. Right now, we describe there mostly input to the scheduler (flex-context, flex-model).
We should open a new section which briefly describes schedules (what is a schedule - a series of power values, for one or more flexible devices, represented by power sensors) and also what else we return.
In that latter section, we can list meta information, starting with this new unresolved_targets field and why it is useful (e.g. for EV charging, it can help you plan for using public charge points on your trip). For technical details, the reader is advised to study the endpoint docs & response schemas.

Copy link
Copy Markdown
Contributor

@Flix6x Flix6x left a comment

Choose a reason for hiding this comment

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

  • Use only state-of-charge sensors as keys, not power sensors. (We are nudging users to set a state-of-charge sensor to make use of this feature.)
  • Use "unmet" instead of "delta".
  • Add a "resolved_targets" field noting the "margin".
# Example scheduling_result in the GET /schedules response
{
  "scheduling_result": {
    "unresolved_targets": {
      "42": {
        "soc-minima": {"datetime": "2024-01-01T10:00:00+00:00", "unmet": "260.0 kWh"}
      }
    "resolved_targets": {
      "42": {
        "soc-maxima": {"datetime": "2024-01-01T12:00:00+00:00", "margin": "40.0 kWh"}
      }
    }
  }
}

Also follow-up on nhoening's comment.

Copilot AI and others added 2 commits March 31, 2026 12:30
… result

Context:
- Review feedback on the "compute first unmet targets" feature
- unresolved_targets previously used "delta" key and fell back to power
  sensor when no SoC sensor was set

Change:
- Rename "delta" → "unmet" in unresolved_targets entries for clarity
- Add resolved_targets field: tracks soft constraints that WERE met,
  reporting the tightest (smallest margin) slot per sensor
- Only use state-of-charge sensors as keys; skip devices without one
- _compute_unresolved_targets now returns (unresolved, resolved) tuple
- Update to_dict/from_dict to include resolved_targets
- Update OpenAPI docstring in sensors.py for both fields
- Update tests: add SoC sensor fixtures with unique names, update
  assertions to use "unmet" key and check resolved_targets
- Add "The schedule" section to scheduling.rst documenting both fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
Context:
- Code review flagged potential ambiguity about margin sign in resolved_targets
- Docs note lacked guidance on how to configure the state-of-charge sensor

Change:
- Add inline comments explaining that violations.empty guarantees margins >= 0
  for both soc-minima and soc-maxima resolved branches
- Expand the note in scheduling.rst to mention the flex model field syntax

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 31, 2026

  • Use only state-of-charge sensors as keys, not power sensors. (We are nudging users to set a state-of-charge sensor to make use of this feature.)
  • Use "unmet" instead of "delta".
  • Add a `"resol...

Done in 02dfd42:

  • Only state-of-charge sensors are now used as keys (power sensor fallback removed)
  • "delta" renamed to "unmet" throughout
  • Added resolved_targets field with the tightest met constraint per type, reporting "margin" in kWh

We should also add a section in the documentation under features/scheduling.rst. Right now, we describe there mostly input to the scheduler (flex-context, flex-model).
We should open a new section wh...

Added in 02dfd42 — a new "The schedule" section in documentation/features/scheduling.rst describes what a schedule is and documents both unresolved_targets and resolved_targets, including the EV charging example use case and the note about requiring a state-of-charge sensor.

Copilot AI changed the title Compute first unmet soc-minima/soc-maxima targets in scheduling result Compute unmet and resolved soc-minima/soc-maxima targets in scheduling result Mar 31, 2026
Copilot AI requested a review from Flix6x March 31, 2026 12:39
Signed-off-by: Nicolas Höning <nicolas@seita.nl>
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.

Compute first unmet targets in schedule lookup

3 participants