import Foundation // 比价记录(「我的比价记录」)。对齐后端 schemas/compare_record.py。 // 金额两种口径:记录级 *_cents 是分(Int);逐平台 price / couponSaved 是元(Double)。 // 来自 raw JSON 列,字段可能不全 → 容错性字段一律 optional。 struct ComparisonResultItemDTO: Codable, Hashable { let platformId: String? let platformName: String? let `package`: String? // 安卓包名,跳转用;Swift 关键字,反引号转义 let price: Double? // 元;失败/未知为 null let isSource: Bool? let rank: Int? let couponSaved: Double? // 纯红包优惠额(元);没用/没抠到为 null } struct ComparisonItemDTO: Codable, Hashable { let name: String var qty: Int = 1 let specs: [String]? enum CodingKeys: String, CodingKey { case name, qty, specs } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) name = try c.decode(String.self, forKey: .name) qty = (try? c.decode(Int.self, forKey: .qty)) ?? 1 specs = try? c.decode([String].self, forKey: .specs) } } struct ComparisonRecordDTO: Codable, Identifiable, Hashable { let id: Int let businessType: String let traceId: String let sourcePlatformId: String? let sourcePlatformName: String? let sourcePackage: String? let sourcePriceCents: Int? let bestPlatformId: String? let bestPlatformName: String? let bestPriceCents: Int? let savedAmountCents: Int? let isSourceBest: Bool? let storeName: String? let totalDishCount: Int? let skippedDishCount: Int? let status: String let information: String? var items: [ComparisonItemDTO] = [] var comparisonResults: [ComparisonResultItemDTO] = [] var skippedDishNames: [String] = [] let createdAt: String enum CodingKeys: String, CodingKey { case id, businessType, traceId, sourcePlatformId, sourcePlatformName, sourcePackage case sourcePriceCents, bestPlatformId, bestPlatformName, bestPriceCents, savedAmountCents case isSourceBest, storeName, totalDishCount, skippedDishCount, status, information case items, comparisonResults, skippedDishNames, createdAt } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) id = try c.decode(Int.self, forKey: .id) businessType = (try? c.decode(String.self, forKey: .businessType)) ?? "food" traceId = (try? c.decode(String.self, forKey: .traceId)) ?? "" sourcePlatformId = try? c.decode(String.self, forKey: .sourcePlatformId) sourcePlatformName = try? c.decode(String.self, forKey: .sourcePlatformName) sourcePackage = try? c.decode(String.self, forKey: .sourcePackage) sourcePriceCents = try? c.decode(Int.self, forKey: .sourcePriceCents) bestPlatformId = try? c.decode(String.self, forKey: .bestPlatformId) bestPlatformName = try? c.decode(String.self, forKey: .bestPlatformName) bestPriceCents = try? c.decode(Int.self, forKey: .bestPriceCents) savedAmountCents = try? c.decode(Int.self, forKey: .savedAmountCents) isSourceBest = try? c.decode(Bool.self, forKey: .isSourceBest) storeName = try? c.decode(String.self, forKey: .storeName) totalDishCount = try? c.decode(Int.self, forKey: .totalDishCount) skippedDishCount = try? c.decode(Int.self, forKey: .skippedDishCount) status = (try? c.decode(String.self, forKey: .status)) ?? "unknown" information = try? c.decode(String.self, forKey: .information) items = (try? c.decode([ComparisonItemDTO].self, forKey: .items)) ?? [] comparisonResults = (try? c.decode([ComparisonResultItemDTO].self, forKey: .comparisonResults)) ?? [] skippedDishNames = (try? c.decode([String].self, forKey: .skippedDishNames)) ?? [] createdAt = (try? c.decode(String.self, forKey: .createdAt)) ?? "" } } struct ComparisonRecordPageDTO: Codable { let items: [ComparisonRecordDTO] let nextCursor: Int? }