Skip to content

Investigate inter-source price-chart lag (EZ vs EPEX vs ENTSO-E 1-2h shifts) #23

Description

@ducroq

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)

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

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

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

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

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

  1. Wait until after augur#X (chart axis normalisation to browser-local) lands so axis labels are correct
  2. Reproduce the lag at a known time
  3. Hover each trace at the same wall-clock time and check what `%{x}` reports for each
  4. Walk through `static/js/modules/data-processor.js` for each source's processing path and identify divergences
  5. Check whether `energyZeroData.today_prices` is the same as `energyZeroData.data.values()` or a derived array
  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions