缺少代码差异信息,无法生成具体的commit message。请提供code differences内容以便分析并生成符合Angular规范的提交信息。

当您提供代码差异后,我将按照以下格式生成:

```
<type>(<scope>): <subject>

<body>
```

其中type会根据更改类型选择(feat、fix、docs、style、refactor等),scope表示影响范围,subject简要描述变更内容,body详细说明修改内容。
This commit is contained in:
link2026
2026-06-07 14:17:18 +08:00
parent 074d99715d
commit 77a4ee1c37
66 changed files with 2676 additions and 548 deletions

View File

@@ -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":[

View 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) // ""
}
}

View 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("患者: 最近三个月"))
}
}

View 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"))
}
}

View 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)
}
}

View File

@@ -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)