fix(fw): EPEX spot price-forecast widget broken — chart doesn't render, shows a bare entry count #222

Open
opened 2026-07-06 23:28:20 +02:00 by dominik.polakovics · 1 comment

Summary

The EPEX spot price-forecast widget on fw's Home Assistant (added in #218 / closes #217) does not work: instead of a price-forecast chart it shows a single small number, and nothing that looks like a forecast. Root cause is two independent code-level defects, both confirmed from source (the fee/VAT math and the attribute mapping are not the problem — see "What is actually correct" below).

Observed behaviour

  • The widget shows a single-digit / small integer instead of a price.
  • It does not render as a chart ("does not look like a forecast").

Root cause

1. apexcharts-card is never loaded — HA is in storage mode, customLovelaceModules only auto-registers in yaml mode

electricity.nix installs the card declaratively:

services.home-assistant.customLovelaceModules = [
  pkgs.home-assistant-custom-lovelace-modules.apexcharts-card
];

The NixOS module ships and serves the JS correctly — www/nixos-lovelace-modulesapexcharts-card.js is symlinked (home-assistant.nix:678-679), so /local/nixos-lovelace-modules/apexcharts-card.js?2.2.3 is reachable — and it adds the resource to lovelace.resources in configuration.yaml (home-assistant.nix:146-151).

But that config key is only honoured in yaml mode. The module says so explicitly:

home-assistant.nix:322"Automatic loading only works with lovelace in yaml mode."

fw's HA runs in the default storage (UI-managed) mode: lovelace.mode defaults to storage unless lovelaceConfig/lovelaceConfigFile is set (home-assistant.nix:431-445), and a repo-wide grep confirms neither is set anywhere in hosts/ or utils/. In storage mode HA ignores YAML-defined lovelace.resources — resources must be registered via the UI (Settings → Dashboards → Resources) or .storage/lovelace_resources.

⇒ The custom:apexcharts-card element is never registered, so a card of type: custom:apexcharts-card cannot render as a chart. This is the "does not look like a forecast" symptom.

(Note: the sibling lovelace-scheduler card in ac.nix uses the same mechanism; if it renders today it was registered manually via the UI at some earlier point — the same one-time step apexcharts now needs.)

2. The forecast sensor's state is an entry count, not a price

sensor.electricity_price_forecast (electricity.nix:54-72) puts the actual forecast in a forecast attribute and sets its state to the number of future intervals:

{{ (state_attr('sensor.epex_spot_data_market_price', 'data') or [])
   | map(attribute='end_time') | map('as_datetime') | select('gt', now())
   | list | count }}

So whenever this entity is shown as a plain value — an Entities/Sensor card, the sidebar, or an ApexCharts series that has no working data_generator — you see a small integer (the count of remaining hourly/15-min slots), never a price. This is the "single digits instead of the price" symptom. The price series is only reachable through the card's data_generator reading entity.attributes.forecast, which cannot fire while defect #1 blocks the card from loading at all.

3. Contributing / process: the chart YAML lives only in UI storage, never verified

The chart was pasted by hand into a storage-mode dashboard; the YAML is captured in the #218 PR description but is not in the repo, so it is not reproducible and was never reviewable. PR #218's own "post-merge verification" left "paste the card YAML onto a dashboard" and "confirm interval resolution" as unchecked human follow-ups — i.e. the widget was merged without ever being confirmed working end-to-end. A mis-pasted multi-line data_generator: also silently breaks the forecast series.

What is actually correct (do not "fix" these)

Verified against the deployed epex_spot 4.1.0 source (.../const.py, localization.py, sensor.py):

  • Attribute keys start_time / end_time / price_per_kwh are correct for EUR (ATTR_PRICE_PER_KWH = "price_per_kwh"); this resolves the "open question" from #217.
  • sensor.epex_spot_data_market_price is already in €/kWh (uom_per_kwh), so (market + fees) × 1.20 is dimensionally consistent — no 1000× unit error.
  • The fee-model constants and 20 % VAT in sensor.electricity_price are correct.
  • The card YAML captured in #218 is itself correct — it uses a proper data_generator: return entity.attributes.forecast.map(...).

Not yet verified live

