const RANGES = Object.freeze({ bp_systolic: { min: 60, max: 260, unit: 'mmHg' }, bp_diastolic: { min: 40, max: 160, unit: 'mmHg' }, hr: { min: 30, max: 200, unit: 'bpm' }, spo2: { min: 50, max: 100, unit: '%' }, temperature: { min: 33, max: 43, unit: 'celsius' }, weight: { min: 20, max: 250, unit: 'kg' }, sleep_minutes: { min: 0, max: 16 * 60, unit: 'min' }, }); export const HEALTH_CONTEXTS = Object.freeze(['morning', 'evening', 'other']); export function inferBloodPressureContext(observedAt, { now = new Date(), timeZone = 'Asia/Shanghai' } = {}) { const date = observedAt instanceof Date ? observedAt : new Date(observedAt ?? now); if (Number.isNaN(date.getTime())) return 'other'; const hourText = new Intl.DateTimeFormat('en-GB', { hour: '2-digit', hourCycle: 'h23', timeZone, }).format(date); const hour = Number(hourText); if (hour < 10) return 'morning'; if (hour >= 18) return 'evening'; return 'other'; } export function validateMetricValue(metricType, value) { const range = RANGES[metricType]; if (!range) { return { ok: false, error: 'unknown_metric' }; } const num = Number(value); if (!Number.isFinite(num)) { return { ok: false, error: 'not_numeric' }; } if (num < range.min || num > range.max) { return { ok: false, error: 'out_of_range', range }; } return { ok: true, value: num, unit: range.unit }; } export function validateBloodPressurePair(systolic, diastolic) { const sys = validateMetricValue('bp_systolic', systolic); const dia = validateMetricValue('bp_diastolic', diastolic); if (!sys.ok) return { ok: false, field: 'bp_systolic', ...sys }; if (!dia.ok) return { ok: false, field: 'bp_diastolic', ...dia }; if (sys.value <= dia.value) { return { ok: false, error: 'systolic_not_greater', field: 'pair' }; } return { ok: true, systolic: sys.value, diastolic: dia.value }; } export function markSuspectVsBaseline(value, baselineMean, { ratio = 0.5 } = {}) { const mean = Number(baselineMean); const num = Number(value); if (!Number.isFinite(mean) || mean <= 0 || !Number.isFinite(num)) { return { qualityFlag: 'ok', suspect: false }; } const deviation = Math.abs(num - mean) / mean; if (deviation >= ratio) { return { qualityFlag: 'suspect', suspect: true, deviation }; } return { qualityFlag: 'ok', suspect: false, deviation }; } export function resolveQualityAfterConfirm({ suspect = false, userCorrected = false } = {}) { if (userCorrected) return 'user_corrected'; if (suspect) return 'ok'; return 'ok'; } export function observationDedupKey({ metricType, observedAt, sourceRef } = {}) { if (!sourceRef) return null; return `${metricType}|${observedAt}|${sourceRef}`; }