|
|
@@ -3,6 +3,9 @@ package com.etotem.cfc.service.impl;
|
|
|
import com.etotem.cfc.dto.HealthDimensionVO;
|
|
|
import com.etotem.cfc.dto.DimensionUploadVO;
|
|
|
import com.etotem.cfc.dto.HealthDimensionQuestionnaireVO;
|
|
|
+import com.etotem.cfc.dto.DimensionNode;
|
|
|
+import com.etotem.cfc.service.DimensionRegistryService;
|
|
|
+import com.etotem.cfc.service.DimensionCodeMapper;
|
|
|
import com.etotem.cfc.entity.HealthDimensionScore;
|
|
|
import com.etotem.cfc.entity.HealthDataSourceRecord;
|
|
|
import com.etotem.cfc.entity.HealthGutFlora;
|
|
|
@@ -38,15 +41,11 @@ import java.util.stream.Collectors;
|
|
|
@Service
|
|
|
public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
|
|
|
- private static final List<String[]> DIMENSIONS = Arrays.asList(
|
|
|
- new String[]{"growth", "生长发育"},
|
|
|
- new String[]{"sleep", "睡眠质量"},
|
|
|
- new String[]{"vision", "视力健康"},
|
|
|
- new String[]{"immunity", "免疫力"},
|
|
|
- new String[]{"nutrition", "营养均衡"},
|
|
|
- new String[]{"gut", "肠胃健康"},
|
|
|
- new String[]{"exercise", "运动活力"}
|
|
|
- );
|
|
|
+ @Resource
|
|
|
+ private DimensionRegistryService dimensionRegistryService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DimensionCodeMapper dimensionCodeMapper;
|
|
|
|
|
|
@Resource
|
|
|
private HealthDimensionScoreService scoreService;
|
|
|
@@ -76,20 +75,57 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
@Resource
|
|
|
private DimensionEnergySyncService dimensionEnergySyncService;
|
|
|
|
|
|
+ /**
|
|
|
+ * 统一维度体系中 7 个身体健康子维度(code=body.growth...body.exercise),来源为注册中心
|
|
|
+ * body 根节点子维度(nodeKind=sub),替代原前端/后端硬编码裸名单(growth/sleep/...)。
|
|
|
+ *
|
|
|
+ * <p>顺序与注册中心 seed 一致(growth/sleep/vision/immunity/nutrition/gut/exercise),
|
|
|
+ * 指标节点(nodeKind=indicator,P1-T3 起混排挂载)不作为健康雷达轴,跳过。</p>
|
|
|
+ *
|
|
|
+ * @return 注册中心 body 根节点的子维度节点列表;未就绪时返回空列表(调用方按空处理)
|
|
|
+ */
|
|
|
+ private List<DimensionNode> bodyDimensions() {
|
|
|
+ DimensionNode body = dimensionRegistryService.getNodeByCode("body");
|
|
|
+ if (body == null || body.getChildren() == null) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ List<DimensionNode> result = new ArrayList<>();
|
|
|
+ for (DimensionNode child : body.getChildren()) {
|
|
|
+ if (child == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ("indicator".equals(child.getNodeKind())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ result.add(child);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public HealthDimensionVO getHealthDimension(Long memberId) {
|
|
|
List<HealthDimensionScore> latest = scoreService.getLatestScores(memberId);
|
|
|
Map<String, HealthDimensionScore> scoreMap = latest.stream()
|
|
|
.collect(Collectors.toMap(HealthDimensionScore::getDimension, s -> s, (a, b) -> a));
|
|
|
|
|
|
+ // 统一维度体系整改:维度清单来源改为注册中心(body 根下子维度,统一 code=body.growth...),
|
|
|
+ // 兼容 DB 仍存旧裸名(growth/sleep/...)的过渡态:scoreMap 同时按 legacy 原文 + 统一码建索引。
|
|
|
+ Map<String, HealthDimensionScore> unifiedScoreMap = new HashMap<>(scoreMap);
|
|
|
+ for (Map.Entry<String, HealthDimensionScore> e : scoreMap.entrySet()) {
|
|
|
+ String unified = dimensionRegistryService.resolveLegacyToUnified(e.getKey());
|
|
|
+ if (unified != null) {
|
|
|
+ unifiedScoreMap.putIfAbsent(unified, e.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
List<HealthDimensionVO.DimensionItem> items = new ArrayList<>();
|
|
|
BigDecimal totalScore = BigDecimal.ZERO;
|
|
|
int validCount = 0;
|
|
|
|
|
|
- for (String[] dim : DIMENSIONS) {
|
|
|
- String code = dim[0];
|
|
|
- String label = dim[1];
|
|
|
- HealthDimensionScore found = scoreMap.get(code);
|
|
|
+ for (DimensionNode dim : bodyDimensions()) {
|
|
|
+ String code = dim.getCode();
|
|
|
+ String label = dim.getName();
|
|
|
+ HealthDimensionScore found = unifiedScoreMap.get(code);
|
|
|
|
|
|
HealthDimensionVO.DimensionItem item = new HealthDimensionVO.DimensionItem();
|
|
|
item.setDimension(code);
|
|
|
@@ -101,7 +137,7 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
item.setDataSource(found.getDataSource());
|
|
|
item.setTier(found.getTier());
|
|
|
item.setLevel(calcLevel(found.getScore()));
|
|
|
- List<HealthDimensionVO.DimensionItem> history = getDimensionHistory(memberId, code, 2);
|
|
|
+ List<HealthDimensionVO.DimensionItem> history = getDimensionHistory(memberId, resolveNormLegacy(code), 2);
|
|
|
if (history.size() >= 2) {
|
|
|
BigDecimal earlierScore = history.get(1).getScore();
|
|
|
BigDecimal latestScore = history.get(0).getScore();
|
|
|
@@ -144,7 +180,7 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
String gender = (member.getGender() != null && !member.getGender().isEmpty())
|
|
|
? member.getGender() : "all";
|
|
|
for (HealthDimensionVO.DimensionItem item : items) {
|
|
|
- String dimCode = item.getDimension();
|
|
|
+ String dimCode = resolveNormLegacy(item.getDimension());
|
|
|
HealthNormReference norm = normMapper.selectNorm(dimCode, gender, (int) ageMonths);
|
|
|
if (norm != null && norm.getPercentile50() != null) {
|
|
|
item.setScore(BigDecimal.valueOf(norm.getPercentile50()));
|
|
|
@@ -400,47 +436,47 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
List<HealthDiseaseRisk> diseaseRisks) {
|
|
|
|
|
|
Map<String, List<Integer>> dimValues = new HashMap<>();
|
|
|
- for (String[] dim : DIMENSIONS) {
|
|
|
- dimValues.put(dim[0], new ArrayList<>());
|
|
|
+ for (DimensionNode dim : bodyDimensions()) {
|
|
|
+ dimValues.put(dim.getCode(), new ArrayList<>());
|
|
|
}
|
|
|
|
|
|
if (report.getGutHealthScore() != null) {
|
|
|
- dimValues.get("immunity").add(report.getGutHealthScore());
|
|
|
- dimValues.get("gut").add(report.getGutHealthScore());
|
|
|
+ dimValues.get("body.immunity").add(report.getGutHealthScore());
|
|
|
+ dimValues.get("body.gut").add(report.getGutHealthScore());
|
|
|
}
|
|
|
if (report.getChronicDiseaseScore() != null) {
|
|
|
int inverted = 100 - report.getChronicDiseaseScore();
|
|
|
- dimValues.get("growth").add(inverted);
|
|
|
- dimValues.get("nutrition").add(inverted);
|
|
|
+ dimValues.get("body.growth").add(inverted);
|
|
|
+ dimValues.get("body.nutrition").add(inverted);
|
|
|
}
|
|
|
if (report.getNutritionScore() != null) {
|
|
|
- dimValues.get("nutrition").add(report.getNutritionScore());
|
|
|
+ dimValues.get("body.nutrition").add(report.getNutritionScore());
|
|
|
}
|
|
|
if (report.getGutBalanceScore() != null) {
|
|
|
- dimValues.get("gut").add(report.getGutBalanceScore());
|
|
|
+ dimValues.get("body.gut").add(report.getGutBalanceScore());
|
|
|
}
|
|
|
if (report.getGutDiversityScore() != null) {
|
|
|
- dimValues.get("immunity").add(report.getGutDiversityScore());
|
|
|
- dimValues.get("gut").add(report.getGutDiversityScore());
|
|
|
+ dimValues.get("body.immunity").add(report.getGutDiversityScore());
|
|
|
+ dimValues.get("body.gut").add(report.getGutDiversityScore());
|
|
|
}
|
|
|
if (report.getBeneficialBacteriaScore() != null) {
|
|
|
- dimValues.get("immunity").add(report.getBeneficialBacteriaScore());
|
|
|
- dimValues.get("gut").add(report.getBeneficialBacteriaScore());
|
|
|
+ dimValues.get("body.immunity").add(report.getBeneficialBacteriaScore());
|
|
|
+ dimValues.get("body.gut").add(report.getBeneficialBacteriaScore());
|
|
|
}
|
|
|
if (report.getHarmfulBacteriaScore() != null) {
|
|
|
int inverted = 100 - report.getHarmfulBacteriaScore();
|
|
|
- dimValues.get("immunity").add(inverted);
|
|
|
- dimValues.get("gut").add(inverted);
|
|
|
+ dimValues.get("body.immunity").add(inverted);
|
|
|
+ dimValues.get("body.gut").add(inverted);
|
|
|
}
|
|
|
if (report.getCoreSpeciesScore() != null) {
|
|
|
- dimValues.get("gut").add(report.getCoreSpeciesScore());
|
|
|
+ dimValues.get("body.gut").add(report.getCoreSpeciesScore());
|
|
|
}
|
|
|
if (report.getGutAge() != null) {
|
|
|
try {
|
|
|
int gutAgeYears = Integer.parseInt(report.getGutAge().replaceAll("[^0-9]", ""));
|
|
|
int gutAgeScore = Math.max(0, Math.min(100, 100 - gutAgeYears * 2));
|
|
|
- dimValues.get("sleep").add(gutAgeScore);
|
|
|
- dimValues.get("gut").add(gutAgeScore);
|
|
|
+ dimValues.get("body.sleep").add(gutAgeScore);
|
|
|
+ dimValues.get("body.gut").add(gutAgeScore);
|
|
|
} catch (NumberFormatException ignored) {}
|
|
|
}
|
|
|
|
|
|
@@ -452,15 +488,15 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
if (val < 0) continue;
|
|
|
|
|
|
if (cat.contains("神经递质")) {
|
|
|
- dimValues.get("sleep").add(val);
|
|
|
+ dimValues.get("body.sleep").add(val);
|
|
|
} else if ("短链脂肪酸".equals(cat)) {
|
|
|
- dimValues.get("nutrition").add(val);
|
|
|
- dimValues.get("gut").add(val);
|
|
|
+ dimValues.get("body.nutrition").add(val);
|
|
|
+ dimValues.get("body.gut").add(val);
|
|
|
} else if ("肠道屏障".equals(cat)) {
|
|
|
- dimValues.get("immunity").add(val);
|
|
|
- dimValues.get("gut").add(val);
|
|
|
+ dimValues.get("body.immunity").add(val);
|
|
|
+ dimValues.get("body.gut").add(val);
|
|
|
} else if (cat.contains("维生素") || cat.contains("矿物质") || cat.contains("微量")) {
|
|
|
- dimValues.get("nutrition").add(val);
|
|
|
+ dimValues.get("body.nutrition").add(val);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -476,21 +512,21 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
int invertedScore = 100 - Math.min(riskVal, 100);
|
|
|
String name = risk.getDiseaseName();
|
|
|
if (name != null && (name.contains("代谢") || name.contains("肥胖") || name.contains("超重"))) {
|
|
|
- dimValues.get("growth").add(invertedScore);
|
|
|
- dimValues.get("nutrition").add(invertedScore);
|
|
|
+ dimValues.get("body.growth").add(invertedScore);
|
|
|
+ dimValues.get("body.nutrition").add(invertedScore);
|
|
|
}
|
|
|
if (name != null && (name.contains("感染") || name.contains("炎症"))) {
|
|
|
- dimValues.get("immunity").add(invertedScore);
|
|
|
+ dimValues.get("body.immunity").add(invertedScore);
|
|
|
}
|
|
|
- dimValues.get("gut").add(invertedScore);
|
|
|
+ dimValues.get("body.gut").add(invertedScore);
|
|
|
}
|
|
|
|
|
|
Map<String, Integer> result = new HashMap<>();
|
|
|
- for (String[] dim : DIMENSIONS) {
|
|
|
- List<Integer> vals = dimValues.get(dim[0]);
|
|
|
+ for (DimensionNode dim : bodyDimensions()) {
|
|
|
+ List<Integer> vals = dimValues.get(dim.getCode());
|
|
|
if (vals != null && !vals.isEmpty()) {
|
|
|
int avg = (int) vals.stream().mapToInt(v -> v).average().orElse(0);
|
|
|
- result.put(dim[0], Math.min(100, Math.max(0, avg)));
|
|
|
+ result.put(dim.getCode(), Math.min(100, Math.max(0, avg)));
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
@@ -507,8 +543,21 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private String resolveNormLegacy(String dimension) {
|
|
|
+ if (dimension == null || dimension.isEmpty()) {
|
|
|
+ return dimension;
|
|
|
+ }
|
|
|
+ List<String> legacyCodes = dimensionCodeMapper.resolveLegacy(dimension);
|
|
|
+ if (legacyCodes != null && !legacyCodes.isEmpty()) {
|
|
|
+ return legacyCodes.get(0);
|
|
|
+ }
|
|
|
+ return dimension;
|
|
|
+ }
|
|
|
+
|
|
|
private Integer calcPercentile(String dimension, int score) {
|
|
|
- HealthNormReference norm = normMapper.selectNorm(dimension, "all", 120);
|
|
|
+ // 统一维度体系:常模表按 legacy 维度码(growth/...)建索引,统一 code(body.growth)需先回解析
|
|
|
+ String normDimension = resolveNormLegacy(dimension);
|
|
|
+ HealthNormReference norm = normMapper.selectNorm(normDimension, "all", 120);
|
|
|
if (norm == null) return null;
|
|
|
if (norm.getPercentile95() != null && score >= norm.getPercentile95()) return 95;
|
|
|
if (norm.getPercentile85() != null && score >= norm.getPercentile85()) return 85;
|
|
|
@@ -551,8 +600,8 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
|
|
|
return norm.getPercentile50();
|
|
|
}
|
|
|
} else {
|
|
|
- // 身体/健康维度走 health_norm_reference 表
|
|
|
- HealthNormReference norm = normMapper.selectNorm(dimension, gender, (int) ageMonths);
|
|
|
+ // 身体/健康维度走 health_norm_reference 表(常模表按 legacy 码建索引,统一 code 需回解析)
|
|
|
+ HealthNormReference norm = normMapper.selectNorm(resolveNormLegacy(dimension), gender, (int) ageMonths);
|
|
|
if (norm != null) {
|
|
|
return norm.getPercentile50();
|
|
|
}
|