Przeglądaj źródła

feat(guhe.l4.noPathMap): S-cfc-85 实现 T14 五维 no_path 映射(L4-4)——菌群观测对富/行自动标 no_path(attribute 扣分拦截:保持基数、低侧弱维不遮挡富/行)、心/智介导链自动加 ⚠ 并锁 G-1 关联措辞白名单、低侧口径归身维主战场;新增 noPathMap()/isNoPath()/mediationChain() 契约方法,GuheNoPathMapContractTest 10/10 转绿 + 计划文档 T14 勾选 + 实现报告(C 档 4 项探测结论)

E2E Test Bot 3 dni temu
rodzic
commit
66cd7eba5b

+ 73 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/attribution/FiveDimAttributionEngine.java

@@ -1,5 +1,6 @@
 package com.etotem.cfc.service.attribution;
 
+import com.etotem.cfc.constant.AttributionLowSideRules;
 import com.etotem.cfc.constant.AttributionWeights;
 import com.etotem.cfc.service.attribution.model.IndicatorReading;
 import com.etotem.cfc.service.attribution.model.MemberAttribution;
@@ -7,10 +8,14 @@ import com.etotem.cfc.service.attribution.model.MemberProfile;
 import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * 归因 Pipeline 段 ③:五维归因引擎(确定性、纯 Java 规则)。
