SAFETY-128 round 2: find late readings from the batteries, not the meter alone
Review B1: the meter fit took repeating house loads (ovens, a heat pump)
for late readings on healthy 1-2 s sites, about 9 % of the recorded
house. B2: at a 5 s meter it never ran. M1: no hysteresis. M2: an
unbounded check.
lateness.Delay now fits how long each battery's reading takes to show
its own request, which the house does not move, from the 1 s reads
(read_inputs -> Answer.watch). The meter fit stays for speed at 1-3 s
meters, but counts only when an ask was slow to start and no battery
fit said on time. Late from 9.5 s after 2 fits, on time under 6.5 s,
dropped after 120 s without a fit. Fits are bounded (a few ms).
Readings on time: no verdict in any reviewer scenario (22 shapes at
1/2/5 s meters, 7 harm runs) nor in 2 days of the recorded house at
1, 2 and 5 s ticks; digests equal to 77fe31b. Late: found 36-68 s after
the start at 1-2 s, about 3 min at 5 s. The row gives the batteries'
delay, or no figure while only the meter has shown it.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
co-authored by
Claude
parent
09e1fbb898
commit
d3f4f9f48b
@@ -2,72 +2,80 @@
|
||||
|
||||
**Status: FIXED on release/1.0 (after 0.20.0).** Code: `app/lateness.py`,
|
||||
`control.compute(hold=, hold_w=)`, `peakguard.cap_floor()`,
|
||||
`Controller._readings_late()`. Tests: `test_safety128.py`
|
||||
(`tools/safety128_rig.py`, TEST-23's plant).
|
||||
`Controller._readings_late()`, `Controller.read_inputs()` (feeds the fits).
|
||||
Tests: `test_safety128.py` (`tools/safety128_rig.py`, TEST-23's plant).
|
||||
|
||||
**Cause** (SAFETY-127 report). Home Assistant can deliver readings late but
|
||||
still changing. Every gate judges arrival (SAFETY-01) or a value that stops
|
||||
changing (SAFETY-18, SAFETY-37), so a late reading passes all of them. The loop
|
||||
then corrects, each cycle, an error its last command has already fixed: a loop
|
||||
with a dead time far longer than its cycle, which swings and grows. The peak
|
||||
guard projects the quarter from the same late figures and pushes to the
|
||||
changing (SAFETY-18, SAFETY-37), so a late reading passes them all. The loop
|
||||
then corrects, each cycle, an error its last command has already fixed: a
|
||||
loop with a dead time far longer than its cycle, which swings and grows. The
|
||||
peak guard projects the quarter from the same late figures and pushes to the
|
||||
inverter limit in its last seconds (OPT-34 rig F-2).
|
||||
|
||||
**Rule.**
|
||||
1. *Find late readings.* `Delay` fits, every 4 s over the last 40 s of new
|
||||
meter values, how long after a power request the meter shows it: the meter
|
||||
is the house less the batteries, so `meter(t) + request(t - d)` is flattest
|
||||
at the right `d`. Over `DELAY_S` the readings are late (`late_seen`). The
|
||||
fit counts only if the requests explain half the meter's swing; a late `d`
|
||||
only if it fits 30 % better than any fresh one and better than a slow
|
||||
battery answering at once (10 or 20 s lag, the SAFETY-85/94 rigs). A
|
||||
silent meter adds no values (SAFETY-99/125). A battery that gives nothing
|
||||
under its ask, or refuses, spoils the fit and is left out for 40 s. With no
|
||||
fit for 120 s (a steady site shows nothing) the verdict is dropped.
|
||||
Nothing below acts unless `late_seen`, so with readings on time control is
|
||||
unchanged.
|
||||
2. *Hold.* While `late_seen`, a request more than `ANSWER_W` from the battery
|
||||
reading is pending until that reading comes within `ANSWER_W` of it, or half
|
||||
way. Meanwhile the loop may not lead the reading by more than `ANSWER_W`
|
||||
that way (`compute(hold=, hold_w=)`; it may come back), and the peak
|
||||
guard's floor is capped at the request that stands (`cap_floor()`). The
|
||||
loop is not held while a battery gives nothing under its ask or refuses:
|
||||
SAFETY-52/94 need it to go on asking.
|
||||
3. *Late.* Pending for `LATE_S` with no reading moving `ANSWER_W` towards it is
|
||||
the late verdict: control holds, then after `stale_input_s` goes to 0 W
|
||||
("readings late"), as for a missing input. It ends when a reading starts
|
||||
towards the request.
|
||||
4. *Seen.* While `late_seen` an amber row says "Home Assistant readings arrive
|
||||
about N seconds late, so control holds the batteries." (NL too), and
|
||||
"every inverter is at 0 W" past the hold. One WARNING (`READINGS_LATE`)
|
||||
and one action-log line per episode, and one when it ends.
|
||||
1. *Find late readings* (`Answer.watch()`, every 2 s, from the 1 s reads).
|
||||
- **Batteries** (`Delay`): fit one `d` so that each battery's reading minus
|
||||
its own request at `t - d` (averaged over 2 s for the battery's lag and
|
||||
the read) is flattest. The house does not move that reading, so a
|
||||
repeating load cannot line up with it (review round 1, B1). Needs 5 new
|
||||
readings in 60 s and the request and the reading each to move 300 W.
|
||||
Late from 9.5 s, after 2 late fits in a row. A late fit must explain 75 %
|
||||
and beat every fit up to 6.5 s by 2x; the shortest delay that fits about
|
||||
as well (within 2x, or within 2 500 W^2) is taken instead, since a swing's
|
||||
period fits again. A slow battery (3-20 s lag) answering at once gives no
|
||||
late fit (checked in the test). No late battery fit counts for 60 s
|
||||
after a battery gave nothing under its ask: an emptied battery lined its
|
||||
old movement up as 85 s late.
|
||||
- **Meter** (`MeterFit`, round 1's fit): `meter(t) + request(t - d)` is the
|
||||
house alone at the right `d`. Quick, but confounded by the house. Needs
|
||||
16 new values in 60 s, so only a meter of about 3.5 s or faster: at 5 s
|
||||
it gave false delays. Late over 8 s.
|
||||
- A meter fit says late only with evidence from the batteries' side too:
|
||||
a request more than 100 W from the battery reading took 3 s or more to
|
||||
move a reading towards it (`SLOW_START_S`; on time they start within
|
||||
0-1 s in the recorded house at 1 and 2 s ticks), and no battery fit in
|
||||
the last 60 s said on time (under 9.5 s). An ask during which a battery
|
||||
gave nothing under its ask is no slow evidence. A repeating load fools
|
||||
the meter fit but not these. No meter verdict in the first 150 s.
|
||||
- On time again on a battery fit under 6.5 s, or a meter fit within 8 s
|
||||
unless a battery fit said late (hysteresis). With no fit for 120 s the
|
||||
verdict is dropped.
|
||||
2. *Hold.* While late, a request more than 100 W from the battery reading is
|
||||
pending until that reading comes within 100 W of it, or half way. Meanwhile
|
||||
the loop may not lead the reading by more than 100 W that way, and the peak
|
||||
guard's floor is capped at the request that stands. The loop is not held
|
||||
while a battery gives nothing under its ask or refuses (SAFETY-52/94).
|
||||
3. *Late verdict.* Pending for `LATE_S` (23 s) with no reading moving 100 W
|
||||
towards it: control holds, then after `stale_input_s` goes to 0 W
|
||||
("readings late"), as for a missing input.
|
||||
4. *Seen.* While late an amber row says "Home Assistant readings show requests
|
||||
about N seconds late, so control holds the batteries." (NL too). N is the
|
||||
batteries' fitted delay: Home Assistant's lag plus the battery's own
|
||||
answer. While only the meter has shown it the row gives no figure. One
|
||||
WARNING (`READINGS_LATE`) per episode and one line when it ends.
|
||||
|
||||
**Constants** (`app/lateness.py`).
|
||||
|
||||
| | Value | Why |
|
||||
|---|---|---|
|
||||
| `ANSWER_W` | 100 W | `control.PROBE_ANSWER_W`: a reading this far towards the ask has answered it (SAFETY-52's probe). |
|
||||
| `DELAY_S` | 8 s | `freshness.PLANT_SETTLE_S` 6 s + one `LOOP_TICK_S` 2 s: a fresh meter shows a request within the plant's settle (SAFETY-75's 6 s dead time) and one meter tick. The fit reads 0 s at 1/2/5 s meters, 5-7 s with a 6 s dead time, 9 s at 10 s late. |
|
||||
| `LATE_S` | 23 s | 2 x `PLANT_SETTLE_S` + `PUBLISH_PERIOD_S` 10 s + `READ_PERIOD_S` 1 s: SAFETY-75's 6 s dead time, the 6 s settle, one publish and one read, the slowest healthy battery answer. |
|
||||
| `FIT_S`, `FIT_EVERY_S` | 40 s, 4 s | Short enough to find 10 s and 30 s late within 50-60 s of the start (the swing has not grown yet); one 2 s cycle in two. |
|
||||
| `MAX_DELAY_S` | 90 s | F-2 measured 17-78 s. |
|
||||
| `FIT_SHARE`, `FIT_BETTER` | 0.5, 0.7 | Below these a battery that stops following (a refusal at N=2) was read as 23 s late. |
|
||||
| `SLOW_TAUS_S` | 10 s, 20 s | The SAFETY-85/94 lagging battery: without this check 16 of 36 of its reversals read as 11-56 s late. |
|
||||
| `FIT_GONE_S` | 120 s | The verdict outlives the lag by at most `FIT_S` + this when the site goes steady. |
|
||||
**With readings on time** no verdict was reached in any reviewer scenario
|
||||
(owner-shaped house, ovens 20/40 to 60/60 s, clouds, a 300 W/s battery, 6-10 s
|
||||
dead times, HA jitter 0-4 s, an EV following the meter, capped batteries, HA
|
||||
restarts, meter pauses; 1, 2 and 5 s meters) nor in 2 days of the recorded
|
||||
house at 1, 2 and 5 s ticks, so the commands are the same as without the gate.
|
||||
The fits run all the time; the pull request has the counts.
|
||||
|
||||
**What it costs.**
|
||||
- Late readings are found only after the requests have moved 100 W and the
|
||||
meter has shown it late: 48-68 s after the lag starts in the tests. Until
|
||||
then control is as before.
|
||||
- The row can stay up to 160 s after the readings are on time again, while
|
||||
the site is steady.
|
||||
- A battery that cannot follow while readings are late (at a limit, not
|
||||
reading 0 W) is held, then set to 0 W after 38 s, until it moves.
|
||||
- The fit costs about 93 x 20 sums every 4 s.
|
||||
- With readings late, a large step is followed in steps of the lag: the loop
|
||||
waits for each request to show before the next.
|
||||
- Found 36-68 s after a lag starts at 1-2 s meters, about 3 minutes at a 5 s
|
||||
meter (battery fit only; the meter fit needs a faster meter). Until then control is as before. At 5 s and 30 s
|
||||
late the export is cut to about a third, not removed.
|
||||
- A battery whose own answer takes about 10 s is close to the threshold: an
|
||||
8 s dead time reads 7, 10 s reads 9, HA 10 s late reads 10.
|
||||
- The row can stay up to 180 s after the readings are on time on a steady
|
||||
site. While only the meter has shown the delay it gives no figure.
|
||||
- A battery that cannot follow while the readings are late (at a limit, not
|
||||
reading 0 W) is held, then set to 0 W after 38 s.
|
||||
- The fits cost a few ms every 2 s (x86, measured after 3 idle days); the
|
||||
history they read is bounded (60 s of readings, 150 s of requests).
|
||||
|
||||
**Left out.** The round trip setpoint -> Home Assistant: the fitted delay gives
|
||||
the same figure from readings the add-on already has. The arbitrage charge's
|
||||
own sizing on late readings (`charge_room_w()`) is not capped here; the test
|
||||
shows it swinging while the peak guard holds (see the pull request).
|
||||
**Left out.** The setpoint -> Home Assistant round trip: the fitted delay gives
|
||||
the figure from readings the add-on already has. The hold's step-and-pull-back
|
||||
at extreme lag and the arbitrage charge's sizing on late readings
|
||||
(`charge_room_w()`) are follow-up cards.
|
||||
|
||||
Reference in New Issue
Block a user