UX-13 P3 review: truthful peak lines, a clock-proof test, Simple stays default
checks / unit (pull_request) Successful in 11m10s
infra-pin / fresh (pull_request) Successful in 2s

- The peak limit's Why and plan block need a real power request (floor above
  0 W), not a blind or floor-limited guard. An Off battery gets the peak
  block only while it gives power. Why no longer says every battery gives.
- test_batteries_screen pins the fixture clock: unpinned it failed every
  morning. New checks cover the trade and cycle filters, the blind guard and
  an Off battery that A covers alone.
- Settings keeps only a picked view; a link to Installer is not remembered.
- Browser check: badges, unsaved change across views, locked value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goodwe-review-agent
2026-09-28 23:20:33 +02:00
parent 2d2b6afb07
commit a9f7d78527
9 changed files with 148 additions and 26 deletions
+11 -8
View File
@@ -380,9 +380,10 @@ def _cycle_hours(phase, soc, cap_kwh, cfg):
def peak_run(c, day0, day1, now, shadow, limit_w):
"""While the batteries hold the grid under the peak limit, one block for
the current 15-minute period on every battery."""
the current 15-minute period on every battery. An Off battery gets it
only while it gives power (OPT-11: Off covers only the shortfall)."""
pg = c.peak_guard
if pg is None or not pg.engaged:
if not peak_engaged(c):
return []
start = now - timedelta(minutes=now.minute % 15, seconds=now.second,
microseconds=now.microsecond)
@@ -390,7 +391,8 @@ def peak_run(c, day0, day1, now, shadow, limit_w):
return [_run(i, "peak", start, end, day0, day1,
run_peak_what(pg.ceiling_w or limit_w, shadow),
run_peak_why(), None)
for i in range(len(c.inverters))]
for i, inv in enumerate(c.inverters)
if not _off(c, inv) or flow(inv.batt) == "giving"]
def house_runs(n, day0, day1, now, shadow, skip=()):
@@ -526,9 +528,11 @@ def refusing(c, inv) -> int:
def peak_engaged(c) -> bool:
"""The peak guard is asking the batteries for power this cycle."""
"""The peak guard asks the batteries for power this cycle. Not when it is
blind or floor-limited: those are engaged with a 0 W floor (no charging
only), so no battery gives power for it."""
pg = getattr(c, "peak_guard", None)
return bool(pg is not None and pg.engaged)
return bool(pg is not None and pg.engaged and (pg.floor_w or 0.0) > 0.0)
def battery_rows(c, shadow, running, held=False):
@@ -559,7 +563,6 @@ def battery_rows(c, shadow, running, held=False):
reading=inv.soc is not None and inv.batt is not None,
phase=phase, shadow=shadow, running=running, held=held,
refused=refusing(c, inv), peak=peak,
moving=flow(inv.batt) in ("giving", "charging"),
giving=flow(inv.batt) == "giving", other_refuses=other,
sf_ahead=sf_ahead)
rows.append({
@@ -592,7 +595,7 @@ def battery_rows(c, shadow, running, held=False):
def battery_why(*, modes_on, mode, off, reading, phase, shadow, running,
held, refused, peak, moving, sf_ahead, giving=False,
held, refused, peak, sf_ahead, giving=False,
other_refuses=None) -> str:
"""The key of one battery's "why" line (`home_words.why`), most urgent
first. Pure, so every branch is tested without a controller."""
@@ -608,7 +611,7 @@ def battery_why(*, modes_on, mode, off, reading, phase, shadow, running,
return "cycle_mode" if modes_on else "cycle"
if off:
# Off gives way to the peak limit only (OPT-11 design section 2).
return "off_peak" if peak and moving else "off"
return "off_peak" if peak and giving else "off"
if refused:
return "refuses_give" if refused > 0 else "refuses_charge"
if peak: