709 lines · 23.4 KB · Swift
RawDownload
import Foundation // MARK: - Amount Response Models struct DeepSeekAmountPayload: Decodable {    let code: Int?    let msg: String?    let data: DeepSeekAmountData?     private enum CodingKeys: String, CodingKey {        case code, msg, data    }     init(from decoder: Decoder) throws {        let container = try decoder.container(keyedBy: CodingKeys.self)        self.code = try container.decodeIfPresent(Int.self, forKey: .code)        self.msg = try container.decodeIfPresent(String.self, forKey: .msg)        if container.contains(.data) {            if let dataValue = try? container.decodeIfPresent(DeepSeekAmountData.self, forKey: .data) {                self.data = dataValue            } else {                self.data = nil            }        } else {            self.data = nil        }    }} struct DeepSeekAmountData: Decodable {    let bizCode: Int?    let bizMsg: String?    let bizData: DeepSeekAmountBizData?     private enum CodingKeys: String, CodingKey {        case bizCode = "biz_code"        case bizMsg = "biz_msg"        case bizData = "biz_data"    }     init(from decoder: Decoder) throws {        let container = try decoder.container(keyedBy: CodingKeys.self)        self.bizCode = try container.decodeIfPresent(Int.self, forKey: .bizCode)        self.bizMsg = try container.decodeIfPresent(String.self, forKey: .bizMsg)        if container.contains(.bizData) {            if let dataValue = try? container.decodeIfPresent(DeepSeekAmountBizData.self, forKey: .bizData) {                self.bizData = dataValue            } else {                self.bizData = nil            }        } else {            self.bizData = nil        }    }} struct DeepSeekAmountBizData: Decodable {    let total: [DeepSeekModelUsage]?    let days: [DeepSeekDayUsage]?     private enum CodingKeys: String, CodingKey {        case total, days    }} // MARK: - Cost Response Models struct DeepSeekCostPayload: Decodable {    let code: Int?    let msg: String?    let data: DeepSeekCostData?     private enum CodingKeys: String, CodingKey {        case code, msg, data    }     init(from decoder: Decoder) throws {        let container = try decoder.container(keyedBy: CodingKeys.self)        self.code = try container.decodeIfPresent(Int.self, forKey: .code)        self.msg = try container.decodeIfPresent(String.self, forKey: .msg)        if container.contains(.data) {            if let dataValue = try? container.decodeIfPresent(DeepSeekCostData.self, forKey: .data) {                self.data = dataValue            } else {                self.data = nil            }        } else {            self.data = nil        }    }} struct DeepSeekCostData: Decodable {    let bizCode: Int?    let bizMsg: String?    let bizData: [DeepSeekCostBizDataItem]?     private enum CodingKeys: String, CodingKey {        case bizCode = "biz_code"        case bizMsg = "biz_msg"        case bizData = "biz_data"    }     init(from decoder: Decoder) throws {        let container = try decoder.container(keyedBy: CodingKeys.self)        self.bizCode = try container.decodeIfPresent(Int.self, forKey: .bizCode)        self.bizMsg = try container.decodeIfPresent(String.self, forKey: .bizMsg)        self.bizData = try container.decodeIfPresent([DeepSeekCostBizDataItem].self, forKey: .bizData)    }} struct DeepSeekCostBizDataItem: Decodable {    let total: [DeepSeekCostModelUsage]?    let days: [DeepSeekCostDayUsage]?    let currency: String?     private enum CodingKeys: String, CodingKey {        case total, days, currency    }} // MARK: - Shared Models struct DeepSeekModelUsage: Decodable {    let model: String?    let usage: [DeepSeekUsageItem]?     private enum CodingKeys: String, CodingKey {        case model, usage    }} struct DeepSeekDayUsage: Decodable {    let date: String?    let data: [DeepSeekModelUsage]?     private enum CodingKeys: String, CodingKey {        case date, data    }} struct DeepSeekUsageItem: Decodable {    let type: String?    let amount: String?     private enum CodingKeys: String, CodingKey {        case type, amount    }} struct DeepSeekCostModelUsage: Decodable {    let model: String?    let usage: [DeepSeekCostItem]?     private enum CodingKeys: String, CodingKey {        case model, usage    }} struct DeepSeekCostDayUsage: Decodable {    let date: String?    let data: [DeepSeekCostModelUsage]?     private enum CodingKeys: String, CodingKey {        case date, data    }} struct DeepSeekCostItem: Decodable {    let type: String?    let amount: String?     private enum CodingKeys: String, CodingKey {        case type, amount    }} // MARK: - Domain Models public struct DeepSeekUsageSummary: Sendable, Equatable {    public let todayTokens: Int    public let currentMonthTokens: Int    public let todayCost: Double?    public let currentMonthCost: Double?    public let requestCount: Int    public let currentMonthRequestCount: Int    public let topModel: String?    public let categoryBreakdown: [DeepSeekCategoryBreakdown]    public let daily: [DeepSeekDailyUsage]    public let currency: String    public let updatedAt: Date     public init(        todayTokens: Int,        currentMonthTokens: Int,        todayCost: Double?,        currentMonthCost: Double?,        requestCount: Int,        currentMonthRequestCount: Int,        topModel: String?,        categoryBreakdown: [DeepSeekCategoryBreakdown],        daily: [DeepSeekDailyUsage],        currency: String,        updatedAt: Date)    {        self.todayTokens = todayTokens        self.currentMonthTokens = currentMonthTokens        self.todayCost = todayCost        self.currentMonthCost = currentMonthCost        self.requestCount = requestCount        self.currentMonthRequestCount = currentMonthRequestCount        self.topModel = topModel        self.categoryBreakdown = categoryBreakdown        self.daily = daily        self.currency = currency        self.updatedAt = updatedAt    }} public struct DeepSeekCategoryBreakdown: Sendable, Equatable {    public let category: DeepSeekUsageCategory    public let tokens: Int    public let cost: Double?     public init(category: DeepSeekUsageCategory, tokens: Int, cost: Double?) {        self.category = category        self.tokens = tokens        self.cost = cost    }} public enum DeepSeekUsageCategory: String, Sendable, Equatable {    case promptCacheHitToken = "PROMPT_CACHE_HIT_TOKEN"    case promptCacheMissToken = "PROMPT_CACHE_MISS_TOKEN"    case responseToken = "RESPONSE_TOKEN"    case request = "REQUEST"     public init?(rawValue: String) {        switch rawValue.uppercased() {        case "PROMPT_CACHE_HIT_TOKEN":            self = .promptCacheHitToken        case "PROMPT_CACHE_MISS_TOKEN":            self = .promptCacheMissToken        case "RESPONSE_TOKEN":            self = .responseToken        case "REQUEST":            self = .request        default:            return nil        }    }} public struct DeepSeekDailyUsage: Sendable, Equatable {    public let date: String    public let totalTokens: Int    public let cost: Double?    public let requestCount: Int     public init(date: String, totalTokens: Int, cost: Double?, requestCount: Int) {        self.date = date        self.totalTokens = totalTokens        self.cost = cost        self.requestCount = requestCount    }} // MARK: - Parsing enum DeepSeekUsageCostParser {    static func decodeAmountPayload(data: Data) throws -> DeepSeekAmountPayload {        try JSONDecoder().decode(DeepSeekAmountPayload.self, from: data)    }     static func decodeCostPayload(data: Data) throws -> DeepSeekCostPayload {        try JSONDecoder().decode(DeepSeekCostPayload.self, from: data)    }     static func parse(        amountData: Data,        costData: Data,        now: Date = Date(),        calendar: Calendar = .current) throws -> DeepSeekUsageSummary    {        let amountPayload: DeepSeekAmountPayload        let costPayload: DeepSeekCostPayload        do {            amountPayload = try self.decodeAmountPayload(data: amountData)        } catch {            throw DeepSeekUsageError.parseFailed("amount: \(error.localizedDescription)")        }        do {            costPayload = try self.decodeCostPayload(data: costData)        } catch {            throw DeepSeekUsageError.parseFailed("cost: \(error.localizedDescription)")        }         // Validate responses        if let code = amountPayload.code, code != 0 {            throw DeepSeekUsageError.apiError("amount code \(code)")        }        if let bizCode = amountPayload.data?.bizCode, bizCode != 0 {            throw DeepSeekUsageError.apiError("amount biz_code \(bizCode)")        }        if let code = costPayload.code, code != 0 {            throw DeepSeekUsageError.apiError("cost code \(code)")        }        if let bizCode = costPayload.data?.bizCode, bizCode != 0 {            throw DeepSeekUsageError.apiError("cost biz_code \(bizCode)")        }         guard let amountBizData = amountPayload.data?.bizData else {            throw DeepSeekUsageError.parseFailed("Missing amount biz_data")        }         let currency = costPayload.data?.bizData?.first?.currency ?? "CNY"         // Parse total amounts        let totalAmounts = amountBizData.total ?? []        let totalCosts = costPayload.data?.bizData?.first?.total ?? []         // Parse daily data        let dailyAmounts = amountBizData.days ?? []        let dailyCosts = costPayload.data?.bizData?.first?.days ?? []         return self.aggregate(input: AggregationInput(            totalAmounts: totalAmounts,            totalCosts: totalCosts,            dailyAmounts: dailyAmounts,            dailyCosts: dailyCosts,            currency: currency,            now: now,            calendar: calendar))    }     // MARK: - Aggregation     private struct AggregationContext {        let calendar: Calendar        let todayString: String        let startOfMonth: Date        let now: Date        let dailyAmountMap: [String: [String: [DeepSeekUsageItem]]]        let dailyCostMap: [String: [String: [DeepSeekCostItem]]]        let allDates: Set<String>         init(            dailyAmounts: [DeepSeekDayUsage],            dailyCosts: [DeepSeekCostDayUsage],            now: Date,            calendar: Calendar)        {            self.calendar = calendar            self.now = now            self.todayString = Self.dayString(now, calendar: calendar)             var components = calendar.dateComponents([.year, .month], from: now)            components.day = 1            self.startOfMonth = calendar.date(from: components) ?? now             self.dailyAmountMap = Self.buildAmountMap(from: dailyAmounts)            self.dailyCostMap = Self.buildCostMap(from: dailyCosts)             var dates: Set<String> = []            for date in self.dailyAmountMap.keys {                dates.insert(date)            }            for date in self.dailyCostMap.keys {                dates.insert(date)            }            self.allDates = dates        }         static func dayString(_ date: Date, calendar: Calendar) -> String {            let components = calendar.dateComponents([.year, .month, .day], from: date)            guard let year = components.year,                  let month = components.month,                  let day = components.day            else { return "" }            return String(format: "%04d-%02d-%02d", year, month, day)        }         static func buildAmountMap(            from dailyAmounts: [DeepSeekDayUsage]) -> [String: [String: [DeepSeekUsageItem]]]        {            var result: [String: [String: [DeepSeekUsageItem]]] = [:]            for dayUsage in dailyAmounts {                guard let date = dayUsage.date else { continue }                var modelMap: [String: [DeepSeekUsageItem]] = [:]                for modelUsage in dayUsage.data ?? [] {                    guard let model = modelUsage.model else { continue }                    let items = modelUsage.usage ?? []                    if !items.isEmpty {                        modelMap[model] = items                    }                }                if !modelMap.isEmpty {                    result[date] = modelMap                }            }            return result        }         static func buildCostMap(            from dailyCosts: [DeepSeekCostDayUsage]) -> [String: [String: [DeepSeekCostItem]]]        {            var result: [String: [String: [DeepSeekCostItem]]] = [:]            for dayUsage in dailyCosts {                guard let date = dayUsage.date else { continue }                var modelMap: [String: [DeepSeekCostItem]] = [:]                for modelUsage in dayUsage.data ?? [] {                    guard let model = modelUsage.model else { continue }                    let items = modelUsage.usage ?? []                    if !items.isEmpty {                        modelMap[model] = items                    }                }                if !modelMap.isEmpty {                    result[date] = modelMap                }            }            return result        }    }     private struct AggregationInput {        let totalAmounts: [DeepSeekModelUsage]        let totalCosts: [DeepSeekCostModelUsage]        let dailyAmounts: [DeepSeekDayUsage]        let dailyCosts: [DeepSeekCostDayUsage]        let currency: String        let now: Date        let calendar: Calendar    }     private static func aggregate(input: AggregationInput) -> DeepSeekUsageSummary {        let ctx = AggregationContext(            dailyAmounts: input.dailyAmounts,            dailyCosts: input.dailyCosts,            now: input.now,            calendar: input.calendar)         // Today aggregation        let todayResult = self.aggregateDay(            dateString: ctx.todayString,            amountMap: ctx.dailyAmountMap,            costMap: ctx.dailyCostMap,            calendar: ctx.calendar)         // Month aggregation        let dailyCtx = DailyAggregationContext(            allDates: ctx.allDates,            startOfMonth: ctx.startOfMonth,            now: ctx.now,            amountMap: ctx.dailyAmountMap,            costMap: ctx.dailyCostMap,            calendar: ctx.calendar)        let monthResult = self.aggregateMonth(ctx: dailyCtx)         // Model and category breakdown from totals        let (topModel, categoryBreakdown) = self.buildBreakdowns(            totalAmounts: input.totalAmounts,            totalCosts: input.totalCosts)         // Daily usage array        let dailyUsages = self.buildDailyUsages(ctx: dailyCtx)         return DeepSeekUsageSummary(            todayTokens: todayResult.tokens,            currentMonthTokens: monthResult.tokens,            todayCost: todayResult.cost,            currentMonthCost: monthResult.cost,            requestCount: todayResult.requests,            currentMonthRequestCount: monthResult.requests,            topModel: topModel,            categoryBreakdown: categoryBreakdown,            daily: dailyUsages,            currency: input.currency,            updatedAt: input.now)    }     private struct DayAggregationResult {        let tokens: Int        let cost: Double?        let requests: Int    }     private struct DailyAggregationContext {        let allDates: Set<String>        let startOfMonth: Date        let now: Date        let amountMap: [String: [String: [DeepSeekUsageItem]]]        let costMap: [String: [String: [DeepSeekCostItem]]]        let calendar: Calendar    }     private static func aggregateDay(        dateString: String,        amountMap: [String: [String: [DeepSeekUsageItem]]],        costMap: [String: [String: [DeepSeekCostItem]]],

Lines 1–500 of 709.