I was blocked from reading the running fw HA instance (production SSH read requires explicit approval), so I could not confirm the exact on-screen manifestation — i.e. whether the user sees a "Custom element doesn't exist: apexcharts-card" error card (defect #1) or a bare entity showing the count (defect #2). Both are downstream of the same two root causes and the fix is the same. If you want live confirmation before/while fixing, check Developer Tools → States → sensor.electricity_price_forecast: its state should be an integer (the count) and forecast should be a populated list of {start, price}; also note the interval resolution (hourly vs 15-min).

Suggested fix (pick a direction during triage)

Defect #1 must be fixed for the chart to render; #2/#3 decide how robust and reproducible it is.

  • Option A — minimal: register the resource once via the UI (Settings → Dashboards → Resources → URL /local/nixos-lovelace-modules/apexcharts-card.js, type JavaScript Module). Persists in .storage/lovelace_resources. Fixes rendering; card stays in UI storage (still not reproducible).
  • Option B — reproducible (recommended): define the price dashboard (or a dedicated dashboard) in yaml mode via services.home-assistant.lovelaceConfig / a yaml dashboards entry. This makes customLovelaceModules auto-register the resource and moves the card YAML into Nix (fixes #1 and #3 together). Trade-off: a yaml-mode dashboard is no longer UI-editable.
  • Optionally (#2): make the forecast sensor's state meaningful when shown bare (e.g. the current consumer price) and/or document that the widget requires the data_generator.

Acceptance criteria

  • apexcharts-card resource is loaded (chart renders; no "Custom element doesn't exist" error).
  • The widget renders a price-forecast chart (today + tomorrow) from the forecast attribute, not a bare number.
  • Live state of sensor.electricity_price_forecast confirmed in Developer Tools (state = count, forecast = populated list; interval resolution noted).
  • If Option B: the card YAML lives in Nix and the fw pre-commit dry-build is green.
  • Verified working on the running instance (not just built).

References

  • Broken widget: hosts/fw/modules/home-assistant/electricity.nix:54-72 (forecast sensor), :25-27 (card install)
  • Prior work: #218 (merged, closes #217)
  • NixOS HA module: nixos/modules/services/home-automation/home-assistant.nix — resources :146-151, mode default :431-445, yaml-mode-only note :322, www symlink :678-679
  • Deployed component: mampfes/epex_spot 4.1.0; keys in .../epex_spot/const.py
  • Related: #137 (fw HA legacy-template migration + 2026.6 pin)
## Summary The EPEX spot **price-forecast widget** on `fw`'s Home Assistant (added in #218 / closes #217) does not work: instead of a price-forecast chart it shows **a single small number**, and **nothing that looks like a forecast**. Root cause is two independent code-level defects, both confirmed from source (the fee/VAT math and the attribute mapping are *not* the problem — see "What is actually correct" below). ## Observed behaviour - The widget shows a **single-digit / small integer** instead of a price. - It **does not render as a chart** ("does not look like a forecast"). ## Root cause ### 1. `apexcharts-card` is never loaded — HA is in *storage* mode, `customLovelaceModules` only auto-registers in *yaml* mode `electricity.nix` installs the card declaratively: ```nix services.home-assistant.customLovelaceModules = [ pkgs.home-assistant-custom-lovelace-modules.apexcharts-card ]; ``` The NixOS module ships and serves the JS correctly — `www/nixos-lovelace-modules` → `apexcharts-card.js` is symlinked (`home-assistant.nix:678-679`), so `/local/nixos-lovelace-modules/apexcharts-card.js?2.2.3` is reachable — and it adds the resource to **`lovelace.resources` in `configuration.yaml`** (`home-assistant.nix:146-151`). **But that config key is only honoured in yaml mode.** The module says so explicitly: > `home-assistant.nix:322` — *"Automatic loading only works with lovelace in `yaml` mode."* fw's HA runs in the **default storage (UI-managed) mode**: `lovelace.mode` defaults to `storage` unless `lovelaceConfig`/`lovelaceConfigFile` is set (`home-assistant.nix:431-445`), and a repo-wide grep confirms **neither is set anywhere** in `hosts/` or `utils/`. In storage mode HA ignores YAML-defined `lovelace.resources` — resources must be registered via the UI (Settings → Dashboards → Resources) or `.storage/lovelace_resources`. ⇒ The `custom:apexcharts-card` element is never registered, so a card of `type: custom:apexcharts-card` cannot render as a chart. This is the "does not look like a forecast" symptom. *(Note: the sibling `lovelace-scheduler` card in `ac.nix` uses the same mechanism; if it renders today it was registered manually via the UI at some earlier point — the same one-time step apexcharts now needs.)* ### 2. The forecast sensor's **state is an entry count, not a price** `sensor.electricity_price_forecast` (`electricity.nix:54-72`) puts the actual forecast in a **`forecast` attribute** and sets its **state to the number of future intervals**: ```jinja {{ (state_attr('sensor.epex_spot_data_market_price', 'data') or []) | map(attribute='end_time') | map('as_datetime') | select('gt', now()) | list | count }} ``` So whenever this entity is shown as a plain value — an Entities/Sensor card, the sidebar, or an ApexCharts series that has no working `data_generator` — you see a **small integer** (the count of remaining hourly/15-min slots), never a price. This is the "single digits instead of the price" symptom. The price series is only reachable through the card's `data_generator` reading `entity.attributes.forecast`, which cannot fire while defect #1 blocks the card from loading at all. ### 3. Contributing / process: the chart YAML lives only in UI storage, never verified The chart was pasted by hand into a storage-mode dashboard; the YAML is captured in the #218 PR description but is **not in the repo**, so it is not reproducible and was never reviewable. PR #218's own "post-merge verification" left *"paste the card YAML onto a dashboard"* and *"confirm interval resolution"* as **unchecked human follow-ups** — i.e. the widget was merged without ever being confirmed working end-to-end. A mis-pasted multi-line `data_generator:` also silently breaks the forecast series. ## What is actually correct (do not "fix" these) Verified against the deployed `epex_spot` 4.1.0 source (`.../const.py`, `localization.py`, `sensor.py`): - Attribute keys `start_time` / `end_time` / `price_per_kwh` are **correct** for EUR (`ATTR_PRICE_PER_KWH = "price_per_kwh"`); this resolves the "open question" from #217. - `sensor.epex_spot_data_market_price` is already in **€/kWh** (`uom_per_kwh`), so `(market + fees) × 1.20` is dimensionally consistent — no 1000× unit error. - The fee-model constants and 20 % VAT in `sensor.electricity_price` are correct. - The card YAML captured in #218 is itself correct — it uses a proper `data_generator: return entity.attributes.forecast.map(...)`. ## Not yet verified live I was blocked from reading the running `fw` HA instance (production SSH read requires explicit approval), so I could not confirm the exact on-screen manifestation — i.e. whether the user sees a *"Custom element doesn't exist: apexcharts-card"* error card (defect #1) or a bare entity showing the count (defect #2). Both are downstream of the same two root causes and the fix is the same. If you want live confirmation before/while fixing, check **Developer Tools → States → `sensor.electricity_price_forecast`**: its state should be an integer (the count) and `forecast` should be a populated list of `{start, price}`; also note the interval resolution (hourly vs 15-min). ## Suggested fix (pick a direction during triage) Defect #1 must be fixed for the chart to render; #2/#3 decide how robust and reproducible it is. - **Option A — minimal:** register the resource once via the UI (Settings → Dashboards → Resources → URL `/local/nixos-lovelace-modules/apexcharts-card.js`, type *JavaScript Module*). Persists in `.storage/lovelace_resources`. Fixes rendering; card stays in UI storage (still not reproducible). - **Option B — reproducible (recommended):** define the price dashboard (or a dedicated dashboard) in **yaml mode** via `services.home-assistant.lovelaceConfig` / a yaml `dashboards` entry. This makes `customLovelaceModules` auto-register the resource **and** moves the card YAML into Nix (fixes #1 and #3 together). Trade-off: a yaml-mode dashboard is no longer UI-editable. - **Optionally (#2):** make the forecast sensor's state meaningful when shown bare (e.g. the current consumer price) and/or document that the widget requires the `data_generator`. ## Acceptance criteria - [ ] `apexcharts-card` resource is loaded (chart renders; no "Custom element doesn't exist" error). - [ ] The widget renders a **price-forecast chart** (today + tomorrow) from the `forecast` attribute, not a bare number. - [ ] Live state of `sensor.electricity_price_forecast` confirmed in Developer Tools (state = count, `forecast` = populated list; interval resolution noted). - [ ] If Option B: the card YAML lives in Nix and the `fw` pre-commit dry-build is green. - [ ] Verified working on the running instance (not just built). ## References - Broken widget: `hosts/fw/modules/home-assistant/electricity.nix:54-72` (forecast sensor), `:25-27` (card install) - Prior work: #218 (merged, closes #217) - NixOS HA module: `nixos/modules/services/home-automation/home-assistant.nix` — resources `:146-151`, mode default `:431-445`, yaml-mode-only note `:322`, www symlink `:678-679` - Deployed component: `mampfes/epex_spot` 4.1.0; keys in `.../epex_spot/const.py` - Related: #137 (fw HA legacy-template migration + 2026.6 pin)
Author
Owner

Correction — real root cause found (not the resource, not a code defect)

Diagnosed live with the reporter. The deployed card turned out to be the apexcharts-card demo/example config — the stock template the UI card picker inserts, left unedited:

// REMOVE ME
data.push([now.getTime() - i*1000*60*60, Math.floor((Math.random() * 10) + 1)])

It ignores the sensor and plots Math.random()*10+1 — random integers 1–10. That random 1–10 data is the "single digits"; two random series is why it "doesn't look like a forecast." The real card YAML from this PR was never pasted in over the demo.

This refutes Defect #1 (apexcharts resource not loaded in storage mode)

The resource is loaded from configuration.yaml via customLovelaceModules — confirmed live two ways: (1) HA's Resources UI reports "Your resources are in YAML mode … manage them in configuration.yaml" (i.e. a ResourceYAMLCollection is active), and (2) the card type is selectable from the picker. So on this instance (HA 2026.5.4), a YAML-defined resources: block is honoured regardless of dashboard mode; the "storage mode ignores YAML resources" reasoning (and the module's :322 note) does not apply here. Defect #2 (state is an entry count) is real but secondary — it only matters if a card shows the state bare.

Actual root cause

The widget was merged with the demo card left in place — the "paste the card YAML onto a dashboard" post-merge step from this PR was never completed (the process gap flagged as the contributing factor is the actual cause).

Fix (applied via UI, no repo change)

Replace the card's YAML with the real config from the PR body (the entity.attributes.forecast.map(...) version). The card config lives in .storage because the dashboard is UI-managed — that is expected and fine.

Optional follow-up

Making the card reproducible in Nix requires a yaml-mode dashboard (lovelaceConfig) — a deliberate, larger change. Track separately only if desired; otherwise this can be closed once the card is replaced.

## Correction — real root cause found (not the resource, not a code defect) Diagnosed live with the reporter. The deployed card turned out to be the **apexcharts-card demo/example config** — the stock template the UI card picker inserts, left unedited: ```js // REMOVE ME data.push([now.getTime() - i*1000*60*60, Math.floor((Math.random() * 10) + 1)]) ``` It ignores the sensor and plots `Math.random()*10+1` — random integers **1–10**. That random 1–10 data *is* the "single digits"; two random series is why it "doesn't look like a forecast." The real card YAML from this PR was never pasted in over the demo. ### This refutes Defect #1 (apexcharts resource not loaded in storage mode) The resource **is** loaded from `configuration.yaml` via `customLovelaceModules` — confirmed live two ways: (1) HA's Resources UI reports *"Your resources are in YAML mode … manage them in configuration.yaml"* (i.e. a `ResourceYAMLCollection` is active), and (2) the card type is selectable from the picker. So on this instance (HA 2026.5.4), a YAML-defined `resources:` block is honoured regardless of dashboard mode; the "storage mode ignores YAML resources" reasoning (and the module's `:322` note) does not apply here. **Defect #2** (state is an entry count) is real but secondary — it only matters if a card shows the state bare. ### Actual root cause The widget was merged with the **demo card left in place** — the "paste the card YAML onto a dashboard" post-merge step from this PR was never completed (the process gap flagged as the contributing factor is the actual cause). ### Fix (applied via UI, no repo change) Replace the card's YAML with the real config from the PR body (the `entity.attributes.forecast.map(...)` version). The card config lives in `.storage` because the dashboard is UI-managed — that is expected and fine. ### Optional follow-up Making the card reproducible in Nix requires a yaml-mode dashboard (`lovelaceConfig`) — a deliberate, larger change. Track separately only if desired; otherwise this can be closed once the card is replaced.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos#222
No description provided.