fix(tui): DeepSeek V4 tiered peak/off-peak pricing resolved per turn - #5470
Conversation
DeepSeek's published V4 pricing (api-docs.deepseek.com/quick_start/pricing, verified 2026-08-17) is tiered: peak hours are 01:00-04:00 and 06:00-10:00 UTC (half-open), off-peak is exactly half of peak. Replace the flat single-tier V4-Pro/V4-Flash rows with tier-aware rows resolved from each turn's recorded time (same contract as claude_sonnet_5_pricing), in both USD and CNY, and replace the stale flat-rate tests with boundary tests. Signed-off-by: Hunter Bown <hmbown@gmail.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Claude finished @Hmbown's task in 3m 7s —— View job Code review: DeepSeek V4 tiered peak/off-peak pricing
Summary: Solid change. The peak-window helper, tier tables, and time-threading through Findings
Good choices worth calling out
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a57b06ef43
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| fn deepseek_v4_pro_pricing(now: DateTime<Utc>) -> ModelPricing { | ||
| let peak = deepseek_is_peak(now); |
There was a problem hiding this comment.
Fail closed when a tiered DeepSeek turn lacks its timestamp
When an offline or legacy RecordedTurn has no created_at, this tier selection is not necessarily based on the turn's recorded time: scorecard.rs lines 344-352 only marks deepseek-chat and deepseek-reasoner as requiring a timestamp, so explicit deepseek-v4-pro/deepseek-v4-flash IDs and compact aliases fall through to Utc::now(). Consequently, the same saved turn can be charged at the peak or off-peak rate depending on when the scorecard is regenerated, instead of failing closed as the existing time-window contract requires.
Useful? React with 👍 / 👎.
| fn deepseek_v4_pro_pricing(now: DateTime<Utc>) -> ModelPricing { | ||
| let peak = deepseek_is_peak(now); |
There was a problem hiding this comment.
Preserve pre-tier rates for earlier recorded turns
For any DeepSeek V4 turn recorded before this peak/off-peak schedule took effect, this unconditional hour-only selection retroactively applies the new August rates. The removed tests and prior rows establish that earlier V4-Pro and V4-Flash calls used the flat 0.003625/0.435/0.87 and 0.0028/0.14/0.28 USD schedules, but a persisted turn from that period is now recalculated at one of the new tiers merely according to its hour, corrupting historical scorecard totals. Retain the prior rows behind the schedule's effective-date cutoff before applying the hourly tier.
Useful? React with 👍 / 👎.
…me-tiered rates) Every direct DeepSeek first-party rate is time-windowed now (V4 flash/pro peak/off-peak tiers; the retired chat/reasoner aliases price through them), so an undated turn cannot be resolved to one price — treat it like an undated claude-sonnet-5 turn and fail closed with `missing_recorded_time` instead of pricing it at whatever tier `now` happens to be. Windows CI caught exactly that: `direct_deepseek_compatibility_aliases_use_the_flash_route` compared a 15:58Z (off-peak) alias against an undated canonical turn priced at 07:14Z (peak). Tests pin their recorded times; a new test asserts the fail-closed behavior.
|
Follow-up commit 5042de6: Windows CI caught |
|
Claude finished @Hmbown's task in 2m 39s —— View job Code review: DeepSeek V4 tiered peak/off-peak pricing (update)
Scope: diffed Verifying the two Codex findings from this thread:
Carried over from my earlier pass on this branch, still unaddressed (nit): the doc comments at Other observations:
|
…026-08-17 Catalog currency sweep against official provider pages (all re-verified 2026-08-17): - OpenAI gpt-5.6-terra 0.20/2.00/12.00 and gpt-5.6-luna 0.02/0.20/1.20 (cache-read/input/output per 1M) replace the retired launch cards. - Anthropic claude-sonnet-5 stays 2.00/10.00 (cache 0.20 / write 2.50): the scheduled 2026-09-01 increase to 3/15 was cancelled upstream, so the time-windowed flip is gone; claude-opus-5 (GA 2026-07-24, 5.00/25.00, 0.50/6.25, 1M/128K) is added to models.rs, the registry, the pricing table, both bundled catalogs and the agent model list. - Moonshot kimi-k3 0.30/3.00/15.00 and kimi-k2.7-code-highspeed 0.38/1.90/8.00 get first-party rows (membership `k3` stays unpriced). - MiniMax-M2.7-highspeed 0.06/0.60/2.40 (write 0.375). - xAI grok-4.5 (0.30/2.00/6.00) and grok-4.3 (0.20/1.25/2.50) join grok-4.6 on the usage-aware 2x-above-200K tier; bundled xai rows carry limits only (same honesty rule as grok-4.6). - Mistral medium/large/small/code first-party rows (cache-write unpublished, kept unpriced). - Google Gemini API context/max-output (1,048,576 / 65,536) for the seeded ids plus gemini-3.7-flash; OpenRouter dots-studio/dots-3-note-preview:free limits (512K) as the only hosted RedNote/Dots route. Tests pin every number (DeepSeek V4 tier-test style, Hmbown#5470); the Sonnet 5 dispatch-boundary aggregation test now rides the DeepSeek off-peak/peak boundary since Sonnet 5 no longer has one. No-Issue: model catalog currency sweep for v0.9.9/v0.9.10
Summary
First-party DeepSeek V4 pricing is tiered by UTC hour, but
crates/tui/src/pricing.rsstill carried a single flat rate per model (0.003625 / 0.435 / 0.87for V4-Pro,0.0028 / 0.14 / 0.28for V4-Flash). This PR replaces those rows with peak/off-peak tiers resolved from each turn's recorded time, in both USD and CNY.Published rates (per 1M tokens: cache-hit / cache-miss / output)
Verified live on https://api-docs.deepseek.com/quick_start/pricing and the
/zh-cnpage on 2026-08-17. Off-peak is exactly half of peak.Hour rule: peak windows are 01:00–04:00 and 06:00–10:00 UTC, half-open — a turn is peak iff its recorded UTC hour is in
{1,2,3,6,7,8,9}; every other hour is off-peak.What changed
deepseek_peak_hour(hour_utc)/deepseek_is_peak(now)helpers (chrono::Timelike::hour()on the recordedDateTime<Utc>).deepseek_v4_pro_pricing(now)anddeepseek_v4_flash_pricing(now)now take the recorded time and return the tier's USD and CNY rows; cache-write policy staysDocumentedAsInputRate(DEEPSEEK_CACHE_WRITE_IS_FREE).pricing_for_model_atpassesnowthrough, so the route audit (audit_turn_cost_for_provider_on_endpoint_at→provider_owned_hand_pricing_at) prices each turn at its own recorded tier, mirroring theclaude_sonnet_5_pricingrecorded-time precedent.crates/config/assets/models_dev.bundled.jsonalready omits DeepSeek-native pricing (its_meta.honestynote says a flat catalog row would be misleading), so no catalog row needed updating; a flatcostrow cannot express the tiers, which is why the time-aware table lives inpricing.rs.Tests
Removed (pinned the stale flat rates):
v4_pro_uses_limited_time_discount_before_expiryv4_pro_keeps_adjusted_rates_after_discount_windowv4_pro_discount_still_applies_just_before_old_may5_expiryv4_flash_keeps_current_published_ratesAdded:
deepseek_peak_window_is_half_open_on_utc_hours— all 24 hours against the{1,2,3,6,7,8,9}setdeepseek_v4_tiers_flip_at_each_published_utc_boundary— 00:59/01:00, 03:59/04:00, 05:59/06:00, 09:59/10:00 UTC, both models, exact USD + CNY valuesdeepseek_v4_pro_off_peak_and_peak_rates_match_published_table(also asserts the retired 0.003625/0.435/0.87 rates never resurface)deepseek_v4_flash_off_peak_and_peak_rates_match_published_table(same for 0.0028/0.14/0.28)deepseek_v4_off_peak_is_exactly_half_of_peakdeepseek_audit_uses_recorded_time_tier_not_now— route audit onDeepseek/DeepseekCN/DeepseekAnthropicprices from the RECORDED time, USD and CNYUpdated to pin a deterministic off-peak/peak time instead of
Utc::now():unpublished_cache_write_fails_closed_but_documented_same_rate_pricescost_estimate_calculates_usd_and_cnyhosted_flash_and_pro_routes_price_from_bundled_docs_ratesNo-Issue: #5241 follow-up; pricing verified against the live published page 2026-08-17