menubar: linear pace on Codex quota windows — deficit/reserve, projection, run-out ETA (#726) (#728)

Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
This commit is contained in:
Aditya Vikram Singh 2026-07-20 17:51:43 +05:30 committed by GitHub
parent b8435073ee
commit bcef34f081
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 217 additions and 4 deletions

View file

@ -0,0 +1,63 @@
import Foundation
/// Pure pace math for a quota window (#726 phase 1): whole-window linear
/// extrapolation plus the noise guards that keep it honest. Deliberately no
/// recent-burst weighting or smoothing with only (used%, resetsAt, window
/// length) to go on, the average pace over the elapsed window is the only
/// rate we can defend.
enum QuotaPace {
struct Result: Equatable {
/// used% expected%; positive means ahead of pace (in deficit).
let deltaPercent: Double
/// Linear projection of used% at the reset boundary.
let projectedPercent: Double
let willOverflow: Bool
/// When the window hits 100% at the current pace. Nil when there is
/// no overflow, or when the window is short enough that a linear ETA
/// would cry wolf (one heavy burst on a 5h window reads as "runs out
/// in 40min", then recovers). Deficit/reserve still shows there.
let hitsLimitAt: Date?
}
/// Windows at or under this length get deficit/reserve only, no ETA.
static let etaSuppressionMaxSeconds: TimeInterval = 6 * 3600
/// Show nothing until this fraction of the window has elapsed: projecting
/// a whole week off the first few minutes is noise, not signal.
static let minimumElapsedFraction = 0.03
static func evaluate(
usedPercent: Double,
resetsAt: Date?,
windowSeconds: Int,
now: Date = Date()
) -> Result? {
guard let resetsAt, windowSeconds > 0 else { return nil }
let duration = TimeInterval(windowSeconds)
let remaining = resetsAt.timeIntervalSince(now)
// Reset in the past, or further out than one full window: clock or
// data skew. Say nothing rather than something wrong.
guard remaining > 0, remaining <= duration else { return nil }
let elapsed = duration - remaining
let elapsedFraction = elapsed / duration
guard elapsedFraction >= minimumElapsedFraction else { return nil }
let used = min(max(usedPercent, 0), 100)
// Exhausted window: the bar already says everything.
guard used < 100 else { return nil }
let delta = used - elapsedFraction * 100
let projected = used / elapsedFraction
var hitsLimitAt: Date?
if projected > 100, duration > etaSuppressionMaxSeconds {
let percentPerSecond = used / elapsed
if percentPerSecond > 0 {
hitsLimitAt = now.addingTimeInterval((100 - used) / percentPerSecond)
}
}
return Result(
deltaPercent: delta,
projectedPercent: projected,
willOverflow: projected > 100,
hitsLimitAt: hitsLimitAt
)
}
}

View file

@ -2076,7 +2076,7 @@ private struct CodexPlanInsight: View {
label: "\(primary.windowLabel) window",
percent: primary.usedPercent,
resetsAt: primary.resetsAt,
projection: nil
projection: pace(for: primary)
)
}
if let secondary = usage.secondary {
@ -2084,7 +2084,7 @@ private struct CodexPlanInsight: View {
label: "\(secondary.windowLabel) window",
percent: secondary.usedPercent,
resetsAt: secondary.resetsAt,
projection: nil
projection: pace(for: secondary)
)
}
// Surface non-zero per-model rate limits (Codex Spark, etc.) so
@ -2095,7 +2095,7 @@ private struct CodexPlanInsight: View {
label: "\(limit.name) · \(p.windowLabel)",
percent: p.usedPercent,
resetsAt: p.resetsAt,
projection: nil
projection: pace(for: p)
)
}
if let s = limit.secondary, s.usedPercent > 0 {
@ -2103,7 +2103,7 @@ private struct CodexPlanInsight: View {
label: "\(limit.name) · \(s.windowLabel)",
percent: s.usedPercent,
resetsAt: s.resetsAt,
projection: nil
projection: pace(for: s)
)
}
}
@ -2126,6 +2126,25 @@ private struct CodexPlanInsight: View {
.padding(.bottom, 8)
}
/// Codex has no snapshot history yet, so this is the linear phase-1 pace
/// (#726): deficit/reserve everywhere, projection and ETA only where the
/// window is long enough for a whole-window rate to be defensible.
private func pace(for window: CodexUsage.Window) -> WindowProjection? {
guard let result = QuotaPace.evaluate(
usedPercent: window.usedPercent,
resetsAt: window.resetsAt,
windowSeconds: window.limitWindowSeconds
) else { return nil }
return WindowProjection(
percent: result.projectedPercent,
willOverflow: result.willOverflow,
hitsLimitAt: result.hitsLimitAt,
source: .linear,
deltaPercent: result.deltaPercent,
compact: TimeInterval(window.limitWindowSeconds) <= QuotaPace.etaSuppressionMaxSeconds
)
}
private func resetCreditsLabel(_ resets: CodexUsage.ResetCredits) -> String {
let count = "\(resets.availableCount) available"
guard let next = resets.nextExpiresAt else { return count }
@ -2145,6 +2164,12 @@ private struct WindowProjection {
let willOverflow: Bool
let hitsLimitAt: Date?
let source: Source
/// used% expected%; set by pace-aware callers (#726) to get the
/// deficit/reserve caption. Nil keeps the original caption wording.
var deltaPercent: Double? = nil
/// Stage-only caption: deficit/reserve without projection or ETA. Used on
/// short windows where a linear extrapolation isn't defensible.
var compact: Bool = false
}
private struct UtilizationRow: View {
@ -2211,6 +2236,21 @@ private struct ProjectionCaption: View {
private var captionText: String {
let projected = String(format: "%.0f%%", projection.percent)
// Deficit-first wording (#726): pace state up front, projection or
// ETA after it, and neither on windows flagged compact.
if let delta = projection.deltaPercent {
if abs(delta) <= 2 {
return projection.compact ? "On pace" : "On pace: \(projected) at reset"
}
let stage = delta > 0
? String(format: "%.0f%% in deficit", delta)
: String(format: "%.0f%% in reserve", -delta)
if projection.compact { return stage }
if projection.willOverflow, let hit = projection.hitsLimitAt {
return "\(stage) · hits 100% \(relativeReset(hit))"
}
return "\(stage) · \(projected) at reset"
}
switch projection.source {
case .linear:
if projection.willOverflow, let hit = projection.hitsLimitAt {

View file

@ -0,0 +1,110 @@
import Foundation
import XCTest
@testable import CodeBurnMenubar
final class QuotaPaceTests: XCTestCase {
private let now = Date(timeIntervalSince1970: 1_800_000_000)
private let week = 7 * 24 * 3600
private let fiveHours = 5 * 3600
/// resetsAt such that `fraction` of the window has elapsed at `now`.
private func resets(afterElapsedFraction fraction: Double, windowSeconds: Int) -> Date {
now.addingTimeInterval(TimeInterval(windowSeconds) * (1 - fraction))
}
func testOnPaceMidWindow() {
let r = QuotaPace.evaluate(
usedPercent: 50, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: week),
windowSeconds: week, now: now
)
XCTAssertEqual(r?.deltaPercent ?? -1, 0, accuracy: 0.001)
XCTAssertEqual(r?.projectedPercent ?? -1, 100, accuracy: 0.001)
XCTAssertEqual(r?.willOverflow, false)
XCTAssertNil(r?.hitsLimitAt)
}
func testDeficitOverflowWeeklyGetsETABeforeReset() {
let resetsAt = resets(afterElapsedFraction: 0.5, windowSeconds: week)
let r = QuotaPace.evaluate(usedPercent: 80, resetsAt: resetsAt, windowSeconds: week, now: now)
XCTAssertEqual(r?.deltaPercent ?? 0, 30, accuracy: 0.001)
XCTAssertEqual(r?.projectedPercent ?? 0, 160, accuracy: 0.001)
XCTAssertEqual(r?.willOverflow, true)
// At this pace: 80% in 3.5 days 100% at 4.375 days elapsed.
let expectedHit = now.addingTimeInterval(TimeInterval(week) * (0.5 * 100 / 80 - 0.5))
XCTAssertEqual(
r?.hitsLimitAt?.timeIntervalSince1970 ?? 0,
expectedHit.timeIntervalSince1970,
accuracy: 1
)
XCTAssertLessThan(r!.hitsLimitAt!, resetsAt)
}
func testReserveNoETA() {
let r = QuotaPace.evaluate(
usedPercent: 20, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: week),
windowSeconds: week, now: now
)
XCTAssertEqual(r?.deltaPercent ?? 0, -30, accuracy: 0.001)
XCTAssertEqual(r?.projectedPercent ?? 0, 40, accuracy: 0.001)
XCTAssertEqual(r?.willOverflow, false)
XCTAssertNil(r?.hitsLimitAt)
}
func testShortWindowOverflowSuppressesETAButKeepsDeficit() {
let r = QuotaPace.evaluate(
usedPercent: 90, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: fiveHours),
windowSeconds: fiveHours, now: now
)
XCTAssertEqual(r?.deltaPercent ?? 0, 40, accuracy: 0.001)
XCTAssertEqual(r?.willOverflow, true)
XCTAssertNil(r?.hitsLimitAt, "5h window must not show a linear run-out ETA")
}
func testEarlyWindowShowsNothing() {
XCTAssertNil(QuotaPace.evaluate(
usedPercent: 5, resetsAt: resets(afterElapsedFraction: 0.02, windowSeconds: week),
windowSeconds: week, now: now
))
}
func testSkewGuards() {
// Reset in the past.
XCTAssertNil(QuotaPace.evaluate(
usedPercent: 50, resetsAt: now.addingTimeInterval(-60), windowSeconds: week, now: now
))
// Reset further out than one full window.
XCTAssertNil(QuotaPace.evaluate(
usedPercent: 50, resetsAt: now.addingTimeInterval(TimeInterval(week) + 3600),
windowSeconds: week, now: now
))
// Missing reset or nonsense window length.
XCTAssertNil(QuotaPace.evaluate(usedPercent: 50, resetsAt: nil, windowSeconds: week, now: now))
XCTAssertNil(QuotaPace.evaluate(
usedPercent: 50, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: week),
windowSeconds: 0, now: now
))
}
func testExhaustedWindowShowsNothing() {
XCTAssertNil(QuotaPace.evaluate(
usedPercent: 100, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: week),
windowSeconds: week, now: now
))
// Over-100 inputs clamp to exhausted, same silence.
XCTAssertNil(QuotaPace.evaluate(
usedPercent: 130, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: week),
windowSeconds: week, now: now
))
}
func testZeroUsageMidWindowIsAllReserve() {
let r = QuotaPace.evaluate(
usedPercent: 0, resetsAt: resets(afterElapsedFraction: 0.5, windowSeconds: week),
windowSeconds: week, now: now
)
XCTAssertEqual(r?.deltaPercent ?? 0, -50, accuracy: 0.001)
XCTAssertEqual(r?.projectedPercent ?? -1, 0, accuracy: 0.001)
XCTAssertEqual(r?.willOverflow, false)
XCTAssertNil(r?.hitsLimitAt)
}
}