Browse Source

质控提示词修改

zhaohan 7 months ago
parent
commit
09f57e0723

+ 128 - 58
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/MedicalRecordQc123Service.java

@@ -90,7 +90,7 @@ public class MedicalRecordQc123Service {
      */
     
     public int processAllUncontrolledRecords() {
-        return processAllUncontrolledRecords(false);  // 启用并行,使用4个线程
+        return processAllUncontrolledRecords(true);  // 启用并行,使用4个线程
     }
 
     /**
@@ -207,8 +207,8 @@ public class MedicalRecordQc123Service {
             Map.entry("lab_reports", "检查检验规则清单"),                 // 检验报告单
             Map.entry("exam_reports", "检查检验规则清单"),                // 检查报告单
             Map.entry("orders", "医嘱规则清单"),                     // 医嘱记录
-            Map.entry("discharge_summary", "出院(死亡)记录规则清单")      // 出院小结
-            // Map.entry("consent_forms", "知情同意书")              // 知情同意书 - 暂时注释掉
+            Map.entry("discharge_summary", "出院(死亡)记录规则清单"), // 出院小结
+            Map.entry("consent_forms", "知情同意书")              // 知情同意书 - 暂时注释掉
     );
 
 
@@ -277,10 +277,10 @@ public class MedicalRecordQc123Service {
             }
 
             // 5. 处理 common 规则(分开质控)
-            processCommonRules(recordId, commonRules, stepMap);
+            processCommonRules(recordId, commonRules, stepMap, record);
 
             // 6. 处理 specific 规则(同时质控)
-            processSpecificRules(recordId, specificRules, stepMap);
+            processSpecificRules(recordId, specificRules, stepMap, record);
 
             // 7. 全部完成,更新状态为已质控
             patientRecordMapper.updateStatusById(recordId, 2);
@@ -314,15 +314,15 @@ public class MedicalRecordQc123Service {
                 new QcStep(11, SECTION_LABEL_MAP.get("lab_reports"), record.getLabReports()),
                 new QcStep(12, SECTION_LABEL_MAP.get("exam_reports"), record.getExamReports()),
                 new QcStep(13, SECTION_LABEL_MAP.get("orders"), record.getOrders()),
-                new QcStep(14, SECTION_LABEL_MAP.get("discharge_summary"), record.getDischargeSummary())
-                // new QcStep(15, SECTION_LABEL_MAP.get("consent_forms"), record.getConsentForms()) // 暂时注释掉知情同意书质控
+                new QcStep(14, SECTION_LABEL_MAP.get("discharge_summary"), record.getDischargeSummary()),
+                new QcStep(15, SECTION_LABEL_MAP.get("consent_forms"), record.getConsentForms()) // 暂时注释掉知情同意书质控
         );
     }
 
     /**
      * 处理 common 规则(分开质控)
      */
-    private void processCommonRules(String recordId, List<QualityControlRuleVo> commonRules, Map<Integer, QcStep> stepMap) {
+    private void processCommonRules(String recordId, List<QualityControlRuleVo> commonRules, Map<Integer, QcStep> stepMap, PatientRecord record) {
         log.info("🔄 开始处理 common 规则,共 {} 条", commonRules.size());
         
         // 按 type 分类 common 规则
@@ -346,16 +346,18 @@ public class MedicalRecordQc123Service {
             
             log.info("📝 病历 {} type={} 开始质控,共 {} 条规则", recordId, type, rules.size());
             
-            // 判断是否需要分批处理(type=2病程记录 或 type=13医嘱记录 或 type=4查房记录)
-            boolean needBatch = (type == 2 || type == 4 || type == 13) && isJsonArray(step.content);
+            // 判断是否需要分批处理(type=2病程记录 或 type=13医嘱记录 或 type=4查房记录 或 type=15知情同意书
+            boolean needBatch = (type == 2 || type == 4 || type == 13 || type == 15) && isJsonArray(step.content);
             
             if (needBatch) {
                 // 大数据量类型,使用分批处理,根据 type 设置不同的批次大小
                 int batchSize;
                 if (type == 13) {
-                    batchSize = 40;  // 医嘱记录:30条一组
+                    batchSize = 40;  // 医嘱记录:40条一组
+                } else if (type == 15) {
+                    batchSize = 5;   // 知情同意书:4条一组
                 } else {
-                    batchSize = 7;   // 病程记录(type=2)和查房记录(type=4):5条一组
+                    batchSize = 7;   // 病程记录(type=2)和查房记录(type=4):7条一组
                 }
                 
                 // 收集所有规则的不通过项
@@ -365,7 +367,7 @@ public class MedicalRecordQc123Service {
                 for (QualityControlRuleVo rule : rules) {
                     try {
                         String ragResult = fetchMatchingRules(rule.getRuleContent());
-                        List<Object> ruleFailures = batchQcForRule(recordId, type, rule, step.content, batchSize, ragResult);
+                        List<Object> ruleFailures = batchQcForRule(recordId, type, rule, step.content, batchSize, ragResult, record);
                         allFailures.addAll(ruleFailures);
                         log.info("✅ 规则 [{}] type={} 分批质控完成(批次大小={}),不通过 {} 条", 
                                 rule.getRuleCode(), type, batchSize, ruleFailures.size());
@@ -401,7 +403,7 @@ public class MedicalRecordQc123Service {
                         String ragResult = fetchMatchingRules(rule.getRuleContent());
                         
                         // 构造提示词(传入病历部分名称)
-                        String prompt = buildPromptWithRule(rule, step.content, ragResult, getTypeName(type));
+                        String prompt = buildPromptWithRule(rule, step.content, ragResult, getTypeName(type), record);
                         
                         // 调用大模型
                         String modelOutput = callLLM(prompt);
@@ -458,7 +460,7 @@ public class MedicalRecordQc123Service {
     /**
      * 处理 specific 规则(同时质控)
      */
-    private void processSpecificRules(String recordId, List<QualityControlRuleVo> specificRules, Map<Integer, QcStep> stepMap) {
+    private void processSpecificRules(String recordId, List<QualityControlRuleVo> specificRules, Map<Integer, QcStep> stepMap, PatientRecord record) {
         log.info("🔄 开始处理 specific 规则,共 {} 条", specificRules.size());
         
         for (QualityControlRuleVo rule : specificRules) {
@@ -469,10 +471,10 @@ public class MedicalRecordQc123Service {
                 
                 if (hasCourseRecords && hasOrders) {
                     // 按日期融合质控
-                    processSpecificRuleWithDateMerge(recordId, rule, stepMap);
+                    processSpecificRuleWithDateMerge(recordId, rule, stepMap, record);
                 } else {
                     // 普通的 specific 规则处理
-                    processSpecificRuleNormally(recordId, rule, stepMap);
+                    processSpecificRuleNormally(recordId, rule, stepMap, record);
                 }
                 
             } catch (Exception e) {
@@ -484,7 +486,7 @@ public class MedicalRecordQc123Service {
     /**
      * 普通的 specific 规则处理(不需要按日期融合)
      */
-    private void processSpecificRuleNormally(String recordId, QualityControlRuleVo rule, Map<Integer, QcStep> stepMap) {
+    private void processSpecificRuleNormally(String recordId, QualityControlRuleVo rule, Map<Integer, QcStep> stepMap, PatientRecord record) {
         LocalDateTime start = LocalDateTime.now();
         
         // 合并 qcTypeList 中所有 type 的病历内容
@@ -517,7 +519,7 @@ public class MedicalRecordQc123Service {
         String combinedSectionName = validTypes.stream()
                 .map(this::getTypeName)
                 .collect(Collectors.joining(" + "));
-        String prompt = buildPromptWithRule(rule, mergedContent.toString(), ragResult, combinedSectionName);
+        String prompt = buildPromptWithRule(rule, mergedContent.toString(), ragResult, combinedSectionName, record);
         
         // 调用大模型
         String modelOutput = callLLM(prompt);
@@ -612,7 +614,7 @@ public class MedicalRecordQc123Service {
     /**
      * 按日期融合 type=2 和 type=13 进行质控
      */
-    private void processSpecificRuleWithDateMerge(String recordId, QualityControlRuleVo rule, Map<Integer, QcStep> stepMap) {
+    private void processSpecificRuleWithDateMerge(String recordId, QualityControlRuleVo rule, Map<Integer, QcStep> stepMap, PatientRecord record) {
         log.info("📝 规则 [{}] 开始按日期融合质控 types={}", rule.getRuleCode(), rule.getQcTypeList());
         
         // 1. 解析病程记录(以病程记录的日期为主时间轴)
@@ -678,7 +680,7 @@ public class MedicalRecordQc123Service {
             // 调用大模型质控(使用融合质控专用提示词)
             String ragResult = fetchMatchingRules(rule.getRuleContent());
             String dateRange = batchDates.get(0) + " ~ " + batchDates.get(batchDates.size() - 1);
-            String prompt = buildPromptForMergedRecords(rule, dateRange, batchContent.toString(), ragResult);
+            String prompt = buildPromptForMergedRecords(rule, dateRange, batchContent.toString(), ragResult, record);
             String modelOutput = callLLM(prompt);
             String processedOutput = processModelOutput(modelOutput);
             
@@ -896,7 +898,7 @@ public class MedicalRecordQc123Service {
     private String getTypeName(int type) {
         String[] names = {"入院志", "首次病程记录", "病程记录", "阶段小结", "查房记录",
                 "会诊记录", "疑难病例讨论", "超长住院讨论", "死亡病例讨论", "抢救记录",
-                "手术记录", "检验报告", "检查报告", "医嘱记录", "出院小结"};
+                "手术记录", "检验报告", "检查报告", "医嘱记录", "出院小结", "知情同意书"};
         return type >= 0 && type < names.length ? names[type] : "未知类型";
     }
     
@@ -915,7 +917,7 @@ public class MedicalRecordQc123Service {
      * 针对单条规则的分批质控(返回不通过项列表)
      */
     private List<Object> batchQcForRule(String recordId, int qcType, QualityControlRuleVo rule, 
-                                        String jsonArrayContent, int batchSize, String ragResult) {
+                                        String jsonArrayContent, int batchSize, String ragResult, PatientRecord record) {
         LocalDateTime start = LocalDateTime.now();
         
         try {
@@ -950,7 +952,7 @@ public class MedicalRecordQc123Service {
                         rule.getRuleCode(), batchNo, totalBatches, startIdx, endIdx - 1, batchItems.size());
                 
                 String batchContent = String.join("\n\n", batchItems);
-                String prompt = buildPromptWithRule(rule, batchContent, ragResult, getTypeName(qcType));
+                String prompt = buildPromptWithRule(rule, batchContent, ragResult, getTypeName(qcType), record);
                 String batchOutput = callLLM(prompt);
                 String processedOutput = processModelOutput(batchOutput);
                 
@@ -994,10 +996,10 @@ public class MedicalRecordQc123Service {
     /**
      * 构造提示词(用于按日期融合的病程记录和医嘱)
      */
-    private String buildPromptForMergedRecords(QualityControlRuleVo rule, String date, String content, String ragResult) {
+    private String buildPromptForMergedRecords(QualityControlRuleVo rule, String date, String content, String ragResult, PatientRecord record) {
         StringBuilder sb = new StringBuilder();
         
-        sb.append("你是一名医疗病历质控员,需要依据【质控规则】对病历进行审查。\n\n");
+        sb.append("你是一名医疗病历质控员,需要依据【质控规则】对病历进行审查。如果提供了【参考内容】,请参考这些内容进行判断。\n\n");
         
         sb.append("***【质控规则】***\n");
         sb.append("规则编码: ").append(rule.getRuleCode()).append("\n");
@@ -1008,7 +1010,8 @@ public class MedicalRecordQc123Service {
         sb.append("\n");
 
         if (ragResult != null && !ragResult.isEmpty()) {
-            sb.append("***【参考内容】***:\n")
+            sb.append("***【参考内容】***\n")
+                    .append("以下是与当前规则相关的参考示例,正例表示符合要求,反例表示不符合要求:\n")
                     .append(ragResult).append("\n\n");
         }
         
@@ -1020,6 +1023,18 @@ public class MedicalRecordQc123Service {
         sb.append("根据给出的参考内容进行判断,正例是符合改规则质控要求,反例为不符合质控要求。\n");
         sb.append("请针对这两部分的融合内容进行质控审查。\n\n");
         
+        // 添加病人基本信息
+        sb.append("【病人基本信息】\n");
+        if (record.getName() != null) sb.append("姓名: ").append(record.getName()).append("\n");
+        if (record.getGender() != null) sb.append("性别: ").append(record.getGender()).append("\n");
+        if (record.getAge() != null) sb.append("年龄: ").append(record.getAge()).append("岁\n");
+        if (record.getAdmissionNo() != null) sb.append("入院编号: ").append(record.getAdmissionNo()).append("\n");
+        if (record.getAdmissionDept() != null) sb.append("入院科室: ").append(record.getAdmissionDept()).append("\n");
+        if (record.getAdmissionDate() != null) sb.append("入院日期: ").append(record.getAdmissionDate()).append("\n");
+        if (record.getDischargeDate() != null) sb.append("出院日期: ").append(record.getDischargeDate()).append("\n");
+        if (record.getAttendingDoctor() != null) sb.append("主治医生: ").append(record.getAttendingDoctor()).append("\n");
+        sb.append("\n");
+        
         sb.append("【病历原文】:\n")
                 .append(content).append("\n\n");
         
@@ -1039,6 +1054,7 @@ public class MedicalRecordQc123Service {
         sb.append("- 禁止输出 Markdown 代码块符号(如 ```json)\n");
         sb.append("- 只针对当前规则进行质控,不要关联其他规则\n");
         sb.append("- 所有结论必须严格基于病历原文记载\n");
+        sb.append("- 输出中不要出现参考内容\n");
         sb.append("- 质控时要综合考虑病程记录和医嘱记录的关联性和一致性\n");
         
         return sb.toString();
@@ -1047,13 +1063,15 @@ public class MedicalRecordQc123Service {
     /**
      * 构造提示词(带规则信息)
      */
-    private String buildPromptWithRule(QualityControlRuleVo rule, String content, String ragResult, String sectionName) {
+    private String buildPromptWithRule(QualityControlRuleVo rule, String content, String ragResult, String sectionName, PatientRecord record) {
         StringBuilder sb = new StringBuilder();
-        
-        sb.append("你是一名医疗病历质控员,需要依据【质控规则】对病历进行审查。\n\n");
-        
+
+        sb.append("你是一名专业的医疗病历质控员,需要依据【质控规则】对病历内容进行审核。\n");
+        sb.append("若提供了【参考内容】,仅用于理解规则,不得直接引用或照抄。\n");
+        sb.append("请严格基于【病历原文】判断是否违反该规则。\n\n");
+
+        // === 质控规则 ===
         sb.append("***【质控规则】***\n");
-        sb.append("以下是本次质控的规则:\n");
         sb.append("规则编码: ").append(rule.getRuleCode()).append("\n");
         sb.append("规则内容: ").append(rule.getRuleContent()).append("\n");
         if (rule.getRuleDesc() != null && !rule.getRuleDesc().isEmpty()) {
@@ -1061,38 +1079,87 @@ public class MedicalRecordQc123Service {
         }
         sb.append("\n");
 
+        // === 参考内容 ===
         if (ragResult != null && !ragResult.isEmpty()) {
             sb.append("***【参考内容】***\n");
-            sb.append("根据给出的参考内容进行判断,正例是符合改规则质控要求,反例为不符合质控要求\n");
-            sb.append("以下是相关的正反例参考:\n")
-                    .append(ragResult).append("\n\n");
+            sb.append("以下为与当前规则相关的参考示例,仅供理解标准使用:\n");
+            sb.append("正例代表符合要求,反例代表不符合要求。\n");
+            sb.append(ragResult).append("\n\n");
         }
-        sb.append("\n");
-        
-        sb.append("【病历原文】\n");
-        sb.append("以下是").append(sectionName != null ? sectionName : "病历").append("部分:\n")
-                .append(content).append("\n\n");
 
-        sb.append("1.输出格式:\n");
-        sb.append("请严格按照以下要求输出:\n");
-        sb.append("如果病历完全符合所有规则:[{\"result\": \"pass\", \"message\": \"病例校验通过\"}]\n\n");
-        
-        sb.append("如果存在不符合规则:逐条输出 JSON 对象,数组形式,每个对象包含以下字段:\n");
-        sb.append("- result: 固定为 \"fail\"\n");
-        sb.append("- detail: 违规位置及内容(如\\\"入院记录-主诉\\\")\n");
-        sb.append("- content:从病历原文中提取违规内容所在的一小段(约10字左右),保持完整病历内容,便于定位。\n");
-        sb.append("- ruleCode: 对应规则编号(如 \"").append(rule.getRuleCode()).append("\")\n");
-        sb.append("- ruleContent: 对应规则详情,必须与质控规则完全一致\n");
-        sb.append("- qcResult: 结论,给出违反本条规则的原因,简要描述。\n\n");
-        
-        sb.append("2.注意事项:\n");
-        sb.append("- 禁止输出 Markdown 代码块符号(如 ```json)\n");
-        sb.append("- 只针对当前规则进行质控,不要关联其他规则\n");
-        sb.append("- 所有结论必须严格基于病历原文记载\n");
-        
+        // === 病人基本信息(可选) ===
+        if (shouldShowPatientInfo(sectionName) && record != null) {
+            sb.append("===【病人基本信息】===\n");
+            if (record.getName() != null) sb.append("姓名: ").append(record.getName()).append("\n");
+            if (record.getGender() != null) sb.append("性别: ").append(record.getGender()).append("\n");
+            if (record.getAge() != null) sb.append("年龄: ").append(record.getAge()).append("岁\n");
+            if (record.getAdmissionNo() != null) sb.append("入院编号: ").append(record.getAdmissionNo()).append("\n");
+            if (record.getAdmissionDept() != null) sb.append("入院科室: ").append(record.getAdmissionDept()).append("\n");
+            if (record.getAdmissionDate() != null) sb.append("入院日期: ").append(record.getAdmissionDate()).append("\n");
+            if (record.getDischargeDate() != null) sb.append("出院日期: ").append(record.getDischargeDate()).append("\n");
+            if (record.getAttendingDoctor() != null) sb.append("主治医生: ").append(record.getAttendingDoctor()).append("\n");
+            sb.append("\n");
+        }
+
+        // === 病历原文 ===
+        sb.append("===【病历原文】===\n");
+        sb.append("以下为").append(sectionName != null ? sectionName : "病历").append("部分:\n");
+        sb.append(content).append("\n\n");
+
+        // === 输出格式 ===
+        sb.append("===【输出要求】===\n");
+        sb.append("请严格输出 JSON 数组格式,不得输出解释性文字或额外内容。\n\n");
+
+        sb.append("如果病历完全符合所有规则,请输出:\n");
+        sb.append("[{\"result\": \"pass\", \"message\": \"病例校验通过\"}]\n\n");
+
+        sb.append("如果存在不符合规则,请输出以下格式的数组,每条违规单独一项:\n");
+        sb.append("[{\n");
+        sb.append("  \"result\": \"fail\", // 质控结果,固定为 'fail'\n");
+        sb.append("  \"detail\": \"违规位置及所属内容(如 '入院记录-主诉')\",\n");
+        sb.append("  \"content\": \"病历中约10字左右的违规文本片段\",\n");
+        sb.append("  \"ruleCode\": \"").append(rule.getRuleCode()).append("\", // 对应规则编号\n");
+        sb.append("  \"ruleContent\": \"").append(rule.getRuleContent()).append("\", // 对应规则内容\n");
+        sb.append("  \"qcResult\": \"违反该规则的原因描述\"\n");
+        sb.append("}]\n\n");
+
+        // === 字段解释 ===
+        sb.append("每个字段说明如下:\n");
+        sb.append("- result: 质控结果,固定为 \"fail\" 表示不通过\n");
+        sb.append("- detail: 违规位置及所属内容(如 \"入院记录-主诉\")\n");
+        sb.append("- content: 从病历原文中截取违规片段(约10字左右),保留上下文,便于定位\n");
+        sb.append("- ruleCode: 对应规则编号(如 \"").append(rule.getRuleCode()).append("\"),用于标识当前规则\n");
+        sb.append("- ruleContent: 对应规则内容(如 \"").append(rule.getRuleContent()).append("\"),必须与质控规则完全一致\n");
+        sb.append("- qcResult: 质控结论,只描述违反本条规则的原因,不要包含其他解释\n\n");
+
+        // === 注意事项 ===
+        sb.append("===【注意事项】===\n");
+        sb.append("- 严禁输出 Markdown 代码块(如 ```json)\n");
+        sb.append("- 严禁输出解释性文字、分析过程或无关说明\n");
+        sb.append("- 不得引用或复述【参考内容】中的文字\n");
+        sb.append("- 若规则不适用于当前病历内容,应视为通过\n");
+        sb.append("- 所有结论必须严格基于【病历原文】内容\n");
+
         return sb.toString();
     }
 
+
+    /**
+     * 判断是否需要显示病人基本信息
+     * 只在入院志、首次病程记录、病程记录、阶段小结、查房记录这几个类型中显示
+     */
+    private boolean shouldShowPatientInfo(String sectionName) {
+        if (sectionName == null) {
+            return false;
+        }
+        // 判断是否包含需要显示病人信息的病历类型
+        return sectionName.contains("入院志") 
+            || sectionName.contains("首次病程记录") 
+            || sectionName.contains("病程记录") 
+            || sectionName.contains("阶段小结") 
+            || sectionName.contains("查房记录");
+    }
+    
     /**
      * 调用 RAG 获取匹配规则
      */
@@ -1118,11 +1185,14 @@ public class MedicalRecordQc123Service {
             }
 
             StringBuilder sb = new StringBuilder();
+            int validCount = 0; // 有效内容计数器
             for (int i = 0; i < nearest.size(); i++) {
                 JSONObject item = nearest.getJSONObject(i);
                 String text = item.getString("text");
                 if (text != null && !text.trim().isEmpty()) {
-                    if (sb.length() > 0) sb.append("\n");
+                    validCount++;
+                    if (sb.length() > 0) sb.append("\n\n"); // 两个换行,增加间距
+                    sb.append(validCount).append(". ");
                     sb.append(text.trim());
                 }
             }