Heat Pump ROI — post-rebuild review (PR #271 follow-up)
Fresh-eyes review of the rebuilt dashboard. The InfluxDB migration itself (PR #271) is sound — entity unions, unit normalization and the survivor utility_meters all check out against live data. But the review found one real correctness bug (the cost chart silently ignores solar) and two methodology problems in the financial numbers that drive the investment picture.
The chained Flux joins renamed the _value columns to _value_j2 / plain _value, not_value_gi / _value_sp as the final map() expected. Both lookups returned null, so solar_ratio was 0 for every row and the whole "Electricity Cost" line was priced at full grid price. Verified live: the joined rows had gi=null, sp=null, line ended at €4,667 = 14,284 kWh × 0.3271 exactly. Real cost ≈ €3.4K — the chart overstated electricity cost by ~€1.1K (37%) and contradicted the "Running Cost" stat on the same dashboard. Fix: each stream renames its column to a unique name (h/d/gi/ge/sp) before joining. The chart tail now lands at €3.37K, consistent with the stat.
2. MAJOR — blended price included 1.4 years of pre-heat-pump PV history — ✅ FIXED
The self-consumption ratio came from the Vitocharge lifetime counters, which date back to the PV install (May 2023) — 17 months before the heat pump existed. Lifetime ratio: 0.322 → €0.2475/kWh. Ratio from counter deltas since Oct 2024 (verified: gi 22,221 kWh, ge 3,761, sp 16,753): 0.369 → €0.2359/kWh. All cost/savings/ROI stats now use the since-install ratio; the cost chart subtracts the install-date counter baselines so its per-day ratio matches. Effect: running cost €3.54K→€3.37K, savings since install €8.38K→€8.54K.
3. MAJOR — "Projected Annual Savings" used a broken calendar extrapolation — ✅ FIXED
The old formula annualized year-to-date cost with 12 / (month + day/30). Two problems: (a) it overcounts elapsed time by a full month (on Jul 17 it says 7.57 months elapsed; actually 6.55) — in January it would divide by 1.2 instead of 0.5 and wildly misproject; (b) linear calendar extrapolation of a heating load is seasonally biased no matter the divisor. Fix: use the actual trailing-365-day consumption (utility_meter spread, verified live: 7,395 + 993 = 8,388 kWh) × blended price. Retitled "Annual Savings (Trailing 12M)" — a true rolling year with real seasonality, stable in every calendar month. Payback panel updated to match. Values barely move today (€4.70K→€4.67K, 15.5y→15.6y) — but now they're right by construction, not by accident of the date.
4. MINOR — stale legend "Last" values (10,734 / 1,230 / €9.47K) — ✅ FIXED
The counters' friendly_name tag changed on 2026-03-04 (verified in InfluxDB), splitting each entity into two table keys. After keep() the tables merged unsorted, so the legend "Last" calc picked the old era's final row — the Historical panels showed Last = 10,734 kWh while the line correctly ended at 12,577. Fix:group() |> sort(_time) before aggregateWindow in both historical panels. Legends now read 12,577 / 1,707 / €11.9K.
5. MINOR — misleading presentation — ✅ FIXED
"Running Cost (Total)" had green/yellow/red thresholds (3000/5000) on a cumulative-since-install figure — it would sit permanently red as a matter of arithmetic. Now neutral single color and retitled "Running Cost (Since Install)", with a description saying it is not an annual figure.
The €6,650 oil baseline was hardcoded in six places. Now a dashboard variable oil_baseline next to Investment/prices, so the whole ROI model is tunable from the toolbar.
Added descriptions to Total Consumption / Running Cost / Cost This Year.
Reviewed and left as-is
SPF gauges (3.2) and SPF-over-time union — agree with the device and the old ViCare values.
Total Consumption 14,284 kWh — matches both utility_meters (12,577 + 1,707).
Oil baseline model (€6,650/yr prorated linearly since Oct 2024) — reasonable given the 2022–23 order data; now variable-driven.
The oil baseline prorates linearly over the year while the electricity side accrues seasonally, so "Savings Since Install" runs slightly ahead in summer and behind in winter within any year. Neutral over full years; left as-is to keep the model simple.
Payback ignores discounting/maintenance/oil-boiler replacement counterfactual — it's a simple payback metric, kept as designed.
Verification
All numbers checked against live InfluxDB via the Grafana datasource API before editing:
Since-install spreads: gi 22,221.4 / ge 3,761.4 / sp 16,753.1 → ratio 0.369, blended €0.23593.
Trailing-365d consumption: 8,387.9 kWh.
Every replacement query executed live before being written into the JSON; expected panel values (0.236 / 3,370 / 1,168 / 4,671 / 15.6 / 8,544 / 11.7%) all reproduced in the AFTER render.
Fixed chart query run across both era boundaries (2026-03-04 tag change, 2026-07-02 ViCare→open3e cutover): smooth, no jumps.
# Heat Pump ROI — post-rebuild review (PR #271 follow-up)
Fresh-eyes review of the rebuilt dashboard. The InfluxDB migration itself (PR #271) is sound — entity unions, unit normalization and the survivor utility_meters all check out against live data. But the review found **one real correctness bug** (the cost chart silently ignores solar) and **two methodology problems** in the financial numbers that drive the investment picture.
## Before


## After


## Review report
### 1. CRITICAL — "Historical Cost Comparison" ignored solar entirely — ✅ FIXED
The chained Flux joins renamed the `_value` columns to `_value_j2` / plain `_value`, **not** `_value_gi` / `_value_sp` as the final `map()` expected. Both lookups returned null, so `solar_ratio` was 0 for **every** row and the whole "Electricity Cost" line was priced at full grid price. Verified live: the joined rows had `gi=null, sp=null`, line ended at €4,667 = 14,284 kWh × 0.3271 exactly. Real cost ≈ €3.4K — the chart overstated electricity cost by ~€1.1K (37%) and contradicted the "Running Cost" stat on the same dashboard.
**Fix:** each stream renames its column to a unique name (`h`/`d`/`gi`/`ge`/`sp`) before joining. The chart tail now lands at €3.37K, consistent with the stat.
### 2. MAJOR — blended price included 1.4 years of pre-heat-pump PV history — ✅ FIXED
The self-consumption ratio came from the Vitocharge **lifetime** counters, which date back to the PV install (May 2023) — 17 months before the heat pump existed. Lifetime ratio: 0.322 → €0.2475/kWh. Ratio from counter **deltas since Oct 2024** (verified: gi 22,221 kWh, ge 3,761, sp 16,753): **0.369 → €0.2359/kWh**. All cost/savings/ROI stats now use the since-install ratio; the cost chart subtracts the install-date counter baselines so its per-day ratio matches. Effect: running cost €3.54K→€3.37K, savings since install €8.38K→€8.54K.
### 3. MAJOR — "Projected Annual Savings" used a broken calendar extrapolation — ✅ FIXED
The old formula annualized year-to-date cost with `12 / (month + day/30)`. Two problems: (a) it overcounts elapsed time by a full month (on Jul 17 it says 7.57 months elapsed; actually 6.55) — in January it would divide by 1.2 instead of 0.5 and wildly misproject; (b) linear calendar extrapolation of a heating load is seasonally biased no matter the divisor. **Fix:** use the actual trailing-365-day consumption (utility_meter spread, verified live: 7,395 + 993 = 8,388 kWh) × blended price. Retitled **"Annual Savings (Trailing 12M)"** — a true rolling year with real seasonality, stable in every calendar month. Payback panel updated to match. Values barely move today (€4.70K→€4.67K, 15.5y→15.6y) — but now they're right by construction, not by accident of the date.
### 4. MINOR — stale legend "Last" values (10,734 / 1,230 / €9.47K) — ✅ FIXED
The counters' `friendly_name` tag changed on 2026-03-04 (verified in InfluxDB), splitting each entity into two table keys. After `keep()` the tables merged unsorted, so the legend "Last" calc picked the old era's final row — the Historical panels showed Last = 10,734 kWh while the line correctly ended at 12,577. **Fix:** `group() |> sort(_time)` before `aggregateWindow` in both historical panels. Legends now read 12,577 / 1,707 / €11.9K.
### 5. MINOR — misleading presentation — ✅ FIXED
- "Running Cost (Total)" had green/yellow/red thresholds (3000/5000) on a **cumulative-since-install** figure — it would sit permanently red as a matter of arithmetic. Now neutral single color and retitled "Running Cost (Since Install)", with a description saying it is not an annual figure.
- The €6,650 oil baseline was hardcoded in six places. Now a dashboard variable **`oil_baseline`** next to Investment/prices, so the whole ROI model is tunable from the toolbar.
- Added descriptions to Total Consumption / Running Cost / Cost This Year.
### Reviewed and left as-is
- SPF gauges (3.2) and SPF-over-time union — agree with the device and the old ViCare values.
- Total Consumption 14,284 kWh — matches both utility_meters (12,577 + 1,707).
- Oil baseline model (€6,650/yr prorated linearly since Oct 2024) — reasonable given the 2022–23 order data; now variable-driven.
- Daily Consumption / Cumulative Recent / Last-7-days panels — verified correct (summer: heating 0, DHW ~3 kWh/day).
### ⚠️ NOT FIXED / for awareness
- The oil baseline prorates **linearly** over the year while the electricity side accrues seasonally, so "Savings Since Install" runs slightly ahead in summer and behind in winter within any year. Neutral over full years; left as-is to keep the model simple.
- Payback ignores discounting/maintenance/oil-boiler replacement counterfactual — it's a simple payback metric, kept as designed.
## Verification
All numbers checked against live InfluxDB via the Grafana datasource API before editing:
- Counter values: heating 12,577 kWh / DHW 1,707.1 (utility_meters); year counters 4,355 + 594.3; vitocharge pv 20,038,720 Wh, feed-in 6,699.2 kWh, grid 28,088.2 kWh.
- Since-install spreads: gi 22,221.4 / ge 3,761.4 / sp 16,753.1 → ratio 0.369, blended €0.23593.
- Trailing-365d consumption: 8,387.9 kWh.
- Every replacement query executed live before being written into the JSON; expected panel values (0.236 / 3,370 / 1,168 / 4,671 / 15.6 / 8,544 / 11.7%) all reproduced in the AFTER render.
- Fixed chart query run across both era boundaries (2026-03-04 tag change, 2026-07-02 ViCare→open3e cutover): smooth, no jumps.
- Historical Cost Comparison: the chained joins renamed _value columns to
_value_j2/_value (not _value_gi/_value_sp as the map expected), so the
solar ratio was always null->0 and the whole cost line was priced at full
grid price (EUR 4.67K instead of ~3.4K). Streams now pre-rename columns
to h/d/gi/ge/sp so the join is unambiguous.
- Blended price now uses counter deltas since heat-pump install (Oct 2024)
instead of device-lifetime counters that include 1.4 years of PV-only
history from May 2023 (ratio 0.369 vs 0.322 -> EUR 0.236/kWh vs 0.248).
Applied consistently to all cost/savings/ROI stats and the cost chart
(chart subtracts install-date counter baselines).
- Annual savings / payback: replace the calendar-YTD extrapolation
(12 / (month + day/30) - which overcounts elapsed time by a month and is
seasonally biased) with the actual trailing-365-day consumption spread.
- Fix stale legend 'Last' values (10734/1230/EUR 9.47K): the counters'
friendly_name tag changed 2026-03-04, splitting each series into two
tables; group()+sort() merges them before aggregateWindow.
- New oil_baseline dashboard variable (6650) replaces six hardcoded copies.
- Running Cost: neutral color (was green/yellow/red on a forever-growing
cumulative), retitled '(Since Install)'; added descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scrape-residue verification (cross-cutting check): the duplicate-Prometheus-scrape residue that hits long-window dashboards (2 jobs x N pod lifetimes = ~12 series/entity before 2026-07-16) does not affect this dashboard. Verified against the JSON on this branch: all 50 datasource references are influxdb, zero Prometheus targets or expr fields remain (PR #271 removed them all, this PR keeps it that way). The default Oct-2024+ window is therefore safe — InfluxDB series here are only split by the friendly_name tag change, which this PR already handles with group() |> sort(_time).
**Scrape-residue verification (cross-cutting check):** the duplicate-Prometheus-scrape residue that hits long-window dashboards (2 jobs x N pod lifetimes = ~12 series/entity before 2026-07-16) does **not** affect this dashboard. Verified against the JSON on this branch: all 50 datasource references are `influxdb`, zero Prometheus targets or `expr` fields remain (PR #271 removed them all, this PR keeps it that way). The default Oct-2024+ window is therefore safe — InfluxDB series here are only split by the `friendly_name` tag change, which this PR already handles with `group() |> sort(_time)`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Heat Pump ROI — post-rebuild review (PR #271 follow-up)
Fresh-eyes review of the rebuilt dashboard. The InfluxDB migration itself (PR #271) is sound — entity unions, unit normalization and the survivor utility_meters all check out against live data. But the review found one real correctness bug (the cost chart silently ignores solar) and two methodology problems in the financial numbers that drive the investment picture.
Before
After
Review report
1. CRITICAL — "Historical Cost Comparison" ignored solar entirely — ✅ FIXED
The chained Flux joins renamed the
_valuecolumns to_value_j2/ plain_value, not_value_gi/_value_spas the finalmap()expected. Both lookups returned null, sosolar_ratiowas 0 for every row and the whole "Electricity Cost" line was priced at full grid price. Verified live: the joined rows hadgi=null, sp=null, line ended at €4,667 = 14,284 kWh × 0.3271 exactly. Real cost ≈ €3.4K — the chart overstated electricity cost by ~€1.1K (37%) and contradicted the "Running Cost" stat on the same dashboard.Fix: each stream renames its column to a unique name (
h/d/gi/ge/sp) before joining. The chart tail now lands at €3.37K, consistent with the stat.2. MAJOR — blended price included 1.4 years of pre-heat-pump PV history — ✅ FIXED
The self-consumption ratio came from the Vitocharge lifetime counters, which date back to the PV install (May 2023) — 17 months before the heat pump existed. Lifetime ratio: 0.322 → €0.2475/kWh. Ratio from counter deltas since Oct 2024 (verified: gi 22,221 kWh, ge 3,761, sp 16,753): 0.369 → €0.2359/kWh. All cost/savings/ROI stats now use the since-install ratio; the cost chart subtracts the install-date counter baselines so its per-day ratio matches. Effect: running cost €3.54K→€3.37K, savings since install €8.38K→€8.54K.
3. MAJOR — "Projected Annual Savings" used a broken calendar extrapolation — ✅ FIXED
The old formula annualized year-to-date cost with
12 / (month + day/30). Two problems: (a) it overcounts elapsed time by a full month (on Jul 17 it says 7.57 months elapsed; actually 6.55) — in January it would divide by 1.2 instead of 0.5 and wildly misproject; (b) linear calendar extrapolation of a heating load is seasonally biased no matter the divisor. Fix: use the actual trailing-365-day consumption (utility_meter spread, verified live: 7,395 + 993 = 8,388 kWh) × blended price. Retitled "Annual Savings (Trailing 12M)" — a true rolling year with real seasonality, stable in every calendar month. Payback panel updated to match. Values barely move today (€4.70K→€4.67K, 15.5y→15.6y) — but now they're right by construction, not by accident of the date.4. MINOR — stale legend "Last" values (10,734 / 1,230 / €9.47K) — ✅ FIXED
The counters'
friendly_nametag changed on 2026-03-04 (verified in InfluxDB), splitting each entity into two table keys. Afterkeep()the tables merged unsorted, so the legend "Last" calc picked the old era's final row — the Historical panels showed Last = 10,734 kWh while the line correctly ended at 12,577. Fix:group() |> sort(_time)beforeaggregateWindowin both historical panels. Legends now read 12,577 / 1,707 / €11.9K.5. MINOR — misleading presentation — ✅ FIXED
oil_baselinenext to Investment/prices, so the whole ROI model is tunable from the toolbar.Reviewed and left as-is
⚠️ NOT FIXED / for awareness
Verification
All numbers checked against live InfluxDB via the Grafana datasource API before editing:
Scrape-residue verification (cross-cutting check): the duplicate-Prometheus-scrape residue that hits long-window dashboards (2 jobs x N pod lifetimes = ~12 series/entity before 2026-07-16) does not affect this dashboard. Verified against the JSON on this branch: all 50 datasource references are
influxdb, zero Prometheus targets orexprfields remain (PR #271 removed them all, this PR keeps it that way). The default Oct-2024+ window is therefore safe — InfluxDB series here are only split by thefriendly_nametag change, which this PR already handles withgroup() |> sort(_time).