OPT-11 P3 review: sale shows no money, honest sell text, tests for four surviving mutants
checks / unit (pull_request) Successful in 14m5s
infra-pin / fresh (pull_request) Successful in 1s

- A sale block on the plan no longer says "Worth up to" at the import
  price: a sale earns the lower injection price.
- The selling switch help (EN/NL) and DOCS say the grid pays less than
  power costs; DECISIONS records why P3 loses on the replay.
- test_modes_p3 part 11: modes on ignore the site trade switch (R13), a
  battery in maintenance takes no part (R10), a headroom stop latches for
  the period (R8). All three mutants survived before and are red now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-09-29 03:51:15 +02:00
co-authored by Claude Opus 5.5
parent 902524ae5e
commit b014fd2e16
6 changed files with 77 additions and 12 deletions
+53
View File
@@ -21,6 +21,8 @@ time. DECISIONS/OPT-11-battery-modes.md has the record.
8. The site Why row: a limit that pushes power up does not "hold it back" (F2).
9. The selling switch (`pack_sell`).
10. The Batteries screen: Why and Today lines from the roles and runs.
11. Review additions: modes on ignore the site trade switch, a battery in
maintenance takes no part, the headroom stop latches for the period.
Mutations (MUTATION.md, purged copies, graded over this file and the named
tests) are listed in the pull request.
@@ -596,6 +598,10 @@ check("the plan draws B's expensive run, the sale inside it, and nothing "
"for Solar first A", _plan[0]
and {r["battery"] for r in _plan[0]} == {1}
and any(r["kind"] == "give" for r in _plan[0]))
_sale = [r for r in _plan[0] if r["what"].startswith("Sells")]
check("a sale block shows no money: the price shown is what the house pays, "
"and a sale earns the lower injection price",
_sale and all(r["amount"] is None and r["eur"] is None for r in _sale))
_why = homeview.battery_why(
modes_on=True, mode=packmode.CHEAP_HOURS, off=False, reading=True,
phase="idle", shadow=False, running=True, held=False, refused=0,
@@ -604,6 +610,53 @@ check("a holding battery's Why names when it gives power",
_why == "saves" and "18:00 - 19:00" in homeview.why_words(
_why, at="18:00 - 19:00"))
# ============================================================================ #
print("\n11. review additions (PR #153): gaps four mutants survived")
import types # noqa: E402
import app.main as _main # noqa: E402
# Modes on with the site switch on: only the modes trade (R13).
ROWS_X, _, _ = run_day("p2_arb", hours=3.0, soc0={"a": 30.0, "b": 30.0},
choice={"a": (True, "solar_first", False),
"b": (True, "solar_first", False)})
check("modes on, 'Trade on the price spread' on, both batteries Solar first: "
"no trade all through the cheap hours (the site trade is not run)",
ROWS_X and all(r["action"] is None for r in ROWS_X)
and all(v >= 0 for r in ROWS_X for v in r["shares"].values())
and not any(r["winner"].startswith("arbitrage") for r in ROWS_X))
# A battery in a maintenance phase takes no part in a run (R10).
ROWS_M, _, _cm = run_day("p3_ch_sell", hours=0.6, house_w=800.0,
soc0={"a": 60.0, "b": 30.0})
_lm = nth(ROWS_M, -1, "the last cycle")
_nm = _lm["now"] if present(_lm) else None
_tm = _cm._mode_roles(_nm, [types.SimpleNamespace(phase=_main.IDLE),
types.SimpleNamespace(phase="CHARGE")]) \
if _nm is not None else None
check("a battery in its monthly cycle is not a charger: no range, no charge "
"role, and no grid charge when it is the only price battery",
_tm is not None and B not in _tm.ranges
and _tm.roles.get(B) != modetrade.CHARGE and _tm.action is None)
# The headroom stopping a charge latches it off for the period (R8).
_cm2 = run_day("p3_ch_sell", hours=0.6, house_w=800.0,
soc0={"a": 60.0, "b": 30.0})[2]
_ok8 = False
if _nm is not None and _cm2.mode_tick is not None \
and _cm2.mode_tick.action == modetrade.CHARGE:
_q = int(_nm.timestamp() // 900)
_cm2.arb_headroom_w = 0.0
_t1 = _cm2._mode_roles(_nm, None)
_cm2.arb_headroom_w = 3000.0
_t2 = _cm2._mode_roles(_nm + timedelta(seconds=5), None)
_nq = datetime.fromtimestamp((_q + 1) * 900 + 5, timezone.utc)
_cm2.tariff_at = _nq
_t3 = _cm2._mode_roles(_nq, None)
_ok8 = (_t1.why == "headroom" and _cm2._charge_blocked_q in (_q, _q + 1)
and _t2.action is None and _t3.action == modetrade.CHARGE)
check("a charge the headroom stopped stays off for the rest of the period, "
"even when the headroom comes back, and may start in the next one", _ok8)
print()
if fails:
print(f"{len(fails)} FAILED:")