SAFETY-125 review: guard row and Home line within UX-08 limits; N=1 maintenance named
checks / unit (pull_request) Successful in 56m45s
infra-pin / fresh (pull_request) Successful in 1s

- The over-the-limit rows ran over STYLE's 15 words (EN 16/19, NL 16/21)
  and two Dutch Home sentences over 12 words; they were not in the samples,
  so test_messages/test_ux46 never saw them. Now:
  "Peak guard: grid charge limited, at this rate 5 814 W, over 5 413 W" /
  "Piekbewaking: laden beperkt, aan dit tempo 5 814 W, boven 5 413 W",
  "...idle, at this rate ..., over ..." / "...in rust, aan dit tempo ...",
  NL Home "Laadt minder van het net om onder 5,4 kW te blijven." and
  "Zou minder van het net laden om onder 5,4 kW te blijven."
  The rows and the line are added to the samples. The under row is unchanged.
- CHANGELOG known issues and the DECISIONS note: at N=1 the maintenance
  charge is not sized to the room, so SAFETY-99 still applies to it.
- DOCS: the peak guard row's new words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude (SAFETY-125 review)
2026-10-29 12:56:01 +01:00
parent 88ab8a50e3
commit 6eebd4882b
7 changed files with 29 additions and 22 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ Contents of this release, in merge order since 0.19.1 (`2932f3a`):
### Known issues ### Known issues
The list under `## 0.19.0` below still applies. The SAFETY-99 line there now The list under `## 0.19.0` below still applies. The SAFETY-99 line there now
covers the guard's discharge only, not a grid charge. covers the guard's discharge, and the maintenance charge with one inverter.
### If you are crossing from a version older than 0.10.0 ### If you are crossing from a version older than 0.10.0
@@ -5,5 +5,6 @@
A grid charge is sized by `peakguard.charge_room_w()`, the guard's headroom A grid charge is sized by `peakguard.charge_room_w()`, the guard's headroom
with the seconds since a late meter reading counted at that reading (the with the seconds since a late meter reading counted at that reading (the
projection leaves them out, SAFETY-99, the bill does not), and the guard's own projection leaves them out, SAFETY-99, the bill does not), and the guard's own
floor is unchanged. Evidence (repro on 0.19.1, sweep, digests): the SAFETY-125 floor is unchanged. Not at N=1: the maintenance charge there is not sized to
pull request. the room (SAFETY-84), so SAFETY-99 still applies to it. Evidence (repro on
0.19.1, sweep, digests): the SAFETY-125 pull request.
+1 -1
View File
@@ -3597,7 +3597,7 @@ for a new install*.
| Meter reading may be stuck | See *Meter reading may be stuck* under Notifications. | | Meter reading may be stuck | See *Meter reading may be stuck* under Notifications. |
| Month peak | This month's highest 15-minute average, when it happened, and how close it is to your peak limit. Amber from 80 %. | | Month peak | This month's highest 15-minute average, when it happened, and how close it is to your peak limit. Amber from 80 %. |
| 15-minute periods measured this month | How many 15-minute periods the add-on has measured and saved this month. | | 15-minute periods measured this month | How many 15-minute periods the add-on has measured and saved this month. |
| Peak guard, Load too high for battery, Battery at its peak reserve | What the add-on does to keep your peak down. *Idle, heading for …* is where this 15-minute period is heading. *Waiting for meter readings* after a restart means the battery does not charge until the add-on can measure the period again. A red peak row is an alarm while control keeps running, so the banner shows its title without *Not ready*. | | Peak guard, Load too high for battery, Battery at its peak reserve | What the add-on does to keep your peak down. *Idle, heading for …* is where this 15-minute period is heading. *At this rate … over …* means it would go over at the current rate. With *grid charge limited*, a charge from the grid is cut back so it does not. *Waiting for meter readings* after a restart means the battery does not charge until the add-on can measure the period again. A red peak row is an alarm while control keeps running, so the banner shows its title without *Not ready*. |
| Battery level, Battery power, Grid power | The reading of that entity. *No usable reading* means the entity is missing, or its reading is too old. Check it in Developer Tools before replacing it. | | Battery level, Battery power, Grid power | The reading of that entity. *No usable reading* means the entity is missing, or its reading is too old. Check it in Developer Tools before replacing it. |
| Inverter control | Whether the inverter can be controlled, and how much it can charge and give. | | Inverter control | Whether the inverter can be controlled, and how much it can charge and give. |
| *name*: battery power reading stopped changing | The reading did not change while the add-on asked for a change. It may be stuck, or the battery may be refusing. The add-on sets the inverters to 0 W until it changes. | | *name*: battery power reading stopped changing | The reading did not change while the add-on asked for a change. It may be stuck, or the battery may be refusing. The add-on sets the inverters to 0 W until it changes. |
+2
View File
@@ -1453,6 +1453,8 @@ def _samples() -> list:
("plan", line_period(1240, 3500, sh)), ("plan", line_period(1240, 3500, sh)),
("plan", line_period(None, 3500, sh)), ("plan", line_period(None, 3500, sh)),
("plan", line_period(1240, 3500, sh, unmeasured=True)), ("plan", line_period(1240, 3500, sh, unmeasured=True)),
("plan", line_period(5814, 5413, sh)),
("plan", line_period(5814, 5413, sh, cut=True)),
("plan", control_text(True, sh)), ("plan", control_text(True, sh)),
("plan", control_text(False, sh))] ("plan", control_text(False, sh))]
out += [("plan", run_grid_why(0.1412)), ("plan", run_grid_amount(1.23)), out += [("plan", run_grid_why(0.1412)), ("plan", run_grid_amount(1.23)),
+13 -8
View File
@@ -1174,18 +1174,18 @@ def row_peak_guard_idle(projected_w, limit_w, month_peak: bool,
cut: bool = False) -> str: cut: bool = False) -> str:
"""The projection first and the limit second (SAFETY-38's ordered pair). """The projection first and the limit second (SAFETY-38's ordered pair).
SAFETY-125: over the limit at this rate it says so, and `cut` that a SAFETY-125: over the limit at this rate it says so, and `cut` that a
grid charge is cut back to the room left.""" grid charge is limited to the room left."""
if float(projected_w) > float(limit_w):
doing, doet = (("grid charge limited", "laden beperkt") if cut
else ("idle", "in rust"))
return Bi(f"Peak guard: {doing}, at this rate {watts(projected_w)}, "
f"over {watts(limit_w)}",
f"Piekbewaking: {doet}, aan dit tempo "
f"{watts(projected_w)}, boven {watts(limit_w)}")
limit = (f"this month's {watts(limit_w)} peak" if month_peak limit = (f"this month's {watts(limit_w)} peak" if month_peak
else f"the {watts(limit_w)} peak limit") else f"the {watts(limit_w)} peak limit")
grens = (f"de maandpiek van {watts(limit_w)}" if month_peak grens = (f"de maandpiek van {watts(limit_w)}" if month_peak
else f"de piekgrens van {watts(limit_w)}") else f"de piekgrens van {watts(limit_w)}")
if float(projected_w) > float(limit_w):
doing, doet = (("cutting the grid charge", "schroeft het laden van "
"het net terug") if cut else ("idle", "in rust"))
return Bi(f"Peak guard: {doing}, at this rate {watts(projected_w)}, "
f"over {limit}",
f"Piekbewaking: {doet}, aan dit tempo "
f"{watts(projected_w)}, boven {grens}")
return Bi(f"Peak guard: idle, heading for {watts(projected_w)}, under " return Bi(f"Peak guard: idle, heading for {watts(projected_w)}, under "
f"{limit}", f"{limit}",
f"Piekbewaking: in rust, richting {watts(projected_w)}, onder " f"Piekbewaking: in rust, richting {watts(projected_w)}, onder "
@@ -1573,6 +1573,11 @@ def status_samples() -> list:
("row_periods_measured", GREEN, row_periods_measured("unreadable")), ("row_periods_measured", GREEN, row_periods_measured("unreadable")),
("row_peak_guard_idle", GREEN, row_peak_guard_idle(1300, 2500, True)), ("row_peak_guard_idle", GREEN, row_peak_guard_idle(1300, 2500, True)),
("row_peak_guard_idle", GREEN, row_peak_guard_idle(1300, 2500, False)), ("row_peak_guard_idle", GREEN, row_peak_guard_idle(1300, 2500, False)),
("row_peak_guard_idle", GREEN, row_peak_guard_idle(5814, 5413, True)),
("row_peak_guard_idle", GREEN, row_peak_guard_idle(5814, 5413, True,
cut=True)),
("row_peak_guard_idle", GREEN, row_peak_guard_idle(3814, 3500, False,
cut=True)),
("row_peak_guard_quiet", GREEN, row_peak_guard_quiet()), ("row_peak_guard_quiet", GREEN, row_peak_guard_quiet()),
("row_peak_guard_unmeasured", AMBER, row_peak_guard_unmeasured()), ("row_peak_guard_unmeasured", AMBER, row_peak_guard_unmeasured()),
*(("row_peak_guard_blind", AMBER, row_peak_guard_blind(k)) *(("row_peak_guard_blind", AMBER, row_peak_guard_blind(k))
+2 -2
View File
@@ -352,8 +352,8 @@
"period": "Dit kwartier gaat richting {kw}.", "period": "Dit kwartier gaat richting {kw}.",
"period_unmeasured": "Een deel van dit kwartier is niet gemeten.", "period_unmeasured": "Een deel van dit kwartier is niet gemeten.",
"period_over": "Aan dit tempo komt dit kwartier op {kw}.", "period_over": "Aan dit tempo komt dit kwartier op {kw}.",
"charge_cut": "Het laden van het net wordt teruggeschroefd om onder {kw} te blijven.", "charge_cut": "Laadt minder van het net om onder {kw} te blijven.",
"charge_cut_would": "Het laden van het net zou teruggeschroefd worden om onder {kw} te blijven.", "charge_cut_would": "Zou minder van het net laden om onder {kw} te blijven.",
"prices": {"now": "Prijs nu {eur} per kWh, alles inbegrepen.", "today": "Vandaag: laagste {lo} om {lo_at}, hoogste {hi} om {hi_at}.", "none": "Geen prijzen voor vandaag."}, "prices": {"now": "Prijs nu {eur} per kWh, alles inbegrepen.", "today": "Vandaag: laagste {lo} om {lo_at}, hoogste {hi} om {hi_at}.", "none": "Geen prijzen voor vandaag."},
"tomorrow": {"known": "De prijzen van morgen zijn binnen.", "unknown": "De prijzen van morgen komen meestal na 13:00."}, "tomorrow": {"known": "De prijzen van morgen zijn binnen.", "unknown": "De prijzen van morgen komen meestal na 13:00."},
"region": {"preset": "Heffingen en btw: {region}, cijfers van {year}.", "own": "Heffingen en btw: uw eigen bedrag."}, "region": {"preset": "Heffingen en btw: {region}, cijfers van {year}.", "own": "Heffingen en btw: uw eigen bedrag."},
+7 -8
View File
@@ -355,9 +355,9 @@ for name, kw, aim, kw_text in (("month peak banked", dict(peak=PEAK),
f"{_u[3] if present(_u) else _u!s} / " f"{_u[3] if present(_u) else _u!s} / "
f"{_u[4] if present(_u) else _u!s}") f"{_u[4] if present(_u) else _u!s}")
check(f"{name}: over the limit at this rate the guard row never says " check(f"{name}: over the limit at this rate the guard row never says "
f"'under'; it says the grid charge is cut", f"'under'; it says the grid charge is limited",
present(_o) and all( present(_o) and all(
"under" not in s[3] and "Peak guard: cutting the grid charge, " "under" not in s[3] and "Peak guard: grid charge limited, "
"at this rate" in s[3] and " over " in s[3] for s in over)) "at this rate" in s[3] and " over " in s[3] for s in over))
check(f"{name}: ...and the month-peak line says the rate, and that the " check(f"{name}: ...and the month-peak line says the rate, and that the "
f"grid charge is cut back to stay under {kw_text}", f"grid charge is cut back to stay under {kw_text}",
@@ -382,17 +382,16 @@ _cut = messages.row_peak_guard_idle(5814.0, 5413.0, month_peak=True,
_ok = messages.row_peak_guard_idle(5200.0, 5413.0, month_peak=True) _ok = messages.row_peak_guard_idle(5200.0, 5413.0, month_peak=True)
print(f" {_en}\n {_cut}\n {_ok}") print(f" {_en}\n {_cut}\n {_ok}")
check("over, nothing cut: 'idle, at this rate ..., over ...'", check("over, nothing cut: 'idle, at this rate ..., over ...'",
_en == "Peak guard: idle, at this rate 5 814 W, over this month's " _en == "Peak guard: idle, at this rate 5 814 W, over 5 413 W")
"5 413 W peak")
check("over, a grid charge cut", check("over, a grid charge cut",
_cut == "Peak guard: cutting the grid charge, at this rate 5 814 W, " _cut == "Peak guard: grid charge limited, at this rate 5 814 W, "
"over this month's 5 413 W peak") "over 5 413 W")
check("under: the 0.19.1 sentence, unchanged", check("under: the 0.19.1 sentence, unchanged",
_ok == "Peak guard: idle, heading for 5 200 W, under this month's " _ok == "Peak guard: idle, heading for 5 200 W, under this month's "
"5 413 W peak") "5 413 W peak")
check("the Dutch forms say the same", check("the Dutch forms say the same",
_cut.nl == "Piekbewaking: schroeft het laden van het net terug, aan " _cut.nl == "Piekbewaking: laden beperkt, aan dit tempo 5 814 W, "
"dit tempo 5 814 W, boven de maandpiek van 5 413 W" "boven 5 413 W"
and _en.nl.startswith("Piekbewaking: in rust, aan dit tempo")) and _en.nl.startswith("Piekbewaking: in rust, aan dit tempo"))
check("the month-peak line in shadow mode says 'would'", check("the month-peak line in shadow mode says 'would'",
homeview.line_period(5814.0, 5413.0, True, cut=True).endswith( homeview.line_period(5814.0, 5413.0, True, cut=True).endswith(