Procházet zdrojové kódy

feat(guhe.l1.guheV2Route): S-cfc-90 T7/L1-1 guhe_v2 指纹路由 — 判定链 aidefansi→beijing→guhe_v2→standard + 触发词三锚点(疾病相似评估/厚壁菌比拟杆菌比值/亚健康及营养饮食不合理) + 32键自检入口(guheV2KeyCountCheck/GUHE_V2_KEYS_32)

实现(计划 2026-11-08-浠艾福平台程序修改计划 §三-T7 / §一-L1① / §2.1):
- ReportParserDispatchService 落地 L1 指纹路由层四锚点:GUHE_V2_ROUTE_CHAIN 判定链常量(值含完整箭头链)、FORMAT_* 四阶段常量(aidefansi/beijing/guhe_v2/standard)、三个触发词常量、GUHE_V2_KEYS_32 键数自检入口
- isGuheV2Format(text):触发词指纹识别,要求至少命中2个且必须含「疾病相似评估」(与旧21键谷禾区分,防 T8 禁强套)
- detectReportFormatVersion(text):按判定链顺序逐阶段识别格式版本
- 键数自检:GUHE_V2_KEYS_32=32(复用 ParsedReportResult,与 T8 同源)

验证:mvn compile EXIT=0;GuheV2RouteContractTest 8/8 GREEN(RED 基线 6 败 → 全绿)
Sisyphus před 4 dny
rodič
revize
7270303b53

+ 105 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ReportParserDispatchService.java

