OPT-11 P4 review: Cheap hours ignores the plan; midnight-stale forecast; stricter ids
- Cheap hours only fills to the ceiling with or without a forecast, as the design (section 1) says. Skipping the sun's share cost 0.31 EUR on real 2026 prices (29.72 against P3's 29.41) and saved nothing on the P3 set. - A forecast reading from before local midnight is stale: until the integration refreshes, "left today" is still yesterday's 0 and "tomorrow" is really today. - Forecast entity ids must be sensor.[a-z0-9_]+ (they go into the /api/states/<id> path). - House profile: a stored day that is not a date, or is after today, is dropped instead of kept forever. - Text: the forecast line says "for the rest of today"; the learning line no longer promises "tomorrow" (a plan needs one full day). - Tests for four surviving review mutants (hour sample floor, the dear run after the cheap run, share by room, daytime use) and the fixes above. - Replay tool: `--fc zero` (a forecast of no sun). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aabc2a27fd
commit
fbd884797d
@@ -24,10 +24,10 @@ Roles (the page's Why line names them):
|
||||
Rules that keep this safe (tests: test_modes_p3.py):
|
||||
* every range holds 0 W (a mode may forbid a direction, never force one);
|
||||
* a Solar first battery never charges from the grid;
|
||||
* P4: with a day plan (`targets`), a Leftover solar or Cheap hours battery
|
||||
charges from the grid only up to its planned level, never above the
|
||||
ceiling; with none, Cheap hours fills to the ceiling and Leftover solar
|
||||
takes solar only (P3 option A, `charge_to()`);
|
||||
* P4: with a day plan (`targets`), a Leftover solar battery charges from
|
||||
the grid only up to its planned level, never above the ceiling; with
|
||||
none it takes solar only (P3 option A). Cheap hours only fills to the
|
||||
ceiling either way (`charge_to()`);
|
||||
* grid charging needs a measured peak guard that is clear and a measured
|
||||
headroom (SAFETY-45); the trade is sized to that headroom, whose aim is
|
||||
`peak_cap_w - peak_margin_w` (SAFETY-79). It starts only at START_W or
|
||||
@@ -98,12 +98,13 @@ def charge_to(p: Pack, ceiling_pct, targets=None):
|
||||
`targets` is the day plan (`dayplan.plan()`: name -> level), or None
|
||||
when there is none (no forecast, stale, or not configured). With no plan
|
||||
this is P3 option A exactly: Cheap hours to the ceiling, the others never.
|
||||
With a plan, a Leftover solar or Cheap hours battery charges only to its
|
||||
planned level (the sun fills the rest), and a battery the plan leaves out
|
||||
falls back to the no-plan answer. Solar first never charges."""
|
||||
if targets is None or p.name not in targets:
|
||||
return float(ceiling_pct) if p.mode in GRID_CHARGE_MODES else None
|
||||
if p.mode not in PLAN_CHARGE_MODES:
|
||||
With a plan, a Leftover solar battery charges only to its planned level
|
||||
(the sun fills the rest); a battery the plan leaves out, Cheap hours only
|
||||
included, gets the no-plan answer. Solar first never charges."""
|
||||
if p.mode in GRID_CHARGE_MODES:
|
||||
return float(ceiling_pct)
|
||||
if targets is None or p.name not in targets \
|
||||
or p.mode not in PLAN_CHARGE_MODES:
|
||||
return None
|
||||
t = targets[p.name]
|
||||
return None if t is None else min(float(ceiling_pct), float(t))
|
||||
|
||||
Reference in New Issue
Block a user