mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(cost): parse override_dates strictly and ignore rules without valid hours
override_dates entries are parsed as date objects with an exact YYYY-MM-DD shape, so week-date, ordinal, and whitespace-padded strings can never select a rule. An override rule also needs a parseable hours_utc, matching how ordinary rules without windows are skipped; previously a rule with broken hours won its date and billed peak all day.
This commit is contained in:
parent
05da2d7de0
commit
c39069b9bb
2 changed files with 50 additions and 19 deletions
|
|
@ -466,8 +466,8 @@ def _weekday_calendar(weekday_timezone: object) -> tzinfo:
|
|||
return timezone.utc
|
||||
|
||||
|
||||
def _calendar_date(reference_utc: datetime, weekday_timezone: object) -> str:
|
||||
return reference_utc.astimezone(_weekday_calendar(weekday_timezone)).date().isoformat()
|
||||
def _calendar_date(reference_utc: datetime, weekday_timezone: object) -> date:
|
||||
return reference_utc.astimezone(_weekday_calendar(weekday_timezone)).date()
|
||||
|
||||
|
||||
def _matches_weekdays(reference_utc: datetime, weekdays: object, weekday_timezone: object) -> bool:
|
||||
|
|
@ -492,23 +492,28 @@ def _as_window_strings(value: object) -> tuple[str, ...]:
|
|||
return ()
|
||||
|
||||
|
||||
def _is_iso_date(value: object) -> bool:
|
||||
if not isinstance(value, str) or len(value) != 10:
|
||||
return False
|
||||
def _parse_iso_date(value: object) -> date | None:
|
||||
if (
|
||||
not isinstance(value, str)
|
||||
or len(value) != 10
|
||||
or value[4] != "-"
|
||||
or value[7] != "-"
|
||||
or not (value[:4] + value[5:7] + value[8:10]).isdigit()
|
||||
):
|
||||
return None
|
||||
try:
|
||||
date.fromisoformat(value)
|
||||
return date(int(value[:4]), int(value[5:7]), int(value[8:10]))
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
return None
|
||||
|
||||
|
||||
def _as_date_strings(value: object) -> tuple[str, ...]:
|
||||
def _as_dates(value: object) -> frozenset[date]:
|
||||
if isinstance(value, str) or not isinstance(value, Sequence):
|
||||
return ()
|
||||
entries: Final = tuple(value)
|
||||
if not entries or not all(_is_iso_date(entry) for entry in entries):
|
||||
return ()
|
||||
return entries
|
||||
return frozenset()
|
||||
parsed: Final = tuple(_parse_iso_date(entry) for entry in value)
|
||||
if not parsed or any(entry is None for entry in parsed):
|
||||
return frozenset()
|
||||
return frozenset(entry for entry in parsed if entry is not None)
|
||||
|
||||
|
||||
def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None = None) -> bool:
|
||||
|
|
@ -517,8 +522,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 (a malformed override_dates
|
||||
disables the rule entirely).
|
||||
skipped), and on any other date the rule never applies (a malformed override_dates or
|
||||
hours_utc disables the rule entirely).
|
||||
"""
|
||||
reference: Final = current_time if current_time is not None else current_billing_time()
|
||||
reference_utc: Final = (
|
||||
|
|
@ -532,7 +537,11 @@ def _is_off_peak(off_peak: Mapping[str, object], current_time: datetime | None =
|
|||
if isinstance(windows, str) or not isinstance(windows, Sequence)
|
||||
else tuple(rule for rule in windows if isinstance(rule, Mapping))
|
||||
)
|
||||
override_rules: Final = tuple(rule for rule in rules if today in _as_date_strings(rule.get("override_dates")))
|
||||
override_rules: Final = tuple(
|
||||
rule
|
||||
for rule in rules
|
||||
if _as_window_strings(rule.get("hours_utc")) and today in _as_dates(rule.get("override_dates"))
|
||||
)
|
||||
if override_rules:
|
||||
for rule in override_rules:
|
||||
if _is_within_off_peak_window(_as_window_strings(rule.get("hours_utc")), reference_utc):
|
||||
|
|
|
|||
|
|
@ -717,13 +717,34 @@ def test_is_off_peak_ignores_malformed_override_dates():
|
|||
|
||||
peak_instant = datetime(2026, 1, 1, 12, 0, tzinfo=timezone.utc)
|
||||
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"]):
|
||||
for bad in (
|
||||
"2026-01-01",
|
||||
[20260101, None],
|
||||
["2026-1-1"],
|
||||
["2026-W01-4"],
|
||||
["2026-01-1 "],
|
||||
):
|
||||
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 disable its rule"
|
||||
)
|
||||
|
||||
|
||||
def test_is_off_peak_override_rule_without_valid_hours_is_disabled():
|
||||
"""An override rule whose hours_utc does not parse never applies, even on a listed
|
||||
date: the flat hours_utc still decides instead of the broken rule billing peak all day."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
block = {
|
||||
"hours_utc": "00:00-00:00",
|
||||
"windows": [
|
||||
{"override_dates": ["2026-03-03"], "hours_utc": 5},
|
||||
{"override_dates": ["2026-03-03"]},
|
||||
],
|
||||
}
|
||||
assert _is_off_peak(block, datetime(2026, 3, 3, 12, 0, tzinfo=timezone.utc)) is True
|
||||
|
||||
|
||||
def test_is_off_peak_disables_rules_with_partially_malformed_override_dates():
|
||||
"""One bad entry disables the whole override_dates list: a rule whose dates do not all
|
||||
parse never applies on any date, so the flat hours decide alone."""
|
||||
|
|
@ -758,7 +779,8 @@ def test_override_dates_use_weekday_timezone_calendar_on_shipped_deepseek_rows()
|
|||
"""Every shipped DeepSeek row with off_peak_pricing carries the same block as
|
||||
deepseek/deepseek-flash: the 2026 PRC holidays and make-up workdays, read on the
|
||||
Asia/Shanghai calendar. The make-up Sunday bills weekday hours and a holiday is
|
||||
off-peak all day."""
|
||||
off-peak all day. Dates per the State Council 2026 notice,
|
||||
https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm, read 2026-09-24."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import litellm
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue