refactor(profile,monitor): move height/weight from MonitorMetric to UserProfile

身高/体重对成人变化慢,作为 Profile 静态字段比每次录入 Indicator 更合适。

- MonitorMetric:6 case(从 8 减),删 .height / .weight
- UserProfile:加 weightKG: Double?(支持小数),加 bmi computed
- summaryLine 加体重段:'175cm · 68.5kg'(整数省小数)
- ProfileEditView basics 加 weight 行 + footer 显示 BMI + 分类(偏瘦/正常/超重/肥胖)
- IndicatorQuickSheet:删 .height 回写 Profile 的特殊逻辑
- UserProfileTests:+5 个(weight 字段、summaryLine 含 weight、BMI 计算)

兼容性:老 Indicator 里的 seriesKey 'weight' / 'height' 数据保留(SwiftData String?
不变),只是新录入路径走 Profile 不走 Indicator;Trends 仍能用 String seriesKey
查询历史(如果将来要展示老数据)。

测试:60 case pass / 0 fail / 0 warning。
This commit is contained in:
link2026
2026-05-26 07:58:47 +08:00
parent 37b47b2076
commit 39edc25dc1
5 changed files with 83 additions and 31 deletions

View File

@@ -17,6 +17,7 @@ struct UserProfileTests {
let p = UserProfile()
#expect(p.birthYear == nil)
#expect(p.heightCM == nil)
#expect(p.weightKG == nil)
#expect(p.biologicalSexRaw == "")
#expect(p.bloodTypeRaw == "")
#expect(p.allergies.isEmpty)
@@ -25,6 +26,7 @@ struct UserProfileTests {
#expect(p.familyHistory.isEmpty)
#expect(p.age == nil)
#expect(p.sex == .undisclosed)
#expect(p.bmi == nil)
#expect(p.hasAnyBasics == false)
#expect(p.summaryLine == "")
}
@@ -50,7 +52,32 @@ struct UserProfileTests {
#expect(line.contains(""))
#expect(line.contains(""))
#expect(line.contains("162cm"))
#expect(!line.contains("")) // ,
#expect(!line.contains("kg")) // ,
#expect(!line.contains("")) // ,
}
@Test func summaryLineIncludesWeightWhenSet() {
let p = UserProfile(heightCM: 175, weightKG: 68.5)
#expect(p.summaryLine.contains("175cm"))
#expect(p.summaryLine.contains("68.5kg"))
}
@Test func summaryLineFormatsIntegerWeightWithoutDecimal() {
let p = UserProfile(weightKG: 70)
#expect(p.summaryLine == "70kg")
}
@Test func bmiNilWithoutBothHeightAndWeight() {
#expect(UserProfile(heightCM: 175).bmi == nil)
#expect(UserProfile(weightKG: 68).bmi == nil)
#expect(UserProfile().bmi == nil)
}
@Test func bmiComputedFromHeightAndWeight() throws {
let p = UserProfile(heightCM: 175, weightKG: 70)
let bmi = try #require(p.bmi)
// 70 / (1.75 * 1.75) = 22.857
#expect(abs(bmi - 22.857) < 0.01)
}
@Test func loadOrCreateReturnsExistingSingleton() throws {