feat(capture): 统一报告捕获流程并集成视觉语言模型识别
- 替换 QuickCaptureFlow 和 ArchiveFlow 为 UnifiedCaptureFlow 统一流程 - 新增 VLSession 封装 Qwen2.5-VL 模型进行图像文本推理 - 实现 AIRuntime 中 VL 模型的准备和分析功能 - 添加 VLPrompts 定义体检化验单识别的 JSON 输出模板 - 创建 CaptureReviewForm 提供 VL 解析结果的可编辑表单界面 - 集成 VisionKit 文档扫描器支持真机多页文档扫描 - 为模拟器实现 PhotosPicker 回退方案选择已有照片 - 在 RootView 中统一使用 UnifiedCaptureFlow 处理快速和归档流程 - 添加 CustomMetricEditor 支持自定义监测指标的创建编辑删除 - 扩展 KangkangApp 模型配置以支持新数据类型 - 实现档案列表中症状结束功能通过时间线行点击触发
This commit is contained in:
107
康康Tests/SeriesBucketTests.swift
Normal file
107
康康Tests/SeriesBucketTests.swift
Normal file
@@ -0,0 +1,107 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import 康康
|
||||
|
||||
struct SeriesBucketTests {
|
||||
|
||||
private func makeIndicator(
|
||||
name: String = "测试",
|
||||
value: String,
|
||||
unit: String = "mmol/L",
|
||||
range: String = "",
|
||||
status: IndicatorStatus = .normal,
|
||||
capturedAt: Date,
|
||||
seriesKey: String?
|
||||
) -> Indicator {
|
||||
Indicator(name: name, value: value, unit: unit, range: range,
|
||||
status: status, capturedAt: capturedAt,
|
||||
seriesKey: seriesKey)
|
||||
}
|
||||
|
||||
@Test func skipsIndicatorsWithoutSeriesKey() {
|
||||
let now = Date()
|
||||
let items = [
|
||||
makeIndicator(value: "5.0", capturedAt: now, seriesKey: nil),
|
||||
makeIndicator(value: "5.2", capturedAt: now, seriesKey: nil),
|
||||
]
|
||||
let buckets = SeriesBucket.build(from: items)
|
||||
#expect(buckets.isEmpty)
|
||||
}
|
||||
|
||||
@Test func filtersOutSeriesWithFewerThanMinPoints() {
|
||||
let now = Date()
|
||||
let items = [
|
||||
makeIndicator(value: "5.0", capturedAt: now, seriesKey: "glucose.fasting"),
|
||||
]
|
||||
let buckets = SeriesBucket.build(from: items, minPoints: 2)
|
||||
#expect(buckets.isEmpty)
|
||||
}
|
||||
|
||||
@Test func singleSeriesBucketSortedAscending() {
|
||||
let day = { (offset: Int) -> Date in
|
||||
Calendar.current.date(byAdding: .day, value: offset, to: .now)!
|
||||
}
|
||||
let items = [
|
||||
makeIndicator(value: "5.5", capturedAt: day(-3), seriesKey: "glucose.fasting"),
|
||||
makeIndicator(value: "5.2", capturedAt: day(-1), seriesKey: "glucose.fasting"),
|
||||
makeIndicator(value: "5.8", capturedAt: day(-2), seriesKey: "glucose.fasting"),
|
||||
]
|
||||
let buckets = SeriesBucket.build(from: items)
|
||||
#expect(buckets.count == 1)
|
||||
let line = try! #require(buckets.first?.lines.first)
|
||||
// sorted ascending → -3, -2, -1
|
||||
let values = line.points.map(\.value)
|
||||
#expect(values == [5.5, 5.8, 5.2])
|
||||
}
|
||||
|
||||
@Test func bloodPressureMergesIntoSingleBucket() {
|
||||
let now = Date()
|
||||
let day = { (offset: Int) -> Date in
|
||||
Calendar.current.date(byAdding: .day, value: offset, to: now)!
|
||||
}
|
||||
let items = [
|
||||
makeIndicator(value: "125", capturedAt: day(-2), seriesKey: "bp.systolic"),
|
||||
makeIndicator(value: "82", capturedAt: day(-2), seriesKey: "bp.diastolic"),
|
||||
makeIndicator(value: "130", capturedAt: day(-1), seriesKey: "bp.systolic"),
|
||||
makeIndicator(value: "85", capturedAt: day(-1), seriesKey: "bp.diastolic"),
|
||||
]
|
||||
let buckets = SeriesBucket.build(from: items)
|
||||
let bp = try! #require(buckets.first { $0.id == "bp" })
|
||||
#expect(bp.lines.count == 2)
|
||||
#expect(bp.title == "血压")
|
||||
#expect(bp.lines.contains { $0.seriesKey == "bp.systolic" })
|
||||
#expect(bp.lines.contains { $0.seriesKey == "bp.diastolic" })
|
||||
}
|
||||
|
||||
@Test func mixedSeriesProducesMultipleBucketsSortedByRecency() {
|
||||
let cal = Calendar.current
|
||||
let day = { (offset: Int) -> Date in
|
||||
cal.date(byAdding: .day, value: offset, to: .now)!
|
||||
}
|
||||
let items = [
|
||||
// weight 较旧
|
||||
makeIndicator(value: "68", capturedAt: day(-10), seriesKey: "weight"),
|
||||
makeIndicator(value: "67", capturedAt: day(-7), seriesKey: "weight"),
|
||||
// glucose 较新
|
||||
makeIndicator(value: "5.1", capturedAt: day(-2), seriesKey: "glucose.fasting"),
|
||||
makeIndicator(value: "5.3", capturedAt: day(-1), seriesKey: "glucose.fasting"),
|
||||
]
|
||||
let buckets = SeriesBucket.build(from: items)
|
||||
#expect(buckets.count == 2)
|
||||
// 最新的 glucose 排前面
|
||||
#expect(buckets.first?.id == "glucose.fasting")
|
||||
#expect(buckets.last?.id == "weight")
|
||||
}
|
||||
|
||||
@Test func nonNumericValueDropped() {
|
||||
let now = Date()
|
||||
let items = [
|
||||
makeIndicator(value: "高", capturedAt: now, seriesKey: "weight"),
|
||||
makeIndicator(value: "68", capturedAt: now, seriesKey: "weight"),
|
||||
makeIndicator(value: "67", capturedAt: now.addingTimeInterval(60), seriesKey: "weight"),
|
||||
]
|
||||
let buckets = SeriesBucket.build(from: items)
|
||||
let line = try! #require(buckets.first?.lines.first)
|
||||
#expect(line.points.count == 2) // "高" 被丢
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user