Files
goodwe-addon/goodwe_controller/test_packshare.py
glenn schrooyenandClaude Opus 5.5 770988c902
checks / unit (pull_request) Successful in 8m44s
TEST-26: a missing value in a check is a named FAIL, not a traceback
Checks and prints that indexed an empty list, exhausted next(), or formatted
None killed the rest of their file (FCST-06). Read them through the new
named_absence.py (nth/first/field/given/present) and conjoin a presence term.

- every site in the brief, plus the family the new scan and a sweep found
- test_named_absence.py: scans every test_*.py for unguarded filtered-list
  indexing, next() without a default and numeric formats on maybe-None
  names; proven to bite; prints what it cannot see, counted
- test_price.py 16a: the `_T17_CAPS is _T17_CAPS` tautology now grades the
  two assemble() calls' arguments
- test_control.py: coverage table re-measured on runs graded to the end

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-25 00:06:58 +02:00

1316 lines
74 KiB
Python

"""Runnable check for OPT-09. `python3 test_packshare.py`
THE CARD IN ONE SENTENCE: the architecture's one free parameter - the allocator's
`weights` - was never passed by anything, so the site target was split 50/50
regardless of what either pack could do, and a pack that could not deliver its
share parked the site with a standing grid error and no alarm.
⚠️ INVERTER B DOES NOT EXIST (expected ~2026-09-12), AND EVERY TWO-UNIT NUMBER
IN THIS FILE IS THEREFORE ARITHMETIC AND NOT HARDWARE. B's figures - 4600 W,
20.48 kWh, 10 % floor - are FROM THE DATASHEET AND THE SERIAL (NOTES.md 1.1b);
A's 5000 W / 15.36 kWh / 10 % are measured. `sim/plantsim.py`'s INVERTER_B is
inverter A copied with `measured=False` on it, so anything driven through a
modelled plant here - part 8 - is TWO COPIES OF A given different numbers. What
this file grades is the SPLIT, which is arithmetic over intervals and weights
and needs no plant at all; where a plant appears it is modelled IN THIS FILE
(`lag_s=0`, obeys within one cycle, bounded by its rating) exactly as
test_alloc.py part 8's is, and the claim is about the control law rather than
about a GW5000S-BP.
⚠️ THE FIXTURE IS THE HARD PART, AND PART 1 IS WHERE IT IS EARNED. Two packs
that differ in ONLY ONE of SoC and capacity cannot tell a SoC-aware weighting
from a capacity-aware one, and two identical packs cannot tell ANY weighting
from any other - every scheme agrees on equal packs, so a weighting bug is
invisible and the checks stay green. That is this repository's recurring defect
(MUTATION.md: "a check whose data cannot reach the failure it names") and part 1
grades the fixture itself against it before anything else is asserted.
⚠️ MUTATING THE MODULES THIS FILE GRADES? PURGE `__pycache__` FIRST - or run
`tools/mutation_run.sh`, which does. `python3 -B` alone does NOT save you.
"""
import ast
import sys
from datetime import datetime, timedelta, timezone
from math import inf
from pathlib import Path
from app import arbitrage, packshare
from named_absence import first, nth, present
from app.allocator import plan
from app.arbiter import (
ACTUATOR_SITE, CLAIM_SCOPE, P_LOOP, P_SAFETY, SCOPE_UNIT, Claim, resolve,
)
from app.control import Tuning, compute
from app.delivery import (
DELIVERY_SHARE_FRACTION, UnitDelivery, account, delinquent,
)
from app.peakguard import discharge_permitted
fails = []
def check(name, cond):
print(f" {'ok ' if cond else 'FAIL'} {name}")
if not cond:
fails.append(name)
# --- the two packs, and every number's provenance -----------------------------
A, Bee = "inverter-a", "inverter-b"
A_W, B_W = 5000.0, 4600.0 # A measured; B FROM DATASHEET
A_KWH, B_KWH = 15.36, 20.48 # A measured; B FROM DATASHEET
FLOOR = 10.0 # both, register 0x0560 / datasheet
A_SOC, B_SOC = 20.0, 80.0 # the fixture's own choice - see part 1
def PE(name, soc, kwh, floor=FLOOR, max_w=0.0):
return packshare.PackEnergy(name, soc, floor, kwh, max_w)
PACKS = [PE(A, A_SOC, A_KWH), PE(Bee, B_SOC, B_KWH)]
def envelopes(packs):
"""`{unit: (lo, hi)}` for a roster, the way main.py files them."""
return {p.name: packshare.envelope(p) for p in packs}
def site_claims(packs, target_w, ratings=(A_W, B_W), envelope=True):
"""The claim list main.py builds for one cycle, at a roster of two.
`envelope=False` is WHAT SHIPPED: the `pack` claim is the half of this card
that constrains, so a baseline that kept it would be comparing OPT-09
against itself.
"""
out = []
for p, rating in zip(packs, ratings):
out.append(Claim.limit("supervised", P_SAFETY, -rating, rating, "max_w",
actuator=p.name))
env = packshare.envelope(p)
if envelope and env.binding:
out.append(Claim.limit("pack", P_SAFETY, env.lo, env.hi, env.why,
actuator=p.name))
out.append(Claim.set("loop", P_LOOP, target_w, "grid",
actuator=ACTUATOR_SITE))
return out
def cycle(packs, target_w, weights="soc", ratings=(A_W, B_W), envelope=True):
"""One `resolve()`, the way main.py calls it. `weights` is the variable.
`weights="shipped"` reverts BOTH halves - no weighting and no envelope -
which is the baseline every "before this card" line below is measured
against.
"""
if weights == "soc":
w = lambda t: packshare.weights(packs, t) # noqa: E731 - OPT-09
elif weights in ("equal", "shipped"):
w = None
envelope = envelope and weights != "shipped"
else:
w = weights
return resolve(site_claims(packs, target_w, ratings, envelope),
units=tuple(p.name for p in packs), weights=w, tick=1)
# ==============================================================================
print("1. THE FIXTURE, GRADED BEFORE ANYTHING IS ASSERTED WITH IT")
# Four weighting schemes anybody might write. On the RIGHT fixture they give
# four different splits; on a wrong one they collapse into each other and every
# check downstream is green whatever the code does.
def split(weights, target=1000.0, packs=None):
r = plan(target, {p.name: _B(-A_W, A_W) for p in (packs or PACKS)},
weights=weights)
assert r.ok, r.refused
return r.shares
class _B:
"""One unit's interval, the way ARB-03 stage 1 hands it over."""
def __init__(self, lo=-inf, hi=inf):
self.lo, self.hi = lo, hi
self.gate = self.pinned_w = self.pinned_by = None
self.contradiction = False
def schemes(packs, ratings=(A_W, B_W)):
"""The four candidate weightings, as `{name: weights dict}`."""
return {
"equal": {p.name: 1.0 for p in packs},
"power": {p.name: r for p, r in zip(packs, ratings)},
"soc only": {p.name: max(0.0, p.soc - p.bms_floor) for p in packs},
"energy": packshare.weights(packs, 1000.0),
}
# ⚠️ THE PRECONDITION, WRITTEN BEFORE THE FIXTURE IS USED FOR ANYTHING. A
# `weights()` that answered None on this roster would make every scheme below
# collapse to `equal` - or, as an earlier draft found, take this file down with a
# TypeError instead of a FAIL. A mutation that crashes is a weaker gate than one
# that fails a named check, so the named check is written first.
check("PRECONDITION: `packshare.weights()` HAS AN OPINION about this roster - "
"both packs state a capacity and a reading, so the answer is a mapping "
"over both units and not the None that means 'weigh everything 1.0'",
isinstance(packshare.weights(PACKS, 1000.0), dict)
and set(packshare.weights(PACKS, 1000.0)) == {A, Bee}
and set(packshare.weights(PACKS, -1000.0)) == {A, Bee})
_sch = schemes(PACKS)
_splits = {k: split(v) for k, v in _sch.items()}
for _k, _v in _splits.items():
print(f" {_k:<9} weights "
+ ", ".join(f"{u}={_sch[_k][u]:g}" for u in sorted(_sch[_k]))
+ " -> " + ", ".join(f"{u}{s:+d}" for u, s in sorted(_v.items())))
check("⚠️ THE FIXTURE SEPARATES ALL FOUR SCHEMES. Two packs at 20 %/15.36 kWh "
"and 80 %/20.48 kWh - DIFFERENT IN BOTH SoC AND CAPACITY - give four "
"distinct splits of the same 1000 W, so a weighting bug has somewhere to "
"show. This is AC 2, and it is the check the other parts rest on",
len({tuple(sorted(v.items())) for v in _splits.values()}) == 4)
_ident = [PE(A, 50.0, A_KWH), PE(Bee, 50.0, A_KWH)]
_ident_splits = {k: split(v, packs=_ident)
for k, v in schemes(_ident, (A_W, A_W)).items()}
print(" the SAME four schemes on two IDENTICAL packs (50 %, 15.36 kWh, "
"5000 W each): "
+ "; ".join(f"{k}: " + ", ".join(f"{u}{s:+d}" for u, s in sorted(v.items()))
for k, v in _ident_splits.items()))
check("...and the counterfactual that says why the fixture had to be built this "
"way: on two IDENTICAL packs every one of the four schemes gives the SAME "
"split, so a fixture of equal packs cannot distinguish the shipped "
"50/50 from any weighting whatever. That is the defect this repository "
"has found eleven times, and it is why the two packs here differ in both "
"dimensions rather than in one",
len({tuple(sorted(v.items())) for v in _ident_splits.values()}) == 1)
_same_soc = [PE(A, 50.0, A_KWH), PE(Bee, 50.0, B_KWH)]
_same_cap = [PE(A, A_SOC, A_KWH), PE(Bee, B_SOC, A_KWH)]
check("...and ONE dimension is not enough either, in EITHER direction, which "
"is the sharper half. Two packs at the SAME SoC cannot tell an "
"energy-aware weighting from a SoC-only one being wrong about capacity - "
"`soc only` collapses onto `equal` while `energy` follows the capacity "
"alone. Two packs of the SAME CAPACITY cannot tell `energy` from "
"`soc only` AT ALL: with capacity constant the energy headroom IS the SoC "
"headroom scaled, so the two give the identical split and a capacity bug "
"is invisible. Only differing in BOTH separates them",
split(schemes(_same_soc)["soc only"], packs=_same_soc)
== split(schemes(_same_soc)["equal"], packs=_same_soc)
!= split(schemes(_same_soc)["energy"], packs=_same_soc)
and split(schemes(_same_cap)["energy"], packs=_same_cap)
== split(schemes(_same_cap)["soc only"], packs=_same_cap)
and split(schemes(PACKS)["energy"]) != split(schemes(PACKS)["soc only"]))
# ==============================================================================
print()
print("2. THE ENVELOPE: what a pack's own state of charge FORBIDS")
_empty = PE(A, FLOOR, A_KWH) # exactly ON its own cut-out
_below = PE(A, 5.0, A_KWH)
_full = PE(Bee, 100.0, B_KWH)
_mid = PE(Bee, 55.0, B_KWH)
for _p in (_empty, _below, _full, _mid):
_e = packshare.envelope(_p)
print(f" {_p.name} at {_p.soc:g} % -> "
f"[{_e.lo:g}, {_e.hi:g}] {_e.why or '(no opinion)'}")
check("a pack AT its own BMS cut-out is not asked to discharge: `hi` closes at "
"0 W. ⚠️ AT, not below - `discharge_permitted()`'s boundary is `<=`, "
"because a pack sitting exactly on the floor has already spent everything "
"there was, and permitting one more cycle there makes the floor mean 'one "
"cycle below the floor'",
packshare.envelope(_empty).hi == 0.0
and packshare.envelope(_below).hi == 0.0),
check("...and it may still be CHARGED - `lo` is untouched. A closed envelope is "
"a direction, not a park: parking the pack would be a claim about what it "
"may do in the direction that would fix it",
packshare.envelope(_empty).lo == -inf)
check("a pack at 100 % is not asked to charge: `lo` closes at 0 W, and it may "
"still discharge",
packshare.envelope(_full).lo == 0.0
and packshare.envelope(_full).hi == inf)
check("a pack with room in both directions gets NO OPINION - `(-inf, +inf)`, "
"`binding` False - and main.py files no claim for it. A limit filed on "
"every unit every tick would put an open interval in the arbiter's "
"considered list forever and bury the one cycle that means something",
packshare.envelope(_mid) == packshare.PackEnvelope()
and not packshare.envelope(_mid).binding
and packshare.envelope(_empty).binding)
check("⚠️ AND THE DISCHARGE HALF ASKS `peakguard.discharge_permitted()` RATHER "
"THAN WRITING `soc <= floor` A THIRD TIME. It is the predicate SAFETY-32 "
"made public and SAFETY-42's `account()` already reuses; a third "
"transcription is two future disagreements about what it means - and in "
"particular the BOUNDARY (`<=`) is that function's, so a change to it "
"moves both. Graded by agreement over the whole percent range, on two "
"different floors, at every readable input",
all((packshare.envelope(PE(A, s, A_KWH, f)).hi == 0.0)
== (discharge_permitted(s, f) is not None)
for f in (10.0, 25.0) for s in [x / 2.0 for x in range(0, 201)]))
check("⚠️ AND A MISSING READING OR A MISSING FLOOR WITHHOLDS THE CLOSURE, NOT "
"THE DISCHARGE - THE OPPOSITE DIRECTION FROM `discharge_permitted()`'s "
"OWN, AND DELIBERATELY SO. That predicate decides whether to SPEND a pack "
"for money, where a full battery is exactly the value that would disarm "
"the floor. This decides whether to take a unit's share AWAY and hand it "
"to a sibling, and an absent measurement is not evidence that a battery "
"is flat: closing on it would park a healthy unit's discharge on every "
"tick for as long as its sensor was missing. Both absences have a "
"stronger answer arriving first - `inputs_ok` stops the whole site on a "
"missing SoC, and rule 8 plus the schema refuse an entry with no "
"`bms_soc_floor` at startup",
not packshare.envelope(PE(A, None, A_KWH)).binding
and not packshare.envelope(PE(A, 5.0, A_KWH, None)).binding
and packshare.envelope(PE(A, 5.0, A_KWH, 10.0)).hi == 0.0)
check("⚠️ AND THE FLOOR IS THE PACK'S OWN `bms_soc_floor`, NOT "
"`peakguard.pack_floor_pct()`'s conjunction with `peak_soc_floor`. The "
"EMS floor bounds the PEAK GUARD ONLY (SAFETY-25, decided Option A on "
"2026-09-02): ordinary self-consumption discharges straight past it, and "
"closing an envelope at it would turn an operator preference into a hard "
"stop on the grid-following loop - the memo's DEFERRED Option B, built by "
"accident. A pack at 15 % with a 10 % cut-out is NOT closed here, though "
"a 20 % `peak_soc_floor` would have closed it",
not packshare.envelope(PE(A, 15.0, A_KWH, 10.0)).binding
and discharge_permitted(15.0, 20.0) is not None)
# ==============================================================================
print()
print("3. THE WEIGHTS: energy headroom, and the direction it is measured in")
_dis = packshare.weights(PACKS, +1000.0)
_chg = packshare.weights(PACKS, -1000.0)
print(f" discharging: " + ", ".join(f"{u}={v:.3f} kWh" for u, v in sorted(_dis.items())))
print(f" charging: " + ", ".join(f"{u}={v:.3f} kWh" for u, v in sorted(_chg.items())))
check("discharging, the weight is the energy ABOVE the pack's own cut-out: "
"15.36 kWh x (20-10) % = 1.536 kWh for A, 20.48 kWh x (80-10) % = "
"14.336 kWh for B",
abs(_dis[A] - 1.536) < 1e-9 and abs(_dis[Bee] - 14.336) < 1e-9)
check("charging, it is the room BELOW full: 15.36 x 80 % = 12.288 kWh for A, "
"20.48 x 20 % = 4.096 kWh for B",
abs(_chg[A] - 12.288) < 1e-9 and abs(_chg[Bee] - 4.096) < 1e-9)
check("⚠️ SO THE TWO DIRECTIONS DISAGREE ABOUT WHICH PACK SHOULD CARRY MORE - "
"B by 9:1 on the way out, A by 3:1 on the way in - which is why the "
"weighting cannot be computed before the site target is known and why "
"`resolve()` takes a CALLABLE. A direction-free weighting is not a "
"compromise between these two, it is wrong in one of them on every tick",
(_dis[Bee] > _dis[A]) and (_chg[A] > _chg[Bee]))
check("⚠️ AND IT IS ENERGY, NOT POWER, WHICH IS THE WHOLE OF DELIVERABLE 2. The "
"packs are asymmetric in OPPOSITE directions - B has ~33 % more energy "
"and ~8 % less power - so a power-rating weighting hands the discharge to "
"A (5000 > 4600) and an energy weighting hands it to B. The question the "
"split answers is 'which pack can sustain its share for the hour'",
A_W > B_W and B_KWH > A_KWH
and split(_sch["power"])[A] > split(_sch["power"])[Bee]
and split(_dis)[Bee] > split(_dis)[A])
check("a roster where ANY pack cannot state its headroom gets NO OPINION for "
"the WHOLE roster - None, i.e. 1.0 each, which is what shipped. A pack "
"weighed in kWh against a sibling weighing a dimensionless 1.0 is not a "
"comparison, and half a weighting is worse than none",
packshare.weights([PE(A, A_SOC, None), PE(Bee, B_SOC, B_KWH)], 1000.0) is None
and packshare.weights([PE(A, None, A_KWH), PE(Bee, B_SOC, B_KWH)], 1000.0) is None
and packshare.weights([PE(A, A_SOC, A_KWH, None), PE(Bee, B_SOC, B_KWH)],
1000.0) is None)
check("...and so does a roster on which NOBODY has headroom in this direction. "
"All-zero weights would drop the whole roster into `_fill()`'s LAST-RESORT "
"pass, where the shares come out equal anyway and every plan reads 'last "
"resort: the preferred units were full' - a true sentence about the wrong "
"thing",
packshare.weights([PE(A, FLOOR, A_KWH), PE(Bee, FLOOR, B_KWH)],
+1000.0) is None
and packshare.weights([PE(A, 100.0, A_KWH), PE(Bee, 100.0, B_KWH)],
-1000.0) is None)
check("...and at a roster of one there is no opinion to have: `plan()` is a "
"pass-through at N<=1 and a weight there is arithmetic nobody reads",
packshare.weights([PE(A, A_SOC, A_KWH)], 1000.0) is None
and packshare.weights([], 1000.0) is None)
check("every weight is non-negative and finite, which `allocator.plan()` "
"REFUSES to accept otherwise - a pack below its own floor is at zero "
"usable energy, not at a negative preference",
all(v >= 0.0 for v in packshare.weights(
[PE(A, 2.0, A_KWH), PE(Bee, B_SOC, B_KWH)], 1000.0).values()))
# ==============================================================================
print()
print("4. AC 1, END TO END: a pack at its floor is not allocated discharge")
_at_floor = [PE(A, FLOOR, A_KWH), PE(Bee, B_SOC, B_KWH)]
_r_now = cycle(_at_floor, 1500.0, weights="shipped")
_r_opt = cycle(_at_floor, 1500.0)
print(f" shipped (no weights, no envelope): {_r_now.allocation.explain()}")
print(f" OPT-09: {_r_opt.allocation.explain()}")
check("⚠️ AC 1. A pack sitting on its own BMS cut-out is allocated NO "
"discharge, and its share moves to the sibling WITHIN THAT SIBLING'S "
"ENVELOPE. Before this card the same cycle handed it half the site "
"target - 750 W into a battery that answers no watts to any command",
_r_opt.shares[A] == 0 and _r_opt.shares[Bee] == 1500
and _r_opt.allocation.unmet_w == 0)
check("...and the shipped behaviour really was the other one, so the line "
"above is not vacuous: an equal split of 1500 W is 750/750, and 750 W is "
"what a pack at its cut-out was being told to produce",
_r_now.shares == {A: 750, Bee: 750})
_plan_a = first((p for p in _r_opt.allocation.plans if p.unit == A), "no plan for A")
print(f" {_plan_a.explain()}")
check("...and the RECORD says why, in the line an operator is already reading. "
"`UnitPlan.within_envelope` is asked rather than assumed, and SAFETY-28's "
"write gate refuses a record that contradicts itself",
present(_plan_a) and _plan_a.within_envelope and _plan_a.share_w == 0
and _plan_a.hi == 0.0 and _r_opt.allocated)
check("a pack at 100 % is not allocated a CHARGE either, and the sibling takes "
"the whole of it - the same sentence in the other direction",
cycle([PE(A, 100.0, A_KWH), PE(Bee, B_SOC, B_KWH)], -1200.0).shares
== {A: 0, Bee: -1200})
check("⚠️ AND THE SIGN-COHERENCE REFUSAL IS NOT TRIPPED BY ANY OF THIS. One "
"envelope closed at 0 and the other open cannot produce two shares of "
"opposite sign, because the fill moves every share in ONE direction from "
"a base that is still 0 for the closed unit. Circulating power stays "
"inexpressible",
all(cycle([PE(A, s, A_KWH), PE(Bee, 100.0 - s, B_KWH)], t).allocated
for s in (0.0, FLOOR, 50.0, 100.0) for t in (-2000.0, 0.0, 2000.0)))
check("a unit whose envelope is closed is still IN the split - it is not "
"dropped. `dropped` means the master is stale and the record says so; a "
"full pack is present, commandable, and told 0 W",
not cycle(_at_floor, 1500.0).allocation.dropped
and A in cycle(_at_floor, 1500.0).shares)
# ==============================================================================
print()
print("5. AC 3: unmet_w when NEITHER unit can take the remainder")
_both = [PE(A, FLOOR, A_KWH), PE(Bee, 8.0, B_KWH)]
_r_both = cycle(_both, 1500.0)
print(f" both packs at or under their cut-out: {_r_both.allocation.explain()}")
check("⚠️ AC 3. With every pack at or under its own cut-out the discharge has "
"nowhere to go: both shares are 0 and the whole 1500 W is reported as "
"`unmet_w`. Not refused - `Allocation` is explicit that fusing `unmet_w` "
"into `ok` would let one saturated inverter park the site - and not "
"silently absorbed either",
_r_both.allocated and _r_both.shares == {A: 0, Bee: 0}
and _r_both.allocation.unmet_w == 1500)
check("...and the identity still holds over integers: shares + unmet == sum_w, "
"which is what makes `unmet_w` a measurement rather than an estimate",
sum(_r_both.shares.values()) + _r_both.allocation.unmet_w
== _r_both.allocation.sum_w)
check("...and it is VISIBLE: `explain()` prints it in the line the action log "
"carries, and main.py edge-logs the transition (`_unmet_last`). Before "
"this card nothing anywhere reported a watt that could not be placed",
"UNMET" in _r_both.allocation.explain())
_partial = [PE(A, FLOOR, A_KWH), PE(Bee, B_SOC, B_KWH)]
check("...and a partial shortfall is reported partially rather than all-or- "
"nothing: one pack closed and the sibling's own rating short of the ask "
"places what it can and names the rest",
cycle(_partial, 6000.0).allocation.unmet_w == 6000 - int(B_W))
# ==============================================================================
print()
print("6. AC 4: at N=1 the allocation is BYTE-IDENTICAL to what shipped")
# ⚠️ THE WHOLE OF AC 4 IS THAT `main.py` DOES NOT FILE THE `pack` CLAIM AT N=1,
# and the reason is `actuator_for()`: at one inverter every source routes to the
# SITE, so a unit interval filed there would bound THE SITE TARGET rather than a
# share. That is a behaviour change on the live single-inverter install and it is
# SAFETY-25's decided Option A. What this part grades is the arithmetic on both
# sides of that `if`.
_one = [PE(A, FLOOR, A_KWH)]
_n1_claims = [Claim.limit("supervised", P_SAFETY, -A_W, A_W, "max_w"),
Claim.set("loop", P_LOOP, 1234.5, "grid")]
_n1_now = resolve(_n1_claims, units=(A,), tick=1)
_n1_opt = resolve(_n1_claims, units=(A,),
weights=lambda t: packshare.weights(_one, t), tick=1)
print(f" N=1 shipped: {_n1_now.shares} OPT-09: {_n1_opt.shares}")
check("the pass-through is untouched: the share IS the site target, as a float, "
"unrounded and unclamped, with or without the weights argument",
_n1_now.shares == _n1_opt.shares == {A: 1234.5}
and _n1_opt.allocation.passthrough)
check("...and that holds across the whole range, sign for sign, because "
"`weights()` answers None at a roster of one and `plan()` never reaches "
"its weighted path there anyway",
all(resolve([Claim.limit("supervised", P_SAFETY, -A_W, A_W, "max_w"),
Claim.set("loop", P_LOOP, t, "grid")], units=(A,),
weights=lambda x: packshare.weights(_one, x),
tick=1).shares
== resolve([Claim.limit("supervised", P_SAFETY, -A_W, A_W, "max_w"),
Claim.set("loop", P_LOOP, t, "grid")], units=(A,),
tick=1).shares
for t in (-5000.0, -1999.5, -1.0, 0.0, 1.0, 999.25, 5000.0)))
check("⚠️ AND A CALLABLE THAT ANSWERS None IS THE SAME AS NO ARGUMENT AT ALL, "
"at every roster size. That is what makes the new parameter inert on any "
"install that has not stated its capacities",
resolve(site_claims(PACKS, 1000.0), units=(A, Bee),
weights=lambda t: None, tick=1).shares
== resolve(site_claims(PACKS, 1000.0), units=(A, Bee), tick=1).shares)
# ==============================================================================
print()
print("7. AC 5: verified BY BREAKING IT - equal weights on unequal packs")
# ⚠️ THE CARD ASKS FOR THIS EXPLICITLY: "force equal weights on unequal packs and
# observe red". The point is that the checks above are not passing for a reason
# unrelated to the weighting - so here the SAME cycles are run with the weighting
# reverted to what shipped, and what they produce is printed beside what OPT-09
# produces.
_cases = [
("1500 W out, A on its cut-out", _at_floor, 1500.0),
("1200 W in, A full", [PE(A, 100.0, A_KWH), PE(Bee, B_SOC, B_KWH)], -1200.0),
("1000 W out, both with room", PACKS, 1000.0),
]
_broken = []
for _why, _pk, _t in _cases:
_o, _e = cycle(_pk, _t), cycle(_pk, _t, weights="shipped")
_broken.append((_why, _o.shares, _e.shares))
print(f" {_why:<32} OPT-09 "
+ ", ".join(f"{u}{s:+d}" for u, s in sorted(_o.shares.items()))
+ " equal-weights "
+ ", ".join(f"{u}{s:+d}" for u, s in sorted(_e.shares.items())))
check("every one of these cycles gives a DIFFERENT answer with BOTH halves "
"reverted to what shipped - equal weights, no envelope - so each check "
"above is separated from its own mutation rather than passing for a "
"reason unrelated to the weighting",
all(o != e for _w, o, e in _broken))
_env_only = cycle(_at_floor, 1500.0, weights="equal")
print(f" and with the ENVELOPE alone (equal weights, `pack` claim filed): "
+ ", ".join(f"{u}{s:+d}" for u, s in sorted(_env_only.shares.items())))
check("...and the two halves are separated from EACH OTHER as well: on a pack "
"sitting at its cut-out the envelope alone already gives the whole ask to "
"the sibling, because a closed `cap` is obeyed absolutely, so this case "
"grades the ENVELOPE. Case three - both packs with room, nothing closed - "
"is the one that can only be the WEIGHTS",
_env_only.shares == {A: 0, Bee: 1500}
and len(_broken) > 2 and _broken[2][1] != _broken[2][2])
check("...and the third case is the one that isolates the WEIGHTS from the "
"ENVELOPE: both packs have room, no interval is closed, nothing is "
"refused - and the split still moves from 500/500 to 97/903 because the "
"energy headroom says so",
nth(_broken, 2)[1] == {A: 97, Bee: 903} and nth(_broken, 2)[2] == {A: 500, Bee: 500})
# ==============================================================================
print()
print("8. ⚠️ WHAT THE WEIGHTS CANNOT DO, WHICH IS THE HALF THAT IS EASY TO "
"OVER-READ")
# ARCH-01: the gain seen at the meter is `sum(a_i * g_i)`, a CONVEX COMBINATION
# of the per-unit gains, so a bad split cannot DESTABILISE the site. That says
# nothing about whether the site can REGULATE. A unit at its energy limit has
# g_i = 0; the combination is still inside [min g, max g] and the load it was
# asked to carry simply does not move.
RATING = 2000.0 # per unit, so the SITE rail is 4000 W
def close_loop(load_w, mode, cycles=200, rating=RATING):
"""One integrator on the meter, two units, each told its OWN share.
`mode` is what varies. "shipped" is equal weights and no envelope; "weights"
is OPT-09's preference with the envelope left open; "envelope" is OPT-09
entire. Unit B is AT ITS FLOOR throughout and therefore answers 0 W to any
discharge command - the benign case `delivery.py` measures as
indistinguishable, IN THE POWER DOMAIN, from a wedged box.
⚠️ `max_w` IS THE SITE RAIL - THE SUM OF THE UNIT RATINGS - because that is
what main.py builds `Tuning` with (`max_w=self.site_max_w`). Handing the
loop one unit's rating would model an add-on that does not exist and would
hide the wind-up this part is about: the first draft of this check did that
and reported a 500 W standing error where the shipped law reaches zero.
"""
tuning = Tuning(gain=1.0, slew_w=1000.0, max_w=2.0 * rating)
prev, i_w, sat = 0.0, None, 0
power = [0.0, 0.0]
grid = load_w
packs = [PE(A, 50.0, A_KWH), PE(Bee, FLOOR, B_KWH)]
trace = []
for _c in range(cycles):
d = compute(prev, grid, sum(power), tuning, sat, i_w)
prev, i_w, sat = d.target_w, d.i_w, d.sat_count
bounds = {p.name: _B(-rating, rating) for p in packs}
if mode == "envelope":
for p in packs:
e = packshare.envelope(p)
bounds[p.name] = _B(max(-rating, e.lo), min(rating, e.hi))
w = None if mode == "shipped" else packshare.weights(packs, d.target_w)
r = plan(d.target_w, bounds, weights=w)
assert r.ok, r.refused
# THE MODELLED PLANT. A obeys within one cycle inside its rating; B is at
# its floor and produces NOTHING in the discharge direction whatever it
# is told - which is `plantsim.Plant.limit()`'s own answer for a pack at
# its floor, and is why SAFETY-42 cannot read this case from power alone.
power = [max(-rating, min(rating, float(r.shares[A]))),
min(0.0, float(r.shares[Bee]))]
grid = load_w - sum(power)
trace.append((round(d.target_w, 1), dict(r.shares), round(grid, 1),
r.unmet_w, d.reason))
return trace
_MODES = ("shipped", "weights", "envelope")
_row = {}
for _load in (1500.0, 3000.0):
for _m in _MODES:
_t = close_loop(_load, _m)[-1]
_row[(_load, _m)] = _t
print(f" load {_load:.0f} W, B at its floor, {_m:<9} "
f"site target {_t[0]:+.0f} W, shares "
+ ", ".join(f"{u}{v:+d}" for u, v in sorted(_t[1].items()))
+ f", grid {_t[2]:+.0f} W, unmet {_t[3]:+d} W, {_t[4]}")
check("AT A LOAD THE HEALTHY PACK CAN COVER, ALL THREE ARMS REGULATE TO ZERO "
"GRID ERROR - and the shipped one is NOT thereby fine. It gets there by "
"winding the SITE TARGET to 3000 W for a 1500 W load and standing a "
"+1500 W command on a battery sitting on its own cut-out, permanently "
"saturation-frozen; OPT-09 reaches the same meter reading with a 1500 W "
"target, nothing standing on the empty pack, and the loop in its "
"deadband. The improvement here is not watts, it is that the site has "
"stopped lying to itself - a loop wound to twice the load has no headroom "
"left for the next one",
abs(_row[(1500.0, "shipped")][2]) < 1.0
and abs(_row[(1500.0, "weights")][2]) < 1.0
and abs(_row[(1500.0, "envelope")][2]) < 1.0
and _row[(1500.0, "shipped")][0] == 3000.0
and _row[(1500.0, "shipped")][1][Bee] == 1500
and _row[(1500.0, "shipped")][4] == "saturated-freeze"
and _row[(1500.0, "envelope")][0] == 1500.0
and _row[(1500.0, "envelope")][1][Bee] == 0)
check("⚠️ AND AT A LOAD IT CANNOT COVER, THE WEIGHTS STOP WORKING ALTOGETHER. "
"The weighted arm and the shipped arm command the SAME +2000 W into the "
"empty pack and deliver the SAME 2000 W, because once the preferred unit "
"is at its cap `_fill()`'s LAST-RESORT PASS hands the remainder to the "
"zero-weighted one - deliberately, so that a preference can never "
"manufacture a standing error at the meter. A weight of 0 is 'please do "
"not', never 'you may not'",
_row[(3000.0, "weights")][1] == _row[(3000.0, "shipped")][1]
== {A: 2000, Bee: 2000}
and _row[(3000.0, "weights")][2] == _row[(3000.0, "shipped")][2])
check("⚠️ AND THE ENVELOPE DOES NOT RESCUE THE DELIVERY EITHER - IT NAMES THE "
"SHORTFALL. Same 2000 W delivered, same 1000 W standing at the meter, "
"because the sibling is already at its rating and there is nowhere for "
"the rest to go; what changes is that the empty pack is commanded 0 W "
"instead of 2000 W and 2000 W of site target is REPORTED as unplaceable "
"instead of appearing to have been placed. THIS IS THE WHOLE OF "
"DELIVERABLE 3: ARCH-01's convex combination guarantees a bad split "
"cannot DESTABILISE the site and says nothing about whether the site can "
"REGULATE. A unit at its energy limit has g_i = 0, `compute()` freezes on "
"the resulting error, and the load DOES NOT SHIFT to the sibling by any "
"mechanism in this add-on",
_row[(3000.0, "envelope")][2] == _row[(3000.0, "weights")][2]
and _row[(3000.0, "envelope")][1] == {A: 2000, Bee: 0}
and _row[(3000.0, "envelope")][3] == 2000
and _row[(3000.0, "weights")][3] == 0)
check("...so: WEIGHTS ARE A COMFORT CHOICE FOR STABILITY AND NOT FOR DELIVERY, "
"and the same sentence stated as arithmetic on one plan rather than as a "
"200-cycle run",
plan(3000.0, {A: _B(-RATING, RATING), Bee: _B(-RATING, RATING)},
weights={A: 1.0, Bee: 0.0}).shares == {A: 2000, Bee: 1000})
check("...and SAFETY-42 is where the case this card CANNOT reach is handled. A "
"WEDGED unit - one that accepts its share and does not produce it - has a "
"healthy state of charge, so NO ENVELOPE CLOSES; that module detects it, "
"latches and notifies, and deliberately does not act. OPT-09 stops the "
"site ASKING a pack whose own SoC says it cannot deliver; SAFETY-42 "
"alarms when a pack that said it could does not. ⚠️ THE WEIGHT, HOWEVER, "
"DOES MOVE - this check used to say it did not - and part 8b grades what "
"that cost the alarm, and what SAFETY-43 did about it",
not packshare.envelope(PE(Bee, 55.0, B_KWH)).binding)
# ==============================================================================
print()
print("8b. ⚠️ WHAT THE WEIGHTS DID TO SAFETY-42's DETECTION WINDOW, AND WHAT "
"SAFETY-43 DID ABOUT IT - GRADED UNDER BOTH RULES, ON ONE FIXTURE")
# ⚠️ THIS PART USED TO PIN THE UNFLATTERING ANSWER ON PURPOSE, AND SAFETY-43 IS
# THE CARD THAT INVERTED IT. What it said - and it was true when it was written
# - was: a wedged unit's weight MOVES whenever its energy headroom differs from
# its sibling's (it is a pack fact, not a delivery fact), `delivery.delinquent()`
# named a unit only while its `short_by()` gap exceeded `min_w`
# (`Tuning.saturation_w`, 500 W) IN ABSOLUTE WATTS, and shrinking a unit's share
# shrinks its gap. Shrink it below 500 W and the alarm stopped firing on a unit
# that was still delivering nothing. The old text ended "if it ever goes red
# because the alarm fired, read the card that made it fire before 'fixing' this
# line". SAFETY-43 is that card, this is that red, and the checks below are the
# same three measurements with their answers the other way round.
#
# ⚠️ AND THE EVIDENCE IS THE DIFF BETWEEN TWO RULES ON ONE RUN, NOT A NUMBER
# THAT CHANGED IN A COMMIT. `delinquent()` takes `share_frac`, and
# `share_frac=None` is SAFETY-42's rule EXACTLY - the absolute arm alone. So
# `wedged_loop` grades every cycle twice, once under each rule, and the red and
# the green sit side by side in one output:
#
# load 1500 W, OPT-09 weights, absolute arm only named on 0/200 cycles
# load 1500 W, OPT-09 weights, the shipped rule named on 200/200 cycles
#
# A future reader who wants to know what this card did runs this file.
#
# ⚠️ WHAT SAFETY-43 DID NOT DO IS LOWER `min_w`, and the check on the window
# arithmetic below is what holds that open: the ABSOLUTE arm's suppression
# window is still 500 W wide and still widens to 6500 W under these weights.
# Nothing that was quiet became noisy; a SECOND way to be named was added, and
# it is a fraction of the unit's OWN ask, so it fires on a unit delivering none
# of 125 W exactly as it fires on one delivering none of 1500 W.
#
# ⚠️ AND THE FIXTURE IS A SAFETY-42 TRUE POSITIVE BY THAT CARD'S OWN DEFINITION,
# checked below before anything is concluded from it: B is WEDGED - it accepts
# its share and produces nothing - at a HEALTHY 15 % against its own 10 % floor,
# so `envelope()` does not close and `account()` explains nothing. This is
# exactly the case §5 says the envelope cannot reach, which is why it is the
# case where the weights' side effect mattered.
#
# ⚠️ TWO COPIES OF A. Inverter B does not exist; the plant below is modelled in
# this file the way part 8's is, and B's ratings are datasheet.
WEDGED_A_SOC, WEDGED_B_SOC = 90.0, 15.0 # both healthy, both above their floor
MIN_W = Tuning().saturation_w # what `delinquent()` is handed
ABSOLUTE_ONLY = None # `share_frac=None` - SAFETY-42's rule
def wedged_loop(load_w, weighted, cycles=200, share_frac=DELIVERY_SHARE_FRACTION,
b_delivers=0.0, b_soc=WEDGED_B_SOC, no_account=False):
"""The closed loop with unit B WEDGED at a healthy SoC. Real everything.
Real `compute()`, real `plan()`, real `packshare.weights()`, real
`delivery.account()` and real `delivery.delinquent()`. The only model is the
plant: A obeys within one cycle inside its rating, B delivers `b_delivers`
of whatever it is told - 0.0 is the wedged box, and is what makes this a
SAFETY-42 case rather than an OPT-09 one.
`weighted=False` is equal weights - what shipped. Envelopes are left OPEN in
both arms deliberately: neither pack's SoC closes one here, so filing them
would change nothing and would blur which half of the card is being graded.
`share_frac` is SAFETY-43's relative arm. `ABSOLUTE_ONLY` grades the same
cycles under SAFETY-42's rule, which is how the red and the green below come
out of ONE run over ONE tree instead of out of two commits.
`no_account=True` DELETES the account gate - every unit is handed
`accounted=None` - and it exists to answer "would this arm still be silent
if `account()` did nothing at all?". A control that answers the same way
with the gate removed is not grading the gate, and the cut-out arm below is
exactly that case. See it there; it is the point of that block.
Returns `(cycles on which delinquent() named somebody, last cycle)`.
"""
packs = [PE(A, WEDGED_A_SOC, A_KWH), PE(Bee, b_soc, B_KWH)]
tuning = Tuning(gain=1.0, slew_w=1000.0, max_w=A_W + B_W)
prev, i_w, sat = 0.0, None, 0
power = [0.0, 0.0]
grid = load_w
named, last = 0, None
for _c in range(cycles):
d = compute(prev, grid, sum(power), tuning, sat, i_w)
prev, i_w, sat = d.target_w, d.i_w, d.sat_count
w = packshare.weights(packs, d.target_w) if weighted else None
r = plan(d.target_w, {A: _B(-A_W, A_W), Bee: _B(-B_W, B_W)}, weights=w)
assert r.ok, r.refused
# THE MODELLED PLANT: A within one cycle inside its rating; B WEDGED.
power = [max(-A_W, min(A_W, float(r.shares[A]))),
b_delivers * max(-B_W, min(B_W, float(r.shares[Bee])))]
grid = load_w - sum(power)
who = delinquent(
[UnitDelivery(p.name, r.shares[p.name], m,
None if no_account else
account(live=True, parked=False, soc=p.soc,
bms_floor=p.bms_floor))
for p, m in zip(packs, power)], MIN_W, share_frac)
named += bool(who)
last = (d.target_w, dict(r.shares), grid, who)
return named, last
_WEDGED_PACK = PE(Bee, WEDGED_B_SOC, B_KWH)
check("PRECONDITION: the wedged unit is a SAFETY-42 TRUE POSITIVE and NOT an "
"OPT-09 case - at 15 % against its own 10 % floor no envelope closes and "
"`account()` explains nothing, so anything the weights do to the alarm "
"here is done to a fault the operator is entitled to be told about",
not packshare.envelope(_WEDGED_PACK).binding
and account(live=True, parked=False, soc=WEDGED_B_SOC,
bms_floor=FLOOR) is None)
check("PRECONDITION: the shipped fraction is a fraction, and it is STRICTLY "
"below 1.0 - at 1.0 the relative arm could only fire on a unit IMPORTING "
"while asked to discharge, which is SAFETY-30's fault and not this one, "
"and this card's own case (a unit delivering NONE of its share, gap over "
"share exactly 1.0) would be missed",
0.0 < DELIVERY_SHARE_FRACTION < 1.0)
_wedge = {}
for _load in (1500.0, 6000.0):
for _weighted in (False, True):
for _frac in (ABSOLUTE_ONLY, DELIVERY_SHARE_FRACTION):
_n, _t = wedged_loop(_load, _weighted, share_frac=_frac)
_wedge[(_load, _weighted, _frac)] = (_n, _t)
print(f" load {_load:.0f} W, B wedged at 15 %, "
f"{'OPT-09 weights' if _weighted else 'equal weights ':<14} "
+ ("absolute arm only" if _frac is ABSOLUTE_ONLY
else f"frac {_frac:.2f} ")
+ f", named on {_n:3d}/200 cycles, site target {_t[0]:+.0f} W,"
+ " shares "
+ ", ".join(f"{u}{v:+d}" for u, v in sorted(_t[1].items()))
+ f", grid {_t[2]:+.0f} W, named="
+ (str(sorted(_t[3])) if _t[3] else "(nobody)"))
check("⚠️ THE CARD, RED: UNDER SAFETY-42's RULE AND OPT-09's WEIGHTS THE WEDGED "
"UNIT IS NAMED ON NONE OF THE 200. Same wedged box, same healthy SoC, "
"same load, same detector: the split moves to 1505/125, the gap on the "
"wedged unit falls to 125 W, and 125 W < 500 W so the absolute arm "
"returns {}. This is the defect SAFETY-43 was filed for, and it is "
"measured here rather than remembered",
_wedge[(1500.0, True, ABSOLUTE_ONLY)][0] == 0
and _wedge[(1500.0, True, ABSOLUTE_ONLY)][1][3] == {}
and _wedge[(1500.0, True, ABSOLUTE_ONLY)][1][1] == {A: 1505, Bee: 125})
check("⚠️ THE CARD, GREEN: THE SHIPPED RULE NAMES IT ON ALL 200, ON THE SAME "
"RUN. The unit is standing at +125 W and delivering 0 W - 100 % of its "
"ask is not arriving - and `gap > frac * share` does not care that 125 W "
"is a small number. The ONLY input that moved between this check and the "
"one above is `share_frac`",
_wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][0] == 200
and sorted(_wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][1][3]) == [Bee]
and _wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][1][1] \
== {A: 1505, Bee: 125})
check("AT EQUAL WEIGHTS - WHAT SHIPPED BEFORE OPT-09 - the wedged unit was "
"already named under the absolute rule on every cycle but the first, at "
"a load its sibling can carry on its own. The loop winds the site target "
"to twice the load, the 50/50 split stands +1500 W on a box producing "
"nothing, and 1500 W > 500 W is what the absolute arm reads",
_wedge[(1500.0, False, ABSOLUTE_ONLY)][0] == 199
and sorted(_wedge[(1500.0, False, ABSOLUTE_ONLY)][1][3]) == [Bee]
and _wedge[(1500.0, False, ABSOLUTE_ONLY)][1][1] == {A: 1500, Bee: 1500})
check("...and the shipped rule adds exactly the FIRST cycle to that arm and "
"nothing else - 199 becomes 200. On cycle one the slew limit holds the "
"split at 500/500, a 500 W gap is NOT over a 500 W threshold (the "
"absolute bound is strict), and the relative arm names a box delivering "
"none of its 500 W. That one cycle is the whole of what this card costs "
"an arm that was already firing",
_wedge[(1500.0, False, DELIVERY_SHARE_FRACTION)][0] == 200
and _wedge[(1500.0, False, DELIVERY_SHARE_FRACTION)][0]
- _wedge[(1500.0, False, ABSOLUTE_ONLY)][0] == 1)
check("...and this is the regime where NOTHING ELSE TELLS THE OPERATOR "
"ANYTHING: the meter is satisfied (grid within 5 W of zero), `unmet_w` is "
"0 because both units accepted their shares, no envelope closed and no "
"action-log line fires. The site is quietly running one inverter, and "
"before this card the one instrument built to say so had gone quiet with "
"it",
abs(_wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][1][2]) <= 5.0
and abs(_wedge[(1500.0, False, DELIVERY_SHARE_FRACTION)][1][2]) <= 5.0)
_w = packshare.weights([PE(A, WEDGED_A_SOC, A_KWH), _WEDGED_PACK], 1000.0)
_window_equal = MIN_W * 2.0
_window_opt09 = MIN_W * sum(_w.values()) / _w[Bee]
print(f" the ABSOLUTE arm's suppression window is UNCHANGED by SAFETY-43: "
f"equal weights, site target < {_window_equal:.0f} W; OPT-09 weights, "
f"site target < {_window_opt09:.0f} W of a {A_W + B_W:.0f} W site")
check("⚠️ AND `min_w` WAS NOT LOWERED, WHICH IS THE HALF OF THIS CARD THAT IS "
"EASIEST TO GET WRONG. The absolute arm still suppresses below 1000 W at "
"equal weights and below 6500 W at these ones - the same arithmetic this "
"part has always printed - because SAFETY-43 ADDED a second way to be "
"named rather than loosening the first. Lowering `saturation_w` would "
"have reported normal saturation on every unit at once, which is the "
"false-trip regime SAFETY-41 spent four attempts in",
abs(_window_equal - 1000.0) < 1.0 and abs(_window_opt09 - 6500.0) < 1.0
and MIN_W == Tuning().saturation_w == 500.0)
check("⚠️ AND THE ABSOLUTE ARM WAS NEVER SILENT EVERYWHERE - which is what a "
"check that only ever ran the quiet load would be pinning. Push the load "
"past the healthy pack's OWN RATING and `_fill()`'s last-resort pass "
"hands the wedged unit 3000 W anyway, the gap goes back over 500 W, and "
"both arms named it under SAFETY-42's rule too. The window was real and "
"it had an upper edge; the hole was the quiet regime below that edge, "
"and the shipped rule now names the unit on every cycle of all four runs",
_wedge[(6000.0, True, ABSOLUTE_ONLY)][0] == 195
and _wedge[(6000.0, False, ABSOLUTE_ONLY)][0] == 199
and _wedge[(6000.0, True, DELIVERY_SHARE_FRACTION)][0] == 200
and _wedge[(6000.0, False, DELIVERY_SHARE_FRACTION)][0] == 200
and sorted(_wedge[(6000.0, True, DELIVERY_SHARE_FRACTION)][1][3]) == [Bee])
print()
print(" THE POSITIVE CONTROL: what the relative arm must STILL NOT say")
# ⚠️ A CARD THAT ADDS A WAY TO RAISE AN ALARM HAS TO SHOW WHAT IT DID NOT ADD,
# or it has traded a missed alarm for a false one - which in this system is not
# a rhetorical risk. SAFETY-41 took four attempts on notification cadence and
# the owner's words were "i'm getting really annoyed by the notifications in my
# HA". So: the same fixture, the same weights, the same tiny 125 W share, with
# the ONE fact that makes the shortfall legitimate changed.
# ⚠️ AND THE FIRST DRAFT OF THIS BLOCK WAS VACUOUS, WHICH IS WHY IT NOW PRINTS
# ITS SHARES. It ran the cut-out arm, saw 0/200, and concluded "`account()`
# explains it" and "identical trace to the wedged arm". BOTH WERE FALSE. At its
# own cut-out the pack's usable energy ABOVE that cut-out is zero, so
# `weights()` gives it 0.0, `_fill()`'s preferred pass gives it nothing, the
# healthy sibling covers the load alone so the LAST-RESORT pass never runs, and
# its share is 0 W on ALL 200 CYCLES. A unit with a 0 W share is not in `asked`,
# so `len(asked) < 2` returns {} BEFORE EITHER ARM IS EVALUATED and `account()`
# is never consulted - measured below by deleting the gate and getting the same
# answer. The wedged arm's share is the 125 W printed on its own line above;
# this one's is 0, printed and asserted below. Nothing was identical and nothing
# reached the comparison.
#
# ⚠️ THE FACT THAT REPLACES IT IS BETTER, NOT WEAKER: at its own cut-out OPT-09
# STOPS ASKING THE PACK AT ALL, so SAFETY-43's relative arm is never reached.
# That is a SECOND, INDEPENDENT protection sitting UPSTREAM of `account()` - the
# allocator declines to ask before the detector has to decline to alarm. The
# account gate itself is graded where it is REAL, on a positive 125 W share, in
# `test_delivery.py` part 1 ("THE ACCOUNT GATE IS UNTOUCHED BY IT").
#
# ⚠️ AND IT WAS INVISIBLE FOR ONE REASON: this was the only line in part 8b that
# did not print its shares. `inverter-b+0` would have been on screen from the
# first run. Every line in this part prints them now.
_sat_named, _sat_last = wedged_loop(1500.0, True, b_soc=FLOOR)
_sat_nogate, _sat_nogate_last = wedged_loop(1500.0, True, b_soc=FLOOR,
no_account=True)
print(f" load 1500 W, B AT ITS OWN {FLOOR:.0f} % CUT-OUT, OPT-09 weights, "
f"frac {DELIVERY_SHARE_FRACTION:.2f}: named on {_sat_named:3d}/200 "
f"cycles, shares "
+ ", ".join(f"{u}{v:+d}" for u, v in sorted(_sat_last[1].items()))
+ ", named="
+ (str(sorted(_sat_last[3])) if _sat_last[3] else "(nobody)")
+ f"; with account() DELETED: {_sat_nogate:3d}/200")
# ⚠️ AND THE `no_account=True` SWITCH ITSELF WAS GRADED BY NOTHING. It is used
# in exactly one place - the fixture above - where it PROVABLY cannot change the
# answer, because a cut-out pack is never asked and the gate is never reached.
# Review neutered the switch so it removed nothing: the file stayed GREEN and
# printed "with account() DELETED: 0/200" while `account()` had not been
# deleted. A line asserting a mechanism that did not happen - B1's shape, one
# level down, in the fix for B1.
#
# The switch DOES bite, on a fixture already reachable from this helper: at the
# cut-out with EQUAL weights, `plan()` hands B a 50/50 share, so the gate is the
# only thing keeping it silent. Measured: gate on 0/200, gate deleted 200/200.
# That proves the switch removes the gate, AND gives this part the end-to-end
# account-gate control on a POSITIVE share that it otherwise has to outsource to
# test_delivery.py part 1.
_eq_gate, _eq_last = wedged_loop(1500.0, False, b_soc=FLOOR)
_eq_nogate, _ = wedged_loop(1500.0, False, b_soc=FLOOR, no_account=True)
print(f" ...and the SWITCH is real: same cut-out, EQUAL weights so B IS "
f"asked ({_eq_last[1][Bee]:+d} W last cycle) - gate on {_eq_gate:3d}/200, "
f"gate DELETED {_eq_nogate:3d}/200")
check("⭐ THE `no_account` SWITCH ACTUALLY REMOVES THE GATE, on a fixture where "
"the gate is the only thing keeping the pack silent. Without this the "
"line above prints 'with account() DELETED' about a run in which nothing "
"was deleted - which is exactly the defect this block was rewritten for",
_eq_gate == 0 and _eq_nogate == 200 and _eq_last[1][Bee] > 0)
check("A PACK AT ITS OWN CUT-OUT IS NEVER ASKED, SO THE RELATIVE ARM IS NEVER "
"REACHED. Its usable energy above its own cut-out is zero, `weights()` "
"answers 0.0 for it, the healthy sibling covers the load so `_fill()`'s "
"last-resort pass never runs, and its share is 0 W on every one of the "
"200 cycles - so it is not in `asked` and `len(asked) < 2` returns {} "
"before either arm is compared. ⚠️ THE SHARE IS ASSERTED, not inferred "
"from the silence: a control that cannot say WHY it is quiet is a control "
"that cannot fail",
_sat_named == 0 and _sat_last[3] == {} and _sat_last[1][Bee] == 0)
check("⚠️ AND THAT IS A SECOND, INDEPENDENT PROTECTION - NOT THE ACCOUNT GATE, "
"WHICH THIS ARM DOES NOT TOUCH. Delete `account()` entirely, hand every "
"unit `accounted=None`, and this arm is STILL silent on all 200 cycles, "
"because the protection is UPSTREAM of it: the allocator declines to ask "
"before the detector has to decline to alarm. ⚠️ SO DO NOT READ THIS "
"BLOCK AS EVIDENCE ABOUT `account()` - it is evidence about the ask. The "
"account gate on a POSITIVE share is graded in test_delivery.py part 1",
_sat_nogate == 0 and _sat_nogate_last[3] == {})
_derate_named, _derate_last = wedged_loop(1500.0, True, b_delivers=0.5)
print(f" load 1500 W, B delivering HALF its share, OPT-09 weights, frac "
f"{DELIVERY_SHARE_FRACTION:.2f}: named on {_derate_named:3d}/200 cycles, "
f"shares "
+ ", ".join(f"{u}{v:+d}" for u, v in sorted(_derate_last[1].items()))
+ ", named="
+ (str(sorted(_derate_last[3])) if _derate_last[3] else "(nobody)"))
check("...and ORDINARY TRACKING ON A SMALL SHARE IS NOT DELINQUENCY EITHER. "
"The same unit at the same weights delivering HALF of what it is asked "
"for is short by 50 % - well inside the shipped fraction - and its gap "
"in watts is far under `min_w`, so NEITHER arm names it. A relative "
"criterion that fired here would be the saturation-shaped alarm this "
"whole module is written against",
_derate_named == 0 and _derate_last[3] == {})
check("...and the DERATED control is not vacuous, which is asserted on the ONE "
"thing that could make it so: unlike the cut-out arm above, its unit IS "
"asked - a positive share on every cycle - so it really does reach both "
"arms and is turned away by the size of its shortfall. ⚠️ THIS GUARD USED "
"TO RE-ASSERT THE WEDGED ARM's 200/200 INSTEAD, WHICH IS WHY IT DID NOT "
"CATCH THE CUT-OUT ARM BEING VACUOUS: a check on a DIFFERENT run cannot "
"say anything about whether THIS one reached the comparison",
_derate_last[1][Bee] > 0
and _wedge[(1500.0, True, DELIVERY_SHARE_FRACTION)][0] == 200)
print()
print(" N=1 IS BYTE-IDENTICAL, AND THAT IS ASSERTED RATHER THAN ARGUED")
# ⚠️ THE LIVE INSTALL IS N=1 AND THIS CARD MUST NOT REACH IT. Two independent
# statements, and each is checked against the thing that would break it:
# 1. `plan()` RETURNS BEFORE `weights` IS READ AT ALL, so no weighting - and
# no change to how one is derived - can move a one-inverter share. Graded
# with a `weights` object that RAISES if anything reads it.
# 2. `delinquent()` returns {} at a roster of one for every input, including
# the input the RELATIVE arm was built for. The proper-subset algebra runs
# before either arm, so the fraction is never even multiplied.
class _Poison(dict):
"""A `weights` that cannot be read without saying so. Not a mock: a tripwire."""
def get(self, *a, **k):
raise AssertionError("plan() read weights at a roster of one")
def __getitem__(self, k):
raise AssertionError("plan() read weights at a roster of one")
_n1_plain = plan(1234.5, {A: _B(-A_W, A_W)})
_n1_poison = plan(1234.5, {A: _B(-A_W, A_W)}, weights=_Poison())
check("`plan()` at a roster of ONE returns the site target unread, and it "
"returns it WITHOUT TOUCHING `weights` - handed a mapping that raises on "
"any read, it answers identically to the same call with no weights at "
"all. The N=1 pass-through is above the weighting, so OPT-09 could not "
"move it and SAFETY-43 does not either",
_n1_poison.shares == _n1_plain.shares == {A: 1234.5}
and _n1_poison.unmet_w == _n1_plain.unmet_w)
try:
plan(1234.5, {A: _B(-A_W, A_W), Bee: _B(-B_W, B_W)}, weights=_Poison())
except AssertionError:
_poison_bites = True
else:
_poison_bites = False
check("...and the tripwire is a real one: the SAME poisoned mapping at a roster "
"of TWO is read and raises. A check that could not fail would be "
"measuring nothing",
_poison_bites)
_n1_rel = [delinquent([UnitDelivery(A, s, m)], MIN_W, f)
for s in (0, 125, 500, 2000, 5000, -2000, None)
for m in (0, 60, 125, 2000, -2000, None)
for f in (ABSOLUTE_ONLY, DELIVERY_SHARE_FRACTION)]
check(f"...and `delinquent()` answers {{}} at a roster of one on all "
f"{len(_n1_rel)} of (share, measured, rule) - INCLUDING 0 of 125 W, the "
f"exact input this card added an arm for. `len(asked) < 2` returns "
f"before either threshold is compared, so the one-inverter install this "
f"add-on is running today cannot tell that SAFETY-43 happened",
all(r == {} for r in _n1_rel))
# ==============================================================================
print()
print("9. PER-PACK ARBITRAGE SIZING")
# ⚠️ THE FUNCTION UNDER TEST IS THE SHIPPED ONE, NOT A TRANSCRIPTION OF IT.
# `Controller.trade_power_w()` is one line - the roster view handed to
# `packshare.trade_power_w()` - and this calls that same function. An earlier
# draft of this part re-implemented the two sums here, which is the defect this
# repository keeps finding: the copy that drifts is always the grading one.
trade_power_w = packshare.trade_power_w
_near_full = [PE(A, 50.0, A_KWH, max_w=A_W), PE(Bee, 95.0, B_KWH, max_w=B_W)]
_c, _d = trade_power_w(_near_full, 20.0, 90.0)
print(f" A 50 %, B 95 %, arbitrage bounds 20/90 %: charge_w {_c:.0f} W, "
f"discharge_w {_d:.0f} W (site rating {A_W + B_W:.0f} W)")
check("⚠️ THE BUY SIDE IS THE ONE THAT BITES. With B over the arbitrage ceiling "
"the trade is sized at A's rating alone, where before this card it was "
"the sum of the ratings - the site would buy at 9600 W with one pack able "
"to absorb it, and the watts that could not be placed would come back as "
"`unmet_w` for the site integrator to chase for the rest of the hour",
_c == A_W and _c < A_W + B_W)
check("...and the sell side is unchanged here, because B is well above the "
"floor. Sizing is a REDUCTION of the ask and never an increase",
_d == A_W + B_W)
check("⚠️ AND THE SELL SIDE IS A NO-OP ON EVERY ROSTER TODAY, WHICH IS SAID "
"RATHER THAN LEFT FOR A REVIEWER TO FIND. `arbitrage.decide()` is handed "
"`pack_floor_soc` - the EMPTIEST pack - so a sell is refused outright "
"whenever ANY pack is at or under the floor, i.e. in exactly the states "
"where this sum could be smaller than the site rating. The arithmetic is "
"graded here; it becomes reachable the day that gate becomes per-pack, "
"and moving it is NOT this card's - it would let a sell be sized off the "
"fullest pack while the weights, which are only a preference, decided "
"where the watts went",
trade_power_w([PE(A, 15.0, A_KWH, max_w=A_W),
PE(Bee, B_SOC, B_KWH, max_w=B_W)], 20.0, 90.0)[1] == B_W
and min(15.0, B_SOC) <= 20.0)
check("a pack whose SoC did not read counts on NEITHER side - withholding is "
"the direction that cannot over-size, and `inputs_ok` has already stopped "
"the site by then",
trade_power_w([PE(A, None, A_KWH, max_w=A_W),
PE(Bee, B_SOC, B_KWH, max_w=B_W)], 20.0, 90.0)
== (B_W, B_W))
# ⚠️ AND THE SIZING MUST NEVER REACH `claim()` AS A ZERO, which is a hazard the
# sizing itself created: `arbitrage.claim()`'s own note says a `Claim.set(..., 0)`
# at P_ARBITRAGE is a PARK ORDER that outranks the loop, i.e. it would silently
# disable self-consumption for the whole hour. Graded on `_act()` directly - the
# branch that builds the decision - because a fixture that reached it through a
# real `TariffPlan` would be grading the plan.
_NOW = datetime(2026, 9, 6, 12, 0, tzinfo=timezone.utc)
class _Pt:
def __init__(self, start, price=0.30):
self.start, self.eur_per_kwh, self.provisional = start, price, False
class _Plan:
"""The smallest object `_point_at()` accepts: `plan.read.match(series=...)`."""
def __init__(self, pt):
self.read = self
self._pt = pt
def match(self, series, no_data):
return series(self)
def at(self, when):
return self._pt
class _V:
def __init__(self, cheapest, dearest):
self.spread_eur_kwh, self.required_spread_eur_kwh = 0.20, 0.05
self.cheapest, self.dearest = cheapest, dearest
_here = _Pt(_NOW)
_other = _Pt(_NOW - timedelta(hours=3))
# SAFETY-45's `headroom_w` is the last positional. It is set to a value that
# CANNOT BIND on either call - larger than every sizing below - so this part
# goes on grading OPT-09's per-pack sizing and not the capacity-tariff cap that
# now sits beside it. test_arbitrage.py section 3b is where it is made to bind.
_CLEAR_W = 1e9
_sell = arbitrage._act(_Plan(_here), _NOW, _V(_other, _here), 60.0,
0.0, 0.0, 20.0, 90.0, _CLEAR_W)
_sell_ok = arbitrage._act(_Plan(_here), _NOW, _V(_other, _here), 60.0,
0.0, 4600.0, 20.0, 90.0, _CLEAR_W)
_buy = arbitrage._act(_Plan(_here), _NOW, _V(_here, _other), 60.0,
0.0, 0.0, 20.0, 90.0, _CLEAR_W)
_buy_ok = arbitrage._act(_Plan(_here), _NOW, _V(_here, _other), 60.0, 5000.0,
0.0, 20.0, 90.0, _CLEAR_W)
print(f" sized at 0 W: sell -> {_sell.action}, buy -> {_buy.action}; "
f"sized at a rating: sell -> {_sell_ok.action}, buy -> {_buy_ok.action}")
check("⚠️ A TRADE NOTHING CAN TAKE IS NOT A TRADE. A 0 W sizing IDLES on both "
"sides rather than becoming a 0 W claim, because a `Claim.set(..., 0.0)` "
"at P_ARBITRAGE is a PARK ORDER that outranks the loop - the failure "
"`arbitrage.claim()`'s own note is written against. The sizing above can "
"never be zero on a tick that trades TODAY, and this makes that "
"structural instead of a coincidence of the sell gate",
not _sell.trading and not _buy.trading
and arbitrage.claim(_sell) is None and arbitrage.claim(_buy) is None
and arbitrage.IDLE_NO_SIZE in _sell.reason
and arbitrage.IDLE_NO_SIZE in _buy.reason)
check("...and the SAME two calls with a real sizing DO trade, so the check "
"above is separated from a fixture that simply never reaches the branch",
_sell_ok.action == arbitrage.DISCHARGE
and _buy_ok.action == arbitrage.CHARGE
and _sell_ok.power_w == 4600.0 and _buy_ok.power_w == -5000.0)
# ==============================================================================
print()
print("10. THE CLAIM, THE SCOPE, AND WHAT THIS FILE CANNOT SEE")
check("the `pack` source is classified in `CLAIM_SCOPE` as a UNIT claim, so "
"`scope_violations()` complains if it is ever filed on the site while the "
"site has named units. It is about one battery in one cupboard, exactly "
"as `device`'s rating is about one box",
CLAIM_SCOPE[packshare.SOURCE] == SCOPE_UNIT)
_sc = resolve(site_claims(_at_floor, 1500.0), units=(A, Bee),
weights=lambda t: packshare.weights(_at_floor, t), tick=1)
check("...and a real cycle files it without a scope complaint",
_sc.scope_errors == [])
_mis = resolve([Claim.limit("pack", P_SAFETY, -inf, 0.0, "misfiled",
actuator=ACTUATOR_SITE),
Claim.limit("supervised", P_SAFETY, -A_W, A_W, "max_w",
actuator=A),
Claim.set("loop", P_LOOP, 1000.0, "grid")],
units=(A, Bee), tick=1)
check("...and misfiling it ON THE SITE is caught and REFUSED, not consumed. A "
"per-pack interval in the site list is the cross-inverter `lo > hi` "
"ARB-03's partition exists to make inexpressible",
_mis.scope_errors and not _mis.allocated)
# ⚠️ AND THE ARGUMENT IS ACTUALLY PASSED, WHICH IS THE WHOLE CARD AND WHICH
# NOTHING BEHAVIOURAL IN THIS FILE CAN SEE. Every cycle above builds its own
# claim list and calls `resolve()` itself, so deleting `weights=` from main.py's
# ONE call site would leave this entire file green - which is exactly the state
# release/1.0 was in, for the whole life of the parameter. It is pinned on
# main.py's own AST, the way test_peakguard.py part 12i pins the arguments of
# `arbitrage.decide()`, and for the same reason: a call site that stopped
# passing it is invisible to every fixture.
_main_src = (Path(__file__).resolve().parent / "app" / "main.py").read_text(
encoding="utf-8")
_resolve_calls = [n for n in ast.walk(ast.parse(_main_src))
if isinstance(n, ast.Call) and isinstance(n.func, ast.Name)
and n.func.id == "resolve"]
_w_arg = [ast.unparse(k.value) for c in _resolve_calls for k in c.keywords
if k.arg == "weights"]
print(f" main.py has {len(_resolve_calls)} `resolve(` call site(s); "
f"weights= is {_w_arg}")
check("⚠️ MAIN.PY ACTUALLY PASSES `weights=`, AND IT PASSES THE METHOD RATHER "
"THAN A DICT. THIS IS THE CARD: the parameter has existed since ALLOC-01 "
"and nothing ever filled it, so the split was 50/50 whatever either pack "
"could do - and no fixture anywhere could see that, because every check "
"calls `resolve()` for itself. A method, not a dict, because the "
"weighting is direction-dependent and the direction is stage 2's output",
len(_resolve_calls) == 1 and _w_arg == ["self.pack_weights"])
# ⚠️ AC 3 SAYS `unmet_w` MUST BE **VISIBLE**, AND `Allocation.explain()` IS ONLY
# HALF OF THAT. The other half is main.py putting it in the action log, which no
# check in this file can reach without building a whole Controller - so it is
# pinned STATICALLY, on `Controller.cycle()`'s own AST, the way test_peakguard.py
# part 10f pins the claim sources. What this grades is that the branch EXISTS,
# reads the allocation's own `unmet_w`, is EDGE-TRIGGERED against the previous
# cycle's value, and logs. What it does NOT grade is the sentence it logs or
# that a real cycle reaches it; that is stated here rather than implied.
_cycle_src = first(
f for cls in ast.walk(ast.parse(
(Path(__file__).resolve().parent / "app" / "main.py")
.read_text(encoding="utf-8")))
if isinstance(cls, ast.ClassDef) and cls.name == "Controller"
for f in cls.body
if isinstance(f, (ast.FunctionDef, ast.AsyncFunctionDef)) and f.name == "cycle")
_unmet_ifs = [n for n in ast.walk(_cycle_src) if isinstance(n, ast.If)
and ast.unparse(n.test) == "_unmet != self._unmet_last"]
_unmet_src = [ast.unparse(n) for n in ast.walk(_cycle_src)
if isinstance(n, ast.Assign) and len(n.targets) == 1
and ast.unparse(n.targets[0]) == "_unmet"]
print(f" main.py computes `_unmet` as {_unmet_src} and branches on it "
f"{len(_unmet_ifs)} time(s)")
check("...and main.py REPORTS an unplaceable watt rather than only recording "
"it: `Controller.cycle()` reads `self.allocation.unmet_w`, compares it "
"against the previous cycle's value - edge-triggered, because an empty "
"pack stays empty and a line a second would flush the 40-row action log "
"of the context that explains it - and calls `log_event` on the change. "
"⚠️ THIS IS A SHAPE PIN: it says the branch exists and what it reads, "
"never what it says or that a live cycle reaches it",
len(_unmet_ifs) == 1
and len(_unmet_src) == 1 and "unmet_w" in _unmet_src[0]
and any(isinstance(n, ast.Call)
and ast.unparse(n.func) == "self.log_event"
for n in ast.walk(_unmet_ifs[0])))
print()
print(" ⚠️ WHAT THIS FILE CANNOT SEE, said plainly:")
print(" * NOTHING HERE IS EVIDENCE ABOUT INVERTER B. It does not exist;")
print(" every B figure is datasheet, and part 8's plant is modelled in")
print(" this file - two copies of A given different numbers.")
print(" * IT DOES NOT GRADE THE TAPER. `spare_kwh()` says a pack at 95 %")
print(" has room; a real pack in constant-voltage taper takes far less")
print(" than it is told for tens of minutes, and this add-on has no")
print(" per-pack charge ceiling to describe that with. `delivery.py`")
print(" says the same thing and calls it a card - it is why SAFETY-42")
print(" is armed on discharge shares only.")
print(" * THE CHARGE ENVELOPE CLOSES AT 100 % AND NOWHERE ELSE. That is")
print(" the only ceiling that is a property of a PACK rather than of a")
print(" policy; `arbitrage_soc_ceiling` is a trade preference and using")
print(" it here would be SAFETY-25's deferred option in the charge")
print(" direction. Everything between is left to the weights, which are")
print(" a preference and are honest about being one.")
print(" * A WEDGED UNIT IS NOT REACHABLE BY THE ENVELOPE. Its SoC reads")
print(" healthy, so nothing closes, and SAFETY-42 owns the detection.")
print(" ⚠️ BUT IT IS REACHABLE BY THE WEIGHTS, WHICH MOVE ON ENERGY")
print(" HEADROOM AND NOT ON DELIVERY. That narrowed SAFETY-42's")
print(" detection window - a regression this file PINNED rather than")
print(" fixed - and SAFETY-43 closed it by making delinquency relative")
print(" to the ask as well as absolute. Part 8b now grades both rules")
print(" on one fixture: the absolute arm still says nothing, and the")
print(" shipped one names the unit on all 200 cycles.")
# ==============================================================================
print()
print("11. MUTATION: what defeats this card, and which check bites")
# ⚠️ EVERY ROW BELOW WAS RUN, on a tree rsynced from this branch with
# `__pycache__` purged and `python3 -B`, not reasoned about. MUTATION.md is the
# method. The two columns that matter are WHICH FILE went red and WHICH CHECK -
# a mutation caught only by a crash is recorded as such, because a traceback is
# a weaker gate than a named failure and the distinction is the point of the
# table.
MUT = [
("M1 envelope(): never close `hi` (the discharge end)",
"test_packshare 2 and 4, and test_delivery 4 - SAFETY-42's cold arm goes "
"back to being ASKED for a share it cannot deliver"),
("M2 envelope(): never close `lo` (the charge end)",
"test_packshare 2, and test_peakguard 10g - the two-inverter cycle's "
"resolved envelope for a full pack reverts to +/-rating"),
("M3 usable_kwh(): drop the capacity, weigh by SoC headroom alone",
"test_packshare 1 (the four schemes stop separating) and 3"),
("M4 weights(): ignore the direction, always use usable_kwh",
"test_packshare 3 - the charge side reports the discharge headroom"),
("M5 envelope(): `soc < floor` instead of discharge_permitted()'s `<=`",
"test_packshare 2 - the boundary and the agreement sweep"),
("M6 main.py: stop passing `weights=` to resolve() <- THE CARD ITSELF",
"test_packshare 10, the AST pin. ⚠️ NOTHING BEHAVIOURAL CATCHES THIS, in "
"this file or any other, because every fixture calls resolve() for "
"itself - which is exactly the state release/1.0 was in"),
("M7 main.py: file the `pack` claim at N=1 too",
"test_peakguard 10f's SoC sweep - and NOT test_packshare, which is "
"recorded rather than fixed: this file grades the arithmetic, and 'which "
"claims a real one-inverter cycle files' is that file's instrument"),
("M8 trade_power_w(): drop the SoC filter, size at the site rating",
"test_packshare 9"),
("M9 main.py: delete the unmet_w action-log branch",
"test_packshare 10, the AST pin - a shape check, and it says so"),
("M10 weights(): answer None for every roster",
"test_packshare 1's PRECONDITION. ⚠️ IT WAS A TRACEBACK BEFORE THAT LINE "
"EXISTED - the first draft of this file had no precondition and the "
"mutation took it down with a TypeError three lines into part 1"),
("M11 weights(): keep the shape, force every weight to 1.0 <- THE PROOF "
"THAT PART 8b CAN GO RED",
"test_packshare 8b, FIVE named checks - re-run for SAFETY-43 rather than "
"carried across, because the part it grades was rewritten: the split "
"reverts to 1500/1500 so the absolute arm names the unit on 199/200 "
"(taking the card's RED check down), the shipped rule's 200/200 comes "
"from the wrong shares (taking the GREEN one down), the OPT-09 window "
"collapses from 6500 W to 1000 W, the 6000 W arm's 195 becomes 199, and "
"the derated positive control moves from 0/200 to 3/200. ⚠️ RUN, NOT "
"REASONED ABOUT - a check that pins a behaviour has to be shown capable "
"of failing, or it is indistinguishable from a check that pins nothing. "
"(It also takes parts 1, 3, 7 and 8 down, which is M3's territory; 8b is "
"the part that names the SAFETY-42 interaction)"),
# --- SAFETY-43's three, and they pin the new arm from BOTH sides --------
("M12 delinquent(): drop the relative arm, keep `gap > min_w` <- THE "
"CARD ITSELF REVERTED",
"SEVEN named checks: test_delivery 1 (THREE - the 0-of-125 W naming, the "
"proportion in the sentence, and the strict relative bound) and "
"test_packshare 8b (FOUR - the GREEN check goes back to 0/200 under "
"OPT-09's weights, the equal arm's 200 back to 199, the 6000 W arm back "
"to 195/199, and the derated control's vacuity guard falls with them). "
"test_alloc and test_control stay green, which is the point - nothing "
"outside this check moves"),
("M13 DELIVERY_SHARE_FRACTION 0.85 -> 1.0 <- THE TOO-LOOSE DIRECTION",
"SEVEN named checks: test_delivery 1 (TWO) and test_packshare 8b (FIVE), "
"including the PRECONDITION that the fraction is strictly below 1.0. At "
"1.0 the arm can only fire on a unit IMPORTING while asked to discharge, "
"so the card's own case - 0 of 125 W, gap over share exactly 1.0 - is "
"missed and the whole card is a no-op"),
("M14 DELIVERY_SHARE_FRACTION 0.85 -> 0.05 <- THE TOO-SENSITIVE ONE",
"SIX named checks: test_delivery 1 (TWO - the strict bound, and 'IT TOOK "
"NOTHING AWAY' stops being true) and 4b (THREE - the `saturation_w` pin "
"goes VACUOUS, because the relative arm names B at every threshold), plus "
"test_packshare 8b's DERATED positive control, where a unit delivering "
"half its share starts being named. ⚠️ THIS IS THE ROW THAT MATTERS - the "
"fraction is pinned from BOTH directions, so it is a derived number with "
"checks around it rather than a literal nobody can move"),
]
for _n, _w in MUT:
print(f" caught {_n}")
print(f" {_w}")
check("every mutation in the table above was RUN and every one of them is "
"caught by a named check on this branch. ⚠️ AND THE ROW WORTH READING IS "
"M6: the whole defect this card fixes - a parameter nothing passes - is "
"invisible to every behavioural fixture in this repository, because they "
"all build their own claim list and call `resolve()` themselves. That is "
"why the call site is pinned statically as well. ⚠️ M12-M14 ARE "
"SAFETY-43's and were run the same way, on trees copied fresh and "
"mounted :ro so no `.pyc` could go stale - MUTATION.md's defect. All "
"three are REDS, which is the direction that defect cannot fabricate. "
"⚠️ AND THEIR COUNTS WERE RE-RUN IN REVIEW AND TWO OF THEM MOVED: "
"test_delivery.py part 1 used to subscript `delinquent(...)['b']` for a "
"print, which raised KeyError under M12 and M13 and ABORTED THE FILE at "
"part 1, leaving parts 2-8 ungraded and the recorded coverage smaller "
"than the truth. It is a `.get` now. M14's count was simply wrong: five "
"named checks in test_delivery, not four",
len(MUT) == 14)
print()
if fails:
print(f"{len(fails)} FAILED: {', '.join(fails)}")
sys.exit(1)
print("all checks passed")