import Foundation import SwiftData enum IndicatorStatus: String, Codable, CaseIterable { case high, low, normal } enum ReportType: String, Codable, CaseIterable { case checkup, lab, imaging, prescription, other var label: String { switch self { case .checkup: return String(appLoc: "体检报告") case .lab: return String(appLoc: "化验单") case .imaging: return String(appLoc: "影像报告") case .prescription: return String(appLoc: "处方") case .other: return String(appLoc: "其他") } } } @Model final class Indicator { var name: String var value: String var unit: String var range: String var statusRaw: String var note: String? var capturedAt: Date var report: Report? var asset: Asset? var pinned: Bool = false /// 长期指标系列 key,如 "bp.systolic" / "glucose.fasting" / "weight"。 /// 来源:IndicatorRecordSheet 选预设时填;VL/Report/自由输入留 nil。 /// 用途:Trends 按 seriesKey 分组;Timeline 配对(如 bp.systolic + bp.diastolic 合并)。 var seriesKey: String? init(name: String, value: String, unit: String, range: String, status: IndicatorStatus, note: String? = nil, capturedAt: Date = .now, report: Report? = nil, asset: Asset? = nil, pinned: Bool = false, seriesKey: String? = nil) { self.name = name self.value = value self.unit = unit self.range = range self.statusRaw = status.rawValue self.note = note self.capturedAt = capturedAt self.report = report self.asset = asset self.pinned = pinned self.seriesKey = seriesKey } var status: IndicatorStatus { IndicatorStatus(rawValue: statusRaw) ?? .normal } } @Model final class Report { var title: String var typeRaw: String var reportDate: Date var institution: String? var note: String? var summary: String? var pageCount: Int var createdAt: Date @Relationship(deleteRule: .cascade, inverse: \Indicator.report) var indicators: [Indicator] = [] @Relationship(deleteRule: .cascade) var assets: [Asset] = [] init(title: String, type: ReportType, reportDate: Date, institution: String? = nil, note: String? = nil, summary: String? = nil, pageCount: Int = 1, createdAt: Date = .now) { self.title = title self.typeRaw = type.rawValue self.reportDate = reportDate self.institution = institution self.note = note self.summary = summary self.pageCount = pageCount self.createdAt = createdAt } var type: ReportType { ReportType(rawValue: typeRaw) ?? .other } } @Model final class DiaryEntry { var content: String var createdAt: Date var tags: [String] init(content: String, createdAt: Date = .now, tags: [String] = []) { self.content = content self.createdAt = createdAt self.tags = tags } } @Model final class Asset { var relativePath: String var mimeType: String var bytes: Int var createdAt: Date init(relativePath: String, mimeType: String = "image/jpeg", bytes: Int = 0, createdAt: Date = .now) { self.relativePath = relativePath self.mimeType = mimeType self.bytes = bytes self.createdAt = createdAt } } @Model final class Symptom { var name: String var startedAt: Date var endedAt: Date? var note: String? var severity: Int var tags: [String] var createdAt: Date init(name: String, startedAt: Date = .now, endedAt: Date? = nil, note: String? = nil, severity: Int = 3, tags: [String] = [], createdAt: Date = .now) { self.name = name self.startedAt = startedAt self.endedAt = endedAt self.note = note self.severity = max(1, min(5, severity)) self.tags = tags self.createdAt = createdAt } var isOngoing: Bool { endedAt == nil } var duration: TimeInterval { (endedAt ?? .now).timeIntervalSince(startedAt) } } /// 用户自定义的长期监测指标。 /// 与 hardcoded `MonitorMetric` 并列出现在 IndicatorQuickSheet 的 grid 里; /// `seriesKey` 自动生成成 `"custom."`,以此和 Indicator 双向关联。 @Model final class CustomMonitorMetric { @Attribute(.unique) var seriesKey: String var name: String var unit: String var lowerBound: Double? var upperBound: Double? var icon: String var createdAt: Date init(name: String, unit: String, lowerBound: Double? = nil, upperBound: Double? = nil, icon: String = "circle.fill", createdAt: Date = .now) { self.seriesKey = "custom.\(UUID().uuidString)" self.name = name self.unit = unit self.lowerBound = lowerBound self.upperBound = upperBound self.icon = icon self.createdAt = createdAt } var referenceRange: ClosedRange? { guard let lo = lowerBound, let hi = upperBound, lo <= hi else { return nil } return lo...hi } var rangeText: String { guard let r = referenceRange else { return "" } return "\(Self.format(r.lowerBound)) - \(Self.format(r.upperBound))" } private static func format(_ v: Double) -> String { v.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", v) : String(format: "%.1f", v) } } /// 长期监测指标的周期性记录提醒。 /// 一个 metric 一条(`metricId` = `MonitorMetric.rawValue`)。 /// 关闭通过 `enabled=false`(保留时间设置),删除走 `ctx.delete`。 @Model final class MetricReminder { @Attribute(.unique) var metricId: String var displayName: String var enabled: Bool var hour: Int // 0...23 var minute: Int // 0...59 var weekdays: [Int] // iOS Calendar 约定:1=日, 2=一, ..., 7=六。全 7 个 = 每天 var createdAt: Date var updatedAt: Date init(metricId: String, displayName: String, hour: Int = 8, minute: Int = 0, weekdays: [Int] = [1, 2, 3, 4, 5, 6, 7], enabled: Bool = true, createdAt: Date = .now) { self.metricId = metricId self.displayName = displayName self.enabled = enabled self.hour = max(0, min(23, hour)) self.minute = max(0, min(59, minute)) self.weekdays = weekdays self.createdAt = createdAt self.updatedAt = createdAt } var isEveryDay: Bool { Set(weekdays) == Set(1...7) } var frequencyLabel: String { if !enabled { return String(appLoc: "已关闭") } if isEveryDay { return String(appLoc: "每天") } if weekdays.isEmpty { return String(appLoc: "未选日") } let names = [String(appLoc: "日"), String(appLoc: "一"), String(appLoc: "二"), String(appLoc: "三"), String(appLoc: "四"), String(appLoc: "五"), String(appLoc: "六")] let sorted = weekdays.sorted() return String(appLoc: "每周 ") + sorted.map { names[$0 - 1] }.joined() } var timeLabel: String { String(format: "%02d:%02d", hour, minute) } } @Model final class ChatTurn { var question: String var answer: String var referencedIndicatorIDs: [String] var referencedReportIDs: [String] var createdAt: Date var decodeRate: Double init(question: String, answer: String, referencedIndicatorIDs: [String] = [], referencedReportIDs: [String] = [], createdAt: Date = .now, decodeRate: Double = 0) { self.question = question self.answer = answer self.referencedIndicatorIDs = referencedIndicatorIDs self.referencedReportIDs = referencedReportIDs self.createdAt = createdAt self.decodeRate = decodeRate } }