MAINT-15 round 2: in-repo differential check, full-hold with-reading check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,11 @@ line is true without a reading: it names minutes of hold, not a level.
|
|||||||
|
|
||||||
- It does not check that the pack is still full at the end of the hold.
|
- It does not check that the pack is still full at the end of the hold.
|
||||||
- It does not say why the reading was lost.
|
- It does not say why the reading was lost.
|
||||||
- With a reading, nothing changes (proved by a differential trace against
|
- With a reading, nothing changes. `test_maint15.py` section 7 compares the new
|
||||||
release/1.0 in `test_maint15.py`).
|
HOLD tick by tick with a frozen copy of the release/1.0 HOLD branch (phase,
|
||||||
|
setpoint, owner, events, `last_completed`) at four `hold_min` values. A wider
|
||||||
|
trace of the whole cycle (800 seeded runs against db9e231) was run outside the
|
||||||
|
repo; its script and log are kept under `scratch/MAINT-15/` of the dev
|
||||||
|
workspace, not in this repository.
|
||||||
- A state file written by an older version with `phase: hold` loads and ends on
|
- A state file written by an older version with `phase: hold` loads and ends on
|
||||||
time; the file is not changed in format.
|
time; the file is not changed in format.
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ import tempfile
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from app.maintenance import (
|
from app.maintenance import (
|
||||||
CHARGE, CHARGE_NEAR_FULL_PCT, CHARGE_TIMEOUT_MIN, CHARGE_WALL_TIMEOUT_MIN,
|
CHARGE, DRAIN, HOLD, IDLE, NOSOC_NOTE_MIN, MaintConfig, Maintenance,
|
||||||
DRAIN, DRAIN_TIMEOUT_MIN, FAIL_BACKOFF_DAYS, HOLD, IDLE, NOSOC_NOTE_MIN,
|
|
||||||
MaintConfig, Maintenance, arbitrate,
|
|
||||||
)
|
)
|
||||||
from app.store import DEFAULTS, Store
|
from app.store import DEFAULTS, Store
|
||||||
|
|
||||||
@@ -241,10 +239,56 @@ m = in_charge()
|
|||||||
res, _, out, _ = run(m, T0, 600, None)
|
res, _, out, _ = run(m, T0, 600, None)
|
||||||
check("CHARGE with no SoC still keeps charging", out is None
|
check("CHARGE with no SoC still keeps charging", out is None
|
||||||
and res.phase == CHARGE and res.charge_only)
|
and res.phase == CHARGE and res.charge_only)
|
||||||
|
|
||||||
|
|
||||||
|
def old_hold_branch(phase_started, last_completed, now, hold_min):
|
||||||
|
"""FROZEN copy of release/1.0 (db9e231) HOLD branch of step(), with a
|
||||||
|
reading: returns (phase, owns_setpoint, setpoint_w, events,
|
||||||
|
last_completed). Kept as a reference for the differential check below."""
|
||||||
|
elapsed = (now - phase_started).total_seconds() / 60.0
|
||||||
|
if elapsed >= hold_min:
|
||||||
|
return (IDLE, False, None,
|
||||||
|
[f"maintenance: cycle COMPLETE ({elapsed:.0f} min hold) "
|
||||||
|
"- handing back to the loop"], now)
|
||||||
|
return (HOLD, True, 0.0, [], last_completed)
|
||||||
|
|
||||||
|
|
||||||
|
print("7. with a reading: the new HOLD against the frozen release/1.0 branch")
|
||||||
|
for hm in (0, 5, 60, 120):
|
||||||
|
cfg = MaintConfig(enabled=True, interval_days=28, start_hour=19,
|
||||||
|
discharge_w=2000, charge_w=2000, soc_floor=11,
|
||||||
|
soc_target=100, hold_min=hm)
|
||||||
|
m = Maintenance(cfg, Mem())
|
||||||
|
m.force_start(T0)
|
||||||
|
m.tick(T0, 11.0)
|
||||||
|
m.tick(H0, 100.0)
|
||||||
|
started, last, same, end = H0, None, True, None
|
||||||
|
for i in range(0, hm + 15):
|
||||||
|
t = H0 + i * MIN
|
||||||
|
if m.phase != HOLD:
|
||||||
|
break
|
||||||
|
want = old_hold_branch(started, last, t, hm)
|
||||||
|
r = m.tick(t, 100.0 - (i % 3))
|
||||||
|
got = (r.phase, r.owns_setpoint, r.setpoint_w, r.events,
|
||||||
|
m.store.get_time("last_completed"))
|
||||||
|
same = same and got == want
|
||||||
|
last = want[4]
|
||||||
|
if r.phase == IDLE:
|
||||||
|
end = t
|
||||||
|
check(f"hold_min {hm}: every tick identical (phase, owns, W, events, "
|
||||||
|
"last_completed)", same)
|
||||||
|
check("...ends at hold_min, last_completed = that tick, one COMPLETE line",
|
||||||
|
end == H0 + hm * MIN and m.store.get_time("last_completed") == end
|
||||||
|
and m.phase == IDLE)
|
||||||
|
|
||||||
m = in_hold()
|
m = in_hold()
|
||||||
res, _, out, _ = run(m, H0, 30, 100.0)
|
res, _, out, ev = run(m, H0, HOLD_MIN + 5, 100.0)
|
||||||
check("HOLD with a reading is unchanged: 0 W, ends at hold_min", out is None
|
check("HOLD with a reading: 0 W until the end, then IDLE at hold_min",
|
||||||
and res.setpoint_w == 0.0)
|
out is not None and out[1] == H0 + HOLD_MIN * MIN and out[0].phase == IDLE)
|
||||||
|
check("...last_completed is that tick and the COMPLETE line is said once",
|
||||||
|
m.store.get_time("last_completed") == H0 + HOLD_MIN * MIN
|
||||||
|
and ev == ["maintenance: cycle COMPLETE (120 min hold) "
|
||||||
|
"- handing back to the loop"])
|
||||||
|
|
||||||
print()
|
print()
|
||||||
if fails:
|
if fails:
|
||||||
|
|||||||
Reference in New Issue
Block a user