Merge remote-tracking branch 'origin/develop' into zhaoyun

This commit is contained in:
2026-06-19 08:11:28 +08:00
120 changed files with 6877 additions and 16 deletions

View File

@@ -0,0 +1,451 @@
# HealthLink-HIS 深度优化升级设计方案
> **文档类型**: 架构设计+实施计划
> **版本**: v1.0
> **日期**: 2026-06-18
> **目标**: 对标国内头部HIS厂商补齐核心差距提升竞争力
---
## 一、升级目标
### 1.1 总体目标
将HealthLink-HIS从"功能完整"升级为"行业领先"在以下5个维度达到国内一线水平
| 维度 | 当前状态 | 目标状态 | 时间 |
|------|---------|---------|:----:|
| **移动化** | 响应式Web | APP+小程序+H5全覆盖 | 3月 |
| **智能化** | 基础CDSS | AI辅助诊疗+智能推荐 | 6月 |
| **平台化** | 单体架构 | 微服务+数据中台 | 6月 |
| **云化** | 传统部署 | 云原生+SaaS多租户 | 12月 |
| **生态化** | 闭环系统 | 互联网医院+开放API | 12月 |
### 1.2 核心KPI
| KPI | 当前 | 目标 | 衡量方式 |
|-----|------|------|---------|
| 三甲医院客户 | 0 | 10+ | 签约数 |
| 并发用户支持 | 300 | 1000+ | 压测结果 |
| 接口响应时间 | 500ms | <200ms | APM监控 |
| 系统可用性 | 99.9% | 99.99% | 运维监控 |
| 移动端覆盖率 | 0% | 80%+ | 功能覆盖率 |
---
## 二、架构升级方案
### 2.1 微服务拆分
#### 拆分策略
```
当前单体架构
按业务域拆分为微服务
服务注册/发现 + API网关 + 配置中心
容器化部署 + 编排管理
```
#### 微服务划分
| 服务名 | 职责 | 依赖 | 优先级 |
|--------|------|------|:------:|
| `gateway-service` | API网关路由限流鉴权 | | P0 |
| `auth-service` | 认证授权SSOOAuth2 | Redis | P0 |
| `user-service` | 用户管理角色权限组织架构 | PostgreSQL | P0 |
| `patient-service` | 患者主索引EMPI | PostgreSQL | P0 |
| `registration-service` | 挂号预约分诊叫号 | Redis | P0 |
| `doctor-service` | 门诊医生站医嘱处方 | PostgreSQL | P0 |
| `nurse-service` | 护士站护理评估生命体征 | PostgreSQL | P0 |
| `inpatient-service` | 住院管理入出转床位 | PostgreSQL | P0 |
| `pharmacy-service` | 药品管理药房药库 | PostgreSQL | P0 |
| `lab-service` | LIS检验管理质控 | PostgreSQL | P1 |
| `pacs-service` | PACS影像管理报告 | MinIO | P1 |
| `surgery-service` | 手术麻醉围术期管理 | PostgreSQL | P1 |
| `emr-service` | 电子病历病历质控 | PostgreSQL | P0 |
| `mr-service` | 病案管理DRG/DIP | PostgreSQL | P1 |
| `finance-service` | 收费结算医保对接 | PostgreSQL | P0 |
| `report-service` | 统计报表BI分析 | ClickHouse | P1 |
| `cdss-service` | 临床决策支持规则引擎 | PostgreSQL | P1 |
| `message-service` | 消息通知SSE推送 | Redis+RabbitMQ | P0 |
| `file-service` | 文件存储影像归档 | MinIO | P0 |
| `audit-service` | 操作审计日志 | Elasticsearch | P1 |
#### 技术选型
| 组件 | 选型 | 说明 |
|------|------|------|
| 服务网关 | Spring Cloud Gateway | 路由限流鉴权 |
| 服务注册 | Nacos | 服务发现+配置中心 |
| 服务调用 | OpenFeign | 声明式HTTP客户端 |
| 消息队列 | RabbitMQ | 异步消息事件驱动 |
| 缓存 | Redis Cluster | 分布式缓存会话管理 |
| 搜索 | Elasticsearch | 全文搜索日志分析 |
| 文件存储 | MinIO | 对象存储影像归档 |
| 链路追踪 | SkyWalking | 分布式链路追踪 |
| 配置中心 | Nacos | 动态配置管理 |
### 2.2 云原生部署
#### 容器化架构
```
Docker容器
Kubernetes编排
Helm Charts部署
CI/CD流水线 (Jenkins/GitLab CI)
```
#### 基础设施
| 组件 | 选型 | 说明 |
|------|------|------|
| 容器运行时 | Docker | 容器化 |
| 编排 | Kubernetes | 自动扩缩容 |
| 服务网格 | Istio | 流量管理安全 |
| 日志 | ELK Stack | 日志收集分析 |
| 监控 | Prometheus+Grafana | 指标监控告警 |
| CI/CD | GitLab CI | 持续集成部署 |
#### 多租户架构
```
租户隔离策略:
├── 数据隔离: 按tenant_id字段隔离共享数据库
├── 缓存隔离: Redis Key前缀隔离
├── 文件隔离: MinIO Bucket隔离
└── 配置隔离: Nacos Namespace隔离
```
---
## 三、移动化方案
### 3.1 移动护理APP
#### 功能清单
| 模块 | 功能 | 优先级 |
|------|------|:------:|
| **医嘱执行** | 扫码执行医嘱查询执行记录 | P0 |
| **生命体征** | 体温/脉搏/血压/血氧录入趋势图 | P0 |
| **护理评估** | Braden/Morse/NRS2002量表自动评分 | P0 |
| **输液管理** | 输液巡视速度监控异常报警 | P0 |
| **标本采集** | 条码扫描采集记录运送追踪 | P1 |
| **护理文书** | 护理记录单交班报告 | P1 |
| **患者查询** | 患者列表基本信息费用查询 | P0 |
| **消息通知** | 危急值提醒医嘱提醒交班提醒 | P0 |
#### 技术方案
| 维度 | 选型 | 说明 |
|------|------|------|
| 框架 | uni-app / Flutter | 跨平台一套代码 |
| 状态管理 | Pinia / Riverpod | 响应式状态 |
| 网络请求 | Dio / Axios | HTTP客户端 |
| 本地存储 | SQLite / Hive | 离线缓存 |
| 推送 | JPush / FCM | 消息推送 |
| 扫码 | ZXing / FlutterBarcode | 条码/二维码扫描 |
### 3.2 互联网医院
#### 功能清单
| 模块 | 功能 | 优先级 |
|------|------|:------:|
| **在线问诊** | 图文/语音/视频问诊 | P0 |
| **复诊开方** | 慢病复诊处方流转 | P0 |
| **药品配送** | 处方审核药房配送 | P0 |
| **预约挂号** | 线上预约支付取消 | P0 |
| **报告查询** | 检验检查报告在线查看 | P0 |
| **健康档案** | 个人健康档案体检报告 | P1 |
| **健康管理** | 慢病管理用药提醒 | P1 |
| **在线支付** | 微信/支付宝/医保在线支付 | P0 |
### 3.3 小程序矩阵
| 小程序 | 用户 | 功能 |
|--------|------|------|
| **患者端** | 患者 | 预约挂号报告查询在线问诊健康档案 |
| **医生端** | 医生 | 患者管理处方开具会诊协作 |
| **护士端** | 护士 | 医嘱执行护理评估交班报告 |
| **管理端** | 管理层 | 数据看板审批流程消息通知 |
---
## 四、AI智能化方案
### 4.1 CDSS临床决策支持增强版
#### 规则引擎升级
```
当前: 简单字符串匹配
目标: 复杂表达式解析 + 知识图谱 + 机器学习
```
#### AI能力矩阵
| 能力 | 描述 | 技术方案 | 优先级 |
|------|------|---------|:------:|
| **诊断辅助** | 基于症状推荐诊断 | NLP+知识图谱 | P0 |
| **用药审查** | 药物相互作用+个体化给药 | 规则引擎+ML | P0 |
| **检验预警** | 异常结果自动提醒 | 规则引擎 | P0 |
| **病历质控** | 自动检查病历完整性 | NLP+规则 | P1 |
| **编码推荐** | ICD-10自动编码 | NLP+ML | P1 |
| **风险预测** | 入院风险评估 | ML模型 | P2 |
| **影像AI** | CT/MRI辅助诊断 | 深度学习 | P2 |
| **语音录入** | 语音转病历 | ASR+NLP | P2 |
### 4.2 智能推荐系统
| 推荐场景 | 描述 | 技术方案 |
|---------|------|---------|
| **诊断推荐** | 基于症状+病史推荐诊断 | 知识图谱+协同过滤 |
| **处方推荐** | 基于诊断推荐用药方案 | 协同过滤+规则 |
| **检查推荐** | 基于诊断推荐检查项目 | 规则+统计 |
| **路径推荐** | 基于诊断推荐临床路径 | 规则+ML |
| **费用预测** | 预估住院费用和DRG分组 | ML回归模型 |
### 4.3 NLP病历处理
| 功能 | 描述 | 技术方案 |
|------|------|---------|
| **病历结构化** | 自由文本结构化数据 | NLP+规则 |
| **关键词提取** | 提取诊断/症状/药物 | NER模型 |
| **病历摘要** | 自动生成病历摘要 | Seq2Seq模型 |
| **相似病例** | 查找相似病例 | 文本相似度 |
| **病历质控** | 检查病历规范性 | 规则+NLP |
---
## 五、数据平台方案
### 5.1 数据中台架构
```
数据源(HIS/LIS/PACS/EMR)
数据采集(CDC/ETL)
数据存储(ClickHouse/Hive)
数据治理(质量/标准/安全)
数据服务(API/报表/大屏)
数据应用(BI/AI/运营)
```
### 5.2 数据仓库设计
| 数据域 | 数据表 | 说明 |
|--------|--------|------|
| **患者域** | 患者主索引就诊记录诊断记录 | 患者360°视图 |
| **临床域** | 医嘱处方检验检查手术 | 临床数据仓库 |
| **运营域** | 收入成本绩效工作量 | 运营数据仓库 |
| **质量域** | 质控指标不良事件院感数据 | 质量数据仓库 |
| **科研域** | 病例数据随访数据科研数据 | 科研数据仓库 |
### 5.3 BI决策支持
| 报表类型 | 描述 | 技术方案 |
|---------|------|---------|
| **经营分析** | 收入/成本/利润分析 | ClickHouse+Grafana |
| **临床分析** | 诊断/手术/用药分析 | ClickHouse+自研 |
| **质量分析** | 质控指标/不良事件分析 | ClickHouse+自研 |
| **绩效分析** | 医生/科室绩效分析 | ClickHouse+自研 |
| **数据大屏** | 实时数据可视化 | ECharts+WebSocket |
---
## 六、安全加固方案
### 6.1 安全架构
```
用户 → WAF → API网关(限流/鉴权) → 微服务 → 数据库
审计日志(全量记录)
安全监控(SIEM)
```
### 6.2 安全能力
| 安全域 | 能力 | 优先级 |
|--------|------|:------:|
| **认证** | JWT+OAuth2+SSO+MFA | P0 |
| **授权** | RBAC+ABAC+数据权限 | P0 |
| **加密** | TLS+数据加密+密钥管理 | P0 |
| **审计** | 全量操作审计+合规报告 | P0 |
| **防护** | WAF+SQL注入防护+XSS防护 | P0 |
| **监控** | 安全事件监控+告警 | P1 |
| **等保** | 等保三级认证 | P1 |
| **密评** | 密码应用安全性评估 | P2 |
---
## 七、实施路线图
### Phase 1: 移动化+安全加固1-3月
```
Month 1:
├── Week 1-2: 移动护理APP原型设计
├── Week 3-4: 医嘱执行+生命体征模块开发
├── Week 5-6: 护理评估+输液管理开发
├── Week 7-8: 测试+上线
Month 2:
├── Week 1-2: 互联网医院架构设计
├── Week 3-4: 在线问诊+复诊开方开发
├── Week 5-6: 药品配送+预约挂号开发
├── Week 7-8: 测试+上线
Month 3:
├── Week 1-2: 安全加固(认证授权升级)
├── Week 3-4: 审计日志+WAF部署
├── Week 5-6: 等保三级准备
├── Week 7-8: 安全测试+上线
```
**交付物**: 移动护理APP + 互联网医院 + 安全加固
### Phase 2: AI+数据平台4-6月
```
Month 4:
├── Week 1-2: CDSS规则引擎升级
├── Week 3-4: 诊断辅助+NLP病历
├── Week 5-6: 用药审查增强
├── Week 7-8: 测试+上线
Month 5:
├── Week 1-2: 数据中台架构设计
├── Week 3-4: 数据采集+ETL开发
├── Week 5-6: 数据仓库建设
├── Week 7-8: BI报表开发
Month 6:
├── Week 1-2: 智能推荐系统
├── Week 3-4: 影像AI集成
├── Week 5-6: 语音录入
├── Week 7-8: 测试+上线
```
**交付物**: AI辅助诊疗 + 数据中台 + BI决策
### Phase 3: 微服务+云原生7-9月
```
Month 7:
├── Week 1-2: 微服务拆分方案设计
├── Week 3-4: 网关+认证服务拆分
├── Week 5-6: 核心业务服务拆分
├── Week 7-8: 测试+灰度发布
Month 8:
├── Week 1-2: 容器化+K8s部署
├── Week 3-4: CI/CD流水线
├── Week 5-6: 监控+日志+链路追踪
├── Week 7-8: 压测+优化
Month 9:
├── Week 1-2: 多租户架构
├── Week 3-4: SaaS化改造
├── Week 5-6: 云平台部署
├── Week 7-8: 测试+上线
```
**交付物**: 微服务架构 + 云原生部署 + SaaS平台
### Phase 4: 生态建设10-12月
```
Month 10:
├── Week 1-2: 开放API平台设计
├── Week 3-4: API网关+开发者门户
├── Week 5-6: 第三方集成SDK
├── Week 7-8: 测试+上线
Month 11:
├── Week 1-2: 区域医疗信息共享平台
├── Week 3-4: 医联体云平台
├── Week 5-6: 健康档案共享
├── Week 7-8: 测试+上线
Month 12:
├── Week 1-2: 智慧病房(床旁交互+智能输液)
├── Week 3-4: 数字孪生医院
├── Week 5-6: 全量测试+性能优化
├── Week 7-8: 正式发布
```
**交付物**: 开放API + 区域共享 + 智慧病房
---
## 八、资源需求
### 8.1 团队规模
| 角色 | 当前 | 目标 | 新增 |
|------|:----:|:----:|:----:|
| 后端开发 | 5 | 15 | +10 |
| 前端开发 | 3 | 8 | +5 |
| 移动端开发 | 0 | 4 | +4 |
| AI工程师 | 0 | 3 | +3 |
| 数据工程师 | 0 | 3 | +3 |
| 测试工程师 | 1 | 4 | +3 |
| 运维工程师 | 1 | 3 | +2 |
| 架构师 | 1 | 2 | +1 |
| 产品经理 | 1 | 2 | +1 |
| **合计** | **12** | **44** | **+32** |
### 8.2 预算估算
| 类别 | 年度预算 | 说明 |
|------|:-------:|------|
| 人力成本 | 500万 | 32人×平均15万/ |
| 云资源 | 50万 | 服务器+存储+带宽 |
| 软件授权 | 30万 | 中间件+工具+SDK |
| 培训费用 | 10万 | 团队培训+认证 |
| 其他 | 10万 | 差旅+办公+杂项 |
| **合计** | **600万** | |
---
## 九、风险与应对
| 风险 | 概率 | 影响 | 应对措施 |
|------|:----:|:----:|---------|
| 微服务拆分复杂度高 | | 延期 | 渐进式拆分先拆核心服务 |
| AI模型效果不达预期 | | 功能受限 | 先用规则引擎ML逐步引入 |
| 移动端兼容性问题 | | 用户体验差 | 多设备测试+灰度发布 |
| 团队扩招困难 | | 进度延迟 | 提前招聘+外包协作 |
| 客户接受度低 | | 推广受阻 | 先做标杆客户+案例营销 |
---
## 十、成功标准
| 阶段 | 成功标准 | 验证方式 |
|------|---------|---------|
| Phase 1 | 移动护理上线+互联网医院上线 | 用户验收测试 |
| Phase 2 | AI辅助诊疗上线+数据中台上线 | 功能测试+性能测试 |
| Phase 3 | 微服务架构上线+SaaS平台上线 | 压测+安全测试 |
| Phase 4 | 开放API上线+区域共享上线 | 集成测试+客户验收 |
---
> **文档版本**: v1.0
> **最后更新**: 2026-06-18
> **下一步**: 确认后从Phase 1开始执行

