import Foundation /// 长期监测指标预设目录。`IndicatorRecordSheet` 顶部 grid 由 `MonitorMetric.allCases` 渲染。 /// /// 录入时按 metric 展开 1 或 2 个 Field;血压拆 2 条 Indicator(同 capturedAt + 各自 seriesKey), /// 其他预设产 1 条。`effectiveRange(for:profile:)` 用 Profile 调整参考范围(目前只 1 条规则: /// 老人收缩压上限 140→150)。 enum MonitorMetric: String, CaseIterable, Identifiable { case bloodPressure // bp.systolic + bp.diastolic case fastingGlucose // glucose.fasting case postprandialGlucose // glucose.postprandial case temperature // temperature case heartRate // heart_rate case spo2 // spo2 // 注:身高 / 体重不在这里——它们是 UserProfile 的字段(单值,不存历史)。 var id: String { rawValue } var displayName: String { switch self { case .bloodPressure: return "血压" case .fastingGlucose: return "空腹血糖" case .postprandialGlucose: return "餐后血糖" case .temperature: return "体温" case .heartRate: return "心率" case .spo2: return "血氧" } } /// SF Symbol。grid 卡片图标。 var icon: String { switch self { case .bloodPressure: return "heart.fill" case .fastingGlucose: return "drop.fill" case .postprandialGlucose: return "drop.circle.fill" case .temperature: return "thermometer.medium" case .heartRate: return "waveform.path.ecg" case .spo2: return "lungs.fill" } } var fields: [Field] { switch self { case .bloodPressure: return [ Field(seriesKey: "bp.systolic", label: "收缩压", unit: "mmHg", placeholder: "120", baseRange: 90...140), Field(seriesKey: "bp.diastolic", label: "舒张压", unit: "mmHg", placeholder: "80", baseRange: 60...90), ] case .fastingGlucose: return [Field(seriesKey: "glucose.fasting", label: "空腹血糖", unit: "mmol/L", placeholder: "5.0", baseRange: 3.9...6.1)] case .postprandialGlucose: return [Field(seriesKey: "glucose.postprandial", label: "餐后 2h", unit: "mmol/L", placeholder: "6.5", baseRange: 0...7.8)] case .temperature: return [Field(seriesKey: "temperature", label: "体温", unit: "°C", placeholder: "36.5", baseRange: 36.0...37.2)] case .heartRate: return [Field(seriesKey: "heart_rate", label: "心率", unit: "bpm", placeholder: "72", baseRange: 60...100)] case .spo2: return [Field(seriesKey: "spo2", label: "血氧", unit: "%", placeholder: "98", baseRange: 95...100)] } } } extension MonitorMetric { struct Field: Identifiable, Hashable { let seriesKey: String let label: String let unit: String let placeholder: String let baseRange: ClosedRange? var id: String { seriesKey } /// 给 IndicatorRecordSheet 显示在数值旁的「90-140 mmHg」字样。 func rangeText(_ range: ClosedRange?) -> String { guard let r = range else { return "无参考范围" } let lower = format(r.lowerBound) let upper = format(r.upperBound) // 餐后血糖 baseRange 是 0...7.8,显示成「<7.8」 if r.lowerBound == 0 { return "<\(upper) \(unit)" } return "\(lower)–\(upper) \(unit)" } private func format(_ v: Double) -> String { v.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", v) : String(format: "%.1f", v) } } /// 给定 field 在 profile 下的有效参考范围。 /// 目前只 1 条规则:age ≥ 65 时 bp.systolic 上限 140 → 150。 /// profile 为 nil(未设资料)时返回 baseRange。 func effectiveRange(for field: Field, profile: UserProfile?) -> ClosedRange? { if let age = profile?.age, age >= 65, field.seriesKey == "bp.systolic" { return 90...150 } return field.baseRange } /// 算出 effectiveRange 后,判定 value 的 status。 /// value 高于上限 → high;低于下限 → low;在内 → normal;无范围 → normal。 static func status(value: Double, in range: ClosedRange?) -> IndicatorStatus { guard let r = range else { return .normal } if value > r.upperBound { return .high } if value < r.lowerBound { return .low } return .normal } /// 给 IndicatorRecordSheet 「按你的年龄(67)调整」提示用: /// 当 effectiveRange ≠ baseRange 时返回 true。 func isRangePersonalized(for field: Field, profile: UserProfile?) -> Bool { guard let p = profile else { return false } let base = field.baseRange let eff = effectiveRange(for: field, profile: p) return base != eff } }