Symptom
On the Prices tab, the four price traces appear to lag each other by 1-2 hours when they should align hour-by-hour. User-reported observations (2026-06-06 morning, NL CEST):
- EnergyZero Live LEADS EPEX by ~1h on the price-decrease phase
- ENTSO-E LAGS EPEX by ~1h (so EZ leads ENTSO-E by ~2h)
- Augur ML Forecast follows EPEX on the decrease, but switches to follow EnergyZero on the increase (from 12:00 onwards)
- EPEX and ENTSO-E rise similarly but lagged ~2.5h vs EZ and Augur on the increase phase
Visual reference: see screenshot attached on the issue thread (or reproduce on / Prices tab at any morning hour).
Disproven hypothesis: timezone convention drift
Initial suspicion was that the four sources publish in different timezones and JS parses inconsistently. Investigation 2026-06-06 ruled this out:
Inspecting `https://energy.jeroenveen.nl/data/energy_price_forecast.json\`:
- `entsoe`: `'2026-06-05T00:00:00+02:00'` → 85.01 (15-min granularity)
- `epex`: `'2026-06-05T00:00:00+02:00'` → 91.18 (hourly)
- `elspot`: `'2026-06-05T00:00:00+02:00'` → 85.01 (hourly)
- `energy_zero`: `'2026-06-05T00:00:00+02:00'` → 0.199852675 (hourly, EUR/kWh)
All four sources publish ISO 8601 with identical `+02:00` offset. JS `Date()` parser handles `+02:00` correctly and converts to absolute moment in time. So pure absolute-moment math gives perfect alignment.
Also: Augur forecast (`augur_forecast_shadow.json`) publishes with `+00:00` (real UTC) — also a fully-qualified offset, also parsed correctly. So the source-data layer is consistent.
Likely actual causes (to investigate)
-
Per-source filtering in `data-processor.js`: EnergyZero uses `energyZeroData.today_prices.filter(...)` while ENTSO-E and EPEX may use different arrays (`data` vs `today_prices` vs some merged view). If `today_prices` is a subset prepared by a different transformation upstream, it might carry different timestamps from what the raw `data` dict has.
-
15-min vs 60-min granularity rendering: ENTSO-E publishes at 15-min granularity, others hourly. Plotly's line interpolation between points of different granularities can visually look like a lag, particularly when zoom level or hover sample-rate differs.
-
Different per-source cutoff thresholds: `cutoffTime` is computed once per render, but each trace may apply additional snipping (e.g., "only show today's prices" vs "show last 48h"). If a trace's cutoff is at a slightly different point, the visible "tail" of each trace differs.
-
Augur consumer forecast vs ML forecast: the ML forecast and consumer forecast are different traces. The "follows EZ on increase, EPEX on decrease" observation could be the user's eye picking up the consumer-vs-wholesale switching. Worth confirming which Augur trace is which.
-
Hover label timezone (separate from chart axis): the `hovertemplate` uses `%{x}` which Plotly auto-formats. Different format choices per trace might confuse the user, even if the underlying x-positions are correct.
How to investigate
- Wait until after augur#X (chart axis normalisation to browser-local) lands so axis labels are correct
- Reproduce the lag at a known time
- Hover each trace at the same wall-clock time and check what `%{x}` reports for each
- Walk through `static/js/modules/data-processor.js` for each source's processing path and identify divergences
- Check whether `energyZeroData.today_prices` is the same as `energyZeroData.data.values()` or a derived array
- Confirm whether the visual lag persists when sources are dropped one at a time (toggle in legend)
Dependencies
Soft dependency on the chart-axis-normalisation issue (`utcToLocalNaiveISO()` everywhere). Once axis labels are correct, hover-checking each trace's reported `x` will be much easier to diagnose.
Out of scope
- Axis labeling itself (separate issue)
- Adding more data sources (separate scope)
- Changing the chart type or layout
🤖 Generated with Claude Code
Symptom
On the Prices tab, the four price traces appear to lag each other by 1-2 hours when they should align hour-by-hour. User-reported observations (2026-06-06 morning, NL CEST):
Visual reference: see screenshot attached on the issue thread (or reproduce on
/Prices tab at any morning hour).Disproven hypothesis: timezone convention drift
Initial suspicion was that the four sources publish in different timezones and JS parses inconsistently. Investigation 2026-06-06 ruled this out:
Inspecting `https://energy.jeroenveen.nl/data/energy_price_forecast.json\`:
All four sources publish ISO 8601 with identical `+02:00` offset. JS `Date()` parser handles `+02:00` correctly and converts to absolute moment in time. So pure absolute-moment math gives perfect alignment.
Also: Augur forecast (`augur_forecast_shadow.json`) publishes with `+00:00` (real UTC) — also a fully-qualified offset, also parsed correctly. So the source-data layer is consistent.
Likely actual causes (to investigate)
Per-source filtering in `data-processor.js`: EnergyZero uses `energyZeroData.today_prices.filter(...)` while ENTSO-E and EPEX may use different arrays (`data` vs `today_prices` vs some merged view). If `today_prices` is a subset prepared by a different transformation upstream, it might carry different timestamps from what the raw `data` dict has.
15-min vs 60-min granularity rendering: ENTSO-E publishes at 15-min granularity, others hourly. Plotly's line interpolation between points of different granularities can visually look like a lag, particularly when zoom level or hover sample-rate differs.
Different per-source cutoff thresholds: `cutoffTime` is computed once per render, but each trace may apply additional snipping (e.g., "only show today's prices" vs "show last 48h"). If a trace's cutoff is at a slightly different point, the visible "tail" of each trace differs.
Augur consumer forecast vs ML forecast: the ML forecast and consumer forecast are different traces. The "follows EZ on increase, EPEX on decrease" observation could be the user's eye picking up the consumer-vs-wholesale switching. Worth confirming which Augur trace is which.
Hover label timezone (separate from chart axis): the `hovertemplate` uses `%{x}` which Plotly auto-formats. Different format choices per trace might confuse the user, even if the underlying x-positions are correct.
How to investigate
Dependencies
Soft dependency on the chart-axis-normalisation issue (`utcToLocalNaiveISO()` everywhere). Once axis labels are correct, hover-checking each trace's reported `x` will be much easier to diagnose.
Out of scope
🤖 Generated with Claude Code