缺少代码差异信息,无法生成具体的commit message。请提供code differences内容以便分析并生成符合Angular规范的提交信息。
当您提供代码差异后,我将按照以下格式生成: ``` <type>(<scope>): <subject> <body> ``` 其中type会根据更改类型选择(feat、fix、docs、style、refactor等),scope表示影响范围,subject简要描述变更内容,body详细说明修改内容。
This commit is contained in:
@@ -123,6 +123,32 @@ struct CaptureServiceJSONTests {
|
||||
#expect(parsed.indicators.first?.status == .high)
|
||||
}
|
||||
|
||||
@Test func parsesIndicatorEvidenceLocation() throws {
|
||||
let raw = """
|
||||
{"title":"t","type":"lab","report_date":"2026-05-01","page_count":2,"indicators":[{"name":"尿酸","value":"486","unit":"μmol/L","range":"208 - 428","status":"high","source_page":2,"source_box":[0.18,0.42,0.68,0.49]}]}
|
||||
"""
|
||||
let parsed = try CaptureService.parseReportJSON(raw, pageCount: 2)
|
||||
let indicator = try #require(parsed.indicators.first)
|
||||
#expect(indicator.sourcePageIndex == 1)
|
||||
#expect(indicator.sourceBoxX == 0.18)
|
||||
#expect(indicator.sourceBoxY == 0.42)
|
||||
#expect(indicator.sourceBoxWidth == 0.68)
|
||||
#expect(indicator.sourceBoxHeight == 0.49)
|
||||
}
|
||||
|
||||
@Test func ignoresInvalidIndicatorEvidenceLocation() throws {
|
||||
let raw = """
|
||||
{"indicators":[{"name":"尿酸","value":"486","unit":"μmol/L","range":"208 - 428","status":"high","source_page":0,"source_box":[-1,0.42,0.68,1.5]}]}
|
||||
"""
|
||||
let parsed = try CaptureService.parseReportJSON(raw)
|
||||
let indicator = try #require(parsed.indicators.first)
|
||||
#expect(indicator.sourcePageIndex == nil)
|
||||
#expect(indicator.sourceBoxX == nil)
|
||||
#expect(indicator.sourceBoxY == nil)
|
||||
#expect(indicator.sourceBoxWidth == nil)
|
||||
#expect(indicator.sourceBoxHeight == nil)
|
||||
}
|
||||
|
||||
@Test func infersStatusFromValueAndReferenceRangeWhenStatusMissing() throws {
|
||||
let raw = """
|
||||
{"indicators":[
|
||||
|
||||
112
康康Tests/ExportTrendBuilderTests.swift
Normal file
112
康康Tests/ExportTrendBuilderTests.swift
Normal file
@@ -0,0 +1,112 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import 康康
|
||||
|
||||
/// `ExportTrendBuilder` 是纯函数,覆盖:方向判定、血压合并、相关性过滤、
|
||||
/// 点数 <2 过滤、跨参考范围边界标记、非数值点丢弃、整行文案格式。
|
||||
struct ExportTrendBuilderTests {
|
||||
|
||||
private func ind(
|
||||
name: String = "血糖",
|
||||
value: String,
|
||||
unit: String = "mmol/L",
|
||||
range: String = "3.9-6.1",
|
||||
status: IndicatorStatus = .normal,
|
||||
daysAgo: Int,
|
||||
seriesKey: String? = nil
|
||||
) -> Indicator {
|
||||
let date = Calendar.current.date(byAdding: .day, value: -daysAgo, to: .now)!
|
||||
return Indicator(name: name, value: value, unit: unit, range: range,
|
||||
status: status, capturedAt: date, seriesKey: seriesKey)
|
||||
}
|
||||
|
||||
@Test func upDirectionFlaggedAndLineFormat() {
|
||||
let items = [
|
||||
ind(value: "5.2", status: .normal, daysAgo: 27),
|
||||
ind(value: "6.8", status: .high, daysAgo: 0),
|
||||
]
|
||||
let trends = ExportTrendBuilder.build(allInWindow: items, relevant: items)
|
||||
let t = try! #require(trends.first)
|
||||
#expect(t.direction == .up)
|
||||
#expect(t.flagged) // 末值 high
|
||||
#expect(t.count == 2)
|
||||
#expect(t.valueText == "5.2→6.8")
|
||||
#expect(t.rangeText == "3.9-6.1")
|
||||
#expect(t.line() == "⚠️ 血糖 5.2→6.8 mmol/L ↑(参考 3.9-6.1),近 27 天 2 次")
|
||||
}
|
||||
|
||||
@Test func downDirection() {
|
||||
let items = [
|
||||
ind(value: "6.0", daysAgo: 10),
|
||||
ind(value: "5.0", daysAgo: 1),
|
||||
]
|
||||
let t = try! #require(ExportTrendBuilder.build(allInWindow: items, relevant: items).first)
|
||||
#expect(t.direction == .down)
|
||||
#expect(!t.flagged) // 两点都 normal
|
||||
}
|
||||
|
||||
@Test func flatWithinThreshold() {
|
||||
// (5.1-5.0)/5.0 = 0.02 < 0.05 → 平稳
|
||||
let items = [
|
||||
ind(value: "5.0", daysAgo: 5),
|
||||
ind(value: "5.1", daysAgo: 1),
|
||||
]
|
||||
let t = try! #require(ExportTrendBuilder.build(allInWindow: items, relevant: items).first)
|
||||
#expect(t.direction == .flat)
|
||||
}
|
||||
|
||||
@Test func filtersSeriesWithFewerThanTwoPoints() {
|
||||
let items = [ind(value: "5.0", daysAgo: 1)]
|
||||
#expect(ExportTrendBuilder.build(allInWindow: items, relevant: items).isEmpty)
|
||||
}
|
||||
|
||||
@Test func excludesIrrelevantSeries() {
|
||||
let glucose = [
|
||||
ind(name: "血糖", value: "5.0", unit: "mmol/L", daysAgo: 3),
|
||||
ind(name: "血糖", value: "5.5", unit: "mmol/L", daysAgo: 1),
|
||||
]
|
||||
let weight = [
|
||||
ind(name: "体重", value: "68", unit: "kg", range: "", daysAgo: 3),
|
||||
ind(name: "体重", value: "67", unit: "kg", range: "", daysAgo: 1),
|
||||
]
|
||||
// weight 有 ≥2 点,但不在 relevant 集 → 不出趋势
|
||||
let trends = ExportTrendBuilder.build(allInWindow: glucose + weight, relevant: glucose)
|
||||
#expect(trends.count == 1)
|
||||
#expect(trends.first?.title == "血糖")
|
||||
}
|
||||
|
||||
@Test func bloodPressureMergesToSingleLine() {
|
||||
let items = [
|
||||
ind(name: "收缩压", value: "150", unit: "mmHg", range: "", daysAgo: 20, seriesKey: "bp.systolic"),
|
||||
ind(name: "舒张压", value: "95", unit: "mmHg", range: "", daysAgo: 20, seriesKey: "bp.diastolic"),
|
||||
ind(name: "收缩压", value: "138", unit: "mmHg", range: "", daysAgo: 1, seriesKey: "bp.systolic"),
|
||||
ind(name: "舒张压", value: "88", unit: "mmHg", range: "", daysAgo: 1, seriesKey: "bp.diastolic"),
|
||||
]
|
||||
let t = try! #require(ExportTrendBuilder.build(allInWindow: items, relevant: items).first)
|
||||
#expect(t.title == "血压")
|
||||
#expect(t.unit == "mmHg")
|
||||
#expect(t.valueText == "150/95→138/88")
|
||||
#expect(t.direction == .down) // 收缩压为准
|
||||
#expect(t.rangeText == nil) // 血压双范围不展示
|
||||
}
|
||||
|
||||
@Test func flaggedWhenStatusCrossesBoundary() {
|
||||
// 首高末正常:跨参考范围边界 → 仍 flagged,提示医生注意变化
|
||||
let items = [
|
||||
ind(value: "6.8", status: .high, daysAgo: 5),
|
||||
ind(value: "5.5", status: .normal, daysAgo: 1),
|
||||
]
|
||||
let t = try! #require(ExportTrendBuilder.build(allInWindow: items, relevant: items).first)
|
||||
#expect(t.flagged)
|
||||
}
|
||||
|
||||
@Test func nonNumericPointDropped() {
|
||||
let items = [
|
||||
ind(value: "高", daysAgo: 3),
|
||||
ind(value: "5.0", daysAgo: 2),
|
||||
ind(value: "5.5", daysAgo: 1),
|
||||
]
|
||||
let t = try! #require(ExportTrendBuilder.build(allInWindow: items, relevant: items).first)
|
||||
#expect(t.count == 2) // "高" 解析失败被丢
|
||||
}
|
||||
}
|
||||
34
康康Tests/HealthExportDialogueTests.swift
Normal file
34
康康Tests/HealthExportDialogueTests.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import 康康
|
||||
|
||||
struct HealthExportDialogueTests {
|
||||
@Test func dialogueTranscriptKeepsTurnOrderAndRoles() {
|
||||
let turns: [HealthExportDialogueTurn] = [
|
||||
.user("我最近头晕,帮我看看"),
|
||||
.assistant("我会结合你的指标和日记整理。"),
|
||||
.user("重点看血压")
|
||||
]
|
||||
|
||||
let transcript = HealthExportDialogueTurn.transcript(from: turns)
|
||||
|
||||
#expect(transcript.contains("患者: 我最近头晕,帮我看看"))
|
||||
#expect(transcript.contains("康康: 我会结合你的指标和日记整理。"))
|
||||
#expect(transcript.contains("患者: 重点看血压"))
|
||||
#expect(transcript.range(of: "患者: 我最近头晕")!.lowerBound < transcript.range(of: "患者: 重点看血压")!.lowerBound)
|
||||
}
|
||||
|
||||
@Test func dialogueTranscriptDropsEmptyTurns() {
|
||||
let turns: [HealthExportDialogueTurn] = [
|
||||
.user(" "),
|
||||
.assistant("请补充想看的问题"),
|
||||
.user("\n最近三个月\n")
|
||||
]
|
||||
|
||||
let transcript = HealthExportDialogueTurn.transcript(from: turns)
|
||||
|
||||
#expect(!transcript.contains("患者: "))
|
||||
#expect(transcript.contains("康康: 请补充想看的问题"))
|
||||
#expect(transcript.contains("患者: 最近三个月"))
|
||||
}
|
||||
}
|
||||
28
康康Tests/HealthExportPromptTests.swift
Normal file
28
康康Tests/HealthExportPromptTests.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
import Testing
|
||||
@testable import 康康
|
||||
|
||||
struct HealthExportPromptTests {
|
||||
@Test func dialogueAnswerPromptContainsQuestionTranscriptAndData() {
|
||||
let prompt = HealthExportPrompts.dialogueAnswer(
|
||||
latestQuestion: "最近血压怎么样?",
|
||||
transcript: "患者: 最近头晕",
|
||||
dataJSON: #"{"indicators":[{"name":"收缩压"}],"diaries":[{"excerpt":"昨晚没睡好"}]}"#
|
||||
)
|
||||
|
||||
#expect(prompt.contains("最近血压怎么样?"))
|
||||
#expect(prompt.contains("患者: 最近头晕"))
|
||||
#expect(prompt.contains("收缩压"))
|
||||
#expect(prompt.contains("昨晚没睡好"))
|
||||
}
|
||||
|
||||
@Test func dialogueReportPromptContainsTranscriptAndFixedReportInstruction() {
|
||||
let prompt = HealthExportPrompts.dialogueReportGeneration(
|
||||
transcript: "患者: 帮我整理给医生\n康康: 已查看记录",
|
||||
dataJSON: #"{"indicators":[],"diaries":[]}"#
|
||||
)
|
||||
|
||||
#expect(prompt.contains("多轮对话"))
|
||||
#expect(prompt.contains("帮我整理给医生"))
|
||||
#expect(prompt.contains("严格 Markdown"))
|
||||
}
|
||||
}
|
||||
54
康康Tests/HealthProfileImportTests.swift
Normal file
54
康康Tests/HealthProfileImportTests.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import 康康
|
||||
|
||||
struct HealthProfileImportTests {
|
||||
@Test func previewKeepsExistingValuesWhenHealthKitValueIsMissing() {
|
||||
let existing = UserProfile(
|
||||
birthYear: 1988,
|
||||
biologicalSexRaw: "female",
|
||||
heightCM: 162,
|
||||
bloodTypeRaw: "A"
|
||||
)
|
||||
let draft = HealthProfileImportDraft(heightCM: 175)
|
||||
|
||||
let preview = HealthProfileImportPreview(draft: draft, current: existing)
|
||||
|
||||
#expect(preview.birthYear.current == "1988")
|
||||
#expect(preview.birthYear.imported == nil)
|
||||
#expect(preview.birthYear.willUpdate == false)
|
||||
#expect(preview.sex.imported == nil)
|
||||
#expect(preview.height.imported == "175cm")
|
||||
#expect(preview.height.willUpdate == true)
|
||||
#expect(preview.bloodType.imported == nil)
|
||||
}
|
||||
|
||||
@Test func applyOnlyOverwritesFieldsPresentInDraft() {
|
||||
let profile = UserProfile(
|
||||
birthYear: 1988,
|
||||
biologicalSexRaw: "female",
|
||||
heightCM: 162,
|
||||
bloodTypeRaw: "A"
|
||||
)
|
||||
let draft = HealthProfileImportDraft(
|
||||
birthYear: 1990,
|
||||
biologicalSexRaw: "male",
|
||||
heightCM: nil,
|
||||
bloodTypeRaw: "O"
|
||||
)
|
||||
|
||||
draft.apply(to: profile, now: Date(timeIntervalSince1970: 123))
|
||||
|
||||
#expect(profile.birthYear == 1990)
|
||||
#expect(profile.biologicalSexRaw == "male")
|
||||
#expect(profile.heightCM == 162)
|
||||
#expect(profile.bloodTypeRaw == "O")
|
||||
#expect(profile.updatedAt == Date(timeIntervalSince1970: 123))
|
||||
}
|
||||
|
||||
@Test func emptyDraftReportsNoImportableFields() {
|
||||
let draft = HealthProfileImportDraft()
|
||||
|
||||
#expect(draft.hasAnyImportableField == false)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import Testing
|
||||
import SwiftData
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
@testable import 康康
|
||||
|
||||
struct ModelsSchemaTests {
|
||||
@@ -138,6 +139,36 @@ struct ModelsSchemaTests {
|
||||
#expect(i.seriesKey == nil)
|
||||
}
|
||||
|
||||
@Test func indicatorEvidenceLocationRoundtrip() throws {
|
||||
let container = try makeContainer()
|
||||
let ctx = ModelContext(container)
|
||||
|
||||
let indicator = Indicator(
|
||||
name: "尿酸",
|
||||
value: "486",
|
||||
unit: "μmol/L",
|
||||
range: "208 - 428",
|
||||
status: .high,
|
||||
source: .report,
|
||||
sourcePageIndex: 1,
|
||||
sourceBoxX: 0.18,
|
||||
sourceBoxY: 0.42,
|
||||
sourceBoxWidth: 0.68,
|
||||
sourceBoxHeight: 0.08
|
||||
)
|
||||
ctx.insert(indicator)
|
||||
try ctx.save()
|
||||
|
||||
let fetched = try #require(try ctx.fetch(FetchDescriptor<Indicator>()).first)
|
||||
#expect(fetched.sourcePageIndex == 1)
|
||||
#expect(fetched.sourceBoxX == 0.18)
|
||||
#expect(fetched.sourceBoxY == 0.42)
|
||||
#expect(fetched.sourceBoxWidth == 0.68)
|
||||
#expect(fetched.sourceBoxHeight == 0.08)
|
||||
#expect(fetched.hasEvidenceBox)
|
||||
#expect(fetched.evidenceRect?.width == 0.68)
|
||||
}
|
||||
|
||||
@Test func userProfileSchemaPersistsAcrossSave() throws {
|
||||
let container = try makeContainer()
|
||||
let ctx = ModelContext(container)
|
||||
|
||||
Reference in New Issue
Block a user