|
@@ -55,7 +55,7 @@ class HealthReportServiceIndicatorsTest {
|
|
|
service = new HealthReportService();
|
|
service = new HealthReportService();
|
|
|
healthReportMapper = mock(HealthReportMapper.class);
|
|
healthReportMapper = mock(HealthReportMapper.class);
|
|
|
mappingService = mock(IndicatorMappingService.class);
|
|
mappingService = mock(IndicatorMappingService.class);
|
|
|
- IndicatorValueMapper valueMapper = mock(IndicatorValueMapper.class);
|
|
|
|
|
|
|
+ valueMapper = mock(IndicatorValueMapper.class);
|
|
|
// 真实 changeService(computeChange 走真实计算)+ spy 拦截 batchResolve 的查库
|
|
// 真实 changeService(computeChange 走真实计算)+ spy 拦截 batchResolve 的查库
|
|
|
IndicatorChangeService realChange = new IndicatorChangeService();
|
|
IndicatorChangeService realChange = new IndicatorChangeService();
|
|
|
ReflectionTestUtils.setField(realChange, "valueMapper", valueMapper);
|
|
ReflectionTestUtils.setField(realChange, "valueMapper", valueMapper);
|
|
@@ -117,16 +117,22 @@ class HealthReportServiceIndicatorsTest {
|
|
|
|
|
|
|
|
// ==================== #1 三档形态 ====================
|
|
// ==================== #1 三档形态 ====================
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 三档形态齐备:档 A(有上一期 → improved)/ 档 B2(无上一期但有更早报告 →
|
|
|
|
|
+ * pending_backfill)/ 未命中字典(previous 与 change 恒 null)。
|
|
|
|
|
+ */
|
|
|
@Test
|
|
@Test
|
|
|
void reportIndicators_threeTiersPresent() {
|
|
void reportIndicators_threeTiersPresent() {
|
|
|
HealthReport r = report();
|
|
HealthReport r = report();
|
|
|
List<HealthIndicator> indicators = Arrays.asList(
|
|
List<HealthIndicator> indicators = Arrays.asList(
|
|
|
indicator(9001L, "低密度脂蛋白 LDL-C", "3.62", "偏高"), // 有上一期 → improved
|
|
indicator(9001L, "低密度脂蛋白 LDL-C", "3.62", "偏高"), // 有上一期 → improved
|
|
|
- indicator(9002L, "白细胞计数", "5.20", "正常")); // 无上一期 + 有更早报告 → pending_backfill
|
|
|
|
|
|
|
+ indicator(9002L, "白细胞计数", "5.20", "正常"), // 命中字典但无上一期 → pending_backfill
|
|
|
|
|
+ indicator(9003L, "未知指标 X", "+", "异常")); // 未命中字典 → 无对比
|
|
|
|
|
|
|
|
- // 映射:只有 LDL 命中字典,白细胞未命中(definitionId=null)
|
|
|
|
|
|
|
+ // 映射:LDL 与白细胞命中字典,未知指标 X 未命中
|
|
|
Map<String, IndicatorMappingService.ResolvedMapping> resolvedMap = new HashMap<>();
|
|
Map<String, IndicatorMappingService.ResolvedMapping> resolvedMap = new HashMap<>();
|
|
|
resolvedMap.put("低密度脂蛋白 LDL-C", resolved("低密度脂蛋白 LDL-C", 512L, "低密度脂蛋白 LDL-C"));
|
|
resolvedMap.put("低密度脂蛋白 LDL-C", resolved("低密度脂蛋白 LDL-C", 512L, "低密度脂蛋白 LDL-C"));
|
|
|
|
|
+ resolvedMap.put("白细胞计数", resolved("白细胞计数", 513L, "白细胞计数"));
|
|
|
when(mappingService.resolveBatch(anyString(), anyCollection())).thenReturn(resolvedMap);
|
|
when(mappingService.resolveBatch(anyString(), anyCollection())).thenReturn(resolvedMap);
|
|
|
|
|
|
|
|
// 上一期:LDL 有上一期值;白细胞无
|
|
// 上一期:LDL 有上一期值;白细胞无
|
|
@@ -140,10 +146,11 @@ class HealthReportServiceIndicatorsTest {
|
|
|
prev.setUnit("mmol/L");
|
|
prev.setUnit("mmol/L");
|
|
|
prev.setStatus("偏高");
|
|
prev.setStatus("偏高");
|
|
|
prevRows.add(prev);
|
|
prevRows.add(prev);
|
|
|
- when(changeService.batchResolve(any(), any(), any(), any())).thenReturn(
|
|
|
|
|
- new HashMap<Long, IndicatorValue>() {{ put(512L, prev); }});
|
|
|
|
|
|
|
+ Map<Long, IndicatorValue> prevMap = new HashMap<>();
|
|
|
|
|
+ prevMap.put(512L, prev);
|
|
|
|
|
+ doReturn(prevMap).when(changeService).batchResolve(any(), any(), any(), any());
|
|
|
|
|
|
|
|
- // 更早报告 3 份 → olderReportCount=3(无上一期 → pending_backfill)
|
|
|
|
|
|
|
+ // 更早报告 3 份 → olderReportCount=3(白细胞无上一期 → pending_backfill)
|
|
|
List<HealthReport> older = new ArrayList<>();
|
|
List<HealthReport> older = new ArrayList<>();
|
|
|
for (int i = 0; i < 3; i++) {
|
|
for (int i = 0; i < 3; i++) {
|
|
|
HealthReport o = new HealthReport();
|
|
HealthReport o = new HealthReport();
|
|
@@ -159,12 +166,13 @@ class HealthReportServiceIndicatorsTest {
|
|
|
assertEquals(3L, ((Number) data.get("olderReportCount")).longValue());
|
|
assertEquals(3L, ((Number) data.get("olderReportCount")).longValue());
|
|
|
|
|
|
|
|
List<Map<String, Object>> items = itemsOf(data);
|
|
List<Map<String, Object>> items = itemsOf(data);
|
|
|
- assertEquals(2, items.size());
|
|
|
|
|
|
|
+ assertEquals(3, items.size());
|
|
|
|
|
|
|
|
- // 档 A:有上一期 → improved
|
|
|
|
|
|
|
+ // 档 A:有上一期 → improved(LDL 从 4.10 降到 3.62,lower_is_better)
|
|
|
Map<String, Object> ldl = items.get(0);
|
|
Map<String, Object> ldl = items.get(0);
|
|
|
assertEquals(512L, ((Number) ldl.get("definitionId")).longValue());
|
|
assertEquals(512L, ((Number) ldl.get("definitionId")).longValue());
|
|
|
assertNotNull(ldl.get("previous"));
|
|
assertNotNull(ldl.get("previous"));
|
|
|
|
|
+ assertEquals(11800L, ((Number) ((Map<String, Object>) ldl.get("previous")).get("reportId")).longValue());
|
|
|
@SuppressWarnings("unchecked")
|
|
@SuppressWarnings("unchecked")
|
|
|
Map<String, Object> ldlChange = (Map<String, Object>) ldl.get("change");
|
|
Map<String, Object> ldlChange = (Map<String, Object>) ldl.get("change");
|
|
|
assertEquals("improved", ldlChange.get("trendCode"));
|
|
assertEquals("improved", ldlChange.get("trendCode"));
|
|
@@ -172,18 +180,32 @@ class HealthReportServiceIndicatorsTest {
|
|
|
assertEquals(-0.48, ((BigDecimal) ldlChange.get("delta")).doubleValue(), 1e-9);
|
|
assertEquals(-0.48, ((BigDecimal) ldlChange.get("delta")).doubleValue(), 1e-9);
|
|
|
assertEquals(true, ldlChange.get("comparable"));
|
|
assertEquals(true, ldlChange.get("comparable"));
|
|
|
|
|
|
|
|
- // 档 B2:无上一期 + 有更早报告 → pending_backfill(诚实降级,不是"首次记录")
|
|
|
|
|
|
|
+ // 档 B1/B2 分界:命中字典但无上一期 + 有更早报告 → pending_backfill(诚实降级,不是"首次记录")
|
|
|
Map<String, Object> wbc = items.get(1);
|
|
Map<String, Object> wbc = items.get(1);
|
|
|
- assertNull(wbc.get("definitionId"), "未命中字典的指标 definitionId 为 null");
|
|
|
|
|
|
|
+ assertEquals(513L, ((Number) wbc.get("definitionId")).longValue());
|
|
|
assertNull(wbc.get("previous"));
|
|
assertNull(wbc.get("previous"));
|
|
|
- assertNull(wbc.get("change"), "未命中字典时无对比对象");
|
|
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ Map<String, Object> wbcChange = (Map<String, Object>) wbc.get("change");
|
|
|
|
|
+ assertEquals("pending_backfill", wbcChange.get("trendCode"));
|
|
|
|
|
+ assertEquals("历史数据整理中,暂无法对比", wbcChange.get("trendLabel"));
|
|
|
|
|
+ assertEquals(false, wbcChange.get("comparable"));
|
|
|
|
|
+
|
|
|
|
|
+ // 未命中字典:definitionId / previous / change 全为 null,前端按"无对比"渲染
|
|
|
|
|
+ Map<String, Object> unknown = items.get(2);
|
|
|
|
|
+ assertNull(unknown.get("definitionId"), "未命中字典的指标 definitionId 为 null");
|
|
|
|
|
+ assertNull(unknown.get("previous"));
|
|
|
|
|
+ assertNull(unknown.get("change"), "未命中字典时无对比对象");
|
|
|
|
|
|
|
|
// summary
|
|
// summary
|
|
|
@SuppressWarnings("unchecked")
|
|
@SuppressWarnings("unchecked")
|
|
|
Map<String, Object> summary = (Map<String, Object>) data.get("summary");
|
|
Map<String, Object> summary = (Map<String, Object>) data.get("summary");
|
|
|
- assertEquals(2, ((Number) summary.get("totalCount")).intValue());
|
|
|
|
|
- assertEquals(1, ((Number) summary.get("improvedCount")).intValue());
|
|
|
|
|
- assertEquals(1, ((Number) summary.get("abnormalCount")).intValue());
|
|
|
|
|
|
|
+ assertEquals(3, ((Number) summary.get("totalCount")).intValue());
|
|
|
|
|
+ assertEquals(1, ((Number) summary.get("changedCount")).intValue());
|
|
|
|
|
+ assertEquals(2, ((Number) summary.get("abnormalCount")).intValue());
|
|
|
|
|
+ assertEquals(1, ((Number) summary.get("comparableCount")).intValue());
|
|
|
|
|
+ assertEquals(0, ((Number) summary.get("firstRecordCount")).intValue());
|
|
|
|
|
+ assertEquals(1, ((Number) summary.get("pendingCount")).intValue());
|
|
|
|
|
+ assertEquals(true, summary.get("dataIncomplete"));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
@Test
|
|
@@ -196,7 +218,7 @@ class HealthReportServiceIndicatorsTest {
|
|
|
Map<String, IndicatorMappingService.ResolvedMapping> resolvedMap = new HashMap<>();
|
|
Map<String, IndicatorMappingService.ResolvedMapping> resolvedMap = new HashMap<>();
|
|
|
resolvedMap.put("载脂蛋白 A1", resolved("载脂蛋白 A1", 513L, "载脂蛋白 A1"));
|
|
resolvedMap.put("载脂蛋白 A1", resolved("载脂蛋白 A1", 513L, "载脂蛋白 A1"));
|
|
|
when(mappingService.resolveBatch(anyString(), anyCollection())).thenReturn(resolvedMap);
|
|
when(mappingService.resolveBatch(anyString(), anyCollection())).thenReturn(resolvedMap);
|
|
|
- when(changeService.batchResolve(any(), any(), any(), any())).thenReturn(new HashMap<>());
|
|
|
|
|
|
|
+ doReturn(new HashMap<Long, IndicatorValue>()).when(changeService).batchResolve(any(), any(), any(), any());
|
|
|
// 无更早报告 → olderReportCount=0 → first_record
|
|
// 无更早报告 → olderReportCount=0 → first_record
|
|
|
when(healthReportMapper.selectList(any())).thenReturn(new ArrayList<>());
|
|
when(healthReportMapper.selectList(any())).thenReturn(new ArrayList<>());
|
|
|
|
|
|
|
@@ -231,22 +253,21 @@ class HealthReportServiceIndicatorsTest {
|
|
|
resolvedMap.put(name, resolved(name, 500L + i, name));
|
|
resolvedMap.put(name, resolved(name, 500L + i, name));
|
|
|
}
|
|
}
|
|
|
when(mappingService.resolveBatch(anyString(), anyCollection())).thenReturn(resolvedMap);
|
|
when(mappingService.resolveBatch(anyString(), anyCollection())).thenReturn(resolvedMap);
|
|
|
- when(changeService.batchResolve(any(), any(), any(), any())).thenReturn(new HashMap<>());
|
|
|
|
|
|
|
+ doReturn(new HashMap<Long, IndicatorValue>()).when(changeService).batchResolve(any(), any(), any(), any());
|
|
|
when(healthReportMapper.selectList(any())).thenReturn(new ArrayList<>());
|
|
when(healthReportMapper.selectList(any())).thenReturn(new ArrayList<>());
|
|
|
|
|
|
|
|
- service.getReportIndicatorsWithPrevious(r, indicators, true);
|
|
|
|
|
|
|
+ Map<String, Object> data = service.getReportIndicatorsWithPrevious(r, indicators, true);
|
|
|
|
|
|
|
|
- // 服务层(报告与指标行由控制器加载后传入)固定 3 次查询:
|
|
|
|
|
|
|
+ // 服务层(报告与指标行由控制器加载后传入)固定 3 次查询,与指标条数无关:
|
|
|
// ① health_reports(olderReportCount)
|
|
// ① health_reports(olderReportCount)
|
|
|
// ② indicator_mapping_rules + indicator_definitions 联合查询(resolveBatch)
|
|
// ② indicator_mapping_rules + indicator_definitions 联合查询(resolveBatch)
|
|
|
// ③ indicator_values(batchResolve 上一期)
|
|
// ③ indicator_values(batchResolve 上一期)
|
|
|
verify(healthReportMapper, times(1)).selectList(any());
|
|
verify(healthReportMapper, times(1)).selectList(any());
|
|
|
verify(mappingService, times(1)).resolveBatch(anyString(), anyCollection());
|
|
verify(mappingService, times(1)).resolveBatch(anyString(), anyCollection());
|
|
|
verify(changeService, times(1)).batchResolve(any(), any(), any(), any());
|
|
verify(changeService, times(1)).batchResolve(any(), any(), any(), any());
|
|
|
- // 绝不能逐指标查库:42 项指标只允许 1 次 indicator_values 查询
|
|
|
|
|
- verify(valueMapper, times(1)).selectList(any());
|
|
|
|
|
|
|
+ // 服务层自身绝不碰 indicator_values 表:全部观测访问必须经由 changeService 批量入口
|
|
|
|
|
+ verify(valueMapper, never()).selectList(any());
|
|
|
|
|
|
|
|
- assertEquals(42, itemsOf(service.getReportIndicatorsWithPrevious(r, indicators, true)).size(),
|
|
|
|
|
- "42 项指标必须全部返回");
|
|
|
|
|
|
|
+ assertEquals(42, itemsOf(data).size(), "42 项指标必须全部返回");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|