feat(kg): 推理引擎+数据导入
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.healthlink.his.web.knowledgegraph.appservice;
|
||||
|
||||
import com.healthlink.his.web.knowledgegraph.dto.ImportResultDto;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public interface IKgDataImportAppService {
|
||||
|
||||
ImportResultDto importDiseaseFromCsv(MultipartFile file);
|
||||
|
||||
ImportResultDto importDrugFromCsv(MultipartFile file);
|
||||
|
||||
ImportResultDto importRelationsFromCsv(MultipartFile file);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.healthlink.his.web.knowledgegraph.appservice;
|
||||
|
||||
import com.healthlink.his.web.knowledgegraph.dto.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IKgReasoningAppService {
|
||||
|
||||
List<DiagnosisResultDto> suggestDiagnosis(List<String> symptoms, Integer topN);
|
||||
|
||||
List<ExaminationResultDto> suggestExaminations(String diseaseCode, Integer topN);
|
||||
|
||||
List<DrugInteractionResultDto> checkDrugInteractions(List<String> drugCodes);
|
||||
|
||||
Map<String, Object> suggestPathway(String diseaseCode);
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.healthlink.his.web.knowledgegraph.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.clinical.domain.KgEntityRelation;
|
||||
import com.healthlink.his.clinical.service.IKgEntityRelationService;
|
||||
import com.healthlink.his.knowledgegraph.domain.KgDisease;
|
||||
import com.healthlink.his.knowledgegraph.domain.KgDrug;
|
||||
import com.healthlink.his.knowledgegraph.service.IKgDiseaseService;
|
||||
import com.healthlink.his.knowledgegraph.service.IKgDrugService;
|
||||
import com.healthlink.his.web.knowledgegraph.appservice.IKgDataImportAppService;
|
||||
import com.healthlink.his.web.knowledgegraph.dto.ImportResultDto;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KgDataImportAppServiceImpl implements IKgDataImportAppService {
|
||||
|
||||
@Autowired
|
||||
private IKgDiseaseService diseaseService;
|
||||
@Autowired
|
||||
private IKgDrugService drugService;
|
||||
@Autowired
|
||||
private IKgEntityRelationService relationService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ImportResultDto importDiseaseFromCsv(MultipartFile file) {
|
||||
ImportResultDto result = new ImportResultDto();
|
||||
List<KgDisease> batch = new ArrayList<>();
|
||||
int totalRows = 0;
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String header = reader.readLine();
|
||||
if (header == null) {
|
||||
result.setMessage("CSV文件为空");
|
||||
result.setTotalRows(0);
|
||||
result.setSuccessCount(0);
|
||||
result.setFailCount(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
totalRows++;
|
||||
try {
|
||||
String[] parts = parseCsvLine(line);
|
||||
if (parts.length < 2) {
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
KgDisease disease = new KgDisease();
|
||||
disease.setDiseaseCode(getField(parts, 0));
|
||||
disease.setDiseaseName(getField(parts, 1));
|
||||
disease.setCategory(getField(parts, 2));
|
||||
disease.setDepartment(getField(parts, 3));
|
||||
disease.setSeverityLevel(getField(parts, 4));
|
||||
disease.setDescription(getField(parts, 5));
|
||||
disease.setKeywords(getField(parts, 6));
|
||||
|
||||
if (hasText(disease.getDiseaseCode()) && hasText(disease.getDiseaseName())) {
|
||||
batch.add(disease);
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析疾病CSV行失败: {}", line, e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!batch.isEmpty()) {
|
||||
diseaseService.saveBatch(batch);
|
||||
successCount = batch.size();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("导入疾病CSV失败", e);
|
||||
failCount = totalRows - successCount;
|
||||
}
|
||||
|
||||
result.setTotalRows(totalRows);
|
||||
result.setSuccessCount(successCount);
|
||||
result.setFailCount(failCount);
|
||||
result.setMessage(String.format("导入完成: 共%d行, 成功%d条, 失败%d条", totalRows, successCount, failCount));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ImportResultDto importDrugFromCsv(MultipartFile file) {
|
||||
ImportResultDto result = new ImportResultDto();
|
||||
List<KgDrug> batch = new ArrayList<>();
|
||||
int totalRows = 0;
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String header = reader.readLine();
|
||||
if (header == null) {
|
||||
result.setMessage("CSV文件为空");
|
||||
result.setTotalRows(0);
|
||||
result.setSuccessCount(0);
|
||||
result.setFailCount(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
totalRows++;
|
||||
try {
|
||||
String[] parts = parseCsvLine(line);
|
||||
if (parts.length < 2) {
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
KgDrug drug = new KgDrug();
|
||||
drug.setDrugCode(getField(parts, 0));
|
||||
drug.setDrugName(getField(parts, 1));
|
||||
drug.setGenericName(getField(parts, 2));
|
||||
drug.setCategory(getField(parts, 3));
|
||||
drug.setDosageForm(getField(parts, 4));
|
||||
drug.setContraindications(getField(parts, 5));
|
||||
drug.setSideEffects(getField(parts, 6));
|
||||
|
||||
if (hasText(drug.getDrugCode()) && hasText(drug.getDrugName())) {
|
||||
batch.add(drug);
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析药物CSV行失败: {}", line, e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!batch.isEmpty()) {
|
||||
drugService.saveBatch(batch);
|
||||
successCount = batch.size();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("导入药物CSV失败", e);
|
||||
failCount = totalRows - successCount;
|
||||
}
|
||||
|
||||
result.setTotalRows(totalRows);
|
||||
result.setSuccessCount(successCount);
|
||||
result.setFailCount(failCount);
|
||||
result.setMessage(String.format("导入完成: 共%d行, 成功%d条, 失败%d条", totalRows, successCount, failCount));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ImportResultDto importRelationsFromCsv(MultipartFile file) {
|
||||
ImportResultDto result = new ImportResultDto();
|
||||
List<KgEntityRelation> batch = new ArrayList<>();
|
||||
int totalRows = 0;
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String header = reader.readLine();
|
||||
if (header == null) {
|
||||
result.setMessage("CSV文件为空");
|
||||
result.setTotalRows(0);
|
||||
result.setSuccessCount(0);
|
||||
result.setFailCount(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
totalRows++;
|
||||
try {
|
||||
String[] parts = parseCsvLine(line);
|
||||
if (parts.length < 5) {
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
KgEntityRelation relation = new KgEntityRelation();
|
||||
relation.setSourceType(getField(parts, 0));
|
||||
relation.setSourceId(getField(parts, 1));
|
||||
relation.setTargetType(getField(parts, 2));
|
||||
relation.setTargetId(getField(parts, 3));
|
||||
relation.setRelationType(getField(parts, 4));
|
||||
|
||||
String strengthStr = getField(parts, 5);
|
||||
if (hasText(strengthStr)) {
|
||||
try {
|
||||
relation.setRelationStrength(new BigDecimal(strengthStr));
|
||||
} catch (NumberFormatException e) {
|
||||
relation.setRelationStrength(BigDecimal.ONE);
|
||||
}
|
||||
} else {
|
||||
relation.setRelationStrength(BigDecimal.ONE);
|
||||
}
|
||||
relation.setDescription(getField(parts, 6));
|
||||
relation.setEvidenceSource(getField(parts, 7));
|
||||
|
||||
if (hasText(relation.getSourceType()) && hasText(relation.getSourceId())
|
||||
&& hasText(relation.getTargetType()) && hasText(relation.getTargetId())) {
|
||||
batch.add(relation);
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析关系CSV行失败: {}", line, e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!batch.isEmpty()) {
|
||||
relationService.saveBatch(batch);
|
||||
successCount = batch.size();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("导入关系CSV失败", e);
|
||||
failCount = totalRows - successCount;
|
||||
}
|
||||
|
||||
result.setTotalRows(totalRows);
|
||||
result.setSuccessCount(successCount);
|
||||
result.setFailCount(failCount);
|
||||
result.setMessage(String.format("导入完成: 共%d行, 成功%d条, 失败%d条", totalRows, successCount, failCount));
|
||||
return result;
|
||||
}
|
||||
|
||||
private String[] parseCsvLine(String line) {
|
||||
List<String> fields = new ArrayList<>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
|
||||
for (char c : line.toCharArray()) {
|
||||
if (c == '"') {
|
||||
inQuotes = !inQuotes;
|
||||
} else if (c == ',' && !inQuotes) {
|
||||
fields.add(current.toString().trim());
|
||||
current = new StringBuilder();
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
fields.add(current.toString().trim());
|
||||
return fields.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private String getField(String[] parts, int index) {
|
||||
if (index < parts.length) {
|
||||
String val = parts[index];
|
||||
return hasText(val) ? val : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasText(String s) {
|
||||
return s != null && !s.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.healthlink.his.web.knowledgegraph.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.clinical.domain.KgClinicalPathway;
|
||||
import com.healthlink.his.clinical.domain.KgEntityRelation;
|
||||
import com.healthlink.his.clinical.domain.KgPathwayStep;
|
||||
import com.healthlink.his.clinical.service.IKgClinicalPathwayService;
|
||||
import com.healthlink.his.clinical.service.IKgEntityRelationService;
|
||||
import com.healthlink.his.clinical.service.IKgPathwayStepService;
|
||||
import com.healthlink.his.knowledgegraph.domain.KgDisease;
|
||||
import com.healthlink.his.knowledgegraph.domain.KgDrug;
|
||||
import com.healthlink.his.knowledgegraph.domain.KgExamination;
|
||||
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
|
||||
import com.healthlink.his.knowledgegraph.service.IKgDiseaseService;
|
||||
import com.healthlink.his.knowledgegraph.service.IKgDrugService;
|
||||
import com.healthlink.his.knowledgegraph.service.IKgExaminationService;
|
||||
import com.healthlink.his.knowledgegraph.service.IKgSymptomService;
|
||||
import com.healthlink.his.web.knowledgegraph.appservice.IKgReasoningAppService;
|
||||
import com.healthlink.his.web.knowledgegraph.dto.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class KgReasoningAppServiceImpl implements IKgReasoningAppService {
|
||||
|
||||
@Autowired
|
||||
private IKgEntityRelationService relationService;
|
||||
@Autowired
|
||||
private IKgSymptomService symptomService;
|
||||
@Autowired
|
||||
private IKgDiseaseService diseaseService;
|
||||
@Autowired
|
||||
private IKgExaminationService examinationService;
|
||||
@Autowired
|
||||
private IKgDrugService drugService;
|
||||
@Autowired
|
||||
private IKgClinicalPathwayService pathwayService;
|
||||
@Autowired
|
||||
private IKgPathwayStepService pathwayStepService;
|
||||
|
||||
@Override
|
||||
public List<DiagnosisResultDto> suggestDiagnosis(List<String> symptoms, Integer topN) {
|
||||
if (symptoms == null || symptoms.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (topN == null || topN <= 0) {
|
||||
topN = 5;
|
||||
}
|
||||
|
||||
// 1. Find symptom IDs by name/code
|
||||
LambdaQueryWrapper<KgSymptom> sw = new LambdaQueryWrapper<>();
|
||||
sw.and(w -> {
|
||||
for (String symptom : symptoms) {
|
||||
w.or().like(KgSymptom::getSymptomName, symptom)
|
||||
.or().like(KgSymptom::getSymptomCode, symptom);
|
||||
}
|
||||
});
|
||||
List<KgSymptom> matchedSymptoms = symptomService.list(sw);
|
||||
if (matchedSymptoms.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> symptomIds = matchedSymptoms.stream()
|
||||
.map(s -> String.valueOf(s.getId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 2. Query relations: symptom -> disease
|
||||
LambdaQueryWrapper<KgEntityRelation> rw = new LambdaQueryWrapper<>();
|
||||
rw.eq(KgEntityRelation::getSourceType, "symptom")
|
||||
.eq(KgEntityRelation::getTargetType, "disease")
|
||||
.in(KgEntityRelation::getSourceId, symptomIds)
|
||||
.orderByDesc(KgEntityRelation::getRelationStrength);
|
||||
List<KgEntityRelation> relations = relationService.list(rw);
|
||||
|
||||
// 3. Group by disease, sum scores
|
||||
Map<String, BigDecimal> diseaseScoreMap = new LinkedHashMap<>();
|
||||
Map<String, List<String>> diseaseSymptomMap = new LinkedHashMap<>();
|
||||
for (KgEntityRelation rel : relations) {
|
||||
String diseaseId = rel.getTargetId();
|
||||
BigDecimal strength = rel.getRelationStrength() != null ? rel.getRelationStrength() : BigDecimal.ONE;
|
||||
diseaseScoreMap.merge(diseaseId, strength, BigDecimal::add);
|
||||
diseaseSymptomMap.computeIfAbsent(diseaseId, k -> new ArrayList<>())
|
||||
.add(rel.getSourceId());
|
||||
}
|
||||
|
||||
// 4. Sort by score descending, take top N
|
||||
List<Map.Entry<String, BigDecimal>> sorted = diseaseScoreMap.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
|
||||
.limit(topN)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 5. Build results with disease info
|
||||
List<DiagnosisResultDto> results = new ArrayList<>();
|
||||
for (Map.Entry<String, BigDecimal> entry : sorted) {
|
||||
KgDisease disease = diseaseService.getById(Long.parseLong(entry.getKey()));
|
||||
if (disease == null) continue;
|
||||
|
||||
DiagnosisResultDto dto = new DiagnosisResultDto();
|
||||
dto.setDiseaseCode(disease.getDiseaseCode());
|
||||
dto.setDiseaseName(disease.getDiseaseName());
|
||||
dto.setCategory(disease.getCategory());
|
||||
dto.setDepartment(disease.getDepartment());
|
||||
dto.setScore(entry.getValue());
|
||||
|
||||
List<String> matched = diseaseSymptomMap.getOrDefault(entry.getKey(), Collections.emptyList());
|
||||
dto.setMatchedSymptoms(String.join(",", matched));
|
||||
results.add(dto);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExaminationResultDto> suggestExaminations(String diseaseCode, Integer topN) {
|
||||
if (!StringUtils.hasText(diseaseCode)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (topN == null || topN <= 0) {
|
||||
topN = 10;
|
||||
}
|
||||
|
||||
// 1. Find disease by code
|
||||
LambdaQueryWrapper<KgDisease> dw = new LambdaQueryWrapper<>();
|
||||
dw.eq(KgDisease::getDiseaseCode, diseaseCode);
|
||||
KgDisease disease = diseaseService.getOne(dw);
|
||||
if (disease == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 2. Query relations: disease -> examination
|
||||
LambdaQueryWrapper<KgEntityRelation> rw = new LambdaQueryWrapper<>();
|
||||
rw.eq(KgEntityRelation::getSourceType, "disease")
|
||||
.eq(KgEntityRelation::getTargetType, "examination")
|
||||
.eq(KgEntityRelation::getSourceId, String.valueOf(disease.getId()))
|
||||
.orderByDesc(KgEntityRelation::getRelationStrength);
|
||||
List<KgEntityRelation> relations = relationService.list(rw);
|
||||
|
||||
// 3. Build results
|
||||
List<ExaminationResultDto> results = new ArrayList<>();
|
||||
for (KgEntityRelation rel : relations) {
|
||||
if (results.size() >= topN) break;
|
||||
KgExamination exam = examinationService.getById(Long.parseLong(rel.getTargetId()));
|
||||
if (exam == null) continue;
|
||||
|
||||
ExaminationResultDto dto = new ExaminationResultDto();
|
||||
dto.setExamCode(exam.getExamCode());
|
||||
dto.setExamName(exam.getExamName());
|
||||
dto.setExamType(exam.getExamType());
|
||||
dto.setClinicalSignificance(exam.getClinicalSignificance());
|
||||
dto.setScore(rel.getRelationStrength());
|
||||
results.add(dto);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DrugInteractionResultDto> checkDrugInteractions(List<String> drugCodes) {
|
||||
if (drugCodes == null || drugCodes.size() < 2) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 1. Find drug IDs by code
|
||||
LambdaQueryWrapper<KgDrug> dw = new LambdaQueryWrapper<>();
|
||||
dw.in(KgDrug::getDrugCode, drugCodes);
|
||||
List<KgDrug> drugs = drugService.list(dw);
|
||||
if (drugs.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Map<String, KgDrug> drugMap = drugs.stream()
|
||||
.collect(Collectors.toMap(KgDrug::getDrugCode, d -> d, (a, b) -> a));
|
||||
|
||||
List<String> drugIds = drugs.stream()
|
||||
.map(d -> String.valueOf(d.getId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 2. Query drug-drug interaction relations
|
||||
LambdaQueryWrapper<KgEntityRelation> rw = new LambdaQueryWrapper<>();
|
||||
rw.eq(KgEntityRelation::getSourceType, "drug")
|
||||
.eq(KgEntityRelation::getTargetType, "drug")
|
||||
.in(KgEntityRelation::getSourceId, drugIds)
|
||||
.in(KgEntityRelation::getTargetId, drugIds);
|
||||
List<KgEntityRelation> relations = relationService.list(rw);
|
||||
|
||||
// 3. Build results
|
||||
List<DrugInteractionResultDto> results = new ArrayList<>();
|
||||
Set<String> added = new HashSet<>();
|
||||
|
||||
for (KgEntityRelation rel : relations) {
|
||||
KgDrug drugA = drugService.getById(Long.parseLong(rel.getSourceId()));
|
||||
KgDrug drugB = drugService.getById(Long.parseLong(rel.getTargetId()));
|
||||
if (drugA == null || drugB == null) continue;
|
||||
|
||||
String key = Collections.min(Arrays.asList(drugA.getDrugCode(), drugB.getDrugCode()))
|
||||
+ "-" + Collections.max(Arrays.asList(drugA.getDrugCode(), drugB.getDrugCode()));
|
||||
if (!added.add(key)) continue;
|
||||
|
||||
DrugInteractionResultDto dto = new DrugInteractionResultDto();
|
||||
dto.setDrugCodeA(drugA.getDrugCode());
|
||||
dto.setDrugNameA(drugA.getDrugName());
|
||||
dto.setDrugCodeB(drugB.getDrugCode());
|
||||
dto.setDrugNameB(drugB.getDrugName());
|
||||
dto.setInteractionType(rel.getRelationType());
|
||||
dto.setDescription(rel.getDescription());
|
||||
dto.setSeverity(rel.getRelationStrength() != null
|
||||
? (rel.getRelationStrength().compareTo(new BigDecimal("0.7")) >= 0 ? "严重" : "一般")
|
||||
: "一般");
|
||||
results.add(dto);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> suggestPathway(String diseaseCode) {
|
||||
if (!StringUtils.hasText(diseaseCode)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
// 1. Find pathway by disease code
|
||||
LambdaQueryWrapper<KgClinicalPathway> pw = new LambdaQueryWrapper<>();
|
||||
pw.eq(KgClinicalPathway::getDiseaseCode, diseaseCode)
|
||||
.eq(KgClinicalPathway::getStatus, "ACTIVE");
|
||||
KgClinicalPathway pathway = pathwayService.getOne(pw);
|
||||
if (pathway == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
// 2. Get pathway steps
|
||||
LambdaQueryWrapper<KgPathwayStep> sw = new LambdaQueryWrapper<>();
|
||||
sw.eq(KgPathwayStep::getPathwayId, pathway.getId())
|
||||
.orderByAsc(KgPathwayStep::getStepOrder);
|
||||
List<KgPathwayStep> steps = pathwayStepService.list(sw);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("pathway", pathway);
|
||||
result.put("steps", steps);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.healthlink.his.web.knowledgegraph.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.web.knowledgegraph.appservice.IKgDataImportAppService;
|
||||
import com.healthlink.his.web.knowledgegraph.dto.ImportResultDto;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "知识图谱-数据导入")
|
||||
@RestController
|
||||
@RequestMapping("/knowledgegraph/import")
|
||||
@AllArgsConstructor
|
||||
public class KgDataImportController {
|
||||
|
||||
private final IKgDataImportAppService kgDataImportAppService;
|
||||
|
||||
@Operation(summary = "导入疾病数据")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
|
||||
@PostMapping("/disease")
|
||||
public R<ImportResultDto> importDisease(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
ImportResultDto result = kgDataImportAppService.importDiseaseFromCsv(file);
|
||||
return R.ok(result);
|
||||
} catch (Exception e) {
|
||||
log.error("导入疾病数据失败", e);
|
||||
return R.fail("导入疾病数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "导入药物数据")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
|
||||
@PostMapping("/drug")
|
||||
public R<ImportResultDto> importDrug(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
ImportResultDto result = kgDataImportAppService.importDrugFromCsv(file);
|
||||
return R.ok(result);
|
||||
} catch (Exception e) {
|
||||
log.error("导入药物数据失败", e);
|
||||
return R.fail("导入药物数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "导入关系数据")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
|
||||
@PostMapping("/relation")
|
||||
public R<ImportResultDto> importRelations(@RequestParam("file") MultipartFile file) {
|
||||
try {
|
||||
ImportResultDto result = kgDataImportAppService.importRelationsFromCsv(file);
|
||||
return R.ok(result);
|
||||
} catch (Exception e) {
|
||||
log.error("导入关系数据失败", e);
|
||||
return R.fail("导入关系数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "下载导入模板")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
|
||||
@GetMapping("/template/{type}")
|
||||
public void downloadTemplate(@PathVariable String type, jakarta.servlet.http.HttpServletResponse response) throws Exception {
|
||||
String filename;
|
||||
String content;
|
||||
|
||||
switch (type) {
|
||||
case "disease":
|
||||
filename = "疾病导入模板.csv";
|
||||
content = "疾病编码,疾病名称,分类,科室,严重等级,描述,关键词\n"
|
||||
+ "J06.900,急性上呼吸道感染,感染性疾病,呼吸内科,轻度,急性上呼吸道感染,发热;咳嗽;咽痛\n";
|
||||
break;
|
||||
case "drug":
|
||||
filename = "药物导入模板.csv";
|
||||
content = "药物编码,药物名称,通用名,分类,剂型,禁忌症,不良反应\n"
|
||||
+ "D00001,阿莫西林胶囊,阿莫西林,抗生素,胶囊剂,青霉素过敏者禁用,皮疹;腹泻\n";
|
||||
break;
|
||||
case "relation":
|
||||
filename = "关系导入模板.csv";
|
||||
content = "来源类型,来源ID,目标类型,目标ID,关系类型,关系强度,描述,证据来源\n"
|
||||
+ "symptom,1001,disease,2001,has_symptom,0.85,发热是急性上呼吸道感染的常见症状,临床指南\n";
|
||||
break;
|
||||
default:
|
||||
response.setStatus(400);
|
||||
response.getWriter().write("不支持的模板类型");
|
||||
return;
|
||||
}
|
||||
|
||||
response.setContentType("text/csv;charset=UTF-8");
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8));
|
||||
response.getOutputStream().write("\uFEFF".getBytes(StandardCharsets.UTF_8));
|
||||
response.getOutputStream().write(content.getBytes(StandardCharsets.UTF_8));
|
||||
response.getOutputStream().flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.healthlink.his.web.knowledgegraph.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.web.knowledgegraph.appservice.IKgReasoningAppService;
|
||||
import com.healthlink.his.web.knowledgegraph.dto.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "知识图谱-推理引擎")
|
||||
@RestController
|
||||
@RequestMapping("/knowledgegraph/reasoning")
|
||||
@AllArgsConstructor
|
||||
public class KgReasoningController {
|
||||
|
||||
private final IKgReasoningAppService kgReasoningAppService;
|
||||
|
||||
@Operation(summary = "诊断推荐 - 基于症状推荐诊断")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
|
||||
@PostMapping("/diagnosis")
|
||||
public R<List<DiagnosisResultDto>> suggestDiagnosis(@RequestBody DiagnosisSuggestDto dto) {
|
||||
try {
|
||||
List<DiagnosisResultDto> results = kgReasoningAppService.suggestDiagnosis(dto.getSymptoms(), dto.getTopN());
|
||||
return R.ok(results);
|
||||
} catch (Exception e) {
|
||||
log.error("诊断推荐失败", e);
|
||||
return R.fail("诊断推荐失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "检查推荐 - 基于诊断推荐检查")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
|
||||
@PostMapping("/examination")
|
||||
public R<List<ExaminationResultDto>> suggestExaminations(@RequestBody ExaminationSuggestDto dto) {
|
||||
try {
|
||||
List<ExaminationResultDto> results = kgReasoningAppService.suggestExaminations(dto.getDiseaseCode(), dto.getTopN());
|
||||
return R.ok(results);
|
||||
} catch (Exception e) {
|
||||
log.error("检查推荐失败", e);
|
||||
return R.fail("检查推荐失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "药物相互作用检查")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
|
||||
@PostMapping("/drug-interaction")
|
||||
public R<List<DrugInteractionResultDto>> checkDrugInteractions(@RequestBody DrugInteractionDto dto) {
|
||||
try {
|
||||
List<DrugInteractionResultDto> results = kgReasoningAppService.checkDrugInteractions(dto.getDrugCodes());
|
||||
return R.ok(results);
|
||||
} catch (Exception e) {
|
||||
log.error("药物相互作用检查失败", e);
|
||||
return R.fail("药物相互作用检查失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "临床路径推荐")
|
||||
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
|
||||
@GetMapping("/pathway/{diseaseCode}")
|
||||
public R<Map<String, Object>> suggestPathway(@PathVariable String diseaseCode) {
|
||||
try {
|
||||
Map<String, Object> result = kgReasoningAppService.suggestPathway(diseaseCode);
|
||||
return result.isEmpty() ? R.fail("未找到临床路径") : R.ok(result);
|
||||
} catch (Exception e) {
|
||||
log.error("临床路径推荐失败", e);
|
||||
return R.fail("临床路径推荐失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user