feat(health): 优化HRV数据质量分析与获取逻辑

- 新增HRV质量评分算法,综合评估数值有效性、数据源可靠性与元数据完整性
- 实现最佳质量HRV值自动选取,优先手动测量并过滤异常值
- 扩展TS类型定义,支持完整HRV数据结构及质量分析接口
- 移除StressMeter中未使用的时间格式化函数与注释代码
- 默认采样数提升至50条,增强质量分析准确性
This commit is contained in:
richarjiang
2025-09-24 18:29:58 +08:00
parent 6303795870
commit 83e534c4a7
3 changed files with 374 additions and 41 deletions

View File

@@ -623,7 +623,9 @@ class HealthKitManager: NSObject, RCTBridgeModule {
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let limit = options["limit"] as? Int ?? HKObjectQueryNoLimit
// 获取更多样本用于质量分析,默认50个样本
let limit = options["limit"] as? Int ?? 50
let query = HKSampleQuery(sampleType: hrvType,
predicate: predicate,
@@ -639,6 +641,7 @@ class HealthKitManager: NSObject, RCTBridgeModule {
resolver([
"data": [],
"count": 0,
"bestQualityValue": nil,
"startDate": self?.dateToISOString(startDate) ?? "",
"endDate": self?.dateToISOString(endDate) ?? ""
])
@@ -646,22 +649,31 @@ class HealthKitManager: NSObject, RCTBridgeModule {
}
let hrvData = hrvSamples.map { sample in
[
let hrvValueMs = sample.quantity.doubleValue(for: HKUnit.secondUnit(with: .milli))
let sourceBundle = sample.sourceRevision.source.bundleIdentifier
return [
"id": sample.uuid.uuidString,
"startDate": self?.dateToISOString(sample.startDate) ?? "",
"endDate": self?.dateToISOString(sample.endDate) ?? "",
"value": sample.quantity.doubleValue(for: HKUnit.secondUnit(with: .milli)),
"value": hrvValueMs,
"source": [
"name": sample.sourceRevision.source.name,
"bundleIdentifier": sample.sourceRevision.source.bundleIdentifier
"bundleIdentifier": sourceBundle
],
"metadata": sample.metadata ?? [:]
"metadata": sample.metadata ?? [:],
"isManualMeasurement": self?.isManualHRVMeasurement(sourceBundle: sourceBundle, metadata: sample.metadata) ?? false,
"qualityScore": self?.calculateHRVQualityScore(value: hrvValueMs, sourceBundle: sourceBundle, metadata: sample.metadata) ?? 0
] as [String : Any]
}
// 计算最佳质量的HRV值
let bestQualityValue = self?.getBestQualityHRVValue(from: hrvData)
let result: [String: Any] = [
"data": hrvData,
"count": hrvData.count,
"bestQualityValue": bestQualityValue,
"startDate": self?.dateToISOString(startDate) ?? "",
"endDate": self?.dateToISOString(endDate) ?? ""
]
@@ -1031,6 +1043,130 @@ class HealthKitManager: NSObject, RCTBridgeModule {
return formatter.string(from: date)
}
// MARK: - HRV Quality Analysis Methods
/// 判断是否为手动/高质量HRV测量
private func isManualHRVMeasurement(sourceBundle: String, metadata: [String: Any]?) -> Bool {
// 来自呼吸应用的测量通常是手动触发的高质量测量
if sourceBundle.contains("Breathe") || sourceBundle.contains("breathe") {
return true
}
// 来自第三方HRV应用的测量通常是手动的
let manualHRVApps = ["HRV4Training", "EliteHRV", "HRVLogger", "Stress & Anxiety Companion"]
if manualHRVApps.contains(where: { sourceBundle.contains($0) }) {
return true
}
// 检查元数据中的手动测量标识
if let metadata = metadata {
if let wasUserEntered = metadata[HKMetadataKeyWasUserEntered] as? Bool, wasUserEntered {
return true
}
}
return false
}
/// 计算HRV测量的质量评分
private func calculateHRVQualityScore(value: Double, sourceBundle: String, metadata: [String: Any]?) -> Int {
var score = 0
// 1. 数值有效性检查 (0-40分)
if value >= 10 && value <= 100 {
// 正常SDNN范围,给予基础分数
if value >= 18 && value <= 76 {
score += 40 // 完全正常范围
} else if value >= 10 && value <= 18 {
score += 30 // 偏低但可能有效
} else if value >= 76 && value <= 100 {
score += 35 // 偏高但可能有效
}
} else if value > 0 && value < 10 {
score += 10 // 数值过低,质量存疑
}
// 2. 数据源质量 (0-35分)
if isManualHRVMeasurement(sourceBundle: sourceBundle, metadata: metadata) {
score += 35 // 手动测量,质量最高
} else if sourceBundle.contains("com.apple.health") {
score += 20 // 系统自动测量,中等质量
} else if sourceBundle.contains("Watch") {
score += 25 // Apple Watch测量,较好质量
} else {
score += 15 // 其他来源,质量一般
}
// 3. 元数据质量指标 (0-25分)
if let metadata = metadata {
var metadataScore = 0
// 用户手动输入
if let wasUserEntered = metadata[HKMetadataKeyWasUserEntered] as? Bool, wasUserEntered {
metadataScore += 15
}
// 设备信息完整性
if metadata[HKMetadataKeyDeviceName] != nil {
metadataScore += 5
}
// 其他质量指标
if metadata[HKMetadataKeyHeartRateMotionContext] != nil {
metadataScore += 5
}
score += metadataScore
}
return min(score, 100) // 限制最大分数为100
}
/// 从HRV数据中获取最佳质量的测量值
private func getBestQualityHRVValue(from hrvData: [[String: Any]]) -> Double? {
guard !hrvData.isEmpty else { return nil }
// 按质量分数和时间排序,优先选择高质量的最新测量
let sortedData = hrvData.sorted { item1, item2 in
let quality1 = item1["qualityScore"] as? Int ?? 0
let quality2 = item2["qualityScore"] as? Int ?? 0
let isManual1 = item1["isManualMeasurement"] as? Bool ?? false
let isManual2 = item2["isManualMeasurement"] as? Bool ?? false
// 优先级:手动测量 > 质量分数 > 时间新旧
if isManual1 && !isManual2 {
return true
} else if !isManual1 && isManual2 {
return false
} else if quality1 != quality2 {
return quality1 > quality2
} else {
// 同等质量下,选择更新的数据
let date1 = item1["endDate"] as? String ?? ""
let date2 = item2["endDate"] as? String ?? ""
return date1 > date2
}
}
// 返回质量最高的测量值
if let bestValue = sortedData.first?["value"] as? Double {
// 对最终值进行合理性验证
if bestValue >= 5 && bestValue <= 150 {
return bestValue
}
}
// 如果最佳值不合理,尝试返回第一个合理的值
for data in sortedData {
if let value = data["value"] as? Double, value >= 10 && value <= 100 {
return value
}
}
// 如果都没有合理值,返回第一个值(可能需要用户注意数据质量)
return sortedData.first?["value"] as? Double
}
// MARK: - Hourly Data Methods
@objc