import assert from 'node:assert/strict'; import test from 'node:test'; import { computeHealthBaselines, deviationFromBaseline } from './health-baseline-engine.mjs'; import { evaluateHealthEvents } from './health-event-engine.mjs'; import { createHealthObservationService } from './health-observation-service.mjs'; import { createInMemoryHealthObservationStore } from './health-observation-store.mjs'; import { buildHealthAssessSummary } from './health-assess-summary.mjs'; import { buildHealthTimeline } from './health-timeline.mjs'; const now = Date.UTC(2026, 8, 2, 8, 0, 0); function seedBp(store, userId, dayOffset, systolic, diastolic, context = 'morning') { const observedAt = now - dayOffset * 24 * 60 * 60 * 1000; return store.insert(userId, { confirmed: true, observedAt, metricType: 'bp_systolic', valueNum: systolic, unit: 'mmHg', context, source: 'manual', qualityFlag: 'ok', }).then(() => store.insert(userId, { confirmed: true, observedAt, metricType: 'bp_diastolic', valueNum: diastolic, unit: 'mmHg', context, source: 'manual', qualityFlag: 'ok', })); } test('health observation service commits blood pressure pair', async () => { const service = createHealthObservationService({ store: createInMemoryHealthObservationStore() }); const result = await service.commit('u1', { confirmed: true, metricSet: 'blood_pressure', systolic: 128, diastolic: 76, context: 'morning', }); assert.equal(result.observations.length, 2); }); test('timeline groups observations by day', async () => { const store = createInMemoryHealthObservationStore(); await seedBp(store, 'u1', 0, 130, 80); const service = createHealthObservationService({ store }); const timeline = await service.listTimeline('u1'); assert.equal(timeline.length, 1); assert.ok(timeline[0].metrics.bp_systolic); }); test('baseline engine reaches stable after 30 samples', () => { const observations = Array.from({ length: 30 }, (_, i) => ({ metricType: 'bp_systolic', valueNum: 120 + (i % 3), observedAt: now - i * 24 * 60 * 60 * 1000, context: 'morning', qualityFlag: 'ok', deletedAt: null, })); const baselines = computeHealthBaselines(observations, { now }); const target = baselines.find((b) => b.metricType === 'bp_systolic' && b.windowDays === 30 && b.context === 'morning'); assert.equal(target.maturity, 'stable'); assert.ok(target.mean > 0); }); test('event engine detects absolute threshold breach', () => { const observations = [{ metricType: 'bp_systolic', valueNum: 185, observedAt: now, context: 'morning', qualityFlag: 'ok', deletedAt: null, }]; const events = evaluateHealthEvents(observations, [], { now }); assert.ok(events.some((e) => e.ruleId === 'bp_sys_high')); }); test('assess summary mentions open events when drift exists', () => { const observations = Array.from({ length: 30 }, (_, i) => ({ metricType: 'bp_systolic', valueNum: i < 5 ? 150 : 120, observedAt: now - i * 24 * 60 * 60 * 1000, context: 'morning', qualityFlag: 'ok', deletedAt: null, })); const summary = buildHealthAssessSummary(observations, { now }); assert.match(summary, /基线|记录|诊断/); }); test('deviationFromBaseline computes percent change', () => { assert.equal(deviationFromBaseline(132, 120), 10); });