Skip to content

feat(cost): add override_dates to off_peak_pricing windows for holidays and make-up workdays - #42880

Draft
devin-ai-integration[bot] wants to merge 11 commits into
mainfrom
litellm_off_peak_holiday_dates
Draft

devin-ai-integration[bot] wants to merge 11 commits into
mainfrom
litellm_off_peak_holiday_dates

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

what to review

DeepSeek bills off-peak all day on Chinese public holidays and peak on weekend make-up workdays; we priced both wrong. A window rule can now carry override_dates, and the eight direct DeepSeek rows list the 2026 dates

summary

  • only rules that carry override_dates behave differently; every other off_peak_pricing block prices as before
  • on a listed date the rules listing it decide alone; weekdays, the flat hours_utc and other rules are skipped
  • a rule whose override_dates has any entry that is not a YYYY-MM-DD string, or whose hours_utc does not parse, is disabled, not widened to every day
  • cost_map_guard.py skips its schema sync and validation steps when the PR also changes the generator, since it runs main's copy of the generator on pull_request_target
  • verified with unit tests, a live base vs head proxy A/B against the real DeepSeek API at peak hour, and a 15 minute sweep of 2026

decisions

  • one key on the existing window rule, rather than top-level date lists or a shared calendar table, so a holiday is hours_utc: "00:00-00:00" and a make-up day is a copy of the weekday hours
  • override wins outright instead of unioning with other rules, because a make-up Saturday has to lose the weekend rule
  • the dates are data in the cost map and need a refresh each year

TLDR

Problem this solves:

  • DeepSeek is off-peak all day on Chinese public holidays
  • weekend make-up workdays are billed at peak
  • a window rule could only express hours and weekdays

How it solves it:

  • override_dates on a window rule: on those dates it alone decides
  • every direct DeepSeek row gains a holiday rule and a make-up workday rule for 2026
  • schema generator, Rust catalog type and TypedDict know the key

User Flow

Before: a team routing to deepseek/deepseek-flash is overcharged on holidays

  1. On 2026-10-01 (National Day, a Thursday) at 08:00 UTC they POST https://litellm-domain/v1/chat/completions with "model": "deepseek/deepseek-flash"
  2. x-litellm-response-cost shows the peak rate, twice what DeepSeek charges that day
  3. On Saturday 2026-10-10, a make-up workday, the same call is undercharged at the off-peak rate

After: the same calls price the way DeepSeek bills them

  1. On 2026-10-01 at 08:00 UTC they POST https://litellm-domain/v1/chat/completions with "model": "deepseek/deepseek-flash"
  2. x-litellm-response-cost shows the off-peak rate all day
  3. On 2026-10-10 the weekday hours apply
  4. Any proxy admin can model another provider's holidays with a window rule carrying override_dates in their own model_info

Relevant issues

Follow-up of #41960

Linear ticket

Resolves LIT-8177

Follow-up of LIT-7469

How it works

_is_off_peak takes the request's date on the weekday_timezone calendar (UTC when unset). If a window rule lists that date in override_dates, the answer is whether the current time falls in that rule's hours_utc. Otherwise the flat hours_utc and the weekday rules run as before, with rules that carry override_dates left out

The DeepSeek rows set weekday_timezone: Asia/Shanghai because the State Council declares the dates on China time. With DeepSeek's hours it changes no price: the two calendars only disagree 16:00 to 00:00 UTC, which DeepSeek bills off-peak on any day, and a 15 minute sweep of 2026 found zero differing instants

The Rust catalog round-trips the cost map with deny_unknown_fields, so it gets the key as Option<Vec<String>>. The JSON schema types it as a non-empty array of YYYY-MM-DD strings, so a typo in the shipped map fails schema CI. OffPeakPricing is not in the proxy OpenAPI spec, so schema.d.ts is unchanged

Dates and sources

