MAINT-20 part 2 review: comment-only fixes in main.py and the MAIN notes
checks / unit (pull_request) Successful in 30m13s
infra-pin / fresh (pull_request) Successful in 1s

- check_peak_floor_limited: restore the SAFETY-25 line (the reserve sentence is
  in messages.peak_reserve() on every branch, unreadable SoC included).
- Three status-page comments that no longer said anything (SAFETY-20 row,
  AC 5 delivery row, banner-vs-text) now say their rule in one line.
- tariff_refresh_wanted docstring: no orphan "TARIFF-24)" line.
- MAIN-orchestration.md: blank line before the first heading.
- MAIN-orchestration-archive.md: refilled intro; says how to find a section
  from a main.py pointer.

No code change: maint20_same_code.py 16029d7 -> SAME CODE, -OO same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-08 19:25:37 +01:00
co-authored by Claude Opus 5.5
parent f8df7cd860
commit bbbc89b235
3 changed files with 20 additions and 12 deletions
@@ -2,9 +2,12 @@
Written by MAINT-20 part 2. Each section is one comment block or docstring as it
stood in `app/main.py` at release/1.0 `d4c01da`, keyed by its first line number
there; the last heading holds the ones that came in by `16029d7` (keyed there). It is a record, not a design: the live rule is the one-line comment now in
the code, and the curated reasoning is in [[MAIN-orchestration]]. Comment blocks
have their `#` removed; docstrings their quotes.
there; the last heading holds the ones that came in by `16029d7` (keyed
there). It is a record, not a design: the live rule is the one-line comment now
in the code, and the curated reasoning is in [[MAIN-orchestration]]. Comment
blocks have their `#` removed; docstrings their quotes. A pointer in `main.py`
names this file only: find its section by searching here for a phrase of the
comment it ends.
### L1080 Set by amain() once the transport is built, so the status page can sho
@@ -8,6 +8,7 @@ comments that were shortened but not rewritten here are kept word for word in
[[SAFETY-30-per-unit-sign]], [[SAFETY-73-refusal-per-inverter]],
[[SAFETY-121-bound-every-quarter]], [[TARIFF-10-fetching-is-not-trading]],
[[TARIFF-24-modes-need-the-tariff-task]], [[OPT-34-the-night-charge-keeps-its-level]].
## Controller construction
- **Per-write budget (DEV-02).** Each write is dispatched concurrently and bounded, so one hung `hass.call()` cannot eat a sibling's layer-1 budget (the ESP32 zeroes after about 30 s, and a fault on A could zero a healthy B). The number is derived: `heartbeat_s` max 25 s, layer-1 veto `validate.FIRMWARE_INGEST_VETO_S` 30 s, so 25 + 4 < 30. test_inverter.py grades the inequality, so raising the heartbeat ceiling goes red instead of taking an inverter to zero. Below hass.py's 10 s HTTP timeout on purpose: abandon the write and re-issue it next tick, since `last_write` moves only on an accepted write.
+13 -9
View File
@@ -162,9 +162,8 @@ TARIFF_MAX_AGE_S = 3 * TARIFF_REFRESH_S
def tariff_refresh_wanted(opts: dict) -> bool:
"""Does this install need the background tariff refresh? (TARIFF-10,
TARIFF-24)
Yes with a price feed, trading armed, or battery modes on. It decides
whether prices are looked up, never whether energy moves.
TARIFF-24) Yes with a price feed, trading armed, or battery modes on. It
decides whether prices are looked up, never whether energy moves.
DECISIONS/TARIFF-10-fetching-is-not-trading.md and
DECISIONS/TARIFF-24-modes-need-the-tariff-task.md."""
return (provider_configured(opts) or arbitrage.enabled(opts)
@@ -4666,7 +4665,9 @@ class Controller:
"lower: %s.", messages.reason_code(g.reason))
_LOG.debug("Evidence: %s.", g.reason)
# A fixed notification_id, and a DIFFERENT one from every other alarm
# here - check_meter_frozen() gives the reasoning.
# here - check_meter_frozen() gives the reasoning. ⚠️ SAFETY-25's
# sentence (the reserve binds the peak guard only) is in
# messages.peak_reserve() on every branch, unreadable SoC included.
# (DECISIONS/MAIN-orchestration-archive.md)
await self.hass.call("persistent_notification", "create",
messages.peak_reserve(
@@ -5183,8 +5184,9 @@ class Controller:
elif rail_unreachable(self.site_max_w,
dev_min=self.site_dev_bound("dev_min"),
dev_max=self.site_dev_bound("dev_max")):
# SAFETY-20, and THE SECOND REASON TO USE THIS ROW RATHER THAN
# A NEW MECHANISM. (DECISIONS/MAIN-orchestration-archive.md)
# SAFETY-20: the same row as the transport case above (the
# check cannot be evaluated here); only the missing term
# differs. (DECISIONS/MAIN-orchestration-archive.md)
_no_rating = (not self.site_max_w
or not is_commandable(self.site_max_w))
out.append({"ok": False, "warn": True, "id": "freeze_check",
@@ -5476,8 +5478,9 @@ class Controller:
inv.name, _dheld,
inv.delivery_wd.cfg.hold_s)})
else:
# AC 5, and the same honesty `circulating power: n/a` uses one
# block up. (DECISIONS/MAIN-orchestration-archive.md)
# AC 5: at one inverter this check has nothing to find, so it is
# not called "armed" (as `circulating power: n/a` above).
# (DECISIONS/MAIN-orchestration-archive.md)
out.append({"ok": True, "warn": False, "id": "delivery",
"text": messages.row_one_inverter("Delivery check")})
@@ -5598,7 +5601,8 @@ class Controller:
level, banner = "bad", messages.banner_direction(
len(self.inverters) > 1)
elif bad:
# ⚠️ `banner` IF THE ROW OFFERS ONE, `text` OTHERWISE.
# ⚠️ `banner` IF THE ROW OFFERS ONE, `text` OTHERWISE: a row may
# say more than fits the headline (SAFETY-25's floor row).
# (DECISIONS/MAIN-orchestration-archive.md)
level, banner = "bad", messages.banner_not_ready(bad[0])
# SAFETY-112: nothing to do but wait, so Home says that instead