2baf29b3ae
Deliver encrypted health zone, observation API, baseline maturity pipeline, page-data bindings, and H5/WeChat channel integration for health P0. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
3.3 KiB
JavaScript
100 lines
3.3 KiB
JavaScript
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);
|
|
});
|