UX-10 review: the line as logged gets the red-alarm shape, clamp lines at 30 words, plain HA 400
Part 6 checked the quoted reason alone, so title + reason + tails logged as 4+ sentences. It now also checks the whole line (at most 3 sentences, none over 30 words); 25 lines failed and are rewritten (one-sentence wrappers, two-sentence circulation and peak reasons, STALE_READING's fixed limits on a DEBUG line). The clamp lines for one inverter are pinned at 30 words with OUTPUT_CLAMP and the numbers kept. Home Assistant's bare '400: Bad Request' while it loads becomes one plain line; still WARNING, throttled, a failed write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -239,8 +239,8 @@ SUSTAINED_CYCLES = 3
|
||||
# UX-10: the reasons below are quoted by a WARNING or ERROR line, so they
|
||||
# follow STYLE.md's rule for a quoted reason (test_messages.py part 6): no
|
||||
# final full stop, the line adds it. The line around them says "software
|
||||
# fault" for an over-rating, so the reason does not.
|
||||
|
||||
# fault" for an over-rating, so the reason does not. Each is one sentence, so
|
||||
# the logged line stays within 3 sentences and about 30 words.
|
||||
|
||||
|
||||
def loop_clamp_note(d) -> str:
|
||||
@@ -251,21 +251,29 @@ def loop_clamp_note(d) -> str:
|
||||
decision's own, i.e. from the tick that clamped.
|
||||
"""
|
||||
if d.clamped:
|
||||
return (f"{REASON_CODE}: the add-on computed {d.requested_w:+.0f} W, "
|
||||
f"over the {abs(d.clamped_w):.0f} W rating, and cut it back to "
|
||||
f"{d.clamped_w:+.0f} W. The command this cycle was "
|
||||
f"{d.target_w:+.0f} W")
|
||||
return (f"{REASON_CODE}: computed {d.requested_w:+.0f} W, cut to the "
|
||||
f"{d.clamped_w:+.0f} W rating, command now {d.target_w:+.0f} W")
|
||||
if d.i_clamped:
|
||||
return (f"{REASON_CODE}: the correction the add-on builds up reached "
|
||||
f"its {d.i_bound_w:+.0f} W limit. The command this cycle was "
|
||||
f"its {d.i_bound_w:+.0f} W limit, command now "
|
||||
f"{d.target_w:+.0f} W")
|
||||
return ""
|
||||
|
||||
|
||||
def write_clamp_note(named) -> str:
|
||||
"""The write-side alarm text: `named` is `[(inverter name, ClampResult)]`
|
||||
for every inverter whose write was cut back this cycle."""
|
||||
return ". ".join(f"{name}: {r.describe()}" for name, r in named)
|
||||
"""The write-side alarm text: `named` is `[(inverter name or None,
|
||||
ClampResult)]` for every inverter whose write was cut back this cycle.
|
||||
|
||||
One inverter reads as `describe()`. Several read as one sentence of
|
||||
asked/sent pairs, so the line stays one sentence however many inverters
|
||||
there are. Which limit cut each one back is on its own action-log row
|
||||
(`describe()`) and in the notification."""
|
||||
if len(named) == 1:
|
||||
name, r = named[0]
|
||||
return f"{name}: {r.describe()}" if name else r.describe()
|
||||
return f"{REASON_CODE}: " + ", ".join(
|
||||
f"{name} asked {r.requested_w:+.0f} W and sent {r.applied_w:+.0f} W"
|
||||
for name, r in named)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -284,10 +292,12 @@ class ClampResult:
|
||||
bound: str = "" # "" | "max_w" | "device" - which stage bit
|
||||
|
||||
def describe(self) -> str:
|
||||
# The sent value IS the limit that bound, so the rating is not
|
||||
# repeated: on "max_w" it equals the rating, and on "device" the
|
||||
# rating was not what cut it back.
|
||||
return (f"{REASON_CODE}: asked {self.requested_w:+.0f} W and sent "
|
||||
f"{self.applied_w:+.0f} W, cut back by "
|
||||
f"{messages.LIMIT_WORDS.get(self.bound, self.bound)}, with a "
|
||||
f"{self.limit_w:.0f} W rating")
|
||||
f"{self.applied_w:+.0f} W, "
|
||||
f"{messages.LIMIT_WORDS.get(self.bound, self.bound)}")
|
||||
|
||||
|
||||
def is_commandable(value) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user