SAFETY-136 round 2: tests for override and direction, true wording
checks / unit (pull_request) Successful in 26m2s
infra-pin / fresh (pull_request) Successful in 2s

F1 the on-change case is tested as slow, F2 override and direction checks,
F3 the 3.75 s rule in the texts, F4 the quarter figures and the live note.
No behaviour change.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
goodwe-agent
2026-11-09 20:51:06 +01:00
parent c50e2279dd
commit 0f9c119bcf
3 changed files with 63 additions and 11 deletions
@@ -18,7 +18,7 @@ limit of SAFETY-128's fast meter fit). It counts arrivals, not changes of
value: on the recorded day at 1-2 s the value-based test (`MeterFit.slow()`) value: on the recorded day at 1-2 s the value-based test (`MeterFit.slow()`)
called 24 % of the cycles slow, because the recorded house repeats a value called 24 % of the cycles slow, because the recorded house repeats a value
for 5 s or more, and the commands then differed from base. So there is no for 5 s or more, and the commands then differed from base. So there is no
option. At a 1-3 s meter it is false, the tuning is never touched and the option. At a meter with a usual gap of 3.75 s or less (1-3 s) it is false, the tuning is never touched and the
commands are those of the tree before. A meter that Home Assistant passes on commands are those of the tree before. A meter that Home Assistant passes on
only when its value changes looks as slow as its changes are far apart, as it only when its value changes looks as slow as its changes are far apart, as it
does to SAFETY-128's fits: a 1-2 s meter with a value that holds for 5 s or does to SAFETY-128's fits: a 1-2 s meter with a value that holds for 5 s or
@@ -63,10 +63,18 @@ from base. Together their export is 10 363 -> 9 559 Wh (-8 %) and import
8 389 -> 7 835 Wh (-7 %). Only 5 export more than 1 Wh and 14 import more: 8 389 -> 7 835 Wh (-7 %). Only 5 export more than 1 Wh and 14 import more:
export by at most 11.5 Wh (a 10 s battery lag, +10 %), import by at most export by at most 11.5 Wh (a 10 s battery lag, +10 %), import by at most
17 Wh (a 12 s dead time, one inverter, +7 %), and the quarter average by at 17 Wh (a 12 s dead time, one inverter, +7 %), and the quarter average by at
most 35 W, once 73 W (10 s meter, 10 s dead time, export -59 Wh). Slow most 35 W, except two import quarters: +73 W (10 s meter, 10 s dead time,
export -59 Wh) and +40 W (`dead10_p6`, -6 to 34 W, export -68 Wh). Slow
batteries with a dead time of 8-10 s export 5-39 % less; a 12 s dead time is batteries with a dead time of 8-10 s export 5-39 % less; a 12 s dead time is
the same. `READINGS_LATE` holds are the same seconds in every run. the same. `READINGS_LATE` holds are the same seconds in every run.
**Live, a 1-2 s meter can be judged slow.** Through Home Assistant on change,
a value that holds for 4-10 s makes a 1 s meter slow. In 20 rig scenarios the
commands were those of base. On the recorded day (1 s, N=1, 1 day, repeats
dropped; its values hold about 5 s) it was slow in 33 % of cycles, half gain
in 83 cycles, 15 422 commands in both trees, 4 016 with another value (at
most 190 W apart), cost 0.61 EUR in both (review of SAFETY-136).
**Does not change.** Meters of 1-2 s: the same commands in all 63 runs (34 **Does not change.** Meters of 1-2 s: the same commands in all 63 runs (34
at 1 s, 29 at 2 s), and the recorded-day digests (see the report). The late at 1 s, 29 at 2 s), and the recorded-day digests (see the report). The late
verdict, the hold and the freeze tests. 15 s late is only partly helped, and verdict, the hold and the freeze tests. 15 s late is only partly helped, and
+3 -3
View File
@@ -1,10 +1,10 @@
"""SAFETY-136: on a slow meter, follow the meter gently until the last move shows. """SAFETY-136: on a slow meter, follow the meter gently until the last move shows.
A meter whose readings come 4 s or more apart, read through Home Assistant, can A meter whose usual gap between readings is over 3.75 s (a 4-5 s meter), read
be 10-15 s late. The loop then answers the same error again at each cycle through Home Assistant, can be 10-15 s late. The loop then answers the same error again at each cycle
and overshoots. After a move of MOVE_W or more the loop therefore runs at and overshoots. After a move of MOVE_W or more the loop therefore runs at
WAIT_SHARE of its gain until the meter has fallen SHOWN of the move, or for at WAIT_SHARE of its gain until the meter has fallen SHOWN of the move, or for at
most WAIT_CYCLES cycles. Off for a meter under 4 s, so those sites run as most WAIT_CYCLES cycles. Off for a meter with a usual gap of 3.75 s or less, so those sites run as
before. DECISIONS/SAFETY-136-slow-meter-settle.md. before. DECISIONS/SAFETY-136-slow-meter-settle.md.
""" """
+50 -6
View File
@@ -118,6 +118,14 @@ check("the meter fell 200 W of 600 W: still half the gain",
check("two cycles waited: the third has the whole gain", check("two cycles waited: the third has the whole gain",
s.tuning(T, 300.0, True) is T and s.pend is None) s.tuning(T, 300.0, True) is T and s.pend is None)
s.moved(500.0, 0.0, 600.0, True) s.moved(500.0, 0.0, 600.0, True)
check("the meter went the other way by half the move: the wait stands",
s.tuning(T, 800.0, True).gain == T.gain * 0.5)
s.reset()
s.moved(500.0, 0.0, -600.0, True)
check("a move down, the meter rose half the move: the whole gain",
s.tuning(T, 800.0, True) is T)
s.reset()
s.moved(500.0, 0.0, 600.0, True)
check("the meter fell half the move: the whole gain at once", check("the meter fell half the move: the whole gain at once",
s.tuning(T, 200.0, True) is T and s.pend is None) s.tuning(T, 200.0, True) is T and s.pend is None)
s.moved(500.0, 0.0, -600.0, True) s.moved(500.0, 0.0, -600.0, True)
@@ -148,6 +156,38 @@ print(f" sum: {tot_on:.1f} Wh, off {tot_off:.1f} Wh")
check("the four together export at most 65 % of the run with it off", check("the four together export at most 65 % of the run with it off",
tot_on <= 0.65 * tot_off) tot_on <= 0.65 * tot_off)
# --------------------------------------------------------------------------- #
print("\n2b. Another writer overrides the loop: the wait ends")
halved = []
real_tuning = settle.Settle.tuning
def watch_tuning(self, tuning, grid_w, slow):
r = real_tuning(self, tuning, grid_w, slow)
halved.append(r is not tuning)
return r
settle.Settle.tuning = watch_tuning
try:
mark = []
def bump(rig, c, t):
# right after a cycle that started a wait, another writer moves the
# command, so the next cycle sees `target != loop_w`
if not mark and c.settle.pend and t > 330.0:
mark.append(len(halved))
c.target = c.target + 300.0
r = R.Rig(house=oven, noise=80.0, p1_s=5.0, lag_s=10.0, lag_on=ON,
lag_off=900.0)
r.run(500.0, bump)
finally:
settle.Settle.tuning = real_tuning
nxt = nth(halved, mark[0] if mark else 10 ** 6)
check("the cycle after an override has the whole gain",
bool(mark) and present(nxt) and nxt is False)
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
print("\n3. 5 s meter, no lag") print("\n3. 5 s meter, no lag")
for name in ("oven", "kettle", "step", "stepup"): for name in ("oven", "kettle", "step", "stepup"):
@@ -202,15 +242,19 @@ for p1, expect in ((1.0, False), (2.0, False), (3.0, False), (4.0, True),
last = nth(seen, -1) last = nth(seen, -1)
check(f"{p1:.0f} s meter: slow is {expect}", check(f"{p1:.0f} s meter: slow is {expect}",
present(last) and last is expect) present(last) and last is expect)
# A 1 s meter on a house that changes every 5 s repeats its value 4 times in 5: # A 1 s meter on a house that changes every 5 s repeats its value 4 times in 5.
# the fit that counts changed values calls that slow, the readings do not (the # Where every reading arrives (the rig, a polled meter) it is not slow; where
# recorded day at 1-2 s showed it). # Home Assistant sends a reading only when the value changes (ha_signed) the
s, m = settle.Settle(), MeterFit() # same meter is slow (T3 of the review; DECISIONS).
s, m, h = settle.Settle(), MeterFit(), settle.Settle()
for k in range(60): for k in range(60):
s.arrive(float(k), k) s.arrive(float(k), k)
h.arrive(float(k), k // 5)
m.add(float(k), 0.0, 1800.0 + 100.0 * (k // 5)) m.add(float(k), 0.0, 1800.0 + 100.0 * (k // 5))
check("a 1 s meter whose value changes every 5 s: not slow, though its value " check("every reading arrives, value changes every 5 s: not slow (the value "
"repeats", not s.slow() and m.slow()) "fit says slow)", not s.slow() and m.slow())
check("the same meter through Home Assistant on change (a reading per "
"change): slow", h.slow())
print() print()
if fails: if fails: