MAINT-20 part 2 r2: app/main.py comments, lines 1-500 (tariff_refresh_wanted .. __init__)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-08 18:06:00 +01:00
co-authored by Claude
parent 8fc5f15b03
commit a5fec377ad
+28 -40
View File
@@ -113,14 +113,13 @@ _LOG = logging.getLogger("goodwe")
# function CFG-04 changes.
__all__ = ["UNIT_A", "Controller", "amain", "main"]
# DEV-02: the per-write budget. Every write to every inverter is dispatched
# concurrently, each bounded by this, so one hung `hass.call()` cannot eat a
# sibling's layer-1 budget.
# DERIVED, NOT CHOSEN: `heartbeat_s` max is 25 s and layer 1's veto is
# `validate.FIRMWARE_INGEST_VETO_S` (30 s), so 25 + 4 < 30. test_inverter.py
# grades the inequality, not the literal. It is below hass.py's 10 s HTTP
# timeout on purpose: abandon and re-issue next tick (`last_write` moves only
# on an ACCEPTED write). DECISIONS/MAIN-orchestration.md.
# DEV-02: the per-write budget: every write is dispatched concurrently, each
# bounded by this, so one hung `hass.call()` cannot eat a sibling's layer-1
# budget. DERIVED: `heartbeat_s` max 25 s + 4 <
# `validate.FIRMWARE_INGEST_VETO_S` (30 s), graded by test_inverter.py; below
# hass.py's 10 s HTTP timeout on purpose (abandon, re-issue next tick;
# `last_write` moves only on an ACCEPTED write).
# DECISIONS/MAIN-orchestration.md.
WRITE_BUDGET_S = 4.0
# SMA-10: the bound for handing a unit back (`driver.release()`); units are
@@ -300,14 +299,10 @@ def why_key(winner: str, claims) -> str:
def tariff_plan_row(plan, tick_words: str, *, age_s, horizon_h: float,
provider: bool, max_age_s: float) -> str:
"""The *Tariff* status row: the 30 s tick's sentence AND the provider plan
(TARIFF-11).
Two sources, two clocks, and the row says so: `tick_words` is regenerated
on this I/O-free tick; `plan` is the refresh's resolution, stamped with its
own age from `tariff_at` (not moved on a kept plan, TARIFF-12), up to
TARIFF_MAX_AGE_S (5400 s) before `fresh_tariff_plan()` retires it. `age_s`
is the caller's measurement: a clock read here would disagree with the one
that judged freshness. "No plan" is said aloud on a site that configured a
provider (a soak read 'no static profile' at all 802 samples).
(TARIFF-11): two sources, two clocks, and the row says so. The plan is
stamped with its own age from `tariff_at` (not moved on a kept plan,
TARIFF-12), up to TARIFF_MAX_AGE_S (5400 s); `age_s` is the caller's
measurement. "No plan" is said aloud on a site that configured a provider.
DECISIONS/MAIN-orchestration.md."""
# UX-08: plain words, every figure kept. The tick's sentence is "now"; the
# plan carries its own age.
@@ -373,14 +368,13 @@ class Controller:
# UX-08: the last event in plain words, for the page. See log_event().
self.hint = ""
# DEV-02: the roster, built before anything reads a rating. Every
# per-device value lives on these objects and nowhere else
# (app/inverter.py); a `Tuning` and an integrator are deliberately NOT
# per device (6.2: one integrator on P1 above the split). The store
# migration runs here, BEFORE the first `Inverter` reads its SAFETY-02
# latch: else an existing tripped install comes up ARMED with a green
# banner. SAFETY-02's pair is the site comparison's pair (SAFETY-30),
# identical at N=1. DECISIONS/MAIN-orchestration.md.
# DEV-02: the roster, built before anything reads a rating; every
# per-device value lives on these objects (app/inverter.py), but a
# `Tuning` and the integrator are deliberately NOT per device (6.2: one
# integrator on P1 above the split). The store migration runs here,
# BEFORE the first `Inverter` reads its SAFETY-02 latch, else a tripped
# install comes up ARMED with a green banner.
# DECISIONS/MAIN-orchestration.md.
self.sign_cfg = sign_cfg = SignWatchdogConfig(
min_w=float(opts.get("sign_disagree_w", 300)),
hold_s=float(opts.get("sign_disagree_s", 180)),
@@ -412,14 +406,11 @@ class Controller:
hold_s=float(opts.get("delivery_hold_s",
DELIVERY_OPTION_DEFAULTS["delivery_hold_s"])),
)
# SAFETY-44: hand the migration the names the machines read:
# SAFETY-44: hand the migration the names the machines read,
# `roster_names(opts)[0]`, never `UNIT_A` (on a declared roster the
# flat latch landed under `unit/inverter-a/`, on no roster, and stamped
# the marker). `roster_names()` returns `[UNIT_A]` on the flat shape,
# so today's upgrade path is byte-identical. `misdirected` is
# unreachable from THIS call site; the `roster` argument stays for new
# callers and is graded in test_maintenance.py.
# DECISIONS/MAIN-orchestration.md.
# latch landed under `unit/inverter-a/`, on no roster, and stamped the
# marker). `roster_names()` is `[UNIT_A]` on the flat shape, so today's
# path is byte-identical. DECISIONS/MAIN-orchestration.md.
_roster_names = roster_names(opts)
self.store_migration = migrate_unit_keys(
store, _roster_names[0], _roster_names)
@@ -465,15 +456,12 @@ class Controller:
)
# CFG-03: no `opts.get(key, <literal>)` for maintenance here;
# `MaintConfig.from_options()` reads them next to
# `maintenance.MAINT_OPTION_DEFAULTS` (two literals once disagreed with
# config.yaml). A new key goes in config.yaml `options:`/`schema:` and
# that dict only.
# MAINT-05: one `Maintenance` per roster entry. The history migration
# runs first (else a pack believes it was never recalibrated), and
# targets `self.inverters[0].name`, NOT `UNIT_A`: the flat keys
# belonged to the first roster entry. The roster is passed, not a
# count, so `migrate_maint_keys()` grades the namespace it wrote
# (test_maintenance.py sections 9 and 11 use `["garage", "shed"]`).
# `maintenance.MAINT_OPTION_DEFAULTS`; a new key goes in config.yaml
# `options:`/`schema:` and that dict only. MAINT-05: one `Maintenance`
# per roster entry; the history migration runs first (else a pack
# believes it was never recalibrated) and targets
# `self.inverters[0].name`, NOT `UNIT_A`; the roster is passed so
# `migrate_maint_keys()` grades the namespace it wrote.
# DECISIONS/MAIN-orchestration.md.
self.maint_migration = migrate_maint_keys(
store, self.inverters[0].name, [inv.name for inv in self.inverters])