fix(pv-system-roi): repair Cumulative Value panel, honest current-power stat, unit + gap fixes #285

Merged
k.renner merged 1 commits from claude-bot/dash-pv-system-roi into master 2026-07-17 13:16:01 +00:00
Member

Fresh-eyes review of the PV System ROI dashboard after the #273/#274 repoint. The financial core checked out against live HA data; four real defects were found and fixed.

Before

before-top
before-bottom

Note in the Before shots: "Solar Power 3.04 kW" at 00:20 at night, Cumulative Value drawing only the yellow Feed-in Revenue line, lowercase "kwh" unit suffixes, and the smooth fake decay ramps bridging late 2025 in all three Power-Flow panels.

After

after-top
after-bottom

Review report

1. CRITICAL — Cumulative Value panel: "Self-Use Savings" and "Total Value" series rendered nothing — FIXED
Only Feed-in Revenue was drawn (see Before/bottom). Two compounding causes, isolated empirically via /api/ds/query: (a) the union'd solar/export/price tables carry different group keys, so pivot() ran per-table and the exists solar and exists export and exists price filter always failed; (b) strompreis is written so rarely that its points almost never land in the same aggregation window as the energy counters. Fix: |> group() before the pivot plus sort() + fill(column: ..., usePrevious: true) for all three columns. Group-only and fill-only variants both still returned 0 points; the combination returns 471 points ending at EUR 4363.36 — matching the Self-Use Savings stat exactly.

2. MAJOR — "Solar Power" stat showed a ~17-hour mean, not current power — FIXED
The query used aggregateWindow(mean) over the dashboard range (3 years -> ~17 h windows), so at 00:20 at night it displayed 3.04 kW while the live sensor read 0.0 W (verified against HA). Now |> last(); the After shot honestly shows 0 W at night. Sparkline dropped (graphMode: none) since a single instant value has no series.

