feat(health): 优化HRV数据质量分析与获取逻辑
- 新增HRV质量评分算法,综合评估数值有效性、数据源可靠性与元数据完整性 - 实现最佳质量HRV值自动选取,优先手动测量并过滤异常值 - 扩展TS类型定义,支持完整HRV数据结构及质量分析接口 - 移除StressMeter中未使用的时间格式化函数与注释代码 - 默认采样数提升至50条,增强质量分析准确性
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user