diff --git a/healthlink-his-mobile/.env.development b/healthlink-his-mobile/.env.development new file mode 100644 index 000000000..390ebe68b --- /dev/null +++ b/healthlink-his-mobile/.env.development @@ -0,0 +1,11 @@ +# 页面标题 +VITE_APP_TITLE = HealthLink移动护理 + +# 开发环境配置 +VITE_APP_ENV = 'development' + +# API地址 +VITE_APP_BASE_API = '/dev-api' + +# 后端代理地址 +VITE_API_PROXY = 'http://localhost:18080/healthlink-his' diff --git a/healthlink-his-mobile/.env.production b/healthlink-his-mobile/.env.production new file mode 100644 index 000000000..8196ebc02 --- /dev/null +++ b/healthlink-his-mobile/.env.production @@ -0,0 +1,8 @@ +# 页面标题 +VITE_APP_TITLE = HealthLink移动护理 + +# 生产环境配置 +VITE_APP_ENV = 'production' + +# API地址 +VITE_APP_BASE_API = '/dev-api' diff --git a/healthlink-his-mobile/.gitignore b/healthlink-his-mobile/.gitignore new file mode 100644 index 000000000..ef1ea7305 --- /dev/null +++ b/healthlink-his-mobile/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.env.local +.env.*.local +*.log +package-lock.json diff --git a/healthlink-his-mobile/index.html b/healthlink-his-mobile/index.html new file mode 100644 index 000000000..0eba5633c --- /dev/null +++ b/healthlink-his-mobile/index.html @@ -0,0 +1,14 @@ + + + + + + + + HealthLink 移动护理 + + +
+ + + diff --git a/healthlink-his-mobile/package.json b/healthlink-his-mobile/package.json new file mode 100644 index 000000000..59f85826b --- /dev/null +++ b/healthlink-his-mobile/package.json @@ -0,0 +1,29 @@ +{ + "name": "healthlink-his-mobile", + "version": "1.0.0", + "type": "module", + "description": "HealthLink-HIS 移动护理H5工作站", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:dev": "vite build", + "preview": "vite preview", + "lint": "echo 'No lint configured'" + }, + "dependencies": { + "vue": "^3.4.0", + "vue-router": "^4.3.0", + "pinia": "^2.1.0", + "axios": "^1.7.0", + "element-plus": "^2.7.0", + "echarts": "^5.5.0", + "js-cookie": "^3.0.5", + "nprogress": "^0.2.0", + "path-to-regexp": "^6.2.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "vite": "^5.4.0", + "sass": "^1.77.0" + } +} diff --git a/healthlink-his-mobile/src/App.vue b/healthlink-his-mobile/src/App.vue new file mode 100644 index 000000000..98240aef8 --- /dev/null +++ b/healthlink-his-mobile/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/healthlink-his-mobile/src/api/index.js b/healthlink-his-mobile/src/api/index.js new file mode 100644 index 000000000..854ca471b --- /dev/null +++ b/healthlink-his-mobile/src/api/index.js @@ -0,0 +1,57 @@ +import axios from 'axios' +import { ElMessage } from 'element-plus' + +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API || '/dev-api', + timeout: 30000 +}) + +service.interceptors.request.use(config => { + const token = localStorage.getItem('Admin-Token') + if (token && !(config.headers && config.headers.isToken === false)) { + config.headers.Authorization = 'Bearer ' + token + } + return config +}) + +service.interceptors.response.use( + response => { + const res = response.data + if (res.code === 401) { + localStorage.removeItem('Admin-Token') + localStorage.removeItem('userInfo') + window.location.href = '/login' + return Promise.reject(new Error('登录已过期')) + } + return res + }, + error => { + if (error.response?.status === 401) { + localStorage.removeItem('Admin-Token') + localStorage.removeItem('userInfo') + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +export const authApi = { + login: (data) => service.post('/login', data, { headers: { isToken: false } }), + getTenants: (username) => service.get('/system/tenant/user-bind/' + username, { headers: { isToken: false } }), + getAllTenants: () => service.get('/system/tenant/page', { headers: { isToken: false }, params: { pageSize: 100 } }), + getInfo: () => service.get('/getInfo') +} + +export const nursingApi = { + getTasks: (params) => service.get('/nurse-station/advice-process/page', { params }), + completeTask: (id, data) => service.post(`/nurse-station/advice-process/execute`, data), + getPatientInfo: (id) => service.get('/inpatientmanage/inhospitalregister/' + id), + getPatientList: (params) => service.get('/inpatientmanage/inhospitalregister/list', { params }), + getOrders: (encounterId) => service.get('/nurse-station/advice-process/page', { params: { encounterId } }), + getVitalSigns: (patientId) => service.get('/nursing/vital-signs/' + patientId), + submitVitalSign: (data) => service.post('/nursing/vital-sign', data), + getAssessments: (encounterId) => service.get('/nursing/assessment/encounter/' + encounterId), + submitAssessment: (data) => service.post('/nursing/assessment', data) +} + +export default service diff --git a/healthlink-his-mobile/src/main.js b/healthlink-his-mobile/src/main.js new file mode 100644 index 000000000..99de3f024 --- /dev/null +++ b/healthlink-his-mobile/src/main.js @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import 'element-plus/dist/index.css' +import App from './App.vue' +import router from './router' +import './styles/mobile.css' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(ElementPlus, { size: 'large', locale: zhCn }) +app.mount('#app') diff --git a/healthlink-his-mobile/src/router/index.js b/healthlink-his-mobile/src/router/index.js new file mode 100644 index 000000000..463e9d8a2 --- /dev/null +++ b/healthlink-his-mobile/src/router/index.js @@ -0,0 +1,22 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { path: '/login', component: () => import('../views/Login.vue'), meta: { title: '登录' } }, + { path: '/', redirect: '/mobile/home' }, + { path: '/mobile', component: () => import('../views/MobileLayout.vue'), meta: { requiresAuth: true }, children: [ + { path: 'home', component: () => import('../views/Home.vue'), meta: { title: '首页' } }, + { path: 'tasks', component: () => import('../views/TaskList.vue'), meta: { title: '任务列表' } }, + { path: 'patients', component: () => import('../views/PatientList.vue'), meta: { title: '患者列表' } }, + { path: 'patient-detail/:id', component: () => import('../views/PatientDetail.vue'), meta: { title: '患者详情' } }, + { path: 'vital-entry/:patientId', component: () => import('../views/VitalSignEntry.vue'), meta: { title: '生命体征录入' } }, + { path: 'assessment/:patientId', component: () => import('../views/AssessmentForm.vue'), meta: { title: '护理评估' } }, + { path: 'mine', component: () => import('../views/Mine.vue'), meta: { title: '我的' } } + ]} +] + +const router = createRouter({ history: createWebHistory(), routes }) +router.beforeEach((to, from, next) => { + if (to.meta.requiresAuth && !localStorage.getItem('Admin-Token')) { next('/login'); return } + next() +}) +export default router diff --git a/healthlink-his-mobile/src/styles/mobile.css b/healthlink-his-mobile/src/styles/mobile.css new file mode 100644 index 000000000..6df6d0b8d --- /dev/null +++ b/healthlink-his-mobile/src/styles/mobile.css @@ -0,0 +1,6 @@ +* { margin: 0; padding: 0; box-sizing: border-box; } +html, body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 16px; color: #333; background: #f5f5f5; -webkit-font-smoothing: antialiased; } +:root { --primary: #1890ff; --success: #52c41a; --warning: #fa8c16; --danger: #f5222d; --bg: #f5f5f5; --card: #fff; --border: #e8e8e8; } +input, button, textarea { font-family: inherit; font-size: inherit; } +button { cursor: pointer; -webkit-tap-highlight-color: transparent; } +::-webkit-scrollbar { display: none; } diff --git a/healthlink-his-mobile/src/views/AssessmentForm.vue b/healthlink-his-mobile/src/views/AssessmentForm.vue new file mode 100644 index 000000000..e52853aaf --- /dev/null +++ b/healthlink-his-mobile/src/views/AssessmentForm.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/Home.vue b/healthlink-his-mobile/src/views/Home.vue new file mode 100644 index 000000000..af57b384a --- /dev/null +++ b/healthlink-his-mobile/src/views/Home.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/Login.vue b/healthlink-his-mobile/src/views/Login.vue new file mode 100644 index 000000000..1ef88fa8a --- /dev/null +++ b/healthlink-his-mobile/src/views/Login.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/Mine.vue b/healthlink-his-mobile/src/views/Mine.vue new file mode 100644 index 000000000..93015e791 --- /dev/null +++ b/healthlink-his-mobile/src/views/Mine.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/MobileLayout.vue b/healthlink-his-mobile/src/views/MobileLayout.vue new file mode 100644 index 000000000..2ab54ec1a --- /dev/null +++ b/healthlink-his-mobile/src/views/MobileLayout.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/PatientDetail.vue b/healthlink-his-mobile/src/views/PatientDetail.vue new file mode 100644 index 000000000..667393b26 --- /dev/null +++ b/healthlink-his-mobile/src/views/PatientDetail.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/PatientList.vue b/healthlink-his-mobile/src/views/PatientList.vue new file mode 100644 index 000000000..af918d774 --- /dev/null +++ b/healthlink-his-mobile/src/views/PatientList.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/TaskList.vue b/healthlink-his-mobile/src/views/TaskList.vue new file mode 100644 index 000000000..4ca1fa8a0 --- /dev/null +++ b/healthlink-his-mobile/src/views/TaskList.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/healthlink-his-mobile/src/views/VitalSignEntry.vue b/healthlink-his-mobile/src/views/VitalSignEntry.vue new file mode 100644 index 000000000..5138b31c5 --- /dev/null +++ b/healthlink-his-mobile/src/views/VitalSignEntry.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/healthlink-his-mobile/vite.config.js b/healthlink-his-mobile/vite.config.js new file mode 100644 index 000000000..79464f8d7 --- /dev/null +++ b/healthlink-his-mobile/vite.config.js @@ -0,0 +1,42 @@ +import { defineConfig, loadEnv } from 'vite' +import path from 'path' +import vue from '@vitejs/plugin-vue' + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()) + return { + base: '/', + plugins: [vue()], + resolve: { + alias: { + '~': path.resolve(__dirname, './'), + '@': path.resolve(__dirname, './src') + }, + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] + }, + server: { + port: 82, + host: true, + proxy: { + '/dev-api': { + target: env.VITE_API_PROXY || 'http://localhost:18080/healthlink-his', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/dev-api/, '') + } + } + }, + build: { + outDir: 'dist', + assetsDir: 'assets', + cssMinify: 'esbuild' + }, + css: { + preprocessorOptions: { + scss: { + api: 'modern-compiler', + silenceDeprecations: ['import', 'global-builtin', 'color-functions', 'legacy-js-api'] + } + } + } + } +}) diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/IKgDataImportAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/IKgDataImportAppService.java new file mode 100644 index 000000000..13d15e98e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/IKgDataImportAppService.java @@ -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); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/IKgReasoningAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/IKgReasoningAppService.java new file mode 100644 index 000000000..cc0897194 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/IKgReasoningAppService.java @@ -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 suggestDiagnosis(List symptoms, Integer topN); + + List suggestExaminations(String diseaseCode, Integer topN); + + List checkDrugInteractions(List drugCodes); + + Map suggestPathway(String diseaseCode); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/impl/KgDataImportAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/impl/KgDataImportAppServiceImpl.java new file mode 100644 index 000000000..d2a8291ba --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/impl/KgDataImportAppServiceImpl.java @@ -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 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 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 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 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(); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/impl/KgReasoningAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/impl/KgReasoningAppServiceImpl.java new file mode 100644 index 000000000..3464524ea --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/appservice/impl/KgReasoningAppServiceImpl.java @@ -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 suggestDiagnosis(List 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 sw = new LambdaQueryWrapper<>(); + sw.and(w -> { + for (String symptom : symptoms) { + w.or().like(KgSymptom::getSymptomName, symptom) + .or().like(KgSymptom::getSymptomCode, symptom); + } + }); + List matchedSymptoms = symptomService.list(sw); + if (matchedSymptoms.isEmpty()) { + return Collections.emptyList(); + } + + List symptomIds = matchedSymptoms.stream() + .map(s -> String.valueOf(s.getId())) + .collect(Collectors.toList()); + + // 2. Query relations: symptom -> disease + LambdaQueryWrapper rw = new LambdaQueryWrapper<>(); + rw.eq(KgEntityRelation::getSourceType, "symptom") + .eq(KgEntityRelation::getTargetType, "disease") + .in(KgEntityRelation::getSourceId, symptomIds) + .orderByDesc(KgEntityRelation::getRelationStrength); + List relations = relationService.list(rw); + + // 3. Group by disease, sum scores + Map diseaseScoreMap = new LinkedHashMap<>(); + Map> 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> sorted = diseaseScoreMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(topN) + .collect(Collectors.toList()); + + // 5. Build results with disease info + List results = new ArrayList<>(); + for (Map.Entry 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 matched = diseaseSymptomMap.getOrDefault(entry.getKey(), Collections.emptyList()); + dto.setMatchedSymptoms(String.join(",", matched)); + results.add(dto); + } + return results; + } + + @Override + public List 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 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 rw = new LambdaQueryWrapper<>(); + rw.eq(KgEntityRelation::getSourceType, "disease") + .eq(KgEntityRelation::getTargetType, "examination") + .eq(KgEntityRelation::getSourceId, String.valueOf(disease.getId())) + .orderByDesc(KgEntityRelation::getRelationStrength); + List relations = relationService.list(rw); + + // 3. Build results + List 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 checkDrugInteractions(List drugCodes) { + if (drugCodes == null || drugCodes.size() < 2) { + return Collections.emptyList(); + } + + // 1. Find drug IDs by code + LambdaQueryWrapper dw = new LambdaQueryWrapper<>(); + dw.in(KgDrug::getDrugCode, drugCodes); + List drugs = drugService.list(dw); + if (drugs.isEmpty()) { + return Collections.emptyList(); + } + + Map drugMap = drugs.stream() + .collect(Collectors.toMap(KgDrug::getDrugCode, d -> d, (a, b) -> a)); + + List drugIds = drugs.stream() + .map(d -> String.valueOf(d.getId())) + .collect(Collectors.toList()); + + // 2. Query drug-drug interaction relations + LambdaQueryWrapper rw = new LambdaQueryWrapper<>(); + rw.eq(KgEntityRelation::getSourceType, "drug") + .eq(KgEntityRelation::getTargetType, "drug") + .in(KgEntityRelation::getSourceId, drugIds) + .in(KgEntityRelation::getTargetId, drugIds); + List relations = relationService.list(rw); + + // 3. Build results + List results = new ArrayList<>(); + Set 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 suggestPathway(String diseaseCode) { + if (!StringUtils.hasText(diseaseCode)) { + return Collections.emptyMap(); + } + + // 1. Find pathway by disease code + LambdaQueryWrapper 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 sw = new LambdaQueryWrapper<>(); + sw.eq(KgPathwayStep::getPathwayId, pathway.getId()) + .orderByAsc(KgPathwayStep::getStepOrder); + List steps = pathwayStepService.list(sw); + + Map result = new LinkedHashMap<>(); + result.put("pathway", pathway); + result.put("steps", steps); + return result; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/controller/KgDataImportController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/controller/KgDataImportController.java new file mode 100644 index 000000000..6881acba0 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/controller/KgDataImportController.java @@ -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 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 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 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(); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/controller/KgReasoningController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/controller/KgReasoningController.java new file mode 100644 index 000000000..60d9c59c1 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/controller/KgReasoningController.java @@ -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> suggestDiagnosis(@RequestBody DiagnosisSuggestDto dto) { + try { + List 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> suggestExaminations(@RequestBody ExaminationSuggestDto dto) { + try { + List 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> checkDrugInteractions(@RequestBody DrugInteractionDto dto) { + try { + List 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> suggestPathway(@PathVariable String diseaseCode) { + try { + Map result = kgReasoningAppService.suggestPathway(diseaseCode); + return result.isEmpty() ? R.fail("未找到临床路径") : R.ok(result); + } catch (Exception e) { + log.error("临床路径推荐失败", e); + return R.fail("临床路径推荐失败: " + e.getMessage()); + } + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DiagnosisResultDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DiagnosisResultDto.java new file mode 100644 index 000000000..bcc63d574 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DiagnosisResultDto.java @@ -0,0 +1,15 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class DiagnosisResultDto { + private String diseaseCode; + private String diseaseName; + private String category; + private String department; + private BigDecimal score; + private String matchedSymptoms; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DiagnosisSuggestDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DiagnosisSuggestDto.java new file mode 100644 index 000000000..5548e005c --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DiagnosisSuggestDto.java @@ -0,0 +1,13 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class DiagnosisSuggestDto { + private List symptoms; + private Integer topN = 5; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DrugInteractionDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DrugInteractionDto.java new file mode 100644 index 000000000..367625979 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DrugInteractionDto.java @@ -0,0 +1,12 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class DrugInteractionDto { + private List drugCodes; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DrugInteractionResultDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DrugInteractionResultDto.java new file mode 100644 index 000000000..f75973e01 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/DrugInteractionResultDto.java @@ -0,0 +1,14 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import lombok.Data; + +@Data +public class DrugInteractionResultDto { + private String drugCodeA; + private String drugNameA; + private String drugCodeB; + private String drugNameB; + private String interactionType; + private String description; + private String severity; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ExaminationResultDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ExaminationResultDto.java new file mode 100644 index 000000000..c6b80dee1 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ExaminationResultDto.java @@ -0,0 +1,14 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ExaminationResultDto { + private String examCode; + private String examName; + private String examType; + private String clinicalSignificance; + private BigDecimal score; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ExaminationSuggestDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ExaminationSuggestDto.java new file mode 100644 index 000000000..ae71f0ffd --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ExaminationSuggestDto.java @@ -0,0 +1,11 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ExaminationSuggestDto { + private String diseaseCode; + private Integer topN = 10; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ImportResultDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ImportResultDto.java new file mode 100644 index 000000000..25beaf14b --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/ImportResultDto.java @@ -0,0 +1,11 @@ +package com.healthlink.his.web.knowledgegraph.dto; + +import lombok.Data; + +@Data +public class ImportResultDto { + private int successCount; + private int failCount; + private int totalRows; + private String message; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDiseaseDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDiseaseDto.java index 86c728731..f5b1c02bb 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDiseaseDto.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDiseaseDto.java @@ -24,4 +24,8 @@ public class KgDiseaseDto implements Serializable { private String department; private String severityLevel; + + private String description; + + private String keywords; } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDrugDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDrugDto.java index dc2de755d..9d393c5ec 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDrugDto.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgDrugDto.java @@ -26,4 +26,6 @@ public class KgDrugDto implements Serializable { private String dosageForm; private String contraindications; + + private String sideEffects; } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgExaminationDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgExaminationDto.java index c79663d5d..ef82c1343 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgExaminationDto.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgExaminationDto.java @@ -24,4 +24,6 @@ public class KgExaminationDto implements Serializable { private String department; private String referenceRange; + + private String clinicalSignificance; } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgSymptomDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgSymptomDto.java index 3cbbf5440..55486c403 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgSymptomDto.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/knowledgegraph/dto/KgSymptomDto.java @@ -22,4 +22,6 @@ public class KgSymptomDto implements Serializable { private String bodyPart; private String symptomType; + + private String severityIndicator; } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/appservice/IMpNursingAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/appservice/IMpNursingAppService.java new file mode 100644 index 000000000..c35571c72 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/appservice/IMpNursingAppService.java @@ -0,0 +1,57 @@ +package com.healthlink.his.web.miniprogram.appservice; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.miniprogram.dto.AssessmentSubmitDto; +import com.healthlink.his.web.miniprogram.dto.TaskCompleteDto; +import com.healthlink.his.web.miniprogram.dto.VitalSignSubmitDto; + +/** + * 移动护理小程序 AppService + */ +public interface IMpNursingAppService { + + /** + * 获取护士任务列表 + * @param nurseId 护士ID + * @param status 任务状态(可选) + */ + R getTaskList(Long nurseId, String status); + + /** + * 完成任务 + * @param taskId 任务ID + * @param dto 完成结果 + */ + R completeTask(Long taskId, TaskCompleteDto dto); + + /** + * 获取患者信息(精简版) + * @param patientId 患者ID + */ + R getPatientInfo(Long patientId); + + /** + * 获取生命体征趋势 + * @param patientId 患者ID + * @param days 查询天数(默认7天) + */ + R getVitalSigns(Long patientId, Integer days); + + /** + * 录入生命体征 + * @param dto 体征数据 + */ + R submitVitalSign(VitalSignSubmitDto dto); + + /** + * 获取评估记录列表 + * @param patientId 患者ID + */ + R getAssessmentList(Long patientId); + + /** + * 提交护理评估 + * @param dto 评估数据 + */ + R submitAssessment(AssessmentSubmitDto dto); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/appservice/impl/MpNursingAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/appservice/impl/MpNursingAppServiceImpl.java new file mode 100644 index 000000000..402e2621b --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/appservice/impl/MpNursingAppServiceImpl.java @@ -0,0 +1,171 @@ +package com.healthlink.his.web.miniprogram.appservice.impl; + +import com.core.common.core.domain.R; +import com.healthlink.his.miniprogram.domain.MpAssessmentRecord; +import com.healthlink.his.miniprogram.domain.MpNursingTask; +import com.healthlink.his.miniprogram.domain.MpVitalSignRecord; +import com.healthlink.his.miniprogram.mapper.MpAssessmentRecordMapper; +import com.healthlink.his.miniprogram.mapper.MpNursingTaskMapper; +import com.healthlink.his.miniprogram.mapper.MpVitalSignRecordMapper; +import com.healthlink.his.miniprogram.service.IMpAssessmentRecordService; +import com.healthlink.his.miniprogram.service.IMpNursingTaskService; +import com.healthlink.his.miniprogram.service.IMpVitalSignRecordService; +import com.healthlink.his.web.miniprogram.appservice.IMpNursingAppService; +import com.healthlink.his.web.miniprogram.dto.AssessmentSubmitDto; +import com.healthlink.his.web.miniprogram.dto.TaskCompleteDto; +import com.healthlink.his.web.miniprogram.dto.VitalSignSubmitDto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 移动护理小程序 AppService实现 + */ +@Slf4j +@Service +public class MpNursingAppServiceImpl implements IMpNursingAppService { + + @Resource + private IMpNursingTaskService nursingTaskService; + + @Resource + private MpNursingTaskMapper nursingTaskMapper; + + @Resource + private IMpVitalSignRecordService vitalSignRecordService; + + @Resource + private MpVitalSignRecordMapper vitalSignRecordMapper; + + @Resource + private IMpAssessmentRecordService assessmentRecordService; + + @Resource + private MpAssessmentRecordMapper assessmentRecordMapper; + + @Override + @Transactional(readOnly = true) + public R getTaskList(Long nurseId, String status) { + List tasks = nursingTaskMapper.selectTaskListByNurse(nurseId, status); + long pendingCount = tasks.stream().filter(t -> "PENDING".equals(t.getTaskStatus())).count(); + long inProgressCount = tasks.stream().filter(t -> "IN_PROGRESS".equals(t.getTaskStatus())).count(); + long completedCount = tasks.stream().filter(t -> "COMPLETED".equals(t.getTaskStatus())).count(); + + return R.ok(Map.of( + "tasks", tasks, + "summary", Map.of( + "pending", pendingCount, + "inProgress", inProgressCount, + "completed", completedCount, + "total", tasks.size() + ) + )); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R completeTask(Long taskId, TaskCompleteDto dto) { + MpNursingTask task = nursingTaskService.getById(taskId); + if (task == null) { + return R.fail("任务不存在"); + } + if ("COMPLETED".equals(task.getTaskStatus())) { + return R.fail("任务已完成"); + } + + task.setTaskStatus("COMPLETED"); + task.setCompleteTime(LocalDateTime.now()); + nursingTaskService.updateById(task); + + log.info("任务完成: taskId={}, nurseId={}, result={}", taskId, task.getNurseId(), dto.getResult()); + return R.ok("任务已完成"); + } + + @Override + @Transactional(readOnly = true) + public R getPatientInfo(Long patientId) { + return R.ok(Map.of( + "patientId", patientId, + "message", "患者信息查询待接入基础数据模块" + )); + } + + @Override + @Transactional(readOnly = true) + public R getVitalSigns(Long patientId, Integer days) { + if (days == null || days <= 0) { + days = 7; + } + List records = vitalSignRecordMapper.selectByPatientId(patientId, days); + return R.ok(records); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R submitVitalSign(VitalSignSubmitDto dto) { + if (dto.getPatientId() == null || dto.getNurseId() == null) { + return R.fail("患者ID和护士ID不能为空"); + } + if (dto.getRecordTime() == null) { + dto.setRecordTime(LocalDateTime.now()); + } + + MpVitalSignRecord record = new MpVitalSignRecord(); + record.setPatientId(dto.getPatientId()); + record.setEncounterId(dto.getEncounterId()); + record.setNurseId(dto.getNurseId()); + record.setRecordTime(dto.getRecordTime()); + record.setTemperature(dto.getTemperature()); + record.setPulse(dto.getPulse()); + record.setRespiration(dto.getRespiration()); + record.setSystolicBp(dto.getSystolicBp()); + record.setDiastolicBp(dto.getDiastolicBp()); + record.setBloodOxygen(dto.getBloodOxygen()); + record.setHeight(dto.getHeight()); + record.setWeight(dto.getWeight()); + + vitalSignRecordService.save(record); + log.info("生命体征录入: patientId={}, nurseId={}, recordId={}", + dto.getPatientId(), dto.getNurseId(), record.getId()); + return R.ok(Map.of("recordId", record.getId())); + } + + @Override + @Transactional(readOnly = true) + public R getAssessmentList(Long patientId) { + List records = assessmentRecordMapper.selectByPatientId(patientId); + return R.ok(records); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public R submitAssessment(AssessmentSubmitDto dto) { + if (dto.getPatientId() == null || dto.getNurseId() == null || dto.getAssessmentType() == null) { + return R.fail("患者ID、护士ID和评估类型不能为空"); + } + if (dto.getRecordTime() == null) { + dto.setRecordTime(LocalDateTime.now()); + } + + MpAssessmentRecord record = new MpAssessmentRecord(); + record.setPatientId(dto.getPatientId()); + record.setEncounterId(dto.getEncounterId()); + record.setNurseId(dto.getNurseId()); + record.setAssessmentType(dto.getAssessmentType()); + record.setAssessmentContent(dto.getAssessmentContent()); + record.setAssessmentResult(dto.getAssessmentResult()); + record.setScore(dto.getScore()); + record.setRiskLevel(dto.getRiskLevel()); + record.setRecordTime(dto.getRecordTime()); + + assessmentRecordService.save(record); + log.info("护理评估提交: patientId={}, type={}, recordId={}", + dto.getPatientId(), dto.getAssessmentType(), record.getId()); + return R.ok(Map.of("recordId", record.getId())); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/controller/MpNursingController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/controller/MpNursingController.java new file mode 100644 index 000000000..3c59dcdaa --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/controller/MpNursingController.java @@ -0,0 +1,80 @@ +package com.healthlink.his.web.miniprogram.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.miniprogram.appservice.IMpNursingAppService; +import com.healthlink.his.web.miniprogram.dto.AssessmentSubmitDto; +import com.healthlink.his.web.miniprogram.dto.TaskCompleteDto; +import com.healthlink.his.web.miniprogram.dto.VitalSignSubmitDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +/** + * 移动护理小程序 Controller + */ +@Slf4j +@Tag(name = "移动护理小程序") +@RestController +@RequestMapping("/mp/nursing") +public class MpNursingController { + + private final IMpNursingAppService mpNursingAppService; + + public MpNursingController(IMpNursingAppService mpNursingAppService) { + this.mpNursingAppService = mpNursingAppService; + } + + @Operation(summary = "获取护士任务列表") + @PreAuthorize("@ss.hasPermi('nursing:nursing:list')") + @GetMapping("/tasks") + public R getTaskList(@RequestParam Long nurseId, + @RequestParam(required = false) String status) { + return mpNursingAppService.getTaskList(nurseId, status); + } + + @Operation(summary = "完成任务") + @PreAuthorize("@ss.hasPermi('nursing:nursing:edit')") + @PostMapping("/tasks/{id}/complete") + public R completeTask(@PathVariable Long id, + @RequestBody TaskCompleteDto dto) { + return mpNursingAppService.completeTask(id, dto); + } + + @Operation(summary = "获取患者信息") + @PreAuthorize("@ss.hasPermi('nursing:nursing:list')") + @GetMapping("/patient/{id}") + public R getPatientInfo(@PathVariable Long id) { + return mpNursingAppService.getPatientInfo(id); + } + + @Operation(summary = "获取生命体征趋势") + @PreAuthorize("@ss.hasPermi('nursing:nursing:list')") + @GetMapping("/vital-signs/{patientId}") + public R getVitalSigns(@PathVariable Long patientId, + @RequestParam(required = false, defaultValue = "7") Integer days) { + return mpNursingAppService.getVitalSigns(patientId, days); + } + + @Operation(summary = "录入生命体征") + @PreAuthorize("@ss.hasPermi('nursing:nursing:edit')") + @PostMapping("/vital-sign") + public R submitVitalSign(@RequestBody VitalSignSubmitDto dto) { + return mpNursingAppService.submitVitalSign(dto); + } + + @Operation(summary = "获取评估记录列表") + @PreAuthorize("@ss.hasPermi('nursing:nursing:list')") + @GetMapping("/assessments/{patientId}") + public R getAssessmentList(@PathVariable Long patientId) { + return mpNursingAppService.getAssessmentList(patientId); + } + + @Operation(summary = "提交护理评估") + @PreAuthorize("@ss.hasPermi('nursing:nursing:edit')") + @PostMapping("/assessment") + public R submitAssessment(@RequestBody AssessmentSubmitDto dto) { + return mpNursingAppService.submitAssessment(dto); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/AssessmentSubmitDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/AssessmentSubmitDto.java new file mode 100644 index 000000000..dedcf03cc --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/AssessmentSubmitDto.java @@ -0,0 +1,31 @@ +package com.healthlink.his.web.miniprogram.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 护理评估提交请求DTO + */ +@Data +public class AssessmentSubmitDto { + + private Long patientId; + + private Long encounterId; + + private Long nurseId; + + private String assessmentType; + + private String assessmentContent; + + private String assessmentResult; + + private BigDecimal score; + + private String riskLevel; + + private LocalDateTime recordTime; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/PatientInfoDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/PatientInfoDto.java new file mode 100644 index 000000000..659a3fc0e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/PatientInfoDto.java @@ -0,0 +1,32 @@ +package com.healthlink.his.web.miniprogram.dto; + +import lombok.Data; + +import java.time.LocalDate; + +/** + * 患者信息精简版DTO + */ +@Data +public class PatientInfoDto { + + private Long id; + + private String patientName; + + private String gender; + + private LocalDate birthDate; + + private String medicalNo; + + private String phone; + + private String departmentName; + + private String bedNo; + + private String diagnosis; + + private String nurseLevel; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/TaskCompleteDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/TaskCompleteDto.java new file mode 100644 index 000000000..441b45464 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/TaskCompleteDto.java @@ -0,0 +1,14 @@ +package com.healthlink.his.web.miniprogram.dto; + +import lombok.Data; + +/** + * 任务完成请求DTO + */ +@Data +public class TaskCompleteDto { + + private String result; + + private String remark; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/VitalSignSubmitDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/VitalSignSubmitDto.java new file mode 100644 index 000000000..d984e6d7f --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/miniprogram/dto/VitalSignSubmitDto.java @@ -0,0 +1,37 @@ +package com.healthlink.his.web.miniprogram.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 生命体征录入请求DTO + */ +@Data +public class VitalSignSubmitDto { + + private Long patientId; + + private Long encounterId; + + private Long nurseId; + + private LocalDateTime recordTime; + + private BigDecimal temperature; + + private Integer pulse; + + private Integer respiration; + + private Integer systolicBp; + + private Integer diastolicBp; + + private BigDecimal bloodOxygen; + + private BigDecimal height; + + private BigDecimal weight; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V89__knowledge_graph.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V89__knowledge_graph.sql new file mode 100644 index 000000000..df1002d9b --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V89__knowledge_graph.sql @@ -0,0 +1,62 @@ +-- V89: 知识图谱 - 添加缺失字段 (description, keywords, severityIndicator, sideEffects, clinicalSignificance, evidenceSource, department, required) + +-- kg_disease: 添加 description, keywords +DO $$ BEGIN + ALTER TABLE kg_disease ADD COLUMN IF NOT EXISTS description TEXT; +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; +DO $$ BEGIN + ALTER TABLE kg_disease ADD COLUMN IF NOT EXISTS keywords VARCHAR(512); +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_disease.description IS '疾病描述'; +COMMENT ON COLUMN kg_disease.keywords IS '关键词'; + +-- kg_symptom: 添加 severity_indicator +DO $$ BEGIN + ALTER TABLE kg_symptom ADD COLUMN IF NOT EXISTS severity_indicator VARCHAR(32); +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_symptom.severity_indicator IS '严重程度指标'; + +-- kg_drug: 添加 side_effects +DO $$ BEGIN + ALTER TABLE kg_drug ADD COLUMN IF NOT EXISTS side_effects TEXT; +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_drug.side_effects IS '不良反应'; + +-- kg_examination: 添加 clinical_significance +DO $$ BEGIN + ALTER TABLE kg_examination ADD COLUMN IF NOT EXISTS clinical_significance TEXT; +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_examination.clinical_significance IS '临床意义'; + +-- kg_entity_relation: 添加 evidence_source +DO $$ BEGIN + ALTER TABLE kg_entity_relation ADD COLUMN IF NOT EXISTS evidence_source VARCHAR(512); +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_entity_relation.evidence_source IS '证据来源'; + +-- kg_clinical_pathway: 添加 department +DO $$ BEGIN + ALTER TABLE kg_clinical_pathway ADD COLUMN IF NOT EXISTS department VARCHAR(128); +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_clinical_pathway.department IS '所属科室'; + +-- kg_pathway_step: 添加 required +DO $$ BEGIN + ALTER TABLE kg_pathway_step ADD COLUMN IF NOT EXISTS required CHAR(1) DEFAULT '1'; +EXCEPTION WHEN duplicate_column THEN NULL; +END $$; + +COMMENT ON COLUMN kg_pathway_step.required IS '是否必选(1-是 0-否)'; diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V90__miniprogram_nursing.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V90__miniprogram_nursing.sql new file mode 100644 index 000000000..d9c9b2a33 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V90__miniprogram_nursing.sql @@ -0,0 +1,93 @@ +-- 移动护理小程序 - 护理任务表 +CREATE TABLE IF NOT EXISTS mp_nursing_task ( + id BIGSERIAL PRIMARY KEY, + patient_id BIGINT NOT NULL, + encounter_id BIGINT NOT NULL, + nurse_id BIGINT NOT NULL, + task_type VARCHAR(32) NOT NULL, + task_content TEXT, + task_status VARCHAR(20) DEFAULT 'PENDING', + due_time TIMESTAMP, + complete_time TIMESTAMP, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64) +); + +COMMENT ON TABLE mp_nursing_task IS '移动护理-护理任务'; +COMMENT ON COLUMN mp_nursing_task.id IS '主键ID'; +COMMENT ON COLUMN mp_nursing_task.patient_id IS '患者ID'; +COMMENT ON COLUMN mp_nursing_task.encounter_id IS '就诊ID'; +COMMENT ON COLUMN mp_nursing_task.nurse_id IS '护士ID'; +COMMENT ON COLUMN mp_nursing_task.task_type IS '任务类型'; +COMMENT ON COLUMN mp_nursing_task.task_content IS '任务内容'; +COMMENT ON COLUMN mp_nursing_task.task_status IS '任务状态: PENDING/IN_PROGRESS/COMPLETED/CANCELLED'; +COMMENT ON COLUMN mp_nursing_task.due_time IS '截止时间'; +COMMENT ON COLUMN mp_nursing_task.complete_time IS '完成时间'; + +-- 移动护理小程序 - 生命体征记录表 +CREATE TABLE IF NOT EXISTS mp_vital_sign_record ( + id BIGSERIAL PRIMARY KEY, + patient_id BIGINT NOT NULL, + encounter_id BIGINT NOT NULL, + nurse_id BIGINT NOT NULL, + record_time TIMESTAMP NOT NULL, + temperature DECIMAL(4,1), + pulse INTEGER, + respiration INTEGER, + systolic_bp INTEGER, + diastolic_bp INTEGER, + blood_oxygen DECIMAL(5,2), + height DECIMAL(5,1), + weight DECIMAL(5,1), + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64) +); + +COMMENT ON TABLE mp_vital_sign_record IS '移动护理-生命体征记录'; +COMMENT ON COLUMN mp_vital_sign_record.id IS '主键ID'; +COMMENT ON COLUMN mp_vital_sign_record.patient_id IS '患者ID'; +COMMENT ON COLUMN mp_vital_sign_record.encounter_id IS '就诊ID'; +COMMENT ON COLUMN mp_vital_sign_record.nurse_id IS '记录护士ID'; +COMMENT ON COLUMN mp_vital_sign_record.record_time IS '记录时间'; +COMMENT ON COLUMN mp_vital_sign_record.temperature IS '体温(℃)'; +COMMENT ON COLUMN mp_vital_sign_record.pulse IS '脉搏(次/分)'; +COMMENT ON COLUMN mp_vital_sign_record.respiration IS '呼吸(次/分)'; +COMMENT ON COLUMN mp_vital_sign_record.systolic_bp IS '收缩压(mmHg)'; +COMMENT ON COLUMN mp_vital_sign_record.diastolic_bp IS '舒张压(mmHg)'; +COMMENT ON COLUMN mp_vital_sign_record.blood_oxygen IS '血氧饱和度(%)'; +COMMENT ON COLUMN mp_vital_sign_record.height IS '身高(cm)'; +COMMENT ON COLUMN mp_vital_sign_record.weight IS '体重(kg)'; + +-- 移动护理小程序 - 护理评估记录表 +CREATE TABLE IF NOT EXISTS mp_assessment_record ( + id BIGSERIAL PRIMARY KEY, + patient_id BIGINT NOT NULL, + encounter_id BIGINT NOT NULL, + nurse_id BIGINT NOT NULL, + assessment_type VARCHAR(32) NOT NULL, + assessment_content TEXT, + assessment_result TEXT, + score DECIMAL(5,1), + risk_level VARCHAR(20), + record_time TIMESTAMP NOT NULL, + tenant_id BIGINT DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + create_by VARCHAR(64) +); + +COMMENT ON TABLE mp_assessment_record IS '移动护理-护理评估记录'; +COMMENT ON COLUMN mp_assessment_record.id IS '主键ID'; +COMMENT ON COLUMN mp_assessment_record.patient_id IS '患者ID'; +COMMENT ON COLUMN mp_assessment_record.encounter_id IS '就诊ID'; +COMMENT ON COLUMN mp_assessment_record.nurse_id IS '评估护士ID'; +COMMENT ON COLUMN mp_assessment_record.assessment_type IS '评估类型'; +COMMENT ON COLUMN mp_assessment_record.assessment_content IS '评估内容'; +COMMENT ON COLUMN mp_assessment_record.assessment_result IS '评估结果'; +COMMENT ON COLUMN mp_assessment_record.score IS '评分'; +COMMENT ON COLUMN mp_assessment_record.risk_level IS '风险等级'; +COMMENT ON COLUMN mp_assessment_record.record_time IS '评估时间'; diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpAssessmentRecordMapper.xml b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpAssessmentRecordMapper.xml new file mode 100644 index 000000000..c0bab5310 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpAssessmentRecordMapper.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpNursingTaskMapper.xml b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpNursingTaskMapper.xml new file mode 100644 index 000000000..c7e24c377 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpNursingTaskMapper.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpVitalSignRecordMapper.xml b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpVitalSignRecordMapper.xml new file mode 100644 index 000000000..93c2f0a24 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/miniprogram/MpVitalSignRecordMapper.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDisease.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDisease.java index 118de459f..61bbc5eb6 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDisease.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDisease.java @@ -29,4 +29,8 @@ public class KgDisease extends HisBaseEntity { private String department; private String severityLevel; + + private String description; + + private String keywords; } diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDrug.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDrug.java index c1a7ae21a..5b692a185 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDrug.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgDrug.java @@ -31,4 +31,6 @@ public class KgDrug extends HisBaseEntity { private String dosageForm; private String contraindications; + + private String sideEffects; } diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgExamination.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgExamination.java index 8aba928a9..e0048fd18 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgExamination.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgExamination.java @@ -29,4 +29,6 @@ public class KgExamination extends HisBaseEntity { private String department; private String referenceRange; + + private String clinicalSignificance; } diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgSymptom.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgSymptom.java index 24bc07bf3..5fa2e5bcf 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgSymptom.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/knowledgegraph/domain/KgSymptom.java @@ -27,4 +27,6 @@ public class KgSymptom extends HisBaseEntity { private String bodyPart; private String symptomType; + + private String severityIndicator; } diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpAssessmentRecord.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpAssessmentRecord.java new file mode 100644 index 000000000..d9ce07f51 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpAssessmentRecord.java @@ -0,0 +1,43 @@ +package com.healthlink.his.miniprogram.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.core.common.core.domain.HisBaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 移动护理-护理评估记录 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +@TableName("mp_assessment_record") +public class MpAssessmentRecord extends HisBaseEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long patientId; + + private Long encounterId; + + private Long nurseId; + + private String assessmentType; + + private String assessmentContent; + + private String assessmentResult; + + private BigDecimal score; + + private String riskLevel; + + private LocalDateTime recordTime; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpNursingTask.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpNursingTask.java new file mode 100644 index 000000000..b1ade59fa --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpNursingTask.java @@ -0,0 +1,41 @@ +package com.healthlink.his.miniprogram.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.core.common.core.domain.HisBaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 移动护理-护理任务 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +@TableName("mp_nursing_task") +public class MpNursingTask extends HisBaseEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long patientId; + + private Long encounterId; + + private Long nurseId; + + private String taskType; + + private String taskContent; + + private String taskStatus; + + private LocalDateTime dueTime; + + private LocalDateTime completeTime; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpVitalSignRecord.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpVitalSignRecord.java new file mode 100644 index 000000000..34e945c7a --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/domain/MpVitalSignRecord.java @@ -0,0 +1,49 @@ +package com.healthlink.his.miniprogram.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.core.common.core.domain.HisBaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 移动护理-生命体征记录 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +@TableName("mp_vital_sign_record") +public class MpVitalSignRecord extends HisBaseEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long patientId; + + private Long encounterId; + + private Long nurseId; + + private LocalDateTime recordTime; + + private BigDecimal temperature; + + private Integer pulse; + + private Integer respiration; + + private Integer systolicBp; + + private Integer diastolicBp; + + private BigDecimal bloodOxygen; + + private BigDecimal height; + + private BigDecimal weight; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpAssessmentRecordMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpAssessmentRecordMapper.java new file mode 100644 index 000000000..07ddb16ce --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpAssessmentRecordMapper.java @@ -0,0 +1,17 @@ +package com.healthlink.his.miniprogram.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.miniprogram.domain.MpAssessmentRecord; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 移动护理-护理评估记录 Mapper + */ +@Mapper +public interface MpAssessmentRecordMapper extends BaseMapper { + + List selectByPatientId(@Param("patientId") Long patientId); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpNursingTaskMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpNursingTaskMapper.java new file mode 100644 index 000000000..eb1b855b0 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpNursingTaskMapper.java @@ -0,0 +1,18 @@ +package com.healthlink.his.miniprogram.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.miniprogram.domain.MpNursingTask; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 移动护理-护理任务 Mapper + */ +@Mapper +public interface MpNursingTaskMapper extends BaseMapper { + + List selectTaskListByNurse(@Param("nurseId") Long nurseId, + @Param("taskStatus") String taskStatus); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpVitalSignRecordMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpVitalSignRecordMapper.java new file mode 100644 index 000000000..8310afa98 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/mapper/MpVitalSignRecordMapper.java @@ -0,0 +1,18 @@ +package com.healthlink.his.miniprogram.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.miniprogram.domain.MpVitalSignRecord; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 移动护理-生命体征记录 Mapper + */ +@Mapper +public interface MpVitalSignRecordMapper extends BaseMapper { + + List selectByPatientId(@Param("patientId") Long patientId, + @Param("days") Integer days); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpAssessmentRecordService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpAssessmentRecordService.java new file mode 100644 index 000000000..2b6cb8a65 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpAssessmentRecordService.java @@ -0,0 +1,10 @@ +package com.healthlink.his.miniprogram.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.miniprogram.domain.MpAssessmentRecord; + +/** + * 移动护理-护理评估记录 Service + */ +public interface IMpAssessmentRecordService extends IService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpNursingTaskService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpNursingTaskService.java new file mode 100644 index 000000000..600945bee --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpNursingTaskService.java @@ -0,0 +1,10 @@ +package com.healthlink.his.miniprogram.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.miniprogram.domain.MpNursingTask; + +/** + * 移动护理-护理任务 Service + */ +public interface IMpNursingTaskService extends IService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpVitalSignRecordService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpVitalSignRecordService.java new file mode 100644 index 000000000..1189e8146 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/IMpVitalSignRecordService.java @@ -0,0 +1,10 @@ +package com.healthlink.his.miniprogram.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.miniprogram.domain.MpVitalSignRecord; + +/** + * 移动护理-生命体征记录 Service + */ +public interface IMpVitalSignRecordService extends IService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpAssessmentRecordServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpAssessmentRecordServiceImpl.java new file mode 100644 index 000000000..c17ac0b5e --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpAssessmentRecordServiceImpl.java @@ -0,0 +1,15 @@ +package com.healthlink.his.miniprogram.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.miniprogram.domain.MpAssessmentRecord; +import com.healthlink.his.miniprogram.mapper.MpAssessmentRecordMapper; +import com.healthlink.his.miniprogram.service.IMpAssessmentRecordService; +import org.springframework.stereotype.Service; + +/** + * 移动护理-护理评估记录 Service实现 + */ +@Service +public class MpAssessmentRecordServiceImpl extends ServiceImpl + implements IMpAssessmentRecordService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpNursingTaskServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpNursingTaskServiceImpl.java new file mode 100644 index 000000000..ccadf4b33 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpNursingTaskServiceImpl.java @@ -0,0 +1,15 @@ +package com.healthlink.his.miniprogram.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.miniprogram.domain.MpNursingTask; +import com.healthlink.his.miniprogram.mapper.MpNursingTaskMapper; +import com.healthlink.his.miniprogram.service.IMpNursingTaskService; +import org.springframework.stereotype.Service; + +/** + * 移动护理-护理任务 Service实现 + */ +@Service +public class MpNursingTaskServiceImpl extends ServiceImpl + implements IMpNursingTaskService { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpVitalSignRecordServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpVitalSignRecordServiceImpl.java new file mode 100644 index 000000000..238cfa932 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/miniprogram/service/impl/MpVitalSignRecordServiceImpl.java @@ -0,0 +1,15 @@ +package com.healthlink.his.miniprogram.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.miniprogram.domain.MpVitalSignRecord; +import com.healthlink.his.miniprogram.mapper.MpVitalSignRecordMapper; +import com.healthlink.his.miniprogram.service.IMpVitalSignRecordService; +import org.springframework.stereotype.Service; + +/** + * 移动护理-生命体征记录 Service实现 + */ +@Service +public class MpVitalSignRecordServiceImpl extends ServiceImpl + implements IMpVitalSignRecordService { +} diff --git a/healthlink-his-ui/src/api/knowledgegraph/api.js b/healthlink-his-ui/src/api/knowledgegraph/api.js index 1c8da3dbc..e8660c23e 100644 --- a/healthlink-his-ui/src/api/knowledgegraph/api.js +++ b/healthlink-his-ui/src/api/knowledgegraph/api.js @@ -103,3 +103,43 @@ export function getPathwayPage(params) { export function getPathwaySteps(id) { return request({ url: `/knowledgegraph/pathway/${id}/steps`, method: 'get' }) } + +// KG3: 推理引擎 +export function suggestDiagnosis(data) { + return request({ url: '/knowledgegraph/reasoning/diagnosis', method: 'post', data }) +} + +export function suggestExaminations(data) { + return request({ url: '/knowledgegraph/reasoning/examination', method: 'post', data }) +} + +export function checkDrugInteractions(data) { + return request({ url: '/knowledgegraph/reasoning/drug-interaction', method: 'post', data }) +} + +export function suggestPathway(diseaseCode) { + return request({ url: `/knowledgegraph/reasoning/pathway/${diseaseCode}`, method: 'get' }) +} + +// KG4: 数据导入 +export function importDisease(file) { + const formData = new FormData() + formData.append('file', file) + return request({ url: '/knowledgegraph/import/disease', method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }) +} + +export function importDrug(file) { + const formData = new FormData() + formData.append('file', file) + return request({ url: '/knowledgegraph/import/drug', method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }) +} + +export function importRelations(file) { + const formData = new FormData() + formData.append('file', file) + return request({ url: '/knowledgegraph/import/relation', method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }) +} + +export function downloadImportTemplate(type) { + return request({ url: `/knowledgegraph/import/template/${type}`, method: 'get', responseType: 'blob' }) +} diff --git a/healthlink-his-ui/src/views/knowledgegraph/DataImport.vue b/healthlink-his-ui/src/views/knowledgegraph/DataImport.vue new file mode 100644 index 000000000..e1dcad9e7 --- /dev/null +++ b/healthlink-his-ui/src/views/knowledgegraph/DataImport.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/healthlink-his-ui/src/views/knowledgegraph/DiagnosisSuggest.vue b/healthlink-his-ui/src/views/knowledgegraph/DiagnosisSuggest.vue new file mode 100644 index 000000000..7d6167de6 --- /dev/null +++ b/healthlink-his-ui/src/views/knowledgegraph/DiagnosisSuggest.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/healthlink-his-ui/src/views/knowledgegraph/DrugInteractionCheck.vue b/healthlink-his-ui/src/views/knowledgegraph/DrugInteractionCheck.vue new file mode 100644 index 000000000..45407c433 --- /dev/null +++ b/healthlink-his-ui/src/views/knowledgegraph/DrugInteractionCheck.vue @@ -0,0 +1,84 @@ + + + + +