SAFETY-73 review: a refusal needs 22 s of evidence, and the cut-out warning is for giving only
- REFUSED_SPAN_S: the 12 cycles of evidence must also span (SETTLE_CYCLES-1) * LOOP_TICK_S = 22 s. At a 1 s meter, lagged healthy readings refused both units 10-16 times an hour after load steps (site held 0 W). 2 s and slower ticks are unchanged. - REFUSES_ABOVE_CUT_OUT only for a refusal to give power: a full battery that stops taking charge is always above its cut-out level. - test_delivery: its wedged fixture (0.2 s cycles) no longer reaches a refusal; the release/1.0 checks are restored and a span check added. - test_refusal_units: lagged-reading and full-battery plants, two new checks, U14/U15 in the mutation table. Stale "> 500 W" wording fixed. DOCS/DECISIONS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1a9c7523c9
commit
06aa09e28c
@@ -592,21 +592,16 @@ check("...and the SHARES now differ, which is OPT-09 and not a weakening of the
|
||||
"cards meet here from opposite sides: OPT-09 stops the ASK, this file "
|
||||
"grades the ALARM, and neither can do the other's half",
|
||||
_pw != _pf
|
||||
and _sf[-1]["shares"][1] == 0.0
|
||||
and max(r["shares"][1] for r in _sw) == 2000.0)
|
||||
# SAFETY-73. A unit that gives < 100 W for 12 cycles while asked > 500 W is a
|
||||
# battery that refuses (owner, 2026-09-27): it is closed, its share moves, and
|
||||
# SAFETY-42 is left with units that deliver something but not enough. This
|
||||
# fixture compresses `delivery_hold_s` to 1 s at 0.2 s cycles, so the report
|
||||
# still comes first here. On a site 185 s against 60 s puts the refusal first.
|
||||
_ref_at = next((i for i, r in enumerate(_sw) if r["shares"][1] == 0.0), None)
|
||||
check("SAFETY-73: the wedged arm's B, giving 0 W against 2000 W, is refused "
|
||||
"after 12 cycles of that evidence and asked 0 W from then, the report "
|
||||
"having come first only because this fixture's hold is 1 s",
|
||||
_ref_at is not None and _ref_at >= control.SETTLE_CYCLES
|
||||
and all(r["shares"][1] > 0 for r in _sw[:_ref_at])
|
||||
and all(r["shares"][1] == 0.0 for r in _sw[_ref_at:])
|
||||
and _cw.inverters[1].refused == 1)
|
||||
and _sf[-1]["shares"][1] == 0.0 and _sw[-1]["shares"][1] == 2000.0)
|
||||
# SAFETY-73. A unit that gives < 100 W while asked > 200 W is refused after
|
||||
# SETTLE_CYCLES cycles that also span REFUSED_SPAN_S (22 s). This fixture's 14
|
||||
# cycles are 0.2 s apart, so the wedged B meets the count and not the span, and
|
||||
# SAFETY-42's report stands alone here. test_refusal_units.py grades the
|
||||
# refusal. On a site the 185 s hold puts the refusal first.
|
||||
check("SAFETY-73: the wedged arm's B is not refused in this fixture: 12 cycles "
|
||||
"of evidence in under 3 s are not the 22 s a refusal needs",
|
||||
_cw.inverters[1].refused == 0
|
||||
and _cw.inverters[1].ref_count >= control.SETTLE_CYCLES)
|
||||
check("...and the state of charge is still what tells them apart",
|
||||
[r["soc"] for r in _sw] != [r["soc"] for r in _sf])
|
||||
print(f" both arms, last cycle: grid {_sw[-1]['grid']:.0f} W, shares "
|
||||
@@ -654,10 +649,10 @@ check("...and `account()` STILL turns this pack away if the ask does arrive - "
|
||||
account(live=True, parked=False, soc=_cold_b.soc,
|
||||
bms_floor=_cold_b.bms_soc_floor) is not None)
|
||||
check("...and the WEDGED arm's B differs from it in exactly that one place - "
|
||||
"which is why it was asked for its share until the refusal, and named",
|
||||
"which is why it is still asked for its share, and still named",
|
||||
account(live=True, parked=False, soc=_cw.inverters[1].soc,
|
||||
bms_floor=_cw.inverters[1].bms_soc_floor) is None
|
||||
and _sw[0]["shares"][1] > 0 and _sw[-1]["named"] == ("inverter-b",))
|
||||
and float(_cw.inverters[1].standing_w()) > 0)
|
||||
|
||||
# ⚠️ WHICH FLOOR, AND THIS IS A WIRING CHECK THAT THE PURE ONES ABOVE CANNOT
|
||||
# MAKE. `peakguard.pack_floor_pct()` conjoins a pack's own cut-out with the
|
||||
@@ -930,13 +925,9 @@ check("...and EVERY WATT IS THE SAME. Grid, site target, both shares and both "
|
||||
"reported or not - which is what 'it does not act' means, stated as a "
|
||||
"measurement instead of as an intention",
|
||||
_power_domain(_sw) == _power_domain(_sn))
|
||||
_trip_at = next((i for i, r in enumerate(_sw) if r["named"]), None)
|
||||
check("...the wedged unit is still commanded its share after the trip, not "
|
||||
"parked by this guard: this is NOT SAFETY-02's remedy. What later takes "
|
||||
"the share is SAFETY-73's refusal, which runs the same in the arm where "
|
||||
"this guard never trips (the line above)",
|
||||
_trip_at is not None and _ref_at is not None and _trip_at < _ref_at
|
||||
and all(r["shares"][1] > 0 for r in _sw[_trip_at:_ref_at]))
|
||||
check("...the wedged unit is still being commanded its share after the trip, "
|
||||
"not parked: this is NOT SAFETY-02's remedy and must not become it",
|
||||
abs(float(_cw.inverters[1].target)) > 0)
|
||||
check("...and nothing named `delivery` reaches the arbiter - the resolution's "
|
||||
"own sentence is unchanged by the trip",
|
||||
"deliver" not in _cw.reason.lower() and _cw.reason == _cn.reason)
|
||||
|
||||
Reference in New Issue
Block a user