Explorar o código

量表计算公式

yuchengwei hai 4 meses
pai
achega
f22c3d2868

+ 6 - 0
pom.xml

@@ -223,6 +223,12 @@
             <version>1.6.1</version>
         </dependency>
 
+        <dependency>
+            <groupId>net.objecthunter</groupId>
+            <artifactId>exp4j</artifactId>
+            <version>0.4.8</version>
+        </dependency>
+
     </dependencies>
 
     <!-- 私有仓库 -->

+ 1 - 0
src/main/java/com/diagbot/config/ResourceServerConfigurer.java

@@ -102,6 +102,7 @@ public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
                 .antMatchers("/kl/conceptInfo/staticKnowledgeIndexWithoutInfo").permitAll()
                 .antMatchers("/kl/conceptInfo/classicCaseIndexWithoutInfo").permitAll()
                 .antMatchers("/kl/conceptInfo/getStaticKnowledge").permitAll()
+                .antMatchers("/kl/conceptInfo/getCalculatorFormulaResult").permitAll()
                 .antMatchers("/kl/conceptInfo/getStaticKnowledgeForHIS").permitAll()
                 .antMatchers("/kl/conceptInfo/isExistForHIS").permitAll()
                 .antMatchers("/kl/conceptInfo/getPage").permitAll()

+ 1 - 0
src/main/java/com/diagbot/config/security/UrlAccessDecisionManager.java

@@ -144,6 +144,7 @@ public class UrlAccessDecisionManager implements AccessDecisionManager {
                 || matchers("/kl/conceptInfo/staticKnowledgeIndexWithoutInfo", request)
                 || matchers("/kl/conceptInfo/classicCaseIndexWithoutInfo", request)
                 || matchers("/kl/conceptInfo/getStaticKnowledge", request)
+                || matchers("/kl/conceptInfo/getCalculatorFormulaResult", request)
                 || matchers("/kl/conceptInfo/getClassicCaseInfo", request)
                 || matchers("/kl/conceptInfo/getStaticKnowledgeForHIS", request)
                 || matchers("/kl/conceptInfo/isExistForHIS", request)

+ 20 - 0
src/main/java/com/diagbot/dto/CalculatorFormulaScoreDTO.java

@@ -0,0 +1,20 @@
+package com.diagbot.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 计算结果分数对象
+ */
+@Getter
+@Setter
+public class CalculatorFormulaScoreDTO {
+    /**
+     * 得分
+     */
+    private String score;
+    /**
+     * 结果
+     */
+    private String result;
+}

+ 79 - 0
src/main/java/com/diagbot/dto/KlCalculatorFormulaDTO.java

@@ -0,0 +1,79 @@
+package com.diagbot.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+public class KlCalculatorFormulaDTO {
+    /**
+     * 术语概念id
+     */
+    private Long conceptId;
+
+    /**
+     * 标题
+     */
+    private String title;
+
+    /**
+     * 备注
+     */
+    private String notes;
+
+    private List<ContentTableData> contentTableData;
+
+    private List<ScoreTableData> scoreTableData;
+
+
+
+
+    @Data
+    public static class ContentTableData{
+        private String name;
+        private String unit;
+    }
+
+    @Data
+    public static class ScoreTableData{
+        private Long groupId;
+
+        private Long conditionId;
+
+        private String conditionName;
+
+        private String conditionType;
+
+        private ConditionContent conditionContent;
+
+        private String formula;
+
+        private List<ScoreRange> scoreRange;
+    }
+
+    @Data
+    public static class ConditionContent{
+        private String textType;
+        private ValueType valueType;
+
+    }
+
+    @Data
+    public static class ValueType{
+        private String maxValueName;
+        private BigDecimal maxValue;
+        private String maxValueUnit;
+
+        private String minValueName;
+        private BigDecimal minValue;
+        private String minValueUnit;
+    }
+
+    @Data
+    public static class ScoreRange{
+        private BigDecimal greaterEqual;
+        private BigDecimal lessEqual;
+        private String result;
+    }
+}

+ 5 - 0
src/main/java/com/diagbot/dto/KlConceptStaticDTO.java

@@ -60,4 +60,9 @@ public class KlConceptStaticDTO {
      * 明细
      */
     List<KlConceptDetailDTO> details;
+
+    /**
+     * 量表计算公式
+     */
+    private KlCalculatorFormulaDTO calculatorFormula;
 }

+ 2 - 0
src/main/java/com/diagbot/dto/StaticKnowledgeDTO.java

@@ -47,4 +47,6 @@ public class StaticKnowledgeDTO {
      * 量表结构
      */
     private ConceptScaleDTO scale;
+
+    private KlCalculatorFormulaDTO calculatorFormula;
 }

+ 265 - 8
src/main/java/com/diagbot/facade/KlConceptStaticFacade.java

@@ -12,20 +12,21 @@ import com.diagbot.enums.IsDeleteEnum;
 import com.diagbot.exception.CommonErrorCode;
 import com.diagbot.exception.CommonException;
 import com.diagbot.service.MappingConfigService;
-import com.diagbot.util.BeanUtil;
-import com.diagbot.util.ListUtil;
-import com.diagbot.util.RespDTOUtil;
-import com.diagbot.util.UserUtils;
+import com.diagbot.util.*;
 import com.diagbot.vo.*;
 import com.google.common.collect.Lists;
 import lombok.val;
+import net.objecthunter.exp4j.ExpressionBuilder;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -150,6 +151,262 @@ public class KlConceptStaticFacade {
         }
     }
 
