You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
virtual-patient/virtual-patient-graph/src/main/java/com/supervision/service/impl/GraphNebulaServiceImpl.java

394 lines
21 KiB
Java

package com.supervision.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.supervision.dao.*;
import com.supervision.domain.*;
import com.supervision.enums.TagEnum;
import com.supervision.exception.BusinessException;
import com.supervision.model.Process;
import com.supervision.model.*;
import com.supervision.service.*;
import com.supervision.vo.EdgeVO;
import com.supervision.vo.GraphVO;
import com.supervision.vo.NodeVO;
import com.supervision.vo.TreeNodeVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nebula.contrib.ngbatis.models.data.NgEdge;
import org.nebula.contrib.ngbatis.models.data.NgSubgraph;
import org.nebula.contrib.ngbatis.models.data.NgVertex;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class GraphNebulaServiceImpl implements GraphNebulaService {
private final ProcessService processService;
private final TreatmentPlanRecordService treatmentPlanRecordService;
private final ProcessMedicalService processMedicalService;
private final MedicalRecService medicalRecService;
private final DiagnosisPrimaryService diagnosisPrimaryService;
private final DiagnosisAncillaryRecordService diagnosisAncillaryRecordService;
private final DiagnosisPhysicalRecordService diagnosisPhysicalRecordService;
private final ConfigPhysicalToolService configPhysicalToolService;
private final ConfigPhysicalLocationService configPhysicalLocationService;
private final ConfigAncillaryItemService configAncillaryItemService;
private final DiseaseService diseaseService;
private final DiagnosisPrimaryRelationService diagnosisPrimaryRelationService;
@Resource
private MedicalRecDao medicalRecDao;
@Resource
private ProcessMedicalDao processMedicalDao;
@Resource
private SelfDescDao selfDescDao;
@Resource
private IllnessHistoryDao illnessHistoryDao;
@Resource
private PersonalHistoryDao personalHistoryDao;
@Resource
private AllergyHistoryDao allergyHistoryDao;
@Resource
private PreviousHistoryDao previousHistoryDao;
@Resource
private FamilyHistoryDao familyHistoryDao;
@Resource
private OperationHistoryDao operationHistoryDao;
@Resource
private PatientDao patientDao;
@Resource
private PhysicalDao physicalDao;
@Resource
private AncillaryDao ancillaryDao;
@Resource
private DiagnosisDao diagnosisDao;
@Resource
private AncillaryResultDao ancillaryResultDao;
@Override
public String creatGraphByNebula(String processId) {
Process process = Optional.ofNullable(processService.getById(processId)).orElseThrow(() -> new BusinessException("未找到流程,创建图谱失败"));
// 根据processId找到对应的诊疗计划,如果没有找到,说明治疗流程未完成,诊断失败
List<TreatmentPlanRecord> treatmentPlanRecordList = treatmentPlanRecordService.lambdaQuery().eq(TreatmentPlanRecord::getProcessId, processId).list();
Assert.notEmpty(treatmentPlanRecordList, () -> new BusinessException("治疗计划为空,请先完成治疗计划"));
Integer disposalMethod = treatmentPlanRecordList.stream().findAny().orElseThrow(() -> new BusinessException("治疗计划为空,请先完成治疗计划")).getDisposalMethod();
// 首先创建一个病历
MedicalRecVertex medicalRecVertex = new MedicalRecVertex();
medicalRecVertex.setNodeValue((disposalMethod == 0 ? "门诊" : "住院") + "(" + DateUtil.format(process.getCreateTime(), "yyyy-MM-dd") + ")");
medicalRecDao.insert(medicalRecVertex);
log.info("病历图谱ID:{}", medicalRecVertex.getId());
// 根据processId找到电子病历
ProcessMedical processMedical = processMedicalService.lambdaQuery().eq(ProcessMedical::getProcessId, processId).oneOpt().orElseThrow(() -> new BusinessException("未找到电子病历"));
ProcessMedicalVertex processMedicalVertex = new ProcessMedicalVertex();
processMedicalVertex.setNodeValue("病历(" + DateUtil.format(processMedical.getCreateTime(), "yyyy-MM-dd") + ")");
processMedicalDao.insert(processMedicalVertex);
medicalRecDao.insertEdge(medicalRecVertex, new NoPropertyEdge(), processMedicalVertex);
// 创建主诉节点
if (StrUtil.isNotBlank(processMedical.getPatientSelfDesc())) {
SelfDescVertex selfDescVertex = new SelfDescVertex();
selfDescVertex.setNodeValue(processMedical.getPatientSelfDesc());
selfDescDao.insert(selfDescVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), selfDescVertex);
}
// 创建现病史节点
if (StrUtil.isNotBlank(processMedical.getIllnessHistory())) {
IllnessHistoryVertex illnessHistoryVertex = new IllnessHistoryVertex();
illnessHistoryVertex.setNodeValue(processMedical.getIllnessHistory());
illnessHistoryDao.insert(illnessHistoryVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), illnessHistoryVertex);
}
// 创建个人史节点
if (StrUtil.isNotBlank(processMedical.getPersonalHistory())) {
PersonalHistoryVertex personalHistoryVertex = new PersonalHistoryVertex();
personalHistoryVertex.setNodeValue(processMedical.getPersonalHistory());
personalHistoryDao.insert(personalHistoryVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), personalHistoryVertex);
}
// 创建过敏史节点
if (ObjectUtil.isNotEmpty(processMedical.getAllergyHistoryFlag()) && 1 == processMedical.getAllergyHistoryFlag() && StrUtil.isNotBlank(processMedical.getAllergyHistory())) {
AllergyHistoryVertex allergyHistoryVertex = new AllergyHistoryVertex();
allergyHistoryVertex.setNodeValue(processMedical.getAllergyHistory());
allergyHistoryDao.insert(allergyHistoryVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), allergyHistoryVertex);
}
// 创建既往史节点
if (ObjectUtil.isNotEmpty(processMedical.getPreviousHistoryFlag()) && 1 == processMedical.getPreviousHistoryFlag() && StrUtil.isNotBlank(processMedical.getPreviousHistory())) {
PreviousHistoryVertex previousHistoryVertex = new PreviousHistoryVertex();
previousHistoryVertex.setNodeValue(processMedical.getPreviousHistory());
previousHistoryDao.insert(previousHistoryVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), previousHistoryVertex);
}
// 创建家族史节点
if (ObjectUtil.isNotEmpty(processMedical.getFamilyHistoryFlag()) && 1 == processMedical.getFamilyHistoryFlag() && StrUtil.isNotBlank(processMedical.getFamilyHistory())) {
FamilyHistoryVertex familyHistoryVertex = new FamilyHistoryVertex();
familyHistoryVertex.setNodeValue(processMedical.getFamilyHistory());
familyHistoryDao.insert(familyHistoryVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), familyHistoryVertex);
}
// 创建手术史节点
if (ObjectUtil.isNotEmpty(processMedical.getOperationHistory()) && 1 == processMedical.getOperationHistoryFlag() && StrUtil.isNotBlank(processMedical.getOperationHistory())) {
OperationHistoryVertex operationHistoryVertex = new OperationHistoryVertex();
operationHistoryVertex.setNodeValue(processMedical.getOperationHistory());
operationHistoryDao.insert(operationHistoryVertex);
// 保存节点之间的关系
processMedicalDao.insertEdge(processMedicalVertex, new NoPropertyEdge(), operationHistoryVertex);
}
// 创建患者节点
MedicalRec medicalRec = Optional.ofNullable(medicalRecService.getById(process.getMedicalRecId())).orElseThrow(() -> new BusinessException("未找到病历"));
PatientVertex patientVertex = new PatientVertex();
patientVertex.setNodeValue(medicalRec.getPatientName());
patientDao.insert(patientVertex);
processMedicalDao.insertEdge(medicalRecVertex, new NoPropertyEdge(), patientVertex);
// 创建体格检查节点(physicalId不为空,即为只查配置了检查结果的结果)
List<DiagnosisPhysicalRecord> physicalRecordList = diagnosisPhysicalRecordService.lambdaQuery()
.isNotNull(DiagnosisPhysicalRecord::getPhysicalId)
.eq(DiagnosisPhysicalRecord::getProcessId, processId).list();
Map<String, PhysicalVertex> physicalConfirmMap = new HashMap<>();
for (DiagnosisPhysicalRecord physicalRecord : physicalRecordList) {
ConfigPhysicalTool tool = configPhysicalToolService.getById(physicalRecord.getToolId());
ConfigPhysicalLocation location = null;
if (StrUtil.isNotBlank(physicalRecord.getLocationId())) {
location = configPhysicalLocationService.getById(physicalRecord.getLocationId());
}
if (ObjectUtil.isNotEmpty(tool)) {
PhysicalVertex physicalVertex = new PhysicalVertex();
physicalVertex.setNodeValue(tool.getToolName() + (location != null ? ("-" + location.getLocationName()) : ""));
physicalDao.insert(physicalVertex);
processMedicalDao.insertEdge(medicalRecVertex, new NoPropertyEdge(), physicalVertex);
// 如果是证实诊断依据,添加到证实诊断依据里面去
if (NumberUtil.equals(1, physicalRecord.getBasisConfirmFlag())) {
physicalConfirmMap.put(physicalRecord.getId(), physicalVertex);
}
}
}
// 创建辅助检查节点
List<DiagnosisAncillaryRecord> ancillaryRecordList = diagnosisAncillaryRecordService.lambdaQuery()
.isNotNull(DiagnosisAncillaryRecord::getAncillaryId)
.eq(DiagnosisAncillaryRecord::getProcessId, processId).list();
Map<String, AncillaryVertex> ancillaryConfirmMap = new HashMap<>();
for (DiagnosisAncillaryRecord diagnosisAncillaryRecord : ancillaryRecordList) {
ConfigAncillaryItem configAncillaryItem = configAncillaryItemService.getById(diagnosisAncillaryRecord.getItemId());
if (ObjectUtil.isNotEmpty(configAncillaryItem)) {
AncillaryVertex ancillaryVertex = new AncillaryVertex();
ancillaryVertex.setNodeValue(configAncillaryItem.getItemName());
ancillaryDao.insert(ancillaryVertex);
// 保存病历和辅助检查连线
medicalRecDao.insertEdge(medicalRecVertex, new NoPropertyEdge(), ancillaryVertex);
// 结果节点
AncillaryResultVertex ancillaryResultVertex = new AncillaryResultVertex();
ancillaryResultVertex.setNodeValue(diagnosisAncillaryRecord.getResult());
ancillaryResultDao.insert(ancillaryResultVertex);
// 保存检查结果连线
ancillaryDao.insertEdge(ancillaryVertex, new SinglePropertyEdge("结果"), ancillaryResultVertex);
// 如果是证实诊断依据,添加到证实诊断依据里面去
if (NumberUtil.equals(1, diagnosisAncillaryRecord.getBasisConfirmFlag())) {
ancillaryConfirmMap.put(diagnosisAncillaryRecord.getId(), ancillaryVertex);
}
}
}
// 创建诊断节点(这个诊断是最终诊断)
List<DiagnosisPrimary> diagnosisPrimaryList = diagnosisPrimaryService.lambdaQuery().eq(DiagnosisPrimary::getProcessId, processId).eq(DiagnosisPrimary::getExcludeFlag, 1).list();
Map<String, DiagnosisVertex> diagnosisMap = new HashMap<>();
for (DiagnosisPrimary diagnosisPrimary : diagnosisPrimaryList) {
Disease disease = diseaseService.getById(diagnosisPrimary.getPrimaryDiagnosisId());
if (ObjectUtil.isNotEmpty(disease)) {
DiagnosisVertex diagnosisVertex = new DiagnosisVertex();
diagnosisVertex.setNodeValue(disease.getDiseaseNameAlias());
diagnosisDao.insert(diagnosisVertex);
diagnosisDao.insertEdge(medicalRecVertex, new SinglePropertyEdge("诊断"), diagnosisVertex);
diagnosisMap.put(diagnosisPrimary.getId(), diagnosisVertex);
}
}
// 获取诊断和体格检查及辅助检查之间的关联关系
List<DiagnosisPrimaryRelation> relationList = diagnosisPrimaryRelationService.lambdaQuery().eq(DiagnosisPrimaryRelation::getProcessId, processId).list();
for (DiagnosisPrimaryRelation relation : relationList) {
// 根据关系找到对应的疾病诊断节点
DiagnosisVertex diagnosisVertex = diagnosisMap.get(relation.getPrimaryId());
if (ObjectUtil.isNotEmpty(diagnosisVertex)) {
// 如果疾病诊断不为空,则根据relationId,查询对应的体格检查节点
PhysicalVertex physicalVertex = physicalConfirmMap.get(relation.getRelationId());
if (ObjectUtil.isNotEmpty(physicalVertex)) {
physicalDao.insertEdge(diagnosisVertex, new SinglePropertyEdge("依据"), physicalVertex);
}
// 再尝试找辅助检查
AncillaryVertex ancillaryVertex = ancillaryConfirmMap.get(relation.getRelationId());
if (ObjectUtil.isNotEmpty(ancillaryVertex)) {
ancillaryDao.insertEdge(diagnosisVertex, new SinglePropertyEdge("依据"), ancillaryVertex);
}
}
}
log.info("病历图谱ID:{}", medicalRecVertex.getId());
processService.lambdaUpdate().set(Process::getGraphId, medicalRecVertex.getId()).eq(Process::getId, processId).update();
return medicalRecVertex.getId();
}
@Override
public GraphVO queryGraph(String processId, Integer level) {
Process process = Optional.ofNullable(processService.getById(processId)).orElseThrow(() -> new BusinessException("未找到对应的问诊流程"));
// 如果图谱ID为空,则创建图谱
if (StrUtil.isEmpty(process.getGraphId())) {
String graphId = creatGraphByNebula(processId);
process.setGraphId(graphId);
}
List<NgSubgraph<String>> subgraphList = medicalRecDao.selectSubgraph(process.getGraphId());
List<NodeVO> nodeList = new ArrayList<>();
List<EdgeVO> edgeList = new ArrayList<>();
for (NgSubgraph<String> subgraph : subgraphList) {
// 首先构建点
List<NgVertex<String>> vertexes = subgraph.getVertexes();
for (NgVertex<String> vertex : vertexes) {
NodeVO nodeVO = new NodeVO();
String vid = vertex.getVid();
nodeVO.setId(vid);
Map<String, Object> properties = vertex.getProperties();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
TagEnum tagEnum = TagEnum.valueOf(key);
if (ObjectUtil.isNotEmpty(tagEnum)) {
nodeVO.setNodeType(tagEnum.name());
nodeVO.setNodeDesc(tagEnum.getType());
nodeVO.setNodeColour(tagEnum.getColour());
nodeVO.setNodeLevel(tagEnum.getLevel());
}
Object value = entry.getValue();
if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
Object nodeValue = map.get("nodeValue");
if (ObjectUtil.isNotEmpty(nodeValue)) {
// 判断是不是富文本,如果是富文本,则desc放数据值
if (StrUtil.startWith(String.valueOf(nodeValue), "<")) {
nodeVO.setNodeDesc(String.valueOf(nodeValue));
nodeVO.setNodeValue(tagEnum.getType());
} else {
nodeVO.setNodeValue(String.valueOf(nodeValue));
}
}
map.forEach((k, v) -> {
if (ObjectUtil.isNotEmpty(k)) {
nodeVO.getParams().put(String.valueOf(k), v);
}
});
}
}
// 校验级别(根据参数的级别来进行判断)
if (ObjectUtil.isNotEmpty(level)) {
if (level >= nodeVO.getNodeLevel()) {
nodeList.add(nodeVO);
}
} else {
nodeList.add(nodeVO);
}
}
// 构建边
List<NgEdge<String>> edges = subgraph.getEdges();
for (NgEdge<String> edge : edges) {
EdgeVO edgeVO = new EdgeVO();
edgeVO.setSource(edge.getSrcID());
edgeVO.setTarget(edge.getDstID());
Map<String, Object> properties = edge.getProperties();
Object nameObject = properties.get("edgeValue");
if (ObjectUtil.isNotEmpty(nameObject)) {
edgeVO.setName(String.valueOf(nameObject));
edgeVO.setLabel(edgeVO.getName());
}
edgeVO.setParams(properties);
edgeList.add(edgeVO);
}
}
// 这里,需要遍历,把重点不存在的节点连线给删掉
Set<String> nodeIdSet = nodeList.stream().map(NodeVO::getId).collect(Collectors.toSet());
// 如果指向的节点不存在,那么这个边也不存在
edgeList.removeIf(edgeVO -> !nodeIdSet.contains(edgeVO.getTarget()));
return new GraphVO(nodeList, edgeList);
}
@Override
public List<TreeNodeVO> queryTreeGraph(String processId, Integer level) {
GraphVO graphVO = queryGraph(processId, level);
List<TreeNodeVO> treeNodeList = graphVO.getNodes().stream().map(node -> BeanUtil.toBean(node, TreeNodeVO.class)).collect(Collectors.toList());
// 首先找到第一级节点
List<TreeNodeVO> firstNodeList = treeNodeList.stream().filter(node -> node.getNodeLevel() == 1).collect(Collectors.toList());
if (CollUtil.isEmpty(firstNodeList)) {
return null;
}
// treeNodeList转换为map,key为节点ID,value为节点对象
Map<String, TreeNodeVO> treeNodeMap = treeNodeList.stream().collect(Collectors.toMap(TreeNodeVO::getId, Function.identity()));
for (TreeNodeVO nodeVO : firstNodeList) {
recursionBuildTree(nodeVO, treeNodeMap, graphVO.getEdges());
}
// 为所有节点分配新的唯一ID(前端需要ID字段为唯一ID)
Snowflake snowflake = IdUtil.getSnowflake(1);
// 先转JSON,再转回去.为了避免出现对象复用的情况,导致ID不能分配为唯一ID
String jsonStr = JSONUtil.toJsonStr(firstNodeList);
List<TreeNodeVO> newTreeNodeList = JSONUtil.toList(jsonStr, TreeNodeVO.class);
recursionGenerateSingleId(newTreeNodeList, snowflake);
return newTreeNodeList;
}
/**
* 为属性结构构建新的唯一ID,把原先的ID迁移到GraphId
*/
private void recursionGenerateSingleId(List<TreeNodeVO> firstNodeList, Snowflake snowflake) {
for (TreeNodeVO treeNodeVO : firstNodeList) {
if (StrUtil.isBlank(treeNodeVO.getGraphId())) {
treeNodeVO.setGraphId(treeNodeVO.getId());
}
String nextId = snowflake.nextIdStr() + "-id";
treeNodeVO.setId(nextId);
if (CollUtil.isNotEmpty(treeNodeVO.getChildren())) {
recursionGenerateSingleId(treeNodeVO.getChildren(), snowflake);
}
}
}
private void recursionBuildTree(TreeNodeVO preNode, Map<String, TreeNodeVO> treeNodeMap, List<EdgeVO> edgeList) {
// 通过preNode的ID找到所有的子节点
List<TreeNodeVO> childNode = new ArrayList<>();
for (EdgeVO edgeVO : edgeList) {
if (StrUtil.equals(edgeVO.getSource(), preNode.getId())) {
TreeNodeVO treeNodeVO = treeNodeMap.get(edgeVO.getTarget());
if (ObjectUtil.isNotEmpty(treeNodeVO)) {
childNode.add(treeNodeVO);
}
;
}
}
preNode.setChildren(childNode);
for (TreeNodeVO treeNodeVO : childNode) {
recursionBuildTree(treeNodeVO, treeNodeMap, edgeList);
}
}
}