Skip to content

Add soft constraint analysis (unresolved/resolved SoC constraints) to scheduling job results#2072

Open
Flix6x with Copilot wants to merge 89 commits into
mainfrom
copilot/compute-first-unmet-targets
Open

Add soft constraint analysis (unresolved/resolved SoC constraints) to scheduling job results#2072
Flix6x with Copilot wants to merge 89 commits into
mainfrom
copilot/compute-first-unmet-targets

Conversation

Copilot AI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Description

Adds soft constraint analysis to scheduling job results: when a StorageScheduler job finishes, GET /api/v3_0/jobs/<uuid> now reports which soc-minima/soc-maxima targets could not be met (unresolved) and which were met, together with their remaining headroom (resolved). This makes it possible to inspect why a schedule looks the way it does without having to reverse-engineer it from the raw power/SoC series.

  • Compute per-device soc-minima/soc-maxima violations and margins in StorageScheduler._compute_unresolved_targets (flexmeasures/data/models/planning/storage.py), keyed by asset ID.
  • Introduce SchedulingJobResult (flexmeasures/data/services/scheduling_result.py) as the serializable container for unresolved/resolved, stored on the RQ job's return_value (flexmeasures/data/services/scheduling.py).
  • Surface the result via the existing result field on GET /api/v3_0/jobs/<uuid> (flexmeasures/api/v3_0/jobs.py) — this is the only place constraint analysis is available; GET /api/v3_0/sensors/<id>/schedules/<uuid> continues to return power values only. The result field of the GET /api/v3_0/jobs/<uuid> endpoint is, for scheduling jobs, no longer a boolean, but an actual object where we can start returning more scheduling results. For now, it only contains unresolved/resolved info.
  • Add a most_relevant_only: bool = False option to _compute_unresolved_targets: by default every violated/met slot is reported (chronologically ordered); set to True to get only the single most relevant slot (first violation, or tightest margin) — either way, each constraint key holds a list.
  • Document the feature end-to-end: OpenAPI docstrings/spec, documentation/features/scheduling.rst ("Accessing constraint results" section, with a worked multi-asset example), and documentation/api/change_log.rst (v3.0-32).
  • Added changelog item in documentation/changelog.rst
  • Update the .github/agents/*.md specialist instructions with lessons learned during this PR (see "Changes to AI infra" below).

Response shape

{
  "status": "FINISHED",
  "message": "Scheduling job finished.",
  "result": {
    "unresolved": [
      {
        "asset": 42,
        "soc-minima": [
          {"datetime": "2024-01-01T10:00:00+00:00", "violation": "260.0 kWh"},
          {"datetime": "2024-01-01T10:15:00+00:00", "violation": "180.0 kWh"}
        ]
      }
    ],
    "resolved": []
  }
}
  • unresolved/resolved are lists of per-asset dicts, keyed by asset ID (not sensor ID) — so a multi-asset scheduling job reports constraint results for every flexible device it scheduled, in one response.
  • Each soc-minima/soc-maxima value is itself a list: one entry per violated slot (unresolved) or per met slot with its margin (resolved), chronologically ordered by default.
  • soc-targets (hard constraints) are never reported here — the scheduler enforces them strictly, so a hard-constraint failure fails the job outright rather than appearing as a soft violation.

Changes to AI infra

Part of this PR's development happened by handing work back and forth between GitHub Copilot's coding agent and Claude Code, both operating against the same repo-local specialist-agent framework (AGENTS.md, .github/agents/*.md, .github/instructions/*.md). As a result, this PR also carries some infra changes unrelated to the feature itself but load-bearing for how the repo's AI agents operate:

  • .github/agents/api-backward-compatibility-specialist.md, architecture-domain-specialist.md, documentation-developer-experience-specialist.md, test-specialist.md: appended "lessons learned" sections captured while building this feature (e.g. asset-keying/format-mismatch patterns, cross-document consistency checks, data-format-transformation test patterns).
  • .github/instructions/feature-branch-sync.instructions.md (new): codifies checking git log --oneline origin/main...HEAD --left-right (not just git status) before starting implementation work, and merging origin/main in before proceeding — added after a session where the feature branch had drifted 10+ commits behind main.
  • .github/instructions/docstrings.instructions.md: minor conventions (:returns: instead of :return:; line breaks only after punctuation).
  • AGENTS.md: a session-lesson entry and a corresponding "branch in sync with main" pre-commit checklist item.
  • .claude/agents/*.md (new, 9 files): thin stubs mirroring the .github/agents/*.md personas for Claude Code, added separately by the maintainer (not generated as part of the feature work).

None of this affects runtime behavior; it's process/tooling scoped to how AI coding agents work in this repo.

Look & Feel

Request:

GET /api/v3_0/jobs/5d28df1b-9f16-4177-ae43-6e750d80fad3

Response (finished scheduling job, one unresolved soc-minima violated at two slots, one resolved soc-maxima with margin):

{
  "status": "FINISHED",
  "message": "Scheduling job has finished.",
  "result": null,
  "func_name": "flexmeasures.data.services.scheduling.create_schedule",
  "origin": "scheduling",
  "enqueued_at": "2026-04-28T10:00:00+00:00",
  "started_at": "2026-04-28T10:00:01+00:00",
  "ended_at": "2026-04-28T10:00:05+00:00",
  "exc_info": null,
  "result": {
    "unresolved": [
      {
        "asset": 42,
        "soc-minima": [
          {"datetime": "2024-01-01T10:00:00+00:00", "violation": "260.0 kWh"},
          {"datetime": "2024-01-01T10:15:00+00:00", "violation": "180.0 kWh"}
        ]
      }
    ],
    "resolved": [
      {
        "asset": 42,
        "soc-maxima": [
          {"datetime": "2024-01-01T12:00:00+00:00", "margin": "40.0 kWh"}
        ]
      }
    ]
  }
}

If no SoC constraints were defined for any scheduled asset, both unresolved and resolved are empty lists.

How to test

Run the storage-scheduler test module, which covers unresolved/resolved reporting end to end (via StorageScheduler.compute()) as well as the most_relevant_only flag directly:

pytest flexmeasures/data/models/planning/tests/test_storage.py -v

Key test functions:

  • test_unresolved_targets_soc_minima / test_unresolved_targets_soc_maxima — unresolved (violated) constraints are reported with the correct asset ID, datetime, and violation magnitude.
  • test_unresolved_targets_none_when_met — met constraints are reported under resolved with a non-negative margin.
  • test_unresolved_targets_no_soc_sensor — asset-keyed reporting still works for devices without a state-of-charge sensor in the flex model.
  • test_unresolved_targets_most_relevant_only_flag_soc_minima_violations — default (most_relevant_only=False) reports every violated slot chronologically; most_relevant_only=True reports only the first.
  • test_unresolved_targets_most_relevant_only_flag_soc_minima_resolved_margins — same distinction for resolved margins (all met slots vs. only the tightest).

End to end, also try triggering a real scheduling job with an unreachable soc-minima/soc-maxima (e.g. via POST /api/v3_0/assets/<id>/schedules/trigger) and then inspecting GET /api/v3_0/jobs/<uuid>.

Full suite (1346 passed, 4 xfailed at the time of writing) and pre-commit run --all-files should both be clean.

Further Improvements

  • most_relevant_only is currently only a parameter of the private StorageScheduler._compute_unresolved_targets method (always called with the default from compute()); it is not yet exposed through the public flex-model/flex-context or the API. A follow-up could expose it if callers want the terser single-slot-per-constraint response.

Related Items

Closes/implements the "compute first unmet SoC targets" work tracked under this PR's branch name (copilot/compute-first-unmet-targets).


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.

Copilot AI linked an issue Mar 30, 2026 that may be closed by this pull request
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

@Flix6x Flix6x left a comment

Copy link
Copy Markdown
Member

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 thread flexmeasures/api/v3_0/sensors.py Outdated
Comment thread flexmeasures/api/v3_0/sensors.py Outdated
Comment thread flexmeasures/api/v3_0/sensors.py Outdated
Comment thread flexmeasures/data/models/planning/tests/test_storage.py Outdated
Comment thread flexmeasures/data/models/planning/tests/test_storage.py Outdated
Comment thread flexmeasures/data/services/scheduling_result.py
Comment thread flexmeasures/data/services/scheduling_result.py Outdated
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>

Copilot AI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor Author

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

@nhoening nhoening left a comment

Copy link
Copy Markdown
Member

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.

@Flix6x Flix6x left a comment

Copy link
Copy Markdown
Member

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>

Copilot AI commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author
  • 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
Flix6x and others added 19 commits July 1, 2026 17:40
Signed-off-by: F.N. Claessen <claessen@seita.nl>
_compute_unresolved_targets now always returns a list of
datetime/violation (or margin) entries per constraint key, covering
every violated or met slot by default, instead of a single dict for
the first violation or tightest margin. Update the OpenAPI docstring
prose and finished-job example on GET /api/v3_0/jobs/<uuid>
accordingly, and regenerate openapi-specs.json to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
The "Accessing constraint results" section described each
soc-minima/soc-maxima entry as a single datetime/violation (or
margin) value for the tightest or first-violated slot. Update the
field descriptions and JSON example to reflect that these are always
lists, containing one entry per violated or met slot by default, to
match the current jobs.py OpenAPI docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
…constraints

Context:
- _compute_unresolved_targets now always wraps each soc-minima/soc-maxima
  entry under `unresolved`/`resolved` in a list (supporting the new `all`
  flag), instead of a single dict for the first/tightest slot.

Change:
- Update the four existing unresolved-target tests to index into the
  single-element list (`entry["soc-minima"][0][...]`) and assert the
  list has exactly one element, since each test only defines a single
  constraint datetime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
…targets

Context:
- _compute_unresolved_targets gained an `all` parameter (default True):
  report every violated/met slot, or (all=False) only the first violation /
  tightest margin. This behavior had no dedicated coverage yet.

Change:
- Add test_unresolved_targets_all_flag_soc_minima_violations: three
  soc-minima checkpoints are all unreachable given a capacity-limited
  battery; asserts the default (all=True) reports all three violated
  slots in chronological order, and that all=False (invoked directly via
  a spy on the private method) reports only the first.
- Add test_unresolved_targets_all_flag_soc_minima_resolved_margins:
  two soc-minima checkpoints (one tight, one slack) are both met with
  different margins; asserts all=True reports both margins and all=False
  reports only the tightest one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
Reports every violated/met slot per constraint by default, instead of
only the first violation or tightest margin. Constraint entries are
now always lists, even when all=False keeps just the single entry.
Avoids shadowing the all() builtin. Flips the polarity so the
new, more informative multi-slot reporting is the implicit default.
Follows the production code rename in _compute_unresolved_targets.
…docs

documentation/api/change_log.rst named the wrong field (result instead
of scheduling_result), and SchedulingJobResult's docstring example
still showed the pre-list-wrapping single-dict-per-constraint shape.
…, not meta

Context:
- Maintainer decided the soft SoC constraint analysis produced by
  StorageScheduler should be surfaced through the job's actual return
  value (job.return_value()) instead of a bolted-on `scheduling_result`
  field sourced from RQ job.meta.

Change:
- make_schedule() now returns the SchedulingJobResult dict when the
  scheduler produced one, or True otherwise (return type: bool | dict).
  This also fixes direct (non-RQ) make_schedule() calls, which
  previously silently dropped the constraint-analysis data.
- GET /api/v3_0/jobs/<uuid> no longer reads job.meta[SCHEDULING_RESULT_KEY]
  or exposes a separate `scheduling_result` field; the data now arrives
  automatically via the existing `result` field.
- Updated the OpenAPI docstring/schema/examples in jobs.py accordingly
  and regenerated flexmeasures/ui/static/openapi-specs.json.
- Updated the SchedulingJobResult docstring to point at the `result`
  field instead of the now-removed `scheduling_result` field.

Note: this is a breaking change for every finished StorageScheduler job,
not only ones whose flex model defines soc-minima/soc-maxima — see the
API changelog entry for details.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
Context:
- Follows the previous commit, which stopped exposing a separate
  `scheduling_result` field on GET /api/v3_0/jobs/<uuid> and instead
  surfaces soft SoC constraint analysis via the existing `result` field.

Change:
- Updated documentation/features/scheduling.rst's constraint-results
  section and worked example to reference `result` instead of
  `scheduling_result`, and to clarify that a finished StorageScheduler
  job always returns the analysis object (with empty arrays when no
  soc-minima/soc-maxima are defined), while other scheduling jobs keep
  returning `true`.
- Marked the v3.0-32 API changelog entry as a breaking change: for
  every finished StorageScheduler job (not only ones with
  soc-minima/soc-maxima defined), `result` is now an object instead of
  the boolean `true` it used to return unconditionally. Flagged the
  risk for external integrators (e.g. flexmeasures-client, or custom
  scripts) that check `result === true`/`result is True` unconditionally.
- Reworded the main changelog's PR #2072 one-liner so it no longer
  reads as purely additive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
Context:
- GET /api/v3_0/jobs/<uuid> no longer exposes a separate
  `scheduling_result` field; scheduling constraint analysis now arrives
  via `result` directly (see the preceding scheduling.py/jobs.py commit).

Change:
- Fixed the stale comment on test_get_job_status_finished's assertion
  and tightened it to assert the exact StorageScheduler result shape
  ({"unresolved": [], "resolved": []}), since this test's flex model
  defines no soc-minima/soc-maxima.
- Added test_get_job_status_finished_with_unresolved_soc_minima,
  driving an unreachable soc-minima (soft constraint via a breach
  price) through the real trigger -> work_on_rq -> job-status flow, and
  asserting `result` contains the expected unresolved entry and that
  the old `scheduling_result` field is really gone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLBuNdhpXdpHgq2ZDLUUq8
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Context:
- Maintainer wants result consistently a dict for every scheduling
  job, not just StorageScheduler jobs with soft SoC constraints, to
  simplify the docs and the UX of GET /api/v3_0/jobs/<uuid>.

Change:
- make_schedule() now always returns a dict: the SchedulingJobResult
  dict when the scheduler produced one, otherwise an empty {} (for
  now, pending a proper result spec for other schedulers).
- Adapted the one caller relying on the old truthy boolean
  (cli/data_add.py's `add schedule` command): an empty dict is falsy,
  so the "New schedule is stored." message no longer depends on the
  return value's truthiness.
- Updated jobs.py's OpenAPI docstring/schema/example accordingly and
  regenerated openapi-specs.json.
Follows the previous commit. Updated scheduling.rst's constraint-
results section, the v3.0-32 API changelog breaking-change note, and
the main changelog's PR #2072 one-liner to state that every finished
scheduling job now returns an object for `result`, not only
StorageScheduler jobs with soc-minima/soc-maxima defined.
- Fixed a stale comment in test_get_job_status_finished.
- Added a regression assertion to test_add_process (ProcessScheduler,
  called directly via CLI, not via RQ) that "New schedule is stored."
  still prints now that make_schedule() can return a falsy {} on
  success.
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Python 3.10 resolves an older pint/numpy (0.24.4/2.2.6 vs 0.25.3/2.4.6 on
3.11+, per uv.lock), which can produce sub-1e-12 float noise after unit
conversion. Exact equality then flakes on some runner architectures even
though the values are correct to any sane precision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Flix6x

Flix6x commented Jul 1, 2026

Copy link
Copy Markdown
Member

@nhoening I still need to deal with the 3 tests failing on Python 3.10 on a floating point imprecision, but nevertheless this is ready for review.

@Flix6x Flix6x requested a review from nhoening July 1, 2026 21:11

@nhoening nhoening left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pretty nice!

As usual, my comments mainly are about the docs & naming.

Comment thread .github/agents/architecture-domain-specialist.md Outdated
Comment thread documentation/features/scheduling.rst
Comment thread documentation/features/scheduling.rst
Comment thread documentation/features/scheduling.rst Outdated
Comment thread flexmeasures/data/services/scheduling_result.py Outdated
Comment thread documentation/features/scheduling.rst
Flix6x and others added 8 commits July 3, 2026 09:37
pytest.approx on the magnitude (added previously) doesn't cover the
str(Quantity) comparisons in the same test, which still fail on Python
3.10's older pint (0.24.4) for cases like 914.7 EUR/kWh -> kEUR/MWh,
where the converted magnitude carries ~1e-13 float noise that shows up
in the string repr (e.g. "914.7000000000003" instead of "914.7").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oSbjKoErHkpDmeBjkxinY
Per review, non-StorageScheduler jobs returned an empty {} result
where StorageScheduler jobs return {"unresolved": [...], "resolved":
[...]}. Default to the same two-key shape for all scheduling jobs, so
callers can rely on one consistent result structure regardless of
scheduler.

Also clarify the scheduling_result.py docstring's cross-reference to
the docs by naming the section explicitly, since "scheduling_constraint_results"
is a Sphinx anchor, not a string a reader would search the docs for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oSbjKoErHkpDmeBjkxinY
- Simplify the API/main changelog entries for the scheduling result
  field per review suggestion, dropping the now-inaccurate note that
  non-StorageScheduler jobs return a different (empty-object) shape.
- Restore the missing "Work on other schedulers" section header in
  scheduling.rst (lost in an earlier edit).
- Fix a duplicated "Checking the status via the API" header; the
  second one covers the CLI command, not the API.
- Add "if the battery is in an EV, charge en-route" to the shortfall
  use-case bullets.
- Explicitly state that `violation`/`margin` are always positive
  magnitudes, never negative.
- Fix a stale session-date placeholder in the architecture specialist's
  lessons-learned log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oSbjKoErHkpDmeBjkxinY
The comment said make_schedule() returns an empty dict; it now
returns {"unresolved": [], "resolved": []} for every scheduler,
per the previous commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oSbjKoErHkpDmeBjkxinY
Per nhoening's review comment, the high-level "Inspecting schedules"
section (errors, job status via API/CLI, asset status page, RQ
dashboard) belongs before the more detailed "Accessing constraint
results" section, not after it — readers should learn how to check
on a job in general before diving into constraint-analysis specifics.

Also cross-reference the constraint-results section from the API
status-check paragraph, since that's precisely where the data lives.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oSbjKoErHkpDmeBjkxinY
… updating the docs

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