|
|
@@ -1,6 +1,7 @@
|
|
|
package com.etotem.cfc.service;
|
|
|
|
|
|
import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.dto.ParsedReportResult;
|
|
|
import com.etotem.cfc.entity.ReportUnknownCluster;
|
|
|
import com.etotem.cfc.entity.ReportUnknownUpload;
|
|
|
import com.etotem.cfc.mapper.ReportUnknownClusterMapper;
|
|
|
@@ -49,6 +50,17 @@ public class ReportParserDispatchService {
|
|
|
|
|
|
private static final int AUTO_LEARN_THRESHOLD = 3;
|
|
|
|
|
|
+ // ==================== 格式版本分流契约(guhe.l1.versionSplit · T8/L1-3 · §2.1) ====================
|
|
|
+
|
|
|
+ /** guhe_v2 专用解析器脚本名(32 键 guhe_v2 报告走此提取器,非 unknown→LLM 兜底) */
|
|
|
+ private static final String GUHE_V2_PARSER_SCRIPT = "extract_guhe_v2";
|
|
|
+
|
|
|
+ /** 旧版标准报告键数(v5 流程打底)—— 禁被 guhe_v2 提取器强套(T8/L1-3) */
|
|
|
+ private static final int LEGACY_KEYS_21 = ParsedReportResult.LEGACY_KEYS_21;
|
|
|
+
|
|
|
+ /** guhe_v2 新版标准报告键数:旧 21 键 + 11 增量键 = 32 键(§2.1) */
|
|
|
+ private static final int GUHE_V2_KEYS_32 = ParsedReportResult.GUHE_V2_KEYS_32;
|
|
|
+
|
|
|
/**
|
|
|
* 解析报告:指纹检测 → 专用解析器 或 LLM 兜底
|
|
|
*/
|
|
|
@@ -91,6 +103,74 @@ public class ReportParserDispatchService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 按报告格式版本分流(T8 / L1-3 · guhe.l1.versionSplit)。
|
|
|
+ *
|
|
|
+ * <p>判定链 {@code aidefansi→beijing→guhe_v2→standard},旧三类路由不变:
|
|
|
+ * <ul>
|
|
|
+ * <li>32 键 guhe_v2 新版报告({@link ParsedReportResult#FORMAT_GUHE_V2} 标记或键数自检达标)
|
|
|
+ * → 走专用提取器 {@link #GUHE_V2_PARSER_SCRIPT},<b>不落入</b> unknown→LLM 兜底
|
|
|
+ * (禁把 32 键误判成路由错误);</li>
|
|
|
+ * <li>旧 21 键(beijing / guhe)→ 保留原 v5 流程,<b>不被</b> guhe_v2 提取器强套。</li>
|
|
|
+ * </ul></p>
|
|
|
+ *
|
|
|
+ * @param parsed 解析结果(含报告格式标记与顶层键数)
|
|
|
+ * @return 路由决策:{@code route}=guhe_v2/legacy_v5、{@code parser}(仅 guhe_v2 分支)、{@code keyCount}
|
|
|
+ */
|
|
|
+ public Map<String, Object> routeByFormatVersion(ParsedReportResult parsed) {
|
|
|
+ Map<String, Object> decision = new LinkedHashMap<>();
|
|
|
+ if (parsed == null) {
|
|
|
+ decision.put("route", "legacy_v5");
|
|
|
+ decision.put("keyCount", 0);
|
|
|
+ return decision;
|
|
|
+ }
|
|
|
+
|
|
|
+ int keyCount = countTopLevelKeys(parsed);
|
|
|
+ boolean isGuheV2 = ParsedReportResult.FORMAT_GUHE_V2.equals(parsed.getReportFormat());
|
|
|
+ // 自检兜底:32 键新版报告即使未打标也不误判成路由错误(T8 禁把 32 键误判成路由错误)
|
|
|
+ if (!isGuheV2 && keyCount >= GUHE_V2_KEYS_32) {
|
|
|
+ isGuheV2 = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isGuheV2) {
|
|
|
+ decision.put("route", "guhe_v2");
|
|
|
+ decision.put("parser", GUHE_V2_PARSER_SCRIPT);
|
|
|
+ } else {
|
|
|
+ // 旧 21 键打底 → 原 v5 流程(禁强套 guhe_v2 提取器)
|
|
|
+ decision.put("route", "legacy_v5");
|
|
|
+ }
|
|
|
+ decision.put("keyCount", keyCount);
|
|
|
+ return decision;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 统计解析结果顶层非空键数(自检:旧 21 键打底 / guhe_v2 32 键边界,§2.1) */
|
|
|
+ private int countTopLevelKeys(ParsedReportResult p) {
|
|
|
+ int count = 0;
|
|
|
+ if (p.getOverallScore() != null) count++;
|
|
|
+ if (p.getGutHealthScore() != null) count++;
|
|
|
+ if (p.getChronicDiseaseScore() != null) count++;
|
|
|
+ if (p.getNutritionScore() != null) count++;
|
|
|
+ if (p.getBalanceScore() != null) count++;
|
|
|
+ if (p.getDiversityScore() != null) count++;
|
|
|
+ if (p.getBeneficialScore() != null) count++;
|
|
|
+ if (p.getHarmfulScore() != null) count++;
|
|
|
+ if (p.getCoreGenusScore() != null) count++;
|
|
|
+ if (p.getGutAge() != null) count++;
|
|
|
+ if (p.getGutType() != null) count++;
|
|
|
+ if (p.getReportNumber() != null) count++;
|
|
|
+ if (p.getReportDate() != null) count++;
|
|
|
+ if (p.getPersonName() != null) count++;
|
|
|
+ if (p.getGender() != null) count++;
|
|
|
+ if (p.getAge() != null) count++;
|
|
|
+ if (p.getBirthday() != null) count++;
|
|
|
+ if (p.getDiseaseRisks() != null) count++;
|
|
|
+ if (p.getGutFlora() != null) count++;
|
|
|
+ if (p.getProbioticSpecies() != null) count++;
|
|
|
+ if (p.getIndicators() != null) count++;
|
|
|
+ if (p.getFoodSuitability() != null) count++;
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 调用 cfc-langgraph 的 LLM 解析端点
|
|
|
*/
|