3. MAJOR — "Solar Installed" off by x1000 — FIXED
input_number.e3_vitocharge_03_solar_power_installed stores 8140 with a kWp unit label, but the value is plainly Wp (8.14 kWp system). With panel unit kwatt it would render 8.14 MW. Added / 1000.0 -> 8.14 kW, and a panel description documenting the quirk. (Alternative: fix the input_number in HA to 8.14 — say the word and I'll adjust the panel back.)

4. MINOR — fake "decay ramps" across a real data hole — FIXED
The e3_vitocharge_03_* series have a genuine gap Nov 2025 – Feb 2026 (verified: 0 points/month in InfluxDB; data resumes Mar 2026). With createEmpty: false the aggregation emitted no null rows, so Grafana connected the line straight across ~4 months, drawing slow declines that never happened (visible in all three Power-Flow panels in the Before shot). Now createEmpty: true on panels 20/21/22 — gaps render as gaps. Cumulative panels (23/24) intentionally keep spanNulls: true since counters are monotonic.

5. MINOR — invalid unit id kwh — FIXED
kwh is not a Grafana unit id, so panels rendered a literal lowercase "kwh" suffix with no scaling. Replaced with kwatth everywhere (8 occurrences) -> proper kWh/MWh formatting (e.g. Total Production now "20.0 MWh").

QUESTION — feed-in tariff 0.082 vs HA 0.08
The dashboard variable feed_in_tariff is 0.082 EUR/kWh, but input_number.einspeiseverguetung in HA says 0.08 (and the Heat Pump ROI dashboard uses 0.08 as solar price). Which is the actual contract tariff? Impact is small (~EUR 13 lifetime) but the two sources should agree. Not changed.

NOT CHANGED (deliberate)

  • Self-Use Savings applies the current strompreis (0.3271) to all historical self-consumption — an approximation, but the established design; historical price data barely exists in the bucket.
  • "Production Today = 0 kWh" in both screenshots is correct: rendering happened shortly after midnight and the sensor had reset.

Verification

  • Live HA states cross-checked at review time (00:20 CEST): pv_power 0.0 W, pv_production_total 20038.72 kWh, grid import 28088.2 / export 6699.2 kWh, battery SoC 65 %, battery_energy 9790 Wh, backup_discharge_limit 20 %, battery_installed_capacity 15 kWh.
  • Financial math re-derived by hand: self-consumed 13339.5 kWh ✓, savings EUR 4363 ✓, revenue EUR 549 ✓, total EUR 4913 ✓, ROI 17.4 % ✓, payback 18.1 y ✓, self-consumption 66.6 % ✓ — all match the panels (sanity values from the #273/#274 fix still hold).
  • Fixed Flux queries executed against the live datasource via /api/ds/query before committing; e3 data gap confirmed by monthly point counts.
  • After shots rendered from a temp preview (pv-system-roi-preview), deleted afterwards. Bottom row verified: Battery Capacity 15 kWh, Solar Installed 8.14 kW, Battery Energy 9.51 kWh, Reserved Energy 3 kWh (= 15 x 20 %).

🤖 Generated with Claude Code

Fresh-eyes review of the PV System ROI dashboard after the #273/#274 repoint. The financial core checked out against live HA data; four real defects were found and fixed. ## Before ![before-top](https://gitea.renner.uno/attachments/74efb2d2-a17b-4cd6-8d8d-1086cf2f7b22) ![before-bottom](https://gitea.renner.uno/attachments/d2fa2fdc-b42d-4bef-abbd-c35855b58662) Note in the Before shots: "Solar Power 3.04 kW" at 00:20 at night, Cumulative Value drawing only the yellow Feed-in Revenue line, lowercase "kwh" unit suffixes, and the smooth fake decay ramps bridging late 2025 in all three Power-Flow panels. ## After ![after-top](https://gitea.renner.uno/attachments/fb0dc4eb-4814-4d31-a4d0-ae39b543b8d3) ![after-bottom](https://gitea.renner.uno/attachments/18b3f5d9-79f9-4efb-85a4-821a23baa4be) ## Review report **1. CRITICAL — Cumulative Value panel: "Self-Use Savings" and "Total Value" series rendered nothing** — FIXED Only Feed-in Revenue was drawn (see Before/bottom). Two compounding causes, isolated empirically via `/api/ds/query`: (a) the union'd solar/export/price tables carry different group keys, so `pivot()` ran per-table and the `exists solar and exists export and exists price` filter always failed; (b) `strompreis` is written so rarely that its points almost never land in the same aggregation window as the energy counters. Fix: `|> group()` before the pivot plus `sort()` + `fill(column: ..., usePrevious: true)` for all three columns. Group-only and fill-only variants both still returned 0 points; the combination returns 471 points ending at EUR 4363.36 — matching the Self-Use Savings stat exactly. **2. MAJOR — "Solar Power" stat showed a ~17-hour mean, not current power** — FIXED The query used `aggregateWindow(mean)` over the dashboard range (3 years -> ~17 h windows), so at 00:20 at night it displayed **3.04 kW** while the live sensor read **0.0 W** (verified against HA). Now `|> last()`; the After shot honestly shows 0 W at night. Sparkline dropped (`graphMode: none`) since a single instant value has no series. **3. MAJOR — "Solar Installed" off by x1000** — FIXED `input_number.e3_vitocharge_03_solar_power_installed` stores **8140** with a `kWp` unit label, but the value is plainly Wp (8.14 kWp system). With panel unit `kwatt` it would render **8.14 MW**. Added `/ 1000.0` -> **8.14 kW**, and a panel description documenting the quirk. (Alternative: fix the input_number in HA to 8.14 — say the word and I'll adjust the panel back.) **4. MINOR — fake "decay ramps" across a real data hole** — FIXED The `e3_vitocharge_03_*` series have a genuine gap **Nov 2025 – Feb 2026** (verified: 0 points/month in InfluxDB; data resumes Mar 2026). With `createEmpty: false` the aggregation emitted no null rows, so Grafana connected the line straight across ~4 months, drawing slow declines that never happened (visible in all three Power-Flow panels in the Before shot). Now `createEmpty: true` on panels 20/21/22 — gaps render as gaps. Cumulative panels (23/24) intentionally keep `spanNulls: true` since counters are monotonic. **5. MINOR — invalid unit id `kwh`** — FIXED `kwh` is not a Grafana unit id, so panels rendered a literal lowercase "kwh" suffix with no scaling. Replaced with `kwatth` everywhere (8 occurrences) -> proper kWh/MWh formatting (e.g. Total Production now "20.0 MWh"). **QUESTION — feed-in tariff 0.082 vs HA 0.08** The dashboard variable `feed_in_tariff` is **0.082 EUR/kWh**, but `input_number.einspeiseverguetung` in HA says **0.08** (and the Heat Pump ROI dashboard uses 0.08 as solar price). Which is the actual contract tariff? Impact is small (~EUR 13 lifetime) but the two sources should agree. Not changed. **NOT CHANGED (deliberate)** - Self-Use Savings applies the *current* strompreis (0.3271) to all historical self-consumption — an approximation, but the established design; historical price data barely exists in the bucket. - "Production Today = 0 kWh" in both screenshots is correct: rendering happened shortly after midnight and the sensor had reset. ## Verification - Live HA states cross-checked at review time (00:20 CEST): pv_power 0.0 W, pv_production_total 20038.72 kWh, grid import 28088.2 / export 6699.2 kWh, battery SoC 65 %, battery_energy 9790 Wh, backup_discharge_limit 20 %, battery_installed_capacity 15 kWh. - Financial math re-derived by hand: self-consumed 13339.5 kWh ✓, savings EUR 4363 ✓, revenue EUR 549 ✓, total EUR 4913 ✓, ROI 17.4 % ✓, payback 18.1 y ✓, self-consumption 66.6 % ✓ — all match the panels (sanity values from the #273/#274 fix still hold). - Fixed Flux queries executed against the live datasource via `/api/ds/query` before committing; e3 data gap confirmed by monthly point counts. - After shots rendered from a temp preview (`pv-system-roi-preview`), deleted afterwards. Bottom row verified: Battery Capacity 15 kWh, Solar Installed 8.14 kW, Battery Energy 9.51 kWh, Reserved Energy 3 kWh (= 15 x 20 %). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-07-16 22:50:58 +00:00
- Cumulative Value: Self-Use Savings and Total Value series rendered nothing.
  The union'd solar/export/price tables had mismatched group keys (pivot ran
  per-table) AND the sparse strompreis series almost never landed in the same
  aggregation window. Fix: group() before pivot + fill(usePrevious) per column.
  Verified via /api/ds/query: 471 points, final value EUR 4363 == stat panel.
- Solar Power stat: was aggregateWindow(mean) over the dashboard range (~17 h
  windows at 3 y), showing 3.04 kW at midnight. Now last() -> honest 0 W at night.
- Solar Installed: HA input_number stores 8140 (Wp, mislabeled kWp); with unit
  kwatt the panel would read 8.14 MW. Divide by 1000 -> 8.14 kW, documented.
- Timeseries gap honesty: e3_* series have a real data hole (Nov 2025 - Feb 2026,
  verified 0 points/month). createEmpty:false let Grafana draw fake decay ramps
  across it. Now createEmpty:true so gaps render as gaps (panels 20/21/22).
- Units: 'kwh' is not a valid Grafana unit id (rendered lowercase 'kwh');
  replaced with 'kwatth' everywhere -> proper kWh/MWh scaling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
Member

Scrape-residue check (negative result): the duplicate-Prometheus-scrape residue that hit the heat-pump dashboards (2 jobs x N pod lifetimes, up to ~30 series/entity on pre-2026-07-16 windows) does not affect this dashboard. All 26 panels are InfluxDB/Flux — zero Prometheus targets — and InfluxDB is written by HA's single influxdb integration, not the scrape path.

Verified explicitly over the full 2023->now window via the live datasource:

  • pv_production_total, grid_feed_in_energy, grid_supplied_energy, strompreis: exactly 1 series each, last values matching live HA (20038720 Wh / 6699.2 kWh / 28088.2 kWh / 0.3271 EUR/kWh).
  • e3_vitocharge_03_solar_power has two series keys (a tag rename in April 2026), but they are strictly sequential — daily point counts across the boundary are a steady 14-22/day with no doubling. The panels union/ungroup before aggregating, so this is already handled.
**Scrape-residue check (negative result):** the duplicate-Prometheus-scrape residue that hit the heat-pump dashboards (2 jobs x N pod lifetimes, up to ~30 series/entity on pre-2026-07-16 windows) **does not affect this dashboard**. All 26 panels are InfluxDB/Flux — zero Prometheus targets — and InfluxDB is written by HA's single influxdb integration, not the scrape path. Verified explicitly over the full 2023->now window via the live datasource: - `pv_production_total`, `grid_feed_in_energy`, `grid_supplied_energy`, `strompreis`: exactly **1 series each**, last values matching live HA (20038720 Wh / 6699.2 kWh / 28088.2 kWh / 0.3271 EUR/kWh). - `e3_vitocharge_03_solar_power` has two series *keys* (a tag rename in April 2026), but they are strictly sequential — daily point counts across the boundary are a steady 14-22/day with no doubling. The panels union/ungroup before aggregating, so this is already handled.
k.renner merged commit 9e3385e821 into master 2026-07-17 13:16:01 +00:00
Sign in to join this conversation.