|
|
@@ -18,9 +18,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
@@ -29,8 +31,8 @@ import java.util.Set;
|
|
|
import java.util.TreeSet;
|
|
|
import java.util.concurrent.Callable;
|
|
|
import java.util.concurrent.Semaphore;
|
|
|
-import java.util.stream.Collectors;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.stream.Collectors;
|
|
|
import java.util.regex.Matcher;
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
@@ -237,7 +239,7 @@ public class MedicalRecordQc123Service {
|
|
|
|
|
|
// 过滤掉 qc_type_list 为空或 [] 的规则
|
|
|
allRules = allRules.stream()
|
|
|
- .filter(rule -> rule.getQcTypeList() != null && !rule.getQcTypeList().isEmpty())
|
|
|
+ .filter(rule -> !CollectionUtils.isEmpty(rule.getQcTypeList()))
|
|
|
.toList();
|
|
|
|
|
|
log.info("📋 病历 {} 开始质控,共加载 {} 条有效规则", recordId, allRules.size());
|
|
|
@@ -251,7 +253,21 @@ public class MedicalRecordQc123Service {
|
|
|
.filter(rule -> "specific".equalsIgnoreCase(rule.getQcBehavior()))
|
|
|
.toList();
|
|
|
|
|
|
- log.info("📊 规则分类: common={} 条, specific={} 条", commonRules.size(), specificRules.size());
|
|
|
+ List<QualityControlRuleVo> unknownRules = allRules.stream()
|
|
|
+ .filter(rule -> !"common".equalsIgnoreCase(rule.getQcBehavior())
|
|
|
+ && !"specific".equalsIgnoreCase(rule.getQcBehavior()))
|
|
|
+ .toList();
|
|
|
+
|
|
|
+ log.info("📊 规则分类: common={} 条, specific={} 条, 未知类型={} 条",
|
|
|
+ commonRules.size(), specificRules.size(), unknownRules.size());
|
|
|
+
|
|
|
+ if (!unknownRules.isEmpty()) {
|
|
|
+ log.warn("⚠️ 发现 {} 条未知 qc_behavior 的规则: {}",
|
|
|
+ unknownRules.size(),
|
|
|
+ unknownRules.stream()
|
|
|
+ .map(r -> String.format("ID=%s,behavior=%s", r.getId(), r.getQcBehavior()))
|
|
|
+ .collect(Collectors.joining(", ")));
|
|
|
+ }
|
|
|
|
|
|
// 4. 构建病历步骤(用于获取各部分内容)
|
|
|
List<QcStep> steps = buildQcSteps(record);
|
|
|
@@ -286,20 +302,20 @@ public class MedicalRecordQc123Service {
|
|
|
return List.of(
|
|
|
new QcStep(0, SECTION_LABEL_MAP.get("admission_note"), record.getAdmissionNote()),
|
|
|
new QcStep(1, SECTION_LABEL_MAP.get("first_course_record"), record.getFirstCourseRecord()),
|
|
|
- new QcStep(2, SECTION_LABEL_MAP.get("course_records"), record.getCourseRecords() != null ? record.getCourseRecords().toString() : null),
|
|
|
- new QcStep(3, SECTION_LABEL_MAP.get("stage_summary"), record.getStageSummary() != null ? record.getStageSummary().toString() : null),
|
|
|
- new QcStep(4, SECTION_LABEL_MAP.get("ward_rounds"), record.getWardRounds() != null ? record.getWardRounds().toString() : null),
|
|
|
- new QcStep(5, SECTION_LABEL_MAP.get("consultation_records"), record.getConsultationRecords() != null ? record.getConsultationRecords().toString() : null),
|
|
|
- new QcStep(6, SECTION_LABEL_MAP.get("difficult_case_discussion"), record.getDifficultCaseDiscussion() != null ? record.getDifficultCaseDiscussion().toString() : null),
|
|
|
- new QcStep(7, SECTION_LABEL_MAP.get("long_stay_discussion"), record.getLongStayDiscussion() != null ? record.getLongStayDiscussion().toString() : null),
|
|
|
- new QcStep(8, SECTION_LABEL_MAP.get("death_case_discussion"), record.getDeathCaseDiscussion() != null ? record.getDeathCaseDiscussion().toString() : null),
|
|
|
- new QcStep(9, SECTION_LABEL_MAP.get("rescue_records"), record.getRescueRecords() != null ? record.getRescueRecords().toString() : null),
|
|
|
- new QcStep(10, SECTION_LABEL_MAP.get("surgery_records"), record.getSurgeryRecords() != null ? record.getSurgeryRecords().toString() : null),
|
|
|
- new QcStep(11, SECTION_LABEL_MAP.get("lab_reports"), record.getLabReports() != null ? record.getLabReports().toString() : null),
|
|
|
- new QcStep(12, SECTION_LABEL_MAP.get("exam_reports"), record.getExamReports() != null ? record.getExamReports().toString() : null),
|
|
|
- new QcStep(13, SECTION_LABEL_MAP.get("orders"), record.getOrders() != null ? record.getOrders().toString() : null),
|
|
|
+ new QcStep(2, SECTION_LABEL_MAP.get("course_records"), record.getCourseRecords()),
|
|
|
+ new QcStep(3, SECTION_LABEL_MAP.get("stage_summary"), record.getStageSummary()),
|
|
|
+ new QcStep(4, SECTION_LABEL_MAP.get("ward_rounds"), record.getWardRounds()),
|
|
|
+ new QcStep(5, SECTION_LABEL_MAP.get("consultation_records"), record.getConsultationRecords()),
|
|
|
+ new QcStep(6, SECTION_LABEL_MAP.get("difficult_case_discussion"), record.getDifficultCaseDiscussion()),
|
|
|
+ new QcStep(7, SECTION_LABEL_MAP.get("long_stay_discussion"), record.getLongStayDiscussion()),
|
|
|
+ new QcStep(8, SECTION_LABEL_MAP.get("death_case_discussion"), record.getDeathCaseDiscussion()),
|
|
|
+ new QcStep(9, SECTION_LABEL_MAP.get("rescue_records"), record.getRescueRecords()),
|
|
|
+ new QcStep(10, SECTION_LABEL_MAP.get("surgery_records"), record.getSurgeryRecords()),
|
|
|
+ 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() != null ? record.getConsentForms().toString() : null) // 暂时注释掉知情同意书质控
|
|
|
+ // new QcStep(15, SECTION_LABEL_MAP.get("consent_forms"), record.getConsentForms()) // 暂时注释掉知情同意书质控
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -334,22 +350,53 @@ public class MedicalRecordQc123Service {
|
|
|
boolean needBatch = (type == 2 || type == 4 || type == 13) && isJsonArray(step.content);
|
|
|
|
|
|
if (needBatch) {
|
|
|
- // 大数据量类型,使用分批处理
|
|
|
+ // 大数据量类型,使用分批处理,根据 type 设置不同的批次大小
|
|
|
+ int batchSize;
|
|
|
+ if (type == 13) {
|
|
|
+ batchSize = 40; // 医嘱记录:30条一组
|
|
|
+ } else {
|
|
|
+ batchSize = 7; // 病程记录(type=2)和查房记录(type=4):5条一组
|
|
|
+ }
|
|
|
+
|
|
|
+ // 收集所有规则的不通过项
|
|
|
+ List<Object> allFailures = new ArrayList<>();
|
|
|
+ LocalDateTime startTime = LocalDateTime.now();
|
|
|
+
|
|
|
for (QualityControlRuleVo rule : rules) {
|
|
|
try {
|
|
|
String ragResult = fetchMatchingRules(rule.getRuleContent());
|
|
|
- batchQcAggregateForRule(recordId, type, rule, step.content, 30, ragResult);
|
|
|
- log.info("✅ 规则 [{}] type={} 分批质控完成", rule.getRuleCode(), type);
|
|
|
+ List<Object> ruleFailures = batchQcForRule(recordId, type, rule, step.content, batchSize, ragResult);
|
|
|
+ allFailures.addAll(ruleFailures);
|
|
|
+ log.info("✅ 规则 [{}] type={} 分批质控完成(批次大小={}),不通过 {} 条",
|
|
|
+ rule.getRuleCode(), type, batchSize, ruleFailures.size());
|
|
|
} catch (Exception e) {
|
|
|
log.error("❌ 规则 [{}] type={} 分批质控失败: {}", rule.getRuleCode(), type, e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 保存聚合结果(所有规则的结果合并成一条记录)
|
|
|
+ MedicalRecordResult result = new MedicalRecordResult();
|
|
|
+ result.setPatientRecordId(recordId);
|
|
|
+ result.setQcType(type);
|
|
|
+ result.setQcStartTime(startTime);
|
|
|
+ result.setQcEndTime(LocalDateTime.now());
|
|
|
+ result.setQcScore("85");
|
|
|
+ result.setQcComments(allFailures.isEmpty()
|
|
|
+ ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
+ : JSON.toJSONString(allFailures));
|
|
|
+ result.setQcResult(allFailures.isEmpty() ? 1 : 0);
|
|
|
+ result.setCreateDate(LocalDateTime.now());
|
|
|
+ resultMapper.insert(result);
|
|
|
+
|
|
|
+ log.info("📊 type={} 所有 common 规则质控完成,共 {} 条规则,总计不通过 {} 条",
|
|
|
+ type, rules.size(), allFailures.size());
|
|
|
} else {
|
|
|
- // 小数据量类型,直接处理
|
|
|
+ // 小数据量类型,直接处理,收集所有规则的不通过项
|
|
|
+ List<Object> allFailures = new ArrayList<>();
|
|
|
+ LocalDateTime startTime = LocalDateTime.now();
|
|
|
+
|
|
|
for (QualityControlRuleVo rule : rules) {
|
|
|
try {
|
|
|
- LocalDateTime start = LocalDateTime.now();
|
|
|
-
|
|
|
// 用 rule.ruleContent 查询知识库获取正反例
|
|
|
String ragResult = fetchMatchingRules(rule.getRuleContent());
|
|
|
|
|
|
@@ -360,20 +407,26 @@ public class MedicalRecordQc123Service {
|
|
|
String modelOutput = callLLM(prompt);
|
|
|
String processedOutput = processModelOutput(modelOutput);
|
|
|
|
|
|
- // 解析结果
|
|
|
- int qcResult = parseQcResult(processedOutput);
|
|
|
-
|
|
|
- // 保存质控结果
|
|
|
- MedicalRecordResult result = new MedicalRecordResult();
|
|
|
- result.setPatientRecordId(recordId);
|
|
|
- result.setQcType(type);
|
|
|
- result.setQcStartTime(start);
|
|
|
- result.setQcEndTime(LocalDateTime.now());
|
|
|
- result.setQcResult(qcResult);
|
|
|
- result.setQcScore("85");
|
|
|
- result.setQcComments(processedOutput);
|
|
|
- result.setCreateDate(LocalDateTime.now());
|
|
|
- resultMapper.insert(result);
|
|
|
+ // 收集不通过项
|
|
|
+ try {
|
|
|
+ JSONArray arr = JSON.parseArray(processedOutput);
|
|
|
+ if (arr != null && !arr.isEmpty()) {
|
|
|
+ for (int i = 0; i < arr.size(); i++) {
|
|
|
+ Object item = arr.get(i);
|
|
|
+ if (item instanceof JSONObject) {
|
|
|
+ JSONObject obj = (JSONObject) item;
|
|
|
+ if (!"pass".equalsIgnoreCase(obj.getString("result"))) {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("⚠️ 规则 [{}] 返回非JSON格式,按文本纳入聚合", rule.getRuleCode());
|
|
|
+ allFailures.add(processedOutput);
|
|
|
+ }
|
|
|
|
|
|
log.info("✅ 规则 [{}] type={} 质控完成", rule.getRuleCode(), type);
|
|
|
|
|
|
@@ -381,6 +434,23 @@ public class MedicalRecordQc123Service {
|
|
|
log.error("❌ 规则 [{}] type={} 质控失败: {}", rule.getRuleCode(), type, e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 保存聚合结果(所有规则的结果合并成一条记录)
|
|
|
+ MedicalRecordResult result = new MedicalRecordResult();
|
|
|
+ result.setPatientRecordId(recordId);
|
|
|
+ result.setQcType(type);
|
|
|
+ result.setQcStartTime(startTime);
|
|
|
+ result.setQcEndTime(LocalDateTime.now());
|
|
|
+ result.setQcScore("85");
|
|
|
+ result.setQcComments(allFailures.isEmpty()
|
|
|
+ ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
+ : JSON.toJSONString(allFailures));
|
|
|
+ result.setQcResult(allFailures.isEmpty() ? 1 : 0);
|
|
|
+ result.setCreateDate(LocalDateTime.now());
|
|
|
+ resultMapper.insert(result);
|
|
|
+
|
|
|
+ log.info("📊 type={} 所有 common 规则质控完成,共 {} 条规则,总计不通过 {} 条",
|
|
|
+ type, rules.size(), allFailures.size());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -456,19 +526,87 @@ public class MedicalRecordQc123Service {
|
|
|
// 解析结果
|
|
|
int qcResult = parseQcResult(processedOutput);
|
|
|
|
|
|
- // 保存质控结果(使用第一个有效type作为qcType)
|
|
|
- MedicalRecordResult result = new MedicalRecordResult();
|
|
|
- result.setPatientRecordId(recordId);
|
|
|
- result.setQcType(validTypes.get(0)); // 使用第一个type
|
|
|
- result.setQcStartTime(start);
|
|
|
- result.setQcEndTime(LocalDateTime.now());
|
|
|
- result.setQcResult(qcResult);
|
|
|
- result.setQcScore("85");
|
|
|
- result.setQcComments(processedOutput);
|
|
|
- result.setCreateDate(LocalDateTime.now());
|
|
|
- resultMapper.insert(result);
|
|
|
+ // 查询已有的该 type 的结果,聚合进去
|
|
|
+ int targetType = validTypes.get(0);
|
|
|
+ MedicalRecordResult existingResult = resultMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<MedicalRecordResult>()
|
|
|
+ .eq(MedicalRecordResult::getPatientRecordId, recordId)
|
|
|
+ .eq(MedicalRecordResult::getQcType, targetType)
|
|
|
+ .last("LIMIT 1"));
|
|
|
|
|
|
- log.info("✅ 规则 [{}] specific 质控完成", rule.getRuleCode());
|
|
|
+ if (existingResult != null) {
|
|
|
+ // 找到已有记录,聚合当前 specific 规则的结果
|
|
|
+ List<Object> allFailures = new ArrayList<>();
|
|
|
+
|
|
|
+ // 解析已有的 qc_comments
|
|
|
+ try {
|
|
|
+ String existingComments = existingResult.getQcComments();
|
|
|
+ if (existingComments != null && !existingComments.trim().isEmpty()) {
|
|
|
+ JSONArray existingArray = JSON.parseArray(existingComments);
|
|
|
+ if (existingArray != null) {
|
|
|
+ for (int i = 0; i < existingArray.size(); i++) {
|
|
|
+ Object item = existingArray.get(i);
|
|
|
+ if (item instanceof JSONObject) {
|
|
|
+ JSONObject obj = (JSONObject) item;
|
|
|
+ if (!"pass".equalsIgnoreCase(obj.getString("result"))) {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("⚠️ 解析已有 qc_comments 失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合并新的 specific 规则结果
|
|
|
+ try {
|
|
|
+ JSONArray newArray = JSON.parseArray(processedOutput);
|
|
|
+ if (newArray != null) {
|
|
|
+ for (int i = 0; i < newArray.size(); i++) {
|
|
|
+ Object item = newArray.get(i);
|
|
|
+ if (item instanceof JSONObject) {
|
|
|
+ JSONObject obj = (JSONObject) item;
|
|
|
+ if (!"pass".equalsIgnoreCase(obj.getString("result"))) {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("⚠️ 解析新结果失败,按文本追加: {}", e.getMessage());
|
|
|
+ allFailures.add(processedOutput);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新记录
|
|
|
+ existingResult.setQcEndTime(LocalDateTime.now());
|
|
|
+ existingResult.setQcComments(allFailures.isEmpty()
|
|
|
+ ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
+ : JSON.toJSONString(allFailures));
|
|
|
+ existingResult.setQcResult(allFailures.isEmpty() ? 1 : 0);
|
|
|
+ resultMapper.updateById(existingResult);
|
|
|
+
|
|
|
+ log.info("✅ 规则 [{}] specific 质控完成,已聚合到已有记录(总计 {} 条)",
|
|
|
+ rule.getRuleCode(), allFailures.size());
|
|
|
+ } else {
|
|
|
+ // 没有已有记录,新增一条
|
|
|
+ MedicalRecordResult result = new MedicalRecordResult();
|
|
|
+ result.setPatientRecordId(recordId);
|
|
|
+ result.setQcType(targetType);
|
|
|
+ result.setQcStartTime(start);
|
|
|
+ result.setQcEndTime(LocalDateTime.now());
|
|
|
+ result.setQcResult(qcResult);
|
|
|
+ result.setQcScore("85");
|
|
|
+ result.setQcComments(processedOutput);
|
|
|
+ result.setCreateDate(LocalDateTime.now());
|
|
|
+ resultMapper.insert(result);
|
|
|
+
|
|
|
+ log.info("✅ 规则 [{}] specific 质控完成(新建记录)", rule.getRuleCode());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -507,6 +645,7 @@ public class MedicalRecordQc123Service {
|
|
|
LocalDateTime startTime = LocalDateTime.now();
|
|
|
|
|
|
List<String> dateList = new ArrayList<>(mergedByDate.keySet());
|
|
|
+ Collections.sort(dateList); // 按日期升序排序
|
|
|
int batchSize = 3; // 三天一批
|
|
|
int totalBatches = (dateList.size() + batchSize - 1) / batchSize;
|
|
|
|
|
|
@@ -547,13 +686,15 @@ public class MedicalRecordQc123Service {
|
|
|
try {
|
|
|
JSONArray arr = JSON.parseArray(processedOutput);
|
|
|
if (arr != null && !arr.isEmpty()) {
|
|
|
+ int beforeSize = aggregatedFailures.size();
|
|
|
for (int j = 0; j < arr.size(); j++) {
|
|
|
JSONObject obj = arr.getJSONObject(j);
|
|
|
if (obj != null && !"pass".equalsIgnoreCase(obj.getString("result"))) {
|
|
|
aggregatedFailures.add(obj);
|
|
|
}
|
|
|
}
|
|
|
- log.info("📌 批次 {}/{} 解析成功:新增不通过 {} 条", batchNo, totalBatches, arr.size());
|
|
|
+ int addedCount = aggregatedFailures.size() - beforeSize;
|
|
|
+ log.info("📌 批次 {}/{} 解析成功:新增不通过 {} 条", batchNo, totalBatches, addedCount);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
log.warn("⚠️ 批次 {}/{} 返回非JSON格式", batchNo, totalBatches);
|
|
|
@@ -561,22 +702,72 @@ public class MedicalRecordQc123Service {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 4. 保存聚合结果
|
|
|
- MedicalRecordResult result = new MedicalRecordResult();
|
|
|
- result.setPatientRecordId(recordId);
|
|
|
- result.setQcType(2); // 使用病程记录的type
|
|
|
- result.setQcStartTime(startTime);
|
|
|
- result.setQcEndTime(LocalDateTime.now());
|
|
|
- result.setQcScore("85");
|
|
|
- result.setQcComments(aggregatedFailures.isEmpty()
|
|
|
- ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
- : JSON.toJSONString(aggregatedFailures));
|
|
|
- result.setQcResult(aggregatedFailures.isEmpty() ? 1 : 0);
|
|
|
- result.setCreateDate(LocalDateTime.now());
|
|
|
- resultMapper.insert(result);
|
|
|
+ // 4. 查询已有的 type=2 的结果,聚合进去
|
|
|
+ MedicalRecordResult existingResult = resultMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<MedicalRecordResult>()
|
|
|
+ .eq(MedicalRecordResult::getPatientRecordId, recordId)
|
|
|
+ .eq(MedicalRecordResult::getQcType, 2)
|
|
|
+ .last("LIMIT 1"));
|
|
|
|
|
|
- log.info("✅ 规则 [{}] 按日期融合质控完成,共 {} 个日期,不通过 {} 条",
|
|
|
- rule.getRuleCode(), mergedByDate.size(), aggregatedFailures.size());
|
|
|
+ if (existingResult != null) {
|
|
|
+ // 找到已有记录,聚合 common 规则 + 当前 specific 规则的结果
|
|
|
+ List<Object> allFailures = new ArrayList<>();
|
|
|
+
|
|
|
+ // 解析已有的 qc_comments
|
|
|
+ try {
|
|
|
+ String existingComments = existingResult.getQcComments();
|
|
|
+ if (existingComments != null && !existingComments.trim().isEmpty()) {
|
|
|
+ JSONArray existingArray = JSON.parseArray(existingComments);
|
|
|
+ if (existingArray != null) {
|
|
|
+ for (int i = 0; i < existingArray.size(); i++) {
|
|
|
+ Object item = existingArray.get(i);
|
|
|
+ // 过滤掉 pass 的记录
|
|
|
+ if (item instanceof JSONObject) {
|
|
|
+ JSONObject obj = (JSONObject) item;
|
|
|
+ if (!"pass".equalsIgnoreCase(obj.getString("result"))) {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ allFailures.add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("⚠️ 解析已有 qc_comments 失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合并新的 specific 规则结果
|
|
|
+ allFailures.addAll(aggregatedFailures);
|
|
|
+
|
|
|
+ // 更新记录
|
|
|
+ existingResult.setQcEndTime(LocalDateTime.now());
|
|
|
+ existingResult.setQcComments(allFailures.isEmpty()
|
|
|
+ ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
+ : JSON.toJSONString(allFailures));
|
|
|
+ existingResult.setQcResult(allFailures.isEmpty() ? 1 : 0);
|
|
|
+ resultMapper.updateById(existingResult);
|
|
|
+
|
|
|
+ log.info("✅ 规则 [{}] 按日期融合质控完成,共 {} 个日期,新增不通过 {} 条,已聚合到已有记录(总计 {} 条)",
|
|
|
+ rule.getRuleCode(), mergedByDate.size(), aggregatedFailures.size(), allFailures.size());
|
|
|
+ } else {
|
|
|
+ // 没有已有记录,新增一条
|
|
|
+ MedicalRecordResult result = new MedicalRecordResult();
|
|
|
+ result.setPatientRecordId(recordId);
|
|
|
+ result.setQcType(2);
|
|
|
+ result.setQcStartTime(startTime);
|
|
|
+ result.setQcEndTime(LocalDateTime.now());
|
|
|
+ result.setQcScore("85");
|
|
|
+ result.setQcComments(aggregatedFailures.isEmpty()
|
|
|
+ ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
+ : JSON.toJSONString(aggregatedFailures));
|
|
|
+ result.setQcResult(aggregatedFailures.isEmpty() ? 1 : 0);
|
|
|
+ result.setCreateDate(LocalDateTime.now());
|
|
|
+ resultMapper.insert(result);
|
|
|
+
|
|
|
+ log.info("✅ 规则 [{}] 按日期融合质控完成,共 {} 个日期,不通过 {} 条(新建记录)",
|
|
|
+ rule.getRuleCode(), mergedByDate.size(), aggregatedFailures.size());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -654,16 +845,12 @@ public class MedicalRecordQc123Service {
|
|
|
String startDate = rangeMatcher.group(1); // 2025-10-09
|
|
|
String endDate = rangeMatcher.group(2); // 2025-10-16
|
|
|
|
|
|
- // 将医嘱添加到时间范围内所有存在病程记录的日期
|
|
|
- for (String courseDate : courseRecordsMap.keySet()) {
|
|
|
- if (courseDate.compareTo(startDate) >= 0 && courseDate.compareTo(endDate) <= 0) {
|
|
|
- courseRecordsMap.get(courseDate).add(order);
|
|
|
- matchedCount++;
|
|
|
- matched = true;
|
|
|
- }
|
|
|
- }
|
|
|
- if (matched) {
|
|
|
+ // 只将医嘱添加到开始日期(如果存在病程记录)
|
|
|
+ if (courseRecordsMap.containsKey(startDate)) {
|
|
|
+ courseRecordsMap.get(startDate).add(order);
|
|
|
+ matchedCount++;
|
|
|
rangeMatchCount++;
|
|
|
+ matched = true;
|
|
|
}
|
|
|
} else {
|
|
|
// 尝试匹配"暂无结束时间"格式
|
|
|
@@ -671,16 +858,12 @@ public class MedicalRecordQc123Service {
|
|
|
if (noEndMatcher.find()) {
|
|
|
String startDate = noEndMatcher.group(1); // 2025-10-11
|
|
|
|
|
|
- // 将医嘱添加到开始日期及之后所有存在病程记录的日期
|
|
|
- for (String courseDate : courseRecordsMap.keySet()) {
|
|
|
- if (courseDate.compareTo(startDate) >= 0) {
|
|
|
- courseRecordsMap.get(courseDate).add(order);
|
|
|
- matchedCount++;
|
|
|
- matched = true;
|
|
|
- }
|
|
|
- }
|
|
|
- if (matched) {
|
|
|
+ // 只将医嘱添加到开始日期(如果存在病程记录)
|
|
|
+ if (courseRecordsMap.containsKey(startDate)) {
|
|
|
+ courseRecordsMap.get(startDate).add(order);
|
|
|
+ matchedCount++;
|
|
|
rangeMatchCount++;
|
|
|
+ matched = true;
|
|
|
}
|
|
|
} else {
|
|
|
// 如果没有结束时间,只匹配开始日期
|
|
|
@@ -696,14 +879,11 @@ public class MedicalRecordQc123Service {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if (!matched) {
|
|
|
- log.debug("⚠️ 医嘱无法匹配日期: {}", order.substring(0, Math.min(80, order.length())));
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- log.info("📋 医嘱融合完成:共 {} 条医嘱,时间范围匹配 {} 条,单日期匹配 {} 条,总计融合 {} 次",
|
|
|
- ordersArray.size(), rangeMatchCount, singleMatchCount, matchedCount);
|
|
|
+ int unmatchedCount = ordersArray.size() - rangeMatchCount - singleMatchCount;
|
|
|
+ log.info("📋 医嘱融合完成:共 {} 条医嘱,时间范围匹配 {} 条,单日期匹配 {} 条,未匹配 {} 条,总计融合 {} 次",
|
|
|
+ ordersArray.size(), rangeMatchCount, singleMatchCount, unmatchedCount, matchedCount);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
log.error("融合医嘱记录失败", e);
|
|
|
@@ -732,17 +912,17 @@ public class MedicalRecordQc123Service {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 针对单条规则的分批质控聚合
|
|
|
+ * 针对单条规则的分批质控(返回不通过项列表)
|
|
|
*/
|
|
|
- private void batchQcAggregateForRule(String recordId, int qcType, QualityControlRuleVo rule,
|
|
|
- String jsonArrayContent, int batchSize, String ragResult) {
|
|
|
+ private List<Object> batchQcForRule(String recordId, int qcType, QualityControlRuleVo rule,
|
|
|
+ String jsonArrayContent, int batchSize, String ragResult) {
|
|
|
LocalDateTime start = LocalDateTime.now();
|
|
|
|
|
|
try {
|
|
|
JSONArray array = JSON.parseArray(jsonArrayContent);
|
|
|
if (array == null || array.isEmpty()) {
|
|
|
log.info("⚠️ 病历 {} 规则 [{}] type={} 数组为空,跳过", recordId, rule.getRuleCode(), qcType);
|
|
|
- return;
|
|
|
+ return new ArrayList<>();
|
|
|
}
|
|
|
|
|
|
List<Object> aggregated = new ArrayList<>();
|
|
|
@@ -795,41 +975,11 @@ public class MedicalRecordQc123Service {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 清理该规则的旧记录
|
|
|
- try {
|
|
|
- int rows = resultMapper.delete(new LambdaQueryWrapper<MedicalRecordResult>()
|
|
|
- .eq(MedicalRecordResult::getPatientRecordId, recordId)
|
|
|
- .eq(MedicalRecordResult::getQcType, qcType)
|
|
|
- .like(MedicalRecordResult::getQcComments, rule.getRuleCode()));
|
|
|
- if (rows > 0) {
|
|
|
- log.info("🧹 清理规则 [{}] 旧记录:删除 {} 条", rule.getRuleCode(), rows);
|
|
|
- }
|
|
|
- } catch (Exception ignore) { }
|
|
|
-
|
|
|
- // 保存聚合结果
|
|
|
- MedicalRecordResult agg = new MedicalRecordResult();
|
|
|
- agg.setPatientRecordId(recordId);
|
|
|
- agg.setQcType(qcType);
|
|
|
- agg.setQcStartTime(start);
|
|
|
- agg.setQcEndTime(LocalDateTime.now());
|
|
|
- agg.setQcScore("85");
|
|
|
- agg.setQcComments(aggregated.isEmpty()
|
|
|
- ? "[{\"result\":\"pass\",\"message\":\"病例校验通过\"}]"
|
|
|
- : JSON.toJSONString(aggregated));
|
|
|
- agg.setQcResult(aggregated.isEmpty() ? 1 : 0);
|
|
|
- agg.setCreateDate(LocalDateTime.now());
|
|
|
- resultMapper.insert(agg);
|
|
|
-
|
|
|
- if (aggregated.isEmpty()) {
|
|
|
- log.info("✅ 规则 [{}] type={} 分批质控完成:全部通过", rule.getRuleCode(), qcType);
|
|
|
- } else {
|
|
|
- log.info("✅ 规则 [{}] type={} 分批质控完成:共有不通过 {} 条",
|
|
|
- rule.getRuleCode(), qcType, aggregated.size());
|
|
|
- }
|
|
|
+ return aggregated;
|
|
|
|
|
|
} catch (Exception ex) {
|
|
|
log.error("❌ 规则 [{}] type={} 分批质控失败", rule.getRuleCode(), qcType, ex);
|
|
|
- throw ex;
|
|
|
+ return new ArrayList<>();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -849,29 +999,30 @@ public class MedicalRecordQc123Service {
|
|
|
|
|
|
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()) {
|
|
|
sb.append("规则描述: ").append(rule.getRuleDesc()).append("\n");
|
|
|
}
|
|
|
sb.append("\n");
|
|
|
+
|
|
|
+ if (ragResult != null && !ragResult.isEmpty()) {
|
|
|
+ sb.append("***【参考内容】***:\n")
|
|
|
+ .append(ragResult).append("\n\n");
|
|
|
+ }
|
|
|
|
|
|
sb.append("【重要说明】\n");
|
|
|
sb.append("本次质控的病历部分是:病程记录 + 医嘱记录(按日期融合)\n");
|
|
|
sb.append("以下是 ").append(date).append(" 时间段内的病程记录和医嘱记录的融合内容。\n");
|
|
|
sb.append("病程记录和医嘱记录是同一时间段的临床记录,存在时间和逻辑上的关联关系。\n");
|
|
|
sb.append("质控时需要综合考虑病程记录中的病情描述与医嘱记录中的医疗处置是否匹配、是否合理。\n");
|
|
|
+ sb.append("根据给出的参考内容进行判断,正例是符合改规则质控要求,反例为不符合质控要求。\n");
|
|
|
sb.append("请针对这两部分的融合内容进行质控审查。\n\n");
|
|
|
|
|
|
sb.append("【病历原文】:\n")
|
|
|
.append(content).append("\n\n");
|
|
|
|
|
|
- if (ragResult != null && !ragResult.isEmpty()) {
|
|
|
- sb.append("【参考正反例】:\n")
|
|
|
- .append(ragResult).append("\n\n");
|
|
|
- }
|
|
|
-
|
|
|
sb.append("1.输出格式:\n");
|
|
|
sb.append("请严格按照以下要求输出:\n");
|
|
|
sb.append("如果病历完全符合所有规则:[{\"result\": \"pass\", \"message\": \"病例校验通过\"}]\n\n");
|
|
|
@@ -901,26 +1052,27 @@ public class MedicalRecordQc123Service {
|
|
|
|
|
|
sb.append("你是一名医疗病历质控员,需要依据【质控规则】对病历进行审查。\n\n");
|
|
|
|
|
|
- sb.append("【质控规则】\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()) {
|
|
|
sb.append("规则描述: ").append(rule.getRuleDesc()).append("\n");
|
|
|
}
|
|
|
sb.append("\n");
|
|
|
-
|
|
|
- sb.append("【重要说明】\n");
|
|
|
- sb.append("本次质控的病历部分是:").append(sectionName != null ? sectionName : "未知部分").append("\n");
|
|
|
- sb.append("请针对该部分的内容进行质控审查。\n\n");
|
|
|
-
|
|
|
- sb.append("【病历原文】:\n")
|
|
|
- .append(content).append("\n\n");
|
|
|
-
|
|
|
+
|
|
|
if (ragResult != null && !ragResult.isEmpty()) {
|
|
|
- sb.append("【参考正反例】:\n")
|
|
|
+ sb.append("***【参考内容】***\n");
|
|
|
+ sb.append("根据给出的参考内容进行判断,正例是符合改规则质控要求,反例为不符合质控要求\n");
|
|
|
+ sb.append("以下是相关的正反例参考:\n")
|
|
|
.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");
|