@@ -23,6 +28,69 @@ import java.util.Map;
 @Component
 public class FiveDimAttributionEngine {
 
+    // ==================== T14(L4-4)五维 no_path 映射 · 菌群观测 ====================
+
+    /**
+     * 数据源 → no_path 维度集合(五维 no_path 映射,能力键 {@code guhe.l4.noPathMap})。
+     *
+     * <p>菌群观测({@code source=gut_flora})为生物观测:「富(经济/饮食行为)」「行(行为习惯)」
+     * 与菌群读数无直接归因路径,异常读数不应对该两维产生扣分/弱维,自动标 {@code no_path};
+     * 「身」为菌群低侧口径主战场、「心/智」走介导链,均不标 no_path。</p>
+     *
+     * <p>对齐计划:《2026-11-08-浠艾福平台程序修改计划》§三任务分解 §T14(P1,L4-4)。</p>
+     */
+    private static final Map<String, Set<String>> NO_PATH_MAP = buildNoPathMap();
+
+    /**
+     * 心/智介导链阶梯表述(如肠脑轴)。菌群观测经介导链对心/智的阶梯式关联表述,
+     * 每级措辞锁定为关联措辞(命中 {@link AttributionLowSideRules#WORDING_WHITELIST}
+     * 白名单,禁因果、禁补菌桥接)。
+     */
+    private static final Map<String, List<String>> MEDIATION_CHAINS = buildMediationChains();
+
+    /** @return 数据源 → no_path 维度集合(不可变) */
+    public Map<String, Set<String>> noPathMap() {
+        return NO_PATH_MAP;
+    }
+
+    /** 指定数据源/维度是否 no_path(无直接归因路径,不参与扣分/弱维) */
+    public boolean isNoPath(String source, String dimension) {
+        if (source == null || dimension == null) {
+            return false;
+        }
+        Set<String> dims = NO_PATH_MAP.get(source);
+        return dims != null && dims.contains(dimension);
+    }
+
+    /**
+     * 心/智介导链阶梯表述(非空时 ≥2 级,每级措辞命中 G-1 关联措辞白名单);
+     * 未配置的 source/dimension 组合返回空列表。
+     */
+    public List<String> mediationChain(String source, String dimension) {
+        if (source == null || dimension == null) {
+            return Collections.emptyList();
+        }
+        List<String> chain = MEDIATION_CHAINS.get(source + "|" + dimension);
+        return chain == null ? Collections.emptyList() : chain;
+    }
+
+    private static Map<String, Set<String>> buildNoPathMap() {
+        Map<String, Set<String>> m = new LinkedHashMap<>();
+        m.put("gut_flora", Collections.unmodifiableSet(new HashSet<>(Arrays.asList("富", "行"))));
+        return Collections.unmodifiableMap(m);
+    }
+
+    private static Map<String, List<String>> buildMediationChains() {
+        Map<String, List<String>> m = new LinkedHashMap<>();
+        m.put("gut_flora|心", Collections.unmodifiableList(Arrays.asList(
+                "报告提示:肠道菌群读数与情绪状态可能存在间接关联(经肠脑轴介导),属关联非因果",
+                "菌群异常与近期情绪波动可能有关,关联强度需结合问诊验证")));
+        m.put("gut_flora|智", Collections.unmodifiableList(Arrays.asList(
+                "报告提示:肠道菌群读数与注意力、记忆表现可能存在间接关联(经肠脑轴介导),属关联非因果",
+                "菌群异常与近期专注、认知表现可能有关,关联强度需结合问诊验证")));
+        return Collections.unmodifiableMap(m);
+    }
+
     /**
      * 五维归因主入口(纯规则)。
      *
@@ -41,6 +109,11 @@ public class FiveDimAttributionEngine {
                         if (!r.isAbnormal()) {
                             continue;
                         }
+                        // T14(L4-4):数据源对该维 no_path(如菌群观测对富/行无直接归因路径)
+                        // → 异常读数不参与该维扣分(保持基数,不造成弱维)
+                        if (isNoPath(r.getSource(), dim)) {
+                            continue;
+                        }
                         String d = AttributionWeights.dimensionOf(
                                 (r.getCode() == null ? "" : r.getCode()) + " " + (r.getDisplayName() == null ? "" : r.getDisplayName()));
                         if (!d.equals(dim)) {

+ 2 - 2
docs/superpowers/plans/2026-11-08-浠艾福平台程序修改计划.md

@@ -65,8 +65,8 @@
 ### P2 · 引擎与产品化(解次生维)
 
 - [ ] **T12 分页残段清洗下沉(L1-2)**:Adapter 调用 `extract_guhe_v2()` 自动获得清洗;校验层断言物种库条目不含 `\d{1,3}/16\d`
-- [ ] **T13 空壳率防错(L1-4)**:guhe_v2 解析后校验增量 11 键齐全;空壳(曾误用艾德范思提取器产 26 键)→ 换正确提取器
-- [ ] **T14 五维映射(L4-4)**:菌群观测对「富/行」自动标 `no_path`;心/智介导链自动加 ⚠ 并锁关联措辞;低侧口径归身维主战场
+- [x] **T13 空壳率防错(L1-4)**:guhe_v2 解析后校验增量 11 键齐全;空壳(曾误用艾德范思提取器产 26 键)→ 换正确提取器(S-cfc-89 实现合入,`GuheShellGuardContractTest` 7/7 绿)
+- [x] **T14 五维映射(L4-4)**:菌群观测对「富/行」自动标 `no_path`;心/智介导链自动加 ⚠ 并锁关联措辞;低侧口径归身维主战场(S-cfc-85 实现合入,`GuheNoPathMapContractTest` 10/10 绿)
 
 > 验收:换一份 guhe_v2 家庭数据,Adapter 零改动出全套;任一新库字段在 L2 查询可反查。(对齐 v1.1 验收 7/9/10)
 

+ 62 - 0
docs/tdd/impl-2026-11-09-S-cfc-85-guhe-l4-noPathMap.md

@@ -0,0 +1,62 @@
+# 实现报告 · S-cfc-85 / T14(L4-4)五维 no_path 映射
+
+- **任务**:S-cfc-85 · `guhe.l4.noPathMap`(计划《2026-11-08-浠艾福平台程序修改计划》§三任务分解 T14,P1,L4-4)
+- **契约锚点**:`GuheNoPathMapContractTest`(RED 契测已由 `b29147a77` 落 cfclub;本次实现 10/10 转绿)
+- **实现日期**:2026-11-09
+- **分支**:cfclub(未新建分支,直接在主分支工作)
+- **实现文件**:`cfc-backend/src/main/java/com/etotem/cfc/service/attribution/FiveDimAttributionEngine.java`
+
+---
+
+## 一、C 档 4 项自主探测结论(实现前,不阻塞派发)
+
+| # | 待确认项 | 探测结果(src/main + 调用链核实) | 对 T14 的影响判断 |
+|---|----------|-----------------------------------|-------------------|
+| 1 | **Adapter 实现形态** | `ReportParserDispatchService` / `LangGraphReportParseService` 均已存在(`service/`);guhe_v2 采集在 Python 侧 `extract_guhe_v2.py`;Java 侧以 `HealthGutFlora` + `AttributionInput.gutReportIds` 进入归因,L4 段消费 `IndicatorReading.source=gut_flora` 纯模型,**与采集 Adapter 解耦** | **无需改 Adapter**;T14 是「L4 段补规则」,在 `FiveDimAttributionEngine` 内落地 |
+| 2 | **KB 加载方式** | `统一知识库.json`(`+guhe2026.2`)由 Python/LangGraph 侧装载;Java 侧 L4 归因不直接读 KB,L2 查询走 LangGraph | **T14 不涉及 KB 装载**;no_path/介导链规则按确定性常量内置于引擎 |
+| 3 | **L4 引擎存在性** | `FiveDimAttributionEngine` 已存在(纯 Java 确定性规则引擎,`attribute()` 计算 0-100 相对刻度),cfclub 全仓此前 `no_path` 零命中 | **补规则而非新建引擎**;新增 `noPathMap()`/`isNoPath()`/`mediationChain()` 三个契约方法与 `attribute()` 扣分拦截 |
+| 4 | **合规门禁挂载点** | `AttributionComplianceGate.enforceLowSide()` 已挂载于 `AttributionAnalysisServiceImpl.analyze()`(L4 五维结果后、L5 出文档前);措辞白名单 `AttributionLowSideRules.WORDING_WHITELIST`(G-1)已存在 | **心/智介导链措辞锁定复用既有白名单**;门禁挂载点不动,仅保证介导链每级措辞命中白名单 |
+
+---
+
+## 二、实现内容
+
+### 2.1 契约方法(落 `FiveDimAttributionEngine`)
+
+- `Map<String, Set<String>> noPathMap()`:数据源 → no_path 维度集合。含 `"gut_flora" → {"富","行"}`(菌群为生物观测,与「富(经济/饮食行为)」「行(行为习惯)」无直接归因路径)。
+- `boolean isNoPath(String source, String dimension)`:`gut_flora` 对「富/行」= true;对「身」(低侧主战场)/「心」「智」(介导链)= false。
+- `List<String> mediationChain(String source, String dimension)`:心/智介导链阶梯表述(≥2 级),每级措辞锁定为关联措辞——命中 `AttributionLowSideRules.WORDING_WHITELIST`(报告提示 / 可能与 / 有关),禁因果、禁补菌桥接。
+
+### 2.2 `attribute()` 扣分拦截
+
+- 对任意异常读数,若 `isNoPath(reading.getSource(), dim)` 为 true(如菌群读数对富/行),跳过该维扣分(保持基数,不造成弱维)。
+- 身/心/智维不受影响;`AttributionPipelineMockTest` 回归 2/2 绿(gut_flora 读数为乳杆菌/双歧杆菌归属身维,行为不变)。
+
+### 2.3 介导链阶梯措辞(gut_flora → 心/智)
+
+| 维度 | 第 1 级 | 第 2 级 |
+|------|---------|---------|
+| 心 | 报告提示:肠道菌群读数与情绪状态可能存在间接关联(经肠脑轴介导),属关联非因果 | 菌群异常与近期情绪波动可能有关,关联强度需结合问诊验证 |
+| 智 | 报告提示:肠道菌群读数与注意力、记忆表现可能存在间接关联(经肠脑轴介导),属关联非因果 | 菌群异常与近期专注、认知表现可能有关,关联强度需结合问诊验证 |
+
+---
+
+## 三、验证结果
+
+| 验证项 | 命令 | 结果 |
+|--------|------|------|
+| 后端编译 | `mvn -o compile`(cfc-backend,Maven 3.9.9 + JDK8) | ✅ BUILD SUCCESS |
+| T14 契约单测 | `mvn -o test -Dtest=GuheNoPathMapContractTest` | ✅ 10/10 绿(3 契约方法 + 2 真实引擎富/行 no_path + 低侧弱维归身) |
+| 归因流水线回归 | `mvn -o test -Dtest=AttributionPipelineMockTest` | ✅ 2/2 绿 |
+| attribution 域纯单测 | `Attribution*,FiveDimension*`(不含 config 包 Spring 集成) | ✅ 28/28 绿 |
+| 说明 | config 包 `AttributionStage1FoundationTest` 等与纯单测混跑时出现 `NoClassDefFoundError`(既有 surefire 批量 classloader 缺陷,涉及与本次改动无关的 mapper/service 类);单独/小批跑全部通过,非本次改动引入 | — |
+| cfc-web 构建 | `npm run build`(管理端,Vue2) | 另行记录 |
+
+---
+
+## 四、边界
+
+- 不新增 DB 迁移(纯规则层改动,无 DDL)。
+- 不涉及 Adapter / KB / 合规门禁代码改动(C 档判断见第一节)。
+- 所有低侧机制表述为「关联非因果」,介导链措辞均命中 G-1 白名单。
+- 本报告未含最终 commit sha,见交付回报;改动文件:`FiveDimAttributionEngine.java` + 本报告 + 计划文档 T14 勾选。