Skip to content

Audit 1/3: production - the fixes that run on the pump - #25

Draft
enoch85 wants to merge 1 commit into
mainfrom
audit/1-production
Draft

Audit 1/3: production - the fixes that run on the pump#25
enoch85 wants to merge 1 commit into
mainfrom
audit/1-production

Conversation

@enoch85

@enoch85 enoch85 commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Production code of the audit, standalone and green: every fix that changes what runs on the pump, with the existing tests updated to match. The new test coverage and the simulator follow in two stacked PRs (#B, #C), so this is the diff to review if you review only one.

Split note: the review asked for five topical PRs (control/lifecycle, tariff+storage, models/physics, diagnostics/UI, CI/tooling). The production change-set is interlocked through const.py and coordinator.py — a file-level topical split leaves intermediate PRs that do not import. The five topics are the sections below instead; each lists its files.

Control & lifecycle safety

  • Thermostat OFF now turns the optimiser off, atomically. One gate (entry.data), no RestoreEntity copy; the pump and every owned actuator (offset, DHW lux, ventilation fan) are neutralized before the gate persists OFF, and OFF fails loudly if the hardware cannot confirm neutral. (climate.py, switch.py, coordinator.py)
  • An unloaded integration can no longer write to the pump. Every write goes through one guarded door per command; a coordinator whose entry unloaded mid-refresh is refused. Platforms now unload before the coordinator dies. (coordinator.py, __init__.py)
  • Reads never drive the pump. HA's refresh hook decides but writes nothing; only the aligned control loop and explicit user commands hold the write lock.
  • User commands are real commands. force_offset/boost_heating bypass startup observation (safety floor still applies), report the offset actually written, and raise when the integration is OFF. boost_dhw opens a window the price optimizer may not cancel (safety still may), its duration is enforced by us, and the target_temp argument — which reached nothing — is removed.
  • The adapter returns the applied integer offset (round(), not int() truncation), and UI surfaces report what NIBE holds, not what the engine wished.

Tariff & storage

  • The tariff bills the hourly mean (was 15-minute — a 9 kW hot-water cycle in an idle hour billed as 9 kW instead of 3), at most one peak per day, from three different days (Ellevio's own rule), with the 22:00–06:00 half-weighting applied consistently.
  • One definition of the billed quantity (billing_period.py): time-weighted, DST-fold-safe (the repeated 02:00 no longer deletes an hour), refuses hours the meter slept through, and carries per-sample provenance — a meter-dropout hour can never be billed as a meter measurement.
  • Upgrades no longer break setup: v1 quarter-hour peak records are migrated (discarded with a log line), not parsed into a KeyError.
  • Monthly peaks reset on the month boundary; a mis-scaled sensor (mW read as MW) cannot poison a month; estimates are never billed.

Pump models & physics

  • All five NIBE profiles carry their EN 14511 rating points verbatim with source URLs (the old COP curves were invented — F750 and F730 shipped byte-identical ones).
  • Each profile carries its pump's factory aux-start DM (F750/F730 −700 per IHB GB 1301-1 menu 4.9.3; S/F11xx −460; F2040+VVM −760) — a hardware fact, distinct from EffektGuard's −1500 floor.
  • The F2040 capacity model uses one complete ErP declaration (avg: 8.2 − 1.1 = 7.1 kW at −10 °C), not a splice of two.
  • Flow temperature comes from the EN 442 emitter law (utils/emitter.py), which reproduces OpenEnergyMonitor's published tool to 0.00 °C — and no longer needs a weather forecast to run (leaving the weather dropdown blank used to silently disable it).
  • The slab research now states what its own model computes (~19% charged at 14 h, slow time constant ~70 h — the old "63% at 14 h" was false), and the pre-heat sizing gap is recorded as an open owner decision, not smuggled in.

Diagnostics & UI

  • Diagnostics report the thermal-mass-adjusted DM band production enforces, every layer's vote, and source liveness; the home's latitude stays out.
  • All 24 sensors translated (en/sv/no/da/fi); current_offset is a TEMPERATURE_DELTA (imperial users no longer see 0 °C as 32 °F); the power validator only flags readings the hardware cannot produce.

Verification

  • Full suite green at this PR's tip (existing tests updated where behavior changed — red-first; the new coverage lands in the stacked tests PR).
  • Run against main, the updated suite fails 556 tests and 40 files cannot import: that is the measured gap this PR closes.
  • Exercised on a live HA: config/options/reconfigure flows, services, reloads, restart — 0 errors.

Open, deliberately: F-124 (saturation trap — mechanism real, honest magnitude 1.4–1.7× excess aux, blocked on a heat-pump policy decision), effect-tariff configurability after the Ellevio repeal (F-107), pre-heat sizing (F-130b), F-112 ladder rescale (parked in stash).

Everything the week-long audit changed about what runs on the pump, with the
existing tests updated where behavior changed. The new test coverage and the
simulator follow as two stacked PRs; the full red-first history lives on
audit/safety-fixes-and-en442 (PR #24).

Control and lifecycle: thermostat OFF is one fact and an atomic hardware
transition; an unloaded coordinator cannot write; reads never drive the pump;
one locked writer; user commands are real commands (and are refused while
OFF); the adapter reports the integer it actually wrote.

Tariff and storage: the bill is the hourly mean, one peak per day from three
days, night hours at half weight; one fold-safe definition of the billed
quantity with per-sample provenance; hours the meter slept through are
refused; v1 quarter-era stores migrate instead of breaking setup.

Models and physics: EN 14511 rating points verbatim with sources; each pump's
factory aux-start; one complete ErP declaration for the F2040; flow
temperature from the EN 442 emitter law, which no longer needs a forecast to
run; the slab documentation says what its own model computes.

Diagnostics and UI: dumps report the band production enforces; 24 sensors
translated in five locales; offset is a temperature delta; the power
validator only flags readings the hardware cannot produce.

Run against main, the updated suite fails 556 tests and 40 files cannot
import. Full suite green at this commit.
@enoch85
enoch85 force-pushed the audit/1-production branch from fb88258 to 8ee951b Compare July 15, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant