Files
hyungi_document_server/Sources/DSKit/Models/Digest.swift
T
Hyungi 0becf7829e feat(s3): SwiftPM scaffold + DSKit data layer + 14-fixture acceptance
- Package.swift: AI (S2-owned) + DSKit (models/client/fixtures) + DSKitTests, tools 6.2, .swiftLanguageMode(.v6), .macOS(.v26)
- JSONValue (Sendable AnyCodable), DSDate (value-type ISO8601FormatStyle cascade, date-only UTC), explicit-CodingKeys decoder
- Models: Auth/Document(+Detail flat-compose, MD-first)/Catalog/Search+Ask/Memo/Digest; non-optional limited to id/file_type/created+updated_at/total
- DSClient protocol + FixtureDSClient (Bundle.module, zero backend) + DSError + DSConfig + DownloadURL (?token= query)
- Tests: 14-fixture contract acceptance (value asserts) + JSONValue number trap + Ask round-trip + AI router fallback/explicit-unavailable

swift build + swift test green (19 tests). Sources/AI untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 17:16:55 +09:00

89 lines
2.9 KiB
Swift

import Foundation
public struct ArticleRef: Codable, Sendable, Identifiable {
public let id: Int
public let title: String?
}
public struct TopicResponse: Codable, Sendable, Identifiable {
public let topicRank: Int
public let topicLabel: String
public let summary: String
public let articleIds: [Int]?
public let articles: [ArticleRef]
public let articleCount: Int?
public let importanceScore: Double?
public let rawWeightSum: Double?
public let llmFallbackUsed: Bool?
public var id: Int { topicRank }
enum CodingKeys: String, CodingKey {
case summary, articles
case topicRank = "topic_rank"
case topicLabel = "topic_label"
case articleIds = "article_ids"
case articleCount = "article_count"
case importanceScore = "importance_score"
case rawWeightSum = "raw_weight_sum"
case llmFallbackUsed = "llm_fallback_used"
}
}
public struct CountryGroup: Codable, Sendable, Identifiable {
public let country: String
public let topics: [TopicResponse]
public var id: String { country }
}
public struct DigestResponse: Codable, Sendable {
public let digestDateRaw: String?
public let windowStartRaw: String?
public let windowEndRaw: String?
public let decayLambda: Double?
public let totalArticles: Int?
public let totalCountries: Int?
public let totalTopics: Int?
public let generationMs: Int?
public let llmCalls: Int?
public let llmFailures: Int?
public let status: String?
public let countries: [CountryGroup]
/// date-only (B-1 r2): DISPLAY the raw string (no Date conversion no KST off-by-one).
public var digestDateDisplay: String { digestDateRaw ?? "" }
/// Date conversions are for sort/window math only (UTC-fixed via DSDate).
public var windowStart: Date? { DSDate.parse(windowStartRaw) }
public var windowEnd: Date? { DSDate.parse(windowEndRaw) }
enum CodingKeys: String, CodingKey {
case status, countries
case digestDateRaw = "digest_date"
case windowStartRaw = "window_start"
case windowEndRaw = "window_end"
case decayLambda = "decay_lambda"
case totalArticles = "total_articles"
case totalCountries = "total_countries"
case totalTopics = "total_topics"
case generationMs = "generation_ms"
case llmCalls = "llm_calls"
case llmFailures = "llm_failures"
}
}
public struct DigestDateSummary: Codable, Sendable {
public let digestDateRaw: String?
public let totalTopics: Int?
public let totalCountries: Int?
public let totalArticles: Int?
public let status: String?
enum CodingKeys: String, CodingKey {
case status
case digestDateRaw = "digest_date"
case totalTopics = "total_topics"
case totalCountries = "total_countries"
case totalArticles = "total_articles"
}
}