Files
kangkang/康康/Models/Models.swift
link2026 1b01923c8e feat(capture): 统一报告捕获流程并集成视觉语言模型识别
- 替换 QuickCaptureFlow 和 ArchiveFlow 为 UnifiedCaptureFlow 统一流程
- 新增 VLSession 封装 Qwen2.5-VL 模型进行图像文本推理
- 实现 AIRuntime 中 VL 模型的准备和分析功能
- 添加 VLPrompts 定义体检化验单识别的 JSON 输出模板
- 创建 CaptureReviewForm 提供 VL 解析结果的可编辑表单界面
- 集成 VisionKit 文档扫描器支持真机多页文档扫描
- 为模拟器实现 PhotosPicker 回退方案选择已有照片
- 在 RootView 中统一使用 UnifiedCaptureFlow 处理快速和归档流程
- 添加 CustomMetricEditor 支持自定义监测指标的创建编辑删除
- 扩展 KangkangApp 模型配置以支持新数据类型
- 实现档案列表中症状结束功能通过时间线行点击触发
2026-05-26 11:18:00 +08:00

289 lines
7.8 KiB
Swift

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 "体检报告"
case .lab: return "化验单"
case .imaging: return "影像报告"
case .prescription: return "处方"
case .other: return "其他"
}
}
}
@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.<uuid>"`, 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<Double>? {
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 "已关闭" }
if isEveryDay { return "每天" }
if weekdays.isEmpty { return "未选日" }
let names = ["", "", "", "", "", "", ""]
let sorted = weekdays.sorted()
return "每周 " + 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
}
}