Dates come from the State Council notice on the 2026 holiday arrangement, https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm, read from the raw HTML on 2026-09-24. Holidays: 01-01 to 01-03, 02-15 to 02-23, 04-04 to 04-06, 05-01 to 05-05, 06-19 to 06-21, 09-25 to 09-27, 10-01 to 10-07. Make-up workdays: 01-04, 02-14, 02-28, 05-09, 09-20, 10-10

DeepSeek's pricing footnote, https://api-docs.deepseek.com/quick_start/pricing, states the policy: peak is 01:00 to 04:00 and 06:00 to 10:00 UTC, Monday to Friday, excluding Chinese public holidays. DeepSeek does not publish the date list. Weekend holiday dates are listed too so the list can be checked line by line against the notice

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Two proxies, base at the merge base and head at 09746bf (the later commit only adds weekday_timezone to the shipped rows), each with its own Postgres and LITELLM_LOCAL_MODEL_COST_MAP=True, both calling the real DeepSeek API on Thursday 2026-09-24 between 06:00 and 06:07 UTC, a peak window. Every call returned 200 with 36 prompt and 5 completion tokens

ds-stock is the shipped deepseek/deepseek-flash row. ds-holiday-weekend-rule adds one rule to it: {"hours_utc": "00:00-00:00", "weekdays": [6, 7], "override_dates": ["2026-09-24"]}, which marks today as a holiday; the weekdays on it is what separates the two sides on a Thursday. ds-bad-override and ds-bad-override-2 put "2026-09-24" (bare string) and [20260924, null] on a 00:00-00:00 rule

curl -s http://127.0.0.1:$PORT/v1/chat/completions -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"model":"ds-holiday-weekend-rule","messages":[{"role":"user","content":"Reply with the single word ok"}],"max_tokens":5}' -D -

Before (port 4817)

  1. ds-stock: x-litellm-response-cost: 1.6800000000000002e-05, peak
  2. ds-holiday-weekend-rule: 1.6800000000000002e-05, the key is unknown and Thursday is not in [6, 7]
  3. spend log: deepseek/deepseek-flash | 1.68e-05 | 36 | 5 for both

After (port 4818)

  1. ds-stock: 1.6800000000000002e-05, same as base since today is not a holiday
  2. ds-holiday-weekend-rule: 8.400000000000001e-06, the override date selects that rule and its weekdays are ignored
  3. ds-bad-override, ds-bad-override-2: 1.6800000000000002e-05 on both, 200, no proxy error
  4. spend log: 1.68e-05, 8.400000000000001e-06, 1.68e-05, 1.68e-05, matching the headers

GET /model/info for ds-stock returns 2 rules in off_peak_pricing.windows on base and 4 on head

Mutation checks: reverting the override early return, the skip of nonmatching override rules, or the all-entries-parse rule each turns its test red

Type

New Feature

Caveats (if any)

Medium

  • the 2026 dates are static data; someone has to add 2027 before January

Low

  • the openrouter/deepseek/... rows keep their own schedule; OpenRouter's holiday policy is not documented
  • cost-map-guard is red on this PR and cannot go green from it: the check runs main's schema generator, which does not know override_dates. The guard change here fixes that for the next generator PR but only once it is on main, so this one merges over the red required check (test_committed_schema_matches_generator_output in the misc shard proves the schema is in sync with this PR's generator)
  • the shipped rows were not exercised on a real holiday; the next one is 2026-09-25, peak window 01:00 UTC

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/3e4563b49e964e089f8abcee782456ec
Open in Devin Desktop: https://app.devin.ai/desktop/session/3e4563b49e964e089f8abcee782456ec?variant=devin
Requested by: @shreshth-berri


Note

Medium Risk
Changes token billing logic in _is_off_peak and ships date-specific rates for DeepSeek; incorrect dates or precedence would mischarge customers, though the PR adds broad tests and schema validation.

Overview
Adds override_dates on off-peak window rules so billing can follow calendar exceptions (e.g. all-day off-peak on holidays, weekday hours on weekend make-up days). On a listed date, only rules that include that date apply—their hours_utc wins and weekdays, flat hours_utc, and other windows are skipped; malformed override_dates or unparseable hours_utc disable that rule instead of widening off-peak.

