chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
import Foundation
|
||||
|
||||
public struct ClaudeAdminAPIUsageSnapshot: Codable, Equatable, Sendable {
|
||||
public struct DailyBucket: Codable, Equatable, Sendable, Identifiable {
|
||||
public let day: String
|
||||
public let startTime: Date
|
||||
public let endTime: Date
|
||||
public let costUSD: Double
|
||||
public let inputTokens: Int
|
||||
public let cacheCreationInputTokens: Int
|
||||
public let cacheReadInputTokens: Int
|
||||
public let outputTokens: Int
|
||||
public let totalTokens: Int
|
||||
public let costItems: [CostBreakdown]
|
||||
public let models: [ModelBreakdown]
|
||||
|
||||
public var id: String {
|
||||
self.day
|
||||
}
|
||||
|
||||
public init(
|
||||
day: String,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
costUSD: Double,
|
||||
inputTokens: Int,
|
||||
cacheCreationInputTokens: Int,
|
||||
cacheReadInputTokens: Int,
|
||||
outputTokens: Int,
|
||||
totalTokens: Int,
|
||||
costItems: [CostBreakdown],
|
||||
models: [ModelBreakdown])
|
||||
{
|
||||
self.day = day
|
||||
self.startTime = startTime
|
||||
self.endTime = endTime
|
||||
self.costUSD = costUSD
|
||||
self.inputTokens = inputTokens
|
||||
self.cacheCreationInputTokens = cacheCreationInputTokens
|
||||
self.cacheReadInputTokens = cacheReadInputTokens
|
||||
self.outputTokens = outputTokens
|
||||
self.totalTokens = totalTokens
|
||||
self.costItems = costItems
|
||||
self.models = models
|
||||
}
|
||||
}
|
||||
|
||||
public struct CostBreakdown: Codable, Equatable, Sendable, Identifiable {
|
||||
public let name: String
|
||||
public let costUSD: Double
|
||||
|
||||
public var id: String {
|
||||
self.name
|
||||
}
|
||||
|
||||
public init(name: String, costUSD: Double) {
|
||||
self.name = name
|
||||
self.costUSD = costUSD
|
||||
}
|
||||
}
|
||||
|
||||
public struct ModelBreakdown: Codable, Equatable, Sendable, Identifiable {
|
||||
public let name: String
|
||||
public let inputTokens: Int
|
||||
public let cacheCreationInputTokens: Int
|
||||
public let cacheReadInputTokens: Int
|
||||
public let outputTokens: Int
|
||||
public let totalTokens: Int
|
||||
|
||||
public var id: String {
|
||||
self.name
|
||||
}
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
inputTokens: Int,
|
||||
cacheCreationInputTokens: Int,
|
||||
cacheReadInputTokens: Int,
|
||||
outputTokens: Int,
|
||||
totalTokens: Int)
|
||||
{
|
||||
self.name = name
|
||||
self.inputTokens = inputTokens
|
||||
self.cacheCreationInputTokens = cacheCreationInputTokens
|
||||
self.cacheReadInputTokens = cacheReadInputTokens
|
||||
self.outputTokens = outputTokens
|
||||
self.totalTokens = totalTokens
|
||||
}
|
||||
}
|
||||
|
||||
public struct Summary: Equatable, Sendable {
|
||||
public let costUSD: Double
|
||||
public let inputTokens: Int
|
||||
public let cacheCreationInputTokens: Int
|
||||
public let cacheReadInputTokens: Int
|
||||
public let outputTokens: Int
|
||||
public let totalTokens: Int
|
||||
|
||||
public init(
|
||||
costUSD: Double,
|
||||
inputTokens: Int,
|
||||
cacheCreationInputTokens: Int,
|
||||
cacheReadInputTokens: Int,
|
||||
outputTokens: Int,
|
||||
totalTokens: Int)
|
||||
{
|
||||
self.costUSD = costUSD
|
||||
self.inputTokens = inputTokens
|
||||
self.cacheCreationInputTokens = cacheCreationInputTokens
|
||||
self.cacheReadInputTokens = cacheReadInputTokens
|
||||
self.outputTokens = outputTokens
|
||||
self.totalTokens = totalTokens
|
||||
}
|
||||
}
|
||||
|
||||
public let daily: [DailyBucket]
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(daily: [DailyBucket], updatedAt: Date) {
|
||||
self.daily = daily.sorted { $0.startTime < $1.startTime }
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
public var last30Days: Summary {
|
||||
self.summary(days: 30)
|
||||
}
|
||||
|
||||
public var last7Days: Summary {
|
||||
self.summary(days: 7)
|
||||
}
|
||||
|
||||
public var currentDay: Summary {
|
||||
self.summary(forLocalDayContaining: self.updatedAt)
|
||||
}
|
||||
|
||||
public var latestDay: Summary {
|
||||
self.summary(days: 1)
|
||||
}
|
||||
|
||||
public func summary(forLocalDayContaining date: Date, calendar _: Calendar = .current) -> Summary {
|
||||
let selected = self.daily.filter { bucket in
|
||||
CostUsageBucketInterval.contains(
|
||||
date,
|
||||
startTime: bucket.startTime,
|
||||
endTime: bucket.endTime)
|
||||
}
|
||||
return Summary(
|
||||
costUSD: selected.reduce(0) { $0 + $1.costUSD },
|
||||
inputTokens: selected.reduce(0) { $0 + $1.inputTokens },
|
||||
cacheCreationInputTokens: selected.reduce(0) { $0 + $1.cacheCreationInputTokens },
|
||||
cacheReadInputTokens: selected.reduce(0) { $0 + $1.cacheReadInputTokens },
|
||||
outputTokens: selected.reduce(0) { $0 + $1.outputTokens },
|
||||
totalTokens: selected.reduce(0) { $0 + $1.totalTokens })
|
||||
}
|
||||
|
||||
public func summary(days: Int) -> Summary {
|
||||
let selected = self.daily.suffix(max(1, days))
|
||||
return Summary(
|
||||
costUSD: selected.reduce(0) { $0 + $1.costUSD },
|
||||
inputTokens: selected.reduce(0) { $0 + $1.inputTokens },
|
||||
cacheCreationInputTokens: selected.reduce(0) { $0 + $1.cacheCreationInputTokens },
|
||||
cacheReadInputTokens: selected.reduce(0) { $0 + $1.cacheReadInputTokens },
|
||||
outputTokens: selected.reduce(0) { $0 + $1.outputTokens },
|
||||
totalTokens: selected.reduce(0) { $0 + $1.totalTokens })
|
||||
}
|
||||
|
||||
public var topModels: [ModelBreakdown] {
|
||||
var totals: [String: ModelAccumulator] = [:]
|
||||
for day in self.daily {
|
||||
for model in day.models {
|
||||
totals[model.name, default: ModelAccumulator()].add(model)
|
||||
}
|
||||
}
|
||||
return totals
|
||||
.map { name, total in total.makeModel(name: name) }
|
||||
.sorted {
|
||||
if $0.totalTokens == $1.totalTokens { return $0.name < $1.name }
|
||||
return $0.totalTokens > $1.totalTokens
|
||||
}
|
||||
}
|
||||
|
||||
public var topCostItems: [CostBreakdown] {
|
||||
var totals: [String: Double] = [:]
|
||||
for day in self.daily {
|
||||
for item in day.costItems {
|
||||
totals[item.name, default: 0] += item.costUSD
|
||||
}
|
||||
}
|
||||
return totals
|
||||
.map { CostBreakdown(name: $0.key, costUSD: $0.value) }
|
||||
.sorted {
|
||||
if $0.costUSD == $1.costUSD { return $0.name < $1.name }
|
||||
return $0.costUSD > $1.costUSD
|
||||
}
|
||||
}
|
||||
|
||||
public func toUsageSnapshot() -> UsageSnapshot {
|
||||
let total = self.last30Days
|
||||
return UsageSnapshot(
|
||||
primary: nil,
|
||||
secondary: nil,
|
||||
providerCost: ProviderCostSnapshot(
|
||||
used: total.costUSD,
|
||||
limit: 0,
|
||||
currencyCode: "USD",
|
||||
period: "Last 30 days",
|
||||
updatedAt: self.updatedAt),
|
||||
claudeAdminAPIUsage: self,
|
||||
updatedAt: self.updatedAt,
|
||||
identity: ProviderIdentitySnapshot(
|
||||
providerID: .claude,
|
||||
accountEmail: nil,
|
||||
accountOrganization: nil,
|
||||
loginMethod: "Admin API"))
|
||||
}
|
||||
|
||||
private struct ModelAccumulator {
|
||||
var inputTokens = 0
|
||||
var cacheCreationInputTokens = 0
|
||||
var cacheReadInputTokens = 0
|
||||
var outputTokens = 0
|
||||
var totalTokens = 0
|
||||
|
||||
mutating func add(_ model: ModelBreakdown) {
|
||||
self.inputTokens += model.inputTokens
|
||||
self.cacheCreationInputTokens += model.cacheCreationInputTokens
|
||||
self.cacheReadInputTokens += model.cacheReadInputTokens
|
||||
self.outputTokens += model.outputTokens
|
||||
self.totalTokens += model.totalTokens
|
||||
}
|
||||
|
||||
func makeModel(name: String) -> ModelBreakdown {
|
||||
ModelBreakdown(
|
||||
name: name,
|
||||
inputTokens: self.inputTokens,
|
||||
cacheCreationInputTokens: self.cacheCreationInputTokens,
|
||||
cacheReadInputTokens: self.cacheReadInputTokens,
|
||||
outputTokens: self.outputTokens,
|
||||
totalTokens: self.totalTokens)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user