OPT-34: the night charge keeps its level when the kept forecast ends
With the forecast sensors silent from 17:05 the kept evening figure ended at midnight + 3 h, in the middle of the night grid charge, and Leftover solar's planned level went with it (rig: A 49.8 % against 79.9 %). - A night grid charge that has started keeps its planned level when the forecast goes (Controller._latch_charge, store key night_charge_latch), and the narrow price gap with it (weather_held). A fresh figure still moves the level. - The evening's tomorrow figure is today's until the bell starts (06:00 local), up to 15 h old but only if it was usable at midnight (solarfc.Hold.stand_in). - Screen: "No solar forecast now. The charge under way goes on to its planned level." (EN/NL) while a latched battery is below its level. - test_opt34.py (whole nights through the real control cycle, mutants), test_opt27 and test_modes_p4 adjusted to the new window and the kept level. 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
deee98d352
commit
412e47f111
@@ -34,11 +34,13 @@ evening and api.forecast.solar is sometimes out for a poll. A kept reading
|
||||
stands in for its sensor (a) for up to `FRESH_S` when the sensor is missing
|
||||
or unavailable, as a sensor that merely stopped updating is already
|
||||
tolerated, and (b) from the end of the bell (`BELL_TO_H`) until local
|
||||
midnight + `KEPT_UNTIL_H`, if it is at most `KEPT_MAX_AGE_S` old when used.
|
||||
Never beyond that, and never
|
||||
for another day: a today figure is only today's, a tomorrow figure is
|
||||
tomorrow's until midnight and then, until midnight + 3 h, today's.
|
||||
DECISIONS/OPT-27-last-fresh-forecast-for-the-night.md.
|
||||
midnight, if it is at most `KEPT_MAX_AGE_S` old when used.
|
||||
Never beyond that, and never for another day: a today figure is only today's,
|
||||
a tomorrow figure is tomorrow's until midnight and then, until the bell starts
|
||||
(`BELL_FROM_H`, 06:00 local), today's (OPT-34: at most `KEPT_MORNING_MAX_AGE_S`
|
||||
old, and it must have been usable at midnight).
|
||||
DECISIONS/OPT-27-last-fresh-forecast-for-the-night.md,
|
||||
DECISIONS/OPT-34-the-night-charge-keeps-its-level.md.
|
||||
|
||||
OPT-17: each day may instead name a list (`pv_forecast_entities`,
|
||||
`pv_forecast_tomorrow_entities`), summed by `read_sum()`. One member that is
|
||||
@@ -63,11 +65,16 @@ FRESH_S = 3 * 3600.0
|
||||
BELL_FROM_H, BELL_TO_H = 6.0, 20.0
|
||||
|
||||
# OPT-27: a kept reading serves the night plan from the end of the bell until
|
||||
# this many hours after local midnight (the card: "local midnight + 3 h").
|
||||
KEPT_UNTIL_H = 3.0
|
||||
# OPT-27: and a reading older than this when used is not kept (a morning
|
||||
# figure must not drive the night).
|
||||
# local midnight, and a reading older than this when used is not kept (a
|
||||
# morning figure must not drive the night).
|
||||
KEPT_MAX_AGE_S = 12 * 3600.0
|
||||
# OPT-34: the evening's "tomorrow" figure, once midnight has passed, is today's
|
||||
# until the bell starts (`BELL_FROM_H`): nothing has been produced yet, so it
|
||||
# is still the whole day. A 17:05 reading is 12 h 55 min old at 06:00, so this
|
||||
# case may be older than `KEPT_MAX_AGE_S`, up to this, but only a figure that
|
||||
# was usable at midnight (at most `KEPT_MAX_AGE_S` old then), so one that has
|
||||
# not been used all evening does not come back at 00:00.
|
||||
KEPT_MORNING_MAX_AGE_S = 15 * 3600.0
|
||||
SAVE_EVERY_S = 900.0
|
||||
|
||||
# Why there is no forecast (the page words are in ui_text `home_words`).
|
||||
@@ -279,7 +286,7 @@ class Hold:
|
||||
`day0`) or "tomorrow" (all of `day0`), and `day0` is the day the figure is
|
||||
FOR. A today figure therefore never answers for another day, and a
|
||||
tomorrow figure answers for its own day only: tomorrow until midnight,
|
||||
then today until midnight + `KEPT_UNTIL_H`. Only the newest reading of a
|
||||
then today until the bell starts (OPT-34). Only the newest reading of a
|
||||
key is kept, so the next fresh reading ends the hold at once.
|
||||
|
||||
`dump()` / `load()` are what the controller keeps in its store, so an
|
||||
@@ -322,25 +329,27 @@ 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,
|
||||
or within `KEPT_UNTIL_H` after it, and then at most `KEPT_MAX_AGE_S`
|
||||
old. `kept` is true when (b) was needed,
|
||||
or `as_today` (a tomorrow figure standing in for today, after
|
||||
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
|
||||
midnight and until the bell starts, `KEPT_MORNING_MAX_AGE_S`)."""
|
||||
rec = self._r.get((kind, entity, day0))
|
||||
if rec is None:
|
||||
return None
|
||||
day, at = rec
|
||||
if (now - at).total_seconds() <= FRESH_S and not as_today:
|
||||
age = (now - at).total_seconds()
|
||||
if age <= FRESH_S and not as_today:
|
||||
return day, at, False
|
||||
if (now - at).total_seconds() > KEPT_MAX_AGE_S:
|
||||
return None
|
||||
# Both sides: a tomorrow sensor is asked with day0 = the next
|
||||
# midnight, which is in the future all day (round 2).
|
||||
after_midnight = day0 <= now < day0 + timedelta(hours=KEPT_UNTIL_H)
|
||||
evening = _local_hour(now) >= BELL_TO_H and now < _bounds(day0)[1]
|
||||
if as_today:
|
||||
return (day, at, True) if after_midnight else None
|
||||
return (day, at, True) if (evening or after_midnight) else None
|
||||
ok = (day0 <= now < _bell_start(day0)
|
||||
and age <= KEPT_MORNING_MAX_AGE_S
|
||||
and (day0 - at).total_seconds() <= KEPT_MAX_AGE_S)
|
||||
return (day, at, True) if ok else None
|
||||
if age > KEPT_MAX_AGE_S:
|
||||
return None
|
||||
# A tomorrow sensor is asked with day0 = the next midnight, which is
|
||||
# in the future all day, so the evening ends at that day's end.
|
||||
evening = _local_hour(now) >= BELL_TO_H and now < _bounds(day0)[1]
|
||||
return (day, at, True) if evening else None
|
||||
|
||||
def dump(self) -> dict:
|
||||
return {"v": 1, "r": [
|
||||
@@ -395,6 +404,15 @@ def _bounds(now):
|
||||
return d0.astimezone(timezone.utc), d1.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def _bell_start(day0):
|
||||
"""The moment the bell starts on the local day that begins at `day0`
|
||||
(`BELL_FROM_H` local, so 06:00 on a 23 h or 25 h day too)."""
|
||||
here = day0.astimezone(BILLING_TZ)
|
||||
t = datetime(here.year, here.month, here.day, tzinfo=BILLING_TZ) \
|
||||
+ timedelta(hours=BELL_FROM_H)
|
||||
return t.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def _today(state, day0, now):
|
||||
"""`(Day, at, None)` or `(None, None, why)` for one today sensor."""
|
||||
today, why = _day(state, day0, now, today=True)
|
||||
@@ -474,7 +492,7 @@ def read_sum(today, tomorrow, now: datetime, hold: Hold | None = None):
|
||||
where `Hold.stand_in()` allows it (the forecast then has `kept` set when
|
||||
that took a reading older than `FRESH_S`). After midnight, if the today
|
||||
side is still short, the tomorrow figures read before midnight are
|
||||
today's, until midnight + `KEPT_UNTIL_H`. Without a `hold` this is
|
||||
today's, until the bell starts (`stand_in()`). Without a `hold` this is
|
||||
exactly as before."""
|
||||
if not today:
|
||||
return None, NOT_SET, ()
|
||||
|
||||
Reference in New Issue
Block a user