+    /**
+     * 获取量表计算公式结果
+     */
+    public CalculatorFormulaScoreDTO getCalculatorFormulaResult(CalculatorFormulaVO calculatorFormulaVO) {
+
+        // 1. 获取计算公式配置
+        KlCalculatorFormulaDTO formulaConfig = getFormulaConfig(calculatorFormulaVO.getConceptId());
+
+        // 2. 参数预处理
+        Map<String, Object> inputParams = Optional.ofNullable(calculatorFormulaVO.getParams())
+                .orElseGet(HashMap::new);
+
+        // 3. 匹配计算公式
+        String matchedFormula = matchFormula(formulaConfig, inputParams);
+
+        // 4. 转换计算参数
+        Map<String, Double> calcParams = convertParams(inputParams);
+
+        // 5. 执行计算并返回结果
+        String score = calculateResult(matchedFormula, calcParams);
+
+        // 6. 比较得分,并返回响应的结果
+        String result = getResult(formulaConfig, matchedFormula, score);
+
+        CalculatorFormulaScoreDTO scoreDTO = new CalculatorFormulaScoreDTO();
+        scoreDTO.setScore(score);
+        scoreDTO.setResult(result);
+        return scoreDTO;
+    }
+
+    private String getResult(KlCalculatorFormulaDTO formulaConfig, String matchedFormula, String score) {
+        String result = null;
+        KlCalculatorFormulaDTO.ScoreTableData scoreTableData = formulaConfig.getScoreTableData().stream().filter(e -> e.getFormula().equalsIgnoreCase(matchedFormula)).findFirst().get();
+        List<KlCalculatorFormulaDTO.ScoreRange> scoreRanges = scoreTableData.getScoreRange();
+        // 6. 匹配得分范围
+        double scoreValue;
+        try {
+            scoreValue = Double.parseDouble(score);
+        } catch (NumberFormatException e) {
+            throw new CommonException(CommonErrorCode.SERVER_IS_ERROR,
+                    "得分格式错误: " + score);
+        }
+
+        for (KlCalculatorFormulaDTO.ScoreRange range : scoreRanges) {
+            BigDecimal ge = range.getGreaterEqual();  // 下限
+            BigDecimal le = range.getLessEqual();     // 上限
+
+            // 边界条件检查
+            boolean lowerBoundMet = ge == null || scoreValue >= ge.doubleValue();
+            boolean upperBoundMet = le == null || scoreValue <= le.doubleValue();
+
+            // 特殊场景处理
+            if (ge != null && le != null) {
+                // 当配置的上下限反序时自动校正
+                if (ge.doubleValue() > le.doubleValue()) {
+                    lowerBoundMet = scoreValue >= le.doubleValue();
+                    upperBoundMet = scoreValue <= ge.doubleValue();
+                }
+            }
+
+            // 匹配条件判断
+            if (lowerBoundMet && upperBoundMet) {
+                result = range.getResult();
+                break; // 找到第一个匹配项即返回
+            }
+        }
+
+        // 后置校验
+        if (result == null) {
+            throw new CommonException(CommonErrorCode.SERVER_IS_ERROR,
+                    "得分" + score + "未匹配到有效范围");
+        }
+        return result;
+    }
+
+    /**
+     * 获取计算公式配置
+     */
+    private KlCalculatorFormulaDTO getFormulaConfig(Long conceptId) {
+        return Optional.ofNullable(cdssCoreClient.getRecordById(new IdVO(conceptId)))
+                .filter(RespDTOUtil::respIsOK)
+                .map(resp -> resp.data.getCalculatorFormula())
+                .orElseThrow(() -> new CommonException(CommonErrorCode.SERVER_IS_ERROR, "计算公式配置不存在"));
+    }
+
+    /**
+     * 匹配计算公式
+     */
+    private String matchFormula(KlCalculatorFormulaDTO formulaConfig, Map<String, Object> params) {
+        return formulaConfig.getScoreTableData().stream()
+                .collect(Collectors.groupingBy(KlCalculatorFormulaDTO.ScoreTableData::getGroupId))
+                .values().stream()
+                .filter(group -> checkGroupConditions(group, params))
+                .findFirst()
+                .map(group -> group.get(0).getFormula())
+                .orElseThrow(() -> new CommonException(CommonErrorCode.SERVER_IS_ERROR, "无匹配计算公式"));
+    }
+
+    /**
+     * 检查组条件
+     */
+    private boolean checkGroupConditions(List<KlCalculatorFormulaDTO.ScoreTableData> group,
+                                         Map<String, Object> params) {
+        return group.stream()
+                .allMatch(tableData -> checkSingleCondition(tableData, params));
+    }
+
+
+    /**
+     * 检查单个条件
+     */
+    private boolean checkSingleCondition(KlCalculatorFormulaDTO.ScoreTableData tableData,
+                                         Map<String, Object> params) {
+        String conditionName = tableData.getConditionName();
+        Object inputValue = params.get(conditionName);
+
+        if (inputValue == null) {
+            throw new CommonException(CommonErrorCode.SERVER_IS_ERROR, "缺少必要参数: " + conditionName);
+        }
+
+        return Optional.ofNullable(tableData.getConditionType())
+                .map(type -> {
+                    if ("数值类型".equals(type)) {
+                        return checkNumericCondition(inputValue, tableData.getConditionContent().getValueType());
+                    } else if ("文本类型".equals(type)) {
+                        return checkTextCondition(inputValue, tableData.getConditionContent().getTextType());
+                    }
+                    throw new CommonException(CommonErrorCode.SERVER_IS_ERROR, "未知条件类型: " + type);
+                })
+                .orElseThrow(() -> new CommonException(CommonErrorCode.SERVER_IS_ERROR, "未指定条件类型"));
+    }
+
+    /**
+     * 数值条件校验(使用JDK8的Function接口)
+     */
+    private boolean checkNumericCondition(Object inputValue, KlCalculatorFormulaDTO.ValueType condition) {
+        // 获取最小值校验函数(已绑定 threshold)
+        Function<Double, Boolean> minCheck = getNumericCheckFunction(
+                condition.getMinValueName(),
+                condition.getMinValue() // 直接传递阈值
+        );
+
+        // 获取最大值校验函数(已绑定 threshold)
+        Function<Double, Boolean> maxCheck = getNumericCheckFunction(
+                condition.getMaxValueName(),
+                condition.getMaxValue() // 直接传递阈值
+        );
+
+        try {
+            double value = Double.parseDouble(inputValue.toString());
+            return minCheck.apply(value) && maxCheck.apply(value);
+        } catch (NumberFormatException e) {
+            throw new CommonException(CommonErrorCode.SERVER_IS_ERROR, "参数类型错误: " + inputValue);
+        }
+    }
+
+    /**
+     * 生成数值校验函数
+     */
+    private Function<Double, Boolean> getNumericCheckFunction(String operator, BigDecimal threshold) {
+        if (operator == null || threshold == null) {
+            return value -> true; // 默认通过校验
+        }
+
+        double thresholdValue = threshold.doubleValue();
+        switch (operator) {
+            case ">":  return value -> value > thresholdValue;
+            case ">=": return value -> value >= thresholdValue;
+            case "<":  return value -> value < thresholdValue;
+            case "<=": return value -> value <= thresholdValue;
+            default:
+                throw new CommonException(CommonErrorCode.SERVER_IS_ERROR, "无效运算符: " + operator);
+        }
+    }
+
+    /**
+     * 文本条件校验
+     */
+    private boolean checkTextCondition(Object inputValue, String expectedText) {
+        return Optional.ofNullable(expectedText)
+                .map(t -> t.equals(inputValue.toString()))
+                .orElseThrow(() -> new CommonException(CommonErrorCode.SERVER_IS_ERROR, "文本条件未配置"));
+    }
+
+    /**
+     * 参数转换(使用Stream API)
+     */
+    private Map<String, Double> convertParams(Map<String, Object> params) {
+        return params.entrySet().stream()
+                .map(entry -> {
+                    Optional<Double> valueOpt = tryParseDouble(entry.getValue());
+                    return valueOpt.map(v -> new AbstractMap.SimpleEntry<>(entry.getKey(), v));
+                })
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .collect(Collectors.toMap(
+                        Map.Entry::getKey,
+                        Map.Entry::getValue,
+                        (existing, replacement) -> existing // 处理重复key的情况
+                ));
+    }
+
+    /**
+     * 安全转换数值方法(返回Optional)
+     */
+    private Optional<Double> tryParseDouble(Object value) {
+        if (value == null) return Optional.empty();
+
+        try {
+            return Optional.of(Double.parseDouble(value.toString()));
+        } catch (NumberFormatException e) {
+            return Optional.empty();
+        }
+    }
+
+    /**
+     * 执行计算
+     */
+    private String calculateResult(String formula, Map<String, Double> params) {
+        try {
+            double result = new ExpressionBuilder(formula)
+                    .variables(params.keySet())
+                    .build()
+                    .setVariables(params)
+                    .evaluate();
+            return formatDouble(result);
+        } catch (IllegalArgumentException | ArithmeticException e) {
+            throw new CommonException(CommonErrorCode.SERVER_IS_ERROR,
+                    "公式计算失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 结果格式化
+     */
+    private String formatDouble(double value) {
+        return BigDecimal.valueOf(value)
+                .setScale(6, RoundingMode.HALF_UP)
+                .stripTrailingZeros()
+                .toPlainString();
+    }
+
+    /**
+     * 安全数值转换
+     */
+    private Double convertToDouble(String paramName, Object value) {
+        try {
+            return Double.parseDouble(value.toString());
+        } catch (NumberFormatException e) {
+            throw new CommonException(CommonErrorCode.SERVER_IS_ERROR,
+                    "参数[" + paramName + "]转换失败: " + value);
+        }
+    }
+
+
+
     /**
      * 医院端获取静态知识(对接)
      *

+ 39 - 0
src/main/java/com/diagbot/util/CalculatorUtil.java

@@ -0,0 +1,39 @@
+package com.diagbot.util;
+
+import net.objecthunter.exp4j.Expression;
+import net.objecthunter.exp4j.ExpressionBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CalculatorUtil {
+
+    public static double evaluate(String formula, Map<String, Double> variables) {
+        // 构建表达式
+        ExpressionBuilder builder = new ExpressionBuilder(formula);
+
+        // 添加变量
+        variables.forEach((name, value) -> builder.variable(name));
+
+        // 编译表达式
+        Expression expr = builder.build();
+
+        // 设置变量值
+        variables.forEach(expr::setVariable);
+
+        // 计算结果
+        return expr.evaluate();
+    }
+
+    public static void main(String[] args) {
+        Map<String, Double> vars = new HashMap<>();
+        vars.put("x", 2.0);
+        vars.put("y", 3.0);
+
+        String formula = "x^2.5 + 3*y - 5/(z+1)";
+        vars.put("z", 1.0); // 注意变量必须全部定义
+
+        double result = evaluate(formula, vars);
+        System.out.println(result); // 输出:2² + 3×3 - 5/(1+1) = 4+9-2.5=10.5
+    }
+}

+ 24 - 0
src/main/java/com/diagbot/vo/CalculatorFormulaVO.java

@@ -0,0 +1,24 @@
+package com.diagbot.vo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 计算公式入参
+ */
+@Getter
+@Setter
+public class CalculatorFormulaVO {
+    /**
+     * 术语概念id
+     */
+    private Long conceptId;
+    /**
+     * 参数
+     */
+    private Map<String,Object> params;
+}

+ 6 - 4
src/main/java/com/diagbot/vo/IdVO.java

@@ -1,7 +1,8 @@
 package com.diagbot.vo;
 
-import lombok.Getter;
-import lombok.Setter;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 
 import javax.validation.constraints.NotNull;
 
@@ -10,8 +11,9 @@ import javax.validation.constraints.NotNull;
  * @Author:zhaops
  * @time: 2020/7/30 13:26
  */
-@Getter
-@Setter
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
 public class IdVO {
     @NotNull(message = "请输入id")
     private Long id;

+ 8 - 0
src/main/java/com/diagbot/web/ConceptInfoController.java

@@ -85,6 +85,14 @@ public class ConceptInfoController {
         return RespDTO.onSuc(data);
     }
 
+    @ApiOperation(value = "页面获取量表计算公式结果")
+    @PostMapping("/getCalculatorFormulaResult")
+    @SysLogger("getCalculatorFormulaResult")
+    public RespDTO<CalculatorFormulaScoreDTO> getCalculatorFormulaResult(@Valid @RequestBody CalculatorFormulaVO calculatorFormulaVO) {
+        CalculatorFormulaScoreDTO data = klConceptStaticFacade.getCalculatorFormulaResult(calculatorFormulaVO);
+        return RespDTO.onSuc(data);
+    }
+
     @ApiOperation(value = "对接获取静态知识[zhaops]",
             notes = "type: 类型:1-诊断、2-药品、3-检验套餐、4-检验细项、5-检查、7-手术和操作、8-量表、9-护理、10-政策法规 <br>" +
                     "contentTypes: 内容类型(多选):1-静态信息、2-注意事项、3-临床路径、4-治疗方案、5-诊疗指南 <br>" +