@@ -105,6 +105,10 @@ const (
105105 defaultBackgroundRefreshInterval = 2 * time .Minute
106106 defaultUsageProbeMaxAge = 10 * time .Minute
107107 defaultRecoveryProbeInterval = 30 * time .Minute
108+ premium5hUrgencyWindow = 4 * time .Hour
109+ premium5hUrgencyMaxBonus = 25.0
110+ premium5hUrgencyMinRemainingPct = 5.0
111+ premium5hUrgencyFullRemainingPct = 50.0
108112)
109113
110114// SchedulerBreakdown 调度评分拆解
@@ -117,6 +121,7 @@ type SchedulerBreakdown struct {
117121 SuccessBonus float64
118122 ProvenBonus float64 // 经过验证的账号(TotalRequests > 10)加分
119123 UsagePenalty7d float64
124+ UsageUrgencyBonus5h float64 // Premium 5h 重置窗口前的紧迫性 bonus
120125 LatencyPenalty float64
121126 SuccessRatePenalty float64 // 滑动窗口成功率惩罚
122127}
@@ -346,8 +351,7 @@ func linearDecay(base float64, elapsed, window time.Duration) float64 {
346351 return base * (1.0 - float64 (elapsed )/ float64 (window ))
347352}
348353
349- func (a * Account ) schedulerBreakdownLocked () SchedulerBreakdown {
350- now := time .Now ()
354+ func (a * Account ) schedulerBreakdownLocked (now time.Time ) SchedulerBreakdown {
351355 breakdown := SchedulerBreakdown {}
352356 premium5hLimited := a .premium5hRateLimitedLocked (now )
353357
@@ -419,6 +423,52 @@ func (a *Account) schedulerBreakdownLocked() SchedulerBreakdown {
419423 return breakdown
420424}
421425
426+ // premium5hUsageUrgencyBonusLocked 计算 plus/pro 账号在 5h 重置窗口前的紧迫性 bonus。
427+ // 当账号还有较多剩余配额且即将到达重置点(剩余 ≤ 4h)时,提升 dispatch score
428+ // 让调度器优先把它的额度消耗掉,避免到点重置时白白浪费。
429+ // 需持有 a.mu 锁。
430+ func (a * Account ) premium5hUsageUrgencyBonusLocked (now time.Time ) float64 {
431+ if ! isPremium5hPlan (a .PlanType ) {
432+ return 0
433+ }
434+ if ! a .UsagePercent5hValid || a .Reset5hAt .IsZero () {
435+ return 0
436+ }
437+ if a .UsagePercent5h >= 100 || a .premium5hRateLimitedLocked (now ) {
438+ return 0
439+ }
440+ if a .AccessToken == "" || a .Status == StatusError || a .HealthTier == HealthTierBanned {
441+ return 0
442+ }
443+ if a .Status == StatusCooldown && now .Before (a .CooldownUtil ) {
444+ return 0
445+ }
446+ if a .usageExhaustedLocked () {
447+ return 0
448+ }
449+
450+ timeRemaining := a .Reset5hAt .Sub (now )
451+ if timeRemaining <= 0 || timeRemaining > premium5hUrgencyWindow {
452+ return 0
453+ }
454+
455+ quotaRemaining := 100 - a .UsagePercent5h
456+ if quotaRemaining <= premium5hUrgencyMinRemainingPct {
457+ return 0
458+ }
459+
460+ timeFactor := 1 - float64 (timeRemaining )/ float64 (premium5hUrgencyWindow )
461+ quotaFactor := quotaRemaining / premium5hUrgencyFullRemainingPct
462+ if quotaFactor > 1 {
463+ quotaFactor = 1
464+ }
465+ if quotaFactor < 0 {
466+ quotaFactor = 0
467+ }
468+
469+ return premium5hUrgencyMaxBonus * timeFactor * quotaFactor
470+ }
471+
422472func (a * Account ) effectiveBaseConcurrencyLocked (storeBaseLimit int64 ) int64 {
423473 if a .BaseConcurrencyOverride != nil && * a .BaseConcurrencyOverride > 0 {
424474 return * a .BaseConcurrencyOverride
@@ -463,7 +513,7 @@ func (a *Account) effectiveScoreBiasLocked(now time.Time, tier AccountHealthTier
463513
464514func (a * Account ) recomputeSchedulerLocked (baseLimit int64 ) {
465515 now := time .Now ()
466- breakdown := a .schedulerBreakdownLocked ()
516+ breakdown := a .schedulerBreakdownLocked (now )
467517 score := 100.0 -
468518 breakdown .UnauthorizedPenalty -
469519 breakdown .RateLimitPenalty -
@@ -507,7 +557,10 @@ func (a *Account) recomputeSchedulerLocked(baseLimit int64) {
507557
508558 baseConcurrencyEffective := a .effectiveBaseConcurrencyLocked (baseLimit )
509559 scoreBiasEffective := a .effectiveScoreBiasLocked (now , tier )
510- dispatchScore := score + float64 (scoreBiasEffective )
560+ if a .dispatchBonusEligibleLocked (now , tier ) {
561+ breakdown .UsageUrgencyBonus5h = a .premium5hUsageUrgencyBonusLocked (now )
562+ }
563+ dispatchScore := score + float64 (scoreBiasEffective ) + breakdown .UsageUrgencyBonus5h
511564
512565 a .HealthTier = tier
513566 a .SchedulerScore = score
@@ -859,6 +912,11 @@ func (a *Account) GetSchedulerDebugSnapshot(baseLimit int64) SchedulerDebugSnaps
859912 defer a .mu .Unlock ()
860913
861914 a .recomputeSchedulerLocked (baseLimit )
915+ now := time .Now ()
916+ breakdown := a .schedulerBreakdownLocked (now )
917+ if a .dispatchBonusEligibleLocked (now , a .HealthTier ) {
918+ breakdown .UsageUrgencyBonus5h = a .premium5hUsageUrgencyBonusLocked (now )
919+ }
862920 return SchedulerDebugSnapshot {
863921 HealthTier : string (a .HealthTier ),
864922 SchedulerScore : a .SchedulerScore ,
@@ -868,7 +926,7 @@ func (a *Account) GetSchedulerDebugSnapshot(baseLimit int64) SchedulerDebugSnaps
868926 BaseConcurrencyOverride : cloneInt64Ptr (a .BaseConcurrencyOverride ),
869927 BaseConcurrencyEffective : a .BaseConcurrencyEffective ,
870928 DynamicConcurrencyLimit : a .DynamicConcurrencyLimit ,
871- Breakdown : a . schedulerBreakdownLocked () ,
929+ Breakdown : breakdown ,
872930 LastUnauthorizedAt : a .LastUnauthorizedAt ,
873931 LastRateLimitedAt : a .LastRateLimitedAt ,
874932 LastTimeoutAt : a .LastTimeoutAt ,
@@ -2295,6 +2353,7 @@ func (s *Store) PersistUsageSnapshot(acc *Account, pct7d float64) {
22952353
22962354 now := time .Now ()
22972355 acc .SetUsageSnapshot (pct7d , now )
2356+ s .fastSchedulerUpdate (acc )
22982357
22992358 if s .db == nil {
23002359 return
0 commit comments