feat(symptom): add Symptom @Model + start/end sheets + ongoing card

- Symptom @Model with severity 1-5 clamp, isOngoing, duration helpers
- SymptomStartSheet / SymptomEndSheet / OngoingSymptomsCard
- RecordSheet 加 .symptom kind 入口
- RootView 增加 'records' tab + ArchiveListView placeholder
- HomeView 顶部加 OngoingSymptomsCard
- ModelsSchemaTests: 2 个 Symptom 烟测(ongoing predicate + severity clamp)

Note: Symptom 是 CLAUDE.md §10 清单外的新功能,由产品负责人决定加入。
ArchiveListView 仍是 placeholder,真实 C1 实现按计划在 W4。
This commit is contained in:
link2026
2026-05-25 23:18:21 +08:00
parent e4a68a1bdd
commit 46b69cf8e1
10 changed files with 643 additions and 41 deletions

View File

@@ -12,6 +12,7 @@ struct ModelsSchemaTests {
DiaryEntry.self,
Asset.self,
ChatTurn.self,
Symptom.self,
])
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
return try ModelContainer(for: schema, configurations: [config])
@@ -58,6 +59,37 @@ struct ModelsSchemaTests {
#expect(remaining.isEmpty)
}
@Test func ongoingSymptomQueryFiltersByEndedAt() throws {
let container = try makeContainer()
let ctx = ModelContext(container)
let active = Symptom(name: "头痛", startedAt: .now.addingTimeInterval(-3600))
let ended = Symptom(
name: "咳嗽",
startedAt: .now.addingTimeInterval(-7200),
endedAt: .now.addingTimeInterval(-1800)
)
ctx.insert(active)
ctx.insert(ended)
try ctx.save()
let predicate = #Predicate<Symptom> { $0.endedAt == nil }
let ongoing = try ctx.fetch(FetchDescriptor<Symptom>(predicate: predicate))
#expect(ongoing.count == 1)
#expect(ongoing.first?.name == "头痛")
#expect(active.isOngoing)
#expect(!ended.isOngoing)
#expect(active.duration >= 3600)
}
@Test func symptomSeverityClampedToRange() throws {
let high = Symptom(name: "腹痛", severity: 99)
let low = Symptom(name: "失眠", severity: -3)
#expect(high.severity == 5)
#expect(low.severity == 1)
}
@Test func chatTurnPersistsReferencedIDs() throws {
let container = try makeContainer()
let ctx = ModelContext(container)