/** * 端到端场景演练(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']); }); }); });