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
@@ -1037,7 +1037,8 @@ check("...and the operator is told WHICH bound bit, so a device-rating clamp "
|
||||
and messages.LIMIT_WORDS["device"] in notes_w[0]["message"]
|
||||
and messages.LIMIT_WORDS["max_w"] not in notes_w[0]["message"])
|
||||
check("...and the action log carries the row, written by write_setpoint()",
|
||||
any(REASON_CODE in e and "device bound" in e for e in ctl_w.events))
|
||||
any(REASON_CODE in e and messages.LIMIT_WORDS["device"] in e
|
||||
for e in ctl_w.events))
|
||||
|
||||
# The same stimulus, the same clamp, the two calls swapped. This is the ticket
|
||||
# in one pair of runs.
|
||||
@@ -1125,8 +1126,8 @@ check("⚠️ THE ORDERED PAIR IN THE §15 ACTION-LOG ROW (main.py:1366). The "
|
||||
"the loop asked for +2000 W and the guard clamped it UP to +9999 W - "
|
||||
"past the 2000 W rating printed four words later - which is the alarm "
|
||||
"reading backwards on the one line an operator is woken by",
|
||||
any(f"the computed setpoint {_asked_x:+.0f} W was clamped to "
|
||||
f"{_sent_x:+.0f} W" in e for e in _rows_x))
|
||||
any(f"asked {_asked_x:+.0f} W and sent {_sent_x:+.0f} W" in e
|
||||
for e in _rows_x))
|
||||
# UX-08: the notification is a SECOND surface off the same numbers, in its own
|
||||
# words (messages.output_clamp): asked first, sent second.
|
||||
def _asked_then_sent(msg, asked, sent):
|
||||
@@ -1142,8 +1143,8 @@ check("⚠️ ...AND IN THE ClampWatchdog NOTIFICATION, which is a SECOND surfac
|
||||
bool(notes_x) and _asked_then_sent(notes_x[0]["message"], _asked_x, _sent_x))
|
||||
check("...and the TRANSPOSED sentence appears on NEITHER surface, so the two "
|
||||
"checks above discriminate rather than decorate",
|
||||
not any(f"the computed setpoint {_sent_x:+.0f} W was clamped to "
|
||||
f"{_asked_x:+.0f} W" in e for e in _rows_x)
|
||||
not any(f"asked {_sent_x:+.0f} W and sent {_asked_x:+.0f} W" in e
|
||||
for e in _rows_x)
|
||||
and not _asked_then_sent(nth(notes_x, 0)["message"], _sent_x, _asked_x)
|
||||
and present(nth(notes_x, 0)))
|
||||
# The DEVICE bound, off part 6c's controller: same sentence, other stage. The
|
||||
@@ -1154,8 +1155,8 @@ check("...and the same ordered pair holds on the DEVICE-bound row, where the "
|
||||
"loop asked for +3000 W and the device took +2500 W. PRECONDITION: those "
|
||||
"two also render differently",
|
||||
f"{_asked_w:+.0f}" != f"{_sent_w:+.0f}"
|
||||
and any(f"the computed setpoint {_asked_w:+.0f} W was clamped to "
|
||||
f"{_sent_w:+.0f} W" in e for e in ctl_w.events)
|
||||
and any(f"asked {_asked_w:+.0f} W and sent {_sent_w:+.0f} W" in e
|
||||
for e in ctl_w.events)
|
||||
and _asked_then_sent(nth(notes_w, 0)["message"], _asked_w, _sent_w))
|
||||
|
||||
|
||||
@@ -2028,10 +2029,11 @@ check("PRECONDITION: the integrator bound engaged, and on a tick where the "
|
||||
"carried integrator AND the emitted command both differ from the bound",
|
||||
any(d.i_w != _f(d, 'i_bound_w') and d.target_w != _f(d, 'i_bound_w')
|
||||
for d, t, r in wind))
|
||||
check("defect 1: the integrator bound is NOT reported as a BUG SIGNAL",
|
||||
wind and all("BUG SIGNAL" not in r for d, t, r in wind))
|
||||
check("defect 1: ...it names the anti-windup bound, from the same tick",
|
||||
wind and all(f"its {_f(d, 'i_bound_w'):+.0f} W anti-windup bound" in r
|
||||
check("defect 1: the integrator bound is NOT reported as an over-rating",
|
||||
wind and all(" rating" not in r and "fault" not in r
|
||||
for d, t, r in wind))
|
||||
check("defect 1: ...it names the integrator's bound, from the same tick",
|
||||
wind and all(f"reached its {_f(d, 'i_bound_w'):+.0f} W limit" in r
|
||||
for d, t, r in wind))
|
||||
check("defect 2: the integrator accumulator is not printed as a request",
|
||||
wind and all(f"{d.i_w:+.0f} W" not in r and "asked for" not in r
|
||||
@@ -2050,7 +2052,7 @@ check("...under its OWN notification_id, so it cannot replace a BUG SIGNAL "
|
||||
"notice still on screen",
|
||||
notes_i and all(n["notification_id"] == BOUND_ID for n in notes_i))
|
||||
check("...logged as a WARNING, not an ERROR",
|
||||
any(x.levelno == logging.WARNING and "anti-windup" in x.getMessage()
|
||||
any(x.levelno == logging.WARNING and "reached its" in x.getMessage()
|
||||
for x in log_i)
|
||||
and not any(x.levelno >= logging.ERROR and "OUTPUT_CLAMP" in x.getMessage()
|
||||
for x in log_i))
|
||||
@@ -2070,12 +2072,13 @@ for d, t, r in over[:2]:
|
||||
check("PRECONDITION: the output clamp engaged on a tick where the slew limit "
|
||||
"rewrote the command AND the integrator differs from the request",
|
||||
any(t != _f(d, 'clamped_w') and d.i_w != _f(d, 'requested_w') for d, t, r in over))
|
||||
check("the over-rating KEEPS the BUG SIGNAL",
|
||||
over and all("BUG SIGNAL" in r for d, t, r in over))
|
||||
check("the over-rating says it went over the rating",
|
||||
over and all(" rating, and cut it back to " in r for d, t, r in over))
|
||||
check("defect 2: it quotes the requested COMMAND of that tick, not i_w",
|
||||
over and all(f"computed {_f(d, 'requested_w'):+.0f} W" in r for d, t, r in over))
|
||||
check("defect 3: 'clamped to' is the clamp's own result, not self.target",
|
||||
over and all(f"clamped to {_f(d, 'clamped_w'):+.0f} W" in r for d, t, r in over))
|
||||
check("defect 3: 'cut it back to' is the clamp's own result, not self.target",
|
||||
over and all(f"cut it back to {_f(d, 'clamped_w'):+.0f} W" in r
|
||||
for d, t, r in over))
|
||||
check("...and its ERROR says it is a software fault and that control keeps "
|
||||
"running (UX-08 2d moved the 14 768 W rationale to a DEBUG line beside "
|
||||
"it, which is still in main.py)",
|
||||
|
||||
Reference in New Issue
Block a user