OPT-38 round 2: a kept tomorrow reading is flagged kept only from 20:00
checks / unit (pull_request) Successful in 23m11s
infra-pin / fresh (pull_request) Successful in 1s

Before the bell ends it replaced today's Home line and the event log with
the kept-for-tonight wording. stand_in() docstring covers the tomorrow case.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-07 06:02:00 +01:00
co-authored by Claude
parent c43873b142
commit 0bacafadf0
4 changed files with 49 additions and 12 deletions
+7 -3
View File
@@ -332,8 +332,9 @@ class Hold:
The kept reading must be for `day0`, and either (a) at most `FRESH_S`
old, or (b) used at night: from the end of the bell to local midnight,
at most `KEPT_MAX_AGE_S` old. `kept` is true when (b) was needed,
or `as_today` (OPT-34: a tomorrow figure standing in for today, after
at most `KEPT_MAX_AGE_S` old; a "tomorrow" reading at any hour
(OPT-38; `read_sum()` flags it kept only from the end of the bell).
`kept` is true when (b) was needed, or `as_today` (OPT-34: a tomorrow figure standing in for today, after
midnight and until the bell starts, `KEPT_MORNING_MAX_AGE_S`)."""
rec = self._r.get((kind, entity, day0))
if rec is None:
@@ -558,6 +559,9 @@ def read_sum(today, tomorrow, now: datetime, hold: Hold | None = None):
if why0 is not None:
return None, why0, gaps
tday = _sum(tdays) if tomorrow and len(tdays) == len(tomorrow) else None
kept = kept or (tday is not None and tkept)
# OPT-38: before the bell ends a kept tomorrow reading is no "kept for
# tonight": it must not replace today's line or the log.
kept = kept or (tday is not None and tkept
and _local_hour(now) >= BELL_TO_H)
# The oldest member's report is when the sum was last fully known.
return Forecast(_sum(days), tday, min(ats), kept), None, gaps
+1 -1
View File
@@ -146,7 +146,7 @@ for label, tm in (("stale", st(12.0, at(4, 9, 5))), ("unavailable", GONE)):
check(f"14:00, today fresh, tomorrow {label} since 09:05 (over 3 h): "
"the 09:05 figure covers it, kept (OPT-38: at any hour)",
fd is not None and fd.tomorrow is not None
and fd.tomorrow.kwh == 12.0 and fd.kept)
and fd.tomorrow.kwh == 12.0 and not fd.kept)
fn = rs(st(0.1, at(4, 20, 55)), tm, at(4, 21, 0), h9)[0]
check(f"21:00, tomorrow {label}: the 09:05 figure covers it, kept",
fn is not None and fn.tomorrow is not None
+2 -2
View File
@@ -186,9 +186,9 @@ check("a fresh figure replaces the kept one at once (04:00: 5.0 today, "
and fy.today.kwh == 5.0 and fy.tomorrow.kwh == 9.0)
fy2 = rs(st(5.0, at(4, 7, 55)), GONE, at(4, 8, 0), hd)[0]
check("... and at 08:00, today fresh and tomorrow's sensor down for over 3 h: "
"tomorrow is the 03:50 figure for the 5th (9.0, kept: OPT-38), never "
"tomorrow is the 03:50 figure for the 5th (9.0, not flagged kept before 20:00: OPT-38), never "
"the 3rd's 12.0", fy2 is not None and fy2.today.kwh == 5.0
and fy2.tomorrow is not None and fy2.tomorrow.kwh == 9.0 and fy2.kept)
and fy2.tomorrow is not None and fy2.tomorrow.kwh == 9.0 and not fy2.kept)
# Restart: the saved hold keeps the figure.
for label, now in (("01:00", at(4, 1, 0)), ("04:00", at(4, 4, 0))):
+39 -6
View File
@@ -16,7 +16,7 @@ from datetime import datetime, timedelta, timezone
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from app import solarfc # noqa: E402
from app import homeview, solarfc # noqa: E402
fails = []
@@ -55,15 +55,16 @@ run(h, [(4.0, at(6, 14, 0)), (5.0, at(6, 14, 54)), (6.0, at(6, 14, 0))],
at(6, 14, 55))
fresh = lambda w: [(4.0, w), (5.0, at(6, 14, 54)), (6.0, w)] # noqa: E731
f, why, gaps = run(h, fresh(at(6, 18, 20)), at(6, 18, 30))
check("18:30, one member last reported 14:54: tomorrow sum is there, kept",
check("18:30, one member last reported 14:54: tomorrow sum is there (not "
"flagged kept before 20:00)",
f is not None and f.tomorrow is not None and f.tomorrow.kwh == 15.0
and f.kept and gaps == ())
and not f.kept and gaps == ())
f, _w, _g = run(h, fresh(at(6, 17, 40)), at(6, 17, 50))
check("17:50 (just under 3 h): fresh, not kept",
f is not None and f.tomorrow.kwh == 15.0 and not f.kept)
f, _w, _g = run(h, fresh(at(6, 17, 55)), at(6, 18, 0))
check("18:00 (stale since 17:54): kept, tomorrow there",
f is not None and f.tomorrow is not None and f.kept)
check("18:00 (stale since 17:54): tomorrow there",
f is not None and f.tomorrow is not None and not f.kept)
f, _w, _g = run(h, fresh(at(6, 19, 50)), at(6, 19, 55))
check("19:55 (before the bell ends): tomorrow there", f.tomorrow is not None)
@@ -80,7 +81,7 @@ check("member 12 h 10 min old: no tomorrow sum (OPT-17 rule), today stays",
f, _w, _g = run(h2, [(4.0, w), (5.0, at(6, 5, 0)), (6.0, w)],
at(6, 16, 50))
check("... and 11 h 50 min old is still kept",
f is not None and f.tomorrow is not None and f.kept)
f is not None and f.tomorrow is not None and not f.kept)
# Not a partial sum: a member never read has nothing to stand in.
f, _w, gaps = run(solarfc.Hold(), [(4.0, at(6, 18, 0)),
@@ -117,6 +118,38 @@ for label, now in (("00:30", at(7, 0, 30)), ("10:00", at(7, 10, 0)),
check(f"{label} on the 7th: the 6th's tomorrow figure is not the "
"7th's tomorrow", f is not None and f.tomorrow is None)
# Before 20:00 a kept tomorrow reading is not "kept for tonight": today's
# line stays the normal one (EN and NL) and the event log (main.py, from
# `fc.kept`) has no "kept".
h5 = solarfc.Hold()
solarfc.read_sum([("t", st(3.0, at(6, 9, 0)))], [("m", st(12.0, at(6, 9, 5)))],
at(6, 9, 6), h5)
for hh in (14, 18, 19):
f, _w, _g = solarfc.read_sum([("t", st(2.5, at(6, hh - 1, 55)))],
[("m", st(12.0, at(6, 9, 5)))],
at(6, hh, 0), h5)
check(f"{hh}:00, today fresh, tomorrow kept from 09:05: tomorrow there, "
"not flagged kept", f is not None and f.tomorrow is not None
and f.tomorrow.kwh == 12.0 and f.kept is False)
f, _w, _g = solarfc.read_sum([("t", st(2.5, at(6, 13, 55)))],
[("m", st(12.0, at(6, 9, 5)))], at(6, 14, 0), h5)
for lang in ("en", "nl"):
with homeview.in_lang(lang):
line = homeview.line_solar(f.today.kwh, f.today.kwh * 0.7,
homeview.hhmm(f.at), kept=f.kept)
kept_line = homeview.line_solar(1, 1, homeview.hhmm(f.at), kept=True)
print(f" {lang}: {line!r}")
if lang == "en":
check("... EN says 'Solar still to come today'",
line.startswith("Solar still to come today"))
check(f"14:00 Home line ({lang}) is the normal line, not the kept one",
line != kept_line and "2,5" in line.replace(".", ",")
and "13:55" in line)
f, _w, _g = solarfc.read_sum([("t", st(0.2, at(6, 20, 55)))],
[("m", st(12.0, at(6, 9, 5)))], at(6, 21, 0), h5)
check("21:00 as before: tomorrow kept from 09:05 is flagged kept",
f is not None and f.kept and f.tomorrow.kwh == 12.0)
print()
if fails:
print(f"{len(fails)} FAILED:")