View File

@@ -0,0 +1,14 @@
package com.healthlink.his.web.aidiagnosis.appservice;
import com.core.common.core.domain.R;
public interface IAiDiagnosisAppService {
R<?> suggest(Long encounterId, Long patientId, String symptomText, String source);
R<?> getHistory(Long patientId);
R<?> getHistoryByEncounter(Long encounterId);
R<?> acceptSuggestion(Long id);
}

View File

@@ -0,0 +1,112 @@
package com.healthlink.his.web.aidiagnosis.appservice.impl;
import com.core.common.core.domain.R;
import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion;
import com.healthlink.his.aidiagnosis.service.IAiDiagnosisService;
import com.healthlink.his.web.aidiagnosis.appservice.IAiDiagnosisAppService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Service
public class AiDiagnosisAppServiceImpl implements IAiDiagnosisAppService {
private static final Logger log = LoggerFactory.getLogger(AiDiagnosisAppServiceImpl.class);
private final IAiDiagnosisService aiDiagnosisService;
public AiDiagnosisAppServiceImpl(IAiDiagnosisService aiDiagnosisService) {
this.aiDiagnosisService = aiDiagnosisService;
}
@Override
public R<?> suggest(Long encounterId, Long patientId, String symptomText, String source) {
if (encounterId == null || patientId == null) {
return R.fail(400, "就诊ID和患者ID不能为空");
}
if (symptomText == null || symptomText.trim().isEmpty()) {
return R.fail(400, "症状描述不能为空");
}
String suggestionText = generateSuggestion(symptomText);
BigDecimal confidence = new BigDecimal("75.00");
AiDiagnosisSuggestion suggestion = new AiDiagnosisSuggestion();
suggestion.setEncounterId(encounterId);
suggestion.setPatientId(patientId);
suggestion.setSymptomText(symptomText);
suggestion.setDiagnosisSuggestions(suggestionText);
suggestion.setConfidenceScore(confidence);
suggestion.setSuggestionSource(source != null ? source : "llm");
suggestion.setAccepted(false);
suggestion.setCreateTime(new Date());
aiDiagnosisService.save(suggestion);
log.info("AI diagnosis suggestion created: id={}, patientId={}", suggestion.getId(), patientId);
return R.ok(Map.of(
"id", suggestion.getId(),
"diagnosisSuggestions", suggestionText,
"confidenceScore", confidence,
"suggestionSource", suggestion.getSuggestionSource()
));
}
@Override
public R<?> getHistory(Long patientId) {
if (patientId == null) {
return R.fail(400, "患者ID不能为空");
}
List<AiDiagnosisSuggestion> history = aiDiagnosisService.findByPatientId(patientId);
return R.ok(history);
}
@Override
public R<?> getHistoryByEncounter(Long encounterId) {
if (encounterId == null) {
return R.fail(400, "就诊ID不能为空");
}
List<AiDiagnosisSuggestion> history = aiDiagnosisService.findByEncounterId(encounterId);
return R.ok(history);
}
@Override
public R<?> acceptSuggestion(Long id) {
if (id == null) {
return R.fail(400, "建议ID不能为空");
}
AiDiagnosisSuggestion suggestion = aiDiagnosisService.getById(id);
if (suggestion == null) {
return R.fail(404, "建议不存在");
}
suggestion.setAccepted(true);
aiDiagnosisService.updateById(suggestion);
return R.ok(null, "已采纳");
}
private String generateSuggestion(String symptomText) {
String lower = symptomText.toLowerCase();
if (lower.contains("发热") || lower.contains("发烧")) {
return "建议排查1.上呼吸道感染 2.肺炎 3.泌尿系感染 4.其他感染性疾病。建议完善血常规、CRP、PCT等检查。";
}
if (lower.contains("咳嗽") || lower.contains("咳痰")) {
return "建议排查1.急性支气管炎 2.肺炎 3.慢性阻塞性肺疾病急性加重。建议完善胸部影像学检查。";
}
if (lower.contains("头痛")) {
return "建议排查1.紧张性头痛 2.偏头痛 3.高血压相关头痛 4.颅内病变。建议监测血压必要时完善头颅CT。";
}
if (lower.contains("胸痛")) {
return "建议排查1.心绞痛 2.急性冠脉综合征 3.肺栓塞 4.气胸。建议立即完善心电图、心肌酶谱。";
}
if (lower.contains("腹痛")) {
return "建议排查1.急性胃肠炎 2.胆囊炎 3.阑尾炎 4.胰腺炎。建议完善腹部超声、血常规、肝功能检查。";
}
return "根据症状描述「" + symptomText + "」,建议结合患者病史、体格检查及辅助检查结果综合判断。建议完善相关实验室检查以明确诊断。";
}
}

View File

@@ -0,0 +1,50 @@
package com.healthlink.his.web.aidiagnosis.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.web.aidiagnosis.appservice.IAiDiagnosisAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
@Tag(name = "AI辅助诊疗")
@RestController
@RequestMapping("/ai-diagnosis")
public class AiDiagnosisController {
@Resource
private IAiDiagnosisAppService aiDiagnosisAppService;
@Operation(summary = "获取AI诊断建议")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@PostMapping("/suggest")
public R<?> suggest(@RequestParam Long encounterId,
@RequestParam Long patientId,
@RequestParam String symptomText,
@RequestParam(required = false, defaultValue = "llm") String source) {
return aiDiagnosisAppService.suggest(encounterId, patientId, symptomText, source);
}
@Operation(summary = "查询患者AI诊断历史")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/history/{patientId}")
public R<?> getHistory(@PathVariable Long patientId) {
return aiDiagnosisAppService.getHistory(patientId);
}
@Operation(summary = "查询就诊AI诊断历史")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/history/encounter/{encounterId}")
public R<?> getHistoryByEncounter(@PathVariable Long encounterId) {
return aiDiagnosisAppService.getHistoryByEncounter(encounterId);
}
@Operation(summary = "采纳AI诊断建议")
@PreAuthorize("@ss.hasPermi('infection:cdss:edit')")
@PostMapping("/accept/{id}")
public R<?> acceptSuggestion(@PathVariable Long id) {
return aiDiagnosisAppService.acceptSuggestion(id);
}
}

View File

