The Summary cards labelled `metered_import` / `metered_export` as "(kWh)", but
both fields are monetary totals (Σ import_cost / Σ export_revenue). Today's
page therefore showed "Import 1.339 kWh" when the meter had actually imported
4.188 kWh — the 1.339 was EUR. Cross-checked against the DSMR cumulative
registers and Home Assistant: our energy figures were correct all along, only
the label was wrong.
summarize() now also aggregates the metered energy, reusing the already-fetched
non-degraded rows so no extra query is issued:
metered_import_kwh = Σ (d1_kwh + d2_kwh)
metered_export_kwh = Σ (r1_kwh + r2_kwh)
The Import/Export cards show kWh as the headline figure and keep the monetary
equivalent as a sub-line, so the split between energy cost and standing
charges/credits behind total_payable stays visible.
The `_kwh` suffix is now the only thing separating energy from money in this
payload, so the docstrings on both summarize() and SummaryResponse call that
out explicitly.
app/integrations/expose.py reads only the money keys, so the HA outbound
sensors are unaffected by the additive fields.
d07a083 changed the /api/energy/prices docstring (sell now deducts sell_fee)
and refreshed openapi/openapi.json, but frontend/src/api/schema.d.ts was not
regenerated, so CI's "check codegen is in sync" step failed. Comment-only diff.
A disabled device with historical readings still cannot be deleted by default
(409 guard stays, to prevent accidental data loss). A new explicit opt-in lets
the user remove it for real:
- DELETE /api/modbus/devices/{uuid}?cascade=true removes the device's readings
and exposed_entity_toggle rows, then the device, in one transaction (FK order:
readings before device). Best-effort clears the HA discovery configs first
(MQTT not connected -> no-op, never blocks the delete). Default (no cascade)
unchanged. Returns 200 with deletion counts.
- Frontend: a 'Force Delete (all data)' button appears only after the 409, with
a red irreversible warning; cascade is sent only on that explicit action.
- Also corrected a stale comment: FK RESTRICT IS enforced at runtime now.
Post-M2 self-walkthrough polish, batched into one commit.
Map / heat:
- fix heat-layer white-screen crash after login (add layer to map before
setLatLngs; an off-map leaflet.heat layer has a null _map and throws)
- normalize each heat layer to the densest pixel cell visible in the CURRENT
viewport (maxZoom:0 so intensity factor f=1) and recompute on moveend/zoomend,
so sparse poo data reaches red and stays normalized at any zoom level
- dark CARTO basemap tiles when the color scheme is dark
UI:
- dark-mode toggle in the top-right, beside the settings gear
- switch top-right nav (records / theme / settings / logout) to Feather icons
with hover tooltips
- home: Grafana-style quick time-range presets + back/forward shift buttons,
placed between the From/To pickers and Apply; fix Select/tooltip z-index
(Leaflet stacking) and the shift-button height alignment
API client:
- stop flooding GET /api/session with 401s: the session probe and the login
endpoint own their 401s (no global redirect), which fixes the logout hang and
the spinning login page
Compose:
- rename docker-compose.override.yml -> docker-compose.dev.yml as an explicit,
non-auto-layered dev stack (8001, -dev container names, prod-copy ./data DB);
update tests/test_deployment.py (read dev.yml, tolerate the !override tag) and
the README "Docker Compose" section
Tests:
- pixel-grid peak counter, time-range presets, heat-layer ordering regression,
and 401-redirect regression