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
@@ -236,6 +236,15 @@ def pack_energy_of(inv):
|
||||
inv.capacity_kwh, inv.max_w)
|
||||
|
||||
|
||||
# SAFETY-73 review: the evidence must also span this long, not only
|
||||
# SETTLE_CYCLES cycles. SETTLE_CYCLES was derived at the 2 s meter tick (22 s
|
||||
# from its first to its twelfth cycle). A 1 s meter makes 12 cycles only 11 s,
|
||||
# shorter than a healthy battery reading's lag (BATT_OBLIGED_S, 17 s, plus the
|
||||
# plant's dead time), and both inverters of a site were then refused after
|
||||
# ordinary load steps. At 2 s and slower this changes nothing.
|
||||
REFUSED_SPAN_S = (_ctl.SETTLE_CYCLES - 1) * _ctl.LOOP_TICK_S
|
||||
|
||||
|
||||
def _refused_interval(sign: int) -> tuple:
|
||||
"""SAFETY-73: the (lo, hi) a refusing unit may be asked: 0 W that way."""
|
||||
return (-math.inf, 0.0) if sign > 0 else (0.0, math.inf)
|
||||
@@ -1171,7 +1180,9 @@ class Controller:
|
||||
_LOG.info("%s: the battery refuses power, so it is asked 0 W and the "
|
||||
"other inverters take over: BATTERY_REFUSES.", inv.name)
|
||||
_LOG.debug("Evidence for %s: BATTERY_REFUSES: %s.", inv.name, evidence)
|
||||
if (inv.soc is not None and inv.bms_soc_floor is not None
|
||||
# Only a refusal to GIVE: a full battery that stops taking charge is
|
||||
# above its cut-out level by definition and says nothing about it.
|
||||
if (sign > 0 and inv.soc is not None and inv.bms_soc_floor is not None
|
||||
and float(inv.soc) > float(inv.bms_soc_floor)):
|
||||
# Owner decision 3: a refusal above the configured cut-out level
|
||||
# may mean that level is set wrong. One line, no safety stop.
|
||||
@@ -1243,12 +1254,15 @@ class Controller:
|
||||
# (a flapping load) reads ~0 through the plant's own lag, and that
|
||||
# is a battery following, not refusing (TEST-19 flapping family).
|
||||
if abs(standing) > _ctl.PROBE_W and abs(b) < _ctl.REFUSED_BELOW_W:
|
||||
inv.ref_count = (min(inv.ref_count + 1, _ctl.SETTLE_CYCLES)
|
||||
if sign == inv.ref_sign else 1)
|
||||
if sign != inv.ref_sign or not inv.ref_count:
|
||||
inv.ref_count, inv.ref_since = 0, self._now_mono
|
||||
inv.ref_count = min(inv.ref_count + 1, _ctl.SETTLE_CYCLES)
|
||||
else:
|
||||
inv.ref_count = 0
|
||||
inv.ref_sign = sign
|
||||
if inv.ref_count >= _ctl.SETTLE_CYCLES:
|
||||
if inv.ref_count >= _ctl.SETTLE_CYCLES and (
|
||||
inv.ref_since is None or self._now_mono is None
|
||||
or self._now_mono - inv.ref_since >= REFUSED_SPAN_S):
|
||||
self._unit_refusal_start(
|
||||
inv, sign,
|
||||
f"asked {standing:+.0f} W, the battery gave {b:+.0f} W "
|
||||
|
||||
Reference in New Issue
Block a user