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

@@ -289,6 +289,27 @@ function validateHeartRate(value: any): number | null {
return null;
}
function validateHRVValue(value: any): number | null {
if (value === undefined || value === null) return null;
const numValue = Number(value);
// HRV SDNN 正常范围检查
// 正常范围: 18-76ms但允许更宽范围 5-150ms 以包含边缘情况
if (numValue >= 5 && numValue <= 150) {
// 保留1位小数的精度避免过度舍入
return Math.round(numValue * 10) / 10;
}
// 记录异常值用于调试
console.warn('HRV数据超出合理范围:', {
value: numValue,
expectedRange: '5-150ms',
normalRange: '18-76ms'
});
return null;
}
// 健康数据获取函数
export async function fetchStepCount(date: Date): Promise<number> {
try {
@@ -487,7 +508,7 @@ export async function fetchBasalEnergyBurned(options: HealthDataOptions): Promis
}
}
async function fetchHeartRateVariability(options: HealthDataOptions): Promise<number | null> {
async function fetchHeartRateVariability(options: HealthDataOptions): Promise<HRVData | null> {
try {
console.log('=== 开始获取HRV数据 ===');
console.log('查询选项:', options);
@@ -496,14 +517,67 @@ async function fetchHeartRateVariability(options: HealthDataOptions): Promise<nu
console.log('HRV API调用结果:', result);
if (result && result.data && Array.isArray(result.data) && result.data.length > 0) {
const hrvValue = result.data[0].value;
logSuccess('HRV数据', result);
return Math.round(hrvValue); // Value already in ms from native
} else {
logWarning('HRV', '为空或格式错误');
console.warn('HRV数据为空原始响应:', result);
return null;
let selectedSample: any = null;
console.log('result~~~', result);
// 优先使用优化后的最佳质量值对应的样本
if (result.bestQualityValue && typeof result.bestQualityValue === 'number') {
const qualityValue = validateHRVValue(result.bestQualityValue);
if (qualityValue !== null) {
// 找到对应的最佳质量样本
selectedSample = result.data[result.data.length - 1];
logSuccess('HRV数据最佳质量', {
value: qualityValue,
totalSamples: result.data.length,
recordedAt: selectedSample.endDate
});
}
}
// 如果没有找到最佳质量样本,使用第一个有效样本
if (!selectedSample) {
for (const sample of result.data) {
const sampleValue = validateHRVValue(sample.value);
if (sampleValue !== null) {
selectedSample = sample;
console.log('使用有效HRV样本:', {
value: sampleValue,
source: sample.source?.name,
recordedAt: sample.endDate
});
break;
}
}
}
// 构建完整的HRV数据对象
if (selectedSample) {
const validatedValue = validateHRVValue(selectedSample.value);
if (validatedValue !== null) {
const hrvData: HRVData = {
value: validatedValue,
recordedAt: selectedSample.startDate,
endDate: selectedSample.endDate,
source: {
name: selectedSample.source?.name || 'Unknown',
bundleIdentifier: selectedSample.source?.bundleIdentifier || ''
},
isManualMeasurement: selectedSample.isManualMeasurement || false,
qualityScore: selectedSample.qualityScore,
sampleId: selectedSample.id
};
logSuccess('HRV完整数据', hrvData);
return hrvData;
}
}
}
logWarning('HRV', '为空或格式错误');
console.warn('HRV数据为空或无效原始响应:', result);
return null;
} catch (error) {
logError('HRV数据', error);
console.error('HRV获取错误详情:', error);
@@ -657,18 +731,18 @@ export async function fetchTodayHealthData(): Promise<TodayHealthData> {
return fetchHealthDataForDate(dayjs().toDate());
}
export async function fetchHRVForDate(date: Date): Promise<number | null> {
export async function fetchHRVForDate(date: Date): Promise<HRVData | null> {
console.log('开始获取指定日期HRV数据...', date);
const options = createDateRange(date);
return fetchHeartRateVariability(options);
}
export async function fetchTodayHRV(): Promise<number | null> {
export async function fetchTodayHRV(): Promise<HRVData | null> {
return fetchHRVForDate(dayjs().toDate());
}
// 获取最近几小时内的实时HRV数据
export async function fetchRecentHRV(hoursBack: number = 2): Promise<number | null> {
export async function fetchRecentHRV(hoursBack: number = 2): Promise<HRVData | null> {
console.log(`开始获取最近${hoursBack}小时内的HRV数据...`);
const now = new Date();
@@ -697,18 +771,63 @@ export async function testHRVDataFetch(date: Date = dayjs().toDate()): Promise<v
// 测试不同时间范围的HRV数据
const options = createDateRange(date);
// 获取今日HRV
// 获取今日HRV(带详细分析)
console.log('--- 测试今日HRV ---');
const result = await HealthKitManager.getHeartRateVariabilitySamples(options);
console.log('原始HRV API响应:', result);
if (result && result.data && Array.isArray(result.data)) {
console.log(`获取到 ${result.data.length} 个HRV样本`);
// 分析数据质量
result.data.forEach((sample: any, index: number) => {
console.log(`样本 ${index + 1}:`, {
value: sample.value,
source: sample.source?.name,
bundleId: sample.source?.bundleIdentifier,
isManual: sample.isManualMeasurement,
qualityScore: sample.qualityScore,
startDate: sample.startDate,
endDate: sample.endDate
});
});
if (result.bestQualityValue !== undefined) {
console.log('最佳质量HRV值:', result.bestQualityValue);
}
}
// 使用优化后的方法获取HRV
const todayHRV = await fetchHeartRateVariability(options);
console.log('今日HRV结果:', todayHRV);
console.log('最终HRV结果:', todayHRV);
// 获取最近2小时HRV
console.log('--- 测试最近2小时HRV ---');
const recentHRV = await fetchRecentHRV(2);
console.log('最近2小时HRV结果:', recentHRV);
// 获取指定日期HRV
console.log('--- 测试指定日期HRV ---');
const dateHRV = await fetchHRVForDate(date);
console.log('指定日期HRV结果:', dateHRV);
// 提供数据解释
if (todayHRV) {
console.log('--- HRV数据解读 ---');
console.log(`HRV值: ${todayHRV.value}ms`);
console.log(`记录时间: ${todayHRV.recordedAt}`);
console.log(`数据来源: ${todayHRV.source.name}`);
console.log(`手动测量: ${todayHRV.isManualMeasurement ? '是' : '否'}`);
if (todayHRV.value >= 18 && todayHRV.value <= 76) {
console.log('✅ HRV值在正常范围内 (18-76ms)');
} else if (todayHRV.value < 18) {
console.log('⚠️ HRV值偏低可能表示压力或疲劳状态');
} else if (todayHRV.value > 76) {
console.log('📈 HRV值较高通常表示良好的恢复状态');
}
}
console.log('=== HRV数据测试完成 ===');
} catch (error) {
console.error('HRV测试过程中出现错误:', error);
@@ -971,3 +1090,102 @@ export function isPermissionDenied(): boolean {
return status === HealthPermissionStatus.Denied;
}
// HRV数据结构
export interface HRVData {
value: number;
recordedAt: string; // ISO string format
endDate: string; // ISO string format
source: {
name: string;
bundleIdentifier: string;
};
isManualMeasurement: boolean;
qualityScore?: number;
sampleId?: string;
}
// HRV数据质量分析和解读
export interface HRVAnalysis {
value: number;
quality: 'excellent' | 'good' | 'fair' | 'poor';
interpretation: string;
recommendations: string[];
dataSource: string;
isManualMeasurement: boolean;
recordedAt: string;
}
export function analyzeHRVData(hrvData: HRVData): HRVAnalysis {
const { value: hrvValue, source, isManualMeasurement, recordedAt } = hrvData;
const sourceName = source.name;
let quality: HRVAnalysis['quality'];
let interpretation: string;
let recommendations: string[] = [];
// 质量评估基于数值范围和数据来源
if (hrvValue >= 18 && hrvValue <= 76) {
if (isManualMeasurement) {
quality = 'excellent';
interpretation = 'HRV值在正常范围内且来自高质量测量';
} else {
quality = 'good';
interpretation = 'HRV值在正常范围内';
}
} else if (hrvValue >= 10 && hrvValue < 18) {
quality = 'fair';
interpretation = 'HRV值偏低可能表示压力、疲劳或恢复不足';
recommendations.push('考虑增加休息和恢复时间');
recommendations.push('评估近期的压力水平和睡眠质量');
} else if (hrvValue > 76 && hrvValue <= 100) {
quality = isManualMeasurement ? 'excellent' : 'good';
interpretation = 'HRV值较高通常表示良好的心血管健康和恢复状态';
recommendations.push('保持当前的生活方式和训练强度');
} else if (hrvValue < 10) {
quality = 'poor';
interpretation = 'HRV值异常低建议关注身体状态或数据准确性';
recommendations.push('建议使用手动测量(如呼吸应用)获得更准确的数据');
recommendations.push('如持续偏低,建议咨询医疗专业人士');
} else if (hrvValue > 100) {
quality = 'fair';
interpretation = 'HRV值异常高可能需要验证数据准确性';
recommendations.push('建议重复测量确认数据准确性');
} else {
quality = 'poor';
interpretation = 'HRV数据超出预期范围';
recommendations.push('建议使用标准化的测量方法');
}
// 根据数据来源添加建议
if (!isManualMeasurement) {
recommendations.push('推荐使用呼吸应用进行手动HRV测量以获得更准确的数据');
}
return {
value: hrvValue,
quality,
interpretation,
recommendations,
dataSource: sourceName,
isManualMeasurement,
recordedAt
};
}
// 获取HRV数据并提供分析
export async function fetchHRVWithAnalysis(date: Date): Promise<{ hrvData: HRVData | null; analysis: HRVAnalysis | null }> {
try {
const hrvData = await fetchHRVForDate(date);
if (hrvData) {
const analysis = analyzeHRVData(hrvData);
return { hrvData, analysis };
}
return { hrvData: null, analysis: null };
} catch (error) {
console.error('获取HRV分析数据失败:', error);
return { hrvData: null, analysis: null };
}
}