Skip to content

Refresh prices every price point, not once a day - #1257

Open
gskjold wants to merge 2 commits into
mainfrom
fix/1255-stale-price-cursor
Open

Refresh prices every price point, not once a day#1257
gskjold wants to merge 2 commits into
mainfrom
fix/1255-stale-price-cursor

Conversation

@gskjold

@gskjold gskjold commented Sep 1, 2026

Copy link
Copy Markdown
Member

Reported in #1255: with the day tariff set to hours 06–22 and the night tariff to 23–05, the price plot shows the change at 00 instead of 23.

Cause

Not a timezone problem — the device is right. PricePlot.svelte draws each bar from two clocks that drift apart:

  • labels come from the live browser clock, re-rendered every 15 minutes via the scheduleUpdate() timer
  • the array offset is json.cursor, captured when importprice.json was last fetched

And the payload is almost never refetched (ui/src/lib/DataStores.js):

if(data.pe && data.p != lastPrice) { lastPrice = data.p; getPrices(); }   // only on a price change

priceFetchTimeout = setTimeout(getPrices, ((24-date.getHours())*3600000)+10)   // otherwise next midnight

One hour of staleness makes every bar show the price of the hour before its label, so a modifier starting at 23 appears to start at 00 — two hours makes it 01, and so on.

Spot-price users never see this: their current price changes every hour, which refetches the payload and keeps the cursor fresh. On a fixed price the current price only changes when a modifier does, so the plot drifts one bar per hour for as long as the page stays open. The reporter's chart is flat at 1.18 from 15 through 23 while NO-zone spot that day ran 1.31→1.35, and 1.18 − 0.99 = (0.464 − 0.314) × 1.25 — a fixed base plus their two energiledd modifiers plus VAT.

Same root cause makes the plot keep yesterday's array for up to 59 minutes past midnight, since the daily timer fires at hours-to-midnight from whatever minute it last ran.

Change

Refresh on every price point (DataStores.js), the way getDayPlot() already rearms on the next hour:

let resolution = importPrices?.resolution ? importPrices.resolution : 60;
priceFetchTimeout = setTimeout(getPrices, ((resolution-(date.getMinutes()%resolution))*60000)+10)

Follows the payload's own resolution, so 15-minute price data refreshes every 15 minutes. This also covers the midnight rollover, so the separate daily timer is no longer needed.

Advance the cursor by the points passed since the fetch (PricePlot.svelte), so the plot no longer depends on the refresh actually landing. A failed or delayed fetch would otherwise put it straight back to mislabelling, silently:

let pointMs = json.resolution * 60000;
i += Math.floor(Date.now()/pointMs) - Math.floor(json.fetched/pointMs);

The payload is stamped with fetched on arrival. Price points start on the whole hour in the price zone, which is a whole number of hours from UTC, so counting them off epoch boundaries needs no timezone handling. A stale payload now draws the bars it still has under the right labels and then runs out, instead of drawing the wrong ones.

Verification

  • Scheduling lands on the next boundary from every starting minute, at both resolutions: :11 → 49 min at 60-minute resolution, 4 min at 15-minute.
  • Cursor advance keeps label and index aligned at 0, 1, 2 and 9 hours of staleness; across midnight a payload fetched at 23:30 resolves 00:10 to index 24, tomorrow's first point in the array it still holds; at 15-minute resolution 15:05 → 15:41 advances 60 → 62.
  • npm run build clean; ui/dist rebuilt and committed as usual.

🤖 Generated with Claude Code

gskjold and others added 2 commits September 1, 2026 17:48
The price plot labels every bar from the live browser clock, refreshed
every 15 minutes, but reads the array starting at the cursor in the last
importprice.json payload. Those two clocks drift apart, because the
prices are only refetched when the current price changes, or once a day:

    priceFetchTimeout = setTimeout(getPrices, ((24-date.getHours())*3600000)+10)

For an hour of staleness every bar shows the price of the hour before
its label, so a modifier configured to start at 23 appears to start at
00. Users on a spot price never see it, since the hourly change of the
current price refetches the payload for them. On a fixed price the
current price only changes when a modifier does, so the plot drifts a
bar per hour for as long as the page stays open, and keeps showing
yesterday's array for up to 59 minutes past midnight.

Rearm on the next price point instead, the way getDayPlot() already
rearms on the next hour.

Reported in #1255

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Refreshing on every price point keeps the cursor current, but only while
the device answers. A failed or delayed fetch puts the plot back to
labelling from the live clock while indexing from an old cursor, and it
mislabels silently.

Stamp the payload when it arrives and skip the points that have passed
since, so a stale payload draws the bars it still has under the right
labels and then runs out, rather than drawing the wrong ones.

Price points start on the whole hour in the price zone, which is a whole
number of hours from UTC, so counting them off epoch boundaries needs no
timezone handling.

Reported in #1255

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🔧 PR Build Artifacts

Version: 624abe3

All environments built successfully. Download the zip files:

Artifacts expire after 7 days. View workflow run

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant