Initial commit: HealthCarePregnant project documentation and platform

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
selfrelease
2026-06-18 09:48:05 +08:00
commit eab91174db
301 changed files with 42491 additions and 0 deletions
@@ -0,0 +1,261 @@
/**
* 端到端场景演练(T-9.2 / PRD §3.2 核心场景 S1–S7、§5.1 闭环、§7 上线判据)。
*
* 在服务层串联各模块,覆盖 V1 可上线场景的实质闭环;
* 标注为 V2/V3 的能力(趋势预测、游戏化)以 it.todo 记录,不在 V1 断言。
*
* 复用既有内存仓储与服务,构造共享依赖的系统实例(makeSystem)。
*/
import { PatientService } from '../modules/patient/patient.service';
import { InMemoryPatientRepository } from '../modules/patient/patient.repository';
import { AnalysisService } from '../modules/analysis/analysis.service';
import { InMemoryAlertRepository } from '../modules/analysis/alert.repository';
import { CaseflowService } from '../modules/caseflow/caseflow.service';
import { InMemoryCaseflowRepository } from '../modules/caseflow/caseflow.repository';
import { ObservationService } from '../modules/observation/observation.service';
import { InMemoryObservationRepository } from '../modules/observation/observation.repository';
import { KnowledgeService } from '../modules/knowledge/knowledge.service';
import { InMemoryKnowledgeRepository } from '../modules/knowledge/knowledge.repository';
import { AiService } from '../modules/ai/ai.service';
import { NotificationService } from '../modules/notification/notification.service';
import { RedflagService } from '../modules/redflag/redflag.service';
import { ReminderService } from '../modules/reminder/reminder.service';
import { FollowupService } from '../modules/followup/followup.service';
import { InMemoryFollowupRepository } from '../modules/followup/followup.repository';
function makeSystem() {
const notification = new NotificationService();
const followupService = new FollowupService(new InMemoryFollowupRepository());
const patientService = new PatientService(new InMemoryPatientRepository());
const analysisService = new AnalysisService(new InMemoryAlertRepository());
const caseflowService = new CaseflowService(new InMemoryCaseflowRepository());
const observationService = new ObservationService(
new InMemoryObservationRepository(),
patientService,
analysisService,
caseflowService,
followupService,
);
const knowledgeService = new KnowledgeService(new InMemoryKnowledgeRepository());
const aiService = new AiService(knowledgeService, analysisService, caseflowService);
const redflagService = new RedflagService(notification, caseflowService);
const reminderService = new ReminderService(patientService, notification);
return {
notification,
patientService,
analysisService,
caseflowService,
observationService,
knowledgeService,
aiService,
redflagService,
reminderService,
};
}
describe('PRD §3.2 核心场景端到端演练(T-9.2', () => {
describe('S1 居家测血糖/血压后自动分析(REQ-1/3)', () => {
it('血糖录入 → 质控通过 → 规则分析 → 可追溯预警', async () => {
const sys = makeSystem();
const patient = await sys.patientService.create({ name: '小雅', age: 30, lmp: '2026-01-01' });
const rec = await sys.observationService.record(patient.id, {
indicator: 'fasting_glucose',
value: 5.6,
});
expect(rec.observation.qcStatus).toBe('accepted');
expect(rec.alert).not.toBeNull();
expect(rec.alert?.level).toBe('medium');
// 可解释 / 可追溯(NFR-3):命中规则 + 关联观测值
expect(rec.alert?.ruleIds.length).toBeGreaterThan(0);
expect(rec.alert?.observationId).toBe(rec.observation.id);
});
it('血压录入 → 升高生成预警', async () => {
const sys = makeSystem();
const patient = await sys.patientService.create({ name: '小雅', age: 30, lmp: '2026-01-01' });
const rec = await sys.observationService.record(patient.id, {
indicator: 'systolic_bp',
value: 150,
});
expect(rec.observation.qcStatus).toBe('accepted');
expect(rec.alert?.level).toBe('medium');
});
it('不可信数值 → 质控拦截,不驱动高风险结论(C-5)', async () => {
const sys = makeSystem();
const patient = await sys.patientService.create({ name: '小雅', age: 30, lmp: '2026-01-01' });
const rec = await sys.observationService.record(patient.id, {
indicator: 'fasting_glucose',
value: 50, // 超出生理合理范围
});
expect(rec.observation.qcStatus).toBe('rejected');
expect(rec.alert).toBeNull();
});
});
describe('S2 GDM 风险孕妇的日常管理闭环(REQ-6/9/10', () => {
it('建档→预警→自动开案→指派→流转→AI建议(需人工确认)→照护计划→提醒', async () => {
const sys = makeSystem();
// 既往 GDM → 基线 medium
const patient = await sys.patientService.create({
name: '小雅',
age: 31,
lmp: '2026-01-01',
historyGdm: true,
});
expect(patient.initialRiskLevel).toBe('medium');
// 录入偏高血糖 → 预警 → 预警驱动开案
const rec = await sys.observationService.record(patient.id, {
indicator: 'fasting_glucose',
value: 6.2,
});
expect(rec.alert).not.toBeNull();
const c = await sys.caseflowService.getCaseByPatient(patient.id);
expect(c.status).toBe('open');
expect(c.riskLevel).toBe('medium');
// 指派管理师 + 推进阶段
await sys.caseflowService.assignManager(c.id, 'cm-1');
const advanced = await sys.caseflowService.advanceStage(c.id, 'assessment', '评估');
expect(advanced.stage).toBe('assessment');
expect(advanced.caseManagerId).toBe('cm-1');
// AI 决策建议:中风险 → 必须人工确认(REQ-10.3)
const reco = await sys.aiService.recommendForPatient(patient.id);
expect(reco.requiresHumanConfirmation).toBe(true);
expect(reco.actions.length).toBeGreaterThan(0);
// 照护计划
const plan = await sys.caseflowService.createCarePlan(c.id, {
goals: ['空腹血糖 < 5.1 mmol/L'],
interventions: [{ kind: 'lifestyle', description: '饮食控制 + 餐后散步' }],
followUpFrequency: 'weekly',
});
expect(plan.goals.length).toBe(1);
// 提醒下发
const reminder = await sys.reminderService.dispatch({ patientId: patient.id, type: 'measurement' });
expect(reminder.effectiveType).toBe('measurement');
expect(sys.notification.findByRecipient(patient.id).length).toBeGreaterThan(0);
});
});
describe('S3 子痫前期红旗症状旁路(REQ-5', () => {
it('危急组合 → 即时就医提示 + 通知管理师/医生 + 升级高风险', async () => {
const sys = makeSystem();
const patient = await sys.patientService.create({ name: '小雅', age: 30, lmp: '2026-01-01' });
const result = await sys.redflagService.evaluate(patient.id, {
systolicBp: 165,
diastolicBp: 112,
symptoms: ['severe_headache'],
});
expect(result.triggered).toBe(true);
expect(result.hits.map((h) => h.ruleId)).toContain('RF-PREECLAMPSIA');
expect(result.patientAdvice).toContain('就医');
// 通知:孕妇 + 管理师 + 医生,均为紧急
const msgs = sys.notification.findByRecipient(patient.id);
const audiences = msgs.filter((m) => m.urgent).map((m) => m.audience);
expect(audiences).toEqual(expect.arrayContaining(['patient', 'case_manager', 'physician']));
// 个案升级为高风险
const c = await sys.caseflowService.getCaseByPatient(patient.id);
expect(c.riskLevel).toBe('high');
});
});
describe('S4 深夜疑问"我能吃这个吗"REQ-7', () => {
it('命中知识 → 带溯源作答;无依据 → 不超纲并建议就医', async () => {
const sys = makeSystem();
await sys.knowledgeService.create({
category: 'guideline',
title: '孕期饮食与血糖',
content: '控制精制碳水、少量多餐;水果适量并计入总量。',
keywords: ['饮食', '血糖', '水果', '能吃'],
source: '某权威指南',
authority: 'authoritative',
});
const grounded = await sys.aiService.ask('孕期能吃水果吗');
expect(grounded.grounded).toBe(true);
expect(grounded.citations.length).toBeGreaterThan(0);
expect(grounded.citations[0].source).toBe('某权威指南');
const ungrounded = await sys.aiService.ask('明天会下雨吗');
expect(ungrounded.grounded).toBe(false);
expect(ungrounded.citations).toHaveLength(0);
});
});
describe('S5 趋势预警/预测(REQ-3.5/4', () => {
it('纵向观测按孕周累积,构成趋势分析的数据基础', async () => {
const sys = makeSystem();
const patient = await sys.patientService.create({ name: '小雅', age: 30, lmp: '2026-01-01' });
await sys.observationService.record(patient.id, { indicator: 'fasting_glucose', value: 4.8 });
await sys.observationService.record(patient.id, { indicator: 'fasting_glucose', value: 5.0 });
const series = await sys.observationService.list(patient.id);
expect(series.length).toBe(2);
// 每条带孕周上下文,支撑后续时序趋势
expect(series.every((o) => typeof o.gestationalWeeks === 'number')).toBe(true);
});
// 趋势异常检测(REQ-3.5,V2)与预测模型(REQ-4,V3,需回顾性+前瞻性验证)超出 V1 范围
it.todo('S5 时序趋势异常检测(REQ-3.5)为 V2');
it.todo('S5 趋势预测模型(REQ-4)为 V3,需经验证');
});
describe('S6 提升坚持度、缓解焦虑(REQ-9', () => {
it('高风险孕妇运动提醒自动替换为休息(REQ-9.2 安全约束)', async () => {
const sys = makeSystem();
// 多胎 + 既往GDM → score 4 → 基线高风险
const patient = await sys.patientService.create({
name: '小雅',
age: 31,
lmp: '2026-01-01',
multipleGestation: true,
historyGdm: true,
});
expect(patient.initialRiskLevel).toBe('high');
const reminder = await sys.reminderService.dispatch({ patientId: patient.id, type: 'exercise' });
expect(reminder.effectiveType).toBe('rest');
expect(reminder.adjustedForRisk).toBe(true);
});
it('非高风险孕妇运动提醒保持运动', async () => {
const sys = makeSystem();
const patient = await sys.patientService.create({ name: '小雅', age: 28, lmp: '2026-01-01' });
const reminder = await sys.reminderService.dispatch({ patientId: patient.id, type: 'exercise' });
expect(reminder.effectiveType).toBe('exercise');
expect(reminder.adjustedForRisk).toBe(false);
});
// 游戏化(积分/徽章/小游戏,REQ-9.3/9.4)为 V2
it.todo('S6 游戏化激励(REQ-9.3/9.4)为 V2');
});
describe('S7 管理师 PC 端批量管理(REQ-13.3', () => {
it('多名孕妇入列,提供风险分层作为工作台排序依据', async () => {
const sys = makeSystem();
await sys.patientService.create({ name: '低风险', age: 28, lmp: '2026-01-01' });
await sys.patientService.create({ name: '中风险', age: 31, lmp: '2026-01-01', historyGdm: true });
await sys.patientService.create({
name: '高风险',
age: 36,
lmp: '2026-01-01',
multipleGestation: true,
historyPih: true,
});
const list = await sys.patientService.list();
expect(list.length).toBe(3);
// 每名孕妇均带初始风险分层(admin-web 工作列表据此按 高→中→低 排序)
const levels = list.map((p) => p.initialRiskLevel).sort();
expect(levels).toEqual(['high', 'low', 'medium']);
});
});
});