Skip to content

Commit aac1786

Browse files
fix(output): don't flag a History slot for a warning in an edge it doesn't trust
The slot reconstruction ignores the first or last five minutes of a slot when they don't hold one state, as that is the previous (or next) slot's status lagging the boundary. The warning flag scanned the whole slot, so a warning that lagged in with that leftover flagged a slot whose shown state was rebuilt without those minutes. Warnings in an untrusted edge are now dropped along with the status they arrived with. Also notes why a warning is held across a recorder gap: it is held with the same forward-filled status the slot's state is rebuilt from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 7017ebb commit aac1786

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

‎apps/predbat/output.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3571,7 +3571,10 @@ def calculate_yesterday(self):
35713571
status = status.split(",")[0].strip()
35723572
predbat_status[minute] = status
35733573
if warning:
3574-
# predbat_status is keyed by minutes ago; plan-minute m is (minutes_now + end_record - m) ago
3574+
# predbat_status is keyed by minutes ago; plan-minute m is (minutes_now + end_record - m) ago.
3575+
# minute_data_state() holds each recorded status until the next one, so across a gap in
3576+
# the recorder the warning is held along with the state it arrived with - the flag
3577+
# stays with the state the slot shows, which is rebuilt from the same held value.
35753578
status_warnings[minutes_now + end_record - minute] = warning
35763579
# The car icon on this table follows the recorded "Hold for car" status, as its SoC is measured.
35773580
# predbat_status is keyed by minutes ago; plan-minute m is (minutes_now + end_record - m) ago.
@@ -3592,6 +3595,12 @@ def calculate_yesterday(self):
35923595
tally_start = 0 if len(set(slot_statuses[:edge_minutes])) == 1 else edge_minutes
35933596
tally_end = self.plan_interval_minutes if len(set(slot_statuses[-edge_minutes:])) == 1 else self.plan_interval_minutes - edge_minutes
35943597

3598+
# A warning in an edge window the tally does not trust came in with the previous (or
3599+
# next) slot's leftover status, so it must not flag this slot either - the flag names
3600+
# a problem that ran alongside the minutes this slot's state was rebuilt from.
3601+
for slot_offset in list(range(0, tally_start)) + list(range(tally_end, self.plan_interval_minutes)):
3602+
status_warnings.pop(minute + slot_offset, None)
3603+
35953604
charge_start_minute = None
35963605
charge_end_minute = None
35973606
export_start_minute = None

‎apps/predbat/tests/test_calculate_yesterday.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,6 +2399,28 @@ def _warning_for_one_hour(stamp):
23992399
print("ERROR: the warning should flag plan minutes {}-{} exactly, got {} minutes from {} to {}".format(warning_start, warning_end - 1, len(flagged), min(flagged) if flagged else None, max(flagged) if flagged else None))
24002400
failed = True
24012401

2402+
# A warning that lags two minutes into the next slot, behind the previous slot's state, is that
2403+
# slot's leftover: the next slot's state is rebuilt without those minutes, so its flag is too.
2404+
lag_slot = 16 * 60
2405+
2406+
def _warning_lags_into_next_slot(stamp):
2407+
plan_minute = int((stamp - plan_start).total_seconds() // 60)
2408+
if lag_slot - 30 <= plan_minute < lag_slot:
2409+
return [(0, "Charging, " + warning)]
2410+
if plan_minute == lag_slot:
2411+
return [(0, "Charging, " + warning), (120, "Exporting")]
2412+
return [(0, "Exporting")]
2413+
2414+
captured = _reconstruct_windows_from_status(my_predbat, _warning_lags_into_next_slot)
2415+
flagged = set((captured.get("status_warnings") or {}).keys())
2416+
if flagged != set(range(lag_slot - 30, lag_slot)):
2417+
print(
2418+
"ERROR: a warning lagging into the next slot's edge should flag only the slot it belongs to ({}-{}), got {} minutes from {} to {}".format(
2419+
lag_slot - 30, lag_slot - 1, len(flagged), min(flagged) if flagged else None, max(flagged) if flagged else None
2420+
)
2421+
)
2422+
failed = True
2423+
24022424
# A clean run is not flagged
24032425
captured = _reconstruct_windows_from_status(my_predbat, lambda stamp: [(0, "Exporting")])
24042426
if captured.get("status_warnings"):

0 commit comments

Comments
 (0)