Skip to content

docs(changelog): say each S2 change plainly - #283

Merged
Flix6x merged 2 commits into
dev/s2-frbc-completefrom
docs/s2-changelog-legible
Nov 8, 2026
Merged

Flix6x merged 2 commits into
dev/s2-frbc-completefrom
docs/s2-changelog-legible

Conversation

@Flix6x

@Flix6x Flix6x commented Nov 8, 2026

Copy link
Copy Markdown
Member

Into the integration branch dev/s2-frbc-complete (#279), not main.

Answers the review comment on #278 that the changelog's sentence constructions reduce legibility, and that the problem is widespread.

The Unreleased section of CHANGELOG-S2.rst is rewritten entry by entry, and so are the three entries this work added to CHANGELOG.rst:

  • Each entry starts with what changes for the reader.
  • Sentences are short, with plain word order. Constructions such as "the moment a state of charge is known as of included" are gone.
  • The longest entries are split into a lead sentence and a list, with the PR links in a closing line.

No claim is added or dropped. The RST parses without warnings.

The rewrite is done here rather than in #278, because #279 replaces the whole Unreleased section that #278 adds.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CcpawsJs74cSwFLXkz7ZwW

Rewrite the Unreleased entries of CHANGELOG-S2.rst, and the three this
work added to CHANGELOG.rst, for legibility: lead with what changes for
the reader, in short sentences with plain word order, and split the
longest entries into a lead sentence and a list. No claim is added or
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcpawsJs74cSwFLXkz7ZwW
Signed-off-by: F.N. Claessen <claessen@seita.nl>
@Flix6x Flix6x added the S2 label Nov 8, 2026
@Flix6x Flix6x added this to the 0.10.0 milestone Nov 8, 2026
@coveralls

coveralls commented Nov 8, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 42489871952

Warning

No base build found for commit 2f89b72 on dev/s2-frbc-complete.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 95.175%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 3171
Covered Lines: 3018
Line Coverage: 95.18%
Coverage Strength: 4.57 hits per line

💛 - Coveralls

A check of every entry against the text it replaced found the rewrite
had given the charging-efficiency risk a direction (wrong for a heat
pump, whose COP exceeds one), made "a lower bound" of the hard upper
bound, and dropped or shifted several smaller facts: planning from an
unrecorded state of charge, the storage efficiency named only once
confirmed, the DST boundary rule, abnormal modes being left out of both
capacities and bands, power being converted into power_unit, polling,
and the handler always tracking the batch in force. Restore each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcpawsJs74cSwFLXkz7ZwW
Signed-off-by: F.N. Claessen <claessen@seita.nl>
@Flix6x
Flix6x merged commit 95cda29 into dev/s2-frbc-complete Nov 8, 2026
19 checks passed
@Flix6x
Flix6x deleted the docs/s2-changelog-legible branch November 8, 2026 14:29
Comment thread CHANGELOG-S2.rst
* ``FRBCSimple`` can record what a resource manager declares about its storage, so it can be seen and planned with. Give it any of the new ``soc_minima_sensor_id``, ``soc_maxima_sensor_id``, ``usage_forecast_sensor_id`` and ``leakage_behaviour_sensor_id``; a declaration without a sensor is not recorded.

- A fill-level target profile is recorded on the minima and maxima sensors, if both are given. It is recorded on the handler's ``resolution`` steps, starting from the step its start falls in. It is kept inside the declared fill-level range: a target past either end, such as a night setback below the range, is cut back to the range, and a target band wholly outside the range is recorded at its nearest end, with a warning. If one of the two bounds cannot be recorded, this is logged and the other is still recorded.
- A usage forecast is recorded in ``<energy_unit>/s``. A negative expected usage is recorded as zero, with a warning.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Even though RMs are expected to use negative expected usage to model soc-gain? Sounds like this decision is a CEM side workaround to remedy a problem in a specific RM for a specific use case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed. S2 allows a negative expected usage, and clipping it was added on the CEM side to keep plans feasible, not because the protocol forbids it.

Recording and planning are separate questions, though. I'd record what the resource manager states, always, and only decide at planning time what to do with a usage the storage cannot absorb. #286 already records it as stated when the usage correction is on. I propose to make that the default: record as stated in every case, and without a correction, clip only the forecast sent with the schedule request, as #286 already does when the correction is unavailable. I'd do that in #286.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in #286 (905254e): a negative expected usage is now recorded as stated. Without a correction, the schedule request carries the latest forecast without its negative values. Only a FlexMeasures server before v0.23.0, which takes a usage only as a sensor, still gets it recorded as zero.

Comment thread CHANGELOG-S2.rst
- A leakage behaviour is recorded as a storage efficiency, each time a schedule is requested (see Changed).

[`#244 <https://github.com/FlexMeasures/flexmeasures-client/pull/244>`__, `#259 <https://github.com/FlexMeasures/flexmeasures-client/pull/259>`__, `#271 <https://github.com/FlexMeasures/flexmeasures-client/pull/271>`__, `#274 <https://github.com/FlexMeasures/flexmeasures-client/pull/274>`__]
* ``FRBCSimple`` takes an ``energy_unit`` (default ``kWh``). The state of charge, the fill-level band, the fill rate and the usage forecast are recorded in this unit (the last two per second). Allowed units are ``J``, ``kJ``, ``MJ``, ``Wh``, ``kWh`` and ``MWh``; any other unit is refused when the handler is created. In a flex model, the state of charge is stated in ``kWh`` or ``MWh``, converted from the other units, because FlexMeasures accepts no others [`#244 <https://github.com/FlexMeasures/flexmeasures-client/pull/244>`__, `#260 <https://github.com/FlexMeasures/flexmeasures-client/pull/260>`__]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Potential FM issue (maybe already exists) to allow arbitrary fill level units, maybe even arbitrary flow units. Conversion efficiencies would require matching units, e.g. kWh/(kW•h) for fill level delta per integral of power (i.e. our old % unit), or °C/(kW•h) when a fill level is recorded in degrees Celsius.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There wasn't one yet (#1796 and #1995 only cover percentages), so I opened FlexMeasures/flexmeasures#2847, with your examples of matching efficiency units and a cross-reference to #2832.

Comment thread CHANGELOG-S2.rst
* ``FRBCSimple`` takes an ``energy_unit`` (default ``kWh``). The state of charge, the fill-level band, the fill rate and the usage forecast are recorded in this unit (the last two per second). Allowed units are ``J``, ``kJ``, ``MJ``, ``Wh``, ``kWh`` and ``MWh``; any other unit is refused when the handler is created. In a flex model, the state of charge is stated in ``kWh`` or ``MWh``, converted from the other units, because FlexMeasures accepts no others [`#244 <https://github.com/FlexMeasures/flexmeasures-client/pull/244>`__, `#260 <https://github.com/FlexMeasures/flexmeasures-client/pull/260>`__]
* ``FRBCSimple`` takes a ``fill_level_scale`` (default 1). Every fill level and fill rate sent to FlexMeasures is multiplied by it: the state of charge, the flex model, the fill-level band, the fill rate, the usage forecast and the storage efficiency. Translating a schedule into instructions keeps the resource manager's own units, because it matches fill levels against the operation modes, which are stated in those units. A scale of zero or below is refused. ``FRBCSimple`` also takes a ``power_unit`` (default ``W``), which an actuator's power is converted into and posted in [`#236 <https://github.com/FlexMeasures/flexmeasures-client/pull/236>`__, `#244 <https://github.com/FlexMeasures/flexmeasures-client/pull/244>`__, `#245 <https://github.com/FlexMeasures/flexmeasures-client/pull/245>`__, `#252 <https://github.com/FlexMeasures/flexmeasures-client/pull/252>`__]
* ``FRBCSimple`` takes a ``hard_bound_factor`` (default 1.5). It sets how far above the declared fill-level range a schedule's hard upper bound lies. If a schedule starts from a state of charge above that bound, the bound is raised to include it. A factor below 1 is refused: FlexMeasures clips the band to the hard bounds, so a hard upper bound inside the declared range would narrow the band to something the resource manager never asked for [`#245 <https://github.com/FlexMeasures/flexmeasures-client/pull/245>`__]
* ``FRBCSimple`` can take the actuator's charging efficiency into account. Give it the new ``charging_efficiency_sensor_id``: it records there what the operation modes say about converting drawn power into stored energy, and requests a schedule with it as ``charging-efficiency``. For a heat pump, this ratio is its coefficient of performance. Without it, the scheduler assumes power converts into stored energy one to one, and plans to reach fill-level targets at a power the actuator cannot deliver.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Possible follow-up: record efficiencies of different operation modes under separate sensors. For now: clarify how efficiencies of different operation modes are apparently stored under a single sensor.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right, this entry hides that. The client records one value per schedule window on the single charging_efficiency_sensor_id, evaluated at the fill level the schedule starts from. Where the operation modes disagree, it records the lowest of them and logs a warning (the sub-bullet below says "the lowest is used", but not that it is one sensor for all modes).

I'll say so in this bullet, and point to FlexMeasures/flexmeasures#2832 (each operation mode stating its own efficiency) as the follow-up, since separate sensors per mode only help once the scheduler can use them per mode.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Clarified in #288, with a pointer to FlexMeasures/flexmeasures#2832.

Comment thread CHANGELOG-S2.rst
- The hard bounds ``soc-min`` and ``soc-max`` are now zero and ``hard_bound_factor`` times the top of the range, raised to include a state of charge above it. In v0.9.0, the declared range itself was the hard bound. So a storage that really was outside it, for example floating above the top or left below the bottom, made the problem infeasible: there was no schedule at all, or one from a fallback scheduler. A storage reported below zero is planned from zero, with a warning, because FlexMeasures refuses a negative ``soc-min``.
- FlexMeasures older than v0.26.0 cannot relax the band. Then the band is left out, and this is logged, so the storage is planned anywhere between the wide hard bounds.
- The usage forecast is sent as ``soc-usage`` if its sensor is given and the resource manager provides a forecast (or has sent one anyway).
- The leakage behaviour is recorded as a ``storage-efficiency`` with each schedule request, over the window of the schedule. It uses the leakage at the starting fill level, as a share of that fill level (or of the middle of the range, if the storage starts below that). It is only named in the flex model if FlexMeasures confirmed storing it. FlexMeasures takes a storage efficiency as a share of the stock, whereas S2 states a rate, so the loss is exact only at that one fill level.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If S2 states a rate, shouldn't this then be better modelled as an extra component in the soc-usage list?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I think that is more faithful. S2 states leakage as a rate per fill-level range, in fill-level units per second. Recording it as a storage efficiency turns that rate into a share of the fill level, so the plan has the storage leak proportionally to how full it is. That is only right where leakage really is proportional, and S2 does not say it is.

As an extra soc-usage component, the rate is used as stated, and constant within a range, which is what S2 describes. Two caveats:

  • A flat rate keeps draining at an empty storage, where physical leakage stops. That is the same situation as a usage forecast the storage cannot absorb, which the usage correction in feat(FRBC): correct a usage forecast no plan can absorb, instead of clipping it #286 handles.
  • The rate depends on the fill-level range, and the plan moves the fill level. With one series, we would still pick the range of the starting fill level, as now.

I'd make this a follow-up PR on top of #286, since it relies on the correction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in #289, stacked on #286.

Comment thread CHANGELOG-S2.rst
- The first ``FRBC.TimerStatus`` for each timer also causes the warning. Every timer status is now stored.

[`#273 <https://github.com/FlexMeasures/flexmeasures-client/pull/273>`__]
* The example resource manager is now a heat pump that keeps a hot-water buffer warm.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Potential follow-up: adding this to the FM QA workflow.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, this example resource manager would make a good S2 end-to-end check in FlexMeasures' QA workflow. Shall I open an issue for it in the FlexMeasures repo?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread CHANGELOG-S2.rst
* ``FillRateBasedControlTUNES`` records an ``FRBC.LeakageBehaviour`` instead of answering it with ``PERMANENT_ERROR``. It read its fill-level scale from a misspelt attribute, and the error that raised was taken for a failed post, so no leakage behaviour was ever recorded [`#255 <https://github.com/FlexMeasures/flexmeasures-client/pull/255>`__]
* A full message history now keeps the newest messages. It used to make room by evicting the newest entry, so it kept a session's first messages and lost all later ones except the last. A message stored again under the same id now counts as the latest [`#274 <https://github.com/FlexMeasures/flexmeasures-client/pull/274>`__]
* ``Handler.is_revoked()`` now finds objects the resource manager revoked. The CEM and its handlers stored a revoked object's id as a ``UUID``, so a lookup by its string id always missed [`#257 <https://github.com/FlexMeasures/flexmeasures-client/pull/257>`__]
* An FRBC actuator status that arrives before any system description is logged and skipped, instead of raising an error in a background task. This can happen after a reconnection [`#274 <https://github.com/FlexMeasures/flexmeasures-client/pull/274>`__]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Potential follow-up: keep around the statuses until a system description came in, then process them.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed. Since an actuator status describes the current state, only the latest per actuator matters: the handler could keep that one and process it as soon as a system description arrives, instead of dropping it. That is a small change; I can make it a follow-up PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in #287: the latest status per actuator is kept, and recorded once a system description arrives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants