Procházet zdrojové kódy

字段标准化修改-台州邵逸夫入北仑

rengb před 4 roky
rodič
revize
ab10700c60
34 změnil soubory, kde provedl 4396 přidání a 0 odebrání
  1. 125 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03047.java
  2. 52 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03048.java
  3. 45 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03049.java
  4. 95 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03050.java
  5. 102 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03051.java
  6. 47 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03053.java
  7. 47 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03055.java
  8. 47 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03056.java
  9. 55 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03057.java
  10. 47 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03058.java
  11. 123 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03059.java
  12. 52 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03060.java
  13. 47 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03061.java
  14. 35 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/clinicalblood/CLI03036.java
  15. 35 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/clinicalblood/CLI03037.java
  16. 64 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/crisisvaluereport/CRI03062.java
  17. 36 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstcourserecord/FIRC0090.java
  18. 45 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP02842.java
  19. 47 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP02844.java
  20. 58 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP03038.java
  21. 51 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP03045.java
  22. 84 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP03046.java
  23. 123 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/preoperativediscussion/PRE03064.java
  24. 72 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03065.java
  25. 300 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03069.java
  26. 200 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03070.java
  27. 143 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03071.java
  28. 300 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03072.java
  29. 296 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03074.java
  30. 298 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03075.java
  31. 329 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03076.java
  32. 502 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03077.java
  33. 428 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03079.java
  34. 66 0
      kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR0588.java

+ 125 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03047.java

@@ -0,0 +1,125 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.entity.Diag;
+import com.lantone.qc.pub.model.label.DiagLabel;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @ClassName : BEH03047
+ * @Description : 初步诊断诊断依据不充分(暂只做高血压)
+ * @Author : 胡敬
+ * @Date: 2020-07-27 14:54
+ */
+@Component
+public class BEH03047 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+        DiagLabel initialDiagLabel = inputInfo.getBeHospitalizedDoc().getInitialDiagLabel();
+        if (initialDiagLabel == null || StringUtil.isBlank(initialDiagLabel.getText())) {
+            return;
+        }
+
+        List<Diag> initDiags = initialDiagLabel.getDiags();
+        List<String> initDiagFilter = initDiags
+                .stream()
+                .map(Diag::getHospitalDiagName)
+                .filter(x -> StringUtil.isNotBlank(x) && x.contains("高血压"))
+                .collect(Collectors.toList());
+
+        if (initDiagFilter.size() > 0) {
+            String regex = "\\d+/\\d+(mmHg)?";
+            List<Integer> highBlood = new ArrayList<>();
+            List<Integer> lowBlood = new ArrayList<>();
+
+            Map<String, String> beStructMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+            //现病史取血压值
+            String presentText = beStructMap.get("现病史");
+            String bloodPressure = regularValue(presentText, regex);
+            saveBloodValue(highBlood, lowBlood, bloodPressure);
+
+            //生命体征取血压
+            String bloodValue = beStructMap.get("血压");
+            saveBloodValue(highBlood, lowBlood, bloodValue);
+
+            //专科检查
+            String special = beStructMap.get("专科体格检查");
+            bloodPressure = regularValue(special, regex);
+            saveBloodValue(highBlood, lowBlood, bloodPressure);
+
+            highBlood.sort(Comparator.reverseOrder());
+            lowBlood.sort(Comparator.reverseOrder());
+
+            Integer high = highBlood.get(0);//收缩压
+            Integer low = lowBlood.get(0);//舒张压
+
+            String diag = initDiagFilter.get(0);//高血压病2级
+            if (diag.contains("1")) {
+                if (high < 140 && low < 90) {
+                    status.set("-1");
+                    info.set(diag + ":" + "血压未达到1级标准");
+                }
+            } else if (diag.contains("2")) {
+                if (high < 160 && low < 100) {
+                    status.set("-1");
+                    info.set(diag + ":" + "血压未达到2级标准");
+                }
+            } else if (diag.contains("3")) {
+                if (high < 180 && low < 110) {
+                    status.set("-1");
+                    info.set(diag + ":" + "血压未达到3级标准");
+                }
+            }
+
+        }
+
+    }
+
+    /**
+     * 从血压值中取收缩压和舒张压(170/97mmHg)
+     *
+     * @param highBlood
+     * @param lowBlood
+     * @param bloodPressure
+     */
+    private void saveBloodValue(List<Integer> highBlood, List<Integer> lowBlood, String bloodPressure) {
+        if (StringUtil.isNotBlank(bloodPressure)) {
+            if (bloodPressure.contains("mmHg")) {
+                bloodPressure = bloodPressure.substring(0, bloodPressure.lastIndexOf("mmHg"));
+            }
+            String[] bloodValue = bloodPressure.split("/");
+            if (bloodValue.length == 2) {
+                highBlood.add(Integer.parseInt(bloodValue[0]));
+                lowBlood.add(Integer.parseInt(bloodValue[1]));
+            }
+        }
+    }
+
+    private String regularValue(String input, String regex) {
+        String value = "";
+        if (StringUtil.isNotBlank(input)) {
+            Pattern p = Pattern.compile(regex);
+            Matcher m = p.matcher(input);
+            if (m.find()) {
+                value = m.group(0);
+            }
+        }
+        return value;
+    }
+
+}

+ 52 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03048.java

@@ -0,0 +1,52 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.google.common.collect.Lists;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.BeHospitalizedDoc;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : BEH03048
+ * @Description : 入院记录体格检查与专科检查不一致
+ * @Author : 胡敬
+ * @Date: 2020-07-28 09:52
+ */
+@Component
+public class BEH03048 extends QCCatalogue {
+    private List<String> vitals = Lists.newArrayList("Murphy征", "麦氏点压痛");
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+
+        BeHospitalizedDoc beHospitalizedDoc = inputInfo.getBeHospitalizedDoc();
+        Map<String, String> beHospitalStructureMap = beHospitalizedDoc.getStructureMap();
+        String vitalLabelSpecial = beHospitalStructureMap.get("专科体格检查");
+        if (StringUtil.isBlank(vitalLabelSpecial)) {
+            return;
+        }
+        vitalLabelSpecial = vitalLabelSpecial.replace("墨菲氏征", "Murphy征");
+
+        for (String vital : vitals) {
+            String v = beHospitalStructureMap.get(vital);
+            if (StringUtil.isNotBlank(v)) {
+                v = v.replace("无", "阴性");
+                v = vital + v;
+                if (vitalLabelSpecial.contains(vital) && !vitalLabelSpecial.contains(v)) {
+                    status.set("-1");
+                    info.set(vital);
+                    return;
+                }
+            }
+
+        }
+    }
+}

+ 45 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03049.java

@@ -0,0 +1,45 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @ClassName : BEH03049
+ * @Description : 入院记录中脉搏与心率不一致
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class BEH03049 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null || inputInfo.getBeHospitalizedDoc().getStructureMap() == null) {
+            return;
+        }
+        Map<String, String> structureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String initialDiag = structureMap.get("初步诊断");
+        if (StringUtil.isBlank(initialDiag)) {
+            return;
+        }
+        String souval = structureMap.get("脉搏");
+        String tarval = structureMap.get("心率");
+        if (StringUtil.isNotBlank(souval)
+                && StringUtil.isNotBlank(tarval)
+                && initialDiag.indexOf("房颤") == -1
+                && initialDiag.indexOf("纤颤") == -1
+                && initialDiag.indexOf("房扑") == -1) {
+            souval = souval.replaceAll("[^0-9]*", "");
+            tarval = tarval.replaceAll("[^0-9]*", "");
+            if (StringUtil.isNotBlank(souval) && StringUtil.isNotBlank(tarval) && !souval.equals(tarval)) {
+                status.set("-1");
+            }
+        }
+    }
+
+}

+ 95 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03050.java

@@ -0,0 +1,95 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.google.common.collect.Lists;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.BeHospitalizedDoc;
+import com.lantone.qc.pub.model.label.ChiefLabel;
+import com.lantone.qc.pub.model.label.PresentLabel;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @ClassName : BEH03050
+ * @Description : 主诉与现病史发病部位不一致
+ * @Author : 胡敬
+ * @Date: 2020-07-28 09:52
+ */
+@Component
+public class BEH03050 extends QCCatalogue {
+    private List<String> positions = Lists.newArrayList("左上", "左下", "右上", "右下", "上", "下", "左", "右");
+    private List<String> containList = Arrays.asList("检查", "术后", "药物", "发现", "误服", "确诊", "经", "异常", "诊断"
+            , "示", "超", "伤", "术", "复查", "体检", "血透", "血液透析", "诱因下");
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+
+        BeHospitalizedDoc beHospitalizedDoc = inputInfo.getBeHospitalizedDoc();
+        ChiefLabel chiefLabel = beHospitalizedDoc.getChiefLabel();
+        PresentLabel presentLabel = beHospitalizedDoc.getPresentLabel();
+        if (chiefLabel == null || presentLabel == null) {
+            return;
+        }
+
+        List<String> chiefPosition = new ArrayList<>();
+        List<String> presentPosition = new ArrayList<>();
+
+
+        String chiefText = chiefLabel.getText();
+        String presentText = presentLabel.getText();
+        if (StringUtil.isBlank(chiefText) || StringUtil.isBlank(presentText)) {
+            return;
+        }
+        //主诉如果包括相关词,不报错
+        for (String word : containList) {
+            if (chiefText.contains(word)) {
+                return;
+            }
+        }
+
+        //取主诉的方位词
+        for (String position : positions) {
+            if (chiefText.contains(position)) {
+                chiefPosition.add(position);
+                chiefText = chiefText.replace(position, "");
+            }
+        }
+
+        //取现病史的方位词,只取现病史前20个字
+        if (presentText.length() > 20) {
+            presentText = presentText.substring(0, 20);
+        }
+        for (String position : positions) {
+            if (presentText.contains(position)) {
+                presentPosition.add(position);
+                presentText = presentText.replace(position, "");
+            }
+        }
+
+        if (chiefPosition.size() == 0 || presentPosition.size() == 0) {
+            return;
+        }
+
+        int matchNum = 0;
+        for (String chiefP : chiefPosition) {
+            for (String presentP : presentPosition) {
+                if (chiefP.equals(presentP)) {
+                    matchNum++;
+                    break;
+                }
+            }
+        }
+
+        if (matchNum == 0) {
+            status.set("-1");
+        }
+    }
+}

+ 102 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03051.java

@@ -0,0 +1,102 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.google.common.collect.ImmutableMap;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.BeHospitalizedDoc;
+import com.lantone.qc.pub.model.entity.Diag;
+import com.lantone.qc.pub.model.label.DiagLabel;
+import com.lantone.qc.pub.model.label.VitalLabelSpecial;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : BEH03051
+ * @Description : 与诊断相关的主要体征未描述
+ * @Author : 胡敬
+ * @Date: 2020-07-28 09:52
+ */
+@Component
+public class BEH03051 extends QCCatalogue {
+    /**
+     * 暂时先整理出以下诊断的体征
+     * 用|分隔。
+     * |前面的是且的关系,每个都要有
+     * |后面的是或的关系,有一个就可以
+     */
+
+    Map<String, String> diagVital = ImmutableMap.<String, String>builder()
+            .put("急性阑尾炎", "右下腹:专科查体无右下腹描述/反跳痛:专科查体无反跳痛描述|麦氏点:专科查体无麦氏点描述/麦氏点压痛/阑尾点")
+            .put("冠状动脉粥样硬化性心脏病", "心率:专科查体无心率描述/律:专科查体无心律描述|瓣膜:专科查体无各瓣膜区听诊描述/杂音/二尖瓣/三尖瓣/主动脉瓣")
+            .put("高血压病", "血压:专科查体无血压值描述")
+            .put("高血压病1级", "血压:专科查体无血压值描述")
+            .put("高血压病2级", "血压:专科查体无血压值描述")
+            .put("高血压病3级", "血压:专科查体无血压值描述")
+            .put("心房颤动", "心率:专科查体无心率描述/律:专科查体无心律描述|脉搏:专科查体无脉搏描述/心音:专科查体无心音描述")
+            .build();
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+
+        BeHospitalizedDoc beHospitalizedDoc = inputInfo.getBeHospitalizedDoc();
+        DiagLabel initialDiagLabel = beHospitalizedDoc.getInitialDiagLabel();
+        VitalLabelSpecial vitalLabelSpecial = beHospitalizedDoc.getVitalLabelSpecial();
+        if (initialDiagLabel == null || vitalLabelSpecial == null) {
+            return;
+        }
+
+        String vitalSpecialText = vitalLabelSpecial.getText();
+        if (StringUtil.isBlank(vitalSpecialText)) {
+            return;
+        }
+
+        List<Diag> initDiags = initialDiagLabel.getDiags();
+        if (initDiags.size() > 0) {
+            String firstInitDiag = initDiags.get(0).getHospitalDiagName();
+            if (StringUtil.isNotBlank(firstInitDiag)) {
+                if (diagVital.containsKey(firstInitDiag)) {
+                    String vitals = diagVital.get(firstInitDiag);
+                    String[] vitalArr = vitals.split("\\|");
+                    String vitalAnd = vitalArr[0];//体征且的关系,每个必须有
+                    for (String vitalInfo : vitalAnd.split("/")) {
+                        String[] vitalInfoArr = vitalInfo.split(":");
+                        String vital = "";
+                        if (!vitalSpecialText.contains(vitalInfoArr[0])) {
+                            status.set("-1");
+                            if (vitalInfoArr.length > 1) {
+                                vital = ":" + vitalInfoArr[1];
+                            }
+                            info.set(firstInitDiag + vital);
+                            return;
+                        }
+                    }
+                    if (vitalArr.length == 2) {
+                        String vitalOr = vitalArr[1];//体征或的关系,有一个就行
+                        int existNum = 0;
+                        String vital = "";
+                        for (String vitalInfo : vitalOr.split("/")) {
+                            String[] vitalInfoArr = vitalInfo.split(":");
+                            if (vitalSpecialText.contains(vitalInfoArr[0])) {
+                                existNum++;
+                            }
+                            if (vitalInfoArr.length > 1) {
+                                vital = ":" + vitalInfoArr[1];
+                            }
+                        }
+                        if (existNum == 0) {
+                            status.set("-1");
+                            info.set(firstInitDiag + vital);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 47 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03053.java

@@ -0,0 +1,47 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @ClassName : BEH03053
+ * @Description : 入院记录体格检查体温与专科检查体温不一致
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class BEH03053 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null || inputInfo.getBeHospitalizedDoc().getStructureMap() == null) {
+            return;
+        }
+        Map<String, String> structureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String souval = structureMap.get("体温");
+        String tarval = null;
+        String vitalLabelSpecial = structureMap.get("专科体格检查");
+        if (StringUtil.isNotBlank(vitalLabelSpecial)) {
+            Pattern pattern = Pattern.compile("(体温)[^,;。,;体温|脉搏|呼吸|血压|疼痛]+");
+            Matcher matcher = pattern.matcher(vitalLabelSpecial);
+            if (matcher.find()) {
+                pattern = Pattern.compile("[0-9]+[./]*[0-9]*");
+                matcher = pattern.matcher(matcher.group());
+                if (matcher.find()) {
+                    tarval = matcher.group();
+                }
+            }
+        }
+        if (StringUtil.isNotBlank(souval) && StringUtil.isNotBlank(tarval) && !souval.equals(tarval)) {
+            status.set("-1");
+        }
+    }
+
+}

+ 47 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03055.java

@@ -0,0 +1,47 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @ClassName : BEH03055
+ * @Description : 入院记录体格检查脉搏与专科检查脉搏不一致
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class BEH03055 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null || inputInfo.getBeHospitalizedDoc().getStructureMap() == null) {
+            return;
+        }
+        Map<String, String> structureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String souval = structureMap.get("脉搏");
+        String tarval = null;
+        String vitalLabelSpecial = structureMap.get("专科体格检查");
+        if (StringUtil.isNotBlank(vitalLabelSpecial)) {
+            Pattern pattern = Pattern.compile("(脉搏)[^,;。,;体温|脉搏|呼吸|血压|疼痛]+");
+            Matcher matcher = pattern.matcher(vitalLabelSpecial);
+            if (matcher.find()) {
+                pattern = Pattern.compile("[0-9]+[./]*[0-9]*");
+                matcher = pattern.matcher(matcher.group());
+                if (matcher.find()) {
+                    tarval = matcher.group();
+                }
+            }
+        }
+        if (StringUtil.isNotBlank(souval) && StringUtil.isNotBlank(tarval) && !souval.equals(tarval)) {
+            status.set("-1");
+        }
+    }
+
+}

+ 47 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03056.java

@@ -0,0 +1,47 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @ClassName : BEH03056
+ * @Description : 入院记录体格检查呼吸与专科检查呼吸不一致
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class BEH03056 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null || inputInfo.getBeHospitalizedDoc().getStructureMap() == null) {
+            return;
+        }
+        Map<String, String> structureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String souval = structureMap.get("呼吸");
+        String tarval = null;
+        String vitalLabelSpecial = structureMap.get("专科体格检查");
+        if (StringUtil.isNotBlank(vitalLabelSpecial)) {
+            Pattern pattern = Pattern.compile("(呼吸)[^,;。,;体温|脉搏|呼吸|血压|疼痛]+");
+            Matcher matcher = pattern.matcher(vitalLabelSpecial);
+            if (matcher.find()) {
+                pattern = Pattern.compile("[0-9]+[./]*[0-9]*");
+                matcher = pattern.matcher(matcher.group());
+                if (matcher.find()) {
+                    tarval = matcher.group();
+                }
+            }
+        }
+        if (StringUtil.isNotBlank(souval) && StringUtil.isNotBlank(tarval) && !souval.equals(tarval)) {
+            status.set("-1");
+        }
+    }
+
+}

+ 55 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03057.java

@@ -0,0 +1,55 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @ClassName : BEH03057
+ * @Description : 入院记录体格检查血压与专科检查血压不一致
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class BEH03057 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null || inputInfo.getBeHospitalizedDoc().getStructureMap() == null) {
+            return;
+        }
+        Map<String, String> structureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String souval = structureMap.get("血压");
+        if (StringUtil.isBlank(souval)) {
+            return;
+        }
+        Pattern pattern = Pattern.compile("[0-9]+[./]*[0-9]*");
+        Matcher matcher = pattern.matcher(souval);
+        if (matcher.find()) {
+            souval = matcher.group();
+        }
+        String tarval = null;
+        String vitalLabelSpecial = structureMap.get("专科体格检查");
+        if (StringUtil.isNotBlank(vitalLabelSpecial)) {
+            pattern = Pattern.compile("(血压)[^,;。,;体温|脉搏|呼吸|血压|疼痛]+");
+            matcher = pattern.matcher(vitalLabelSpecial);
+            if (matcher.find()) {
+                pattern = Pattern.compile("[0-9]+[./]*[0-9]*");
+                matcher = pattern.matcher(matcher.group());
+                if (matcher.find()) {
+                    tarval = matcher.group();
+                }
+            }
+        }
+        if (StringUtil.isNotBlank(souval) && StringUtil.isNotBlank(tarval) && !souval.equals(tarval)) {
+            status.set("-1");
+        }
+    }
+
+}

+ 47 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03058.java

@@ -0,0 +1,47 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @ClassName : BEH03058
+ * @Description : 入院记录体格检查疼痛评分与专科检查疼痛评分不一致
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class BEH03058 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null || inputInfo.getBeHospitalizedDoc().getStructureMap() == null) {
+            return;
+        }
+        Map<String, String> structureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String souval = structureMap.get("疼痛评分");
+        String tarval = null;
+        String vitalLabelSpecial = structureMap.get("专科体格检查");
+        if (StringUtil.isNotBlank(vitalLabelSpecial)) {
+            Pattern pattern = Pattern.compile("(疼痛)[^,;。,;体温|脉搏|呼吸|血压|疼痛]+");
+            Matcher matcher = pattern.matcher(vitalLabelSpecial);
+            if (matcher.find()) {
+                pattern = Pattern.compile("[0-9]+[./]*[0-9]*");
+                matcher = pattern.matcher(matcher.group());
+                if (matcher.find()) {
+                    tarval = matcher.group();
+                }
+            }
+        }
+        if (StringUtil.isNotBlank(souval) && StringUtil.isNotBlank(tarval) && !souval.equals(tarval)) {
+            status.set("-1");
+        }
+    }
+
+}

+ 123 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03059.java

@@ -0,0 +1,123 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.structure.ai.BeHospitalizedAI;
+import com.lantone.qc.kernel.structure.ai.model.ConflictFinder;
+import com.lantone.qc.kernel.structure.ai.model.EntityBlock;
+import com.lantone.qc.kernel.structure.ai.model.Lemma;
+import com.lantone.qc.kernel.structure.ai.model.Relation;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.label.PresentLabel;
+import com.lantone.qc.pub.model.label.VitalLabelSpecial;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : BEH03059
+ * @Description : 现病史描述与专科检查相互矛盾
+ * @Author : 胡敬
+ * @Date: 2020-07-31 11:09
+ */
+@Component
+public class BEH03059 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+
+        Map<String, String> beHospitalStructure = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String present = beHospitalStructure.get("现病史");
+        String specialVital = beHospitalStructure.get("专科体格检查");
+
+        if (StringUtil.isBlank(present) || StringUtil.isBlank(specialVital)) {
+            return;
+        }
+
+        PresentLabel presentLabel = inputInfo.getBeHospitalizedDoc().getPresentLabel();
+        VitalLabelSpecial vitalLabelSpecial = inputInfo.getBeHospitalizedDoc().getVitalLabelSpecial();
+        if (presentLabel == null || vitalLabelSpecial == null) {
+            return;
+        }
+        JSONObject pCrfOutput = presentLabel.getCrfOutput();
+        JSONObject vsCrfOutput = vitalLabelSpecial.getCrfOutput();
+        if (pCrfOutput.size() == 0 || vsCrfOutput.size() == 0) {
+            return;
+        }
+        List<Lemma> presentLemmas = getLemma(pCrfOutput);//现病史实体
+        List<Lemma> vitalSpecialLemmas = getLemma(vsCrfOutput);//现病史关系
+        List<Relation> presentRelations = getRelation(pCrfOutput);//专科检查实体
+        List<Relation> vitalSpecialRelations = getRelation(vsCrfOutput);//专科检查关系
+
+        ConflictFinder finder = new ConflictFinder();
+        Object[] checkPairs;
+        Object[] presentPairs;
+        checkPairs = new Object[] { vitalSpecialLemmas, vitalSpecialRelations };
+        presentPairs = new Object[] { presentLemmas, presentRelations };
+
+        List<Object[]> positionPairs = finder.findConflictPositions(checkPairs, presentPairs);
+        if (positionPairs.size() > 0) {
+            status.set("-1");
+            String infoStr = "";
+            for (int i = 0; i < positionPairs.size(); i++) {
+                if (i % 2 != 0) {
+                    String words = "";
+                    Object[] entityBlocks = positionPairs.get(i);
+                    for (Object o : entityBlocks) {
+                        EntityBlock entityBlock = (EntityBlock) o;
+                        words += entityBlock.getSearchWord() + "->";
+                    }
+                    infoStr = CatalogueUtil.concatInfo(infoStr, words.substring(0, words.length() - 2));
+                }
+            }
+            info.set(infoStr);
+        }
+    }
+
+    private List<Lemma> getLemma(JSONObject crfOutput) {
+        List<Lemma> lemmaList = new ArrayList<>();
+        JSONObject aiOut = crfOutput.getJSONObject(BeHospitalizedAI.entityRelationObject).getJSONObject(BeHospitalizedAI.outputs);
+        JSONObject annotation = aiOut.getJSONObject("annotation");
+        JSONArray entitys = annotation.getJSONArray("T");
+        entitys.remove(0);//第一个为"",这里去除
+        Lemma lemma;
+        for (Object entityObject : entitys) {
+            JSONObject entity = (JSONObject) entityObject;
+            lemma = new Lemma();
+            lemma.setFrom(entity.getInteger("start"));
+            lemma.setTo(entity.getInteger("end"));
+            lemma.setProperty(entity.getString("name"));
+            lemma.setText(entity.getString("value"));
+            lemma.setId(entity.getInteger("id"));
+            lemmaList.add(lemma);
+        }
+        return lemmaList;
+    }
+
+    private List<Relation> getRelation(JSONObject crfOutput) {
+        List<Relation> relationList = new ArrayList<>();
+        JSONObject aiOut = crfOutput.getJSONObject(BeHospitalizedAI.entityRelationObject).getJSONObject(BeHospitalizedAI.outputs);
+        JSONObject annotation = aiOut.getJSONObject("annotation");
+        JSONArray relations = annotation.getJSONArray("R");
+        relations.remove(0);//第一个为"",这里去除
+        Relation relation;
+        for (Object relationObject : relations) {
+            JSONObject entity = (JSONObject) relationObject;
+            relation = new Relation();
+            relation.setFrom(entity.getInteger("from"));
+            relation.setTo(entity.getInteger("to"));
+            relation.setRelationName(entity.getString("name"));
+            relationList.add(relation);
+        }
+        return relationList;
+    }
+}

+ 52 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03060.java

@@ -0,0 +1,52 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.google.common.collect.Lists;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.label.DiagLabel;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @ClassName : BEH03060
+ * @Description : 诊断与体征描述相互矛盾
+ * @Author : 胡敬
+ * @Date: 2020-08-10 19:00
+ */
+@Component
+public class BEH03060 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+        DiagLabel initialDiagLabel = inputInfo.getBeHospitalizedDoc().getInitialDiagLabel();
+        if (initialDiagLabel == null || StringUtil.isBlank(initialDiagLabel.getText())) {
+            return;
+        }
+
+        List<String> diags = Lists.newArrayList(initialDiagLabel.getText().split("\n"));
+        List<String> matchDiag = diags.stream().filter(x -> x.contains("房扑") || x.contains("房颤")).collect(Collectors.toList());
+        if (matchDiag.size() > 0) {
+            String diag = matchDiag.get(0);//取第一个包含房扑或房颤的疾病名称
+            Map<String, String> behStructureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+            String heartRate = behStructureMap.get("心律");
+            if (StringUtil.isNotBlank(heartRate) && "齐".equals(heartRate)) {
+                status.set("-1");
+                info.set(diag + "><心律齐");
+                return;
+            }
+            String specialCheck = behStructureMap.get("专科体格检查");
+            if (StringUtil.isNotBlank(specialCheck) && specialCheck.contains("律齐")) {
+                status.set("-1");
+                info.set(diag + "><心律齐");
+            }
+        }
+    }
+}

+ 47 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/behospitalized/BEH03061.java

@@ -0,0 +1,47 @@
+package com.lantone.qc.kernel.catalogue.behospitalized;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @ClassName : BEH03061
+ * @Description : 体征前后描述矛盾
+ * @Author : 胡敬
+ * @Date: 2020-08-11 15:28
+ */
+@Component
+public class BEH03061 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getBeHospitalizedDoc() == null) {
+            return;
+        }
+        Map<String, String> behStructureMap = inputInfo.getBeHospitalizedDoc().getStructureMap();
+        String heartRate = behStructureMap.get("心律");
+        String specialCheck = behStructureMap.get("专科体格检查");
+        if (StringUtil.isNotBlank(heartRate) && StringUtil.isNotBlank(specialCheck)) {
+            Pattern p = Pattern.compile("心律.*齐");
+            Matcher m = p.matcher(specialCheck);
+            if (m.find()) {
+                String matchVital = m.group(0);
+                if (StringUtil.isNotBlank(matchVital)) {
+                    if ("齐".equals(heartRate) && matchVital.contains("不")) {
+                        status.set("-1");
+                        info.set("心律齐><" + matchVital);
+                    } else if ("不齐".equals(heartRate) && !matchVital.contains("不")) {
+                        status.set("-1");
+                        info.set("心律不齐><" + matchVital);
+                    }
+                }
+            }
+        }
+    }
+}

+ 35 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/clinicalblood/CLI03036.java

@@ -0,0 +1,35 @@
+package com.lantone.qc.kernel.catalogue.clinicalblood;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.ClinicalBloodDoc;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : CLI03036
+ * @Description :   输血前预防输血反应措施未填写
+ * @Author : kwz
+ * @Date: 2020-07-06 15:28
+ */
+@Component
+public class CLI03036 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<ClinicalBloodDoc> clinicalBloodDocs = inputInfo.getClinicalBloodDocs();
+        if (clinicalBloodDocs != null && clinicalBloodDocs.size() > 0) {
+            for (ClinicalBloodDoc cliB : clinicalBloodDocs) {
+                Map<String, String> cliBStructureMap = cliB.getStructureMap();
+                if (StringUtils.isEmpty(cliBStructureMap.get("输血前预防输血反应措施"))) {
+                    status.set("-1");
+                    return;
+                }
+            }
+        }
+
+    }
+}

+ 35 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/clinicalblood/CLI03037.java

@@ -0,0 +1,35 @@
+package com.lantone.qc.kernel.catalogue.clinicalblood;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.ClinicalBloodDoc;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : CLI03037
+ * @Description :   输血后生命体征变化未填写
+ * @Author : kwz
+ * @Date: 2020-07-06 15:28
+ */
+@Component
+public class CLI03037 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<ClinicalBloodDoc> clinicalBloodDocs = inputInfo.getClinicalBloodDocs();
+        if (clinicalBloodDocs != null && clinicalBloodDocs.size() > 0) {
+            for (ClinicalBloodDoc cliB : clinicalBloodDocs) {
+                Map<String, String> cliBStructureMap = cliB.getStructureMap();
+                if (StringUtils.isEmpty(cliBStructureMap.get("输血后生命体征变化"))) {
+                    status.set("-1");
+                    return;
+                }
+            }
+        }
+
+    }
+}

+ 64 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/crisisvaluereport/CRI03062.java

@@ -0,0 +1,64 @@
+package com.lantone.qc.kernel.catalogue.crisisvaluereport;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.CrisisInfoDoc;
+import com.lantone.qc.pub.model.doc.CrisisValueReportDoc;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description: 危急值无报告处理记录
+ * @author: 胡敬
+ * @time: 2020/8/12 14:18
+ */
+@Component
+public class CRI03062 extends QCCatalogue {
+
+    @Override
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        //无危急值结构化信息
+        List<CrisisInfoDoc> crisisInfoDocs = inputInfo.getCrisisInfoDocs();
+        if (crisisInfoDocs == null || crisisInfoDocs.size() == 0) {
+            return;
+        }
+
+        List<CrisisValueReportDoc> crisisValueReportDocs = inputInfo.getCrisisValueReportDocs();
+        //如果有危急值结构化数据 但无文书则直接提醒无危急值报告
+        if (crisisValueReportDocs == null || crisisValueReportDocs.size() == 0) {
+            status.set("-1");
+            return;
+        }
+        List<String> findCrisises = new ArrayList<>();
+        for (CrisisInfoDoc crisisInfoDoc : crisisInfoDocs) {
+            String reptTimeStr = crisisInfoDoc.getStructureMap().get("报告时间");
+            String crisisName = crisisInfoDoc.getStructureMap().get("危急值名称");
+            String crisisValue = crisisInfoDoc.getStructureMap().get("危急结果值");
+            crisisName = crisisName == null ? "" : crisisName.split("\\[|\\(|\\(")[0];
+            if (StringUtil.isBlank(reptTimeStr)) {
+                continue;
+            }
+            int crisisMatchSum = 0;
+            for (CrisisValueReportDoc crisisValueReportDoc : crisisValueReportDocs) {
+                String docReptContent = crisisValueReportDoc.getStructureMap().get("危急值记录内容");
+                if (!docReptContent.contains(crisisName) && !docReptContent.contains(crisisValue)) { //危急值报告内容不包含危急值内容,跳过
+                    crisisMatchSum++;
+                }
+            }
+
+            if (crisisMatchSum == crisisValueReportDocs.size()) {//该条危急值记录没有报告
+                findCrisises.add(crisisName + ":" + crisisValue + "->" + reptTimeStr);
+            }
+        }
+
+        if (findCrisises.size() > 0) {
+            status.set("-1");
+            info.set(findCrisises.toString().replaceAll("[\\[\\]]", ""));
+        }
+    }
+}

+ 36 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstcourserecord/FIRC0090.java

@@ -0,0 +1,36 @@
+package com.lantone.qc.kernel.catalogue.firstcourserecord;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.FirstCourseRecordDoc;
+import com.lantone.qc.pub.model.label.TreatPlanLabel;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @ClassName : FIRC0090
+ * @Description : 治疗措施不具体
+ * @Author : 胡敬
+ * @Date: 2020-07-10 14:41
+ */
+@Component
+public class FIRC0090 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        FirstCourseRecordDoc firstCourseRecordDoc = inputInfo.getFirstCourseRecordDoc();
+        if (firstCourseRecordDoc == null) {
+            status.set("0");
+            return;
+        }
+        TreatPlanLabel treatPlanLabel = firstCourseRecordDoc.getTreatPlanLabel();
+        if (treatPlanLabel == null) {
+            return;
+        }
+        if (treatPlanLabel.getMedicine() != null || treatPlanLabel.getTreat().size() > 0
+                || treatPlanLabel.getPacs().size() > 0) {
+            status.set("0");
+        }
+    }
+}

+ 45 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP02842.java

@@ -0,0 +1,45 @@
+package com.lantone.qc.kernel.catalogue.firstpagerecord;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : FIRP02842
+ * @Description : 出院诊断疾病编码未填写
+ * @Author : kwz
+ * @Date: 2020-07-15 17:45
+ */
+@Component
+public class FIRP02842 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getFirstPageRecordDoc() == null) {
+            return;
+        }
+        if (inputInfo.getFirstPageRecordDoc().getStructureMap() != null) {
+            Map<String, Object> firstpageStructureMap = inputInfo.getFirstPageRecordDoc().getStructureExtMap();
+            JSONArray out_diag = (JSONArray)firstpageStructureMap.get("出院诊断");
+            if (out_diag.size() > 0) {
+                for (int i=0;i<out_diag.size();i++) {
+                    JSONObject jsonObject = out_diag.getJSONObject(i);
+                    String diag_code = (String)jsonObject.get("诊断编码");
+                    if(StringUtils.isBlank(diag_code)){
+                        status.set("-1");
+                        return;
+                    }
+
+                }
+
+            }
+        }
+    }
+}

+ 47 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP02844.java

@@ -0,0 +1,47 @@
+package com.lantone.qc.kernel.catalogue.firstpagerecord;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : FIRP02844
+ * @Description : 手术及操作编码未填
+ * @Author : kwz
+ * @Date: 2020-07-15 17:45
+ */
+@Component
+public class FIRP02844 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getFirstPageRecordDoc() == null) {
+            return;
+        }
+        if (inputInfo.getFirstPageRecordDoc().getStructureMap() != null) {
+            List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+            long count = operationDocs.stream().filter(x -> x.getOperationRecordDoc() != null).count();
+            Map<String, Object> firstpageStructureMap = inputInfo.getFirstPageRecordDoc().getStructureExtMap();
+            JSONArray out_diag = (JSONArray)firstpageStructureMap.get("手术信息");
+            if (out_diag != null && out_diag.size() > 0) {
+                for (int i=0;i<out_diag.size();i++) {
+                    JSONObject jsonObject = out_diag.getJSONObject(i);
+                    String diag_code = (String)jsonObject.get("手术编码");
+                    if(StringUtils.isBlank(diag_code)){
+                        status.set("-1");
+                        return;
+                    }
+
+                }
+
+            }
+        }
+    }
+}

+ 58 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP03038.java

@@ -0,0 +1,58 @@
+package com.lantone.qc.kernel.catalogue.firstpagerecord;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.lantone.qc.dbanaly.util.KernelConstants;
+import com.lantone.qc.dbanaly.util.SpecialStorageUtil;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @ClassName : FIRP03038
+ * @Description : 出院诊断疾病编码填写错误
+ * @Author : kwz
+ * @Date: 2020-07-15 17:45
+ */
+@Component
+public class FIRP03038 extends QCCatalogue {
+    @Autowired
+    private SpecialStorageUtil specialStorageUtil;
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getFirstPageRecordDoc() == null) {
+            return;
+        }
+        Map<String, Map<String,String>> hostpital_standDiag = specialStorageUtil.getJsonStringValue(KernelConstants.HOSPITAL_DIAG_MAP);
+        if (inputInfo.getFirstPageRecordDoc().getStructureMap() != null) {
+            Map<String, Object> firstpageStructureMap = inputInfo.getFirstPageRecordDoc().getStructureExtMap();
+            JSONArray out_diag = (JSONArray)firstpageStructureMap.get("出院诊断");
+            if (out_diag.size() > 0) {
+                for (int i=0;i<out_diag.size();i++) {
+                    JSONObject jsonObject = out_diag.getJSONObject(i);
+                    String diag_code = (String)jsonObject.get("诊断编码");
+                    String diag_name = (String) jsonObject.get("诊断名称");
+                    if(StringUtils.isNotBlank(diag_code) && hostpital_standDiag.containsKey(diag_name)){
+                        Map<String,String> s = hostpital_standDiag.get(diag_name);
+                        if(s != null){
+                            String icd10 = s.get("icd10");
+                            if(StringUtils.isNotBlank(icd10) && icd10.equals(diag_code)){
+                                status.set("0");
+                            }else {
+                                status.set("-1");
+                                return;
+                            }
+                        }
+                    }
+
+                }
+
+            }
+        }
+    }
+}

+ 51 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP03045.java

@@ -0,0 +1,51 @@
+package com.lantone.qc.kernel.catalogue.firstpagerecord;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @ClassName : FIRP03045
+ * @Description : 呼吸机辅助治疗未记录
+ * 医嘱里找呼吸机
+ * 手术和操作里面找
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class FIRP03045 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getFirstPageRecordDoc() != null && inputInfo.getFirstPageRecordDoc().getStructureExtMap() != null
+                && inputInfo.getDoctorAdviceDocs().size() > 0) {
+            List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+            List<String> docAdviceElement = doctorAdviceDocs
+                    .stream()
+                    .map(x -> x.getStructureMap().get("医嘱项目名称"))
+                    .filter(x -> x != null && x.contains("呼吸机"))
+                    .collect(Collectors.toList());
+
+            Map<String, Object> extMap = inputInfo.getFirstPageRecordDoc().getStructureExtMap();
+            List<Map<String, String>> operationList = (List<Map<String, String>>) extMap.get("手术信息");
+            if (operationList != null && operationList.size() > 0) {
+                List<String> firstPageElement = operationList
+                        .stream()
+                        .map(x -> x.get("手术名称"))
+                        .filter(x -> StringUtil.isNotBlank(x) && x.contains("呼吸机"))
+                        .collect(Collectors.toList());
+
+                if (docAdviceElement.size() > 0 && firstPageElement.size() == 0) {
+                    status.set("-1");
+                }
+            }
+
+        }
+    }
+}

+ 84 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/firstpagerecord/FIRP03046.java

@@ -0,0 +1,84 @@
+package com.lantone.qc.kernel.catalogue.firstpagerecord;
+
+import com.google.common.collect.Lists;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.Content;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.util.ListUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @ClassName : FIRP03046
+ * @Description : 主要诊断与主要手术/操作不匹配
+ * @Author : 楼辉荣
+ * @Date: 2020-03-06 17:28
+ */
+@Component
+public class FIRP03046 extends QCCatalogue {
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        if (inputInfo.getFirstPageRecordDoc() == null || inputInfo.getFirstPageRecordDoc().getStructureExtMap() == null) {
+            return;
+        }
+        Map<String, Object> structureExtMap = inputInfo.getFirstPageRecordDoc().getStructureExtMap();
+        List<Map<String, String>> diags = (List) structureExtMap.get(Content.dischargeDiag);
+        List<Map<String, String>> opers = (List) structureExtMap.get(Content.operative_information);
+        if (ListUtil.isEmpty(diags) || ListUtil.isEmpty(opers)) {
+            return;
+        }
+        String diagName = diags.get(0).get("诊断名称");
+        String operName = opers.get(0).get("手术名称");
+        diagOperList.forEach(i -> {
+            String[] diagOperArry = i.split("=");
+            if (diagOperArry[0].indexOf(diagName) > -1 && diagOperArry[1].indexOf(operName) > -1) {
+                status.set("-1");
+                return;
+            }
+        });
+    }
+
+    private List<String> diagOperList = Lists.newArrayList(
+            "诊断名称=手术名称",
+            "冠状动脉粥样硬化性心脏病=体内埋藏式自动除颤起搏器植入",
+            "感染性休克(好转)=鼻内镜鼻腔鼻窦手术",
+            "发热=手术名称 : 非血管介入: 经皮肝穿刺活检术,非血管介入: 各个部位脓肿、囊肿穿刺引流术",
+            "胆囊占位性病变=经皮冠脉造影术",
+            "肝癌=心电监护下左眼白内障超声乳化囊外摘除联合人工晶体植入/房角粘连分离/胬肉切除术/自体结膜移植术",
+            "低钠血症=神经导航下复发颅咽管瘤囊腔Ommaya泵置入",
+            "甲状腺结节=右侧甲状腺癌根治(右侧甲状腺叶切除;峡部切除;右侧气食管沟清扫)",
+            "急性胆管炎=左输尿管镜探查",
+            "冠状动脉粥样硬化性心脏病=主动脉、颈动脉造影术;",
+            "肝功能异常=骨髓穿刺",
+            "休克=胸椎后路T5/6骨折复位、椎管减压、植骨融合、椎弓根钉棒系统内固定术;",
+            "慢性便秘=腹腔镜辅助下全结肠切除",
+            "膀胱肿瘤=冠状动脉药物涂层支架置入术",
+            "子宫多发肌瘤=取环",
+            "腰椎管狭窄=尿道狭窄扩张",
+            "病态窦房结综合征=脑血管造影",
+            "颈部皮肤肿物=头皮肿物切除术",
+            "妊娠期糖尿病=子宫下段剖宫产术",
+            "宫颈管息肉=宫颈粘连分离",
+            "盆腔炎性疾病后遗症=腹腔镜左侧输卵管切除术",
+            "颈椎间盘突出=后路腰4/5经右侧椎间孔减压、椎体间融合、钉棒系统内固定术",
+            "左半侧颜面萎缩=腹部吸脂",
+            "妊娠期高血压=急诊子宫下段剖宫产",
+            "左侧股骨远端骨折术后=左膝关节松解",
+            "轻度认知障碍=腰椎穿刺术",
+            "风湿性瓣膜性联合性心脏病=经皮心房颤动射频消融术",
+            "惊恐障碍=无痛肠镜",
+            "取卵术后=(住院)超声科B超引导下腹腔积液置管引流",
+            "后循环脑血管缺血=无痛肠镜申请单(和爽)(西甲硅油)|经肠镜结直肠息肉摘除术",
+            "左侧卵巢囊肿蒂扭转=腹腔镜下双侧附件切除",
+            "左侧股骨转子间骨折=左髋关节滑膜切除",
+            "病态窦房结综合征=用两根导管的冠状动脉造影术",
+            "肋软骨痛=无痛肠镜申请单(和爽)(西甲硅油)|经肠镜结直肠息肉摘除术",
+            "心律失常=用两根导管的冠状动脉造影术",
+            "病态窦房结综合征=用两根导管的冠状动脉造影术"
+    );
+
+}

+ 123 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/preoperativediscussion/PRE03064.java

@@ -0,0 +1,123 @@
+package com.lantone.qc.kernel.catalogue.preoperativediscussion;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.LisDoc;
+import com.lantone.qc.pub.model.doc.PacsDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationRecordDoc;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: 术前未完成常规检查
+ * @author: HUJING
+ * @time: 2020/8/14 13:14
+ */
+@Component
+public class PRE03064 extends QCCatalogue {
+    private List<String> normalCheck = Lists.newArrayList("出凝血时间", "HBSAG", "血常规", "尿常规", "血型", "心电图");
+
+    @Override
+    protected void start(InputInfo inputInfo, OutputInfo outputInfo) throws ParseException {
+        status.set("0");
+        List<LisDoc> lisDocs = inputInfo.getLisDocs();
+        List<PacsDoc> pacsDocs = inputInfo.getPacsDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+
+        if (operationDocs.size() == 0 || lisDocs.size() == 0 || pacsDocs.size() == 0) {
+            return;
+        }
+
+        //取化验名称
+        Map<String, String> lisRet = Maps.newHashMap();
+        lisDocs.stream().map(LisDoc::getStructureMap).forEach(x -> lisRet.put(x.get("报告名称"), x.get("报告创建时间")));
+        Map<String, String> newLisRet = processMap(lisRet);
+        //取辅检名称
+        Map<String, String> pacsRet = Maps.newHashMap();
+        pacsDocs.stream().map(PacsDoc::getStructureMap).forEach(x -> pacsRet.put(x.get("报告名称"), x.get("报告创建时间")));
+        Map<String, String> newPacsRet = processMap(pacsRet);
+
+        //取手术时间
+        List<String> operationDateList = operationDocs.stream().map(OperationDoc::getOperationRecordDoc)
+                .filter(Objects::nonNull)
+                .map(OperationRecordDoc::getStructureMap).map(x -> x.get("手术日期")).filter(StringUtil::isNotBlank).collect(Collectors.toList());
+
+        List<String> lisKey = new ArrayList<>(newLisRet.keySet());
+        List<String> pacsKey = new ArrayList<>(newPacsRet.keySet());
+        //lisKey.retainAll(normalCheck);//如果存在相同元素,lisKey中仅保留相同的元素。 如果不存在相同元素,lisKey会变为空。
+        //pacsKey.retainAll(normalCheck);//如果存在相同元素,pacsKey中仅保留相同的元素。 如果不存在相同元素,pacsKey会变为空。
+
+        if (operationDateList.size() > 0) {
+            String firstOperationDateStr = operationDateList.get(0);
+            Date firstOperationDate = StringUtil.parseDateTime(firstOperationDateStr);
+            int matchSum = 0;
+            List<String> missCheck = Lists.newArrayList();
+            getMatchSum(newLisRet, lisKey, firstOperationDate, matchSum, missCheck);
+            getMatchSum(newPacsRet, pacsKey, firstOperationDate, matchSum, missCheck);
+
+            if (matchSum < normalCheck.size()) {
+                status.set("-1");
+                info.set("未完成常规检查:" + missCheck.toString().replaceAll("[\\[\\]]", ""));
+            }
+        }
+//        for (String operationDateStr : operationDateList) {
+//            Date operationDate = StringUtil.parseDateTime(operationDateStr);
+//            if (operationDate == null) {
+//                continue;
+//            }
+//
+//        }
+//        System.out.println(operationDateList);
+
+    }
+
+    //获取检查中与必须要的检查匹配数
+    private void getMatchSum(Map<String, String> newRet, List<String> keySet, Date firstOperationDate, int matchSum, List<String> miss) {
+        for (String check : normalCheck) {
+            int checkMiss = 0;
+            for (String lis : keySet) {
+                if (lis.contains(check)) {
+                    String lisDateStr = newRet.get(lis);
+                    Date lisDate = StringUtil.parseDateTime(lisDateStr);
+                    if (lisDate.before(firstOperationDate)) {
+                        matchSum++;
+                        break;
+                    } else {
+                        checkMiss++;
+                    }
+                } else {
+                    checkMiss++;
+                }
+
+            }
+            //如果所有化验里都没有必要的当前检查(normalCheck里的检查)
+            if (checkMiss == keySet.size()) {
+                miss.add(check);
+            }
+        }
+    }
+
+    private Map<String, String> processMap(Map<String, String> ret) {
+        Map<String, String> newRet = Maps.newHashMap();
+        for (Map.Entry<String, String> enrty : ret.entrySet()) {
+            String key = enrty.getKey();
+            if (key.contains("=")) {
+                String pubName = key.split("=")[0];
+                if (StringUtil.isNotBlank(pubName)) {
+                    newRet.put(pubName, enrty.getValue());
+                }
+            } else {
+                newRet.put(key, enrty.getValue());
+            }
+        }
+        return newRet;
+    }
+}

+ 72 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03065.java

@@ -0,0 +1,72 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.LisDoc;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-14 15:52
+ * @desc 住院48小时以上无血尿常规化验结果
+ **/
+@Component
+public class THR03065 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        List<LisDoc> lisDocs = inputInfo.getLisDocs();
+        if (lisDocs == null) {
+            return;
+        }
+        if (leaveHospitalDoc != null) {
+            String beginDateStr = leaveHospitalDoc.getStructureMap().get("入院时间");
+            String endDateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (StringUtil.isBlank(beginDateStr) || StringUtil.isBlank(endDateStr)) {
+                return;
+            }
+            Date beginDate = StringUtil.parseDateTime(beginDateStr);
+            Date endDate = StringUtil.parseDateTime(endDateStr);
+            if (beginDate == null || endDate == null) {
+                return;
+            }
+            if (!CatalogueUtil.compareTime(beginDate, endDate, 48 * 60L)) {
+                return;
+            }
+        }
+        List<String> lisName = lisDocs.stream().map(LisDoc::getStructureMap).map(x -> x.get("报告名称")).collect(Collectors.toList());
+        //      血常规                尿常规
+        boolean findCBC = false, findUrineTest = false;
+        for (String lis : lisName) {
+            if (lis.contains("血常规") || lis.contains("CBC")) {
+                findCBC = true;
+            }
+            if (lis.contains("尿常规")) {
+                findUrineTest = true;
+            }
+        }
+
+        List<String> miss = Lists.newArrayList();
+        if (!findCBC) {
+            miss.add("血常规");
+        }
+        if (!findUrineTest) {
+            miss.add("尿常规");
+        }
+
+        if (miss.size() > 0) {
+            status.set("-1");
+            info.set(miss.toString().replaceAll("[\\[\\]]", ""));
+        }
+    }
+
+}

+ 300 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03069.java

@@ -0,0 +1,300 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-21 10:21
+ * @desc 抗生素加用原因不明确
+ **/
+@Component
+public class THR03069 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0) {
+            return;
+        }
+        //抗生素及开医嘱时间(包括加用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDate = Maps.newHashMap();
+        //抗生素加用集合   key:抗生素名    value:  0:未加用,1及以上:加用次数
+        Map<String, Integer> antibioticStatus = Maps.newHashMap();
+        //抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValue = Maps.newHashMap();
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("抗生素") && StringUtil.isNotBlank(x.get("医嘱单次剂量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+
+        String drugName = null, value = null, startDateStr = null;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("医嘱单次剂量");
+            startDateStr = structMap.get("医嘱开始时间");
+            drugName = removeBracket(drugName);
+            collectAntibioticInfo(antibioticDate, antibioticStatus, antibioticValue, drugName, value, startDateStr);
+        }
+
+        //把抗生素没加用过的抗生素删除
+        for (Map.Entry<String, Integer> as : antibioticStatus.entrySet()) {
+            if (as.getValue() == 0) {
+                antibioticDate.remove(as.getKey());
+                antibioticValue.remove(as.getKey());
+            }
+        }
+        //抗生素加用过的集合如果为空,则一个抗生素都没有加用过,直接返回0
+        if (antibioticDate.size() == 0) {
+            return;
+        }
+
+        //病程记录中抗生素及查房时间(包括加用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDateCourse = Maps.newHashMap();
+        //病程记录中抗生素加用集合   key:抗生素名    value:  0:未加用,1及以上:加用次数
+        Map<String, Integer> antibioticStatusCourse = Maps.newHashMap();
+        //病程记录中抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValueCourse = Maps.newHashMap();
+        String dateStr = null;
+        /*********************************************查房记录********************************************************/
+        if (threeLevelWardDocs.size() > 0) {
+            List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+            for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+                if (doc.getThreeLevelWardLabel().size() == 0) {
+                    continue;
+                }
+                dateStr = doc.getStructureMap().get("查房日期");
+                ThreeLevelWardLabel label = doc.getThreeLevelWardLabel().get(0);
+                List<Drug> drugs = label.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************术后首程********************************************************/
+        if (operationDocs.size() > 0) {
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null)
+                    .collect(Collectors.toList());
+            for (OperationDiscussionDoc discussionDoc : operationDiscussionDocs) {
+                dateStr = discussionDoc.getStructureMap().get("记录日期");
+                if (StringUtil.isBlank(dateStr)) {
+                    continue;
+                }
+                List<Drug> drugs = discussionDoc.getOperationDiscussionLabel().getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************出院大结********************************************************/
+        if (leaveHospitalDoc != null) {
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        //将病程日期排序
+        antibioticDateCourse.forEach((x, y) -> y.sort(Comparator.naturalOrder()));
+
+        /**
+         * 1.antibioticDate:从医嘱中取   key:抗生素名    value:医嘱中该抗生素所有加用时的时间(包括初始使用时间)
+         * 2.antibioticDateWard:从查房记录中取     key:抗生素名    value:查房记录中该抗生素所有加用时的查房时间(包括初始使用时间)
+         * 3.医嘱中该抗生素初始使用时间往后两天内,查房记录中该抗生素初始使用时间也在这两天内,则满足一半。
+         * 4.医嘱中该抗生素加用时的时间往后两天内,查房记录中该抗生素加用时间也在这两天内,则满足条件,该抗生素通过该条规则
+         * 5.继续判断下一个抗生素
+         * 6.若医嘱中加用过的抗生素,查房记录中没出现过,则该抗生素会报出来(存入miss,规则最后会把所有不符合的抗生素都报出来)
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null, start = null, change = null, wardStartStr = null, wardChangeStr = null;
+        List<String> dateList = null;
+        for (Map.Entry<String, List<String>> ad : antibioticDate.entrySet()) {
+            drugKey = ad.getKey();
+            List<Double> antibioticValueList = antibioticValue.get(drugKey);//医嘱中该药品对应的所有用量
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            /*if (!antibioticDateCourse.containsKey(drugKey)) {
+                for (String date : ad.getValue()) {
+                    infoAppend(sb, drugKey, date);
+                }
+                continue;
+            }*/
+            if (antibioticDateCourse.containsKey(drugKey)) {
+                List<Double> antibioticValueWardList = antibioticValueCourse.get(drugKey);//病程记录中该药品对应的所有用量
+                int findNum = 0;
+                for (int i = 1; i < antibioticValueList.size(); i++) {//从加用的值开始,如果加用过的值查房记录中都有,则不报该药
+                    if (antibioticValueWardList.contains(antibioticValueList.get(i))) {
+                        findNum++;
+                    }
+                }
+                if (findNum == antibioticValueList.size() - 1) {
+                    continue;
+                }
+                dateList = ad.getValue();
+                int matchNum = 0;
+                List<String> wardDateStr = antibioticDateCourse.get(drugKey);
+                for (int i = 0; i < dateList.size() - 1; i++) {
+                    start = dateList.get(i);        //抗生素开医嘱时间
+                    change = dateList.get(i + 1);   //抗生素用量改变时间
+                    Date adStart = StringUtil.parseDateTime(start);
+                    Date adChange = StringUtil.parseDateTime(change);
+                    for (int j = 0; j < wardDateStr.size() - 1; j++) {
+                        wardStartStr = wardDateStr.get(j);         //查房记录开抗生素时间
+                        wardChangeStr = wardDateStr.get(j + 1);    //查房记录改变抗生素用量时间
+                        Date wardStart = StringUtil.parseDateTime(wardStartStr);
+                        Date wardChange = StringUtil.parseDateTime(wardChangeStr);
+                        if (!CatalogueUtil.compareTime(adStart, wardStart, 48 * 60L) && !CatalogueUtil.compareTime(adChange, wardChange, 48 * 60L)) {
+                            matchNum++;
+                        }
+                    }
+                }
+                if (dateList.size() - 1 != matchNum) {
+                    infoAppend(sb, drugKey, change);
+                }
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set(sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 收集各模块药品信息
+     *
+     * @param antibioticDateWard   病程中抗生素使用所有时间
+     * @param antibioticStatusWard 病程中抗生素用量改变状态
+     * @param antibioticValueWard  病程中抗生素及用量
+     * @param dateStr              记录日期
+     * @param drugs                模型提取出的药品列表
+     */
+    private void getCourseDrugInfo(Map<String, List<String>> antibioticDateWard, Map<String, Integer> antibioticStatusWard,
+                                   Map<String, List<Double>> antibioticValueWard, String dateStr, List<Drug> drugs) {
+        for (Drug drug : drugs) {
+            String wardDrug = drug.getName();
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            //药品用量和使用原因都有时
+            if (drug.getConsumption() != null) {
+                //查房记录抗生素加用过的集合中没包含该抗生素,则认为该抗生素是第一次出现,此时不需要加用原因
+                if (!antibioticStatusWard.containsKey(wardDrug) || drug.getUsageWardRound() != null) {
+                    String consumption = drug.getConsumption().getName();
+                    collectAntibioticInfo(antibioticDateWard, antibioticStatusWard, antibioticValueWard, wardDrug, consumption, dateStr);
+                }
+            }
+        }
+    }
+
+    /**
+     * 收集抗生素各种信息
+     *
+     * @param antibioticDate   抗生素使用所有时间
+     * @param antibioticStatus 抗生素用量改变状态
+     * @param antibioticValue  抗生素及用量
+     * @param drugName         抗生素名称
+     * @param value            抗生素用量
+     * @param startDateStr     抗生素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo(Map<String, List<String>> antibioticDate, Map<String, Integer> antibioticStatus,
+                                       Map<String, List<Double>> antibioticValue, String drugName, String value, String startDateStr) {
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03075:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticValue.containsKey(drugName)) {
+            antibioticValue.put(drugName, Lists.newArrayList(v));
+            antibioticDate.put(drugName, Lists.newArrayList(startDateStr));
+            antibioticStatus.put(drugName, 0);
+        } else {
+            List<Double> beforeValue = antibioticValue.get(drugName);
+            if (beforeValue.get(beforeValue.size() - 1) < v) {
+                beforeValue.add(v);
+                antibioticValue.put(drugName, beforeValue);//添加该抗生素更大的值
+                antibioticStatus.put(drugName, antibioticStatus.get(drugName) + 1);
+                antibioticDate.get(drugName).add(startDateStr);
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date) {
+        sb.append(drugKey).append("(").append(DateUtil.formatDate(StringUtil.parseDateTime(date))).append(")").append(",");
+    }
+
+}

+ 200 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03070.java

@@ -0,0 +1,200 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.CrisisInfoDoc;
+import com.lantone.qc.pub.model.doc.LisDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-17 15:29
+ * @desc 异常化验未记录
+ **/
+@Component
+public class THR03070 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<LisDoc> lisDocs = inputInfo.getLisDocs();
+        List<CrisisInfoDoc> crisisInfoDocs = inputInfo.getCrisisInfoDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        if (lisDocs.size() == 0 || threeLevelWardDocs.size() == 0) {
+            return;
+        }
+        List<String> crisisNames = crisisInfoDocs
+                .stream()
+                .map(CrisisInfoDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("危急值名称")))
+                .map(x -> x.get("危急值名称"))
+                .collect(Collectors.toList());
+        //异常数据列表
+        List<String> abnormal = Lists.newArrayList();
+        Map<String, String> abnormalMap = Maps.newHashMap();
+        StringBuffer sb = new StringBuffer();
+        for (LisDoc lisDoc : lisDocs) {
+            double resultValue = -1, max = -1, min = -1;
+            Map<String, String> structureMap = lisDoc.getStructureMap();
+            String itemName = structureMap.get("报告名称");
+            if (crisisNames.contains(itemName)) {
+                continue;
+            }
+            String result = structureMap.get("检验结果");
+            String reference = structureMap.get("参考值");
+            String itemDate = structureMap.get("报告创建时间");
+            if (StringUtil.isBlank(itemName) || StringUtil.isBlank(result)
+                    || StringUtil.isBlank(reference) || StringUtil.isBlank(itemDate)) {
+                continue;
+            }
+            itemName = itemName.split("=")[1];
+            //1.化验结果是阳性时,直接把该化验名称放入异常数据列表中
+            if (result.contains("阳")) {
+                abnormal.add(itemName);
+                abnormalMap.put(itemName, itemDate);
+                continue;
+            }
+            //2.化验结果为阴性,或化验结果中不包含数字,跳过该条化验结果
+            if (result.contains("阴") || !CatalogueUtil.numberExist(result)) {
+                continue;
+            }
+            try {
+                resultValue = Double.parseDouble(result);
+            } catch (Exception e) {
+                System.out.println("THR03070--解析result出错:" + itemName + "->" + result);
+            }
+            if (resultValue < 0) {
+                continue;
+            }
+            //3.化验正常值在一个范围内,以“-”分割,最小值最大值返回都扩大10%,再与化验结果比较,在这范围之外,把该化验名称放入异常数据列表中
+            if (reference.contains("-")) {
+                try {
+                    String[] minMax = reference.split("-");
+                    min = Double.parseDouble(getNumber(minMax[0])) * 0.85;        //最小值范围缩小10%
+                    String maxNumber = getNumber(minMax[1]);
+                    if (StringUtil.isNotBlank(maxNumber)) {
+                        max = Double.parseDouble(maxNumber) * 1.15;    //最大值范围扩大10%
+                    }
+                    //化验结果在正常范围之外(返回扩大10%),把该化验名称放入异常数据列表中
+                    if (min >= 0 && max >= 0 && (resultValue < min || resultValue > max)) {
+                        abnormal.add(itemName);
+                        abnormalMap.put(itemName, itemDate);
+                    }
+                } catch (Exception e) {
+                    System.out.println("THR03070--3.出异常");
+                }
+            }
+            //4.化验正常值比某个值小,但该化验结果比该值大,把该化验名称放入异常数据列表中
+            else if (reference.contains("<")) {
+                try {
+                    String maxNumber = getNumber(reference);
+                    if (StringUtil.isNotBlank(maxNumber)) {
+                        max = Double.parseDouble(maxNumber);
+                    }
+                    if (resultValue > max) {
+                        abnormal.add(itemName);
+                        abnormalMap.put(itemName, itemDate);
+                    }
+                } catch (Exception e) {
+                    System.out.println("THR03070--4.出异常");
+                }
+            }
+            //5.化验正常值比某个值大,但该化验结果比该值小,把该化验名称放入异常数据列表中
+            else if (reference.contains(">")) {
+                try {
+                    String maxNumber = getNumber(reference);
+                    if (StringUtil.isNotBlank(maxNumber)) {
+                        min = Double.parseDouble(maxNumber);
+                    }
+                    if (resultValue < min) {
+                        abnormal.add(itemName);
+                        abnormalMap.put(itemName, itemDate);
+                    }
+                } catch (Exception e) {
+                    System.out.println("THR03070--5.出异常");
+                }
+            }
+        }
+
+        Map<String, Integer> abnormalCount = Maps.newHashMap();
+        abnormal = abnormal.stream().map(x -> {
+            if (x.contains("[")) {
+                x = x.substring(0, x.indexOf("["));
+            }
+            if (x.contains("(")) {
+                x = x.substring(0, x.indexOf("("));
+            }
+            x = x.replaceAll("[^\\u4e00-\\u9fa5]", "");
+            return x;
+        }).distinct().collect(Collectors.toList());
+        abnormal.forEach(i -> abnormalCount.put(i, 0));
+
+        List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+        for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+            Map<String, String> structureMap = doc.getStructureMap();
+            String content = CatalogueUtil.structureMapJoin(structureMap, Lists.newArrayList("体检", "病情记录"));
+            for (String lis : abnormal) {
+                if (content.contains(lis)) {
+                    abnormalCount.put(lis, abnormalCount.get(lis) + 1);
+                }
+            }
+        }
+
+        List<String> abnormalMiss = Lists.newArrayList();
+        for (Map.Entry<String, Integer> lis : abnormalCount.entrySet()) {
+            if (lis.getValue() == 0) {
+                abnormalMiss.add(lis.getKey());
+            }
+            if (abnormalMiss.size() > 0) {
+                infoAppend(sb, lis.getKey(), abnormalMap);
+            }
+        }
+//        if (abnormalMiss.size() > 0) {
+//            status.set("-1");
+//            info.set(abnormalMiss.toString().replaceAll("[\\[\\]]", ""));
+//        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set("化验:" + sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    public String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param repNm
+     * @param abnormalMap
+     */
+    private void infoAppend(StringBuffer sb, String repNm, Map<String, String> abnormalMap) {
+        for (Map.Entry<String, String> map : abnormalMap.entrySet()) {
+            if (repNm.equals(map.getKey())) {
+                sb.append(repNm).append("(").append(DateUtil.formatDateTime(StringUtil.parseDateTime(map.getValue()))).append(")").append("_");
+            }
+        }
+    }
+}

+ 143 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03071.java

@@ -0,0 +1,143 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.FirstCourseRecordDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.PacsDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author HUJING
+ * @create 2020-08-17 15:29
+ * @desc 异常检查未记录
+ **/
+@Component
+public class THR03071 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<PacsDoc> pacsDocs = inputInfo.getPacsDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        FirstCourseRecordDoc firstCourseRecordDoc = inputInfo.getFirstCourseRecordDoc();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (pacsDocs.size() == 0) {
+            return;
+        }
+        //辅检数据
+        StringBuffer sb = new StringBuffer();
+        Map<String, List<String>> pacsMap = Maps.newHashMap();
+        Map<String, Integer> pacsCount = Maps.newHashMap();
+        Map<String, Set<String>> pacsDate = Maps.newHashMap();
+        String splitRegex = "[;;]";
+        for (PacsDoc pacsDoc : pacsDocs) {
+            Map<String, String> structureMap = pacsDoc.getStructureMap();
+            String itemName = structureMap.get("报告名称");
+            String itemDiag = structureMap.get("检查结果诊断");
+            String itemDate = structureMap.get("报告创建时间");
+            if (StringUtil.isBlank(itemName) || StringUtil.isBlank(itemDiag)) {
+                continue;
+            }
+//            if (itemName.contains("检查")) {
+//                itemName = itemName.substring(0, itemName.indexOf("检查") + 2);
+//            }
+            String[] itemDiags = itemDiag.split(splitRegex);
+            List<String> itemDiagList = Lists.newArrayList(itemDiags);
+            Set<String> itemDateList = Sets.newHashSet(itemDate);
+            if (pacsMap.containsKey(itemName)) {
+                pacsMap.get(itemName).addAll(itemDiagList);
+                pacsDate.get(itemName).addAll(itemDateList);
+            } else {
+                pacsMap.put(itemName, itemDiagList);
+                pacsDate.put(itemName, itemDateList);
+            }
+        }
+        //检查项目对应数据初始化
+        pacsMap.keySet().stream().forEach(i -> pacsCount.put(i, 0));
+
+        if (threeLevelWardDocs.size() > 0) {
+            for (ThreeLevelWardDoc doc : threeLevelWardDocs.get(0).getAllDoctorWradDocs()) {
+                Map<String, String> structureMap = doc.getStructureMap();
+                String content = CatalogueUtil.structureMapJoin(structureMap, Lists.newArrayList("体检", "病情记录"));
+                findPacs(pacsMap, pacsCount, content);
+            }
+        }
+
+        if (firstCourseRecordDoc != null) {
+            Map<String, String> structureMap = firstCourseRecordDoc.getStructureMap();
+            String content = CatalogueUtil.structureMapJoin(structureMap, Lists.newArrayList("病例特点"));
+            findPacs(pacsMap, pacsCount, content);
+        }
+
+        if (leaveHospitalDoc != null) {
+            Map<String, String> structureMap = leaveHospitalDoc.getStructureMap();
+            String content = CatalogueUtil.structureMapJoin(structureMap, Lists.newArrayList("诊治经过"));
+            findPacs(pacsMap, pacsCount, content);
+        }
+
+        String reptNm = null;
+        List<String> pacsMiss = Lists.newArrayList();
+        for (Map.Entry<String, Integer> pacs : pacsCount.entrySet()) {
+            reptNm = pacs.getKey().split("=")[0];
+            if (pacs.getValue() == 0) {
+                pacsMiss.add(pacs.getKey());
+            }
+            if (pacsMiss.size() > 0) {
+                infoAppend(sb, reptNm, pacsDate);
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set("检查:" + sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 从文本中查找辅检对应的诊断信息,只要找到一个能对上,pacsCount对应辅检项目的数量就+1
+     *
+     * @param pacsMap   key:辅检项目  value:List[诊断信息]
+     * @param pacsCount key:辅检项目    value:匹配的数量
+     * @param content   文本
+     */
+    private void findPacs(Map<String, List<String>> pacsMap, Map<String, Integer> pacsCount, String content) {
+        for (Map.Entry<String, List<String>> map : pacsMap.entrySet()) {
+            for (String itemDiag : map.getValue()) {
+                if (content.contains(itemDiag)) {
+                    pacsCount.put(map.getKey(), pacsCount.get(map.getKey()) + 1);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param reptNm
+     * @param pacsDate
+     */
+    private void infoAppend(StringBuffer sb, String reptNm, Map<String, Set<String>> pacsDate) {
+        for (Map.Entry<String, Set<String>> map : pacsDate.entrySet()) {
+            if (reptNm.equals(map.getKey())) {
+                for (String date : map.getValue()) {
+                    String checkDate = DateUtil.formatDateTime(StringUtil.parseDateTime(date));
+                    sb.append(reptNm).append("(").append(checkDate).append(")").append("_");
+                }
+            }
+        }
+    }
+
+}

+ 300 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03072.java

@@ -0,0 +1,300 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-21 10:21
+ * @desc 抗生素减用原因不明确
+ **/
+@Component
+public class THR03072 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0) {
+            return;
+        }
+        //抗生素及开医嘱时间(包括减用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDate = Maps.newHashMap();
+        //抗生素减用集合   key:抗生素名    value:  0:未减用,1及以上:减用次数
+        Map<String, Integer> antibioticStatus = Maps.newHashMap();
+        //抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValue = Maps.newHashMap();
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("抗生素") && StringUtil.isNotBlank(x.get("医嘱单次剂量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+
+        String drugName = null, value = null, startDateStr = null;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("医嘱单次剂量");
+            startDateStr = structMap.get("医嘱开始时间");
+            drugName = removeBracket(drugName);
+            collectAntibioticInfo(antibioticDate, antibioticStatus, antibioticValue, drugName, value, startDateStr);
+        }
+
+        //把抗生素没减用过的抗生素删除
+        for (Map.Entry<String, Integer> as : antibioticStatus.entrySet()) {
+            if (as.getValue() == 0) {
+                antibioticDate.remove(as.getKey());
+                antibioticValue.remove(as.getKey());
+            }
+        }
+        //抗生素减用过的集合如果为空,则一个抗生素都没有减用过,直接返回0
+        if (antibioticDate.size() == 0) {
+            return;
+        }
+
+        //病程记录中抗生素及查房时间(包括减用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDateCourse = Maps.newHashMap();
+        //病程记录中抗生素减用集合   key:抗生素名    value:  0:未减用,1及以上:减用次数
+        Map<String, Integer> antibioticStatusCourse = Maps.newHashMap();
+        //病程记录中抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValueCourse = Maps.newHashMap();
+        String dateStr = null;
+        /*********************************************查房记录********************************************************/
+        if (threeLevelWardDocs.size() > 0) {
+            List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+            for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+                if (doc.getThreeLevelWardLabel().size() == 0) {
+                    continue;
+                }
+                dateStr = doc.getStructureMap().get("查房日期");
+                ThreeLevelWardLabel label = doc.getThreeLevelWardLabel().get(0);
+                List<Drug> drugs = label.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************术后首程********************************************************/
+        if (operationDocs.size() > 0) {
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null)
+                    .collect(Collectors.toList());
+            for (OperationDiscussionDoc discussionDoc : operationDiscussionDocs) {
+                dateStr = discussionDoc.getStructureMap().get("记录日期");
+                if (StringUtil.isBlank(dateStr)) {
+                    continue;
+                }
+                List<Drug> drugs = discussionDoc.getOperationDiscussionLabel().getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************出院小结********************************************************/
+        if (leaveHospitalDoc != null) {
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        //将病程日期排序
+        antibioticDateCourse.forEach((x, y) -> y.sort(Comparator.naturalOrder()));
+
+        /**
+         * 1.antibioticDate:从医嘱中取   key:抗生素名    value:医嘱中该抗生素所有减用时的时间(包括初始使用时间)
+         * 2.antibioticDateWard:从查房记录中取     key:抗生素名    value:查房记录中该抗生素所有减用时的查房时间(包括初始使用时间)
+         * 3.医嘱中该抗生素初始使用时间往后两天内,查房记录中该抗生素初始使用时间也在这两天内,则满足一半。
+         * 4.医嘱中该抗生素减用时的时间往后两天内,查房记录中该抗生素减用时间也在这两天内,则满足条件,该抗生素通过该条规则
+         * 5.继续判断下一个抗生素
+         * 6.若医嘱中减用过的抗生素,查房记录中没出现过,则该抗生素会报出来(存入miss,规则最后会把所有不符合的抗生素都报出来)
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null, start = null, change = null, wardStartStr = null, wardChangeStr = null;
+        List<String> dateList = null;
+        for (Map.Entry<String, List<String>> ad : antibioticDate.entrySet()) {
+            drugKey = ad.getKey();
+            List<Double> antibioticValueList = antibioticValue.get(drugKey);//医嘱中该药品对应的所有用量
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            /*if (!antibioticDateCourse.containsKey(drugKey)) {
+                for (String date : ad.getValue()) {
+                    infoAppend(sb, drugKey, date);
+                }
+                continue;
+            }*/
+            if (antibioticDateCourse.containsKey(drugKey)) {
+                List<Double> antibioticValueWardList = antibioticValueCourse.get(drugKey);//病程记录中该药品对应的所有用量
+                int findNum = 0;
+                for (int i = 1; i < antibioticValueList.size(); i++) {//从加用的值开始,如果加用过的值查房记录中都有,则不报该药
+                    if (antibioticValueWardList.contains(antibioticValueList.get(i))) {
+                        findNum++;
+                    }
+                }
+                if (findNum == antibioticValueList.size() - 1) {
+                    continue;
+                }
+                dateList = ad.getValue();
+                int matchNum = 0;
+                List<String> wardDateStr = antibioticDateCourse.get(drugKey);
+                for (int i = 0; i < dateList.size() - 1; i++) {
+                    start = dateList.get(i);        //抗生素开医嘱时间
+                    change = dateList.get(i + 1);   //抗生素用量改变时间
+                    Date adStart = StringUtil.parseDateTime(start);
+                    Date adChange = StringUtil.parseDateTime(change);
+                    for (int j = 0; j < wardDateStr.size() - 1; j++) {
+                        wardStartStr = wardDateStr.get(j);         //查房记录开抗生素时间
+                        wardChangeStr = wardDateStr.get(j + 1);    //查房记录改变抗生素用量时间
+                        Date wardStart = StringUtil.parseDateTime(wardStartStr);
+                        Date wardChange = StringUtil.parseDateTime(wardChangeStr);
+                        if (!CatalogueUtil.compareTime(adStart, wardStart, 48 * 60L) && !CatalogueUtil.compareTime(adChange, wardChange, 48 * 60L)) {
+                            matchNum++;
+                        }
+                    }
+                }
+                if (dateList.size() - 1 != matchNum) {
+                    infoAppend(sb, drugKey, change);
+                }
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set(sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 收集各模块药品信息
+     *
+     * @param antibioticDateWard   病程中抗生素使用所有时间
+     * @param antibioticStatusWard 病程中抗生素用量改变状态
+     * @param antibioticValueWard  病程中抗生素及用量
+     * @param dateStr              记录日期
+     * @param drugs                模型提取出的药品列表
+     */
+    private void getCourseDrugInfo(Map<String, List<String>> antibioticDateWard, Map<String, Integer> antibioticStatusWard,
+                                   Map<String, List<Double>> antibioticValueWard, String dateStr, List<Drug> drugs) {
+        for (Drug drug : drugs) {
+            String wardDrug = drug.getName();
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            //药品用量和使用原因都有时
+            if (drug.getConsumption() != null) {
+                //查房记录抗生素减用过的集合中没包含该抗生素,则认为该抗生素是第一次出现,此时不需要减用原因
+                if (!antibioticStatusWard.containsKey(wardDrug) || drug.getUsageWardRound() != null) {
+                    String consumption = drug.getConsumption().getName();
+                    collectAntibioticInfo(antibioticDateWard, antibioticStatusWard, antibioticValueWard, wardDrug, consumption, dateStr);
+                }
+            }
+        }
+    }
+
+    /**
+     * 收集抗生素各种信息
+     *
+     * @param antibioticDate   抗生素使用所有时间
+     * @param antibioticStatus 抗生素用量改变状态
+     * @param antibioticValue  抗生素及用量
+     * @param drugName         抗生素名称
+     * @param value            抗生素用量
+     * @param startDateStr     抗生素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo(Map<String, List<String>> antibioticDate, Map<String, Integer> antibioticStatus,
+                                       Map<String, List<Double>> antibioticValue, String drugName, String value, String startDateStr) {
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03075:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticValue.containsKey(drugName)) {
+            antibioticValue.put(drugName, Lists.newArrayList(v));
+            antibioticDate.put(drugName, Lists.newArrayList(startDateStr));
+            antibioticStatus.put(drugName, 0);
+        } else {
+            List<Double> beforeValue = antibioticValue.get(drugName);
+            if (beforeValue.get(beforeValue.size() - 1) > v) {
+                beforeValue.add(v);
+                antibioticValue.put(drugName, beforeValue);//添减该抗生素更小的值
+                antibioticStatus.put(drugName, antibioticStatus.get(drugName) + 1);
+                antibioticDate.get(drugName).add(startDateStr);
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date) {
+        sb.append(drugKey).append("(").append(DateUtil.formatDate(StringUtil.parseDateTime(date))).append(")").append(",");
+    }
+
+}

+ 296 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03074.java

@@ -0,0 +1,296 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-18 19:13
+ * @desc 加用抗生素未记录
+ **/
+@Component
+public class THR03074 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0) {
+            return;
+        }
+        //抗生素及开医嘱时间(包括加用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDate = Maps.newHashMap();
+        //抗生素加用集合   key:抗生素名    value:  0:未加用,1及以上:加用次数
+        Map<String, Integer> antibioticStatus = Maps.newHashMap();
+        //抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValue = Maps.newHashMap();
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("抗生素") && StringUtil.isNotBlank(x.get("医嘱单次剂量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+
+        String drugName = null, value = null, startDateStr = null;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("医嘱单次剂量");
+            startDateStr = structMap.get("医嘱开始时间");
+            drugName = removeBracket(drugName);
+            collectAntibioticInfo(antibioticDate, antibioticStatus, antibioticValue, drugName, value, startDateStr);
+        }
+
+        //把抗生素没加用过的抗生素删除
+        for (Map.Entry<String, Integer> as : antibioticStatus.entrySet()) {
+            if (as.getValue() == 0) {
+                antibioticDate.remove(as.getKey());
+                antibioticValue.remove(as.getKey());
+            }
+        }
+        //抗生素加用过的集合如果为空,则一个抗生素都没有加用过,直接返回0
+        if (antibioticDate.size() == 0) {
+            return;
+        }
+
+        //病程记录中抗生素及查房时间(包括加用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDateCourse = Maps.newHashMap();
+        //病程记录中抗生素加用集合   key:抗生素名    value:  0:未加用,1及以上:加用次数
+        Map<String, Integer> antibioticStatusCourse = Maps.newHashMap();
+        //病程记录中抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValueCourse = Maps.newHashMap();
+        String dateStr = null;
+        /*********************************************查房记录********************************************************/
+        if (threeLevelWardDocs.size() > 0) {
+            List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+            for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+                if (doc.getThreeLevelWardLabel().size() == 0) {
+                    continue;
+                }
+                dateStr = doc.getStructureMap().get("查房日期");
+                ThreeLevelWardLabel label = doc.getThreeLevelWardLabel().get(0);
+                List<Drug> drugs = label.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************术后首程********************************************************/
+        if (operationDocs.size() > 0) {
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null)
+                    .collect(Collectors.toList());
+            for (OperationDiscussionDoc discussionDoc : operationDiscussionDocs) {
+                dateStr = discussionDoc.getStructureMap().get("记录日期");
+                if (StringUtil.isBlank(dateStr)) {
+                    continue;
+                }
+                List<Drug> drugs = discussionDoc.getOperationDiscussionLabel().getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************出院小结********************************************************/
+        if (leaveHospitalDoc != null) {
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        //将病程日期排序
+        antibioticDateCourse.forEach((x, y) -> y.sort(Comparator.naturalOrder()));
+
+        /**
+         * 1.antibioticDate:从医嘱中取   key:抗生素名    value:医嘱中该抗生素所有加用时的时间(包括初始使用时间)
+         * 2.antibioticDateWard:从查房记录中取     key:抗生素名    value:查房记录中该抗生素所有加用时的查房时间(包括初始使用时间)
+         * 3.医嘱中该抗生素初始使用时间往后两天内,查房记录中该抗生素初始使用时间也在这两天内,则满足一半。
+         * 4.医嘱中该抗生素加用时的时间往后两天内,查房记录中该抗生素加用时间也在这两天内,则满足条件,该抗生素通过该条规则
+         * 5.继续判断下一个抗生素
+         * 6.若医嘱中加用过的抗生素,查房记录中没出现过,则该抗生素会报出来
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null, start = null, change = null, wardStartStr = null, wardChangeStr = null;
+        List<String> dateList = null;
+        for (Map.Entry<String, List<String>> ad : antibioticDate.entrySet()) {
+            drugKey = ad.getKey();
+            List<Double> antibioticValueList = antibioticValue.get(drugKey);//医嘱中该药品对应的所有用量
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            /*if (!antibioticDateCourse.containsKey(drugKey)) {
+                for (String date : ad.getValue()) {
+                    infoAppend(sb, drugKey, date);
+                }
+                continue;
+            }*/
+            if (antibioticDateCourse.containsKey(drugKey)) {
+                List<Double> antibioticValueWardList = antibioticValueCourse.get(drugKey);//病程记录中该药品对应的所有用量
+                int findNum = 0;
+                for (int i = 1; i < antibioticValueList.size(); i++) {//从加用的值开始,如果加用过的值查房记录中都有,则不报该药
+                    if (antibioticValueWardList.contains(antibioticValueList.get(i))) {
+                        findNum++;
+                    }
+                }
+                if (findNum == antibioticValueList.size() - 1) {
+                    continue;
+                }
+                dateList = ad.getValue();
+                int matchNum = 0;
+                List<String> wardDateStr = antibioticDateCourse.get(drugKey);
+                for (int i = 0; i < dateList.size() - 1; i++) {
+                    start = dateList.get(i);        //抗生素开医嘱时间
+                    change = dateList.get(i + 1);   //抗生素用量改变时间
+                    Date adStart = StringUtil.parseDateTime(start);
+                    Date adChange = StringUtil.parseDateTime(change);
+                    for (int j = 0; j < wardDateStr.size() - 1; j++) {
+                        wardStartStr = wardDateStr.get(j);         //查房记录开抗生素时间
+                        wardChangeStr = wardDateStr.get(j + 1);    //查房记录改变抗生素用量时间
+                        Date wardStart = StringUtil.parseDateTime(wardStartStr);
+                        Date wardChange = StringUtil.parseDateTime(wardChangeStr);
+                        if (!CatalogueUtil.compareTime(adStart, wardStart, 48 * 60L) && !CatalogueUtil.compareTime(adChange, wardChange, 48 * 60L)) {
+                            matchNum++;
+                        }
+                    }
+                }
+                if (dateList.size() - 1 != matchNum) {
+                    infoAppend(sb, drugKey, change);
+                }
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set(sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 收集各模块药品信息
+     *
+     * @param antibioticDateWard   病程中抗生素使用所有时间
+     * @param antibioticStatusWard 病程中抗生素用量改变状态
+     * @param antibioticValueWard  病程中抗生素及用量
+     * @param dateStr              记录日期
+     * @param drugs                模型提取出的药品列表
+     */
+    private void getCourseDrugInfo(Map<String, List<String>> antibioticDateWard, Map<String, Integer> antibioticStatusWard,
+                                   Map<String, List<Double>> antibioticValueWard, String dateStr, List<Drug> drugs) {
+        for (Drug drug : drugs) {
+            String wardDrug = drug.getName();
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            if (drug.getConsumption() != null) {
+                String consumption = drug.getConsumption().getName();
+                collectAntibioticInfo(antibioticDateWard, antibioticStatusWard, antibioticValueWard, wardDrug, consumption, dateStr);
+            }
+        }
+    }
+
+    /**
+     * 收集抗生素各种信息
+     *
+     * @param antibioticDate   抗生素使用所有时间
+     * @param antibioticStatus 抗生素用量改变状态
+     * @param antibioticValue  抗生素及用量
+     * @param drugName         抗生素名称
+     * @param value            抗生素用量
+     * @param startDateStr     抗生素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo(Map<String, List<String>> antibioticDate, Map<String, Integer> antibioticStatus,
+                                       Map<String, List<Double>> antibioticValue, String drugName, String value, String startDateStr) {
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03074:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticValue.containsKey(drugName)) {
+            antibioticValue.put(drugName, Lists.newArrayList(v));
+            antibioticDate.put(drugName, Lists.newArrayList(startDateStr));
+            antibioticStatus.put(drugName, 0);
+        } else {
+            List<Double> beforeValue = antibioticValue.get(drugName);
+            if (beforeValue.get(beforeValue.size() - 1) < v) {
+                beforeValue.add(v);
+                antibioticValue.put(drugName, beforeValue);//添加该抗生素更大的值
+                antibioticStatus.put(drugName, antibioticStatus.get(drugName) + 1);
+                antibioticDate.get(drugName).add(startDateStr);
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date) {
+        sb.append(drugKey).append("(").append(DateUtil.formatDate(StringUtil.parseDateTime(date))).append(")").append(",");
+    }
+
+}

+ 298 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03075.java

@@ -0,0 +1,298 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-18 19:13
+ * @desc 减用抗生素未记录
+ **/
+@Component
+public class THR03075 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0) {
+            return;
+        }
+        //抗生素及开医嘱时间(包括减用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDate = Maps.newHashMap();
+        //抗生素减用集合   key:抗生素名    value:  0:未减用,1及以上:减用次数
+        Map<String, Integer> antibioticStatus = Maps.newHashMap();
+        //抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValue = Maps.newHashMap();
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("抗生素") && StringUtil.isNotBlank(x.get("医嘱单次剂量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+
+        String drugName = null, value = null, startDateStr = null;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("医嘱单次剂量");
+            startDateStr = structMap.get("医嘱开始时间");
+            drugName = removeBracket(drugName);
+            collectAntibioticInfo(antibioticDate, antibioticStatus, antibioticValue, drugName, value, startDateStr);
+        }
+
+        //把抗生素没减用过的抗生素删除
+        for (Map.Entry<String, Integer> as : antibioticStatus.entrySet()) {
+            if (as.getValue() == 0) {
+                antibioticDate.remove(as.getKey());
+                antibioticValue.remove(as.getKey());
+            }
+        }
+        //抗生素减用过的集合如果为空,则一个抗生素都没有减用过,直接返回0
+        if (antibioticDate.size() == 0) {
+            return;
+        }
+
+        //病程记录中抗生素及查房时间(包括减用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDateCourse = Maps.newHashMap();
+        //病程记录中抗生素减用集合   key:抗生素名    value:  0:未减用,1及以上:减用次数
+        Map<String, Integer> antibioticStatusCourse = Maps.newHashMap();
+        //病程记录中抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValueCourse = Maps.newHashMap();
+        String dateStr = null;
+        /*********************************************查房记录********************************************************/
+        if (threeLevelWardDocs.size() > 0) {
+            List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+            for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+                if (doc.getThreeLevelWardLabel().size() == 0) {
+                    continue;
+                }
+                dateStr = doc.getStructureMap().get("查房日期");
+                ThreeLevelWardLabel label = doc.getThreeLevelWardLabel().get(0);
+                List<Drug> drugs = label.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************术后首程********************************************************/
+        if (operationDocs.size() > 0) {
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null)
+                    .collect(Collectors.toList());
+            for (OperationDiscussionDoc discussionDoc : operationDiscussionDocs) {
+                dateStr = discussionDoc.getStructureMap().get("记录日期");
+                if (StringUtil.isBlank(dateStr)) {
+                    continue;
+                }
+                List<Drug> drugs = discussionDoc.getOperationDiscussionLabel().getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        /*********************************************出院小结********************************************************/
+        if (leaveHospitalDoc != null) {
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, antibioticStatusCourse, antibioticValueCourse, dateStr, drugs);
+            }
+        }
+        //将病程日期排序
+        antibioticDateCourse.forEach((x, y) -> y.sort(Comparator.naturalOrder()));
+
+        /**
+         * 1.antibioticDate:从医嘱中取   key:抗生素名    value:医嘱中该抗生素所有减用时的时间(包括初始使用时间)
+         * 2.antibioticDateWard:从查房记录中取     key:抗生素名    value:查房记录中该抗生素所有减用时的查房时间(包括初始使用时间)
+         * 3.医嘱中该抗生素初始使用时间往后两天内,查房记录中该抗生素初始使用时间也在这两天内,则满足一半。
+         * 4.医嘱中该抗生素减用时的时间往后两天内,查房记录中该抗生素减用时间也在这两天内,则满足条件,该抗生素通过该条规则
+         * 5.继续判断下一个抗生素
+         * 6.若医嘱中减用过的抗生素,查房记录中没出现过,则该抗生素会报出来(存入miss,规则最后会把所有不符合的抗生素都报出来)
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null, start = null, change = null, wardStartStr = null, wardChangeStr = null;
+        List<String> dateList = null;
+        for (Map.Entry<String, List<String>> ad : antibioticDate.entrySet()) {
+            drugKey = ad.getKey();
+            List<Double> antibioticValueList = antibioticValue.get(drugKey);//医嘱中该药品对应的所有用量
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            /*if (!antibioticDateCourse.containsKey(drugKey)) {
+                for (String date : ad.getValue()) {
+                    infoAppend(sb, drugKey, date);
+                }
+                continue;
+            }*/
+            if (antibioticDateCourse.containsKey(drugKey)) {
+                List<Double> antibioticValueWardList = antibioticValueCourse.get(drugKey);//病程记录中该药品对应的所有用量
+                int findNum = 0;
+                for (int i = 1; i < antibioticValueList.size(); i++) {//从加用的值开始,如果加用过的值查房记录中都有,则不报该药
+                    if (antibioticValueWardList.contains(antibioticValueList.get(i))) {
+                        findNum++;
+                    }
+                }
+                if (findNum == antibioticValueList.size() - 1) {
+                    continue;
+                }
+                dateList = ad.getValue();
+                int matchNum = 0;
+                List<String> wardDateStr = antibioticDateCourse.get(drugKey);
+                for (int i = 0; i < dateList.size() - 1; i++) {
+                    start = dateList.get(i);        //抗生素开医嘱时间
+                    change = dateList.get(i + 1);   //抗生素用量改变时间
+                    Date adStart = StringUtil.parseDateTime(start);
+                    Date adChange = StringUtil.parseDateTime(change);
+                    for (int j = 0; j < wardDateStr.size() - 1; j++) {
+                        wardStartStr = wardDateStr.get(j);         //查房记录开抗生素时间
+                        wardChangeStr = wardDateStr.get(j + 1);    //查房记录改变抗生素用量时间
+                        Date wardStart = StringUtil.parseDateTime(wardStartStr);
+                        Date wardChange = StringUtil.parseDateTime(wardChangeStr);
+                        if (!CatalogueUtil.compareTime(adStart, wardStart, 48 * 60L) && !CatalogueUtil.compareTime(adChange, wardChange, 48 * 60L)) {
+                            matchNum++;
+                        }
+                    }
+                }
+                if (dateList.size() - 1 != matchNum) {
+                    infoAppend(sb, drugKey, change);
+                }
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set(sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 收集各模块药品信息
+     *
+     * @param antibioticDateWard   病程中抗生素使用所有时间
+     * @param antibioticStatusWard 病程中抗生素用量改变状态
+     * @param antibioticValueWard  病程中抗生素及用量
+     * @param dateStr              记录日期
+     * @param drugs                模型提取出的药品列表
+     */
+    private void getCourseDrugInfo
+    (Map<String, List<String>> antibioticDateWard, Map<String, Integer> antibioticStatusWard,
+     Map<String, List<Double>> antibioticValueWard, String dateStr, List<Drug> drugs) {
+        for (Drug drug : drugs) {
+            String wardDrug = drug.getName();
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            if (drug.getConsumption() != null) {
+                String consumption = drug.getConsumption().getName();
+                collectAntibioticInfo(antibioticDateWard, antibioticStatusWard, antibioticValueWard, wardDrug, consumption, dateStr);
+            }
+        }
+    }
+
+    /**
+     * 收集抗生素各种信息
+     *
+     * @param antibioticDate   抗生素使用所有时间
+     * @param antibioticStatus 抗生素用量改变状态
+     * @param antibioticValue  抗生素及用量
+     * @param drugName         抗生素名称
+     * @param value            抗生素用量
+     * @param startDateStr     抗生素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo
+    (Map<String, List<String>> antibioticDate, Map<String, Integer> antibioticStatus,
+     Map<String, List<Double>> antibioticValue, String drugName, String value, String startDateStr) {
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03075:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticValue.containsKey(drugName)) {
+            antibioticValue.put(drugName, Lists.newArrayList(v));
+            antibioticDate.put(drugName, Lists.newArrayList(startDateStr));
+            antibioticStatus.put(drugName, 0);
+        } else {
+            List<Double> beforeValue = antibioticValue.get(drugName);
+            if (beforeValue.get(beforeValue.size() - 1) > v) {
+                beforeValue.add(v);
+                antibioticValue.put(drugName, beforeValue);//添减该抗生素更小的值
+                antibioticStatus.put(drugName, antibioticStatus.get(drugName) + 1);
+                antibioticDate.get(drugName).add(startDateStr);
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date) {
+        sb.append(drugKey).append("(").append(DateUtil.formatDate(StringUtil.parseDateTime(date))).append(")").append(",");
+    }
+
+}

+ 329 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03076.java

@@ -0,0 +1,329 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.FirstCourseRecordDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.consultation.ConsultationDoc;
+import com.lantone.qc.pub.model.doc.consultation.ConsultationResultsDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationRecordDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.DrugLabel;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-26 10:51
+ * @desc 医嘱与病程记录抗生素剂量不一致
+ **/
+@Component
+public class THR03076 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        FirstCourseRecordDoc firstCourseRecordDoc = inputInfo.getFirstCourseRecordDoc();
+        List<ConsultationDoc> consultationDocs = inputInfo.getConsultationDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0 || threeLevelWardDocs.size() == 0) {
+            return;
+        }
+
+        Map<Date, String> extData = null;
+        if (outputInfo.getResult().get("THR02985") != null) {
+            extData = (Map<Date, String>) outputInfo.getResult().get("THR02985").get("extData");
+        }
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("抗生素") && StringUtil.isNotBlank(x.get("一次使用数量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+        docAdvStruct.removeIf(x -> StringUtil.isNotBlank(x.get("给药方式")) && !filterKey.contains(x.get("给药方式")));
+
+        //抗生素及开医嘱时间 <抗生素名,<抗生素用量,[抗生素使用时间...]>>
+        Map<String, Map<String, List<Double>>> antibioticInfo = Maps.newLinkedHashMap();
+        Map<String, Map<Date, Integer>> antibioticDateTimes = Maps.newHashMap();
+        //记录同一抗生素同一天内是否开过多次,用于医嘱中需要处理的抗生素过滤(一天内同一抗生素开过多次的抗生素直接过滤)
+        getAntibioticTimes(docAdvStruct, antibioticDateTimes);
+        String drugName = null, value = null, startDateStr = null;
+        Date startDate = null;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("一次使用数量");
+            startDateStr = structMap.get("医嘱开始时间");
+//            startDate = DateUtil.dateZeroClear(StringUtil.parseDateTime(startDateStr));
+            startDate = StringUtil.parseDateTime(startDateStr);
+            if (extData != null && extData.containsKey(startDate) && extData.get(startDate).equals(drugName)) {
+                continue;   //THR02985  医嘱有抗生素使用病程无记录,规则中没报未记录的抗生素继续走这条规则,报未记录的抗生素过滤
+            }
+            if (antibioticDateTimes.get(drugName).get(startDate) > 0) {
+                continue;   //一天内同一抗生素开过多次的抗生素直接过滤
+            }
+            collectAntibioticInfo(antibioticInfo, drugName, value, startDateStr);
+        }
+
+        //抗生素及开医嘱时间 <抗生素名,<抗生素用量,[抗生素使用时间...]>>
+        Map<String, Map<String, List<Double>>> antibioticWardInfo = Maps.newLinkedHashMap();
+        String dateStr = null;
+        /*********************************************首程治疗计划********************************************************/
+        if (firstCourseRecordDoc != null) {
+            DrugLabel drugLabel = firstCourseRecordDoc.getDrugLabel();
+            dateStr = firstCourseRecordDoc.getStructureMap().get("记录时间");
+            if (drugLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = drugLabel.getDrugs();
+                getCourseDrugInfo(antibioticWardInfo, drugs, dateStr);
+            }
+        }
+        /*********************************************查房记录********************************************************/
+        if (threeLevelWardDocs.size() > 0) {
+            List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+            for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+                if (doc.getThreeLevelWardLabel().size() == 0) {
+                    continue;
+                }
+                dateStr = doc.getStructureMap().get("查房日期");
+                ThreeLevelWardLabel label = doc.getThreeLevelWardLabel().get(0);
+                List<Drug> drugs = label.getDrugs();
+                getCourseDrugInfo(antibioticWardInfo, drugs, dateStr);
+            }
+        }
+        /**********************************************手术记录、术后首程************************************************/
+        if (operationDocs.size() > 0) {
+            //手术记录
+            List<OperationRecordDoc> operationRecordDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationRecordDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationRecordLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("病历日期")))
+                    .collect(Collectors.toList());
+            operationRecordDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getOperationRecordLabel().getDrugs(), x.getStructureMap().get("病历日期")));
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("记录日期")))
+                    .collect(Collectors.toList());
+            operationDiscussionDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getOperationDiscussionLabel().getDrugs(), x.getStructureMap().get("记录日期")));
+        }
+        /*********************************************会诊结果单********************************************************/
+        /*if (consultationDocs.size() > 0) {
+            List<ConsultationResultsDoc> consultationResultsDocs = consultationDocs
+                    .stream()
+                    .map(ConsultationDoc::getConsultationResultsDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getConsultationResultLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("会诊日期及时间")))
+                    .collect(Collectors.toList());
+            consultationResultsDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getConsultationResultLabel().getDrugs(), x.getStructureMap().get("会诊日期及时间")));
+        }*/
+        /*********************************************出院小结********************************************************/
+        if (leaveHospitalDoc != null) {
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticWardInfo, drugs, dateStr);
+            }
+        }
+
+        /**
+         * 1.医嘱中开了抗生素,查房记录中没有该抗生素,则医嘱中该抗生素出现过的所有时间都会提示出来
+         * 2.医嘱中开了抗生素,查房记录中有该抗生素:
+         *      2.1 医嘱中该抗生素某使用量(如50),查房记录中没有该使用量(如只有100),则医嘱中该抗生素使用量出现过的所有时间都会提示出来
+         *      2.2 医嘱中该抗生素某使用量(如50),查房记录中也有该使用量,对比这两个时间,若医嘱时间两天内的查房记录没有该使用量,则该医嘱时间会提示出来
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null;
+        for (Map.Entry<String, Map<String, List<Double>>> ai : antibioticInfo.entrySet()) {
+            drugKey = ai.getKey();
+            drugKey = removeBracket(drugKey).replaceAll("[^\u4e00-\u9fa5]", "");
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            if (antibioticWardInfo.containsKey(drugKey)) {
+                Map<String, List<Double>> adDateValue = ai.getValue();
+                Map<String, List<Double>> wardDateValue = antibioticWardInfo.get(drugKey);
+                for (Map.Entry<String, List<Double>> adMap : adDateValue.entrySet()) {
+                    String adDateStr = adMap.getKey();
+                    Date adDate = StringUtil.parseDateTime(adDateStr);
+                    List<Double> adUsage = adMap.getValue();
+                    for (Map.Entry<String, List<Double>> wdvMap : wardDateValue.entrySet()) {
+                        String wardDateStr = wdvMap.getKey();
+                        Date wardDate = StringUtil.parseDateTime(wardDateStr);
+                        List<Double> wardUsage = wdvMap.getValue();
+                        if ((adDate.before(wardDate) && !CatalogueUtil.compareTime(adDate, wardDate, 48 * 60L))
+                                || (wardDate.before(adDate) && !CatalogueUtil.compareTime(wardDate, adDate, 24 * 60L))) {
+                            wardUsage.removeAll(adUsage);//比如adUsage有1.0、2.0,wardUsage中有2.0、3.0,removeAll之后wardUsage只剩3.0
+                            adDateStr = DateUtil.formatDateTime(adDate);
+                            if (wardUsage.size() > 0 && !sb.toString().contains(drugKey + "(" + adDateStr + ")")) {
+                                infoAppend(sb, ai.getKey(), adDateStr);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set("医嘱:" + sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 记录同一抗生素同一天内是否开过多次,用于医嘱中需要处理的抗生素过滤(一天内同一抗生素开过多次的抗生素直接过滤)
+     *
+     * @param docAdvStruct
+     * @param antibioticDateTimes
+     */
+    private void getAntibioticTimes(List<Map<String, String>> docAdvStruct, Map<String, Map<Date, Integer>> antibioticDateTimes) {
+        String drugName;
+        String startDateStr;
+        Date startDate;
+        Map<Date, Integer> antibioticDateTime;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            startDateStr = structMap.get("医嘱开始时间");
+//            startDate = DateUtil.dateZeroClear(StringUtil.parseDateTime(startDateStr));
+            startDate = StringUtil.parseDateTime(startDateStr);
+            if (antibioticDateTimes.containsKey(drugName)) {
+                Map<Date, Integer> map = antibioticDateTimes.get(drugName);
+                if (map.containsKey(startDate)) {
+                    map.put(startDate, map.get(startDate) + 1);
+                } else {
+                    map.put(startDate, 0);
+                }
+            } else {
+                antibioticDateTime = Maps.newHashMap();
+                antibioticDateTime.put(startDate, 0);
+                antibioticDateTimes.put(drugName, antibioticDateTime);
+            }
+        }
+    }
+
+    private void getCourseDrugInfo(Map<String, Map<String, List<Double>>> antibioticWardInfo, List<Drug> drugs, String dateStr) {
+        for (Drug drug : drugs) {
+            String wardDrug = drug.getName();
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            if (drug.getConsumption() != null) {
+                String consumption = drug.getConsumption().getName();
+                collectAntibioticInfo(antibioticWardInfo, wardDrug, consumption, dateStr);
+            }
+        }
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date) {
+        sb.append(drugKey).append("(").append(date).append(")").append("_");
+    }
+
+    /**
+     * 收集抗生素各种信息
+     *
+     * @param antibioticInfo 抗生素使用量及所有时间
+     * @param drugName       抗生素名称
+     * @param value          抗生素用量
+     * @param startDateStr   抗生素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo(Map<String, Map<String, List<Double>>> antibioticInfo, String drugName, String value, String startDateStr) {
+        Map<String, List<Double>> antibioticValueList = null;
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03076:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticInfo.containsKey(drugName)) {
+            //存该抗生素使用第1个值
+            antibioticValueList = Maps.newLinkedHashMap();
+            antibioticValueList.put(startDateStr, Lists.newArrayList(v));
+            antibioticInfo.put(drugName, antibioticValueList);
+        } else {
+            antibioticValueList = antibioticInfo.get(drugName);
+            //存该抗生素使用时间时第n个量
+            if (antibioticValueList.containsKey(startDateStr)) {
+                antibioticValueList.get(startDateStr).add(v);
+            } else {
+                //存该抗生素使用时间时第1个量
+                antibioticValueList.put(startDateStr, Lists.newArrayList(v));
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    private static final List<String> filterKey = Lists.newArrayList("ACF", "ID", "IG", "IM", "IP", "IV",
+            "关节腔注射", "宫颈注射", "皮下注射", "皮下注射(儿童)", "皮下注射(免费)", "皮下注射(成人)", "皮内", "皮内注射",
+            "结膜下注射", "肌注", "肌肉注射(儿童)", "肌肉注射(公卫专用)", "肌肉注射(成人)", "胸腔注射", "腹腔内注射", "腹腔注射",
+            "静滴(儿童)", "静滴(成人)", "静脉注射", "静脉注射(儿童)", "静脉注射(免费)", "静脉注射(成人)", "静脉注射(泵)",
+            "静脉滴注", "静脉滴注(泵)", "鞘内注射");
+
+}

+ 502 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03077.java

@@ -0,0 +1,502 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.FirstCourseRecordDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.consultation.ConsultationDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationRecordDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.DrugLabel;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-08-28 14:10
+ * @desc 病程中抗生素记录不规范
+ **/
+@Component
+public class THR03077 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        FirstCourseRecordDoc firstCourseRecordDoc = inputInfo.getFirstCourseRecordDoc();
+        List<ConsultationDoc> consultationDocs = inputInfo.getConsultationDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0) {
+            return;
+        }
+        Map<Date, String> extData = null;
+        if (outputInfo.getResult().get("THR02985") != null) {
+            extData = (Map<Date, String>) outputInfo.getResult().get("THR02985").get("extData");
+        }
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("抗生素") && StringUtil.isNotBlank(x.get("医嘱单次剂量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+        docAdvStruct.removeIf(x -> StringUtil.isNotBlank(x.get("给药方式")) && !filterKey.contains(x.get("给药方式")));
+
+        //抗生素及开医嘱时间(包括加用过抗生素的时间)     key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDate = Maps.newHashMap();
+        //抗生素加用集合   key:抗生素名    value:  0:未加用,1及以上:加用次数
+        Map<String, Integer> antibioticStatus = Maps.newHashMap();
+        //抗生素及各初始剂量     key:抗生素名    value:抗生素第一次使用时剂量
+        Map<String, List<Double>> antibioticValue = Maps.newHashMap();
+        //记录同一天内是否开过多次同一抗生素
+        Map<String, Map<Date, Integer>> antibioticDateTimes = Maps.newHashMap();
+        String drugName = null, value = null, startDateStr = null;
+        Date startDate = null;
+        getAntibioticTimes(docAdvStruct, antibioticDateTimes);
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("医嘱单次剂量");
+            startDateStr = structMap.get("医嘱开始时间");
+            startDate = DateUtil.dateZeroClear(StringUtil.parseDateTime(startDateStr));
+            if (StringUtil.isNotBlank(drugName)) {
+                if (antibioticDateTimes.get(drugName).get(startDate) > 0) {
+                    continue;   //一天内同一抗生素开过多次的抗生素直接过滤
+                }
+                drugName = removeBracket(drugName).replaceAll("[^\u4e00-\u9fa5]", "");
+                String drugStandardWord = similarityUtil.getDrugStandardWord(drugName);
+                if (StringUtil.isNotBlank(drugStandardWord)) {
+                    drugName = drugStandardWord;
+                }
+
+                if (extData != null && extData.containsKey(startDate) && extData.get(startDate).equals(drugName)) {
+                    continue;   //THR02985  医嘱有抗生素使用病程无记录,规则中没报未记录的抗生素继续走这条规则,报未记录的抗生素过滤
+                }
+
+                if (Arrays.asList(KSS).contains(drugName)) {
+                    collectAntibioticInfo(antibioticDate, antibioticStatus, antibioticValue, drugName, value, startDateStr);
+                }
+            }
+        }
+
+        //把抗生素使用剂量没变化过的抗生素删除
+        antibioticStatus.forEach((x, y) -> {
+            if (y == 0) {
+                antibioticDate.remove(x);
+                antibioticValue.remove(x);
+            }
+        });
+        //把同一天内同一个抗生素开过多次的抗生素删除
+//        antibioticDateTimes.forEach((x, y) -> {
+//            if (y > 0) {
+//                antibioticDate.remove(x);
+//                antibioticValue.remove(x);
+//            }
+//        });
+        //抗生素加用过的集合如果为空,则一个抗生素都没有加用过,直接返回0
+        if (antibioticDate.size() == 0) {
+            return;
+        }
+
+        //病程记录中没有用量的抗生素及查房时间       key:抗生素名    "2020-08-20,2020-08-21 ..."
+        Map<String, List<String>> antibioticDateCourse = Maps.newHashMap();
+        String dateStr = null;
+        /*********************************************首程治疗计划********************************************************/
+        if (firstCourseRecordDoc != null) {
+            DrugLabel drugLabel = firstCourseRecordDoc.getDrugLabel();
+            dateStr = firstCourseRecordDoc.getStructureMap().get("记录时间");
+            if (drugLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = drugLabel.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, dateStr, drugs,
+                        CatalogueUtil.structureMapJoin(firstCourseRecordDoc.getStructureMap(), Lists.newArrayList("治疗计划")));
+            }
+        }
+        /*********************************************查房记录********************************************************/
+        if (threeLevelWardDocs.size() > 0) {
+            List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+            for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+                if (doc.getThreeLevelWardLabel().size() == 0) {
+                    continue;
+                }
+                dateStr = doc.getStructureMap().get("查房日期");
+                ThreeLevelWardLabel label = doc.getThreeLevelWardLabel().get(0);
+                List<Drug> drugs = label.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, dateStr, drugs,
+                        CatalogueUtil.structureMapJoin(doc.getStructureMap(), Lists.newArrayList("病情记录", "治疗计划和措施")));
+            }
+        }
+        /*********************************************手术记录、术后首程************************************************/
+        if (operationDocs.size() > 0) {
+            //手术记录
+            List<OperationRecordDoc> operationRecordDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationRecordDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationRecordLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("病历日期")))
+                    .collect(Collectors.toList());
+            operationRecordDocs.forEach(x -> getCourseDrugInfo(antibioticDateCourse, x.getStructureMap().get("病历日期"), x.getOperationRecordLabel().getDrugs()
+                    , CatalogueUtil.structureMapJoin(x.getStructureMap(), Lists.newArrayList("手术经过"))));
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("记录日期")))
+                    .collect(Collectors.toList());
+            operationDiscussionDocs.forEach(x -> getCourseDrugInfo(antibioticDateCourse, x.getStructureMap().get("记录日期"), x.getOperationDiscussionLabel().getDrugs()
+                    , CatalogueUtil.structureMapJoin(x.getStructureMap(), Lists.newArrayList("手术经过", "治疗计划和措施"))));
+        }
+        /*********************************************会诊结果单********************************************************/
+        /*if (consultationDocs.size() > 0) {
+            List<ConsultationResultsDoc> consultationResultsDocs = consultationDocs
+                    .stream()
+                    .map(ConsultationDoc::getConsultationResultsDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getConsultationResultLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("会诊日期及时间")))
+                    .collect(Collectors.toList());
+            consultationResultsDocs.forEach(x -> getCourseDrugInfo(antibioticDateCourse, x.getStructureMap().get("会诊日期及时间"), x.getConsultationResultLabel().getDrugs()
+                    , CatalogueUtil.structureMapJoin(x.getStructureMap(), Lists.newArrayList("会诊意见"))));
+        }*/
+        /*********************************************出院小结********************************************************/
+        if (leaveHospitalDoc != null) {
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticDateCourse, dateStr, drugs
+                        , CatalogueUtil.structureMapJoin(leaveHospitalDoc.getStructureMap(), Lists.newArrayList("诊治经过", "出院带药")));
+            }
+        }
+        //将病程日期排序
+        antibioticDateCourse.forEach((x, y) -> y.sort(Comparator.naturalOrder()));
+
+        /**
+         * 1.antibioticDate:从医嘱中取   key:抗生素名    value:医嘱中该抗生素所有剂量变化的时间(包括初始使用时间)
+         * 2.antibioticDateWard:从查房记录中取     key:抗生素名    value:病程记录中该抗生素所有没有用量时的查房时间(包括初始使用时间)
+         * 3.医嘱中该抗生素初始使用时间往后两天内,查房记录中出现该抗生素并且该抗生素没有用量,报出该抗生素
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null, start = null, change = null, wardStartStr = null, wardChangeStr = null;
+        List<String> dateList = null;
+        for (Map.Entry<String, List<String>> ad : antibioticDate.entrySet()) {
+            drugKey = ad.getKey();
+            drugKey = removeBracket(drugKey).replaceAll("[^\u4e00-\u9fa5]", "");
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            if (antibioticDateCourse.containsKey(drugKey)) {
+                dateList = ad.getValue();
+                List<String> wardDateStr = antibioticDateCourse.get(drugKey);
+                for (int i = 0; i < dateList.size(); i++) {
+                    start = dateList.get(i);        //抗生素开医嘱时间
+                    Date adStart = DateUtil.dateZeroClear(StringUtil.parseDateTime(start));
+                    for (int j = 0; j < wardDateStr.size(); j++) {
+                        wardStartStr = wardDateStr.get(j);         //查房记录开抗生素时间
+                        wardStartStr = wardStartStr.split("=")[0];
+                        Date wardStart = StringUtil.parseDateTime(wardStartStr);
+                        if ((adStart.before(wardStart) && !CatalogueUtil.compareTime(adStart, wardStart, 48 * 60L))
+                                || (wardStart.before(adStart) && !CatalogueUtil.compareTime(wardStart, adStart, 24 * 60L))) {
+                            infoAppend(sb, ad.getKey(), start, wardDateStr.get(j).split("=")[1]);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set("医嘱:" + sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 记录同一抗生素同一天内是否开过多次,用于医嘱中需要处理的抗生素过滤(一天内同一抗生素开过多次的抗生素直接过滤)
+     *
+     * @param docAdvStruct
+     * @param antibioticDateTimes
+     */
+    private void getAntibioticTimes(List<Map<String, String>> docAdvStruct, Map<String, Map<Date, Integer>> antibioticDateTimes) {
+        String drugName;
+        String startDateStr;
+        Date startDate;
+        Map<Date, Integer> antibioticDateTime;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            startDateStr = structMap.get("医嘱开始时间");
+            startDate = DateUtil.dateZeroClear(StringUtil.parseDateTime(startDateStr));
+            if (antibioticDateTimes.containsKey(drugName)) {
+                Map<Date, Integer> map = antibioticDateTimes.get(drugName);
+                if (map.containsKey(startDate)) {
+                    map.put(startDate, map.get(startDate) + 1);
+                } else {
+                    map.put(startDate, 0);
+                }
+            } else {
+                antibioticDateTime = Maps.newHashMap();
+                antibioticDateTime.put(startDate, 0);
+                antibioticDateTimes.put(drugName, antibioticDateTime);
+            }
+        }
+    }
+
+    /**
+     * 收集各模块药品信息
+     *
+     * @param antibioticDateWard 病程中没有用量+用法+频率的抗生素使用所有时间
+     * @param dateStr            记录日期
+     * @param drugs              模型提取出的药品列表
+     */
+    private void getCourseDrugInfo(Map<String, List<String>> antibioticDateWard, String dateStr, List<Drug> drugs, String content) {
+        StringBuffer sb = null;
+        for (Drug drug : drugs) {
+            sb = new StringBuffer();
+            String wardDrug = drug.getName();
+            int position = content.indexOf(wardDrug);
+            int lastPosition = content.lastIndexOf(wardDrug);
+            if (position != -1 && lastPosition != -1) {
+                String keyword = content.substring(Math.max(0, position - 10), position);
+                String lastKeyword = content.substring(Math.max(0, lastPosition - 10), lastPosition);
+                if (position != lastPosition) {
+                    if (lastKeyword.contains("继续") || lastKeyword.contains("停")) {
+                        continue;
+                    }
+                }
+                if (keyword.contains("继续") || keyword.contains("停")) {
+                    continue;
+                }
+            }
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            if (drug.getConsumption() == null) {
+                concatInfo(dateStr, sb, "用量");
+            }
+            if (drug.getUsageWardRound() == null) {
+                concatInfo(dateStr, sb, "用法");
+            }
+            if (drug.getFrequency() == null) {
+                concatInfo(dateStr, sb, "频率");
+            }
+            if (sb.toString().length() > 0) {
+                if (antibioticDateWard.containsKey(wardDrug)) {
+                    antibioticDateWard.get(wardDrug).add(sb.toString());
+                } else {
+                    antibioticDateWard.put(wardDrug, Lists.newArrayList(sb.toString()));
+                }
+            }
+        }
+    }
+
+    /**
+     * 拼接抗生素缺失信息
+     *
+     * @param dateStr
+     * @param sb
+     */
+    private void concatInfo(String dateStr, StringBuffer sb, String missType) {
+        if (sb.toString().contains("=")) {
+            sb.append(",").append(missType);
+        } else {
+            sb.append(dateStr).append("=").append(missType);
+        }
+    }
+
+    /**
+     * 收集抗生素各种信息
+     *
+     * @param antibioticDate   抗生素使用所有时间
+     * @param antibioticStatus 抗生素用量改变状态
+     * @param antibioticValue  抗生素及用量
+     * @param drugName         抗生素名称
+     * @param value            抗生素用量
+     * @param startDateStr     抗生素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo(Map<String, List<String>> antibioticDate, Map<String, Integer> antibioticStatus,
+                                       Map<String, List<Double>> antibioticValue, String drugName, String value, String startDateStr) {
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03077:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticValue.containsKey(drugName)) {
+            antibioticValue.put(drugName, Lists.newArrayList(v));
+            antibioticDate.put(drugName, Lists.newArrayList(startDateStr));
+            antibioticStatus.put(drugName, 0);
+        } else {
+            //1.如果抗生素剂量有变化,则记录该抗生素开始时间
+            List<Double> beforeValue = antibioticValue.get(drugName);
+            if (beforeValue.get(beforeValue.size() - 1) != v) {
+                beforeValue.add(v);
+                antibioticValue.put(drugName, beforeValue);//添加该抗生素更大的值
+                antibioticStatus.put(drugName, antibioticStatus.get(drugName) + 1);
+                antibioticDate.get(drugName).add(startDateStr);
+                return;
+            }
+            //2.如果抗生素剂量两次开启的时间间隔相差3天,也记录该抗生素开始时间
+            List<String> currentAntibioticDate = antibioticDate.get(drugName);
+            if (currentAntibioticDate.size() > 0) {
+                String lastDate = currentAntibioticDate.get(currentAntibioticDate.size() - 1);
+                if (CatalogueUtil.compareTime(StringUtil.parseDateTime(lastDate), StringUtil.parseDateTime(startDateStr), 72 * 60L)) {
+                    beforeValue.add(v);
+                    antibioticValue.put(drugName, beforeValue);//添加该抗生素值
+                    antibioticStatus.put(drugName, antibioticStatus.get(drugName) + 1);
+                    antibioticDate.get(drugName).add(startDateStr);
+                }
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date, String missType) {
+        sb.append(drugKey).append("(").append(DateUtil.formatDate(StringUtil.parseDateTime(date)))
+                .append(",").append(missType).append(")").append("、");
+    }
+
+    private static final String[] KSS = {
+            "万古霉素",
+            "两性霉素B",
+            "亚胺培南西司他丁",
+            "伊曲康唑",
+            "伏立康唑",
+            "依替米星氯化钠",
+            "克拉霉素",
+            "克林霉素",
+            "利奈唑胺",
+            "利奈唑胺葡萄糖",
+            "利福昔明",
+            "制霉菌素",
+            "卡泊芬净",
+            "厄他培南",
+            "吗啉硝唑氯化钠",
+            "呋喃唑酮",
+            "哌拉西林他唑巴坦",
+            "复方磺胺甲噁唑",
+            "多粘菌素B",
+            "多西环素",
+            "夫西地酸",
+            "头孢丙烯",
+            "头孢他啶",
+            "头孢他啶阿维巴坦",
+            "头孢他美酯",
+            "头孢克洛",
+            "头孢克肟",
+            "头孢吡肟",
+            "头孢呋辛",
+            "头孢哌酮舒巴坦",
+            "头孢唑林",
+            "头孢噻肟",
+            "头孢地嗪",
+            "头孢地尼",
+            "头孢拉定",
+            "头孢曲松",
+            "头孢替安",
+            "头孢美唑",
+            "头孢西丁",
+            "奥硝唑",
+            "妥布霉素",
+            "妥布霉素地塞米松",
+            "左氧氟沙星",
+            "左氧氟沙星氯化钠",
+            "庆大霉素",
+            "异帕米星",
+            "拉氧头孢",
+            "替加环素",
+            "替硝唑",
+            "替考拉宁",
+            "比阿培南",
+            "氟康唑",
+            "氟康唑氯化钠",
+            "氟胞嘧啶",
+            "氨曲南",
+            "氨苄西林",
+            "泊沙康唑",
+            "特比萘芬",
+            "甲硝唑",
+            "甲硝唑氯化钠",
+            "磷霉素",
+            "磷霉素氨丁三醇",
+            "米卡芬净",
+            "米诺环素",
+            "红霉素",
+            "美罗培南",
+            "苄星青霉素",
+            "莫西沙星",
+            "莫西沙星氯化钠",
+            "达托霉素",
+            "阿奇霉素",
+            "阿奇霉素枸橼酸二氢钠",
+            "阿洛西林",
+            "阿米卡星",
+            "阿莫西林",
+            "阿莫西林克拉维酸",
+            "青霉素"
+    };
+
+    private static final List<String> filterKey = Lists.newArrayList("ACF", "ID", "IG", "IM", "IP", "IV",
+            "关节腔注射", "宫颈注射", "皮下注射", "皮下注射(儿童)", "皮下注射(免费)", "皮下注射(成人)", "皮内", "皮内注射",
+            "结膜下注射", "肌注", "肌肉注射(儿童)", "肌肉注射(公卫专用)", "肌肉注射(成人)", "胸腔注射", "腹腔内注射", "腹腔注射",
+            "静滴(儿童)", "静滴(成人)", "静脉注射", "静脉注射(儿童)", "静脉注射(免费)", "静脉注射(成人)", "静脉注射(泵)",
+            "静脉滴注", "静脉滴注(泵)", "鞘内注射");
+
+}

+ 428 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03079.java

@@ -0,0 +1,428 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.kernel.util.SimilarityUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
+import com.lantone.qc.pub.model.doc.FirstCourseRecordDoc;
+import com.lantone.qc.pub.model.doc.LeaveHospitalDoc;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.model.doc.consultation.ConsultationDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDiscussionDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationDoc;
+import com.lantone.qc.pub.model.doc.operation.OperationRecordDoc;
+import com.lantone.qc.pub.model.entity.Drug;
+import com.lantone.qc.pub.model.label.DrugLabel;
+import com.lantone.qc.pub.model.label.LeaveHospitalLabel;
+import com.lantone.qc.pub.model.label.ThreeLevelWardLabel;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @author HUJING
+ * @create 2020-09-18 14:23
+ * @desc 病程记录激素剂量与医嘱不一致
+ **/
+@Component
+public class THR03079 extends QCCatalogue {
+    @Autowired
+    SimilarityUtil similarityUtil;
+
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        FirstCourseRecordDoc firstCourseRecordDoc = inputInfo.getFirstCourseRecordDoc();
+        List<ConsultationDoc> consultationDocs = inputInfo.getConsultationDocs();
+        List<OperationDoc> operationDocs = inputInfo.getOperationDocs();
+        LeaveHospitalDoc leaveHospitalDoc = inputInfo.getLeaveHospitalDoc();
+        if (doctorAdviceDocs.size() == 0 || threeLevelWardDocs.size() == 0) {
+            return;
+        }
+        List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+        if (allDoctorWradDocs.size() == 0) {
+            return;
+        }
+        Map<Date, String> extData = null;
+        if (outputInfo.getResult().get("THR02986") != null) {
+            extData = (Map<Date, String>) outputInfo.getResult().get("THR02986").get("extData");
+        }
+
+        List<Map<String, String>> docAdvStruct = doctorAdviceDocs
+                .stream()
+                .filter(Objects::nonNull)
+                .map(DoctorAdviceDoc::getStructureMap)
+                .filter(x -> StringUtil.isNotBlank(x.get("药品类型")) && x.get("药品类型").contains("激素") && StringUtil.isNotBlank(x.get("一次使用数量")))
+                .filter(x -> StringUtil.isNotBlank(x.get("医嘱状态判别")) && !x.get("医嘱状态判别").contains("已停止"))
+                .collect(Collectors.toList());
+        docAdvStruct.removeIf(x -> StringUtil.isNotBlank(x.get("给药方式")) && !filterKey.contains(x.get("给药方式")));
+
+        //激素及开医嘱时间 <激素名,<激素用量,[激素使用时间...]>>
+        Map<String, Map<String, List<Double>>> antibioticInfo = Maps.newLinkedHashMap();
+        Map<String, Map<Date, Integer>> antibioticDateTimes = Maps.newHashMap();
+        //记录同一激素同一天内是否开过多次,用于医嘱中需要处理的激素过滤(一天内同一激素开过多次的激素直接过滤)
+        getAntibioticTimes(docAdvStruct, antibioticDateTimes);
+        String drugName = null, value = null, startDateStr = null;
+        Date startDate = null;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            value = structMap.get("一次使用数量");
+            startDateStr = structMap.get("医嘱开始时间");
+            startDate = DateUtil.parseDateTime(startDateStr);
+            drugName = removeBracket(drugName).replaceAll("[^\u4e00-\u9fa5]", "");
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugName);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugName = drugStandardWord;
+            }
+            if (extData != null && extData.containsKey(startDate) && extData.get(startDate).equals(drugName)) {
+                continue;   //THR02986  医嘱有激素使用病程无记录,规则中没报未记录的激素继续走这条规则,报未记录的激素过滤
+            }
+            if (antibioticDateTimes.get(drugName) != null) {
+                if (antibioticDateTimes.get(drugName).get(startDate) != null && antibioticDateTimes.get(drugName).get(startDate) > 0) {
+                    continue;   //一天内同一激素开过多次的激素直接过滤
+                }
+            }
+
+            if (drugName.contains("甲泼尼龙") || drugName.contains("泼尼松") || drugName.contains("地塞米松") || drugName.contains("可的松")) {
+                collectAntibioticInfo(antibioticInfo, structMap.get("医嘱项目名称"), value, startDateStr);
+            }
+        }
+
+        //激素及开医嘱时间 <激素名,<激素用量,[激素使用时间...]>>
+        Map<String, Map<String, List<Double>>> antibioticWardInfo = Maps.newLinkedHashMap();
+        String dateStr = null;
+        Map<String, Date> mapInfo = Maps.newLinkedHashMap();
+        /*********************************************首程治疗计划********************************************************/
+        if (firstCourseRecordDoc != null) {
+            getInfo(mapInfo, firstCourseRecordDoc.getStructureMap(), "首次病程录", "记录时间", "治疗计划");
+            DrugLabel drugLabel = firstCourseRecordDoc.getDrugLabel();
+            dateStr = firstCourseRecordDoc.getStructureMap().get("记录时间");
+            if (drugLabel != null && StringUtil.isNotBlank(dateStr)) {
+                List<Drug> drugs = drugLabel.getDrugs();
+                getCourseDrugInfo(antibioticWardInfo, drugs, dateStr);
+            }
+        }
+        /*********************************************查房记录********************************************************/
+        List<ThreeLevelWardDoc> wardDocs = allDoctorWradDocs
+                .stream()
+                .filter(x -> StringUtil.isNotBlank(x.getStructureMap().get("查房日期")) && x.getThreeLevelWardLabel().size() > 0)
+                .collect(Collectors.toList());
+        wardDocs.forEach(x -> getInfo(mapInfo, x.getStructureMap(), "查房记录", "查房日期", "病情记录", "治疗计划和措施"));
+        wardDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getThreeLevelWardLabel().get(x.getThreeLevelWardLabel().size() - 1).getDrugs(), x.getStructureMap().get("查房日期")));
+        /**********************************************手术记录、术后首程************************************************/
+        if (operationDocs.size() > 0) {
+            //手术记录
+            List<OperationRecordDoc> operationRecordDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationRecordDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationRecordLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("病历日期")))
+                    .collect(Collectors.toList());
+            operationRecordDocs.forEach(x -> getInfo(mapInfo, x.getStructureMap(), "手术记录", "病历日期", "手术经过"));
+            operationRecordDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getOperationRecordLabel().getDrugs(), x.getStructureMap().get("病历日期")));
+            //术后首程
+            List<OperationDiscussionDoc> operationDiscussionDocs = operationDocs
+                    .stream()
+                    .map(OperationDoc::getOperationDiscussionDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getOperationDiscussionLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("记录日期")))
+                    .collect(Collectors.toList());
+            operationDiscussionDocs.forEach(x -> getInfo(mapInfo, x.getStructureMap(), "术后首程", "记录日期", "手术经过", "治疗计划和措施"));
+            operationDiscussionDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getOperationDiscussionLabel().getDrugs(), x.getStructureMap().get("记录日期")));
+        }
+        /*********************************************会诊结果单********************************************************/
+        /*if (consultationDocs.size() > 0) {
+            List<ConsultationResultsDoc> consultationResultsDocs = consultationDocs
+                    .stream()
+                    .map(ConsultationDoc::getConsultationResultsDoc)
+                    .filter(Objects::nonNull)
+                    .filter(x -> x.getConsultationResultLabel() != null && StringUtil.isNotBlank(x.getStructureMap().get("会诊日期及时间")))
+                    .collect(Collectors.toList());
+            consultationResultsDocs.forEach(x -> getCourseDrugInfo(antibioticWardInfo, x.getConsultationResultLabel().getDrugs(), x.getStructureMap().get("会诊日期及时间")));
+        }*/
+        /*********************************************出院小结********************************************************/
+        if (leaveHospitalDoc != null) {
+            getInfo(mapInfo, leaveHospitalDoc.getStructureMap(), "出院小结", "出院时间", "诊治经过", "出院带药");
+            LeaveHospitalLabel leaveHospitalLabel = leaveHospitalDoc.getLeaveHospitalLabel();
+            dateStr = leaveHospitalDoc.getStructureMap().get("出院时间");
+            if (leaveHospitalLabel != null && StringUtil.isNotBlank(dateStr)) {
+                getInfo(mapInfo, leaveHospitalDoc.getStructureMap(), "出院小结", "出院时间", "诊治经过", "出院带药");
+                List<Drug> drugs = leaveHospitalLabel.getDrugs();
+                getCourseDrugInfo(antibioticWardInfo, drugs, dateStr);
+            }
+        }
+
+        /**
+         * 1.医嘱中开了激素,查房记录中没有该激素,则医嘱中该激素出现过的所有时间都会提示出来
+         * 2.医嘱中开了激素,查房记录中有该激素:
+         *      2.1 医嘱中该激素某使用量(如50),查房记录中没有该使用量(如只有100),则医嘱中该激素使用量出现过的所有时间都会提示出来
+         *      2.2 医嘱中该激素某使用量(如50),查房记录中也有该使用量,对比这两个时间,若医嘱时间两天内的查房记录没有该使用量,则该医嘱时间会提示出来
+         */
+        StringBuffer sb = new StringBuffer();
+        String drugKey = null;
+        for (Map.Entry<String, Map<String, List<Double>>> ai : antibioticInfo.entrySet()) {
+            drugKey = ai.getKey();
+            drugKey = removeBracket(drugKey).replaceAll("[^\u4e00-\u9fa5]", "");
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugKey);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugKey = drugStandardWord;
+            }
+            if (antibioticWardInfo.containsKey(drugKey)) {
+                Map<String, List<Double>> adDateValue = ai.getValue();
+                Map<String, List<Double>> wardDateValue = antibioticWardInfo.get(drugKey);
+                for (Map.Entry<String, List<Double>> adMap : adDateValue.entrySet()) {
+                    boolean match = false;
+                    String adDateStr = adMap.getKey();
+                    Date adDate = StringUtil.parseDateTime(adDateStr);
+                    List<Double> adUsage = adMap.getValue();
+                    for (Map.Entry<String, List<Double>> wdvMap : wardDateValue.entrySet()) {
+                        String wardDateStr = wdvMap.getKey();
+                        Date wardDate = StringUtil.parseDateTime(wardDateStr);
+                        List<Double> wardUsage = wdvMap.getValue();
+                        adDate = DateUtil.dateZeroClear(adDate);
+                        if (adDate.before(wardDate) && !CatalogueUtil.compareTime(adDate, wardDate, 48 * 60L)) {
+                            if (wardUsage.toString().equals(adUsage.toString())) {
+                                match = true;
+                            }
+                        }
+                        if (wardDate.before(adDate) && !CatalogueUtil.compareTime(wardDate, adDate, 24 * 60L)) {
+                            boolean isWard = false;
+                            if (wardUsage.toString().equals(adUsage.toString())) {
+                                isWard = true;
+                            }
+                            match = isWard;
+                        }
+                    }
+                    adDateStr = DateUtil.formatDate(adDate);
+                    if (!match && !sb.toString().contains(drugKey + "(" + adDateStr + ")")) {
+                        List<Double> drugDosage = isContinueTreat(ai.getKey(), adDate, mapInfo, wardDateValue);
+                        //去*号
+                        drugKey = ai.getKey().replace("*", "");
+                        if (drugDosage == null) {
+                            infoAppend(sb, drugKey, adDateStr);
+                        } else {
+                            drugDosage.removeAll(adUsage);//比如adUsage有1.0、2.0,wardUsage中有2.0、3.0,removeAll之后wardUsage只剩3.0
+                            if (drugDosage.size() != 0) {
+                                infoAppend(sb, drugKey, adDateStr);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set("医嘱:" + sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 记录病程记录中是否有继续治疗,治疗同前等
+     *
+     * @param drug
+     * @param adDate
+     * @param mapInfo
+     * @param wardDateMaps
+     */
+    private List<Double> isContinueTreat(String drug, Date adDate, Map<String, Date> mapInfo, Map<String, List<Double>> wardDateMaps) {
+        for (Map.Entry<String, Date> map : mapInfo.entrySet()) {
+            String content = map.getKey();
+            Date dateValue = map.getValue();
+            //开医嘱时间起,昨天今天明天记录内查找药
+            if ((adDate.before(dateValue) && !CatalogueUtil.compareTime(adDate, dateValue, 48 * 60L))
+                    || (dateValue.before(adDate) && !CatalogueUtil.compareTime(dateValue, adDate, 24 * 60L))) {
+                String standardDrug = null;
+                drug = drug.replaceAll("[^\\u4e00-\\u9fa5]", "");
+                standardDrug = similarityUtil.getDrugStandardWord(drug);
+                if (StringUtil.isBlank(drug)) {
+                    continue;
+                }
+                if (!content.contains(drug) && (StringUtil.isNotBlank(content) && StringUtil.isNotBlank(standardDrug) && !content.contains(standardDrug)) &&
+                        (regexFind(content, "继续", "治疗") || regexFind(content, "维持", "治疗") || regexFind(content, "继续", "抗感染") ||
+                                regexFind(content, "治疗", "同前") || regexFind(content, "治疗同前"))) {
+                    List<Double> drugDosage = null;
+                    for (Map.Entry<String, List<Double>> wdvMap : wardDateMaps.entrySet()) {
+                        String wardDateStr = wdvMap.getKey();
+                        Date wardDate = StringUtil.parseDateTime(wardDateStr);
+                        if (wardDate.before(dateValue)) {
+                            drugDosage = wdvMap.getValue();
+                        }
+                    }
+                    return drugDosage;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean regexFind(String content, String... str) {
+        String s = "";
+        for (String word : str) {
+            s += word + ".*";
+        }
+        s = s.substring(0, s.lastIndexOf(".*"));
+        Pattern p = Pattern.compile(s);
+        Matcher m = p.matcher(content);
+        return m.find();
+    }
+
+    /**
+     * 记录同一激素同一天内是否开过多次,用于医嘱中需要处理的激素过滤(一天内同一激素开过多次的激素直接过滤)
+     *
+     * @param docAdvStruct
+     * @param antibioticDateTimes
+     */
+    private void getAntibioticTimes(List<Map<String, String>> docAdvStruct, Map<String, Map<Date, Integer>> antibioticDateTimes) {
+        String drugName;
+        String startDateStr;
+        Date startDate;
+        Map<Date, Integer> antibioticDateTime;
+        for (Map<String, String> structMap : docAdvStruct) {
+            drugName = structMap.get("医嘱项目名称");
+            startDateStr = structMap.get("医嘱开始时间");
+            startDate = StringUtil.parseDateTime(startDateStr);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(drugName);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                drugName = drugStandardWord;
+            }
+            if (antibioticDateTimes.containsKey(drugName)) {
+                Map<Date, Integer> map = antibioticDateTimes.get(drugName);
+                if (map.containsKey(startDate)) {
+                    map.put(startDate, map.get(startDate) + 1);
+                } else {
+                    map.put(startDate, 0);
+                }
+            } else {
+                antibioticDateTime = Maps.newHashMap();
+                antibioticDateTime.put(startDate, 0);
+                antibioticDateTimes.put(drugName, antibioticDateTime);
+            }
+        }
+    }
+
+    private void getCourseDrugInfo(Map<String, Map<String, List<Double>>> antibioticWardInfo, List<Drug> drugs, String dateStr) {
+        for (Drug drug : drugs) {
+            String wardDrug = drug.getName();
+            wardDrug = removeBracket(wardDrug);
+            String drugStandardWord = similarityUtil.getDrugStandardWord(wardDrug);
+            if (StringUtil.isNotBlank(drugStandardWord)) {
+                wardDrug = drugStandardWord;
+            }
+            if (drug.getConsumption() != null) {
+                String consumption = drug.getConsumption().getName();
+                collectAntibioticInfo(antibioticWardInfo, wardDrug, consumption, dateStr);
+            }
+        }
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param drugKey
+     * @param date
+     */
+    private void infoAppend(StringBuffer sb, String drugKey, String date) {
+        sb.append(drugKey).append("(").append(date).append(")").append("_");
+    }
+
+    /**
+     * 获取各模块信息<入院记录、首次病程录、手术记录、术后首程、会诊结果单、查房记录、出院小结>
+     *
+     * @param info
+     * @param structureMap
+     * @param modelType
+     * @param dateKey
+     * @param contentKey
+     */
+    private void getInfo(Map<String, Date> info, Map<String, String> structureMap, String modelType, String dateKey, String... contentKey) {
+        String content = CatalogueUtil.structureMapJoin(structureMap, Lists.newArrayList(contentKey));
+        String recordDateStr = structureMap.get(dateKey);
+        if (StringUtil.isNotBlank(recordDateStr)) {
+            Date date = StringUtil.parseDateTime(recordDateStr);
+            if (StringUtil.isNotBlank(content) && date != null) {
+                info.put(modelType + "->" + content, date);
+            }
+        }
+    }
+
+    /**
+     * 收集激素各种信息
+     *
+     * @param antibioticInfo 激素使用量及所有时间
+     * @param drugName       激素名称
+     * @param value          激素用量
+     * @param startDateStr   激素使用时间(医嘱开始时间或查房时间)
+     */
+    private void collectAntibioticInfo(Map<String, Map<String, List<Double>>> antibioticInfo, String drugName, String value, String startDateStr) {
+        Map<String, List<Double>> antibioticValueList = null;
+        double v = -1;
+        try {
+            v = Double.parseDouble(getNumber(value));
+        } catch (Exception e) {
+            System.out.println("THR03076:       " + drugName + ":" + value + "解析异常");
+        }
+        if (v < 0) {
+            return;
+        }
+        if (v > 100) {
+            v = v / 1000;
+        }
+        if (!antibioticInfo.containsKey(drugName)) {
+            //存该激素使用第1个值
+            antibioticValueList = Maps.newLinkedHashMap();
+            antibioticValueList.put(startDateStr, Lists.newArrayList(v));
+            antibioticInfo.put(drugName, antibioticValueList);
+        } else {
+            antibioticValueList = antibioticInfo.get(drugName);
+            //存该激素使用时间时第n个量
+            if (antibioticValueList.containsKey(startDateStr)) {
+                antibioticValueList.get(startDateStr).add(v);
+            } else {
+                //存该激素使用时间时第1个量
+                antibioticValueList.put(startDateStr, Lists.newArrayList(v));
+            }
+        }
+    }
+
+    public static String getNumber(String content) {
+        String group = "";
+        String compile = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9]|\\.\\d*[1-9]|0)";
+        Pattern p = Pattern.compile(compile);
+        Matcher matcher = p.matcher(content);
+        if (matcher.find()) {
+            group = matcher.group(0);
+        }
+        return group;
+    }
+
+    /**
+     * 如果文本包含中括号([海正]美罗培南针),取括号之后的文字
+     *
+     * @param str
+     * @return
+     */
+    private String removeBracket(String str) {
+        if (str.contains("]") && str.indexOf("]") != str.length() - 1) {
+            return str.substring(str.indexOf("]") + 1);
+        }
+        return str;
+    }
+
+    private static final List<String> filterKey = Lists.newArrayList("口服", "静脉滴注", "静脉注射(泵)", "静脉注射", "皮下注射", "冲服",
+            "肌注", "关节腔注射", "饭后口服", "饭前口服", "饭中口服", "嚼服", "局部注射", "术中宫体注射",
+            "含服", "饭后冲服", "皮内注射", "饭后嚼服", "饭前嚼服", "泡服", "动脉注射",
+            "IG", "PO", "SC", "IM", "IV", "IP", "JB", "GT", "OWH", "ACI", "XX");
+
+}

+ 66 - 0
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR0588.java

@@ -0,0 +1,66 @@
+package com.lantone.qc.kernel.catalogue.threelevelward;
+
+import com.lantone.qc.kernel.catalogue.QCCatalogue;
+import com.lantone.qc.kernel.util.CatalogueUtil;
+import com.lantone.qc.pub.model.InputInfo;
+import com.lantone.qc.pub.model.OutputInfo;
+import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
+import com.lantone.qc.pub.util.DateUtil;
+import com.lantone.qc.pub.util.StringUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * @ClassName : THR0588
+ * @Description : 住院期间连续3天无病程记录
+ * @Author : 王世延
+ * @Date: 2020-09-8 14:22
+ */
+@Component
+public class THR0588 extends QCCatalogue {
+    public void start(InputInfo inputInfo, OutputInfo outputInfo) {
+        status.set("0");
+        //查房记录
+        List<ThreeLevelWardDoc> threeLevelWardDocs = inputInfo.getThreeLevelWardDocs();
+        if (threeLevelWardDocs.size() == 0) {
+            return;
+        }
+        //所有查房记录的日期天
+        List<Date> dateThreeLevelDay = new ArrayList<>();
+        StringBuffer sb = new StringBuffer();
+        //所有的查房记录
+        List<ThreeLevelWardDoc> allDoctorWradDocs = threeLevelWardDocs.get(0).getAllDoctorWradDocs();
+        for (ThreeLevelWardDoc doc : allDoctorWradDocs) {
+            Date threeLevelDate = StringUtil.parseDateTime(doc.getStructureMap().get("查房日期"));
+            if (threeLevelDate == null) {
+                continue;
+            }
+            dateThreeLevelDay.add(threeLevelDate);
+        }
+        //获取连续3天无查房记录的时间
+        for (int i = 0; i < dateThreeLevelDay.size(); i++) {
+            if (i + 1 < dateThreeLevelDay.size()) {
+                if (CatalogueUtil.compareTime(dateThreeLevelDay.get(i), dateThreeLevelDay.get(i + 1), 72 * 60L)) {
+                    infoAppend(sb, dateThreeLevelDay.get(i), dateThreeLevelDay.get(i + 1));
+                }
+            }
+        }
+        if (sb.toString().length() > 0) {
+            status.set("-1");
+            info.set(sb.toString().substring(0, sb.toString().length() - 1));
+        }
+    }
+
+    /**
+     * 拼接提示信息
+     *
+     * @param sb
+     * @param bfDate
+     * @param afDate
+     */
+    private void infoAppend(StringBuffer sb, Date bfDate, Date afDate) {
+        sb.append("(").append(DateUtil.formatDate(bfDate))
+                .append("->").append(DateUtil.formatDate(afDate)).append(")").append("、");
+    }
+}