UX-10: the last plain-language leftovers: log lines, two table rows, 'Why: Other', red at 0 s
- test_messages.py part 6: every dynamic part of a WARNING+ line is classified as data or as a built text, and every built text is produced by the real code and checked. This is the gap the 2d scan had: it counted `%s` as one word, so reasons built by loop_clamp_note() and friends were never seen. - Quoted reasons rewritten to STYLE.md (red-alarm shape, codes kept): OUTPUT_CLAMP, METER_FROZEN, CIRCULATING_POWER, STALE_READING, the peak guard (codes now PEAK_GUARD*), the migration report, price feed, price history and gate reasons. Plurals via messages.times()/count(). - Top table: Price history "96 prices over 24 hours for 26 Sep", Tariff "one every 15 minutes or every hour", "not final yet" dropped. - Levels and noise: per-cycle shadow line DEBUG, shadow on/off INFO, aiohttp access log only at log level debug, settings warnings once per start, one plain line per Home Assistant outage (hass.Outage). - INFO jargon and error texts: no "§8.3", ISO times, "banked", "keys ARE", "NOT_OBSERVED", "did NOT land", "AND". - Display bugs: WHY_WINNER covers every winner and safety reason (main.why_key(), part 7 finds them in the code); the meter row is amber "waiting for the first reading" inside its limit after a start; the missing-reading age is counted when the page asks. - STYLE.md and DECISIONS/UX-08-short-notifications.md (UX-10 section). No change to safety behaviour, alarm levels, notification ids, triggers, cadence or the banner cascade. One row colour changes (meter at start), justified in DECISIONS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
125b0702a2
commit
23a3a3f5b5
@@ -222,6 +222,8 @@ class MigrationReport:
|
||||
self.misdirected = []
|
||||
# Why nothing ran, when nothing ran. Empty means "already at schema".
|
||||
self.skipped = ""
|
||||
# The inverter list the moves were graded against, for `explain()`.
|
||||
self.roster = []
|
||||
|
||||
@property
|
||||
def ok(self) -> bool:
|
||||
@@ -237,19 +239,27 @@ class MigrationReport:
|
||||
# the flat keys ARE its namespace - and an operator reading the
|
||||
# startup log needs to be told which, because only one of them
|
||||
# changes when a second inverter is declared.
|
||||
return self.skipped or (f"store already at schema "
|
||||
f"{self.from_version}; no "
|
||||
f"per-inverter migration needed")
|
||||
out = (f"migrated {len(self.moved)} flat key(s) to inverter "
|
||||
f"namespaces: " + ", ".join(sorted(self.moved)))
|
||||
return self.skipped or (f"the saved state is already at version "
|
||||
f"{self.from_version}, so nothing moves")
|
||||
# UX-10: plain sentences (test_messages.py part 6). Each problem is
|
||||
# named by its key and what went wrong, and `problems`, `lost` and
|
||||
# `misdirected` keep their full notes for the checks that read them.
|
||||
n = len(self.moved)
|
||||
out = (f"moved {n} saved {'value' if n == 1 else 'values'} to one part "
|
||||
f"per inverter: " + ", ".join(sorted(self.moved)) if n
|
||||
else "moved nothing")
|
||||
if self.kept:
|
||||
out += " (left alone, already namespaced: " + ", ".join(sorted(self.kept)) + ")"
|
||||
if self.problems:
|
||||
out += " - PROBLEMS: " + "; ".join(self.problems)
|
||||
if self.lost:
|
||||
out += " - STATE LOST: " + "; ".join(self.lost)
|
||||
if self.misdirected:
|
||||
out += " - WRONG NAMESPACE: " + "; ".join(self.misdirected)
|
||||
out += ". Already in place: " + ", ".join(sorted(self.kept))
|
||||
def keys(notes):
|
||||
return ", ".join(note.split(" ", 1)[0] for note in notes)
|
||||
other = [n for n in self.problems
|
||||
if n not in self.lost and n not in self.misdirected]
|
||||
parts = ([f"lost {keys(self.lost)}"] if self.lost else []) + (
|
||||
[f"moved under an inverter not in the list {self.roster!r}: "
|
||||
f"{keys(self.misdirected)}"] if self.misdirected else []) + (
|
||||
[f"read back differently {keys(other)}"] if other else [])
|
||||
if parts:
|
||||
out += ". Problems: " + ", and ".join(parts)
|
||||
return out
|
||||
|
||||
|
||||
@@ -436,6 +446,7 @@ def _verify_moves(store, rep: MigrationReport, roster=None) -> None:
|
||||
if roster is None:
|
||||
return
|
||||
names = [str(n) for n in roster]
|
||||
rep.roster = names
|
||||
# `unit_key()` builds `unit/<unit>/<key>`, so the unit is field 1. Split
|
||||
# through the same spelling the writer used rather than re-deriving it.
|
||||
#
|
||||
@@ -469,8 +480,8 @@ def _verify_moves(store, rep: MigrationReport, roster=None) -> None:
|
||||
if unit in names:
|
||||
continue
|
||||
rep.verified = False
|
||||
note = (f"{target} was migrated into namespace {unit!r}, which is NOT "
|
||||
f"on the roster {names!r} - nothing reads that key")
|
||||
note = (f"{target} was moved under {unit!r}, which is not in the "
|
||||
f"inverter list {names!r}, so nothing reads it")
|
||||
rep.problems.append(note)
|
||||
rep.misdirected.append(note)
|
||||
|
||||
@@ -536,14 +547,13 @@ def migrate_maint_keys(store, unit: str, roster) -> MigrationReport:
|
||||
names = [str(n) for n in roster]
|
||||
rep.from_version = int(data.get(MAINT_SCHEMA_KEY, 0) or 0)
|
||||
if len(names) <= 1:
|
||||
rep.skipped = ("one inverter: the flat maintenance keys ARE that pack's "
|
||||
"history and are read in place, so there is nothing to "
|
||||
"move and the store layout does not change")
|
||||
rep.skipped = ("one inverter, so its maintenance history is read "
|
||||
"where it is and nothing moves")
|
||||
return rep
|
||||
if rep.from_version >= MAINT_SCHEMA_VERSION:
|
||||
rep.skipped = (f"maintenance history already at schema "
|
||||
f"{rep.from_version}; written by a multi-inverter "
|
||||
f"version and not touched")
|
||||
rep.skipped = (f"maintenance history already at version "
|
||||
f"{rep.from_version}, written for several inverters, so "
|
||||
f"it is left as it is")
|
||||
return rep
|
||||
|
||||
rep.ran = True
|
||||
@@ -567,8 +577,8 @@ def migrate_maint_keys(store, unit: str, roster) -> MigrationReport:
|
||||
# the re-run a no-op where the keys did land, so refusing the marker
|
||||
# costs one repeated pass and buys the difference between a migration
|
||||
# that can still be fixed and one the store says already happened.
|
||||
_LOG.error("per-pack maintenance-history migration did NOT land and "
|
||||
"has not been marked done: %s", rep.explain())
|
||||
_LOG.error("Moving the maintenance history to one part per inverter "
|
||||
"did not work, so it is not marked done: %s", rep.explain())
|
||||
return rep
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user