Browse Source

1.添加规则:现病史描述与专科检查相互矛盾
2.初步诊断诊断依据不充分(暂只做高血压)修改逻辑,只处理取出血压值小于给定值情况
3.医嘱有抗生素/激素使用病程无记录修改取值,原本取值1、2,现取值抗生素、激素
4.抗生素使用指征不明确中性粒白细胞组合化验项单独处理

hujing 5 years ago
parent
commit
2543c22819

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

@@ -70,12 +70,12 @@ public class BEH03047 extends QCCatalogue {
 
             String diag = initDiagFilter.get(0);//高血压病2级
             if (diag.contains("1")) {
-                if ((high > 160 || high < 140) && (low > 100 || low < 90)) {
+                if (high < 140 && low < 90) {
                     status.set("-1");
                     info.set(diag + ":" + "血压未达到1级标准");
                 }
             } else if (diag.contains("2")) {
-                if ((high > 180 || high < 160) && (low > 110 || low < 100)) {
+                if (high < 160 && low < 100) {
                     status.set("-1");
                     info.set(diag + ":" + "血压未达到2级标准");
                 }

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

@@ -0,0 +1,107 @@
+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.Lemma;
+import com.lantone.qc.kernel.structure.ai.model.Relation;
+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);
+        System.out.println(positionPairs.size());
+
+    }
+
+    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;
+    }
+}

+ 7 - 3
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR02985.java

@@ -8,11 +8,15 @@ import com.lantone.qc.pub.model.doc.DoctorAdviceDoc;
 import com.lantone.qc.pub.model.doc.FirstCourseRecordDoc;
 import com.lantone.qc.pub.model.doc.ThreeLevelWardDoc;
 import com.lantone.qc.pub.util.StringUtil;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Component;
 
 import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * @ClassName : THR02985
@@ -38,7 +42,7 @@ public class THR02985 extends QCCatalogue {
             String name = adviceDocStructureMap.get("医嘱项目名称");
             String drugCategory = adviceDocStructureMap.get("药品类型");
             String startDateStr = adviceDocStructureMap.get("医嘱开始时间");
-            if (StringUtil.isNotBlank(drugCategory) && drugCategory.equals("1")) {
+            if (StringUtil.isNotBlank(drugCategory) && drugCategory.equals("抗生素")) {
                 if (StringUtil.isNotBlank(name)) {
                     if(Arrays.asList(KSS).contains(name)){
                        continue;

+ 1 - 1
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR02986.java

@@ -41,7 +41,7 @@ public class THR02986 extends QCCatalogue {
             String name = adviceDocStructureMap.get("医嘱项目名称");
             String drugCategory = adviceDocStructureMap.get("药品类型");
             String startDateStr = adviceDocStructureMap.get("医嘱开始时间");
-            if (StringUtil.isNotBlank(drugCategory) && drugCategory.equals("2")) {
+            if (StringUtil.isNotBlank(drugCategory) && drugCategory.equals("激素")) {
 
                 if (StringUtil.isNotBlank(name)) {
                     if(Arrays.asList(JS).contains(name)){

+ 43 - 8
kernel/src/main/java/com/lantone/qc/kernel/catalogue/threelevelward/THR03044.java

@@ -30,9 +30,9 @@ public class THR03044 extends QCCatalogue {
             .put("超敏C反应蛋白", ">10")
             .put("降钙素CT(免疫)", ">0.25")
             .put("尿=白细胞", ">13.2")
-            .put("白细胞计数[电阻抗法]", "<0.4")
-            .put("中性粒细胞绝对值", ">12")
-            .put("中性粒细胞百分率", ">70%")
+            //.put("白细胞计数[电阻抗法]", "<0.4")
+            //.put("中性粒细胞绝对值", ">12")
+            //.put("中性粒细胞百分率", ">70%")
             .put("血培养报阳时间", "")
             //            .put("尿培养加菌落计数", "细菌生长")
             .put("一般细菌涂片检查", "细菌生长")
@@ -52,6 +52,12 @@ public class THR03044 extends QCCatalogue {
             .put("G-杆菌:痰夜", "少,中等,多,偶见")
             .build();
 
+    public static Map<String, String> lisGroupData = ImmutableMap.<String, String>builder()
+            .put("白细胞计数[电阻抗法]", "<0.4")
+            .put("中性粒细胞绝对数", ">12")
+            .put("中性粒百分数", ">70")
+            .build();
+
     public void start(InputInfo inputInfo, OutputInfo outputInfo) {
         status.set("0");
         List<DoctorAdviceDoc> doctorAdviceDocs = inputInfo.getDoctorAdviceDocs();
@@ -71,7 +77,7 @@ public class THR03044 extends QCCatalogue {
             return;
         }
 
-        Set<String> lises = new HashSet<>();
+        Map<String, String> lises = new HashMap<>();
         Map<String, String> lisReportMap = new HashMap<>();
 
         for (LisDoc lisDoc : lisDocs) {
@@ -80,11 +86,40 @@ public class THR03044 extends QCCatalogue {
             String result = lisDocStructureMap.get("检验结果");
             lisReportMap.put(lisName, result);
             if (lisName.contains("=")) {
-                lises.add(lisName.split("=")[1]);
+                lises.put(lisName.split("=")[1], result);
             } else {
-                lises.add(lisName);
+                lises.put(lisName, result);
+            }
+        }
+
+
+        /**
+         * 白细胞组合项单独处理
+         */
+        if (lises.containsKey("白细胞计数[电阻抗法]")) {
+            String wbcValue = lisGroupData.get("白细胞计数[电阻抗法]");
+            String wbc = lises.get("白细胞计数[电阻抗法]");
+            if (!compare(wbc, wbcValue)) {
+                status.set("-1");
+                return;
+            }
+            if (lises.containsKey("中性粒细胞绝对数")) {
+                String neutrophilValue = lisGroupData.get("中性粒细胞绝对数");
+                String neutrophil = lises.get("中性粒细胞绝对数");
+                if (!compare(neutrophil, neutrophilValue)) {
+                    status.set("-1");
+                    return;
+                }
+            } else if (lises.containsKey("中性粒百分数")) {
+                String neutrophilValue = lisGroupData.get("中性粒百分数");
+                String neutrophil = lises.get("中性粒百分数");
+                if (!compare(neutrophil, neutrophilValue)) {
+                    status.set("-1");
+                    return;
+                }
             }
         }
+
         //如果这个病例的化验项目不在lisData,不报
         Set<String> dataLises = lisData.keySet();
         Set<String> collect = new HashSet<>();
@@ -95,7 +130,7 @@ public class THR03044 extends QCCatalogue {
                 collect.add(dataLis);
             }
         }
-        collect.retainAll(lises);
+        collect.retainAll(lises.keySet());
         if (collect.size() == 0) {
             return;
         }
@@ -156,7 +191,7 @@ public class THR03044 extends QCCatalogue {
                 flag = true;
             }
         } else {
-            if (result.equals(s)) {
+            if (s.contains(result)) {
                 flag = true;
             }
         }

+ 6 - 1
kernel/src/main/java/com/lantone/qc/kernel/structure/ai/BeHospitalizedAI.java

@@ -113,7 +113,7 @@ public class BeHospitalizedAI extends ModelAI {
             /* 专科检查(专科体格检查) */
             if (beHospitalizedDoc.getVitalLabelSpecial() != null && beHospitalizedDoc.getVitalLabelSpecial().isCrfLabel()) {
                 String vitalSpecialText = beHospitalizedDoc.getVitalLabelSpecial().getText();
-                putContent(crfContent, medicalTextType.get(3), vitalSpecialText, Content.special_exam);
+                putContent(crfContent, medicalTextType.get(7), vitalSpecialText, Content.special_exam);
             }
             /* 一般体格检查(存放一般查体) */
             if (beHospitalizedDoc.getVitalLabel() != null && beHospitalizedDoc.getVitalLabel().isCrfLabel()) {
@@ -154,6 +154,7 @@ public class BeHospitalizedAI extends ModelAI {
             /* 处理现病史 */
             if (beHospitalizedDoc.getPresentLabel() != null && beHospitalizedDoc.getPresentLabel().isCrfLabel()) {
                 putPresentCrfData(midData.getJSONObject(Content.present), inputInfo);
+                beHospitalizedDoc.getPresentLabel().setCrfOutput(midData.getJSONObject(Content.present));
             }
             /* 处理既往史 */
             if (beHospitalizedDoc.getPastLabel() != null && beHospitalizedDoc.getPastLabel().isCrfLabel()) {
@@ -187,6 +188,10 @@ public class BeHospitalizedAI extends ModelAI {
             if (beHospitalizedDoc.getSuppleDiagLabel() != null && beHospitalizedDoc.getSuppleDiagLabel().isCrfLabel()) {
                 putSuppleDiagCrfData(midData.getJSONObject(Content.supple_diag), inputInfo);
             }
+            /* 处理专科体格检查 */
+            if (beHospitalizedDoc.getVitalLabelSpecial() != null && beHospitalizedDoc.getVitalLabelSpecial().isCrfLabel()) {
+                beHospitalizedDoc.getVitalLabelSpecial().setCrfOutput(midData.getJSONObject(Content.special_exam));
+            }
             /* 处理辅助检查 */
             if (beHospitalizedDoc.getPacsLabel() != null && beHospitalizedDoc.getPacsLabel().isCrfLabel()) {
                 /* 辅检提取时间、地点和提取疾病分为两个模型 */

+ 204 - 0
kernel/src/main/java/com/lantone/qc/kernel/structure/ai/model/ConflictFinder.java

@@ -0,0 +1,204 @@
+package com.lantone.qc.kernel.structure.ai.model;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @Description: 找出冲突
+ * @Author: HUJING
+ * @Date: 2020/8/2 15:04
+ */
+public class ConflictFinder {
+
+    private static Set<String> ignoreWords;
+    private static Set<String> negativeWords;
+    private static Map<String, String> conflictWordPairMap;
+
+    static {
+        ignoreWords = Sets.newHashSet("部", "侧");
+        negativeWords = Sets.newHashSet("无", "无殊", "否认", "不伴", "未找到",
+                "未萌出", "未见", "未", "不", "不伴有", "非", "不明显", "无明显");
+        conflictWordPairMap = Maps.newHashMap();
+        conflictWordPairMap.put("可", "差");
+        conflictWordPairMap.put("尚可", "差");
+    }
+
+
+    /**
+     * 搜索和配对
+     *
+     * @param invertedIndexTableCheck   专科检查的倒排索引
+     * @param invertedIndexTablePresent 现病史的倒排索引
+     */
+    public List<EntityBlock[]> match(Map<String, Set<EntityBlock>> invertedIndexTableCheck,
+                                     Map<String, Set<EntityBlock>> invertedIndexTablePresent) {
+
+        List<EntityBlock[]> entityBlockPairs = Lists.newArrayList();
+        Set<String> existedIdPairs = Sets.newHashSet();  // 去除多搜索词搜到同一组实体块对引起的重复
+        invertedIndexTableCheck.forEach((char_, entityBlockSetCheck) -> {
+            invertedIndexTablePresent.getOrDefault(char_, Sets.newHashSet()).forEach(entityBlockPresent -> {
+                entityBlockSetCheck.forEach(entityBlockCheck -> {
+                    String idPair = "" + entityBlockCheck.getId() + "_" + entityBlockPresent.getId();
+                    if (!existedIdPairs.contains(idPair)) {
+                        entityBlockPairs.add(new EntityBlock[] { entityBlockCheck, entityBlockPresent });
+
+                        existedIdPairs.add(idPair);
+                    }
+                });
+            });
+        });
+
+        return entityBlockPairs;
+    }
+
+    /**
+     * 字符串1没在字符串2中的子词, // 并不是很严格意义上的那种,不过可以用
+     *
+     * @param word1 字符串1
+     * @param word2 字符串2
+     * @return 字符串列表
+     */
+    private List<String> subWordsInWord1NotInWord2(String word1, String word2) {
+
+        StringBuilder inWord1NotInWord2 = new StringBuilder();
+        Map<String, Integer> word2Map = Maps.newHashMap();
+        for (int i = 0; i < word2.length(); i++) {
+            word2Map.put(word2.substring(i, i + 1), 1);
+        }
+
+        for (int i = 0; i < word1.length(); i++) {
+            String char_ = word1.substring(i, i + 1).trim();
+            if (!"".equals(char_) && !ignoreWords.contains(char_)) {
+                if (!word2Map.containsKey(char_)) {
+                    inWord1NotInWord2.append(char_);
+                } else {
+                    inWord1NotInWord2.append("_s_");  // _s_ 作为分隔符
+                }
+            }
+        }
+        List<String> subWords = Lists.newArrayList();
+        for (String subWord : inWord1NotInWord2.toString().split("_s_")) {
+            if (!"".equals(subWord.trim())) {
+                subWords.add(subWord.trim());
+            }
+
+        }
+        return subWords;
+    }
+
+    /**
+     * 只缺少一个阴性词
+     *
+     * @param word_1 词1
+     * @param word_2 词2
+     * @return 冲突则ture,否则false
+     */
+    private boolean onlyLackOfNegativeWord(String word_1, String word_2) {
+
+        List<String> subWords_1 = subWordsInWord1NotInWord2(word_1, word_2);
+        List<String> subWords_2 = subWordsInWord1NotInWord2(word_2, word_1);
+        if (subWords_1.size() == 1 && subWords_2.size() == 0) {
+            return negativeWords.contains(subWords_1.get(0));
+        }
+        if (subWords_2.size() == 1 && subWords_1.size() == 0) {
+            return negativeWords.contains(subWords_2.get(0));
+        }
+
+        return false;
+    }
+
+    /**
+     * 不同的只有冲突词
+     *
+     * @param word_1 词1
+     * @param word_2 词2
+     * @return 冲突则ture,否则false
+     */
+    private boolean onlyDifferentWithConflictWords(String word_1, String word_2) {
+
+        List<String> subWords_1 = subWordsInWord1NotInWord2(word_1, word_2);
+        List<String> subWords_2 = subWordsInWord1NotInWord2(word_2, word_1);
+        if (subWords_1.size() == 1 && subWords_2.size() == 1) {
+            return conflictWordPairMap.getOrDefault(subWords_1.get(0), "").equals(subWords_2.get(0)) ||
+                    conflictWordPairMap.getOrDefault(subWords_2.get(0), "").equals(subWords_1.get(0));
+
+        }
+        return false;
+    }
+
+    /**
+     * 是否冲突
+     *
+     * @param entityBlock_1 实体块1
+     * @param entityBlock_2 实体块2
+     * @return 冲突true,否则false
+     */
+    public boolean isConflict(EntityBlock entityBlock_1, EntityBlock entityBlock_2) {
+
+        StringBuilder word1Builder = new StringBuilder();
+        entityBlock_1.getEntityWords().forEach(word1Builder::append);
+        String word_1 = word1Builder.toString();
+
+        StringBuilder word2Builder = new StringBuilder();
+        entityBlock_2.getEntityWords().forEach(word2Builder::append);
+        String word_2 = word2Builder.toString();
+
+        if (word_1.length() == 0 || word_2.length() == 0) {
+            return false;
+        }
+
+        return onlyLackOfNegativeWord(word_1, word_2) || onlyDifferentWithConflictWords(word_1, word_2);
+    }
+
+    /**
+     * 获取位置
+     *
+     * @param entityBlock_1 实体块1
+     * @param entityBlock_2 实体块2
+     * @return List<int [ ]> 对
+     */
+    public Object[] getPositions(EntityBlock entityBlock_1, EntityBlock entityBlock_2) {
+        return new Object[] { entityBlock_1.getPositions(), entityBlock_2.getPositions() };
+    }
+
+
+    /**
+     * 找出冲突的位置
+     *
+     * @param checkPairs   专科检查实体和关系列表
+     * @param presentPairs 现病史实体和关系列表
+     * @return 冲突位置组对列表
+     */
+    public List<Object[]> findConflictPositions(Object[] checkPairs, Object[] presentPairs) {
+        List<Lemma> checkLemmas = (List<Lemma>) checkPairs[0];
+        List<Relation> checkRelations = (List<Relation>) checkPairs[1];
+
+        List<Lemma> presentLemmas = (List<Lemma>) presentPairs[0];
+        List<Relation> presentRelations = (List<Relation>) presentPairs[1];
+
+        InvertedIndexTableBuilder checkInvertedIndexTableBuilder = new InvertedIndexTableBuilder(checkLemmas, checkRelations);
+        InvertedIndexTableBuilder presentInvertedIndexTableBuilder = new InvertedIndexTableBuilder(presentLemmas, presentRelations);
+        Map<String, Set<EntityBlock>> checkInvertedIndexTable = checkInvertedIndexTableBuilder.generateInvertedIndexTablePipeline();
+        Map<String, Set<EntityBlock>> presentInvertedIndexTable = presentInvertedIndexTableBuilder.generateInvertedIndexTablePipeline();
+
+        List<EntityBlock[]> entityBlockPairs = match(checkInvertedIndexTable, presentInvertedIndexTable);
+
+        List<Object[]> conflictPositions = Lists.newArrayList();
+        entityBlockPairs.forEach(entityBlockPair -> {
+            EntityBlock entityBlock_1 = entityBlockPair[0];
+            EntityBlock entityBlock_2 = entityBlockPair[1];
+            boolean conflict = isConflict(entityBlock_1, entityBlock_2);
+            // TODO: 删除调
+            System.out.println("" + conflict + ":" + entityBlock_1 + " ---> " + entityBlock_2);
+            if (conflict) {
+                conflictPositions.add(getPositions(entityBlock_1, entityBlock_2));
+            }
+        });
+        return conflictPositions;
+    }
+}

+ 29 - 0
kernel/src/main/java/com/lantone/qc/kernel/structure/ai/model/EntityBlock.java

@@ -0,0 +1,29 @@
+package com.lantone.qc.kernel.structure.ai.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @Description:
+ * @Author: HUJING
+ * @Date: 2020/7/31 11:19
+ */
+@Setter
+@Getter
+public class EntityBlock {
+    private List<String> entityWords;
+    private List<String> entityTypes;
+    private List<int[]> positions;
+    private String genre;
+    private String searchWord;
+    private Integer id;
+
+    @Override
+    public String toString() {
+        return "EntityBlock{" +
+                "entityWords=" + entityWords +
+                '}';
+    }
+}

+ 282 - 0
kernel/src/main/java/com/lantone/qc/kernel/structure/ai/model/InvertedIndexTableBuilder.java

@@ -0,0 +1,282 @@
+package com.lantone.qc.kernel.structure.ai.model;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @Description:
+ * @Author: HUJING
+ * @Date: 2020/8/2 12:25
+ */
+public class InvertedIndexTableBuilder {
+    private static Set<String> selectedLonelyEntityTypes;  // 选定的孤独实体类型
+    private static Set<String> selectedRelationTypes;  // 选定的关系类型
+    // 选定的多步关系模式,比如:身体部位->临床表现<-否定
+    private static Set<Set<String>> selectedMultiStepRelationTypes;
+
+    private Map<Integer, Lemma> idEntityMap;
+    private List<Relation> relations;
+    private List<Lemma> lonelyEntityList;
+
+    private static Set<String> removeIndexWords; // 被移除的搜索词
+
+    static {  // 把该模块从现病史和专科检查扩展到别的比较中需要修改的地方
+        selectedLonelyEntityTypes = Sets.newHashSet("临床表现", "修饰");
+        selectedRelationTypes = Sets.newHashSet("身体部位-临床表现",
+                "一般情况-一般情况描述", "否定-临床表现");
+        selectedMultiStepRelationTypes = Sets.newHashSet(Sets.newHashSet());  // 暂且没用上
+
+        removeIndexWords = Sets.newHashSet();
+
+        for (int i = 0; i < 10; i++) {
+            removeIndexWords.add("" + i);
+        }
+
+        for (int i = 0; i < 10; i++) {  //数字
+            removeIndexWords.add(String.valueOf(i));
+        }
+
+        for (int i = 'a'; i <= 'z'; i++) {  //英文词
+            removeIndexWords.add(String.valueOf((char) i));
+            removeIndexWords.add(String.valueOf((char) i).toUpperCase());
+        }
+
+        List<String> punctuations = Lists.newArrayList();//添加标点符号
+        List<String> positionWords = Lists.newArrayList();//添加方位词
+        List<String> emrWords = Lists.newArrayList("无");//添加一些病例中无用的词
+
+        List<String> words = new ArrayList<>();
+        words.addAll(emrWords);
+        words.addAll(punctuations);
+        words.addAll(positionWords);
+
+        removeIndexWords.addAll(words);
+    }
+
+    public InvertedIndexTableBuilder(List<Lemma> lemmas, List<Relation> relations) {
+        idEntityMap = Maps.newHashMap();
+        for (Lemma lemma : lemmas) {
+            idEntityMap.put(lemma.getId(), lemma);
+        }
+
+        this.relations = relations;
+
+    }
+
+    /**
+     * 过滤实体和关系
+     */
+    private void filterEntitiesAndRelations() {
+
+        // 实体删除规则:没在关系中,且其实体类型没在已选的孤独实体类型中
+        Map<Integer, Lemma> newIdEntityMap = Maps.newHashMap();
+        idEntityMap.forEach((id, lemma) -> {
+            if (selectedLonelyEntityTypes.contains(lemma.getProperty())) {
+                newIdEntityMap.put(id, lemma);
+            }
+        });
+
+        List<Relation> filteredRelations = Lists.newArrayList();
+        Set<Integer> entityIdInRelationSet = Sets.newHashSet();
+        for (Relation relation : relations) {
+            Integer fromId = relation.getFrom();
+            Integer toId = relation.getTo();
+            if (selectedRelationTypes.contains(relation.getRelationName())) {
+                filteredRelations.add(relation);
+                newIdEntityMap.put(fromId, idEntityMap.get(fromId));
+                newIdEntityMap.put(toId, idEntityMap.get(toId));
+            }
+
+            entityIdInRelationSet.add(fromId);
+            entityIdInRelationSet.add(toId);
+        }
+
+        this.idEntityMap = newIdEntityMap;
+        this.relations = filteredRelations;
+        this.lonelyEntityList = Lists.newArrayList();
+        this.idEntityMap.forEach((id, lemma) -> {
+            if (!entityIdInRelationSet.contains(id)) { // 不在关系中的实体为孤独实体
+                this.lonelyEntityList.add(lemma);
+            }
+        });
+
+    }
+
+    /**
+     * 分组有关系的实体id对
+     *
+     * @param idPairs 实体id对
+     * @return 实体id对组
+     */
+    public static List<List<Integer>> groupIdPairs(List<Integer[]> idPairs) {
+
+        Map<Integer, Set<Integer>> idRelatedIdsMap = Maps.newHashMap();
+        for (Integer[] idPair : idPairs) {
+            if (idPair.length == 2) {
+                Integer fromId = idPair[0];
+                Integer toId = idPair[1];
+                Set<Integer> fromIds = idRelatedIdsMap.getOrDefault(fromId, Sets.newHashSet());
+                Set<Integer> toIds = idRelatedIdsMap.getOrDefault(toId, Sets.newHashSet());
+                fromIds.addAll(toIds);
+                fromIds.add(fromId);
+                fromIds.add(toId);
+                fromIds.forEach(id -> {  // 更新每个id所关联的ids
+                    idRelatedIdsMap.put(id, fromIds);
+                });
+
+            }
+        }
+
+        List<List<Integer>> idGroups = Lists.newArrayList();
+        Set<Integer> usedIds = Sets.newHashSet();
+
+        idRelatedIdsMap.forEach((id, ids) -> {
+            if (!usedIds.contains(id)) {
+                List<Integer> idsList = Lists.newArrayList();
+                idsList.addAll(ids);
+                idsList.sort(Integer::compareTo);
+                idGroups.add(idsList);
+
+                usedIds.addAll(ids);  // 避免重复
+            }
+        });
+
+        return idGroups;
+    }
+
+    /**
+     * 用关系合并实体
+     *
+     * @return 实体组列表
+     */
+    private List<List<Lemma>> mergeEntitiesByRelations() {
+
+        List<List<Lemma>> entityGroups = Lists.newArrayList();
+
+        // 孤独实体
+        for (Lemma lemma : lonelyEntityList) {
+            entityGroups.add(Lists.newArrayList(lemma));
+        }
+
+        List<Integer[]> idPairs = Lists.newArrayList();
+        relations.forEach(relation -> {
+            idPairs.add(new Integer[] { relation.getFrom(), relation.getTo() });
+        });
+
+        List<List<Integer>> idGroups = groupIdPairs(idPairs);
+        idGroups.forEach(idGroup -> {
+            if (idGroup.size() == 2) {
+                List<Lemma> entityGroup = Lists.newArrayList();
+                idGroup.forEach(id -> {
+                    entityGroup.add(idEntityMap.get(id));
+                });
+                entityGroups.add(entityGroup);
+            } else if (idGroup.size() == 3) {
+                // 将来会用一些模式去匹配,比如:(腹,压痛,反跳痛) 本质是=> 腹->压痛, 腹->反跳痛, 需要拆开成两组
+
+                List<Lemma> entityGroup = Lists.newArrayList();
+                idGroup.forEach(id -> {
+                    entityGroup.add(idEntityMap.get(id));
+                });
+                entityGroups.add(entityGroup);
+            } else {
+                // 将来会用一些模式去配,这里先留下
+            }
+        });
+
+        return entityGroups;
+    }
+
+    /**
+     * 设置一个实体块对象
+     *
+     * @param entityGroup 实体组
+     * @param id          实体块id
+     * @return 实体块对象
+     */
+    private EntityBlock setEntityBlock(List<Lemma> entityGroup, Integer id) {
+
+        List<String> entityWords = Lists.newArrayList();
+        List<String> entityTypes = Lists.newArrayList();
+        List<int[]> positions = Lists.newArrayList();
+        entityGroup.forEach(lemma -> {
+            entityWords.add(lemma.getText());
+            entityTypes.add(lemma.getProperty());
+            positions.add(new int[] { lemma.getFrom(), lemma.getTo() });
+        });
+
+        StringBuilder searchWord = new StringBuilder();
+        for (String word : entityWords) {
+            searchWord.append(word);
+        }
+
+        EntityBlock entityBlock = new EntityBlock();
+        entityBlock.setEntityWords(entityWords);
+        entityBlock.setEntityTypes(entityTypes);
+        entityBlock.setPositions(positions);
+        entityBlock.setSearchWord(searchWord.toString());
+        entityBlock.setId(id);
+
+        return entityBlock;
+    }
+
+    /**
+     * 设置所有实体块对象
+     *
+     * @param entityGroups 实体组列表
+     * @return 实体块对象列表
+     */
+    private List<EntityBlock> setEntityBlocks(List<List<Lemma>> entityGroups) {
+
+        List<EntityBlock> entityBlocks = Lists.newArrayList();
+        for (int i = 0; i < entityGroups.size(); i++) {
+            entityBlocks.add(setEntityBlock(entityGroups.get(i), i));
+        }
+        return entityBlocks;
+    }
+
+    /**
+     * 生成倒排索引表
+     *
+     * @param entityBlocks 实体块对象列表
+     * @return {字符:实体块对象集合},倒排索引表
+     */
+    private Map<String, Set<EntityBlock>> generateInvertedIndexTable(List<EntityBlock> entityBlocks) {
+
+        Map<String, Map<Integer, EntityBlock>> invertedIndexTableTemp = Maps.newHashMap();  // 防止重复
+        entityBlocks.forEach(entityBlock -> {
+            String searchWord = entityBlock.getSearchWord();
+            for (int i = 0; i < searchWord.length(); i++) {
+                String char_ = searchWord.substring(i, i + 1);
+                Map<Integer, EntityBlock> idEntityBlockMap = invertedIndexTableTemp.getOrDefault(char_, Maps.newHashMap());
+                idEntityBlockMap.put(entityBlock.getId(), entityBlock);
+                invertedIndexTableTemp.put(char_, idEntityBlockMap);
+            }
+        });
+
+        Map<String, Set<EntityBlock>> invertedIndexTable = Maps.newHashMap();
+        invertedIndexTableTemp.forEach((char_, idEntityBlockMap) -> {
+            invertedIndexTable.put(char_, Sets.newHashSet(idEntityBlockMap.values()));
+        });
+
+        return invertedIndexTable;
+    }
+
+    /**
+     * 倒排索引生成流水线
+     * @return 倒排索引表
+     */
+    public Map<String, Set<EntityBlock>> generateInvertedIndexTablePipeline(){
+        filterEntitiesAndRelations();
+        List<List<Lemma>> entityGroups = mergeEntitiesByRelations();
+        List<EntityBlock> entityBlocks = setEntityBlocks(entityGroups);
+        return generateInvertedIndexTable(entityBlocks);
+    }
+
+}

+ 91 - 0
kernel/src/test/java/com/lantone/qc/kernel/KnowledgeTest.java

@@ -0,0 +1,91 @@
+package com.lantone.qc.kernel;
+
+import com.google.common.collect.Lists;
+import com.lantone.qc.kernel.structure.ai.model.Lemma;
+import com.lantone.qc.kernel.structure.ai.model.ConflictFinder;
+import com.lantone.qc.kernel.structure.ai.model.Relation;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @Description:
+ * @Author: HUJING
+ * @Date: 2020/8/1 13:32
+ */
+
+
+public class KnowledgeTest {
+
+    public static Lemma lemmaSetting(Integer start, Integer end, Integer id, String name, String value) {
+        Lemma lemma_1 = new Lemma();
+        lemma_1.setFrom(start);
+        lemma_1.setTo(end);
+        lemma_1.setProperty(name);
+        lemma_1.setText(value);
+        lemma_1.setId(id);
+        return lemma_1;
+    }
+
+    public static Relation relationSetting(Integer fromId, Integer toId, String relationName){
+        Relation relation = new Relation();
+        relation.setFrom(fromId);
+        relation.setTo(toId);
+        relation.setRelationName(relationName);
+        return relation;
+    }
+
+    public static void main(String[] args) {
+
+        //        Set<Integer[]> xx = new HashSet<>();
+        //        xx.add(new Integer[]{11, 22});
+        //        xx.add(new Integer[]{11, 22});
+        //        System.out.println(xx.size());
+
+        //
+        ConflictFinder finder = new ConflictFinder();
+
+        Lemma lemma_1 = lemmaSetting(11, 20, 1, "指标值", "20次/分钟;体温");
+        Lemma lemma_23 = lemmaSetting(129, 130, 23, "身体部位", "腹");
+        Lemma lemma_24 = lemmaSetting(130, 131, 24, "临床表现", "软");
+        Lemma lemma_25 = lemmaSetting(132, 133, 25, "否定", "无");
+
+        Lemma lemma_30 = lemmaSetting(100, 103, 30, "修饰", "边界清");
+        Lemma lemma_31 = lemmaSetting(100, 103, 31, "修饰", "边界不清");
+        Lemma lemma_26 = lemmaSetting(100, 103, 26, "临床表现", "压痛");
+        Lemma lemma_40 = lemmaSetting(100, 103, 40, "一般情况", "胃纳");
+        Lemma lemma_41 = lemmaSetting(100, 103, 41, "一般情况描述", "差");
+
+        Lemma lemma_50 = lemmaSetting(100, 103, 50, "临床表现", "胃纳可");
+
+        Relation relation23_26 = relationSetting(23, 26, "身体部位-临床表现");
+        Relation relation25_26 = relationSetting(25, 26, "否定-临床表现");
+        Relation relation40_41 = relationSetting(40, 41, "一般情况-一般情况描述");
+
+
+        // 放入专科检查中的实体列表
+        List<Lemma> checkLemmas = Lists.newArrayList(lemma_1, lemma_23, lemma_24, lemma_25, lemma_30, lemma_26,
+                lemma_40, lemma_41);
+
+        // 放入现病史中的实体列表
+        List<Lemma> presentLemmas = Lists.newArrayList(lemma_1, lemma_23, lemma_24, lemma_25,
+                lemma_31, lemma_26, lemma_50);
+
+        // 放入专科检查中的关系列表
+        List<Relation> checkRelations = Lists.newArrayList(relation23_26, relation25_26, relation40_41);
+        // 放入现病史中的关系列表
+        List<Relation> presentRelations = Lists.newArrayList(relation23_26);
+
+        Object[] checkPairs;
+        Object[] presentPairs;
+        checkPairs = new Object[] {checkLemmas, checkRelations};
+        presentPairs = new Object[] {presentLemmas, presentRelations};
+
+        List<Object[]> positionPairs = finder.findConflictPositions(checkPairs, presentPairs);
+
+        System.out.println("有冲突的位置组数目:" + positionPairs.size());
+
+    }
+}

+ 3 - 0
public/src/main/java/com/lantone/qc/pub/model/label/GeneralLabel.java

@@ -1,5 +1,6 @@
 package com.lantone.qc.pub.model.label;
 
+import com.alibaba.fastjson.JSONObject;
 import com.google.common.collect.Maps;
 import lombok.Getter;
 import lombok.Setter;
@@ -19,6 +20,8 @@ public class GeneralLabel {
     private String text;
     private String aiText;
     private boolean crfLabel = true;
+    //CRF返回信息存储
+    private JSONObject crfOutput = new JSONObject();
     protected  <T> void add(List<T> list, T obj) {
         list.add(obj);
     }

+ 48 - 0
public/src/main/java/com/lantone/qc/pub/model/label/VitalLabelSpecial.java

@@ -1,10 +1,58 @@
 package com.lantone.qc.pub.model.label;
 
+import com.lantone.qc.pub.model.entity.BeHospitalizedWay;
+import com.lantone.qc.pub.model.entity.Cause;
+import com.lantone.qc.pub.model.entity.Clinical;
+import com.lantone.qc.pub.model.entity.Diag;
+import com.lantone.qc.pub.model.entity.General;
+import com.lantone.qc.pub.model.entity.GeneralDesc;
+import com.lantone.qc.pub.model.entity.Lis;
+import com.lantone.qc.pub.model.entity.Medicine;
+import com.lantone.qc.pub.model.entity.Operation;
+import com.lantone.qc.pub.model.entity.PD;
+import com.lantone.qc.pub.model.entity.Pacs;
+import com.lantone.qc.pub.model.entity.Treat;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * @ClassName : VatilLabel
  * @Description : 体格检查(专科检查)
  * @Author : 楼辉荣
  * @Date: 2020-03-03 18:49
  */
+@Getter
+@Setter
 public class VitalLabelSpecial extends GeneralLabel {
+    //临床表现
+    private List<Clinical> clinicals = new ArrayList<>();
+    //辅助检查
+    private List<Pacs> pacses;
+    //化验
+    private List<Lis> lises;
+    //治疗
+    private List<Treat> treats;
+    //药物
+    private List<Medicine> medicines;
+    //一般情况
+    private List<General> gens = new ArrayList<>();
+    //一般情况描述
+    private List<GeneralDesc> generals = new ArrayList<>();
+    //诱因
+    private List<Cause> causes;
+    //疾病信息
+    private List<Diag> diags = new ArrayList<>();
+    //手术信息
+    private List<Operation> operations = new ArrayList<>();
+    //入院途径
+    private BeHospitalizedWay beHospitalizedWay;
+    //现病史中所有时间实体存入
+    private List<PD> pds = new ArrayList<>();
+
+    public <T> void add(List<T> list, T obj) {
+        list.add(obj);
+    }
 }

+ 1 - 1
trans/src/main/java/com/lantone/qc/trans/shaoyf/ShaoyfBeHospitalizedDocTrans.java

@@ -166,7 +166,7 @@ public class ShaoyfBeHospitalizedDocTrans extends ModelDocTrans {
         //        structureMap.remove("体格检查");
 
         VitalLabelSpecial vitalLabelSpecial = new VitalLabelSpecial();
-        vitalLabelSpecial.setCrfLabel(false);
+        vitalLabelSpecial.setCrfLabel(true);
         vitalLabelSpecial.setText(structureMap.get("专科体格检查"));
         beHospitalizedDoc.setVitalLabelSpecial(vitalLabelSpecial);
         //        structureMap.remove("专科体格检查");