From f114a19dcc9b75ea69b2348868cff6e75c10629c Mon Sep 17 00:00:00 2001 From: glenn schrooyen Date: Sun, 8 Nov 2026 18:06:00 +0100 Subject: [PATCH] MAINT-20 part 2 r2: app/main.py comments, lines 501-1000 (_refusal_probe .. _refusal_update) Co-Authored-By: Claude --- goodwe_controller/app/main.py | 43 +++++++++++++---------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/goodwe_controller/app/main.py b/goodwe_controller/app/main.py index ebb8b42..4bd7d05 100644 --- a/goodwe_controller/app/main.py +++ b/goodwe_controller/app/main.py @@ -499,9 +499,7 @@ class Controller: # SAFETY-02: one watchdog per actuator, each restored from ITS OWN # namespaced store keys (a trip on B latches B, A keeps commanding). # Restored, because a latch held only in memory is cleared by the - # restart an operator staring at a FAILSAFE banner tries first. Graded - # on every start: a latch that failed to move namespace, or was taken - # by an older add-on during a rollback, is a disarmed guard, so a + # restart an operator tries first. Graded on every start: a # disagreement with the flat value re-arms by hand and shouts (the # fail-safe direction is ON). DECISIONS/MAIN-orchestration.md. self._grade_store_migration(store) @@ -639,27 +637,20 @@ class Controller: # FCST-04: the 8.3 carry-forward store (published prices of the last # few Brussels days) so unprinted hours can be filled from yesterday - # and FLAGGED. An instrument's backing file, not a strategy. Carrying - # widens what the optimiser SEES, never what it may act on (carried - # hours are provisional). The windowing DOES move energy: hours - # ALREADY ELAPSED leave the spread, so a past cheap night no longer - # sets the day's cheapest hour; this caveat must always travel with the - # guarantee (`resolve_tariff()`, DOCS.md, CHANGELOG). A broken or - # absent store answers `no_data`; `PriceHistory._load()` drops - # unreadable records (the constructor and `.words` are unguarded). + # and FLAGGED. An instrument, not a strategy: carrying widens what the + # optimiser SEES, never what it may act on. ⚠️ The windowing DOES move + # energy: hours ALREADY ELAPSED leave the spread, so a past cheap night + # no longer sets the day's cheapest hour; that caveat must always + # travel with the guarantee (`resolve_tariff()`, DOCS.md, CHANGELOG). # DECISIONS/FCST-04-the-day-that-is-carried.md. self.price_history = PriceHistory(price_history_path) _LOG.info("Price history: %s", self.price_history.words) # SAFETY-07: the guard is stateless (peakguard.py); the only state here - # is what an EDGE needs, so the log and notification fire on - # transitions. The ceiling has no config of its own (`peak_cap_w`). - # SAFETY-24's floor does: exactly two keys, defaults from - # `peakguard.PEAK_OPTION_DEFAULTS` (no literals here, as a literal - # repeated the 2000-vs-2500 `maintenance_charge_w` divergence of - # validate.py rule 6; closed by `maintenance.MAINT_OPTION_DEFAULTS`). - # The rule: no numeric fallback in main.py that config.yaml has not - # shipped. DECISIONS/SAFETY-07-peak-guard.md. + # is what an EDGE needs. The ceiling has no config of its own + # (`peak_cap_w`); SAFETY-24's floor has exactly two keys, defaults from + # `peakguard.PEAK_OPTION_DEFAULTS`: no numeric fallback in main.py that + # config.yaml has not shipped. DECISIONS/SAFETY-07-peak-guard.md. self.peak_guard = None # SAFETY-79: the guard aims this far under `peak_cap_w`; the bill and # every report keep the real cap (`self.peak.cap_w`). @@ -788,6 +779,7 @@ class Controller: self._settle = {} # Set by amain() once the transport is built, so the status page can # show what only the transport knows (homewizard_local's unchanged_s). + # None when P1 is off or the transport has no such counter. # (DECISIONS/MAIN-orchestration-archive.md) self.p1_source = None @@ -863,14 +855,11 @@ class Controller: # on it and DEV-02 reads `shares` off it. self.allocation = None # SAFETY-28: the tick the record above belongs to, incremented ONCE PER - # RESOLUTION, not per loop iteration. A new resolution makes the - # previous record stale at once (no replay into a later tick). A cycle - # with NO resolution does not bump it: the "inputs missing, not yet - # stale" hold returns before the allocator so one missed poll does not - # zero the inverter, and the heartbeat re-affirms `self.target` while - # it does; bumping would make that heartbeat unauthorised and drop the - # site to 0 W. The record is SUPERSEDED, never expired. - # DECISIONS/MAIN-orchestration.md. + # RESOLUTION, not per loop iteration. A cycle with NO resolution (the + # "inputs missing, not yet stale" hold) does not bump it: the heartbeat + # re-affirms `self.target` meanwhile, and bumping would make that + # heartbeat unauthorised and drop the site to 0 W. The record is + # SUPERSEDED, never expired. DECISIONS/MAIN-orchestration.md. self.alloc_tick = 0 # What the last cycle's allocator dropped, so the log line is # edge-triggered. Starts empty.