|
@@ -0,0 +1,312 @@
|
|
|
|
|
+package com.etotem.cfc.unit;
|
|
|
|
|
+
|
|
|
|
|
+import com.etotem.cfc.constant.AttributionLowSideRules;
|
|
|
|
|
+import com.etotem.cfc.service.attribution.AttributionComplianceGate;
|
|
|
|
|
+import com.etotem.cfc.service.attribution.AttributionReportBuilder;
|
|
|
|
|
+import com.etotem.cfc.service.attribution.model.IndicatorReading;
|
|
|
|
|
+import com.etotem.cfc.service.attribution.model.MemberAttribution;
|
|
|
|
|
+import org.junit.jupiter.api.DisplayName;
|
|
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 契约级单测:谷禾 v2 低侧红线组(能力键 {@code guhe.gate.lowSideRedline},T5/G-1,P0)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>契约来源(2026-11-08《浠艾福平台程序修改计划(guhe_v2 兼容 · 知识库低侧口径 · 专家同步)》§三任务分解 T5):
|
|
|
|
|
+ * <blockquote>
|
|
|
|
|
+ * T5 低侧红线组(G-1):新增合规规则——{@code association_only == true};禁写「缺 X→致病/需补充」;
|
|
|
|
|
+ * 禁把调整建议写成处方;措辞白名单「相关联/报告提示/可能与…有关」。
|
|
|
|
|
+ * <br>验收:任意「偏低」归因节点 {@code association_only==true};存量无「缺大肠杆菌→补菌」措辞;
|
|
|
|
|
+ * 低侧机制证据全部标 PUB/KB(该子项归 T11 证据码隔离 G-3,本节不另断言)。</blockquote></p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p><b>契约语义</b>:本能力键是「合规门禁(横切)」的低侧红线组——在 L4 出结论前对任意
|
|
|
|
|
+ * 「偏低」归因节点强制 {@code association_only == true}(低侧机制禁因果),并在 L5 出文档前
|
|
|
|
|
+ * 反向回扫「缺 X→致病/需补充」桥接与处方化建议,命中即收集为合规违规(不阻断,E4 弱提示),
|
|
|
|
|
+ * 由报告侧 {@code sections.complianceGate} 暴露。挂载点(C 档待确认项 #4)经自主探测落于
|
|
|
|
|
+ * {@link AttributionAnalysisServiceImpl#analyze}(L4 enforce + L5 scan)。</p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p><b>实现现状(worktree src/main 探测,2026-11-09)</b>:T5/G-1 门禁已落地
|
|
|
|
|
+ * ({@code AttributionLowSideRules} / {@code AttributionComplianceGate} / 报告
|
|
|
|
|
+ * {@code sections.complianceGate} / {@link IndicatorReading#associationOnly} 已并入主链
|
|
|
|
|
+ * commit a13bb8192)。本契约测试据此断言契约行为;若实测断言全过(绿),按任务纪律注明
|
|
|
|
|
+ * 「实现已存在」并交编排侧转测试登记,不私改实现。</p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p><b>GREEN 升级指引(若未来实现回退/缺失)</b>:在 {@code AttributionLowSideRules} 保留
|
|
|
|
|
+ * {@code GROUP_CODE="G-1"} 与三规则码 + 措辞白名单;{@code AttributionComplianceGate} 提供
|
|
|
|
|
+ * {@code enforceLowSide(attributions)}(偏低强制 association_only)与 {@code scan(text)}
|
|
|
|
|
+ * (缺X桥接/处方回扫);{@code AttributionReportBuilder} 暴露 {@code sections.complianceGate}
|
|
|
|
|
+ * 后本测试自动转绿。</p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p><b>权限流说明</b>:本能力为后端内部合规门禁(L4 出结论前 / L5 出文档前),无用户权限
|
|
|
|
|
+ * 维度,权限流不适用,以说明性用例标注。</p>
|
|
|
|
|
+ */
|
|
|
|
|
+class GuheGateLowSideRedlineContractTest {
|
|
|
|
|
+
|
|
|
|
|
+ private final AttributionComplianceGate gate = new AttributionComplianceGate();
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 正常流:规则层常量(G-1 分组 + 三规则码 + 措辞白名单)
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-1:AttributionLowSideRules 应暴露 G-1 分组码与三规则码")
|
|
|
|
|
+ void lowSideRules_exposesGroupAndRuleCodes() {
|
|
|
|
|
+ assertEquals("G-1", AttributionLowSideRules.GROUP_CODE,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 低侧红线组分组码应为 G-1(§三任务分解 T5)。");
|
|
|
|
|
+ assertNotNull(AttributionLowSideRules.GROUP_TITLE,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] GROUP_TITLE 不应为 null。");
|
|
|
|
|
+ assertEquals("association_only", AttributionLowSideRules.RULE_ASSOCIATION_ONLY,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 规则码 association_only(偏低节点禁因果)。");
|
|
|
|
|
+ assertEquals("no_causal_bridge", AttributionLowSideRules.RULE_NO_CAUSAL_BRIDGE,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 规则码 no_causal_bridge(禁缺X→致病/需补充)。");
|
|
|
|
|
+ assertEquals("no_prescription", AttributionLowSideRules.RULE_NO_PRESCRIPTION,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 规则码 no_prescription(禁处方化建议)。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-2:措辞白名单应含「相关联/报告提示/可能与…有关」")
|
|
|
|
|
+ void lowSideRules_wordingWhitelistCoversThreePhrases() {
|
|
|
|
|
+ List<String> whitelist = AttributionLowSideRules.WORDING_WHITELIST;
|
|
|
|
|
+ assertNotNull(whitelist, "[guhe.gate.lowSideRedline] WORDING_WHITELIST 不应为 null。");
|
|
|
|
|
+ assertFalse(whitelist.isEmpty(), "[guhe.gate.lowSideRedline] 措辞白名单不能为空。");
|
|
|
|
|
+ assertTrue(whitelist.contains("相关联"), "白名单须含「相关联」");
|
|
|
|
|
+ assertTrue(whitelist.contains("报告提示"), "白名单须含「报告提示」");
|
|
|
|
|
+ assertTrue(whitelist.contains("可能与…有关"), "白名单须含「可能与…有关」");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 正常流:enforceLowSide 对任意「偏低」归因节点强制 association_only
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-3:任意偏低归因节点强制 association_only=true(偏高/正常/待补不强制)")
|
|
|
|
|
+ void enforceLowSide_forcesAssociationOnlyOnEveryLowNode() {
|
|
|
|
|
+ MemberAttribution a = buildMember(1L, "妈妈", Arrays.asList(
|
|
|
|
|
+ reading("大肠杆菌", IndicatorReading.STATUS_LOW, "0.5%", "P3"),
|
|
|
|
|
+ reading("双歧杆菌", IndicatorReading.STATUS_LOW, "2.1%", "P10"),
|
|
|
|
|
+ reading("罗伊氏乳杆菌", IndicatorReading.STATUS_HIGH, "7.3%", "P90"),
|
|
|
|
|
+ reading("维生素D", IndicatorReading.STATUS_NORMAL, "45", null),
|
|
|
|
|
+ reading("血清铁", IndicatorReading.STATUS_PENDING, "8", null)
|
|
|
|
|
+ ));
|
|
|
|
|
+
|
|
|
|
|
+ AttributionComplianceGate.GateReport report =
|
|
|
|
|
+ gate.enforceLowSide(Collections.singletonList(a));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(2, report.associationOnlyCount(),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 2 个偏低节点应全部强制 associationOnly(验收:任意偏低节点)。");
|
|
|
|
|
+ assertTrue(report.getNodes().stream().anyMatch(n -> n.getIndicator().contains("大肠杆菌")));
|
|
|
|
|
+ assertTrue(report.getNodes().stream().anyMatch(n -> n.getIndicator().contains("双歧杆菌")));
|
|
|
|
|
+ for (AttributionComplianceGate.LowSideNode n : report.getNodes()) {
|
|
|
|
|
+ assertTrue(n.isAssociationOnly(),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 低侧节点 " + n.getIndicator() + " 应恒为仅关联(associationOnly)。");
|
|
|
|
|
+ }
|
|
|
|
|
+ for (IndicatorReading r : a.getReadings()) {
|
|
|
|
|
+ if (IndicatorReading.STATUS_LOW.equals(r.getStatus())) {
|
|
|
|
|
+ assertTrue(r.isAssociationOnly(),
|
|
|
|
|
+ r.getDisplayName() + " 应被强制 associationOnly=true");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ assertFalse(r.isAssociationOnly(),
|
|
|
|
|
+ r.getDisplayName() + " 不应被强制 associationOnly");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-4:无偏低节点时 enforceLowSide 返回空报告且不误报")
|
|
|
|
|
+ void enforceLowSide_noLowNode_emptyReport() {
|
|
|
|
|
+ MemberAttribution a = buildMember(1L, "爸爸", Collections.singletonList(
|
|
|
|
|
+ reading("血红蛋白", IndicatorReading.STATUS_NORMAL, "140", null)
|
|
|
|
|
+ ));
|
|
|
|
|
+ AttributionComplianceGate.GateReport report =
|
|
|
|
|
+ gate.enforceLowSide(Collections.singletonList(a));
|
|
|
|
|
+ assertTrue(report.getNodes().isEmpty(), "[guhe.gate.lowSideRedline] 无偏低节点时不应产出低侧节点。");
|
|
|
|
|
+ assertTrue(report.getViolations().isEmpty(), "[guhe.gate.lowSideRedline] 无偏低节点时不应有违规。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 正常流:scan 回扫「缺 X→致病/需补充」桥接(no_causal_bridge)
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-5:scan 命中「缺 X→致病/需补充」因果桥接 → no_causal_bridge 违规")
|
|
|
|
|
+ void scan_causalBridge_hitsNoCausalBridge() {
|
|
|
|
|
+ List<AttributionComplianceGate.Violation> vs = gate.scan(
|
|
|
|
|
+ "缺大肠杆菌→需补充该菌,否则易致肠炎感染");
|
|
|
|
|
+ assertFalse(vs.isEmpty(), "[guhe.gate.lowSideRedline] 缺X→需补充桥接应命中(T5 禁写)。");
|
|
|
|
|
+ assertViolationsContainRule(vs, AttributionLowSideRules.RULE_NO_CAUSAL_BRIDGE);
|
|
|
|
|
+ assertNotNull(vs.get(0).getSnippet(), "[guhe.gate.lowSideRedline] 违规应带命中片段。");
|
|
|
|
|
+ assertNotNull(vs.get(0).getSuggestion(), "[guhe.gate.lowSideRedline] 违规应带白名单改写建议。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-6:scan 命中「缺 X 补 X」产品桥接(报告缺X→买含X菌株产品)")
|
|
|
|
|
+ void scan_causalBridge_hitsSupplementProductBridge() {
|
|
|
|
|
+ List<AttributionComplianceGate.Violation> vs = gate.scan(
|
|
|
|
|
+ "报告缺大肠杆菌→建议购买含大肠杆菌菌株的产品");
|
|
|
|
|
+ assertFalse(vs.isEmpty(),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 报告缺X→买含X菌株产品桥接应命中(叠加 §5.2 16S 粒度声明)。");
|
|
|
|
|
+ assertViolationsContainRule(vs, AttributionLowSideRules.RULE_NO_CAUSAL_BRIDGE);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 正常流:scan 回扫处方化建议(no_prescription)
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-7:scan 命中处方化建议(剂量/用法用量/疗程)→ no_prescription 违规")
|
|
|
|
|
+ void scan_prescription_hitsNoPrescription() {
|
|
|
|
|
+ List<AttributionComplianceGate.Violation> vs = gate.scan(
|
|
|
|
|
+ "建议每日 5mg 补充维生素D3,连服 7 天,剂量 200IU/天");
|
|
|
|
|
+ assertFalse(vs.isEmpty(), "[guhe.gate.lowSideRedline] 处方化建议应命中(T5 禁把调整建议写成处方)。");
|
|
|
|
|
+ assertViolationsContainRule(vs, AttributionLowSideRules.RULE_NO_PRESCRIPTION);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("正常流-8:白名单关联措辞不误报(scan 无违规)")
|
|
|
|
|
+ void scan_whitelistWording_noFalsePositive() {
|
|
|
|
|
+ for (String safe : Arrays.asList(
|
|
|
|
|
+ "与肠道环境相关联",
|
|
|
|
|
+ "报告提示可能与饮食结构有关",
|
|
|
|
|
+ "可能与压力负荷有关",
|
|
|
|
|
+ "偏低与慢性炎症相关(仅描述关联)",
|
|
|
|
|
+ "报告提示营养状态与菌群多样性相关",
|
|
|
|
|
+ "儿童剂量由医生定",
|
|
|
|
|
+ "按产品说明服用"
|
|
|
|
|
+ )) {
|
|
|
|
|
+ List<AttributionComplianceGate.Violation> vs = gate.scan(safe);
|
|
|
|
|
+ assertTrue(vs.isEmpty(), "[guhe.gate.lowSideRedline] 白名单措辞不应误报:" + safe);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 边界流:merge 语义(base 节点保留 + 追加违规)
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("边界流-1:merge 追加违规并保留基础低侧节点")
|
|
|
|
|
+ void merge_appendsViolationsKeepsBaseNodes() {
|
|
|
|
|
+ MemberAttribution base = buildMember(1L, "女儿", Collections.singletonList(
|
|
|
|
|
+ reading("大肠杆菌", IndicatorReading.STATUS_LOW, "0.3%", "P1")
|
|
|
|
|
+ ));
|
|
|
|
|
+ AttributionComplianceGate.GateReport baseReport = gate.enforceLowSide(Collections.singletonList(base));
|
|
|
|
|
+ List<AttributionComplianceGate.Violation> extra = gate.scan("缺锌导致缺钙致病");
|
|
|
|
|
+ AttributionComplianceGate.GateReport merged = AttributionComplianceGate.merge(baseReport, extra);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(baseReport.getNodes().size(), merged.getNodes().size(),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] merge 应保留基础低侧节点。");
|
|
|
|
|
+ assertEquals(baseReport.associationOnlyCount(), merged.associationOnlyCount(),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] merge 后 association_only 计数不变。");
|
|
|
|
|
+ assertEquals(baseReport.getViolations().size() + extra.size(), merged.getViolations().size(),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] merge 应追加违规。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 边界流:门禁挂载点(L4 出结论前 enforce + L5 出文档前 scan)
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("边界流-2:AttributionAnalysisServiceImpl 应挂载 AttributionComplianceGate")
|
|
|
|
|
+ void analysisService_mountsComplianceGate() {
|
|
|
|
|
+ Class<?> svc = loadClass("com.etotem.cfc.service.impl.AttributionAnalysisServiceImpl");
|
|
|
|
|
+ assertNotNull(svc, "[guhe.gate.lowSideRedline] AttributionAnalysisServiceImpl 类应存在。");
|
|
|
|
|
+ boolean wired = false;
|
|
|
|
|
+ for (Field f : svc.getDeclaredFields()) {
|
|
|
|
|
+ if (AttributionComplianceGate.class.getName().equals(f.getType().getName())) {
|
|
|
|
|
+ wired = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ assertTrue(wired,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] AttributionAnalysisServiceImpl 应注入 AttributionComplianceGate "
|
|
|
|
|
+ + "(L4 出结论前 enforce + L5 出文档前 scan,C 档挂载点经探测落此)。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("边界流-3:AttributionReportBuilder 应暴露带 GateReport 的 buildReportJson 重载(sections.complianceGate)")
|
|
|
|
|
+ void reportBuilder_exposesGateReportOverload() {
|
|
|
|
|
+ boolean hasOverload = false;
|
|
|
|
|
+ for (Method m : AttributionReportBuilder.class.getDeclaredMethods()) {
|
|
|
|
|
+ if (!"buildReportJson".equals(m.getName())) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ Class<?>[] params = m.getParameterTypes();
|
|
|
|
|
+ if (params.length >= 8
|
|
|
|
|
+ && AttributionComplianceGate.GateReport.class == params[params.length - 1]) {
|
|
|
|
|
+ hasOverload = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ assertTrue(hasOverload,
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] AttributionReportBuilder 应提供带 "
|
|
|
|
|
+ + "AttributionComplianceGate.GateReport 的 buildReportJson 重载,"
|
|
|
|
|
+ + "供报告侧暴露 sections.complianceGate(低侧节点/违规/白名单)。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 权限流(不适用):后端内部合规门禁,无用户权限维度
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @DisplayName("权限流:本能力为后端内部合规门禁,无用户权限维度,权限流不适用")
|
|
|
|
|
+ void permissionFlow_notApplicable() {
|
|
|
|
|
+ // 说明性用例:契约 AC 为「L4 出结论前 / L5 出文档前」内部规则,无权限校验维度。
|
|
|
|
|
+ assertTrue(true, "权限流不适用:guhe.gate.lowSideRedline 为后端内部合规门禁,无用户权限维度。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // 反射与数据构造辅助
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ private static void assertViolationsContainRule(List<AttributionComplianceGate.Violation> vs, String rule) {
|
|
|
|
|
+ assertTrue(vs.stream().anyMatch(v -> rule.equals(v.getRule())),
|
|
|
|
|
+ "[guhe.gate.lowSideRedline] 违规列表缺少规则码「" + rule + "」:" + vs);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Class<?> loadClass(String name) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
|
|
|
|
|
+ } catch (ClassNotFoundException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static IndicatorReading reading(String display, String status, String value, String percentile) {
|
|
|
|
|
+ IndicatorReading r = new IndicatorReading();
|
|
|
|
|
+ r.setCode(display);
|
|
|
|
|
+ r.setDisplayName(display);
|
|
|
|
|
+ r.setValue(value);
|
|
|
|
|
+ r.setPercentile(percentile);
|
|
|
|
|
+ r.setStatus(status);
|
|
|
|
|
+ return r;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static MemberAttribution buildMember(Long memberId, String name, List<IndicatorReading> readings) {
|
|
|
|
|
+ MemberAttribution a = new MemberAttribution();
|
|
|
|
|
+ a.setMemberId(memberId);
|
|
|
|
|
+ a.setName(name);
|
|
|
|
|
+ a.setRole("mom");
|
|
|
|
|
+ a.setRoleLabel("妈妈");
|
|
|
|
|
+ a.setReadings(new ArrayList<>(readings));
|
|
|
|
|
+ a.setDimensionEvidence(new java.util.LinkedHashMap<>());
|
|
|
|
|
+ a.setFiveDim(new java.util.LinkedHashMap<>());
|
|
|
|
|
+ a.setWeakDimensions(new ArrayList<>());
|
|
|
|
|
+ a.setActions(new ArrayList<>());
|
|
|
|
|
+ for (IndicatorReading r : readings) {
|
|
|
|
|
+ if (r.isAbnormal()) {
|
|
|
|
|
+ a.setAbnormalCount(a.getAbnormalCount() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return a;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|