Compute unmet and resolved soc-minima/soc-maxima targets in scheduling result#2072
Compute unmet and resolved soc-minima/soc-maxima targets in scheduling result#2072
Conversation
Documentation build overview
Show files changed (9 files in total): 📝 8 modified | ➕ 1 added | ➖ 0 deleted
|
…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>
Flix6x
left a comment
There was a problem hiding this comment.
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.
flexmeasures/api/v3_0/sensors.py
Outdated
| 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. |
There was a problem hiding this comment.
- Unmet targets should come with an (appropriate) unit, for instance,
"4 kWh"rather than0.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.
There was a problem hiding this comment.
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).
| { | ||
| "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}, | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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>
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. |
There was a problem hiding this comment.
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.
Flix6x
left a comment
There was a problem hiding this comment.
- 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.
… 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>
Done in 02dfd42:
Added in 02dfd42 — a new "The schedule" section in |
Signed-off-by: Nicolas Höning <nicolas@seita.nl>
The schedule fetch endpoint (
GET /sensors/<id>/schedules/<uuid>) returned no information about whether soft SoC constraints were actually met. This PR adds ascheduling_resultfield to that response reporting violated and metsoc-minima/soc-maximaconstraints, keyed by state-of-charge sensor ID.New:
SchedulingJobResultA JSON-serializable dataclass stored in RQ job metadata after scheduling completes:
Both
unresolved_targetsandresolved_targetsare keyed by state-of-charge sensor ID (setting astate-of-chargesensor 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.unmetis always a positive kWh magnitude — forsoc-minimait is the shortage; forsoc-maximait 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).marginreports the headroom in kWh. An empty{}means no constraints of that type were defined.scheduling_resultfield is left out entirely for jobs predating this feature.Changes
scheduling_result.py— NewSchedulingJobResultdataclass withunresolved_targetsandresolved_targetsfields;to_dict/from_dictround-tripstorage.py—SCHEDULING_RESULT_KEYconstant;_build_soc_schedulealso integrates MWh SoC for devices with soc-minima/soc-maxima constraints;_compute_unresolved_targetsnow 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 intoSchedulingJobResultscheduling.py—make_schedule()persistsSchedulingJobResultinrq_job.metasensors.py—get_scheduleincludesscheduling_resultonly when present (left out for legacy jobs); OpenAPI schema updated to document bothunresolved_targetsandresolved_targetsdocumentation/features/scheduling.rst— New "The schedule" section describing schedule output: what a schedule is, and theunresolved_targets/resolved_targetsmetadata fields with an EV charging use-case example"unmet": "260.0 kWh"), unresolved soc-maxima (exact"unmet": "160.0 kWh"), and no violation when constraint is met (checksresolved_targetsmargin)📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.