|
|
@@ -0,0 +1,116 @@
|
|
|
+package com.etotem.cfc.service.attribution;
|
|
|
+
|
|
|
+import com.etotem.cfc.constant.AttributionCaseAggregateConstants;
|
|
|
+import com.etotem.cfc.service.attribution.model.LowSideReadingEntry;
|
|
|
+import com.etotem.cfc.service.attribution.model.LowSideStrategyCluster;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+/**
|
|
|
+ * P3 跨案例聚合服务(能力键 {@code guhe.p3.lowSideAggregate},T15)。
|
|
|
+ *
|
|
|
+ * <p>低侧口径并入既有 P3 聚合口径:按《2026-11-08 浠艾福平台程序修改计划》§三-T15,
|
|
|
+ * 以「条件致病菌低侧判读补充(人工)」为统一判读口径,把跨案例的
|
|
|
+ * 「同条件致病菌偏低」归因节点聚为同一策略集群,可检索「同条件致病菌低侧」应对方案。
|
|
|
+ *
|
|
|
+ * <p><b>口径纪律</b>(§三-L4 归因引擎③ / §五 边界声明 / G-1 低侧红线组):
|
|
|
+ * 条件致病菌「偏低」按四机制解释、全部标「关联非因果」;参考下限 0 的菌不标「偏低超标」;
|
|
|
+ * ⛔ 禁「缺 X → 买含 X 菌株产品」桥接——聚合输出的应对方案不因偏低而推荐补充该菌株产品。
|
|
|
+ *
|
|
|
+ * @author 家庭归因阶段二(ATT-S2-B1,T15 跨案例聚合)
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class AttributionCaseAggregateService {
|
|
|
+
|
|
|
+ /** 判读口径键名(§2.2 KB 新增子键,聚合口径事实锚点) */
|
|
|
+ public static final String LOW_SIDE_READING_KEY = AttributionCaseAggregateConstants.LOW_SIDE_READING_KEY;
|
|
|
+
|
|
|
+ /** 聚合口径边界声明(§五) */
|
|
|
+ public static final String LOW_SIDE_ASSOCIATION_ONLY_NOTICE =
|
|
|
+ AttributionCaseAggregateConstants.LOW_SIDE_ASSOCIATION_ONLY_NOTICE;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按低侧判读口径聚合跨案例归因节点,检索「同条件致病菌低侧」应对方案(聚合同策略)。
|
|
|
+ *
|
|
|
+ * <p>聚类键 = 条件致病菌(优先 indicatorCode,回落 indicator 显示名)+ 低侧判读口径锚点;
|
|
|
+ * 同一聚类键下的跨案例节点合并为一个策略集群,应对方向跨案例去重合并。
|
|
|
+ *
|
|
|
+ * @param lowSideCases 跨案例「条件致病菌偏低」归因节点清单(可为 null/空)
|
|
|
+ * @return 按(条件致病菌 + 低侧判读口径)聚类后的同策略集群;无输入时返回空列表
|
|
|
+ */
|
|
|
+ public List<LowSideStrategyCluster> aggregateLowSideStrategy(List<LowSideReadingEntry> lowSideCases) {
|
|
|
+ List<LowSideStrategyCluster> clusters = new ArrayList<LowSideStrategyCluster>();
|
|
|
+ if (lowSideCases == null || lowSideCases.isEmpty()) {
|
|
|
+ return clusters;
|
|
|
+ }
|
|
|
+ Map<String, List<LowSideReadingEntry>> groups = new LinkedHashMap<String, List<LowSideReadingEntry>>();
|
|
|
+ for (LowSideReadingEntry entry : lowSideCases) {
|
|
|
+ if (entry == null || blank(entry.getIndicator())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String key = clusterKey(entry) + "|" + LOW_SIDE_READING_KEY;
|
|
|
+ List<LowSideReadingEntry> group = groups.get(key);
|
|
|
+ if (group == null) {
|
|
|
+ group = new ArrayList<LowSideReadingEntry>();
|
|
|
+ groups.put(key, group);
|
|
|
+ }
|
|
|
+ group.add(entry);
|
|
|
+ }
|
|
|
+ for (List<LowSideReadingEntry> group : groups.values()) {
|
|
|
+ clusters.add(cluster(group));
|
|
|
+ }
|
|
|
+ return clusters;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 聚类键:条件致病菌(指标编码优先,回落显示名) */
|
|
|
+ private static String clusterKey(LowSideReadingEntry entry) {
|
|
|
+ if (!blank(entry.getIndicatorCode())) {
|
|
|
+ return entry.getIndicatorCode().trim();
|
|
|
+ }
|
|
|
+ return entry.getIndicator().trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 同一「条件致病菌低侧」的跨案例节点 → 一个同策略集群 */
|
|
|
+ private LowSideStrategyCluster cluster(List<LowSideReadingEntry> group) {
|
|
|
+ LowSideStrategyCluster c = new LowSideStrategyCluster();
|
|
|
+ LowSideReadingEntry first = group.get(0);
|
|
|
+ c.setIndicator(first.getIndicator());
|
|
|
+ c.setReadingKey(LOW_SIDE_READING_KEY);
|
|
|
+ c.setStrategyTitle("同条件致病菌低侧 → 同策略(" + first.getIndicator() + ")");
|
|
|
+ // 低侧机制一律关联非因果(G-1 强制,不构成健康评估/诊断/干预建议)
|
|
|
+ c.setAssociationOnly(true);
|
|
|
+ Set<String> caseIds = new LinkedHashSet<String>();
|
|
|
+ Set<Long> memberIds = new LinkedHashSet<Long>();
|
|
|
+ Set<String> measures = new LinkedHashSet<String>();
|
|
|
+ for (LowSideReadingEntry entry : group) {
|
|
|
+ if (!blank(entry.getCaseId())) {
|
|
|
+ caseIds.add(entry.getCaseId().trim());
|
|
|
+ }
|
|
|
+ if (entry.getMemberId() != null) {
|
|
|
+ memberIds.add(entry.getMemberId());
|
|
|
+ }
|
|
|
+ if (entry.getMeasures() != null) {
|
|
|
+ for (String m : entry.getMeasures()) {
|
|
|
+ if (!blank(m)) {
|
|
|
+ measures.add(m.trim());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c.setCaseIds(new ArrayList<String>(caseIds));
|
|
|
+ c.setMemberIds(new ArrayList<Long>(memberIds));
|
|
|
+ c.setMeasures(new ArrayList<String>(measures));
|
|
|
+ c.setCaseCount(group.size());
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean blank(String s) {
|
|
|
+ return s == null || s.trim().isEmpty();
|
|
|
+ }
|
|
|
+}
|