Преглед на файлове

feat(backend): ReportBlockAssembler gut_flora 组装 + fromMap 键名转换

Xiaogang Liao преди 1 месец
родител
ревизия
41fe832490

+ 196 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ReportBlockAssembler.java

@@ -0,0 +1,196 @@
+package com.etotem.cfc.service;
+
+import com.etotem.cfc.dto.ParsedReportPayload;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 报告通用展示块组装器 — 解析结果 → blocks JSON 结构
+ * 前端只消费 blocks,不接触实体字段名。
+ */
+@Service
+public class ReportBlockAssembler {
+
+    private static final java.util.Set<String> IMPORTANT_LEVELS =
+            new java.util.HashSet<>(java.util.Arrays.asList("需注意", "注意", "高风险", "异常"));
+
+    /** 判断是否重要风险(需注意/注意/高风险/异常 → 重要;低风险/其余 → 其它) */
+    public boolean isImportantRisk(String level) {
+        return level != null && IMPORTANT_LEVELS.contains(level);
+    }
+
+    /** 编辑链路:第一套键 Map → 标准 Payload(第二套键) */
+    public ParsedReportPayload.Payload fromMap(Map<String, Object> map) {
+        ParsedReportPayload.Payload p = new ParsedReportPayload.Payload();
+        if (map == null || map.isEmpty()) return p;
+        ParsedReportPayload.Summary s = new ParsedReportPayload.Summary();
+        s.setOverallScore(toInt(map.get("overallScore")));
+        s.setGutHealthScore(toInt(map.get("gutHealthScore")));
+        s.setChronicDiseaseScore(toInt(map.get("chronicDiseaseScore")));
+        s.setNutritionScore(toInt(map.get("nutritionScore")));
+        s.setBalanceScore(toInt(map.get("gutBalanceScore")));
+        s.setDiversityScore(toInt(map.get("gutDiversityScore")));
+        s.setBeneficialScore(toInt(map.get("beneficialBacteriaScore")));
+        s.setHarmfulScore(toInt(map.get("harmfulBacteriaScore")));
+        s.setCoreGenusScore(toInt(map.get("coreSpeciesScore")));
+        s.setGutAge(str(map.get("gutAge")));
+        s.setGutType(str(map.get("gutType")));
+        p.setSummary(s);
+        return p;
+    }
+
+    /** 肠道菌群报告 → blocks */
+    public List<Map<String, Object>> assembleGutFlora(ParsedReportPayload.Payload payload) {
+        List<Map<String, Object>> blocks = new ArrayList<>();
+        if (payload == null) return blocks;
+
+        // 1. score 块:9 项评分(null 跳过)
+        ParsedReportPayload.Summary s = payload.getSummary();
+        if (s != null) {
+            List<Map<String, Object>> items = new ArrayList<>();
+            addScore(items, "综合", s.getOverallScore());
+            addScore(items, "菌群健康", s.getGutHealthScore());
+            addScore(items, "慢病控制", s.getChronicDiseaseScore());
+            addScore(items, "营养均衡", s.getNutritionScore());
+            addScore(items, "平衡", s.getBalanceScore());
+            addScore(items, "多样性", s.getDiversityScore());
+            addScore(items, "有益菌", s.getBeneficialScore());
+            addScore(items, "有害菌", s.getHarmfulScore());
+            addScore(items, "核心菌属", s.getCoreGenusScore());
+            if (!items.isEmpty()) {
+                Map<String, Object> block = new LinkedHashMap<>();
+                block.put("type", "score");
+                block.put("title", "健康评分");
+                block.put("items", items);
+                blocks.add(block);
+            }
+        }
+
+        // 2. risk_group 块:按风险等级分两组
+        List<ParsedReportPayload.DiseaseRisk> risks = payload.getDiseaseRisks();
+        if (risks != null && !risks.isEmpty()) {
+            List<Map<String, Object>> important = new ArrayList<>();
+            List<Map<String, Object>> normal = new ArrayList<>();
+            for (ParsedReportPayload.DiseaseRisk r : risks) {
+                Map<String, Object> m = new LinkedHashMap<>();
+                m.put("name", r.getDiseaseName());
+                m.put("value", r.getRiskValue());
+                m.put("level", r.getRiskLevel());
+                if (isImportantRisk(r.getRiskLevel())) important.add(m);
+                else normal.add(m);
+            }
+            if (!important.isEmpty()) blocks.add(riskBlock("重要风险", important));
+            if (!normal.isEmpty()) blocks.add(riskBlock("其它风险", normal));
+        }
+
+        // 3. indicator 块:indicators 按 category 分组
+        List<ParsedReportPayload.Indicator> inds = payload.getIndicators();
+        if (inds != null && !inds.isEmpty()) {
+            Map<String, List<Map<String, Object>>> byCategory = new LinkedHashMap<>();
+            for (ParsedReportPayload.Indicator ind : inds) {
+                String cat = ind.getCategory() == null ? "其他指标" : ind.getCategory();
+                byCategory.computeIfAbsent(cat, k -> new ArrayList<>()).add(indicatorItem(ind));
+            }
+            for (Map.Entry<String, List<Map<String, Object>>> e : byCategory.entrySet()) {
+                Map<String, Object> block = new LinkedHashMap<>();
+                block.put("type", "indicator");
+                block.put("title", e.getKey());
+                block.put("items", e.getValue());
+                blocks.add(block);
+            }
+        }
+
+        // 4. list 块:菌种列表(gutFlora + 病原菌)与食物推荐
+        List<Map<String, Object>> floraItems = new ArrayList<>();
+        addFloraItems(floraItems, payload.getGutFlora());
+        addFloraItems(floraItems, payload.getPathogenGenus());
+        addFloraItems(floraItems, payload.getPathogenDetection());
+        if (!floraItems.isEmpty()) {
+            Map<String, Object> block = new LinkedHashMap<>();
+            block.put("type", "list");
+            block.put("title", "菌种详情");
+            block.put("columns", java.util.Arrays.asList(
+                    col("name", "菌种"), col("value", "数值"), col("range", "正常范围"), col("status", "状态")));
+            block.put("items", floraItems);
+            blocks.add(block);
+        }
+        List<ParsedReportPayload.FoodItem> foods = payload.getFoods();
+        if (foods != null && !foods.isEmpty()) {
+            List<Map<String, Object>> foodItems = new ArrayList<>();
+            for (ParsedReportPayload.FoodItem f : foods) {
+                Map<String, Object> m = new LinkedHashMap<>();
+                m.put("name", f.getName());
+                m.put("category", f.getCategory());
+                m.put("score", f.getScore());
+                foodItems.add(m);
+            }
+            Map<String, Object> block = new LinkedHashMap<>();
+            block.put("type", "list");
+            block.put("title", "食物推荐");
+            block.put("columns", java.util.Arrays.asList(
+                    col("name", "食材"), col("category", "类别"), col("score", "推荐指数")));
+            block.put("items", foodItems);
+            blocks.add(block);
+        }
+        return blocks;
+    }
+
+    private Map<String, Object> riskBlock(String title, List<Map<String, Object>> items) {
+        Map<String, Object> block = new LinkedHashMap<>();
+        block.put("type", "risk_group");
+        block.put("title", title);
+        block.put("items", items);
+        return block;
+    }
+
+    private Map<String, Object> indicatorItem(ParsedReportPayload.Indicator ind) {
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("name", ind.getIndicatorName());
+        m.put("value", ind.getIndicatorValue());
+        m.put("unit", ind.getUnit());
+        m.put("refRange", ind.getRefRange());
+        m.put("status", ind.getStatus());
+        return m;
+    }
+
+    private void addFloraItems(List<Map<String, Object>> items, List<ParsedReportPayload.Flora> list) {
+        if (list == null) return;
+        for (ParsedReportPayload.Flora f : list) {
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("name", f.getBacteriaName());
+            m.put("value", f.getBacteriaValue());
+            m.put("range", f.getNormalRange());
+            m.put("status", f.getPopulationLevel() != null ? f.getPopulationLevel() : f.getLevel());
+            items.add(m);
+        }
+    }
+
+    private void addScore(List<Map<String, Object>> items, String label, Integer value) {
+        if (value == null) return;
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("label", label);
+        m.put("value", value);
+        m.put("max", 100);
+        items.add(m);
+    }
+
+    private Map<String, Object> col(String key, String label) {
+        Map<String, Object> c = new LinkedHashMap<>();
+        c.put("key", key);
+        c.put("label", label);
+        return c;
+    }
+
+    private Integer toInt(Object v) {
+        if (v == null) return null;
+        try { return Integer.valueOf(v.toString()); } catch (Exception e) { return null; }
+    }
+
+    private String str(Object v) {
+        return v == null ? null : v.toString();
+    }
+}

+ 80 - 0
cfc-backend/src/test/java/com/etotem/cfc/service/ReportBlockAssemblerTest.java

@@ -0,0 +1,80 @@
+package com.etotem.cfc.service;
+
+import com.etotem.cfc.dto.ParsedReportPayload;
+import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.Map;
+import static org.junit.jupiter.api.Assertions.*;
+
+class ReportBlockAssemblerTest {
+
+    private final ReportBlockAssembler assembler = new ReportBlockAssembler();
+
+    private ParsedReportPayload.Payload samplePayload() {
+        ParsedReportPayload.Payload p = new ParsedReportPayload.Payload();
+        ParsedReportPayload.Summary s = new ParsedReportPayload.Summary();
+        s.setOverallScore(57);
+        s.setGutHealthScore(76);
+        s.setChronicDiseaseScore(64);
+        s.setNutritionScore(30);
+        s.setBalanceScore(38);
+        s.setDiversityScore(50);
+        s.setBeneficialScore(22);
+        s.setHarmfulScore(26);
+        s.setCoreGenusScore(85);
+        s.setGutAge("55.52");
+        s.setGutType("普雷沃氏菌型");
+        p.setSummary(s);
+
+        ParsedReportPayload.DiseaseRisk r1 = new ParsedReportPayload.DiseaseRisk();
+        r1.setDiseaseName("心脑血管疾病"); r1.setRiskValue("0.37"); r1.setRiskLevel("注意");
+        ParsedReportPayload.DiseaseRisk r2 = new ParsedReportPayload.DiseaseRisk();
+        r2.setDiseaseName("抑郁症"); r2.setRiskValue("0.27"); r2.setRiskLevel("低风险");
+        ParsedReportPayload.DiseaseRisk r3 = new ParsedReportPayload.DiseaseRisk();
+        r3.setDiseaseName("炎症性肠炎"); r3.setRiskValue("0.24"); r3.setRiskLevel("低风险");
+        p.setDiseaseRisks(java.util.Arrays.asList(r1, r2, r3));
+        return p;
+    }
+
+    @Test
+    void assembleGutFlora_shouldContainNineScoreItems() {
+        List<Map<String, Object>> blocks = assembler.assembleGutFlora(samplePayload());
+        Map<String, Object> scoreBlock = blocks.stream()
+                .filter(b -> "score".equals(b.get("type"))).findFirst().orElse(null);
+        assertNotNull(scoreBlock, "应有score块");
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> items = (List<Map<String, Object>>) scoreBlock.get("items");
+        assertEquals(9, items.size(), "9项评分应齐全");
+        assertEquals("多样性", items.get(5).get("label"));
+        assertEquals(50, items.get(5).get("value"));
+    }
+
+    @Test
+    void assembleGutFlora_shouldGroupRiskByLevel() {
+        List<Map<String, Object>> blocks = assembler.assembleGutFlora(samplePayload());
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> riskBlocks = (List<Map<String, Object>>) (List<?>) blocks.stream()
+                .filter(b -> "risk_group".equals(b.get("type"))).collect(java.util.stream.Collectors.toList());
+        assertEquals(2, riskBlocks.size(), "应分重要/其它两组");
+        assertEquals("重要风险", riskBlocks.get(0).get("title"));
+        assertEquals("其它风险", riskBlocks.get(1).get("title"));
+    }
+
+    @Test
+    void fromMap_shouldMapFirstSetKeysToSecondSet() {
+        java.util.Map<String, Object> map = new java.util.HashMap<>();
+        map.put("overallScore", 57);
+        map.put("gutBalanceScore", 38);
+        map.put("gutDiversityScore", 50);
+        map.put("beneficialBacteriaScore", 22);
+        map.put("harmfulBacteriaScore", 26);
+        map.put("coreSpeciesScore", 85);
+        ParsedReportPayload.Payload p = assembler.fromMap(map);
+        assertEquals(57, p.getSummary().getOverallScore());
+        assertEquals(38, p.getSummary().getBalanceScore());
+        assertEquals(50, p.getSummary().getDiversityScore());
+        assertEquals(22, p.getSummary().getBeneficialScore());
+        assertEquals(26, p.getSummary().getHarmfulScore());
+        assertEquals(85, p.getSummary().getCoreGenusScore());
+    }
+}