Files
memind/health-phase-stack.test.mjs
T
john 2baf29b3ae feat(health): complete P0 health channel — baseline engine, page-data, MindSpace UI
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>
2026-09-09 18:04:57 +08:00

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);
});