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