Files
AIclinicalresearch/backend/prisma/seed-iit-qc-rules.ts
HaHafeng 5db4a7064c feat(iit): Implement real-time quality control system
Summary:

- Add 4 new database tables: iit_field_metadata, iit_qc_logs, iit_record_summary, iit_qc_project_stats

- Implement pg-boss debounce mechanism in WebhookController

- Refactor QC Worker for dual output: QC logs + record summary

- Enhance HardRuleEngine to support form-based rule filtering

- Create QcService for QC data queries

- Optimize ChatService with new intents: query_enrollment, query_qc_status

- Add admin batch operations: one-click full QC + one-click full summary

- Create IIT Admin management module: project config, QC rules, user mapping

Status: Code complete, pending end-to-end testing
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:56:11 +08:00

360 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* IIT Manager Agent - 质控规则初始化脚本
*
* 项目:原发性痛经队列研究
*
* 使用方法:
* npx tsx prisma/seed-iit-qc-rules.ts
*
* 创建日期2026-02-07
*/
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ============================================================
// 项目配置
// ============================================================
const PROJECT_ID = 'test0102-project-id'; // 需要替换为实际的项目 ID
// ============================================================
// 纳入标准规则
// ============================================================
const INCLUSION_RULES = [
{
id: 'inc_001',
name: '年龄范围检查',
field: 'age',
logic: {
and: [
{ '>=': [{ var: 'age' }, 16] },
{ '<=': [{ var: 'age' }, 35] }
]
},
message: '年龄不在 16-35 岁范围内',
severity: 'error',
category: 'inclusion'
},
{
id: 'inc_002',
name: '出生日期范围检查',
field: 'birth_date',
logic: {
and: [
{ '>=': [{ var: 'birth_date' }, '1989-01-01'] },
{ '<=': [{ var: 'birth_date' }, '2008-01-01'] }
]
},
message: '出生日期不在 1989-01-01 至 2008-01-01 范围内',
severity: 'error',
category: 'inclusion'
},
{
id: 'inc_003',
name: '月经周期规律性检查',
field: 'menstrual_cycle',
logic: {
and: [
{ '>=': [{ var: 'menstrual_cycle' }, 21] },
{ '<=': [{ var: 'menstrual_cycle' }, 35] }
]
},
message: '月经周期不在 21-35 天范围内28±7天',
severity: 'error',
category: 'inclusion'
},
{
id: 'inc_004',
name: 'VAS 评分检查',
field: 'vas_score',
logic: { '>=': [{ var: 'vas_score' }, 4] },
message: 'VAS 疼痛评分 < 4 分,不符合入组条件',
severity: 'error',
category: 'inclusion'
},
{
id: 'inc_005',
name: '知情同意书签署检查',
field: 'informed_consent',
logic: { '==': [{ var: 'informed_consent' }, 1] },
message: '未签署知情同意书',
severity: 'error',
category: 'inclusion'
}
];
// ============================================================
// 排除标准规则
// ============================================================
const EXCLUSION_RULES = [
{
id: 'exc_001',
name: '继发性痛经排除',
field: 'secondary_dysmenorrhea',
logic: { '!=': [{ var: 'secondary_dysmenorrhea' }, 1] },
message: '存在继发性痛经(盆腔炎、子宫内膜异位症、子宫腺肌病等)',
severity: 'error',
category: 'exclusion'
},
{
id: 'exc_002',
name: '妊娠哺乳期排除',
field: 'pregnancy_lactation',
logic: { '!=': [{ var: 'pregnancy_lactation' }, 1] },
message: '妊娠或哺乳期妇女,不符合入组条件',
severity: 'error',
category: 'exclusion'
},
{
id: 'exc_003',
name: '严重疾病排除',
field: 'severe_disease',
logic: { '!=': [{ var: 'severe_disease' }, 1] },
message: '合并有心脑血管、肝、肾、造血系统等严重疾病或精神病',
severity: 'error',
category: 'exclusion'
},
{
id: 'exc_004',
name: '月经周期不规律排除',
field: 'irregular_menstruation',
logic: { '!=': [{ var: 'irregular_menstruation' }, 1] },
message: '月经周期不规律或间歇性痛经发作',
severity: 'error',
category: 'exclusion'
}
];
// ============================================================
// 血常规变量范围规则
// ============================================================
const LAB_VALUE_RULES = [
// 1. 白细胞计数
{ id: 'lab_001', name: '白细胞计数', field: 'wbc', min: 3.5, max: 9.5, unit: '*10^9/L' },
// 2. 红细胞计数
{ id: 'lab_002', name: '红细胞计数', field: 'rbc', min: 3.8, max: 5.1, unit: '*10^12/L' },
// 3. 血红蛋白
{ id: 'lab_003', name: '血红蛋白', field: 'hgb', min: 115, max: 150, unit: 'g/L' },
// 4. 红细胞压积
{ id: 'lab_004', name: '红细胞压积', field: 'hct', min: 35, max: 45, unit: '%' },
// 5. 平均红细胞体积
{ id: 'lab_005', name: '平均红细胞体积', field: 'mcv', min: 82, max: 100, unit: 'fL' },
// 6. 平均血红蛋白量
{ id: 'lab_006', name: '平均血红蛋白量', field: 'mch', min: 27, max: 34, unit: 'pg' },
// 7. 平均血红蛋白浓度
{ id: 'lab_007', name: '平均血红蛋白浓度', field: 'mchc', min: 316, max: 354, unit: 'g/L' },
// 8. 红细胞体积分布宽度-SD
{ id: 'lab_008', name: '红细胞体积分布宽度-SD', field: 'rdw_sd', min: 37, max: 51, unit: 'fL' },
// 9. 红细胞体积分布宽度-CV
{ id: 'lab_009', name: '红细胞体积分布宽度-CV', field: 'rdw_cv', min: 0, max: 14.9, unit: '%' },
// 10. 血小板计数
{ id: 'lab_010', name: '血小板计数', field: 'plt', min: 125, max: 350, unit: '*10^9/L' },
// 11. 血小板平均体积
{ id: 'lab_011', name: '血小板平均体积', field: 'mpv', min: 7.2, max: 13.2, unit: 'fL' },
// 12. 血小板体积分布宽度
{ id: 'lab_012', name: '血小板体积分布宽度', field: 'pdw', min: 9, max: 13, unit: 'fL' },
// 13. 血小板比积
{ id: 'lab_013', name: '血小板比积', field: 'pct', min: 0.18, max: 0.22, unit: '%' },
// 14. 大血小板比率
{ id: 'lab_014', name: '大血小板比率', field: 'p_lcr', min: 13, max: 43, unit: '%' },
// 15. 中性粒细胞绝对值
{ id: 'lab_015', name: '中性粒细胞绝对值', field: 'neut_abs', min: 1.8, max: 6.3, unit: '*10^9/L' },
// 16. 淋巴细胞绝对值
{ id: 'lab_016', name: '淋巴细胞绝对值', field: 'lymph_abs', min: 1.1, max: 3.2, unit: '*10^9/L' },
// 17. 单核细胞绝对值
{ id: 'lab_017', name: '单核细胞绝对值', field: 'mono_abs', min: 0.1, max: 0.6, unit: '*10^9/L' },
// 18. 嗜酸细胞绝对值
{ id: 'lab_018', name: '嗜酸细胞绝对值', field: 'eo_abs', min: 0.02, max: 0.52, unit: '*10^9/L' },
// 19. 嗜碱细胞绝对值
{ id: 'lab_019', name: '嗜碱细胞绝对值', field: 'baso_abs', min: 0, max: 0.06, unit: '*10^9/L' },
// 20. 中性粒细胞相对值
{ id: 'lab_020', name: '中性粒细胞相对值', field: 'neut_pct', min: 40, max: 75, unit: '%' },
// 21. 淋巴细胞相对值
{ id: 'lab_021', name: '淋巴细胞相对值', field: 'lymph_pct', min: 20, max: 50, unit: '%' },
// 22. 单核细胞相对值
{ id: 'lab_022', name: '单核细胞相对值', field: 'mono_pct', min: 3, max: 10, unit: '%' },
// 23. 嗜酸细胞相对值
{ id: 'lab_023', name: '嗜酸细胞相对值', field: 'eo_pct', min: 0.4, max: 8, unit: '%' },
// 24. 嗜碱细胞相对值
{ id: 'lab_024', name: '嗜碱细胞相对值', field: 'baso_pct', min: 0, max: 1, unit: '%' },
];
// 将实验室检查转换为 JSON Logic 规则
const LAB_RULES = LAB_VALUE_RULES.map(item => ({
id: item.id,
name: `${item.name}范围检查`,
field: item.field,
logic: {
or: [
{ '==': [{ var: item.field }, null] }, // 允许为空(可选检查项)
{
and: [
{ '>=': [{ var: item.field }, item.min] },
{ '<=': [{ var: item.field }, item.max] }
]
}
]
},
message: `${item.name}超出正常范围(${item.min}-${item.max} ${item.unit}`,
severity: 'warning',
category: 'lab_values',
metadata: { min: item.min, max: item.max, unit: item.unit }
}));
// ============================================================
// 字段映射配置
// ============================================================
const FIELD_MAPPINGS = [
// 基本信息
{ aliasName: '年龄', actualName: 'age', fieldType: 'number', fieldLabel: '年龄' },
{ aliasName: 'age', actualName: 'age', fieldType: 'number', fieldLabel: '年龄' },
{ aliasName: '出生日期', actualName: 'birth_date', fieldType: 'date', fieldLabel: '出生日期' },
{ aliasName: 'birth_date', actualName: 'birth_date', fieldType: 'date', fieldLabel: '出生日期' },
// 入排标准相关
{ aliasName: '月经周期', actualName: 'menstrual_cycle', fieldType: 'number', fieldLabel: '月经周期(天)' },
{ aliasName: 'menstrual_cycle', actualName: 'menstrual_cycle', fieldType: 'number', fieldLabel: '月经周期(天)' },
{ aliasName: 'VAS评分', actualName: 'vas_score', fieldType: 'number', fieldLabel: 'VAS疼痛评分' },
{ aliasName: 'vas_score', actualName: 'vas_score', fieldType: 'number', fieldLabel: 'VAS疼痛评分' },
{ aliasName: '知情同意', actualName: 'informed_consent', fieldType: 'radio', fieldLabel: '是否签署知情同意书' },
{ aliasName: 'informed_consent', actualName: 'informed_consent', fieldType: 'radio', fieldLabel: '是否签署知情同意书' },
// 排除标准相关
{ aliasName: '继发性痛经', actualName: 'secondary_dysmenorrhea', fieldType: 'radio', fieldLabel: '继发性痛经' },
{ aliasName: '妊娠哺乳', actualName: 'pregnancy_lactation', fieldType: 'radio', fieldLabel: '妊娠或哺乳期' },
{ aliasName: '严重疾病', actualName: 'severe_disease', fieldType: 'radio', fieldLabel: '严重疾病' },
{ aliasName: '月经不规律', actualName: 'irregular_menstruation', fieldType: 'radio', fieldLabel: '月经周期不规律' },
// 血常规字段映射
{ aliasName: '白细胞', actualName: 'wbc', fieldType: 'number', fieldLabel: '白细胞计数' },
{ aliasName: '红细胞', actualName: 'rbc', fieldType: 'number', fieldLabel: '红细胞计数' },
{ aliasName: '血红蛋白', actualName: 'hgb', fieldType: 'number', fieldLabel: '血红蛋白' },
{ aliasName: '血小板', actualName: 'plt', fieldType: 'number', fieldLabel: '血小板计数' },
// ... 更多字段可按需添加
];
// ============================================================
// 主函数
// ============================================================
async function main() {
console.log('🚀 开始初始化 IIT Manager 质控规则...\n');
// 1. 先获取项目 ID
const project = await prisma.iitProject.findFirst({
where: { name: 'test0102' }
});
if (!project) {
console.error('❌ 未找到项目 test0102请先创建项目');
console.log('💡 提示:可以在 iit_schema.projects 表中创建项目');
return;
}
const projectId = project.id;
console.log(`✅ 找到项目: ${project.name} (${projectId})\n`);
// 2. 创建质控技能配置
console.log('📋 创建质控技能配置...');
const allRules = [...INCLUSION_RULES, ...EXCLUSION_RULES, ...LAB_RULES];
await prisma.iitSkill.upsert({
where: {
projectId_skillType: {
projectId: projectId,
skillType: 'qc_process'
}
},
update: {
name: '原发性痛经队列研究-入组质控',
description: '包含纳入标准、排除标准、血常规范围检查',
config: {
version: '1.0',
rules: allRules,
summary: {
totalRules: allRules.length,
inclusionRules: INCLUSION_RULES.length,
exclusionRules: EXCLUSION_RULES.length,
labRules: LAB_RULES.length
}
},
isActive: true,
triggerType: 'webhook',
updatedAt: new Date()
},
create: {
projectId: projectId,
skillType: 'qc_process',
name: '原发性痛经队列研究-入组质控',
description: '包含纳入标准、排除标准、血常规范围检查',
config: {
version: '1.0',
rules: allRules,
summary: {
totalRules: allRules.length,
inclusionRules: INCLUSION_RULES.length,
exclusionRules: EXCLUSION_RULES.length,
labRules: LAB_RULES.length
}
},
isActive: true,
triggerType: 'webhook'
}
});
console.log(` ✅ 已创建质控技能,共 ${allRules.length} 条规则`);
console.log(` - 纳入标准: ${INCLUSION_RULES.length}`);
console.log(` - 排除标准: ${EXCLUSION_RULES.length}`);
console.log(` - 血常规范围: ${LAB_RULES.length}\n`);
// 3. 创建字段映射
console.log('🔗 创建字段映射...');
for (const mapping of FIELD_MAPPINGS) {
await prisma.iitFieldMapping.upsert({
where: {
projectId_aliasName: {
projectId: projectId,
aliasName: mapping.aliasName
}
},
update: {
actualName: mapping.actualName,
fieldType: mapping.fieldType,
fieldLabel: mapping.fieldLabel
},
create: {
projectId: projectId,
aliasName: mapping.aliasName,
actualName: mapping.actualName,
fieldType: mapping.fieldType,
fieldLabel: mapping.fieldLabel
}
});
}
console.log(` ✅ 已创建 ${FIELD_MAPPINGS.length} 条字段映射\n`);
// 4. 输出汇总
console.log('=' .repeat(60));
console.log('📊 初始化完成汇总');
console.log('=' .repeat(60));
console.log(`项目名称: ${project.name}`);
console.log(`项目 ID: ${projectId}`);
console.log(`质控规则总数: ${allRules.length}`);
console.log(`字段映射总数: ${FIELD_MAPPINGS.length}`);
console.log('=' .repeat(60));
console.log('\n✅ IIT Manager 质控规则初始化完成!');
}
main()
.catch((e) => {
console.error('❌ 初始化失败:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});