import { isValidObservationForBaseline } from './health-baseline-maturity.mjs'; const METRIC_LABELS = Object.freeze({ bp_systolic: '收缩压', bp_diastolic: '舒张压', hr: '心率', spo2: '血氧', weight: '体重', temperature: '体温', sleep_minutes: '睡眠', symptom: '症状', }); function dayKey(observedAt) { const date = new Date(Number(observedAt)); if (Number.isNaN(date.getTime())) return 'unknown'; return date.toISOString().slice(0, 10); } function formatMetricValue(row) { if (row.metricType === 'symptom') { return row.valueText ?? '—'; } if (row.valueNum == null) return '—'; const unit = row.unit ? ` ${row.unit}` : ''; return `${row.valueNum}${unit}`; } export function buildHealthTimeline(observations = [], { limitDays = 30 } = {}) { const valid = observations.filter(isValidObservationForBaseline); const byDay = new Map(); for (const row of valid) { const key = dayKey(row.observedAt); if (!byDay.has(key)) byDay.set(key, []); byDay.get(key).push(row); } const days = [...byDay.keys()].sort((a, b) => b.localeCompare(a)).slice(0, limitDays); return days.map((date) => { const rows = byDay.get(date) ?? []; const metrics = {}; for (const row of rows) { const label = METRIC_LABELS[row.metricType] ?? row.metricType; metrics[row.metricType] = { label, value: formatMetricValue(row), context: row.context ?? null, qualityFlag: row.qualityFlag ?? 'ok', observedAt: row.observedAt, }; } return { date, metrics, count: rows.length }; }); } export function summarizeTimelineForAssess(timeline = []) { if (!timeline.length) { return '还没有足够的健康记录。请先连续录入几天晨间血压、睡眠和症状,基线成熟后才能做评估。'; } const latest = timeline[0]; const lines = [`最近一天(${latest.date}):`]; for (const item of Object.values(latest.metrics)) { lines.push(`- ${item.label}: ${item.value}`); } if (timeline.length >= 7) { lines.push('', `近 ${Math.min(timeline.length, 14)} 天已有 ${timeline.length} 天记录,可开始观察个人基线变化。`); } else { lines.push('', `当前仅 ${timeline.length} 天记录,建议至少连续 7 天后再看趋势。`); } lines.push('', '这不是医疗诊断;如有不适请及时就医。'); return lines.join('\n'); }