Skip to content

Fill the repeated DST hour from the next forecast value - #101

Open
Miggets7 wants to merge 3 commits into
mainfrom
fix/distro-dst-repeated-hour-fill
Open

Miggets7 wants to merge 3 commits into
mainfrom
fix/distro-dst-repeated-hour-fill

Conversation

@Miggets7

@Miggets7 Miggets7 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Description

The fall-back day has 100 ISPs, and a producer writing a fixed 96-slot day leaves the four ISPs of the repeated hour without a predicted datapoint. Those positions went out as 0.0, which posts a real zero trading position for an hour that exists.

They now reuse the next forecast value of the day. Only an ISP whose market local time repeats in the day's grid qualifies, so every other day is byte-identical to before and every other gap still goes out as 0.0.

Commit messages carry the scope reasoning, the storage-frame behaviour and the test detail.

Checklist

  • 1. Acceptance criteria of the linked issue(s) are met
  • 2. Tests are written and all tests pass
  • 3. Changes are manually tested by you and the reviewer
  • 4. Documentation is written or updated

The fall-back day has 100 ISPs, but a forecast producer that writes a
fixed 96-slot day leaves the four ISPs of the repeated hour without a
predicted datapoint of their own. Those positions were submitted as 0.0,
which posts a real zero trading position for an hour that genuinely
exists.

They now reuse the next forecast value of the day instead. The scope is
deliberately narrow: an ISP qualifies only when its market local time
occurs more than once in the day's ISP grid, which is true of the
repeated hour and nothing else. Every other gap still goes out as 0.0,
since it means the producer skipped intervals it covered or its horizon
ended, and both are already logged. A repeated-hour ISP with nothing
left in the day to borrow from also falls back to 0.0, because the API
requires a volume on every entry.

The interior/trailing gap counters move into the fill pass. The old
formula derived interior gaps by subtracting the trailing ones from the
total, which would now count every position filled from a neighbour as a
gap that went out as 0.0, over-reporting by exactly that many. A filled
position is always interior, since filling requires a real value later
in the day, so the subtraction could not go negative. It would just be
wrong.

The collapse test can no longer enumerate every position holding the
seeded value: the positions between the two passes of the repeated hour
now borrow that same value, so a read and a fill are indistinguishable
by value alone. It asserts the two passes directly instead.
Three cases, all under a UTC storage frame so the two passes of the
fall-back hour keep separate rows and a dropped ISP is a genuinely empty
position rather than a collapsed one.

Dropping either pass of the repeated hour leaves 96 of 100 ISPs covered,
the shape a producer writing a fixed 96-slot day produces, and the
emptied positions take the next real value. A forecast that ends before
the repeated hour has nothing to borrow, so those positions stay 0.0. A
gap in the afternoon of the same day stays 0.0 too, which pins the scope
to the repeated hour rather than to the DST day as a whole.
The javadoc claimed the method "becomes exact without changing" once
predicted datapoints move to UTC. That is not true of the repeated hour.
While the storage frame collapses the hour, both passes read the one
surviving row and carry its distinct values. Under UTC each instant gets
its own row, a producer that writes the hour once leaves the other pass
empty, and that pass is filled flat on the next forecast value. The
submitted day changes on the fall-back date when openremote/openremote#3292
lands, so the javadoc now says so and says the flat hour is deliberate.

Also drops the claim that the rule covers "those four ISPs": detection is
generic over any repeated market local time, which is two ISPs against a
half-hour transition such as Australia/Lord_Howe.

Test gaps:

- Nothing pinned that a filled 0.0 is never itself a fill source. A
  refactor updating the carried value on every position, not just real
  ones, passed the whole suite and flattened the repeated hour to 0.0.
  The new case leaves the hour and the two ISPs after it unwritten, so
  the scan has to cross two filled zeros to reach 03:30.
- The parameterised case asserted the dropped positions and everything
  outside the hour, never the pass that kept its rows, so a fill
  overwriting real values inside the hour went unnoticed.
- The Amsterdam collapse guard only asserted "not 0.0", which holds for
  almost any wrong value. It now pins what each pass reads.
- Comparing against a repeated list rather than calling every() also
  pins the element count.

Hoists a duplicated toLocalDateTime() call in the detection loop.
@Miggets7 Miggets7 added the Bug Error or issue in the system label Sep 18, 2026
@Miggets7
Miggets7 requested a review from wborn September 18, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Error or issue in the system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant