UX-13 P3 review: truthful peak lines, a clock-proof test, Simple stays default
- 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:
@@ -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:
|
||||
|
||||
@@ -473,9 +473,11 @@
|
||||
window.UX13.batteries(batts);
|
||||
fillNotes();
|
||||
}
|
||||
function setMode(v, redraw) {
|
||||
// `remember`: only the person's own pick of a view is kept for next time;
|
||||
// a jump to Installer (a link, a setting to fix) is not.
|
||||
function setMode(v, redraw, remember) {
|
||||
mode = v;
|
||||
try { localStorage.setItem("ux13.settings.view", v); } catch (e) { /* no storage */ }
|
||||
if (remember) try { localStorage.setItem("ux13.settings.view", v); } catch (e) { /* no storage */ }
|
||||
document.querySelectorAll("#set-view [role=radio]").forEach((b) => {
|
||||
const on = b.dataset.v === v;
|
||||
b.setAttribute("aria-checked", String(on)); b.tabIndex = on ? 0 : -1;
|
||||
@@ -486,13 +488,13 @@
|
||||
if (redraw && M && view === "settings") render();
|
||||
}
|
||||
document.querySelectorAll("#set-view [role=radio]").forEach((b, i, all) => {
|
||||
b.addEventListener("click", () => setMode(b.dataset.v, true));
|
||||
b.addEventListener("click", () => setMode(b.dataset.v, true, true));
|
||||
b.addEventListener("keydown", (e) => {
|
||||
const d = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[e.key];
|
||||
if (!d) return;
|
||||
e.preventDefault();
|
||||
const j = all[(i + d + all.length) % all.length];
|
||||
setMode(j.dataset.v, true); j.focus();
|
||||
setMode(j.dataset.v, true, true); j.focus();
|
||||
});
|
||||
});
|
||||
setMode(mode, false);
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
"off_peak": "The peak limit comes before Off. It rests again after.",
|
||||
"refuses_give": "The battery refuses to give power. The add-on tries again every 15 minutes.",
|
||||
"refuses_charge": "The battery refuses to charge. The add-on tries again every 15 minutes.",
|
||||
"peak": "The peak limit needs power, so every battery gives.",
|
||||
"peak": "The peak limit needs power from the batteries.",
|
||||
"modes_off": "Battery modes are off, so it follows the house.",
|
||||
"solar_first": "Solar first: it gets spare solar before the others.",
|
||||
"leftover": "It gets the solar that Solar first batteries cannot take.",
|
||||
|
||||
@@ -259,10 +259,10 @@
|
||||
"cycle": "De maandelijkse cyclus houdt de meting van het laadniveau juist.",
|
||||
"cycle_mode": "De maandelijkse cyclus gaat voor. Daarna volgt ze weer haar modus.",
|
||||
"off": "U hebt haar uitgezet.",
|
||||
"off_peak": "De piekgrens gaat voor Uit. Daarna rust ze weer.",
|
||||
"off_peak": "De piekgrens gaat voor op Uit. Daarna rust ze weer.",
|
||||
"refuses_give": "De batterij weigert te leveren. De add-on probeert het elke 15 minuten opnieuw.",
|
||||
"refuses_charge": "De batterij weigert te laden. De add-on probeert het elke 15 minuten opnieuw.",
|
||||
"peak": "De piekgrens vraagt vermogen, dus elke batterij levert.",
|
||||
"peak": "De piekgrens vraagt vermogen van de batterijen.",
|
||||
"modes_off": "Batterijmodi staan uit, dus ze volgt het huis.",
|
||||
"solar_first": "Eerst zonne-energie: ze krijgt het overschot vóór de andere.",
|
||||
"leftover": "Ze krijgt de zonne-energie die de batterijen met Eerst zonne-energie niet opnemen.",
|
||||
|
||||
Reference in New Issue
Block a user