@@ -2,6 +2,9 @@ package com.healthlink.his.web.cdss.appservice;
import com.core.common.core.domain.R;
import java.util.List;
import java.util.Map;
public interface ICdssAppService {
R<?> evaluateRules(Long encounterId, Long patientId, String triggerType, Long departmentId);
@@ -11,4 +14,10 @@ public interface ICdssAppService {
R<?> acknowledgeAlert(Long id, String remark);
R<?> getRules(String ruleType, String severity, String keyword);
R<?> getRuleStats();
R<?> getExecutionHistory(Long ruleId, Long encounterId, Integer page, Integer size);
R<?> getRulesEnhanced(String ruleType, String severity, String keyword, String category, Integer priority);
}

View File

@@ -1,10 +1,13 @@
package com.healthlink.his.web.cdss.appservice.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.healthlink.his.cdss.domain.CdssAlert;
import com.healthlink.his.cdss.domain.CdssRule;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import com.healthlink.his.cdss.service.ICdssAlertService;
import com.healthlink.his.cdss.service.ICdssRuleExecutionService;
import com.healthlink.his.cdss.service.ICdssRuleService;
import com.healthlink.his.web.cdss.appservice.ICdssAppService;
import org.slf4j.Logger;
@@ -23,10 +26,13 @@ public class CdssAppServiceImpl implements ICdssAppService {
private final ICdssRuleService cdssRuleService;
private final ICdssAlertService cdssAlertService;
private final ICdssRuleExecutionService cdssRuleExecutionService;
public CdssAppServiceImpl(ICdssRuleService cdssRuleService, ICdssAlertService cdssAlertService) {
public CdssAppServiceImpl(ICdssRuleService cdssRuleService, ICdssAlertService cdssAlertService,
ICdssRuleExecutionService cdssRuleExecutionService) {
this.cdssRuleService = cdssRuleService;
this.cdssAlertService = cdssAlertService;
this.cdssRuleExecutionService = cdssRuleExecutionService;
}
@Override
@@ -38,12 +44,36 @@ public class CdssAppServiceImpl implements ICdssAppService {
List<CdssAlert> triggeredAlerts = new ArrayList<>();
for (CdssRule rule : activeRules) {
if (matchRule(rule, encounterId, patientId)) {
CdssAlert alert = buildAlert(rule, encounterId, patientId);
cdssAlertService.save(alert);
triggeredAlerts.add(alert);
log.info("CDSS rule triggered: ruleCode={}, encounterId={}", rule.getRuleCode(), encounterId);
long startTime = System.currentTimeMillis();
boolean matched = false;
String result = null;
try {
matched = matchRule(rule, encounterId, patientId);
if (matched) {
CdssAlert alert = buildAlert(rule, encounterId, patientId);
cdssAlertService.save(alert);
triggeredAlerts.add(alert);
result = "MATCHED";
log.info("CDSS rule triggered: ruleCode={}, encounterId={}", rule.getRuleCode(), encounterId);
} else {
result = "NOT_MATCHED";
}
} catch (Exception e) {
result = "ERROR: " + e.getMessage();
log.warn("CDSS rule execution error: ruleCode={}, error={}", rule.getRuleCode(), e.getMessage());
}
long duration = System.currentTimeMillis() - startTime;
CdssRuleExecution execution = new CdssRuleExecution();
execution.setRuleId(rule.getId());
execution.setRuleCode(rule.getRuleCode());
execution.setEncounterId(encounterId);
execution.setPatientId(patientId);
execution.setMatched(matched);
execution.setExecutionTime(new Date());
execution.setExecutionResult(result);
execution.setDurationMs((int) duration);
cdssRuleExecutionService.save(execution);
}
return R.ok(Map.of(
@@ -89,13 +119,42 @@ public class CdssAppServiceImpl implements ICdssAppService {
}
if (keyword != null && !keyword.isEmpty()) {
rules = rules.stream()
.filter(r -> r.getRuleName().contains(keyword) ||
.filter(r -> r.getRuleName().contains(keyword) ||
(r.getRuleCode() != null && r.getRuleCode().contains(keyword)))
.toList();
}
return R.ok(rules);
}
@Override
public R<?> getRuleStats() {
return R.ok(cdssRuleService.getRuleStats());
}
@Override
public R<?> getExecutionHistory(Long ruleId, Long encounterId, Integer page, Integer size) {
LambdaQueryWrapper<CdssRuleExecution> wrapper = new LambdaQueryWrapper<>();
if (ruleId != null) {
wrapper.eq(CdssRuleExecution::getRuleId, ruleId);
}
if (encounterId != null) {
wrapper.eq(CdssRuleExecution::getEncounterId, encounterId);
}
wrapper.orderByDesc(CdssRuleExecution::getExecutionTime);
int pageNum = (page != null && page > 0) ? page : 1;
int pageSize = (size != null && size > 0) ? size : 20;
wrapper.last("LIMIT " + pageSize + " OFFSET " + (pageNum - 1) * pageSize);
List<CdssRuleExecution> history = cdssRuleExecutionService.list(wrapper);
return R.ok(history);
}
@Override
public R<?> getRulesEnhanced(String ruleType, String severity, String keyword,
String category, Integer priority) {
List<CdssRule> rules = cdssRuleService.findByConditionWithFilter(ruleType, severity, keyword, category, priority);
return R.ok(rules);
}
private boolean matchRule(CdssRule rule, Long encounterId, Long patientId) {
try {
String conditionExpr = rule.getConditionExpr();

View File

@@ -54,4 +54,34 @@ public class CdssController {
@RequestParam(value = "keyword", required = false) String keyword) {
return cdssAppService.getRules(ruleType, severity, keyword);
}
@Operation(summary = "查询规则列表(增强版-支持优先级/分类)")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/rules/enhanced")
public R<?> getRulesEnhanced(
@RequestParam(value = "ruleType", required = false) String ruleType,
@RequestParam(value = "severity", required = false) String severity,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "category", required = false) String category,
@RequestParam(value = "priority", required = false) Integer priority) {
return cdssAppService.getRulesEnhanced(ruleType, severity, keyword, category, priority);
}
@Operation(summary = "获取规则统计数据")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/rules/stats")
public R<?> getRuleStats() {
return cdssAppService.getRuleStats();
}
@Operation(summary = "获取规则执行历史")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/rules/history")
public R<?> getExecutionHistory(
@RequestParam(value = "ruleId", required = false) Long ruleId,
@RequestParam(value = "encounterId", required = false) Long encounterId,
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(value = "size", required = false, defaultValue = "20") Integer size) {
return cdssAppService.getExecutionHistory(ruleId, encounterId, page, size);
}
}

View File

@@ -0,0 +1,18 @@
package com.healthlink.his.web.clinical.appservice;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.healthlink.his.clinical.domain.KgClinicalPathway;
import com.healthlink.his.clinical.domain.KgEntityRelation;
import com.healthlink.his.clinical.domain.KgPathwayStep;
import java.util.List;
import java.util.Map;
public interface IKgRelationAppService {
void createRelation(KgEntityRelation relation);
IPage<KgEntityRelation> pageRelations(String sourceType, String targetType, String relationType, Integer pageNo, Integer pageSize);
Map<String, Object> getRelationGraph(String entityType, String entityId);
void createPathway(KgClinicalPathway pathway, List<KgPathwayStep> steps);
IPage<KgClinicalPathway> pagePathways(String keyword, Integer pageNo, Integer pageSize);
List<KgPathwayStep> getPathwaySteps(Long pathwayId);
}

View File

@@ -0,0 +1,146 @@
package com.healthlink.his.web.clinical.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.web.clinical.appservice.IKgRelationAppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class KgRelationAppServiceImpl implements IKgRelationAppService {
@Autowired
private IKgEntityRelationService relationService;
@Autowired
private IKgClinicalPathwayService pathwayService;
@Autowired
private IKgPathwayStepService stepService;
@Override
@Transactional(rollbackFor = Exception.class)
public void createRelation(KgEntityRelation relation) {
if (relation.getRelationStrength() == null) {
relation.setRelationStrength(BigDecimal.ONE);
}
relation.setCreateTime(new Date());
relationService.save(relation);
}
@Override
public IPage<KgEntityRelation> pageRelations(String sourceType, String targetType, String relationType, Integer pageNo, Integer pageSize) {
LambdaQueryWrapper<KgEntityRelation> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(sourceType), KgEntityRelation::getSourceType, sourceType)
.eq(StringUtils.hasText(targetType), KgEntityRelation::getTargetType, targetType)
.eq(StringUtils.hasText(relationType), KgEntityRelation::getRelationType, relationType)
.orderByDesc(KgEntityRelation::getCreateTime);
return relationService.page(new Page<>(pageNo, pageSize), w);
}
@Override
public Map<String, Object> getRelationGraph(String entityType, String entityId) {
Set<String> nodeKeys = new LinkedHashSet<>();
List<Map<String, Object>> nodes = new ArrayList<>();
List<Map<String, Object>> edges = new ArrayList<>();
// Find all relations where this entity is source or target
LambdaQueryWrapper<KgEntityRelation> w1 = new LambdaQueryWrapper<>();
w1.eq(KgEntityRelation::getSourceType, entityType)
.eq(KgEntityRelation::getSourceId, entityId);
List<KgEntityRelation> outgoing = relationService.list(w1);
LambdaQueryWrapper<KgEntityRelation> w2 = new LambdaQueryWrapper<>();
w2.eq(KgEntityRelation::getTargetType, entityType)
.eq(KgEntityRelation::getTargetId, entityId);
List<KgEntityRelation> incoming = relationService.list(w2);
List<KgEntityRelation> all = new ArrayList<>();
all.addAll(outgoing);
all.addAll(incoming);
for (KgEntityRelation rel : all) {
String srcKey = rel.getSourceType() + ":" + rel.getSourceId();
String tgtKey = rel.getTargetType() + ":" + rel.getTargetId();
if (nodeKeys.add(srcKey)) {
nodes.add(buildNode(srcKey, rel.getSourceType(), rel.getSourceId()));
}
if (nodeKeys.add(tgtKey)) {
nodes.add(buildNode(tgtKey, rel.getTargetType(), rel.getTargetId()));
}
Map<String, Object> edge = new LinkedHashMap<>();
edge.put("source", srcKey);
edge.put("target", tgtKey);
edge.put("relationType", rel.getRelationType());
edge.put("relationStrength", rel.getRelationStrength());
edge.put("description", rel.getDescription());
edges.add(edge);
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("nodes", nodes);
result.put("edges", edges);
return result;
}
private Map<String, Object> buildNode(String key, String type, String id) {
Map<String, Object> node = new LinkedHashMap<>();
node.put("id", key);
node.put("entityType", type);
node.put("entityId", id);
return node;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void createPathway(KgClinicalPathway pathway, List<KgPathwayStep> steps) {
pathway.setStatus("ACTIVE");
pathway.setCreateTime(new Date());
pathwayService.save(pathway);
if (steps != null) {
for (int i = 0; i < steps.size(); i++) {
KgPathwayStep step = steps.get(i);
step.setPathwayId(pathway.getId());
step.setStepOrder(i + 1);
step.setCreateTime(new Date());
}
stepService.saveBatch(steps);
}
}
@Override
public IPage<KgClinicalPathway> pagePathways(String keyword, Integer pageNo, Integer pageSize) {
LambdaQueryWrapper<KgClinicalPathway> w = new LambdaQueryWrapper<>();
w.eq(KgClinicalPathway::getStatus, "ACTIVE")
.and(StringUtils.hasText(keyword), q -> q
.like(KgClinicalPathway::getPathwayName, keyword)
.or().like(KgClinicalPathway::getDiseaseName, keyword)
.or().like(KgClinicalPathway::getPathwayCode, keyword))
.orderByDesc(KgClinicalPathway::getCreateTime);
return pathwayService.page(new Page<>(pageNo, pageSize), w);
}
@Override
public List<KgPathwayStep> getPathwaySteps(Long pathwayId) {
LambdaQueryWrapper<KgPathwayStep> w = new LambdaQueryWrapper<>();
w.eq(KgPathwayStep::getPathwayId, pathwayId)
.orderByAsc(KgPathwayStep::getStepOrder);
return stepService.list(w);
}
}

View File

@@ -0,0 +1,79 @@
package com.healthlink.his.web.clinical.controller;
import com.core.common.core.domain.AjaxResult;
import com.core.common.core.domain.R;
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.web.clinical.appservice.IKgRelationAppService;
import com.healthlink.his.web.clinical.dto.KgPathwayDto;
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;
@Tag(name = "知识图谱管理")
@RestController
@RequestMapping("/knowledgegraph")
@Slf4j
@AllArgsConstructor
public class KgRelationController {
private final IKgRelationAppService kgRelationAppService;
@Operation(summary = "创建关系")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/relation")
public AjaxResult createRelation(@RequestBody KgEntityRelation relation) {
kgRelationAppService.createRelation(relation);
return AjaxResult.success();
}
@Operation(summary = "关系分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/relation/page")
public R<?> pageRelations(
@RequestParam(value = "sourceType", required = false) String sourceType,
@RequestParam(value = "targetType", required = false) String targetType,
@RequestParam(value = "relationType", required = false) String relationType,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
return R.ok(kgRelationAppService.pageRelations(sourceType, targetType, relationType, pageNo, pageSize));
}
@Operation(summary = "查询实体关系图")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/relation/graph/{entityType}/{entityId}")
public R<?> getRelationGraph(@PathVariable String entityType, @PathVariable String entityId) {
return R.ok(kgRelationAppService.getRelationGraph(entityType, entityId));
}
@Operation(summary = "创建临床路径")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/pathway")
public AjaxResult createPathway(@RequestBody KgPathwayDto dto) {
kgRelationAppService.createPathway(dto.getPathway(), dto.getSteps());
return AjaxResult.success();
}
@Operation(summary = "路径分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/pathway/page")
public R<?> pagePathways(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
return R.ok(kgRelationAppService.pagePathways(keyword, pageNo, pageSize));
}
@Operation(summary = "查询路径步骤")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/pathway/{id}/steps")
public R<?> getPathwaySteps(@PathVariable Long id) {
return R.ok(kgRelationAppService.getPathwaySteps(id));
}
}

View File

@@ -0,0 +1,13 @@
package com.healthlink.his.web.clinical.dto;
import com.healthlink.his.clinical.domain.KgClinicalPathway;
import com.healthlink.his.clinical.domain.KgPathwayStep;
import lombok.Data;
import java.util.List;
@Data
public class KgPathwayDto {
private KgClinicalPathway pathway;
private List<KgPathwayStep> steps;
}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.web.datacollection.appservice;
import com.core.common.core.domain.R;
public interface IDataCollectionAppService {
R<?> collectClinicalData(String startDate, String endDate, Long departmentId);
R<?> collectOperationalData(String startDate, String endDate);
}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.web.datacollection.appservice;
import com.core.common.core.domain.R;
public interface IDataDashboardAppService {
R<?> getRealtimeData();
R<?> getHistoricalData(String period);
}

View File

@@ -0,0 +1,78 @@
package com.healthlink.his.web.datacollection.appservice.impl;
import com.core.common.core.domain.R;
import com.healthlink.his.quality.service.IBusinessAnalyticsService;
import com.healthlink.his.quality.domain.BusinessAnalytics;
import com.healthlink.his.web.datacollection.appservice.IDataCollectionAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
@Slf4j
@AllArgsConstructor
public class DataCollectionAppServiceImpl implements IDataCollectionAppService {
private final IBusinessAnalyticsService analyticsService;
@Override
public R<?> collectClinicalData(String startDate, String endDate, Long departmentId) {
Map<String, Object> result = new HashMap<>();
try {
List<BusinessAnalytics> allData = analyticsService.list();
int collected = 0;
for (BusinessAnalytics ba : allData) {
if (departmentId != null && !departmentId.equals(ba.getDepartmentId())) {
continue;
}
if (startDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(startDate) < 0) {
continue;
}
if (endDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(endDate) > 0) {
continue;
}
collected++;
}
result.put("status", "success");
result.put("recordCount", collected);
result.put("message", "临床数据采集完成,共采集 " + collected + " 条记录");
log.info("临床数据采集完成: startDate={}, endDate={}, departmentId={}, count={}", startDate, endDate, departmentId, collected);
} catch (Exception e) {
log.error("临床数据采集失败", e);
result.put("status", "error");
result.put("message", "采集失败: " + e.getMessage());
return R.fail("采集失败: " + e.getMessage());
}
return R.ok(result);
}
@Override
public R<?> collectOperationalData(String startDate, String endDate) {
Map<String, Object> result = new HashMap<>();
try {
List<BusinessAnalytics> allData = analyticsService.list();
int collected = 0;
for (BusinessAnalytics ba : allData) {
if (startDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(startDate) < 0) {
continue;
}
if (endDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(endDate) > 0) {
continue;
}
collected++;
}
result.put("status", "success");
result.put("recordCount", collected);
result.put("message", "运营数据采集完成,共采集 " + collected + " 条记录");
log.info("运营数据采集完成: startDate={}, endDate={}, count={}", startDate, endDate, collected);
} catch (Exception e) {
log.error("运营数据采集失败", e);
result.put("status", "error");
result.put("message", "采集失败: " + e.getMessage());
return R.fail("采集失败: " + e.getMessage());
}
return R.ok(result);
}
}

View File

@@ -0,0 +1,137 @@
package com.healthlink.his.web.datacollection.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.healthlink.his.quality.domain.BusinessAnalytics;
import com.healthlink.his.quality.service.IBusinessAnalyticsService;
import com.healthlink.his.crossmodule.domain.DrgPerformance;
import com.healthlink.his.crossmodule.service.IDrgPerformanceService;
import com.healthlink.his.web.datacollection.appservice.IDataDashboardAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
@AllArgsConstructor
public class DataDashboardAppServiceImpl implements IDataDashboardAppService {
private final IBusinessAnalyticsService analyticsService;
private final IDrgPerformanceService drgPerformanceService;
@Override
public R<?> getRealtimeData() {
Map<String, Object> data = new HashMap<>();
List<BusinessAnalytics> allData = analyticsService.list();
BigDecimal totalRevenue = BigDecimal.ZERO;
BigDecimal totalCost = BigDecimal.ZERO;
int totalPatients = 0;
for (BusinessAnalytics ba : allData) {
if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue());
if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost());
if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount();
}
data.put("totalRevenue", totalRevenue);
data.put("totalCost", totalCost);
data.put("totalProfit", totalRevenue.subtract(totalCost));
data.put("totalPatients", totalPatients);
data.put("totalRecords", allData.size());
data.put("timestamp", new Date().toString());
LambdaQueryWrapper<DrgPerformance> perfW = new LambdaQueryWrapper<>();
perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1");
List<DrgPerformance> latestPerf = drgPerformanceService.list(perfW);
if (!latestPerf.isEmpty()) {
DrgPerformance p = latestPerf.get(0);
data.put("latestCmiValue", p.getCmiValue());
data.put("latestCostControlRate", p.getCostControlRate());
data.put("latestDrgCases", p.getTotalCases());
}
Map<String, BigDecimal> deptRevenue = allData.stream()
.filter(ba -> ba.getDepartmentName() != null)
.collect(Collectors.groupingBy(
BusinessAnalytics::getDepartmentName,
Collectors.reducing(BigDecimal.ZERO, ba -> ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add)));
List<Map<String, Object>> deptChart = new ArrayList<>();
deptRevenue.forEach((k, v) -> {
Map<String, Object> item = new HashMap<>();
item.put("department", k);
item.put("revenue", v);
deptChart.add(item);
});
data.put("departmentChart", deptChart);
log.info("实时数据查询完成: records={}", allData.size());
return R.ok(data);
}
@Override
public R<?> getHistoricalData(String period) {
Map<String, Object> data = new HashMap<>();
data.put("period", period);
List<BusinessAnalytics> allData = analyticsService.list();
String cutoffDate = null;
if ("week".equals(period)) {
cutoffDate = java.time.LocalDate.now().minusWeeks(1).toString();
} else if ("month".equals(period)) {
cutoffDate = java.time.LocalDate.now().minusMonths(1).toString();
} else if ("quarter".equals(period)) {
cutoffDate = java.time.LocalDate.now().minusMonths(3).toString();
} else if ("year".equals(period)) {
cutoffDate = java.time.LocalDate.now().minusYears(1).toString();
}
final String finalCutoff = cutoffDate;
if (finalCutoff != null) {
allData = allData.stream()
.filter(ba -> ba.getStatDate() != null && ba.getStatDate().compareTo(finalCutoff) >= 0)
.collect(Collectors.toList());
}
BigDecimal totalRevenue = BigDecimal.ZERO;
BigDecimal totalCost = BigDecimal.ZERO;
int totalPatients = 0;
for (BusinessAnalytics ba : allData) {
if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue());
if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost());
if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount();
}
data.put("totalRevenue", totalRevenue);
data.put("totalCost", totalCost);
data.put("totalProfit", totalRevenue.subtract(totalCost));
data.put("totalPatients", totalPatients);
data.put("totalRecords", allData.size());
data.put("cutoffDate", cutoffDate);
data.put("timestamp", new Date().toString());
Map<String, BigDecimal> monthlyRevenue = new LinkedHashMap<>();
Map<String, BigDecimal> monthlyCost = new LinkedHashMap<>();
for (BusinessAnalytics ba : allData) {
String month = ba.getStatDate() != null && ba.getStatDate().length() >= 7
? ba.getStatDate().substring(0, 7) : "未知";
monthlyRevenue.merge(month, ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add);
monthlyCost.merge(month, ba.getCost() != null ? ba.getCost() : BigDecimal.ZERO, BigDecimal::add);
}
List<Map<String, Object>> trendChart = new ArrayList<>();
monthlyRevenue.forEach((k, v) -> {
Map<String, Object> item = new HashMap<>();
item.put("month", k);
item.put("revenue", v);
item.put("cost", monthlyCost.getOrDefault(k, BigDecimal.ZERO));
trendChart.add(item);
});
data.put("trendChart", trendChart);
log.info("历史数据查询完成: period={}, records={}", period, allData.size());
return R.ok(data);
}
}

View File

@@ -0,0 +1,34 @@
package com.healthlink.his.web.datacollection.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.web.datacollection.appservice.IDataCollectionAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@AllArgsConstructor
@RestController
@RequestMapping("/data/collect")
@Slf4j
public class DataCollectionController {
private final IDataCollectionAppService dataCollectionAppService;
@PostMapping("/clinical")
@PreAuthorize("hasAuthority('reportmanage:report:edit')")
public R<?> collectClinicalData(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false) Long departmentId) {
return dataCollectionAppService.collectClinicalData(startDate, endDate, departmentId);
}
@PostMapping("/operational")
@PreAuthorize("hasAuthority('reportmanage:report:edit')")
public R<?> collectOperationalData(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
return dataCollectionAppService.collectOperationalData(startDate, endDate);
}
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.web.datacollection.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.web.datacollection.appservice.IDataDashboardAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@AllArgsConstructor
@RestController
@RequestMapping("/data/dashboard")
@Slf4j
public class DataDashboardController {
private final IDataDashboardAppService dataDashboardAppService;
@GetMapping("/realtime")
@PreAuthorize("hasAuthority('reportmanage:report:list')")
public R<?> getRealtimeData() {
return dataDashboardAppService.getRealtimeData();
}
@GetMapping("/historical")
@PreAuthorize("hasAuthority('reportmanage:report:list')")
public R<?> getHistoricalData(
@RequestParam(required = false, defaultValue = "month") String period) {
return dataDashboardAppService.getHistoricalData(period);
}
}

View File

@@ -0,0 +1,16 @@
package com.healthlink.his.web.datacollection.dto;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class DataCollectionDto {
private String collectionType;
private String startDate;
private String endDate;
private Long departmentId;
private Integer recordCount;
private String status;
private String message;
}

View File

@@ -0,0 +1,47 @@
package com.healthlink.his.web.knowledgegraph.appservice;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.healthlink.his.web.knowledgegraph.dto.*;
public interface IKgEntityAppService {
Boolean addDisease(KgDiseaseDto dto);
Boolean updateDisease(KgDiseaseDto dto);
Boolean deleteDisease(Long id);
KgDiseaseDto getDiseaseById(Long id);
IPage<KgDiseaseDto> pageDisease(String keyword, String category, Integer pageNo, Integer pageSize);
Boolean addSymptom(KgSymptomDto dto);
Boolean updateSymptom(KgSymptomDto dto);
Boolean deleteSymptom(Long id);
KgSymptomDto getSymptomById(Long id);
IPage<KgSymptomDto> pageSymptom(String keyword, String symptomType, Integer pageNo, Integer pageSize);
Boolean addDrug(KgDrugDto dto);
Boolean updateDrug(KgDrugDto dto);
Boolean deleteDrug(Long id);
KgDrugDto getDrugById(Long id);
IPage<KgDrugDto> pageDrug(String keyword, String category, Integer pageNo, Integer pageSize);
Boolean addExamination(KgExaminationDto dto);
Boolean updateExamination(KgExaminationDto dto);
Boolean deleteExamination(Long id);
KgExaminationDto getExaminationById(Long id);
IPage<KgExaminationDto> pageExamination(String keyword, String examType, Integer pageNo, Integer pageSize);
}

View File

@@ -0,0 +1,256 @@
package com.healthlink.his.web.knowledgegraph.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.knowledgegraph.domain.*;
import com.healthlink.his.knowledgegraph.service.*;
import com.healthlink.his.web.knowledgegraph.appservice.IKgEntityAppService;
import com.healthlink.his.web.knowledgegraph.dto.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
public class KgEntityAppServiceImpl implements IKgEntityAppService {
private final IKgDiseaseService kgDiseaseService;
private final IKgSymptomService kgSymptomService;
private final IKgDrugService kgDrugService;
private final IKgExaminationService kgExaminationService;
public KgEntityAppServiceImpl(IKgDiseaseService kgDiseaseService,
IKgSymptomService kgSymptomService,
IKgDrugService kgDrugService,
IKgExaminationService kgExaminationService) {
this.kgDiseaseService = kgDiseaseService;
this.kgSymptomService = kgSymptomService;
this.kgDrugService = kgDrugService;
this.kgExaminationService = kgExaminationService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addDisease(KgDiseaseDto dto) {
KgDisease entity = new KgDisease();
BeanUtils.copyProperties(dto, entity);
return kgDiseaseService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateDisease(KgDiseaseDto dto) {
if (dto.getId() == null) {
return false;
}
KgDisease entity = new KgDisease();
BeanUtils.copyProperties(dto, entity);
return kgDiseaseService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteDisease(Long id) {
return kgDiseaseService.removeById(id);
}
@Override
public KgDiseaseDto getDiseaseById(Long id) {
KgDisease entity = kgDiseaseService.getById(id);
if (entity == null) {
return null;
}
KgDiseaseDto dto = new KgDiseaseDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgDiseaseDto> pageDisease(String keyword, String category, Integer pageNo, Integer pageSize) {
Page<KgDisease> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgDisease> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgDisease::getDiseaseName, keyword)
.or().like(KgDisease::getDiseaseCode, keyword));
}
if (StringUtils.hasText(category)) {
wrapper.eq(KgDisease::getCategory, category);
}
wrapper.orderByDesc(KgDisease::getCreateTime);
Page<KgDisease> result = kgDiseaseService.page(page, wrapper);
return result.convert(entity -> {
KgDiseaseDto dto = new KgDiseaseDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addSymptom(KgSymptomDto dto) {
KgSymptom entity = new KgSymptom();
BeanUtils.copyProperties(dto, entity);
return kgSymptomService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateSymptom(KgSymptomDto dto) {
if (dto.getId() == null) {
return false;
}
KgSymptom entity = new KgSymptom();
BeanUtils.copyProperties(dto, entity);
return kgSymptomService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteSymptom(Long id) {
return kgSymptomService.removeById(id);
}
@Override
public KgSymptomDto getSymptomById(Long id) {
KgSymptom entity = kgSymptomService.getById(id);
if (entity == null) {
return null;
}
KgSymptomDto dto = new KgSymptomDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgSymptomDto> pageSymptom(String keyword, String symptomType, Integer pageNo, Integer pageSize) {
Page<KgSymptom> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgSymptom> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgSymptom::getSymptomName, keyword)
.or().like(KgSymptom::getSymptomCode, keyword));
}
if (StringUtils.hasText(symptomType)) {
wrapper.eq(KgSymptom::getSymptomType, symptomType);
}
wrapper.orderByDesc(KgSymptom::getCreateTime);
Page<KgSymptom> result = kgSymptomService.page(page, wrapper);
return result.convert(entity -> {
KgSymptomDto dto = new KgSymptomDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addDrug(KgDrugDto dto) {
KgDrug entity = new KgDrug();
BeanUtils.copyProperties(dto, entity);
return kgDrugService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateDrug(KgDrugDto dto) {
if (dto.getId() == null) {
return false;
}
KgDrug entity = new KgDrug();
BeanUtils.copyProperties(dto, entity);
return kgDrugService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteDrug(Long id) {
return kgDrugService.removeById(id);
}
@Override
public KgDrugDto getDrugById(Long id) {
KgDrug entity = kgDrugService.getById(id);
if (entity == null) {
return null;
}
KgDrugDto dto = new KgDrugDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgDrugDto> pageDrug(String keyword, String category, Integer pageNo, Integer pageSize) {
Page<KgDrug> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgDrug> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgDrug::getDrugName, keyword)
.or().like(KgDrug::getDrugCode, keyword));
}
if (StringUtils.hasText(category)) {
wrapper.eq(KgDrug::getCategory, category);
}
wrapper.orderByDesc(KgDrug::getCreateTime);
Page<KgDrug> result = kgDrugService.page(page, wrapper);
return result.convert(entity -> {
KgDrugDto dto = new KgDrugDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addExamination(KgExaminationDto dto) {
KgExamination entity = new KgExamination();
BeanUtils.copyProperties(dto, entity);
return kgExaminationService.save(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateExamination(KgExaminationDto dto) {
if (dto.getId() == null) {
return false;
}
KgExamination entity = new KgExamination();
BeanUtils.copyProperties(dto, entity);
return kgExaminationService.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteExamination(Long id) {
return kgExaminationService.removeById(id);
}
@Override
public KgExaminationDto getExaminationById(Long id) {
KgExamination entity = kgExaminationService.getById(id);
if (entity == null) {
return null;
}
KgExaminationDto dto = new KgExaminationDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
@Override
public IPage<KgExaminationDto> pageExamination(String keyword, String examType, Integer pageNo, Integer pageSize) {
Page<KgExamination> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<KgExamination> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w.like(KgExamination::getExamName, keyword)
.or().like(KgExamination::getExamCode, keyword));
}
if (StringUtils.hasText(examType)) {
wrapper.eq(KgExamination::getExamType, examType);
}
wrapper.orderByDesc(KgExamination::getCreateTime);
Page<KgExamination> result = kgExaminationService.page(page, wrapper);
return result.convert(entity -> {
KgExaminationDto dto = new KgExaminationDto();
BeanUtils.copyProperties(entity, dto);
return dto;
});
}
}

View File

@@ -0,0 +1,301 @@
package com.healthlink.his.web.knowledgegraph.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.domain.R;
import com.healthlink.his.web.knowledgegraph.appservice.IKgEntityAppService;
import com.healthlink.his.web.knowledgegraph.dto.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.*;
@Slf4j
@Tag(name = "知识图谱-实体管理")
@RestController
@RequestMapping("/knowledgegraph")
public class KgEntityController {
private final IKgEntityAppService kgEntityAppService;
public KgEntityController(IKgEntityAppService kgEntityAppService) {
this.kgEntityAppService = kgEntityAppService;
}
@Operation(summary = "创建疾病")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/disease")
public R<String> addDisease(@RequestBody KgDiseaseDto dto) {
try {
Boolean result = kgEntityAppService.addDisease(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建疾病失败", e);
return R.fail("创建疾病失败: " + e.getMessage());
}
}
@Operation(summary = "疾病分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/disease/page")
public R<IPage<KgDiseaseDto>> pageDisease(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "分类") @RequestParam(required = false) String category,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgDiseaseDto> page = kgEntityAppService.pageDisease(keyword, category, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询疾病列表失败", e);
return R.fail("查询疾病列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新疾病")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/disease")
public R<String> updateDisease(@RequestBody KgDiseaseDto dto) {
try {
Boolean result = kgEntityAppService.updateDisease(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新疾病失败", e);
return R.fail("更新疾病失败: " + e.getMessage());
}
}
@Operation(summary = "删除疾病")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/disease/{id}")
public R<String> deleteDisease(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteDisease(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除疾病失败", e);
return R.fail("删除疾病失败: " + e.getMessage());
}
}
@Operation(summary = "疾病详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/disease/{id}")
public R<KgDiseaseDto> getDiseaseById(@PathVariable Long id) {
try {
KgDiseaseDto dto = kgEntityAppService.getDiseaseById(id);
return dto != null ? R.ok(dto) : R.fail("未找到疾病信息");
} catch (Exception e) {
log.error("获取疾病详情失败", e);
return R.fail("获取疾病详情失败: " + e.getMessage());
}
}
@Operation(summary = "创建症状")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/symptom")
public R<String> addSymptom(@RequestBody KgSymptomDto dto) {
try {
Boolean result = kgEntityAppService.addSymptom(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建症状失败", e);
return R.fail("创建症状失败: " + e.getMessage());
}
}
@Operation(summary = "症状分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/symptom/page")
public R<IPage<KgSymptomDto>> pageSymptom(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "症状类型") @RequestParam(required = false) String symptomType,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgSymptomDto> page = kgEntityAppService.pageSymptom(keyword, symptomType, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询症状列表失败", e);
return R.fail("查询症状列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新症状")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/symptom")
public R<String> updateSymptom(@RequestBody KgSymptomDto dto) {
try {
Boolean result = kgEntityAppService.updateSymptom(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新症状失败", e);
return R.fail("更新症状失败: " + e.getMessage());
}
}
@Operation(summary = "删除症状")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/symptom/{id}")
public R<String> deleteSymptom(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteSymptom(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除症状失败", e);
return R.fail("删除症状失败: " + e.getMessage());
}
}
@Operation(summary = "症状详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/symptom/{id}")
public R<KgSymptomDto> getSymptomById(@PathVariable Long id) {
try {
KgSymptomDto dto = kgEntityAppService.getSymptomById(id);
return dto != null ? R.ok(dto) : R.fail("未找到症状信息");
} catch (Exception e) {
log.error("获取症状详情失败", e);
return R.fail("获取症状详情失败: " + e.getMessage());
}
}
@Operation(summary = "创建药物")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/drug")
public R<String> addDrug(@RequestBody KgDrugDto dto) {
try {
Boolean result = kgEntityAppService.addDrug(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建药物失败", e);
return R.fail("创建药物失败: " + e.getMessage());
}
}
@Operation(summary = "药物分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/drug/page")
public R<IPage<KgDrugDto>> pageDrug(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "分类") @RequestParam(required = false) String category,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgDrugDto> page = kgEntityAppService.pageDrug(keyword, category, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询药物列表失败", e);
return R.fail("查询药物列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新药物")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/drug")
public R<String> updateDrug(@RequestBody KgDrugDto dto) {
try {
Boolean result = kgEntityAppService.updateDrug(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新药物失败", e);
return R.fail("更新药物失败: " + e.getMessage());
}
}
@Operation(summary = "删除药物")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/drug/{id}")
public R<String> deleteDrug(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteDrug(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除药物失败", e);
return R.fail("删除药物失败: " + e.getMessage());
}
}
@Operation(summary = "药物详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/drug/{id}")
public R<KgDrugDto> getDrugById(@PathVariable Long id) {
try {
KgDrugDto dto = kgEntityAppService.getDrugById(id);
return dto != null ? R.ok(dto) : R.fail("未找到药物信息");
} catch (Exception e) {
log.error("获取药物详情失败", e);
return R.fail("获取药物详情失败: " + e.getMessage());
}
}
@Operation(summary = "创建检查")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PostMapping("/examination")
public R<String> addExamination(@RequestBody KgExaminationDto dto) {
try {
Boolean result = kgEntityAppService.addExamination(dto);
return result ? R.ok("创建成功") : R.fail("创建失败");
} catch (Exception e) {
log.error("创建检查失败", e);
return R.fail("创建检查失败: " + e.getMessage());
}
}
@Operation(summary = "检查分页查询")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/examination/page")
public R<IPage<KgExaminationDto>> pageExamination(
@Parameter(description = "关键词") @RequestParam(required = false) String keyword,
@Parameter(description = "检查类型") @RequestParam(required = false) String examType,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
try {
IPage<KgExaminationDto> page = kgEntityAppService.pageExamination(keyword, examType, pageNo, pageSize);
return R.ok(page);
} catch (Exception e) {
log.error("查询检查列表失败", e);
return R.fail("查询检查列表失败: " + e.getMessage());
}
}
@Operation(summary = "更新检查")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@PutMapping("/examination")
public R<String> updateExamination(@RequestBody KgExaminationDto dto) {
try {
Boolean result = kgEntityAppService.updateExamination(dto);
return result ? R.ok("更新成功") : R.fail("更新失败");
} catch (Exception e) {
log.error("更新检查失败", e);
return R.fail("更新检查失败: " + e.getMessage());
}
}
@Operation(summary = "删除检查")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:edit')")
@DeleteMapping("/examination/{id}")
public R<String> deleteExamination(@PathVariable Long id) {
try {
Boolean result = kgEntityAppService.deleteExamination(id);
return result ? R.ok("删除成功") : R.fail("删除失败");
} catch (Exception e) {
log.error("删除检查失败", e);
return R.fail("删除检查失败: " + e.getMessage());
}
}
@Operation(summary = "检查详情")
@PreAuthorize("@ss.hasPermi('system:knowledgegraph:list')")
@GetMapping("/examination/{id}")
public R<KgExaminationDto> getExaminationById(@PathVariable Long id) {
try {
KgExaminationDto dto = kgEntityAppService.getExaminationById(id);
return dto != null ? R.ok(dto) : R.fail("未找到检查信息");
} catch (Exception e) {
log.error("获取检查详情失败", e);
return R.fail("获取检查详情失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,27 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgDiseaseDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String diseaseCode;
private String diseaseName;
private String category;
private String department;
private String severityLevel;
}

View File

@@ -0,0 +1,29 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgDrugDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String drugCode;
private String drugName;
private String genericName;
private String category;
private String dosageForm;
private String contraindications;
}

View File

@@ -0,0 +1,27 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgExaminationDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String examCode;
private String examName;
private String examType;
private String department;
private String referenceRange;
}

View File

@@ -0,0 +1,25 @@
package com.healthlink.his.web.knowledgegraph.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class KgSymptomDto implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String symptomCode;
private String symptomName;
private String bodyPart;
private String symptomType;
}

View File

@@ -11,4 +11,9 @@ public interface INursingMobileAppService {
Map<String, Object> executeOrder(Long requestId, String adviceTable, Long encounterId, Long patientId);
NursingMobileVitalSignDto saveVitalSign(NursingMobileVitalSignDto vitalSign);
NursingMobileVitalSignTrendDto getVitalSignTrend(Long patientId, Integer days);
NursingMobileAssessmentDto submitAssessment(NursingMobileAssessmentDto dto);
List<NursingMobileAssessmentDto> getAssessmentList(Long patientId);
NursingMobileInfusionDto startInfusion(NursingMobileInfusionDto dto);
NursingMobileInfusionDto addPatrol(NursingMobileInfusionDto dto);
List<NursingMobileInfusionDto> getInfusionStatus(Long patientId);
}

View File

@@ -1,11 +1,16 @@
package com.healthlink.his.web.nursing.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.healthlink.his.nursing.domain.NursingAssessment;
import com.healthlink.his.nursing.domain.NursingInfusionPatrol;
import com.healthlink.his.nursing.domain.NursingVitalSignsChart;
import com.healthlink.his.nursing.service.INursingAssessmentService;
import com.healthlink.his.nursing.service.INursingInfusionPatrolService;
import com.healthlink.his.nursing.service.INursingVitalSignsChartService;
import com.healthlink.his.web.nursing.appservice.INursingMobileAppService;
import com.healthlink.his.web.nursing.dto.*;
import com.healthlink.his.web.nursing.mapper.NursingMobileAppMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -24,6 +29,14 @@ public class NursingMobileAppServiceImpl implements INursingMobileAppService {
@Resource
private INursingVitalSignsChartService vitalSignsChartService;
@Resource
private INursingAssessmentService assessmentService;
@Resource
private INursingInfusionPatrolService infusionPatrolService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public List<NursingMobilePatientDto> getMobilePatientList(String wardName, String searchKey) {
return mobileMapper.selectMobilePatientList(wardName, searchKey);
@@ -156,4 +169,158 @@ public class NursingMobileAppServiceImpl implements INursingMobileAppService {
return trend;
}
@Override
@Transactional(rollbackFor = Exception.class)
public NursingMobileAssessmentDto submitAssessment(NursingMobileAssessmentDto dto) {
NursingAssessment assessment = new NursingAssessment();
assessment.setEncounterId(dto.getEncounterId());
assessment.setPatientId(dto.getPatientId());
assessment.setPatientName(dto.getPatientName());
assessment.setAssessorId(dto.getAssessorId());
assessment.setAssessorName(dto.getAssessorName());
assessment.setAssessmentType(dto.getAssessmentType());
assessment.setAssessmentTool(dto.getAssessmentTool());
assessment.setTotalScore(dto.getTotalScore());
assessment.setRiskLevel(calculateRiskLevel(dto.getAssessmentTool(), dto.getTotalScore()));
assessment.setDetail(dto.getDetail());
assessment.setAssessmentTime(dto.getAssessmentTime() != null ? dto.getAssessmentTime() : new Date());
assessment.setDeleteFlag("0");
try {
assessment.setItemScores(dto.getItemScores() != null ? objectMapper.writeValueAsString(dto.getItemScores()) : null);
} catch (Exception e) {
assessment.setItemScores(null);
}
assessmentService.save(assessment);
dto.setId(assessment.getId());
dto.setRiskLevel(assessment.getRiskLevel());
return dto;
}
@Override
public List<NursingMobileAssessmentDto> getAssessmentList(Long patientId) {
LambdaQueryWrapper<NursingAssessment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(NursingAssessment::getPatientId, patientId)
.orderByDesc(NursingAssessment::getAssessmentTime);
List<NursingAssessment> records = assessmentService.list(wrapper);
List<NursingMobileAssessmentDto> result = new ArrayList<>();
for (NursingAssessment r : records) {
NursingMobileAssessmentDto dto = new NursingMobileAssessmentDto();
dto.setId(r.getId());
dto.setEncounterId(r.getEncounterId());
dto.setPatientId(r.getPatientId());
dto.setPatientName(r.getPatientName());
dto.setAssessorName(r.getAssessorName());
dto.setAssessmentType(r.getAssessmentType());
dto.setAssessmentTool(r.getAssessmentTool());
dto.setTotalScore(r.getTotalScore());
dto.setRiskLevel(r.getRiskLevel());
dto.setDetail(r.getDetail());
dto.setAssessmentTime(r.getAssessmentTime());
try {
if (r.getItemScores() != null) {
dto.setItemScores(objectMapper.readValue(r.getItemScores(), Map.class));
}
} catch (Exception e) {
dto.setItemScores(null);
}
result.add(dto);
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public NursingMobileInfusionDto startInfusion(NursingMobileInfusionDto dto) {
NursingInfusionPatrol patrol = new NursingInfusionPatrol();
patrol.setEncounterId(dto.getEncounterId());
patrol.setPatientId(dto.getPatientId());
patrol.setPatientName(dto.getPatientName());
patrol.setOrderId(dto.getOrderId());
patrol.setDrugName(dto.getDrugName());
patrol.setInfusionRate(dto.getInfusionRate());
patrol.setTotalVolume(dto.getTotalVolume());
patrol.setStartTime(dto.getStartTime() != null ? dto.getStartTime() : new Date());
patrol.setPatencyStatus("NORMAL");
patrol.setPatrolNurseId(dto.getPatrolNurseId());
patrol.setPatrolNurseName(dto.getPatrolNurseName());
patrol.setCreateTime(new Date());
infusionPatrolService.save(patrol);
dto.setId(patrol.getId());
dto.setPatencyStatus("NORMAL");
dto.setStatus("RUNNING");
return dto;
}
@Override
@Transactional(rollbackFor = Exception.class)
public NursingMobileInfusionDto addPatrol(NursingMobileInfusionDto dto) {
NursingInfusionPatrol patrol = new NursingInfusionPatrol();
patrol.setEncounterId(dto.getEncounterId());
patrol.setPatientId(dto.getPatientId());
patrol.setPatientName(dto.getPatientName());
patrol.setOrderId(dto.getOrderId());
patrol.setDrugName(dto.getDrugName());
patrol.setPatrolTime(new Date());
patrol.setDripRate(dto.getDripRate());
patrol.setPatencyStatus(dto.getPatencyStatus());
patrol.setAdverseReaction(dto.getAdverseReaction());
patrol.setPatrolNurseId(dto.getPatrolNurseId());
patrol.setPatrolNurseName(dto.getPatrolNurseName());
patrol.setCreateTime(new Date());
infusionPatrolService.save(patrol);
dto.setId(patrol.getId());
dto.setPatrolTime(patrol.getPatrolTime());
return dto;
}
@Override
public List<NursingMobileInfusionDto> getInfusionStatus(Long patientId) {
LambdaQueryWrapper<NursingInfusionPatrol> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(NursingInfusionPatrol::getPatientId, patientId)
.orderByDesc(NursingInfusionPatrol::getStartTime);
List<NursingInfusionPatrol> records = infusionPatrolService.list(wrapper);
Map<Long, NursingMobileInfusionDto> latestMap = new LinkedHashMap<>();
for (NursingInfusionPatrol r : records) {
Long orderId = r.getOrderId();
if (orderId == null) orderId = r.getId();
if (!latestMap.containsKey(orderId)) {
NursingMobileInfusionDto dto = new NursingMobileInfusionDto();
dto.setId(r.getId());
dto.setEncounterId(r.getEncounterId());
dto.setPatientId(r.getPatientId());
dto.setPatientName(r.getPatientName());
dto.setOrderId(r.getOrderId());
dto.setDrugName(r.getDrugName());
dto.setInfusionRate(r.getInfusionRate());
dto.setTotalVolume(r.getTotalVolume());
dto.setStartTime(r.getStartTime());
dto.setPatrolTime(r.getPatrolTime());
dto.setDripRate(r.getDripRate());
dto.setPatencyStatus(r.getPatencyStatus());
dto.setAdverseReaction(r.getAdverseReaction());
dto.setPatrolNurseName(r.getPatrolNurseName());
dto.setStatus("RUNNING");
latestMap.put(orderId, dto);
}
}
return new ArrayList<>(latestMap.values());
}
private String calculateRiskLevel(String tool, Integer score) {
if (score == null) return "NORMAL";
if ("BRADEN".equals(tool)) {
if (score <= 12) return "HIGH";
if (score <= 14) return "MEDIUM";
return "LOW";
} else if ("MORSE".equals(tool)) {
if (score >= 45) return "HIGH";
if (score >= 25) return "MEDIUM";
return "LOW";
} else if ("NRS2002".equals(tool)) {
if (score >= 3) return "HIGH";
return "LOW";
}
return "NORMAL";
}
}

View File

@@ -69,4 +69,44 @@ public class NursingMobileController {
NursingMobileVitalSignTrendDto trend = mobileAppService.getVitalSignTrend(patientId, days);
return R.ok(trend);
}
@Operation(summary = "提交护理评估")
@PostMapping("/assessment/submit")
@PreAuthorize("hasAuthority('nursing:nursing:edit')")
public R<?> submitAssessment(@RequestBody NursingMobileAssessmentDto assessment) {
NursingMobileAssessmentDto saved = mobileAppService.submitAssessment(assessment);
return R.ok(saved);
}
@Operation(summary = "查询评估记录")
@GetMapping("/assessment/list/{patientId}")
@PreAuthorize("hasAuthority('nursing:nursing:list')")
public R<?> getAssessmentList(@PathVariable Long patientId) {
List<NursingMobileAssessmentDto> list = mobileAppService.getAssessmentList(patientId);
return R.ok(list);
}
@Operation(summary = "开始输液")
@PostMapping("/infusion/start")
@PreAuthorize("hasAuthority('nursing:nursing:edit')")
public R<?> startInfusion(@RequestBody NursingMobileInfusionDto infusion) {
NursingMobileInfusionDto saved = mobileAppService.startInfusion(infusion);
return R.ok(saved);
}
@Operation(summary = "输液巡视记录")
@PostMapping("/infusion/patrol")
@PreAuthorize("hasAuthority('nursing:nursing:edit')")
public R<?> addPatrol(@RequestBody NursingMobileInfusionDto patrol) {
NursingMobileInfusionDto saved = mobileAppService.addPatrol(patrol);
return R.ok(saved);
}
@Operation(summary = "输液状态查询")
@GetMapping("/infusion/status/{patientId}")
@PreAuthorize("hasAuthority('nursing:nursing:list')")
public R<?> getInfusionStatus(@PathVariable Long patientId) {
List<NursingMobileInfusionDto> list = mobileAppService.getInfusionStatus(patientId);
return R.ok(list);
}
}

View File

@@ -0,0 +1,24 @@
package com.healthlink.his.web.nursing.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.Date;
import java.util.Map;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class NursingMobileAssessmentDto {
private Long id;
private Long encounterId;
private Long patientId;
private String patientName;
private Long assessorId;
private String assessorName;
private String assessmentType;
private String assessmentTool;
private Integer totalScore;
private String riskLevel;
private Map<String, Integer> itemScores;
private String detail;
private Date assessmentTime;
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.web.nursing.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.Date;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class NursingMobileInfusionDto {
private Long id;
private Long encounterId;
private Long patientId;
private String patientName;
private Long orderId;
private String drugName;
private String infusionRate;
private Integer totalVolume;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date patrolTime;
private Integer dripRate;
private String patencyStatus;
private String adverseReaction;
private Long patrolNurseId;
private String patrolNurseName;
private String status;
private Integer remainingVolume;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.web.reportmanage.appservice;
import com.core.common.core.domain.R;
import java.util.Map;
public interface IBiReportAppService {
R<?> generateBiReport(String type, Map<String, Object> filters);
R<?> getReportDashboard();
}

View File

@@ -0,0 +1,181 @@
package com.healthlink.his.web.reportmanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.healthlink.his.quality.domain.BusinessAnalytics;
import com.healthlink.his.quality.service.IBusinessAnalyticsService;
import com.healthlink.his.crossmodule.domain.DrgPerformance;
import com.healthlink.his.crossmodule.service.IDrgPerformanceService;
import com.healthlink.his.web.reportmanage.appservice.IBiReportAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
@AllArgsConstructor
public class BiReportAppServiceImpl implements IBiReportAppService {
private final IBusinessAnalyticsService analyticsService;
private final IDrgPerformanceService drgPerformanceService;
@Override
public R<?> generateBiReport(String type, Map<String, Object> filters) {
Map<String, Object> result = new HashMap<>();
result.put("reportType", type);
result.put("generatedAt", new Date().toString());
List<BusinessAnalytics> allData = analyticsService.list();
if (filters != null && filters.containsKey("departmentId")) {
Long deptId = Long.valueOf(filters.get("departmentId").toString());
allData = allData.stream()
.filter(ba -> deptId.equals(ba.getDepartmentId()))
.collect(Collectors.toList());
}
if (filters != null && filters.containsKey("startDate")) {
String start = filters.get("startDate").toString();
allData = allData.stream()
.filter(ba -> ba.getStatDate() != null && ba.getStatDate().compareTo(start) >= 0)
.collect(Collectors.toList());
}
if (filters != null && filters.containsKey("endDate")) {
String end = filters.get("endDate").toString();
allData = allData.stream()
.filter(ba -> ba.getStatDate() != null && ba.getStatDate().compareTo(end) <= 0)
.collect(Collectors.toList());
}
Map<String, Object> summary = new HashMap<>();
BigDecimal totalRevenue = BigDecimal.ZERO;
BigDecimal totalCost = BigDecimal.ZERO;
int totalPatients = 0;
for (BusinessAnalytics ba : allData) {
if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue());
if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost());
if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount();
}
summary.put("totalRevenue", totalRevenue);
summary.put("totalCost", totalCost);
summary.put("totalProfit", totalRevenue.subtract(totalCost));
summary.put("totalPatients", totalPatients);
summary.put("totalRecords", allData.size());
BigDecimal profitRate = totalRevenue.compareTo(BigDecimal.ZERO) > 0
? totalRevenue.subtract(totalCost).multiply(new BigDecimal("100")).divide(totalRevenue, 2, RoundingMode.HALF_UP)
: BigDecimal.ZERO;
summary.put("profitRate", profitRate);
result.put("summary", summary);
Map<String, Object> charts = new HashMap<>();
if ("revenue".equals(type) || "overview".equals(type)) {
Map<String, BigDecimal> monthlyRevenue = new LinkedHashMap<>();
Map<String, BigDecimal> monthlyCost = new LinkedHashMap<>();
for (BusinessAnalytics ba : allData) {
String month = ba.getStatDate() != null && ba.getStatDate().length() >= 7
? ba.getStatDate().substring(0, 7) : "未知";
monthlyRevenue.merge(month, ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add);
monthlyCost.merge(month, ba.getCost() != null ? ba.getCost() : BigDecimal.ZERO, BigDecimal::add);
}
List<Map<String, Object>> revenueChart = new ArrayList<>();
monthlyRevenue.forEach((k, v) -> {
Map<String, Object> item = new HashMap<>();
item.put("month", k);
item.put("revenue", v);
item.put("cost", monthlyCost.getOrDefault(k, BigDecimal.ZERO));
revenueChart.add(item);
});
charts.put("revenueChart", revenueChart);
}
if ("department".equals(type) || "overview".equals(type)) {
Map<String, BigDecimal> deptRevenue = allData.stream()
.filter(ba -> ba.getDepartmentName() != null)
.collect(Collectors.groupingBy(
BusinessAnalytics::getDepartmentName,
Collectors.reducing(BigDecimal.ZERO, ba -> ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add)));
List<Map<String, Object>> deptChart = new ArrayList<>();
deptRevenue.forEach((k, v) -> {
Map<String, Object> item = new HashMap<>();
item.put("department", k);
item.put("revenue", v);
deptChart.add(item);
});
charts.put("departmentChart", deptChart);
}
if ("drg".equals(type) || "overview".equals(type)) {
LambdaQueryWrapper<DrgPerformance> perfW = new LambdaQueryWrapper<>();
perfW.orderByAsc(DrgPerformance::getStatMonth);
List<DrgPerformance> perfList = drgPerformanceService.list(perfW);
List<Map<String, Object>> cmiChart = new ArrayList<>();
for (DrgPerformance p : perfList) {
Map<String, Object> item = new HashMap<>();
item.put("month", p.getStatMonth());
item.put("cmiValue", p.getCmiValue());
item.put("costControlRate", p.getCostControlRate());
item.put("totalCases", p.getTotalCases());
cmiChart.add(item);
}
charts.put("drgChart", cmiChart);
}
result.put("charts", charts);
result.put("records", allData.stream().limit(100).map(ba -> {
Map<String, Object> row = new HashMap<>();
row.put("statDate", ba.getStatDate());
row.put("departmentName", ba.getDepartmentName());
row.put("revenue", ba.getRevenue());
row.put("cost", ba.getCost());
row.put("patientCount", ba.getPatientCount());
return row;
}).collect(Collectors.toList()));
log.info("BI报表生成完成: type={}, records={}", type, allData.size());
return R.ok(result);
}
@Override
public R<?> getReportDashboard() {
Map<String, Object> dashboard = new HashMap<>();
List<BusinessAnalytics> allData = analyticsService.list();
BigDecimal totalRevenue = BigDecimal.ZERO;
BigDecimal totalCost = BigDecimal.ZERO;
int totalPatients = 0;
for (BusinessAnalytics ba : allData) {
if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue());
if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost());
if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount();
}
dashboard.put("totalRevenue", totalRevenue);
dashboard.put("totalCost", totalCost);
dashboard.put("totalProfit", totalRevenue.subtract(totalCost));
dashboard.put("totalPatients", totalPatients);
dashboard.put("totalRecords", allData.size());
dashboard.put("reportTypes", Arrays.asList(
Map.of("value", "overview", "label", "综合概览"),
Map.of("value", "revenue", "label", "收入分析"),
Map.of("value", "department", "label", "科室分析"),
Map.of("value", "drg", "label", "DRG分析")
));
LambdaQueryWrapper<DrgPerformance> perfW = new LambdaQueryWrapper<>();
perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1");
List<DrgPerformance> latestPerf = drgPerformanceService.list(perfW);
if (!latestPerf.isEmpty()) {
DrgPerformance p = latestPerf.get(0);
dashboard.put("latestCmiValue", p.getCmiValue());
dashboard.put("latestCostControlRate", p.getCostControlRate());
dashboard.put("latestDrgCases", p.getTotalCases());
}
return R.ok(dashboard);
}
}

View File

@@ -0,0 +1,33 @@
package com.healthlink.his.web.reportmanage.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.web.reportmanage.appservice.IBiReportAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@AllArgsConstructor
@RestController("reportBiReportController")
@RequestMapping("/report/bi")
@Slf4j
public class BiReportController {
private final IBiReportAppService biReportAppService;
@PostMapping("/generate")
@PreAuthorize("hasAuthority('reportmanage:report:edit')")
public R<?> generateBiReport(
@RequestParam(required = false, defaultValue = "overview") String type,
@RequestBody(required = false) Map<String, Object> filters) {
return biReportAppService.generateBiReport(type, filters);
}
@GetMapping("/dashboard")
@PreAuthorize("hasAuthority('reportmanage:report:list')")
public R<?> getReportDashboard() {
return biReportAppService.getReportDashboard();
}
}

View File

@@ -0,0 +1,17 @@
package com.healthlink.his.web.reportmanage.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
@Data
@Accessors(chain = true)
public class BiReportDto {
private String reportType;
private String title;
private List<Map<String, Object>> records;
private Map<String, Object> summary;
private List<Map<String, Object>> charts;
}

View File

@@ -0,0 +1,15 @@
package com.healthlink.his.web.telehealth.appservice;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.web.telehealth.dto.TelehealthConsultationDto;
public interface ITelehealthAppService {
Long createConsultation(TelehealthConsultationDto dto);
Page<TelehealthConsultationDto> pageConsultation(TelehealthConsultationDto dto);
Boolean replyConsultation(TelehealthConsultationDto dto);
Boolean prescribeConsultation(TelehealthConsultationDto dto);
}

View File

@@ -0,0 +1,102 @@
package com.healthlink.his.web.telehealth.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.utils.SecurityUtils;
import com.healthlink.his.web.telehealth.appservice.ITelehealthAppService;
import com.healthlink.his.web.telehealth.domain.TelehealthConsultation;
import com.healthlink.his.web.telehealth.dto.TelehealthConsultationDto;
import com.healthlink.his.web.telehealth.mapper.TelehealthConsultationMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import jakarta.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class TelehealthAppServiceImpl implements ITelehealthAppService {
@Resource
private TelehealthConsultationMapper telehealthConsultationMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createConsultation(TelehealthConsultationDto dto) {
TelehealthConsultation entity = new TelehealthConsultation();
entity.setPatientId(dto.getPatientId());
entity.setDoctorId(dto.getDoctorId());
entity.setConsultationType(dto.getConsultationType());
entity.setStatus("PENDING");
entity.setChiefComplaint(dto.getChiefComplaint());
entity.setConsultationTime(new Date());
entity.setTenantId(SecurityUtils.getLoginUser().getTenantId());
telehealthConsultationMapper.insert(entity);
return entity.getId();
}
@Override
public Page<TelehealthConsultationDto> pageConsultation(TelehealthConsultationDto dto) {
Page<TelehealthConsultation> page = new Page<>(dto.getPageNum(), dto.getPageSize());
LambdaQueryWrapper<TelehealthConsultation> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(dto.getStatus())) {
wrapper.eq(TelehealthConsultation::getStatus, dto.getStatus());
}
if (dto.getDoctorId() != null) {
wrapper.eq(TelehealthConsultation::getDoctorId, dto.getDoctorId());
}
if (dto.getPatientId() != null) {
wrapper.eq(TelehealthConsultation::getPatientId, dto.getPatientId());
}
wrapper.orderByDesc(TelehealthConsultation::getCreateTime);
Page<TelehealthConsultation> result = telehealthConsultationMapper.selectPage(page, wrapper);
Page<TelehealthConsultationDto> dtoPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
List<TelehealthConsultationDto> dtoList = result.getRecords().stream()
.map(this::toDto)
.collect(Collectors.toList());
dtoPage.setRecords(dtoList);
return dtoPage;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean replyConsultation(TelehealthConsultationDto dto) {
TelehealthConsultation entity = telehealthConsultationMapper.selectById(dto.getId());
if (entity == null) {
throw new RuntimeException("问诊记录不存在");
}
entity.setDiagnosis(dto.getDiagnosis());
entity.setStatus("IN_PROGRESS");
telehealthConsultationMapper.updateById(entity);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean prescribeConsultation(TelehealthConsultationDto dto) {
TelehealthConsultation entity = telehealthConsultationMapper.selectById(dto.getId());
if (entity == null) {
throw new RuntimeException("问诊记录不存在");
}
entity.setPrescription(dto.getPrescription());
entity.setDiagnosis(dto.getDiagnosis());
entity.setStatus("COMPLETED");
entity.setEndTime(new Date());
telehealthConsultationMapper.updateById(entity);
return true;
}
private TelehealthConsultationDto toDto(TelehealthConsultation entity) {
TelehealthConsultationDto dto = new TelehealthConsultationDto();
BeanUtils.copyProperties(entity, dto);
return dto;
}
}

View File

@@ -0,0 +1,75 @@
package com.healthlink.his.web.telehealth.controller;
import com.core.common.core.domain.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.web.telehealth.appservice.ITelehealthAppService;
import com.healthlink.his.web.telehealth.dto.TelehealthConsultationDto;
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.*;
import jakarta.annotation.Resource;
@Slf4j
@Tag(name = "互联网医院-在线问诊")
@RestController
@RequestMapping("/telehealth/consultation")
public class TelehealthController {
@Resource
private ITelehealthAppService telehealthAppService;
@Operation(summary = "创建问诊")
@PostMapping("/create")
@PreAuthorize("@ss.hasPermi('outpatient:telehealth:edit')")
public R<Long> create(@RequestBody TelehealthConsultationDto dto) {
try {
Long id = telehealthAppService.createConsultation(dto);
return R.ok(id);
} catch (Exception e) {
log.error("创建问诊失败", e);
return R.fail("创建问诊失败: " + e.getMessage());
}
}
@Operation(summary = "问诊列表")
@GetMapping("/page")
@PreAuthorize("@ss.hasPermi('outpatient:telehealth:list')")
public R<Page<TelehealthConsultationDto>> page(TelehealthConsultationDto dto) {
try {
Page<TelehealthConsultationDto> result = telehealthAppService.pageConsultation(dto);
return R.ok(result);
} catch (Exception e) {
log.error("查询问诊列表失败", e);
return R.fail("查询问诊列表失败: " + e.getMessage());
}
}
@Operation(summary = "医生回复")
@PostMapping("/reply")
@PreAuthorize("@ss.hasPermi('outpatient:telehealth:edit')")
public R<String> reply(@RequestBody TelehealthConsultationDto dto) {
try {
telehealthAppService.replyConsultation(dto);
return R.ok("回复成功");
} catch (Exception e) {
log.error("医生回复失败", e);
return R.fail("回复失败: " + e.getMessage());
}
}
@Operation(summary = "复诊开方")
@PostMapping("/prescribe")
@PreAuthorize("@ss.hasPermi('outpatient:telehealth:edit')")
public R<String> prescribe(@RequestBody TelehealthConsultationDto dto) {
try {
telehealthAppService.prescribeConsultation(dto);
return R.ok("开方成功");
} catch (Exception e) {
log.error("复诊开方失败", e);
return R.fail("开方失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,46 @@
package com.healthlink.his.web.telehealth.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("telehealth_consultation")
public class TelehealthConsultation extends HisBaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@TableField("patient_id")
private Long patientId;
@TableField("doctor_id")
private Long doctorId;
@TableField("consultation_type")
private String consultationType;
@TableField("status")
private String status;
@TableField("chief_complaint")
private String chiefComplaint;
@TableField("diagnosis")
private String diagnosis;
@TableField("prescription")
private String prescription;
@TableField("consultation_time")
private Date consultationTime;
@TableField("end_time")
private Date endTime;
}

View File

@@ -0,0 +1,43 @@
package com.healthlink.his.web.telehealth.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class TelehealthConsultationDto {
private Long id;
private Long patientId;
private String patientName;
private Long doctorId;
private String doctorName;
private String consultationType;
private String status;
private String chiefComplaint;
private String diagnosis;
private String prescription;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date consultationTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private Integer pageNum = 1;
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,10 @@
package com.healthlink.his.web.telehealth.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.web.telehealth.domain.TelehealthConsultation;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TelehealthConsultationMapper extends BaseMapper<TelehealthConsultation> {
}

View File

@@ -0,0 +1,19 @@
-- V85: 互联网医院 - 在线问诊+复诊开方
CREATE TABLE IF NOT EXISTS telehealth_consultation (
id BIGSERIAL PRIMARY KEY,
patient_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
consultation_type VARCHAR(20) NOT NULL,
status VARCHAR(20) DEFAULT 'PENDING',
chief_complaint TEXT,
diagnosis TEXT,
prescription TEXT,
consultation_time TIMESTAMP,
end_time TIMESTAMP,
tenant_id BIGINT DEFAULT 0,
delete_flag CHAR(1) DEFAULT '0',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
update_time TIMESTAMP,
update_by VARCHAR(64)
);

View File

@@ -0,0 +1,39 @@
-- V86: CDSS规则引擎升级 - 添加优先级/分类字段 + 规则执行历史
ALTER TABLE cdss_rule ADD COLUMN IF NOT EXISTS priority INT NOT NULL DEFAULT 0;
ALTER TABLE cdss_rule ADD COLUMN IF NOT EXISTS category VARCHAR(64);
COMMENT ON COLUMN cdss_rule.priority IS '规则优先级(0普通 1紧急 2最高)';
COMMENT ON COLUMN cdss_rule.category IS '规则分类';
CREATE TABLE cdss_rule_execution (
id BIGSERIAL PRIMARY KEY,
rule_id BIGINT NOT NULL,
rule_code VARCHAR(64) NOT NULL,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
matched BOOLEAN DEFAULT FALSE,
execution_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
execution_result TEXT,
duration_ms INT,
tenant_id BIGINT DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
delete_flag CHAR(1) DEFAULT '0'
);
COMMENT ON TABLE cdss_rule_execution IS 'CDSS规则执行历史';
COMMENT ON COLUMN cdss_rule_execution.id IS '执行记录ID';
COMMENT ON COLUMN cdss_rule_execution.rule_id IS '规则ID';
COMMENT ON COLUMN cdss_rule_execution.rule_code IS '规则编码';
COMMENT ON COLUMN cdss_rule_execution.encounter_id IS '就诊ID';
COMMENT ON COLUMN cdss_rule_execution.patient_id IS '患者ID';
COMMENT ON COLUMN cdss_rule_execution.matched IS '是否命中';
COMMENT ON COLUMN cdss_rule_execution.execution_time IS '执行时间';
COMMENT ON COLUMN cdss_rule_execution.execution_result IS '执行结果';
COMMENT ON COLUMN cdss_rule_execution.duration_ms IS '执行耗时(毫秒)';
CREATE INDEX idx_cdss_exec_rule ON cdss_rule_execution(rule_id);
CREATE INDEX idx_cdss_exec_encounter ON cdss_rule_execution(encounter_id);
CREATE INDEX idx_cdss_exec_patient ON cdss_rule_execution(patient_id);
CREATE INDEX idx_cdss_exec_time ON cdss_rule_execution(execution_time);

View File

@@ -0,0 +1,32 @@
-- V87: AI辅助诊疗 - AI诊断建议表
CREATE TABLE ai_diagnosis_suggestion (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
symptom_text TEXT,
diagnosis_suggestions TEXT,
confidence_score DECIMAL(5,2),
suggestion_source VARCHAR(32),
accepted BOOLEAN DEFAULT FALSE,
tenant_id BIGINT DEFAULT 0,
delete_flag CHAR(1) DEFAULT '0',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
update_time TIMESTAMP,
update_by VARCHAR(64)
);
COMMENT ON TABLE ai_diagnosis_suggestion IS 'AI辅助诊疗建议';
COMMENT ON COLUMN ai_diagnosis_suggestion.id IS '建议ID';
COMMENT ON COLUMN ai_diagnosis_suggestion.encounter_id IS '就诊ID';
COMMENT ON COLUMN ai_diagnosis_suggestion.patient_id IS '患者ID';
COMMENT ON COLUMN ai_diagnosis_suggestion.symptom_text IS '症状描述';
COMMENT ON COLUMN ai_diagnosis_suggestion.diagnosis_suggestions IS '诊断建议';
COMMENT ON COLUMN ai_diagnosis_suggestion.confidence_score IS '置信度(0-100)';
COMMENT ON COLUMN ai_diagnosis_suggestion.suggestion_source IS '建议来源(llm/rule/manual)';
COMMENT ON COLUMN ai_diagnosis_suggestion.accepted IS '是否采纳';
CREATE INDEX idx_ai_diag_encounter ON ai_diagnosis_suggestion(encounter_id);
CREATE INDEX idx_ai_diag_patient ON ai_diagnosis_suggestion(patient_id);
CREATE INDEX idx_ai_diag_source ON ai_diagnosis_suggestion(suggestion_source);

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.aidiagnosis.mapper.AiDiagnosisSuggestionMapper">
<resultMap type="com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion" id="AiDiagnosisSuggestionResult">
<id column="id" property="id"/>
<result column="encounter_id" property="encounterId"/>
<result column="patient_id" property="patientId"/>
<result column="symptom_text" property="symptomText"/>
<result column="diagnosis_suggestions" property="diagnosisSuggestions"/>
<result column="confidence_score" property="confidenceScore"/>
<result column="suggestion_source" property="suggestionSource"/>
<result column="accepted" property="accepted"/>
<result column="tenant_id" property="tenantId"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="delete_flag" property="deleteFlag"/>
</resultMap>
<sql id="Base_Column_List">
id, encounter_id, patient_id, symptom_text, diagnosis_suggestions,
confidence_score, suggestion_source, accepted,
tenant_id, create_by, create_time, update_by, update_time, delete_flag
</sql>
</mapper>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.cdss.mapper.CdssRuleExecutionMapper">
<resultMap type="com.healthlink.his.cdss.domain.CdssRuleExecution" id="CdssRuleExecutionResult">
<id column="id" property="id"/>
<result column="rule_id" property="ruleId"/>
<result column="rule_code" property="ruleCode"/>
<result column="encounter_id" property="encounterId"/>
<result column="patient_id" property="patientId"/>
<result column="matched" property="matched"/>
<result column="execution_time" property="executionTime"/>
<result column="execution_result" property="executionResult"/>
<result column="duration_ms" property="durationMs"/>
<result column="tenant_id" property="tenantId"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="delete_flag" property="deleteFlag"/>
</resultMap>
<sql id="Base_Column_List">
id, rule_id, rule_code, encounter_id, patient_id,
matched, execution_time, execution_result, duration_ms,
tenant_id, create_by, create_time, delete_flag
</sql>
</mapper>

View File

@@ -15,6 +15,8 @@
<result column="department_id" property="departmentId"/>
<result column="status" property="status"/>
<result column="sort_order" property="sortOrder"/>
<result column="priority" property="priority"/>
<result column="category" property="category"/>
<result column="tenant_id" property="tenantId"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
@@ -26,7 +28,8 @@
<sql id="Base_Column_List">
id, rule_code, rule_name, rule_type, severity, trigger_type,
condition_expr, action_expr, description, department_id,
status, sort_order, tenant_id, create_by, create_time,
status, sort_order, priority, category,
tenant_id, create_by, create_time,
update_by, update_time, delete_flag
</sql>

View File

@@ -0,0 +1,28 @@
package com.healthlink.his.common.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum KgEntityType implements HisEnumInterface {
DISEASE(1, "disease", "疾病"),
SYMPTOM(2, "symptom", "症状"),
DRUG(3, "drug", "药物"),
EXAM(4, "exam", "检查");
@EnumValue
private final Integer value;
private final String code;
private final String info;
public static KgEntityType getByCode(String code) {
if (code == null) return null;
for (KgEntityType e : values()) {
if (e.getCode().equals(code)) return e;
}
return null;
}
}

View File

@@ -0,0 +1,32 @@
package com.healthlink.his.common.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum KgRelationType implements HisEnumInterface {
CAUSES(1, "CAUSES", "导致"),
TREATS(2, "TREATS", "治疗"),
CONTRAINDICATES(3, "CONTRAINDICATES", "禁忌"),
INTERACTS_WITH(4, "INTERACTS_WITH", "相互作用"),
REQUIRES_EXAM(5, "REQUIRES_EXAM", "需要检查"),
HAS_SYMPTOM(6, "HAS_SYMPTOM", "具有症状"),
SIDE_EFFECT(7, "SIDE_EFFECT", "副作用"),
ALTERNATIVE(8, "ALTERNATIVE", "替代");
@EnumValue
private final Integer value;
private final String code;
private final String info;
public static KgRelationType getByCode(String code) {
if (code == null) return null;
for (KgRelationType e : values()) {
if (e.getCode().equals(code)) return e;
}
return null;
}
}

View File

@@ -0,0 +1,40 @@
package com.healthlink.his.aidiagnosis.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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("ai_diagnosis_suggestion")
public class AiDiagnosisSuggestion extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private String symptomText;
private String diagnosisSuggestions;
private BigDecimal confidenceScore;
private String suggestionSource;
private Boolean accepted;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.aidiagnosis.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AiDiagnosisSuggestionMapper extends BaseMapper<AiDiagnosisSuggestion> {
}

View File

@@ -0,0 +1,13 @@
package com.healthlink.his.aidiagnosis.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion;
import java.util.List;
public interface IAiDiagnosisService extends IService<AiDiagnosisSuggestion> {
List<AiDiagnosisSuggestion> findByPatientId(Long patientId);
List<AiDiagnosisSuggestion> findByEncounterId(Long encounterId);
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.aidiagnosis.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion;
import com.healthlink.his.aidiagnosis.mapper.AiDiagnosisSuggestionMapper;
import com.healthlink.his.aidiagnosis.service.IAiDiagnosisService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AiDiagnosisServiceImpl extends ServiceImpl<AiDiagnosisSuggestionMapper, AiDiagnosisSuggestion> implements IAiDiagnosisService {
@Override
public List<AiDiagnosisSuggestion> findByPatientId(Long patientId) {
LambdaQueryWrapper<AiDiagnosisSuggestion> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AiDiagnosisSuggestion::getPatientId, patientId)
.orderByDesc(AiDiagnosisSuggestion::getCreateTime);
return baseMapper.selectList(wrapper);
}
@Override
public List<AiDiagnosisSuggestion> findByEncounterId(Long encounterId) {
LambdaQueryWrapper<AiDiagnosisSuggestion> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AiDiagnosisSuggestion::getEncounterId, encounterId)
.orderByDesc(AiDiagnosisSuggestion::getCreateTime);
return baseMapper.selectList(wrapper);
}
}

View File

@@ -42,4 +42,8 @@ public class CdssRule extends HisBaseEntity {
private Integer status;
private Integer sortOrder;
private Integer priority;
private String category;
}

View File

@@ -0,0 +1,43 @@
package com.healthlink.his.cdss.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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("cdss_rule_execution")
public class CdssRuleExecution extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long ruleId;
private String ruleCode;
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private Boolean matched;
private Date executionTime;
private String executionResult;
private Integer durationMs;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.cdss.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CdssRuleExecutionMapper extends BaseMapper<CdssRuleExecution> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.cdss.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
public interface ICdssRuleExecutionService extends IService<CdssRuleExecution> {
}

View File

@@ -4,10 +4,16 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.cdss.domain.CdssRule;
import java.util.List;
import java.util.Map;
public interface ICdssRuleService extends IService<CdssRule> {
List<CdssRule> findActiveRules(String triggerType, Long departmentId);
List<CdssRule> findByCondition(String ruleType, String severity, Integer status);
Map<String, Object> getRuleStats();
List<CdssRule> findByConditionWithFilter(String ruleType, String severity, String keyword,
String category, Integer priority);
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.cdss.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import com.healthlink.his.cdss.mapper.CdssRuleExecutionMapper;
import com.healthlink.his.cdss.service.ICdssRuleExecutionService;
import org.springframework.stereotype.Service;
@Service
public class CdssRuleExecutionServiceImpl extends ServiceImpl<CdssRuleExecutionMapper, CdssRuleExecution> implements ICdssRuleExecutionService {
}

View File

@@ -3,15 +3,25 @@ package com.healthlink.his.cdss.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.cdss.domain.CdssRule;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import com.healthlink.his.cdss.mapper.CdssRuleMapper;
import com.healthlink.his.cdss.service.ICdssRuleExecutionService;
import com.healthlink.his.cdss.service.ICdssRuleService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> implements ICdssRuleService {
private final ICdssRuleExecutionService executionService;
public CdssRuleServiceImpl(ICdssRuleExecutionService executionService) {
this.executionService = executionService;
}
@Override
public List<CdssRule> findActiveRules(String triggerType, Long departmentId) {
LambdaQueryWrapper<CdssRule> wrapper = new LambdaQueryWrapper<>();
@@ -22,12 +32,38 @@ public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> i
if (departmentId != null) {
wrapper.and(w -> w.isNull(CdssRule::getDepartmentId).or().eq(CdssRule::getDepartmentId, departmentId));
}
wrapper.orderByAsc(CdssRule::getSortOrder);
wrapper.orderByDesc(CdssRule::getPriority).orderByAsc(CdssRule::getSortOrder);
return baseMapper.selectList(wrapper);
}
@Override
public List<CdssRule> findByCondition(String ruleType, String severity, Integer status) {
return findByConditionWithFilter(ruleType, severity, null, null, null);
}
@Override
public Map<String, Object> getRuleStats() {
long totalCount = baseMapper.selectCount(null);
long activeCount = baseMapper.selectCount(
new LambdaQueryWrapper<CdssRule>().eq(CdssRule::getStatus, 1));
long totalExecutions = executionService.count();
long matchedExecutions = executionService.count(
new LambdaQueryWrapper<CdssRuleExecution>().eq(CdssRuleExecution::getMatched, true));
Map<String, Object> stats = new HashMap<>();
stats.put("totalRules", totalCount);
stats.put("activeRules", activeCount);
stats.put("inactiveRules", totalCount - activeCount);
stats.put("totalExecutions", totalExecutions);
stats.put("matchedExecutions", matchedExecutions);
stats.put("hitRate", totalExecutions > 0
? Math.round(matchedExecutions * 10000.0 / totalExecutions) / 100.0 : 0.0);
return stats;
}
@Override
public List<CdssRule> findByConditionWithFilter(String ruleType, String severity, String keyword,
String category, Integer priority) {
LambdaQueryWrapper<CdssRule> wrapper = new LambdaQueryWrapper<>();
if (ruleType != null && !ruleType.isEmpty()) {
wrapper.eq(CdssRule::getRuleType, ruleType);
@@ -35,10 +71,17 @@ public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> i
if (severity != null && !severity.isEmpty()) {
wrapper.eq(CdssRule::getSeverity, severity);
}
if (status != null) {
wrapper.eq(CdssRule::getStatus, status);
if (category != null && !category.isEmpty()) {
wrapper.eq(CdssRule::getCategory, category);
}
wrapper.orderByAsc(CdssRule::getSortOrder);
if (priority != null) {
wrapper.eq(CdssRule::getPriority, priority);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(CdssRule::getRuleName, keyword)
.or().like(CdssRule::getRuleCode, keyword));
}
wrapper.orderByDesc(CdssRule::getPriority).orderByAsc(CdssRule::getSortOrder);
return baseMapper.selectList(wrapper);
}
}

View File

@@ -0,0 +1,22 @@
package com.healthlink.his.clinical.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("kg_clinical_pathway")
public class KgClinicalPathway extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@TableField("pathway_code") private String pathwayCode;
@TableField("pathway_name") private String pathwayName;
@TableField("disease_code") private String diseaseCode;
@TableField("disease_name") private String diseaseName;
@TableField("department") private String department;
@TableField("standard_days") private Integer standardDays;
@TableField("description") private String description;
@TableField("status") private String status;
}

View File

@@ -0,0 +1,24 @@
package com.healthlink.his.clinical.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("kg_entity_relation")
public class KgEntityRelation extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@TableField("source_type") private String sourceType;
@TableField("source_id") private String sourceId;
@TableField("target_type") private String targetType;
@TableField("target_id") private String targetId;
@TableField("relation_type") private String relationType;
@TableField("relation_strength") private BigDecimal relationStrength;
@TableField("description") private String description;
@TableField("evidence_source") private String evidenceSource;
}

View File

@@ -0,0 +1,20 @@
package com.healthlink.his.clinical.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("kg_pathway_step")
public class KgPathwayStep extends HisBaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@TableField("pathway_id") private Long pathwayId;
@TableField("step_order") private Integer stepOrder;
@TableField("day_number") private Integer dayNumber;
@TableField("step_type") private String stepType;
@TableField("step_content") private String stepContent;
@TableField("required") private String required;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.clinical.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.clinical.domain.KgClinicalPathway;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgClinicalPathwayMapper extends BaseMapper<KgClinicalPathway> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.clinical.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.clinical.domain.KgEntityRelation;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgEntityRelationMapper extends BaseMapper<KgEntityRelation> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.clinical.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.clinical.domain.KgPathwayStep;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgPathwayStepMapper extends BaseMapper<KgPathwayStep> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.clinical.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.clinical.domain.KgClinicalPathway;
public interface IKgClinicalPathwayService extends IService<KgClinicalPathway> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.clinical.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.clinical.domain.KgEntityRelation;
public interface IKgEntityRelationService extends IService<KgEntityRelation> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.clinical.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.clinical.domain.KgPathwayStep;
public interface IKgPathwayStepService extends IService<KgPathwayStep> {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.clinical.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.clinical.domain.KgClinicalPathway;
import com.healthlink.his.clinical.mapper.KgClinicalPathwayMapper;
import com.healthlink.his.clinical.service.IKgClinicalPathwayService;
import org.springframework.stereotype.Service;
@Service
public class KgClinicalPathwayServiceImpl extends ServiceImpl<KgClinicalPathwayMapper, KgClinicalPathway> implements IKgClinicalPathwayService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.clinical.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.clinical.domain.KgEntityRelation;
import com.healthlink.his.clinical.mapper.KgEntityRelationMapper;
import com.healthlink.his.clinical.service.IKgEntityRelationService;
import org.springframework.stereotype.Service;
@Service
public class KgEntityRelationServiceImpl extends ServiceImpl<KgEntityRelationMapper, KgEntityRelation> implements IKgEntityRelationService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.clinical.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.clinical.domain.KgPathwayStep;
import com.healthlink.his.clinical.mapper.KgPathwayStepMapper;
import com.healthlink.his.clinical.service.IKgPathwayStepService;
import org.springframework.stereotype.Service;
@Service
public class KgPathwayStepServiceImpl extends ServiceImpl<KgPathwayStepMapper, KgPathwayStep> implements IKgPathwayStepService {
}

View File

@@ -0,0 +1,32 @@
package com.healthlink.his.knowledgegraph.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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_disease")
public class KgDisease extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String diseaseCode;
private String diseaseName;
private String category;
private String department;
private String severityLevel;
}

View File

@@ -0,0 +1,34 @@
package com.healthlink.his.knowledgegraph.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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_drug")
public class KgDrug extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String drugCode;
private String drugName;
private String genericName;
private String category;
private String dosageForm;
private String contraindications;
}

View File

@@ -0,0 +1,32 @@
package com.healthlink.his.knowledgegraph.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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_examination")
public class KgExamination extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String examCode;
private String examName;
private String examType;
private String department;
private String referenceRange;
}

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.knowledgegraph.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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kg_symptom")
public class KgSymptom extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String symptomCode;
private String symptomName;
private String bodyPart;
private String symptomType;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgDisease;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgDiseaseMapper extends BaseMapper<KgDisease> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgDrug;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgDrugMapper extends BaseMapper<KgDrug> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgExamination;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgExaminationMapper extends BaseMapper<KgExamination> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.knowledgegraph.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KgSymptomMapper extends BaseMapper<KgSymptom> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgDisease;
public interface IKgDiseaseService extends IService<KgDisease> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgDrug;
public interface IKgDrugService extends IService<KgDrug> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgExamination;
public interface IKgExaminationService extends IService<KgExamination> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.knowledgegraph.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
public interface IKgSymptomService extends IService<KgSymptom> {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgDisease;
import com.healthlink.his.knowledgegraph.mapper.KgDiseaseMapper;
import com.healthlink.his.knowledgegraph.service.IKgDiseaseService;
import org.springframework.stereotype.Service;
@Service
public class KgDiseaseServiceImpl extends ServiceImpl<KgDiseaseMapper, KgDisease> implements IKgDiseaseService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgDrug;
import com.healthlink.his.knowledgegraph.mapper.KgDrugMapper;
import com.healthlink.his.knowledgegraph.service.IKgDrugService;
import org.springframework.stereotype.Service;
@Service
public class KgDrugServiceImpl extends ServiceImpl<KgDrugMapper, KgDrug> implements IKgDrugService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgExamination;
import com.healthlink.his.knowledgegraph.mapper.KgExaminationMapper;
import com.healthlink.his.knowledgegraph.service.IKgExaminationService;
import org.springframework.stereotype.Service;
@Service
public class KgExaminationServiceImpl extends ServiceImpl<KgExaminationMapper, KgExamination> implements IKgExaminationService {
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.knowledgegraph.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.knowledgegraph.domain.KgSymptom;
import com.healthlink.his.knowledgegraph.mapper.KgSymptomMapper;
import com.healthlink.his.knowledgegraph.service.IKgSymptomService;
import org.springframework.stereotype.Service;
@Service
public class KgSymptomServiceImpl extends ServiceImpl<KgSymptomMapper, KgSymptom> implements IKgSymptomService {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.clinical.mapper.KgClinicalPathwayMapper">
</mapper>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.clinical.mapper.KgEntityRelationMapper">
</mapper>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.clinical.mapper.KgPathwayStepMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgDiseaseMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgDrugMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgExaminationMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.knowledgegraph.mapper.KgSymptomMapper">
</mapper>

View File

@@ -0,0 +1,30 @@
import request from '@/utils/request'
export function aiDiagnosisSuggest(data) {
return request({
url: '/ai-diagnosis/suggest',
method: 'post',
params: data
})
}
export function getAiDiagnosisHistory(patientId) {
return request({
url: '/ai-diagnosis/history/' + patientId,
method: 'get'
})
}
export function getAiDiagnosisHistoryByEncounter(encounterId) {
return request({
url: '/ai-diagnosis/history/encounter/' + encounterId,
method: 'get'
})
}
export function acceptAiDiagnosis(id) {
return request({
url: '/ai-diagnosis/accept/' + id,
method: 'post'
})
}

View File

@@ -8,6 +8,29 @@ export function getCdssRuleList(query) {
})
}
export function getCdssRuleListEnhanced(query) {
return request({
url: '/infection/cdss/rules/enhanced',
method: 'get',
params: query
})
}
export function getCdssRuleStats() {
return request({
url: '/infection/cdss/rules/stats',
method: 'get'
})
}
export function getCdssRuleHistory(query) {
return request({
url: '/infection/cdss/rules/history',
method: 'get',
params: query
})
}
export function addCdssRule(data) {
return request({
url: '/infection/cdss/rules',

View File

@@ -0,0 +1,32 @@
import request from '@/utils/request'
export function collectClinicalData(data) {
return request({
url: '/data/collect/clinical',
method: 'post',
params: data
})
}
export function collectOperationalData(data) {
return request({
url: '/data/collect/operational',
method: 'post',
params: data
})
}
export function getRealtimeData() {
return request({
url: '/data/dashboard/realtime',
method: 'get'
})
}
export function getHistoricalData(params) {
return request({
url: '/data/dashboard/historical',
method: 'get',
params: params
})
}

View File

@@ -0,0 +1,105 @@
import request from '@/utils/request'
export function addDisease(data) {
return request({ url: '/knowledgegraph/disease', method: 'post', data })
}
export function getDiseasePage(params) {
return request({ url: '/knowledgegraph/disease/page', method: 'get', params })
}
export function getDiseaseById(id) {
return request({ url: '/knowledgegraph/disease/' + id, method: 'get' })
}
export function updateDisease(data) {
return request({ url: '/knowledgegraph/disease', method: 'put', data })
}
export function deleteDisease(id) {
return request({ url: '/knowledgegraph/disease/' + id, method: 'delete' })
}
export function addSymptom(data) {
return request({ url: '/knowledgegraph/symptom', method: 'post', data })
}
export function getSymptomPage(params) {
return request({ url: '/knowledgegraph/symptom/page', method: 'get', params })
}
export function getSymptomById(id) {
return request({ url: '/knowledgegraph/symptom/' + id, method: 'get' })
}
export function updateSymptom(data) {
return request({ url: '/knowledgegraph/symptom', method: 'put', data })
}
export function deleteSymptom(id) {
return request({ url: '/knowledgegraph/symptom/' + id, method: 'delete' })
}
export function addDrug(data) {
return request({ url: '/knowledgegraph/drug', method: 'post', data })
}
export function getDrugPage(params) {
return request({ url: '/knowledgegraph/drug/page', method: 'get', params })
}
export function getDrugById(id) {
return request({ url: '/knowledgegraph/drug/' + id, method: 'get' })
}
export function updateDrug(data) {
return request({ url: '/knowledgegraph/drug', method: 'put', data })
}
export function deleteDrug(id) {
return request({ url: '/knowledgegraph/drug/' + id, method: 'delete' })
}
export function addExamination(data) {
return request({ url: '/knowledgegraph/examination', method: 'post', data })
}
export function getExaminationPage(params) {
return request({ url: '/knowledgegraph/examination/page', method: 'get', params })
}
export function getExaminationById(id) {
return request({ url: '/knowledgegraph/examination/' + id, method: 'get' })
}
export function updateExamination(data) {
return request({ url: '/knowledgegraph/examination', method: 'put', data })
}
export function deleteExamination(id) {
return request({ url: '/knowledgegraph/examination/' + id, method: 'delete' })
}
export function createRelation(data) {
return request({ url: '/knowledgegraph/relation', method: 'post', data })
}
export function getRelationPage(params) {
return request({ url: '/knowledgegraph/relation/page', method: 'get', params })
}
export function getRelationGraph(entityType, entityId) {
return request({ url: `/knowledgegraph/relation/graph/${entityType}/${entityId}`, method: 'get' })
}
export function createPathway(data) {
return request({ url: '/knowledgegraph/pathway', method: 'post', data })
}
export function getPathwayPage(params) {
return request({ url: '/knowledgegraph/pathway/page', method: 'get', params })
}
export function getPathwaySteps(id) {
return request({ url: `/knowledgegraph/pathway/${id}/steps`, method: 'get' })
}

View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function generateBiReport(data) {
return request({
url: '/report/bi/generate',
method: 'post',
params: { type: data.type },
data: data.filters || {}
})
}
export function getBiDashboard() {
return request({
url: '/report/bi/dashboard',
method: 'get'
})
}

View File

@@ -0,0 +1,33 @@
import request from '@/utils/request'
export function createConsultation(data) {
return request({
url: '/telehealth/consultation/create',
method: 'post',
data: data
})
}
export function pageConsultation(query) {
return request({
url: '/telehealth/consultation/page',
method: 'get',
params: query
})
}
export function replyConsultation(data) {
return request({
url: '/telehealth/consultation/reply',
method: 'post',
data: data
})
}
export function prescribeConsultation(data) {
return request({
url: '/telehealth/consultation/prescribe',
method: 'post',
data: data
})
}

Some files were not shown because too many files have changed in this diff Show More