diff --git a/goodwe_controller/CHANGELOG.md b/goodwe_controller/CHANGELOG.md index 4470cbe..e22c7d9 100644 --- a/goodwe_controller/CHANGELOG.md +++ b/goodwe_controller/CHANGELOG.md @@ -289,7 +289,11 @@ own rating, the allocator's last-resort pass hands the wedged unit a big share again and the alarm returns. So in the quiet regime - where `unmet_w` is 0, the meter is satisfied and no action-log line fires - **a wedged inverter with less energy headroom than its sibling can now go unreported at ordinary house -loads.** `test_packshare.py` part 8b grades it and pins the CURRENT behaviour, so +loads.** + +⚠️ BOTH BOUNDS ARE ARITHMETIC ABOUT `_fill()`'s PROPORTIONAL REGIME AND NEITHER IS A REACHABLE STEADY STATE. Swept on this fixture, the healthy pack's own 5000 W rating binds FIRST: suppressed 0/200 up to a 3000 W load, 4/200 at 4000 W, 41/200 at 4900 W (site target 5320 W, the largest reached with the wedged unit still under 500 W), then 195/200 at 5000 W once `_fill()` pins A at its cap and hands B 3000 W. So the alarm returns BELOW the 6500 W formula bound, not above it. The equal-weights 1000 W bound is unreachable for the same reason - the equal arm names the wedged unit 199/200 at every load swept, 1000 W included. + +`test_packshare.py` part 8b grades it and pins the CURRENT behaviour, so the day it changes a named check moves. Closing it means making `delinquent()` relative to the ask rather than absolute, which is SAFETY-42's code and a design change; that is filed as its own card and is deliberately not done here. diff --git a/goodwe_controller/DECISIONS/OPT-09-soc-aware-weights.md b/goodwe_controller/DECISIONS/OPT-09-soc-aware-weights.md index 5732102..16f6233 100644 --- a/goodwe_controller/DECISIONS/OPT-09-soc-aware-weights.md +++ b/goodwe_controller/DECISIONS/OPT-09-soc-aware-weights.md @@ -1,6 +1,6 @@ # OPT-09 - the weights parameter, and what it is honestly for -**Status:** implemented. **Branch:** `OPT-09`. **Base:** `release/1.0` @ `3368231` +**Status:** implemented. **Branch:** `OPT-09`. **Base:** `release/1.0` @ `3d29993` (rebased onto the UX-02 merge; it read `3368231`, and before that `464d398` - each rebase moved it and review caught it each time, which is why the counts in `test_translations.py` are now computed rather than typed) (`git merge-base HEAD 3368231`; the branch was rebased and this line used to name the pre-rebase base, `464d398`). @@ -198,8 +198,12 @@ named**. The suppression window widens from `site target < 2 * min_w` = 1000 W t `site target < min_w * (w_A + w_B) / w_B`, which on that fixture is **6500 W** of a 9600 W site; above it - once the healthy pack is at its own rating and `_fill()`'s last-resort pass hands the wedged unit a big share again - the alarm -returns. `test_packshare.py` part 8b grades all of it and **pins the current, -unflattering behaviour**, so that the day it changes a named check moves. +returns. ⚠️ BOTH BOUNDS ARE ARITHMETIC ABOUT `_fill()`'s PROPORTIONAL REGIME AND NEITHER IS A REACHABLE STEADY STATE. Swept on this fixture, the healthy pack's own 5000 W rating binds FIRST: suppressed 0/200 up to a 3000 W load, 4/200 at 4000 W, 41/200 at 4900 W (site target 5320 W, the largest reached with the wedged unit still under 500 W), then 195/200 at 5000 W once `_fill()` pins A at its cap and hands B 3000 W. So the alarm returns BELOW the 6500 W formula bound, not above it. The equal-weights 1000 W bound is unreachable for the same reason - the equal arm names the wedged unit 199/200 at every load swept, 1000 W included. + +`test_packshare.py` part 8b grades all of it and **pins the current, +unflattering behaviour**, so that the day it changes a named check moves. The +closure - `delinquent()` measured against the ask rather than a flat 500 W - is +card **SAFETY-43**. So the seam, stated accurately: **OPT-09 stops the ask AND narrows SAFETY-42's detection window for a low-headroom unit.** The two cards do not merely meet from diff --git a/goodwe_controller/app/packshare.py b/goodwe_controller/app/packshare.py index d222c45..a353d96 100644 --- a/goodwe_controller/app/packshare.py +++ b/goodwe_controller/app/packshare.py @@ -80,6 +80,8 @@ suppression window widens from `site target < 2 * min_w` = 1000 W to an upper edge: past the healthy pack's own rating, `_fill()`'s last-resort pass hands the wedged unit a big share again and the alarm returns. + ⚠️ BOTH BOUNDS ARE ARITHMETIC ABOUT `_fill()`'s PROPORTIONAL REGIME AND NEITHER IS A REACHABLE STEADY STATE. Swept on this fixture, the healthy pack's own 5000 W rating binds FIRST: suppressed 0/200 up to a 3000 W load, 4/200 at 4000 W, 41/200 at 4900 W (site target 5320 W, the largest reached with the wedged unit still under 500 W), then 195/200 at 5000 W once `_fill()` pins A at its cap and hands B 3000 W. So the alarm returns BELOW the 6500 W formula bound, not above it. The equal-weights 1000 W bound is unreachable for the same reason - the equal arm names the wedged unit 199/200 at every load swept, 1000 W included. + ⚠️ SO THE SEAM IS NOT "OPT-09 CANNOT SEE IT". IT IS: OPT-09 STOPS THE ASK **AND** NARROWS SAFETY-42's DETECTION WINDOW FOR A LOW-HEADROOM UNIT. That is a disclosed residual, not a fixed thing. Closing it means making diff --git a/goodwe_controller/test_packshare.py b/goodwe_controller/test_packshare.py index 97445e3..f1b555e 100644 --- a/goodwe_controller/test_packshare.py +++ b/goodwe_controller/test_packshare.py @@ -629,9 +629,11 @@ print("8b. ⚠️ AND WHAT THE WEIGHTS DO TO SAFETY-42's DETECTION WINDOW, WHICH # window is `site target < 2 * min_w` = 1000 W; at OPT-09's weights it is # `site target < min_w * (w_A + w_B) / w_B`, which on this fixture is 6500 W. # +# ⚠️ BOTH BOUNDS ARE ARITHMETIC ABOUT `_fill()`'s PROPORTIONAL REGIME AND NEITHER IS A REACHABLE STEADY STATE. Swept on this fixture, the healthy pack's own 5000 W rating binds FIRST: suppressed 0/200 up to a 3000 W load, 4/200 at 4000 W, 41/200 at 4900 W (site target 5320 W, the largest reached with the wedged unit still under 500 W), then 195/200 at 5000 W once `_fill()` pins A at its cap and hands B 3000 W. So the alarm returns BELOW the 6500 W formula bound, not above it. The equal-weights 1000 W bound is unreachable for the same reason - the equal arm names the wedged unit 199/200 at every load swept, 1000 W included. +# # ⚠️ THIS IS A DETECTION REGRESSION AT N=2 AND IT IS NOT FIXED HERE. Making # `delinquent()` relative to the ask rather than absolute is SAFETY-42's code and -# a design change; it is filed as its own card. What this part does is GRADE THE +# a design change; it is filed as card SAFETY-43. What this part does is GRADE THE # CURRENT BEHAVIOUR so that the day it changes, a named check moves. # # ⚠️ AND THE FIXTURE IS A SAFETY-42 TRUE POSITIVE BY THAT CARD'S OWN DEFINITION, @@ -747,7 +749,11 @@ check("...and the arithmetic behind it, stated as a window rather than as one " "fixture: `delinquent()` reads an ABSOLUTE gap, so a unit weighted to a " "fraction f of the site target is invisible while `f * target < min_w`. " "Equal weights suppress below 1000 W; these weights suppress below " - "6500 W of a 9600 W site. That is the regression, and it is a property " + "6500 W of a 9600 W site. ⚠️ BOTH ARE PROPORTIONAL-REGIME BOUNDS AND " + "NEITHER IS REACHABLE: the healthy pack's own rating binds first, so the " + "alarm returns BELOW 6500 W (swept: 0/200 to a 3000 W load, 41/200 at " + "4900 W, 195/200 at 5000 W) and the equal arm never suppresses at all. " + "That is the regression, and it is a property " "of the threshold being absolute rather than of any number chosen here", abs(_window_equal - 1000.0) < 1.0 and abs(_window_opt09 - 6500.0) < 1.0)