|
|
@@ -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)) {
|