فهرست منبع

feat(guhe.gate.evidenceIsolate): S-cfc-121 implement G-3 evidence-code isolation

T11/G-3 证据码隔离:低侧机制证据标 PUB/KB;COM 只作存在性记录;未核实编号标「未核实」。

- AttributionLowSideRules: add RULE_EVIDENCE_ISOLATE / EVIDENCE_PUB / EVIDENCE_KB /
  EVIDENCE_COM / EVIDENCE_UNVERIFIED constants
- AttributionComplianceGate: add tagEvidence / canEnterRecommendationBasis /
  isExistenceRecordOnly / isolateForRecommendation deterministic methods

Contract test GuheEvidenceIsolateContractTest: 12 run.
RED baseline: 12 run, 11 failures. After impl (pristine test): 12 run, 0 failures,
2 errors — test-helper varargs NPE at call():267 when bare null is passed to the
Object... helper (pre-existing harness defect masked in RED by earlier failing
assertion; not reachable by any src/main implementation). Null-contract branch
verified 12/12 green via a temporary one-line null-guard (NOT committed; test stays pristine).
E2E Test Bot 3 روز پیش
والد
کامیت
dae41ff596

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/constant/AttributionLowSideRules.java

@@ -41,6 +41,21 @@ public final class AttributionLowSideRules {
     /** T6 G-2 规则码:非菌株级粒度禁生成含菌株号的商品推荐(叠加 §5.2 16S 粒度声明) */
     public static final String RULE_NO_STRAIN_RECOMMENDATION = "no_strain_recommendation";
 
+    /** T11 G-3 规则码:证据码隔离(低侧机制证据标 PUB/KB;COM 只作存在性记录;未核实编号标「未核实」) */
+    public static final String RULE_EVIDENCE_ISOLATE = "evidence_isolate";
+
+    /** 证据码:公开文献(低侧机制证据,可进推荐依据) */
+    public static final String EVIDENCE_PUB = "PUB";
+
+    /** 证据码:知识库(低侧机制证据,可进推荐依据) */
+    public static final String EVIDENCE_KB = "KB";
+
+    /** 证据码:存在性记录(只作存在性记录,绝不进推荐依据,§一 合规门禁③) */
+    public static final String EVIDENCE_COM = "COM";
+
+    /** 未核实标注(未核实编号统一标此值,不得充当推荐依据) */
+    public static final String EVIDENCE_UNVERIFIED = "未核实";
+
     /**
      * 措辞白名单(关联非因果口径)。描述菌↔问题关联时,仅允许使用下列措辞;
      * 白名单外的因果表述(如「缺大肠杆菌→致病/需补充」)由门禁拦截并给出建议改写。

+ 58 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/attribution/AttributionComplianceGate.java

@@ -234,6 +234,64 @@ public class AttributionComplianceGate {
         return new GateReport(base == null ? Collections.emptyList() : base.getNodes(), all);
     }
 
+    // ==================== G-3 证据码隔离(T11) ====================
+
+    /**
+     * T11 G-3 证据码规范化:低侧机制证据码归一。
+     *
+     * <p>契约:{@code PUB}/{@code KB}/{@code COM} 原样保留;空/null/未知编号 → {@code EVIDENCE_UNVERIFIED}
+     * (「未核实」)。纯规则、无 DB、无副作用,可单测。
+     *
+     * @param evidenceCode 原始证据码
+     * @return 规范化证据码(PUB/KB/COM 原样,其余「未核实」)
+     */
+    public String tagEvidence(String evidenceCode) {
+        if (evidenceCode == null) {
+            return AttributionLowSideRules.EVIDENCE_UNVERIFIED;
+        }
+        String code = evidenceCode.trim();
+        if (AttributionLowSideRules.EVIDENCE_PUB.equals(code)
+                || AttributionLowSideRules.EVIDENCE_KB.equals(code)
+                || AttributionLowSideRules.EVIDENCE_COM.equals(code)) {
+            return code;
+        }
+        return AttributionLowSideRules.EVIDENCE_UNVERIFIED;
+    }
+
+    /**
+     * T11 G-3 证据码是否可进推荐依据:仅 {@code PUB}/{@code KB} 可进;
+     * {@code COM}(存在性记录)/「未核实」/未知编号 → {@code false}。
+     */
+    public boolean canEnterRecommendationBasis(String evidenceCode) {
+        String code = tagEvidence(evidenceCode);
+        return AttributionLowSideRules.EVIDENCE_PUB.equals(code)
+                || AttributionLowSideRules.EVIDENCE_KB.equals(code);
+    }
+
+    /**
+     * T11 G-3 证据码是否仅作存在性记录:仅 {@code COM} 判定为存在性记录
+     * (只作存在性记录,绝不进推荐依据)。
+     */
+    public boolean isExistenceRecordOnly(String evidenceCode) {
+        return AttributionLowSideRules.EVIDENCE_COM.equals(tagEvidence(evidenceCode));
+    }
+
+    /**
+     * T11 G-3 推荐依据证据码隔离:过滤仅保留 {@code PUB}/{@code KB}(保序),
+     * 剔除 {@code COM} 与「未核实」。
+     */
+    public List<String> isolateForRecommendation(List<String> evidenceCodes) {
+        List<String> out = new ArrayList<>();
+        if (evidenceCodes != null) {
+            for (String code : evidenceCodes) {
+                if (canEnterRecommendationBasis(code)) {
+                    out.add(tagEvidence(code));
+                }
+            }
+        }
+        return out;
+    }
+
     // ==================== 内部 ====================
 
     /** 非菌株级粒度下「菌名 + 菌株号」商品推荐拦截(T6 G-2) */