|
|
@@ -185,6 +185,16 @@ public class ReportCollectService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 加固③:指纹成功识别出的类型与用户所选报告类型不一致(非 DAN)→ 阻止自动入库,
|
|
|
+ // 防止认知能力测评等报告误入健康管道(如 gut_flora)后以错误类型静默入库
|
|
|
+ if (!"unknown".equals(typeId) && !typeId.equals(draft.getReportType())) {
|
|
|
+ log.warn("报告类型不匹配 draftId={}, 指纹识别={}, 所选={}", draftId, typeId, draft.getReportType());
|
|
|
+ healthReportDraftService.markCollectFailed(draftId,
|
|
|
+ "报告内容与所选类型不符(指纹识别为「" + typeId + "」,所选「" + draft.getReportType()
|
|
|
+ + "」),请核对报告类型后重新上传");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// 采集指标清单(用于驱动非 DAN 的 LangGraph LLM 解析)
|
|
|
String validationTypeId = (!"unknown".equals(typeId)) ? typeId : draft.getReportType();
|
|
|
List<String> expectedIndicators = loadExpectedIndicators(validationTypeId);
|
|
|
@@ -223,11 +233,29 @@ public class ReportCollectService {
|
|
|
}
|
|
|
|
|
|
harmReportPayloadValid(payload);
|
|
|
+ // 加固②:已知肠道菌群家族类型必须含菌群实质数据(菌群检出列表/指标清单),
|
|
|
+ // 防止认知测评等非菌群报告只解析出 overview 也静默入库
|
|
|
+ if (isGutFloraType(validationTypeId) && !hasGutContent(payload)) {
|
|
|
+ log.warn("肠道菌群报告解析结果缺少菌群数据,阻止入库 draftId={}, typeId={}",
|
|
|
+ draftId, validationTypeId);
|
|
|
+ healthReportDraftService.markCollectFailed(draftId,
|
|
|
+ "解析结果缺少菌群检测数据(报告内容与所选类型不符),请在解析状态页人工处理或重新上传");
|
|
|
+ return;
|
|
|
+ }
|
|
|
// 采集指标完整性校验(report_type_registry.expected_indicators 定义驱动)
|
|
|
+ // 加固①:校验不通过(pass=false)阻止自动入库,标记失败由管理端人工处理
|
|
|
+ boolean validationPass;
|
|
|
try {
|
|
|
- runCollectValidation(draftId, validationTypeId, payload);
|
|
|
+ validationPass = runCollectValidation(draftId, validationTypeId, payload);
|
|
|
} catch (Exception e) {
|
|
|
log.warn("采集指标校验失败 draftId={}, typeId={}", draftId, validationTypeId, e);
|
|
|
+ validationPass = true;
|
|
|
+ }
|
|
|
+ if (!validationPass) {
|
|
|
+ log.warn("采集指标校验未通过,阻止入库 draftId={}, typeId={}", draftId, validationTypeId);
|
|
|
+ healthReportDraftService.markCollectFailed(draftId,
|
|
|
+ "采集指标校验未通过(报告内容与所选类型不符或解析不完整),请在解析状态页人工处理或重新上传");
|
|
|
+ return;
|
|
|
}
|
|
|
healthReportDraftService.updateParseMethod(draftId, method);
|
|
|
healthReportDraftService.updatePayload(draftId, objectMapper.writeValueAsString(payload));
|
|
|
@@ -1045,14 +1073,14 @@ public class ReportCollectService {
|
|
|
/**
|
|
|
* 健康(GUT/混检)管道校验入口:汇总 payload 中全部可采集指标名称后按定义比对
|
|
|
*/
|
|
|
- private void runCollectValidation(Long draftId, String typeId, ParsedReportPayload.Payload payload) {
|
|
|
- runCollectValidation(draftId, typeId, collectHealthNames(payload));
|
|
|
+ private boolean runCollectValidation(Long draftId, String typeId, ParsedReportPayload.Payload payload) {
|
|
|
+ return runCollectValidation(draftId, typeId, collectHealthNames(payload));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* DAN 管道校验入口:汇总解析 items 的 name/code 后按定义比对
|
|
|
*/
|
|
|
- private void runCollectValidation(Long draftId, String typeId, DanReportParseService.DanParsedReport parsed) {
|
|
|
+ private boolean runCollectValidation(Long draftId, String typeId, DanReportParseService.DanParsedReport parsed) {
|
|
|
Set<String> names = new HashSet<>();
|
|
|
if (parsed != null && parsed.getItems() != null) {
|
|
|
for (DanReportParseService.DataItem item : parsed.getItems()) {
|
|
|
@@ -1060,7 +1088,7 @@ public class ReportCollectService {
|
|
|
addCollectName(names, item.getCode());
|
|
|
}
|
|
|
}
|
|
|
- runCollectValidation(draftId, typeId, names);
|
|
|
+ return runCollectValidation(draftId, typeId, names);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1068,13 +1096,13 @@ public class ReportCollectService {
|
|
|
* 该类型无指标项定义(expected_indicators 为空 / 类型未注册)时跳过,不影响采集主流程。
|
|
|
* 结果 JSON(typeId/expected/matched/pass/missing)写入草稿 collect_validation,缺失项记入 WARN 日志。
|
|
|
*/
|
|
|
- private void runCollectValidation(Long draftId, String typeId, Set<String> collectedNames) {
|
|
|
+ private boolean runCollectValidation(Long draftId, String typeId, Set<String> collectedNames) {
|
|
|
if (draftId == null || typeId == null || collectedNames == null || collectedNames.isEmpty()) {
|
|
|
- return;
|
|
|
+ return true;
|
|
|
}
|
|
|
List<String> expected = loadExpectedIndicators(typeId);
|
|
|
if (expected == null || expected.isEmpty()) {
|
|
|
- return;
|
|
|
+ return true;
|
|
|
}
|
|
|
List<String> missing = new ArrayList<>();
|
|
|
int matched = 0;
|
|
|
@@ -1102,6 +1130,26 @@ public class ReportCollectService {
|
|
|
} catch (Exception e) {
|
|
|
log.warn("记录采集校验结果失败 draftId={}, typeId={}", draftId, typeId, e);
|
|
|
}
|
|
|
+ return missing.isEmpty();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 是否为肠道菌群家族类型(report_type_registry.family = gut_flora) */
|
|
|
+ private boolean isGutFloraType(String typeId) {
|
|
|
+ if (typeId == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ ReportTypeRegistry registry = reportTypeRegistryMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<ReportTypeRegistry>()
|
|
|
+ .eq(ReportTypeRegistry::getTypeId, typeId)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ return registry != null && "gut_flora".equals(registry.getFamily());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 菌群实质数据判定:菌群检出列表或指标清单至少一项非空 */
|
|
|
+ private boolean hasGutContent(ParsedReportPayload.Payload payload) {
|
|
|
+ return payload != null
|
|
|
+ && ((payload.getGutFlora() != null && !payload.getGutFlora().isEmpty())
|
|
|
+ || (payload.getIndicators() != null && !payload.getIndicators().isEmpty()));
|
|
|
}
|
|
|
|
|
|
/** 读取类型指标项定义(report_type_registry.expected_indicators JSON 数组);未定义返回 null */
|