@@ -50,6 +50,111 @@ public class ReportParserDispatchService {
 
 
     private static final int AUTO_LEARN_THRESHOLD = 3;
     private static final int AUTO_LEARN_THRESHOLD = 3;
 
 
+    // ==================== guhe_v2 指纹路由(T7/L1-1) ====================
+
+    /**
+     * 报告格式判定链顺序(T7/L1-1,§2.1):{@code aidefansi→beijing→guhe_v2→standard};
+     * 旧三类路由(aidefansi / beijing / standard, triplet-inline)不变,guhe_v2 插在 beijing 之后、
+     * standard 之前判定(32 键新版报告不可落入 standard→LLM 兜底而丢失增量 11 键)。
+     */
+    public static final String GUHE_V2_ROUTE_CHAIN = "aidefansi→beijing→guhe_v2→standard";
+
+    /** 判定链阶段:aidefansi(艾德范思肠道微生态检测报告) */
+    public static final String FORMAT_AIDEFANSI = "aidefansi";
+
+    /** 判定链阶段:beijing(北京菌群报告,GMHI / FMT) */
+    public static final String FORMAT_BEIJING = "beijing";
+
+    /** 判定链阶段:guhe_v2(谷禾 2026-11 新版标准报告,32 键 = 旧 21 键打底 + 11 增量键) */
+    public static final String FORMAT_GUHE_V2 = ParsedReportResult.FORMAT_GUHE_V2;
+
+    /** 判定链阶段:standard(标准/旧版谷禾报告,triplet / inline,21 键) */
+    public static final String FORMAT_STANDARD = "standard";
+
+    /** guhe_v2 指纹触发词-1:疾病相似评估(T7,§2.1;guhe_v2 新版章节特征,旧 21 键报告无此词) */
+    public static final String TRIGGER_WORD_GUHE_V2_DISEASE_SIMILARITY = "疾病相似评估";
+
+    /** guhe_v2 指纹触发词-2:厚壁菌比拟杆菌比值(T7,§2.1) */
+    public static final String TRIGGER_WORD_GUHE_V2_FIRMICUTES_BACTEROIDETES = "厚壁菌比拟杆菌比值";
+
+    /** guhe_v2 指纹触发词-3:亚健康及营养饮食不合理(T7,§2.1;健康总分评级文字) */
+    public static final String TRIGGER_WORD_GUHE_V2_SUBHEALTH_DIET = "亚健康及营养饮食不合理";
+
+    /** guhe_v2 触发词判定阈值:至少命中 2 个触发词,且必须包含「疾病相似评估」(见 {@link #isGuheV2Format(String)}) */
+    public static final int GUHE_V2_TRIGGER_MIN_HITS = 2;
+
+    /** guhe_v2 键数自检:32 键(旧 21 键打底 + 11 增量键,§2.1 / T7 键数自检 32 键) */
+    public static final int GUHE_V2_KEYS_32 = ParsedReportResult.GUHE_V2_KEYS_32;
+
+    /**
+     * guhe_v2 指纹识别(T7/L1-1):判定链 {@code aidefansi→beijing→guhe_v2→standard} 中的 <b>guhe_v2 阶段</b>。
+     *
+     * <p>触发词「疾病相似评估 / 厚壁菌比拟杆菌比值 / 亚健康及营养饮食不合理」(§2.1)。
+     * 其中「疾病相似评估」为 guhe_v2 新版新增章节特征(旧 21 键谷禾报告为「疾病风险评估」,
+     * 不含此词),故判定要求<b>至少命中 {@value #GUHE_V2_TRIGGER_MIN_HITS} 个触发词且必须含「疾病相似评估」</b>——
+     * 既满足 guhe_v2 识别,又避免把旧 21 键报告误判为 guhe_v2(T8 禁把旧 21 键强套 guhe_v2)。</p>
+     *
+     * @param text 归一化后的报告全文(可含 0/1/2/3 个触发词)
+     * @return true=guhe_v2 新版标准报告(32 键)
+     */
+    public boolean isGuheV2Format(String text) {
+        if (text == null || text.isEmpty()) {
+            return false;
+        }
+        int hits = 0;
+        if (text.contains(TRIGGER_WORD_GUHE_V2_DISEASE_SIMILARITY)) {
+            hits++;
+        }
+        if (text.contains(TRIGGER_WORD_GUHE_V2_FIRMICUTES_BACTEROIDETES)) {
+            hits++;
+        }
+        if (text.contains(TRIGGER_WORD_GUHE_V2_SUBHEALTH_DIET)) {
+            hits++;
+        }
+        return hits >= GUHE_V2_TRIGGER_MIN_HITS
+                && text.contains(TRIGGER_WORD_GUHE_V2_DISEASE_SIMILARITY);
+    }
+
+    /**
+     * 报告格式版本检测(T7/L1-1 指纹路由入口):按判定链
+     * {@code aidefansi→beijing→guhe_v2→standard} 顺序逐阶段识别格式版本。
+     *
+     * @param fullText 报告全文文本
+     * @return 判定格式版本:{@link #FORMAT_AIDEFANSI} / {@link #FORMAT_BEIJING} /
+     *         {@link #FORMAT_GUHE_V2} / {@link #FORMAT_STANDARD}(兜底)
+     */
+    public String detectReportFormatVersion(String fullText) {
+        if (fullText == null || fullText.trim().isEmpty()) {
+            return FORMAT_STANDARD;
+        }
+        // ① aidefansi:艾德范思报告正文「致客户」信稳定含「艾德范思」
+        if (fullText.contains("艾德范思")) {
+            return FORMAT_AIDEFANSI;
+        }
+        // ② beijing:与 PdfParseService#isBeijingFormat 指纹一致(肠道菌群检测 + 高通量测序 + 健康整体评估)
+        if (fullText.contains("肠道菌群检测") && fullText.contains("高通量测序")
+                && fullText.contains("健康整体评估")) {
+            return FORMAT_BEIJING;
+        }
+        // ③ guhe_v2:触发词指纹(32 键新版标准报告)
+        if (isGuheV2Format(fullText)) {
+            return FORMAT_GUHE_V2;
+        }
+        // ④ standard:兜底(旧 21 键谷禾 / triplet / inline)
+        return FORMAT_STANDARD;
+    }
+
+    /**
+     * guhe_v2 32 键自检(T7 键数自检 32 键 / §2.1):校验解析产物顶层键数达 32
+     * (旧 21 键打底 {@link ParsedReportResult#LEGACY_KEYS_21} + 增量 11 键)。
+     *
+     * @param keyCount 解析产物顶层键数
+     * @return true=键数齐全(≥ 32,T2/T13 空壳率防错阶段可复用)
+     */
+    public boolean guheV2KeyCountCheck(int keyCount) {
+        return keyCount >= GUHE_V2_KEYS_32;
+    }
+
     // ==================== guhe_v2 格式版本分流(T8/L1-3) ====================
     // ==================== guhe_v2 格式版本分流(T8/L1-3) ====================
 
 
     /** guhe_v2 专用解析器脚本名(32 键新版标准报告应走此提取器,不落入 unknown→LLM 兜底) */
     /** guhe_v2 专用解析器脚本名(32 键新版标准报告应走此提取器,不落入 unknown→LLM 兜底) */