Files
2026-07-13 12:22:33 +08:00

976 lines
38 KiB
Swift

import CodexBarCore
import Foundation
enum HistoricalUsageWindowKind: String, Codable {
case secondary
}
enum HistoricalUsageRecordSource: String, Codable {
case live
case backfill
}
struct HistoricalUsageRecord: Codable {
let v: Int
let provider: UsageProvider
let windowKind: HistoricalUsageWindowKind
let source: HistoricalUsageRecordSource
let accountKey: String?
let sampledAt: Date
let usedPercent: Double
let resetsAt: Date
let windowMinutes: Int
init(
v: Int,
provider: UsageProvider,
windowKind: HistoricalUsageWindowKind,
source: HistoricalUsageRecordSource,
accountKey: String?,
sampledAt: Date,
usedPercent: Double,
resetsAt: Date,
windowMinutes: Int)
{
self.v = v
self.provider = provider
self.windowKind = windowKind
self.source = source
self.accountKey = accountKey
self.sampledAt = sampledAt
self.usedPercent = usedPercent
self.resetsAt = resetsAt
self.windowMinutes = windowMinutes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.v = try container.decodeIfPresent(Int.self, forKey: .v) ?? 1
self.provider = try container.decode(UsageProvider.self, forKey: .provider)
self.windowKind = try container.decode(HistoricalUsageWindowKind.self, forKey: .windowKind)
self.source = try container.decodeIfPresent(HistoricalUsageRecordSource.self, forKey: .source) ?? .live
self.accountKey = try container.decodeIfPresent(String.self, forKey: .accountKey)
self.sampledAt = try container.decode(Date.self, forKey: .sampledAt)
self.usedPercent = try container.decode(Double.self, forKey: .usedPercent)
self.resetsAt = try container.decode(Date.self, forKey: .resetsAt)
self.windowMinutes = try container.decode(Int.self, forKey: .windowMinutes)
}
}
struct HistoricalWeekProfile {
let resetsAt: Date
let windowMinutes: Int
let curve: [Double]
}
struct CodexHistoricalDataset {
static let gridPointCount = 169
let weeks: [HistoricalWeekProfile]
}
actor HistoricalUsageHistoryStore {
private static let schemaVersion = 1
private static let writeInterval: TimeInterval = 30 * 60
private static let writeDeltaThreshold: Double = 1
private static let retentionDays: TimeInterval = 56 * 24 * 60 * 60
private static let minimumWeekSamples = 6
private static let boundaryCoverageWindow: TimeInterval = 24 * 60 * 60
private static let backfillWindowCapWeeks = 8
private static let backfillCalibrationMinimumUsedPercent = 1.0
private static let backfillCalibrationMinimumCredits = 0.001
private static let backfillSampleFractions: [Double] = (0...14).map { Double($0) / 14.0 }
private static let coverageTolerance: TimeInterval = 16 * 60 * 60
private static let resetBucketSeconds: TimeInterval = 5 * 60
private let fileURL: URL
private var records: [HistoricalUsageRecord] = []
private var loaded = false
init(fileURL: URL? = nil) {
self.fileURL = fileURL ?? HistoricalUsageHistoryStore.defaultFileURL()
}
func loadCodexDataset(accountKey: String?) -> CodexHistoricalDataset? {
self.ensureLoaded()
return self.buildDataset(accountKey: accountKey)
}
func loadCodexDataset(
canonicalAccountKey: String?,
canonicalEmailHashKey: String?,
legacyEmailHash: String?,
hasAdjacentMultiAccountVeto: Bool) -> CodexHistoricalDataset?
{
self.ensureLoaded()
return self.buildDataset(
canonicalAccountKey: canonicalAccountKey,
canonicalEmailHashKey: canonicalEmailHashKey,
legacyEmailHash: legacyEmailHash,
hasAdjacentMultiAccountVeto: hasAdjacentMultiAccountVeto)
}
func recordCodexWeekly(
window: RateWindow,
sampledAt: Date = .init(),
accountKey: String?) -> CodexHistoricalDataset?
{
guard let rawResetsAt = window.resetsAt else { return self.loadCodexDataset(accountKey: accountKey) }
guard let windowMinutes = window.windowMinutes, windowMinutes > 0 else {
return self.loadCodexDataset(accountKey: accountKey)
}
self.ensureLoaded()
let resetsAt = Self.normalizeReset(rawResetsAt)
let sample = HistoricalUsageRecord(
v: Self.schemaVersion,
provider: .codex,
windowKind: .secondary,
source: .live,
accountKey: accountKey,
sampledAt: sampledAt,
usedPercent: Self.clamp(window.usedPercent, lower: 0, upper: 100),
resetsAt: resetsAt,
windowMinutes: windowMinutes)
if !self.shouldAccept(sample) {
return self.buildDataset(accountKey: accountKey)
}
self.records.append(sample)
self.pruneOldRecords(now: sampledAt)
self.records.sort { lhs, rhs in
if lhs.sampledAt == rhs.sampledAt {
if lhs.resetsAt == rhs.resetsAt {
return lhs.usedPercent < rhs.usedPercent
}
return lhs.resetsAt < rhs.resetsAt
}
return lhs.sampledAt < rhs.sampledAt
}
self.persist()
return self.buildDataset(accountKey: accountKey)
}
func backfillCodexWeeklyFromUsageBreakdown(
_ breakdown: [OpenAIDashboardDailyBreakdown],
referenceWindow: RateWindow,
now: Date = .init(),
accountKey: String?) -> CodexHistoricalDataset?
{
self.ensureLoaded()
let existingDataset = self.buildDataset(accountKey: accountKey)
guard let rawResetsAt = referenceWindow.resetsAt else { return existingDataset }
guard let windowMinutes = referenceWindow.windowMinutes, windowMinutes > 0 else { return existingDataset }
let resetsAt = Self.normalizeReset(rawResetsAt)
let duration = TimeInterval(windowMinutes) * 60
guard duration > 0 else { return existingDataset }
let windowStart = resetsAt.addingTimeInterval(-duration)
let calibrationEnd = Self.clampDate(now, lower: windowStart, upper: resetsAt)
let dayUsages = Self.parseDayUsages(
from: breakdown,
asOf: calibrationEnd,
fillingFrom: windowStart)
guard !dayUsages.isEmpty else { return existingDataset }
guard let coverageStart = dayUsages.first?.start, let coverageEnd = dayUsages.last?.end else {
return existingDataset
}
guard coverageStart <= windowStart.addingTimeInterval(Self.coverageTolerance) else {
return existingDataset
}
guard coverageEnd >= calibrationEnd.addingTimeInterval(-Self.coverageTolerance) else {
return existingDataset
}
let currentUsedPercent = Self.clamp(referenceWindow.usedPercent, lower: 0, upper: 100)
guard currentUsedPercent >= Self.backfillCalibrationMinimumUsedPercent else { return existingDataset }
let currentCredits = Self.creditsUsed(
from: dayUsages,
between: windowStart,
and: calibrationEnd)
guard currentCredits > Self.backfillCalibrationMinimumCredits else { return existingDataset }
let estimatedCreditsAtLimit = currentCredits / (currentUsedPercent / 100)
guard estimatedCreditsAtLimit.isFinite, estimatedCreditsAtLimit > Self.backfillCalibrationMinimumCredits else {
return existingDataset
}
struct RecordKey: Hashable {
let resetsAt: Date
let sampledAt: Date
let windowMinutes: Int
let accountKey: String?
}
var synthesized: [HistoricalUsageRecord] = []
synthesized.reserveCapacity(Self.backfillWindowCapWeeks * Self.backfillSampleFractions.count)
for weeksBack in 1...Self.backfillWindowCapWeeks {
let reset = Self.normalizeReset(resetsAt.addingTimeInterval(-duration * Double(weeksBack)))
let start = reset.addingTimeInterval(-duration)
guard start >= coverageStart.addingTimeInterval(-Self.coverageTolerance),
reset <= coverageEnd.addingTimeInterval(Self.coverageTolerance)
else {
continue
}
let existingForWeek = self.records.filter {
$0.provider == .codex &&
$0.windowKind == .secondary &&
$0.windowMinutes == windowMinutes &&
$0.accountKey == accountKey &&
$0.resetsAt == reset
}
if Self.isCompleteWeek(samples: existingForWeek, windowStart: start, resetsAt: reset) {
continue
}
var existingRecordKeys = Set(existingForWeek.map {
RecordKey(
resetsAt: $0.resetsAt,
sampledAt: $0.sampledAt,
windowMinutes: $0.windowMinutes,
accountKey: $0.accountKey)
})
let weekCredits = Self.creditsUsed(from: dayUsages, between: start, and: reset)
guard weekCredits > Self.backfillCalibrationMinimumCredits else { continue }
for fraction in Self.backfillSampleFractions {
let sampledAt = start.addingTimeInterval(duration * fraction)
let recordKey = RecordKey(
resetsAt: reset,
sampledAt: sampledAt,
windowMinutes: windowMinutes,
accountKey: accountKey)
guard !existingRecordKeys.contains(recordKey) else { continue }
let cumulativeCredits = Self.creditsUsed(from: dayUsages, between: start, and: sampledAt)
let usedPercent = Self.clamp((cumulativeCredits / estimatedCreditsAtLimit) * 100, lower: 0, upper: 100)
synthesized.append(HistoricalUsageRecord(
v: Self.schemaVersion,
provider: .codex,
windowKind: .secondary,
source: .backfill,
accountKey: accountKey,
sampledAt: sampledAt,
usedPercent: usedPercent,
resetsAt: reset,
windowMinutes: windowMinutes))
existingRecordKeys.insert(recordKey)
}
}
guard !synthesized.isEmpty else { return existingDataset }
self.records.append(contentsOf: synthesized)
self.pruneOldRecords(now: now)
self.records.sort { lhs, rhs in
if lhs.sampledAt == rhs.sampledAt {
if lhs.resetsAt == rhs.resetsAt {
return lhs.usedPercent < rhs.usedPercent
}
return lhs.resetsAt < rhs.resetsAt
}
return lhs.sampledAt < rhs.sampledAt
}
self.persist()
return self.buildDataset(accountKey: accountKey)
}
private func shouldAccept(_ sample: HistoricalUsageRecord) -> Bool {
guard let prior = self.records
.last(where: {
$0.provider == sample.provider &&
$0.windowKind == sample.windowKind &&
$0.accountKey == sample.accountKey &&
$0.windowMinutes == sample.windowMinutes
})
else {
return true
}
if prior.resetsAt != sample.resetsAt { return true }
if sample.sampledAt.timeIntervalSince(prior.sampledAt) >= Self.writeInterval { return true }
if abs(sample.usedPercent - prior.usedPercent) >= Self.writeDeltaThreshold { return true }
return false
}
private func pruneOldRecords(now: Date) {
let cutoff = now.addingTimeInterval(-Self.retentionDays)
self.records.removeAll { $0.sampledAt < cutoff }
}
private func ensureLoaded() {
guard !self.loaded else { return }
self.loaded = true
self.records = self.readRecordsFromDisk()
self.pruneOldRecords(now: .init())
}
private func readRecordsFromDisk() -> [HistoricalUsageRecord] {
guard let data = try? Data(contentsOf: self.fileURL), !data.isEmpty else { return [] }
guard let text = String(data: data, encoding: .utf8) else { return [] }
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
var decoded: [HistoricalUsageRecord] = []
decoded.reserveCapacity(text.count / 80)
for rawLine in text.split(separator: "\n", omittingEmptySubsequences: true) {
let line = String(rawLine).trimmingCharacters(in: .whitespacesAndNewlines)
guard !line.isEmpty, let lineData = line.data(using: .utf8) else { continue }
guard var record = try? decoder.decode(HistoricalUsageRecord.self, from: lineData) else { continue }
record = HistoricalUsageRecord(
v: record.v,
provider: record.provider,
windowKind: record.windowKind,
source: record.source,
accountKey: record.accountKey?.isEmpty == false ? record.accountKey : nil,
sampledAt: record.sampledAt,
usedPercent: Self.clamp(record.usedPercent, lower: 0, upper: 100),
resetsAt: Self.normalizeReset(record.resetsAt),
windowMinutes: record.windowMinutes)
decoded.append(record)
}
return decoded
}
private func persist() {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.sortedKeys]
var lines: [String] = []
lines.reserveCapacity(self.records.count)
for record in self.records {
guard let data = try? encoder.encode(record),
let line = String(data: data, encoding: .utf8)
else {
continue
}
lines.append(line)
}
let payload = (lines.joined(separator: "\n") + "\n").data(using: .utf8) ?? Data()
let directory = self.fileURL.deletingLastPathComponent()
do {
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
try payload.write(to: self.fileURL, options: [.atomic])
} catch {
// Best-effort cache file; ignore write failures.
}
}
private func buildDataset(accountKey: String?) -> CodexHistoricalDataset? {
let scoped = self.records.filter { record in
guard Self.isCodexSecondaryRecord(record) else { return false }
if let accountKey {
return record.accountKey == accountKey
}
return record.accountKey == nil
}
return self.buildDataset(from: scoped)
}
private func buildDataset(
canonicalAccountKey: String?,
canonicalEmailHashKey: String?,
legacyEmailHash: String?,
hasAdjacentMultiAccountVeto: Bool) -> CodexHistoricalDataset?
{
guard let canonicalAccountKey else {
return self.buildDataset(accountKey: nil)
}
let shouldIncludeUnscoped = CodexHistoryOwnership.hasStrictSingleAccountContinuity(
scopedRawKeys: Self.scopedRawKeysRelevantToCodexUnscopedHistory(self.records),
targetCanonicalKey: canonicalAccountKey,
canonicalEmailHashKey: canonicalEmailHashKey,
legacyEmailHash: legacyEmailHash,
hasAdjacentMultiAccountVeto: hasAdjacentMultiAccountVeto)
let scoped = self.records.filter { record in
guard Self.isCodexSecondaryRecord(record) else { return false }
guard let rawKey = record.accountKey else {
return shouldIncludeUnscoped
}
let owner = CodexHistoryOwnership.classifyPersistedKey(rawKey, legacyEmailHash: legacyEmailHash)
return CodexHistoryOwnership.belongsToTargetContinuity(
owner,
targetCanonicalKey: canonicalAccountKey,
canonicalEmailHashKey: canonicalEmailHashKey)
}
return self.buildDataset(from: scoped)
}
private func buildDataset(from scoped: [HistoricalUsageRecord]) -> CodexHistoricalDataset? {
struct WeekKey: Hashable {
let resetsAt: Date
let windowMinutes: Int
}
if scoped.isEmpty { return nil }
let grouped = Dictionary(grouping: scoped) {
WeekKey(resetsAt: $0.resetsAt, windowMinutes: $0.windowMinutes)
}
var weeks: [HistoricalWeekProfile] = []
weeks.reserveCapacity(grouped.count)
for (key, samples) in grouped {
let duration = TimeInterval(key.windowMinutes) * 60
guard duration > 0 else { continue }
let windowStart = key.resetsAt.addingTimeInterval(-duration)
guard Self.isCompleteWeek(samples: samples, windowStart: windowStart, resetsAt: key.resetsAt) else {
continue
}
guard let curve = Self.reconstructWeekCurve(
samples: samples,
windowStart: windowStart,
windowDuration: duration,
gridPointCount: CodexHistoricalDataset.gridPointCount)
else {
continue
}
weeks.append(HistoricalWeekProfile(
resetsAt: key.resetsAt,
windowMinutes: key.windowMinutes,
curve: curve))
}
weeks.sort { $0.resetsAt < $1.resetsAt }
if weeks.isEmpty { return nil }
return CodexHistoricalDataset(weeks: weeks)
}
private nonisolated static func isCodexSecondaryRecord(_ record: HistoricalUsageRecord) -> Bool {
record.provider == .codex && record.windowKind == .secondary && record.windowMinutes > 0
}
private static func reconstructWeekCurve(
samples: [HistoricalUsageRecord],
windowStart: Date,
windowDuration: TimeInterval,
gridPointCount: Int) -> [Double]?
{
guard gridPointCount >= 2 else { return nil }
var points = samples.map { sample -> (u: Double, value: Double) in
let offset = sample.sampledAt.timeIntervalSince(windowStart)
let u = Self.clamp(offset / windowDuration, lower: 0, upper: 1)
return (u: u, value: Self.clamp(sample.usedPercent, lower: 0, upper: 100))
}
points.sort { lhs, rhs in
if lhs.u == rhs.u {
return lhs.value < rhs.value
}
return lhs.u < rhs.u
}
guard !points.isEmpty else { return nil }
// Enforce monotonicity on observed samples before interpolation.
var monotonePoints: [(u: Double, value: Double)] = []
monotonePoints.reserveCapacity(points.count)
var runningMax = 0.0
for point in points {
runningMax = max(runningMax, point.value)
monotonePoints.append((u: point.u, value: runningMax))
}
// Anchor reconstructed curves to reset start and end-of-week plateau.
let endValue = monotonePoints.last?.value ?? 0
monotonePoints.append((u: 0, value: 0))
monotonePoints.append((u: 1, value: endValue))
monotonePoints.sort { lhs, rhs in
if lhs.u == rhs.u {
return lhs.value < rhs.value
}
return lhs.u < rhs.u
}
runningMax = 0
for index in monotonePoints.indices {
runningMax = max(runningMax, monotonePoints[index].value)
monotonePoints[index].value = runningMax
}
var curve = Array(repeating: 0.0, count: gridPointCount)
let first = monotonePoints[0]
let last = monotonePoints[monotonePoints.count - 1]
var upperIndex = 1
let denominator = Double(gridPointCount - 1)
for index in 0..<gridPointCount {
let u = Double(index) / denominator
if u <= first.u {
curve[index] = first.value
continue
}
if u >= last.u {
curve[index] = last.value
continue
}
while upperIndex < monotonePoints.count, monotonePoints[upperIndex].u < u {
upperIndex += 1
}
let hi = monotonePoints[min(upperIndex, monotonePoints.count - 1)]
let lo = monotonePoints[max(0, upperIndex - 1)]
if hi.u <= lo.u {
curve[index] = max(lo.value, hi.value)
continue
}
let ratio = Self.clamp((u - lo.u) / (hi.u - lo.u), lower: 0, upper: 1)
curve[index] = lo.value + (hi.value - lo.value) * ratio
}
// Re-enforce monotonicity on reconstructed grid.
var curveMax = 0.0
for index in curve.indices {
curve[index] = Self.clamp(curve[index], lower: 0, upper: 100)
curveMax = max(curveMax, curve[index])
curve[index] = curveMax
}
return curve
}
private static func isCompleteWeek(samples: [HistoricalUsageRecord], windowStart: Date, resetsAt: Date) -> Bool {
guard samples.count >= self.minimumWeekSamples else { return false }
let startBoundary = windowStart.addingTimeInterval(Self.boundaryCoverageWindow)
let endBoundary = resetsAt.addingTimeInterval(-Self.boundaryCoverageWindow)
let hasStartCoverage = samples.contains { sample in
sample.sampledAt >= windowStart && sample.sampledAt <= startBoundary
}
let hasEndCoverage = samples.contains { sample in
sample.sampledAt >= endBoundary && sample.sampledAt <= resetsAt
}
return hasStartCoverage && hasEndCoverage
}
private nonisolated static func scopedRawKeysRelevantToCodexUnscopedHistory(
_ records: [HistoricalUsageRecord]) -> [String]
{
let unscopedRecords = records.filter { record in
Self.isCodexSecondaryRecord(record) && record.accountKey == nil
}
guard let continuityWindow = self.historicalContinuityWindow(for: unscopedRecords) else {
return []
}
return records.compactMap { record in
guard Self.isCodexSecondaryRecord(record),
let accountKey = record.accountKey,
continuityWindow.contains(record.sampledAt)
else {
return nil
}
return accountKey
}
}
private nonisolated static func historicalContinuityWindow(
for records: [HistoricalUsageRecord]) -> ClosedRange<Date>?
{
let sampledDates = records.map(\.sampledAt)
guard let lowerBound = sampledDates.min(),
let upperBound = sampledDates.max()
else {
return nil
}
let expansion = TimeInterval(records.map(\.windowMinutes).max() ?? 0) * 60
return lowerBound.addingTimeInterval(-expansion)...upperBound.addingTimeInterval(expansion)
}
private struct DayUsage {
let start: Date
let end: Date
let creditsUsed: Double
}
private static func parseDayUsages(
from breakdown: [OpenAIDashboardDailyBreakdown],
asOf: Date,
fillingFrom expectedCoverageStart: Date? = nil) -> [DayUsage]
{
var creditsByStart: [Date: Double] = [:]
creditsByStart.reserveCapacity(breakdown.count)
for day in breakdown {
guard let dayStart = Self.dayStart(for: day.day) else { continue }
creditsByStart[dayStart, default: 0] += max(0, day.totalCreditsUsed)
}
let calendar = Self.gregorianCalendar()
var dayUsages: [DayUsage] = []
dayUsages.reserveCapacity(creditsByStart.count)
for (dayStart, credits) in creditsByStart {
guard let nominalEnd = calendar.date(byAdding: .day, value: 1, to: dayStart) else { continue }
let effectiveEnd: Date = if dayStart <= asOf, asOf < nominalEnd {
asOf
} else {
nominalEnd
}
guard effectiveEnd > dayStart else { continue }
dayUsages.append(DayUsage(start: dayStart, end: effectiveEnd, creditsUsed: credits))
}
dayUsages.sort { lhs, rhs in lhs.start < rhs.start }
return Self.fillMissingZeroUsageDays(
in: dayUsages,
through: asOf,
fillingFrom: expectedCoverageStart)
}
private static func fillMissingZeroUsageDays(
in dayUsages: [DayUsage],
through asOf: Date,
fillingFrom expectedCoverageStart: Date? = nil) -> [DayUsage]
{
guard let firstStart = dayUsages.first?.start else { return [] }
let calendar = Self.gregorianCalendar()
let fillStart: Date = if let expectedCoverageStart {
min(firstStart, calendar.startOfDay(for: expectedCoverageStart))
} else {
firstStart
}
let finalDayStart = calendar.startOfDay(for: asOf)
guard fillStart <= finalDayStart else { return dayUsages }
let creditsByStart = Dictionary(uniqueKeysWithValues: dayUsages.map { ($0.start, $0.creditsUsed) })
let daySpan = max(0, calendar.dateComponents([.day], from: fillStart, to: finalDayStart).day ?? 0)
var filled: [DayUsage] = []
filled.reserveCapacity(daySpan + 1)
var cursor = fillStart
while cursor <= finalDayStart {
guard let nominalEnd = calendar.date(byAdding: .day, value: 1, to: cursor) else { break }
let effectiveEnd: Date = if cursor <= asOf, asOf < nominalEnd {
asOf
} else {
nominalEnd
}
guard effectiveEnd > cursor else { break }
filled.append(DayUsage(
start: cursor,
end: effectiveEnd,
creditsUsed: creditsByStart[cursor] ?? 0))
guard let next = calendar.date(byAdding: .day, value: 1, to: cursor) else { break }
cursor = next
}
return filled
}
private static func dayStart(for key: String) -> Date? {
let components = key.split(separator: "-", omittingEmptySubsequences: true)
guard components.count == 3,
let year = Int(components[0]),
let month = Int(components[1]),
let day = Int(components[2])
else {
return nil
}
let calendar = Self.gregorianCalendar()
var dateComponents = DateComponents()
dateComponents.calendar = calendar
dateComponents.timeZone = calendar.timeZone
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = 0
dateComponents.minute = 0
dateComponents.second = 0
return dateComponents.date
}
private static func creditsUsed(from dayUsages: [DayUsage], between start: Date, and end: Date) -> Double {
guard end > start else { return 0 }
var total = 0.0
for day in dayUsages {
if day.end <= start { continue }
if day.start >= end { break }
let overlapStart = max(day.start, start)
let overlapEnd = min(day.end, end)
guard overlapEnd > overlapStart else { continue }
let dayDuration = day.end.timeIntervalSince(day.start)
guard dayDuration > 0 else { continue }
let overlap = overlapEnd.timeIntervalSince(overlapStart)
total += day.creditsUsed * (overlap / dayDuration)
}
return max(0, total)
}
nonisolated static func defaultFileURL() -> URL {
let root = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
?? FileManager.default.homeDirectoryForCurrentUser
return root
.appendingPathComponent("CodexBar", isDirectory: true)
.appendingPathComponent("usage-history.jsonl", isDirectory: false)
}
private nonisolated static func clamp(_ value: Double, lower: Double, upper: Double) -> Double {
min(upper, max(lower, value))
}
private nonisolated static func clampDate(_ value: Date, lower: Date, upper: Date) -> Date {
min(upper, max(lower, value))
}
private nonisolated static func normalizeReset(_ value: Date) -> Date {
let bucket = Self.resetBucketSeconds
guard bucket > 0 else { return value }
let rounded = (value.timeIntervalSinceReferenceDate / bucket).rounded() * bucket
return Date(timeIntervalSinceReferenceDate: rounded)
}
private nonisolated static func gregorianCalendar() -> Calendar {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone.current
return calendar
}
#if DEBUG
nonisolated static func _dayStartForTesting(_ key: String) -> Date? {
self.dayStart(for: key)
}
nonisolated static func _creditsUsedForTesting(
breakdown: [OpenAIDashboardDailyBreakdown],
asOf: Date,
start: Date,
end: Date) -> Double
{
let dayUsages = Self.parseDayUsages(from: breakdown, asOf: asOf)
return Self.creditsUsed(from: dayUsages, between: start, and: end)
}
#endif
}
enum CodexHistoricalPaceEvaluator {
static let minimumCompleteWeeksForHistorical = 3
static let minimumWeeksForRisk = 5
private static let recencyTauWeeks: Double = 3
private static let epsilon: Double = 1e-9
private static let resetBucketSeconds: TimeInterval = 5 * 60
static func evaluate(window: RateWindow, now: Date, dataset: CodexHistoricalDataset?) -> UsagePace? {
guard let dataset else { return nil }
guard let resetsAt = window.resetsAt else { return nil }
let minutes = window.windowMinutes ?? 10080
guard minutes > 0 else { return nil }
let duration = TimeInterval(minutes) * 60
guard duration > 0 else { return nil }
let timeUntilReset = resetsAt.timeIntervalSince(now)
guard timeUntilReset > 0, timeUntilReset <= duration else { return nil }
let normalizedResetsAt = Self.normalizeReset(resetsAt)
let elapsed = Self.clamp(duration - timeUntilReset, lower: 0, upper: duration)
let actual = Self.clamp(window.usedPercent, lower: 0, upper: 100)
if elapsed == 0, actual > 0 { return nil }
let uNow = Self.clamp(elapsed / duration, lower: 0, upper: 1)
let scopedWeeks = dataset.weeks.filter { week in
week.windowMinutes == minutes && week.resetsAt < normalizedResetsAt
}
guard scopedWeeks.count >= Self.minimumCompleteWeeksForHistorical else { return nil }
let weightedWeeks = scopedWeeks.map { week in
let ageWeeks = Self.clamp(
normalizedResetsAt.timeIntervalSince(week.resetsAt) / duration,
lower: 0,
upper: Double.greatestFiniteMagnitude)
let weight = exp(-ageWeeks / Self.recencyTauWeeks)
return (week: week, weight: weight)
}
let totalWeight = weightedWeeks.reduce(0.0) { $0 + $1.weight }
guard totalWeight > Self.epsilon else { return nil }
let totalWeightSquared = weightedWeeks.reduce(0.0) { $0 + ($1.weight * $1.weight) }
let nEff = totalWeightSquared > Self.epsilon ? (totalWeight * totalWeight) / totalWeightSquared : 0
let lambda = Self.clamp((nEff - 2) / 6, lower: 0, upper: 1)
let gridCount = CodexHistoricalDataset.gridPointCount
let denominator = Double(gridCount - 1)
var expectedCurve = Array(repeating: 0.0, count: gridCount)
for index in 0..<gridCount {
let u = Double(index) / denominator
let values = weightedWeeks.map { $0.week.curve[index] }
let weights = weightedWeeks.map(\.weight)
let historicalMedian = Self.weightedMedian(values: values, weights: weights)
let linearBaseline = 100 * u
// Historical demand can exceed a sustainable quota pace. Never call that excess a reserve.
expectedCurve[index] = Self.clamp(
(lambda * historicalMedian) + ((1 - lambda) * linearBaseline),
lower: 0,
upper: linearBaseline)
}
// Expected cumulative usage should be monotone.
var runningExpected = 0.0
for index in expectedCurve.indices {
runningExpected = max(runningExpected, expectedCurve[index])
expectedCurve[index] = runningExpected
}
let expectedNow = Self.interpolate(curve: expectedCurve, at: uNow)
var weightedRunOutMass = 0.0
var crossingCandidates: [(etaSeconds: TimeInterval, weight: Double)] = []
crossingCandidates.reserveCapacity(weightedWeeks.count)
for weighted in weightedWeeks {
var extendedCurve = weighted.week.curve
if let capIndex = extendedCurve.firstIndex(where: { $0 >= 100 - Self.epsilon }),
capIndex > 0, capIndex < extendedCurve.count - 1
{
let gridCount = CodexHistoricalDataset.gridPointCount
let uCap = Double(capIndex) / Double(gridCount - 1)
let valCap = extendedCurve[capIndex]
let slope: Double = valCap / uCap
for i in capIndex..<extendedCurve.count {
let u = Double(i) / Double(gridCount - 1)
extendedCurve[i] = slope * u
}
}
let weight = weighted.weight
let weekNow = Self.interpolate(curve: extendedCurve, at: uNow)
let shift = actual - weekNow
let shiftedEnd = (extendedCurve.last ?? 0) + shift
let runOut = shiftedEnd >= 100 - Self.epsilon
if runOut {
weightedRunOutMass += weight
if let crossingU = Self.firstCrossing(
after: uNow,
curve: extendedCurve,
shift: shift,
actualAtNow: actual)
{
let etaSeconds = max(0, (crossingU - uNow) * duration)
crossingCandidates.append((etaSeconds: etaSeconds, weight: weight))
}
}
}
let smoothedProbability = Self.clamp(
(weightedRunOutMass + 0.5) / (totalWeight + 1),
lower: 0,
upper: 1)
var runOutProbability: Double? = scopedWeeks.count >= Self.minimumWeeksForRisk ? smoothedProbability : nil
var willLastToReset = smoothedProbability < 0.5
var etaSeconds: TimeInterval?
if actual >= 100 {
willLastToReset = false
etaSeconds = 0
runOutProbability = 1
} else if !willLastToReset {
let values = crossingCandidates.map(\.etaSeconds)
let weights = crossingCandidates.map(\.weight)
if values.isEmpty {
willLastToReset = true
} else {
etaSeconds = max(0, Self.weightedMedian(values: values, weights: weights))
}
}
return UsagePace.historical(
expectedUsedPercent: expectedNow,
actualUsedPercent: actual,
etaSeconds: etaSeconds,
willLastToReset: willLastToReset,
runOutProbability: runOutProbability,
projectedRemainingUsage: max(0, (expectedCurve.last ?? expectedNow) - expectedNow))
}
private static func firstCrossing(
after uNow: Double,
curve: [Double],
shift: Double,
actualAtNow: Double) -> Double?
{
let gridCount = curve.count
guard gridCount >= 2 else { return nil }
let denominator = Double(gridCount - 1)
var previousU = uNow
var previousValue = actualAtNow
let startIndex = min(gridCount - 1, max(1, Int(floor(uNow * denominator)) + 1))
for index in startIndex..<gridCount {
let u = Double(index) / denominator
if u <= uNow + Self.epsilon { continue }
let value = Self.clamp(curve[index] + shift, lower: 0, upper: 100)
if previousValue < 100 - Self.epsilon, value >= 100 - Self.epsilon {
let delta = value - previousValue
if abs(delta) <= Self.epsilon { return u }
let ratio = Self.clamp((100 - previousValue) / delta, lower: 0, upper: 1)
return Self.clamp(previousU + ratio * (u - previousU), lower: uNow, upper: 1)
}
previousU = u
previousValue = value
}
return nil
}
private static func interpolate(curve: [Double], at u: Double) -> Double {
guard !curve.isEmpty else { return 0 }
if curve.count == 1 { return curve[0] }
let clipped = Self.clamp(u, lower: 0, upper: 1)
let scaled = clipped * Double(curve.count - 1)
let lower = Int(floor(scaled))
let upper = min(curve.count - 1, lower + 1)
if lower == upper { return curve[lower] }
let ratio = scaled - Double(lower)
return curve[lower] + ((curve[upper] - curve[lower]) * ratio)
}
private static func weightedMedian(values: [Double], weights: [Double]) -> Double {
guard values.count == weights.count, !values.isEmpty else { return 0 }
let pairs = zip(values, weights)
.map { (value: $0, weight: max(0, $1)) }
.sorted { lhs, rhs in lhs.value < rhs.value }
let totalWeight = pairs.reduce(0.0) { $0 + $1.weight }
if totalWeight <= Self.epsilon {
let sortedValues = values.sorted()
return sortedValues[sortedValues.count / 2]
}
let threshold = totalWeight / 2
var cumulative = 0.0
for pair in pairs {
cumulative += pair.weight
if cumulative >= threshold {
return pair.value
}
}
return pairs.last?.value ?? 0
}
private static func clamp(_ value: Double, lower: Double, upper: Double) -> Double {
min(upper, max(lower, value))
}
private static func normalizeReset(_ value: Date) -> Date {
let bucket = Self.resetBucketSeconds
guard bucket > 0 else { return value }
let rounded = (value.timeIntervalSinceReferenceDate / bucket).rounded() * bucket
return Date(timeIntervalSinceReferenceDate: rounded)
}
}