fix(cost): disable off_peak rules whose override_dates are malformed

A rule carrying the override_dates key now never participates in the normal weekday path, even when its entries cannot be parsed, matching how a rule with fully malformed weekdays never matches. Previously a malformed override_dates was treated as absent, which let a whole-day rule silently widen the off-peak hours.
This commit is contained in:
shrey kharbanda 2026-09-24 06:05:04 +00:00
parent 20507eb3e9
commit 09746bff48
5 changed files with 12 additions and 10 deletions

View file

@ -66,7 +66,8 @@ OFF_PEAK_WINDOW: Final[JsonSchema] = {
"minItems": 1,
"description": (
"YYYY-MM-DD dates, read on weekday_timezone, on which this rule alone decides: "
"weekdays and every other window are ignored. On any other date the rule does not apply."
"weekdays and every other window are ignored. On any other date the rule does not "
"apply, and malformed entries disable it."
),
},
},

View file

@ -504,7 +504,8 @@ def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None =
hours apply only on its weekdays. A rule carrying override_dates applies only on those
dates, read on the weekday_timezone calendar: on a listed date the matching rules alone
decide (their weekdays are ignored, and the flat hours_utc and every other rule are
skipped), and on any other date the rule never applies.
skipped), and on any other date the rule never applies (a malformed override_dates
disables the rule entirely).
"""
reference: Final = current_time if current_time is not None else current_billing_time()
reference_utc: Final = (
@ -528,7 +529,7 @@ def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None =
if flat_windows and _is_within_off_peak_window(flat_windows, reference_utc):
return True
for rule in rules:
if _as_date_strings(rule.get("override_dates")):
if "override_dates" in rule:
continue
rule_windows = _as_window_strings(rule.get("hours_utc"))
if not rule_windows:

View file

@ -235,7 +235,7 @@ class OffPeakWindow(TypedDict, total=False):
omitted means every day. The weekday is read on the calendar named by the block's
weekday_timezone. override_dates lists YYYY-MM-DD dates on that calendar on which this
rule alone decides, ignoring weekdays and every other window; on any other date the
rule does not apply.
rule does not apply, and malformed entries disable it entirely.
"""
hours_utc: ReadOnly[str | Sequence[str]]

View file

@ -568,7 +568,7 @@
"pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
},
"minItems": 1,
"description": "YYYY-MM-DD dates, read on weekday_timezone, on which this rule alone decides: weekdays and every other window are ignored. On any other date the rule does not apply."
"description": "YYYY-MM-DD dates, read on weekday_timezone, on which this rule alone decides: weekdays and every other window are ignored. On any other date the rule does not apply, and malformed entries disable it."
}
},
"required": [

View file

@ -711,16 +711,16 @@ def test_is_off_peak_override_dates_read_on_weekday_timezone_calendar():
def test_is_off_peak_ignores_malformed_override_dates():
"""A bare string, non-string entries, or an unparseable date are ignored, so malformed
override_dates can never silently widen the off-peak hours."""
"""A bare string, non-string entries, or an unparseable date disable the rule carrying
them, so malformed override_dates can never silently widen the off-peak hours."""
from datetime import datetime, timezone
peak_instant = datetime(2026, 1, 1, 12, 0, tzinfo=timezone.utc)
weekday_windows = [{"hours_utc": "00:30-01:00", "weekdays": [1, 2, 3, 4, 5]}]
peak_windows = [{"hours_utc": "00:30-01:00", "weekdays": [1, 2, 3, 4, 5]}]
for bad in ("2026-01-01", [20260101, None], ["2026-1-1"]):
block = {"windows": [{**weekday_windows[0], "override_dates": bad}]}
block = {"windows": peak_windows + [{"hours_utc": "00:00-00:00", "override_dates": bad}]}
assert _is_off_peak(block, peak_instant) is False, (
f"override_dates={bad!r} is malformed and must be ignored"
f"override_dates={bad!r} is malformed and must disable its rule"
)