_is_off_peak in utils.py implements the precedence (dates read on weekday_timezone). The schema generator, committed schema, Rust OffPeakWindow, and OffPeakWindow TypedDict document and validate the field. Direct DeepSeek entries in the cost map gain 2026 PRC holiday and make-up rules plus weekday_timezone: Asia/Shanghai.

cost_map_guard.py skips schema sync/validation when the PR also edits ci_cd/generate_model_prices_schema.py, while still enforcing backup parity. Tests cover override behavior end-to-end and guard/schema drift checks.

Reviewed by Cursor Bugbot for commit 2c24623. Bugbot is set up for automated code reviews on this repo. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@codspeed

codspeed Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_off_peak_holiday_dates (2c24623) with main (c9e8a04)

Open in CodSpeed

@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

shrey kharbanda added 2 commits September 24, 2026 05:45
…icing windows

A window rule can now list override_dates: YYYY-MM-DD dates read on the block's weekday_timezone calendar. On a listed date only the matching rules decide (weekdays ignored, flat hours_utc and other rules skipped); on any other date a rule carrying override_dates never applies. The DeepSeek rows model the 2026 PRC public holidays (off-peak all day) and make-up workdays (weekday off-peak hours) this way.
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.
@devin-ai-integration devin-ai-integration Bot changed the title feat(cost): honor holiday and make-up workday dates in off_peak_pricing feat(cost): add override_dates to off_peak_pricing windows for holidays and make-up workdays Sep 24, 2026
The shipped DeepSeek rows model PRC public holidays and make-up workdays, which the State Council notice declares on China Standard Time, so the rows now set weekday_timezone: Asia/Shanghai and their override_dates follow the Shanghai calendar instead of UTC.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread model_prices_and_context_window.json
Comment thread litellm/litellm_core_utils/llm_cost_calc/utils.py Outdated
shrey kharbanda added 2 commits September 24, 2026 06:38
A window rule's override_dates now applies only when the value is a non-empty sequence of strings that all parse as YYYY-MM-DD. One malformed entry disables the whole list, matching how the rule already disappears when override_dates itself is malformed. Also updates the hand-written schema in test_utils for the new windows key.
Every direct DeepSeek row carrying off_peak_pricing now shares the same windows, weekday_timezone Asia/Shanghai, and override_dates for the 2026 public holidays and make-up workdays; each row keeps its own halved off-peak rates. A unit test moment that pinned 2026-09-25 (now a modeled holiday) moves to 2026-09-18.
shrey kharbanda added 2 commits September 24, 2026 19:27
…id 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.
cost-map-guard runs the base branch's generator, so a diff that edits ci_cd/generate_model_prices_schema.py makes its schema sync and validation checks stale by definition. The parse and backup checks still run, and the head generator is covered by the schema unit tests.
@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge, although the existing non-blocking concern about renewing DeepSeek dates remains.

Findings

  1. P2 Holiday dates need renewal ▶

Summary

The PR adds date-specific off-peak window overrides, updates the eight direct DeepSeek price-map rows for 2026 holidays and make-up workdays, and aligns the schema, types, guard, and tests.

  • The latest changes prevent wholly unparseable override hours from taking precedence and add pricing and catalog assertions.
  • The previously reported need for an annual date-renewal safeguard remains.

Reviews (2) · Last reviewed commit: "fix(cost): disable override rules whose ..."

Comment thread litellm/litellm_core_utils/llm_cost_calc/utils.py Outdated
},
{
"hours_utc": "00:00-00:00",
"override_dates": [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Holiday dates need renewal

The shipped DeepSeek holiday and make-up-workday overrides contain only 2026 dates, with no renewal check or reminder in the changed tests. Without a refresh, later dates fall back to the ordinary weekday rules, silently bringing back the pricing errors this change addresses. An annual refresh safeguard would make that maintenance visible.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread model_prices_and_context_window.json

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2c24623. Configure here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants