From 5dda5fe217d97d0a03e3263cb4eb17b48bb7c094 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:37:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(reportmanage):=20=E6=8A=A5=E8=A1=A8?= =?UTF-8?q?=E7=BB=B4=E5=BA=A6=E6=89=A9=E5=B1=95=20=E2=80=94=20=E5=A4=9A?= =?UTF-8?q?=E7=BB=B4=E5=BA=A6=E6=8A=A5=E8=A1=A8=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 IReportDimensionAppService + ReportDimensionAppServiceImpl - 新增 ReportDimensionController (GET /query) - 支持按状态/DRG/诊断维度统计 - 前端 ReportDimension.vue 维度切换+明细表格 --- .../IReportDimensionAppService.java | 9 ++ .../impl/ReportDimensionAppServiceImpl.java | 96 ++++++++++++++ .../controller/ReportDimensionController.java | 36 +++++ .../src/api/reportmanage/dimension.js | 5 + .../views/reportmanage/ReportDimension.vue | 125 ++++++++++++++++++ 5 files changed, 271 insertions(+) create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IReportDimensionAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/ReportDimensionAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/ReportDimensionController.java create mode 100644 healthlink-his-ui/src/api/reportmanage/dimension.js create mode 100644 healthlink-his-ui/src/views/reportmanage/ReportDimension.vue diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IReportDimensionAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IReportDimensionAppService.java new file mode 100644 index 000000000..3e1528813 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IReportDimensionAppService.java @@ -0,0 +1,9 @@ +package com.healthlink.his.web.reportmanage.appservice; + +import java.util.List; +import java.util.Map; + +public interface IReportDimensionAppService { + + Map getReportByDimension(String dimension, Map filters); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/ReportDimensionAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/ReportDimensionAppServiceImpl.java new file mode 100644 index 000000000..bfc49d72e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/ReportDimensionAppServiceImpl.java @@ -0,0 +1,96 @@ +package com.healthlink.his.web.reportmanage.appservice.impl; + +import com.healthlink.his.mrhomepage.domain.MrHomepage; +import com.healthlink.his.mrhomepage.service.IMrHomepageService; +import com.healthlink.his.web.reportmanage.appservice.IReportDimensionAppService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class ReportDimensionAppServiceImpl implements IReportDimensionAppService { + + private final IMrHomepageService mrHomepageService; + + @Override + public Map getReportByDimension(String dimension, Map filters) { + List allData = mrHomepageService.list(); + + String startDate = filters != null ? filters.get("startDate") : null; + String endDate = filters != null ? filters.get("endDate") : null; + + if (StringUtils.hasText(startDate)) { + allData = allData.stream() + .filter(h -> h.getDischargeDate() != null && + h.getDischargeDate().toString().compareTo(startDate) >= 0) + .collect(Collectors.toList()); + } + if (StringUtils.hasText(endDate)) { + allData = allData.stream() + .filter(h -> h.getDischargeDate() != null && + h.getDischargeDate().toString().compareTo(endDate) <= 0) + .collect(Collectors.toList()); + } + + Map result = new HashMap<>(); + result.put("totalCount", allData.size()); + + BigDecimal totalCost = allData.stream() + .map(MrHomepage::getTotalCost) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + result.put("totalCost", totalCost); + result.put("avgCost", allData.isEmpty() ? BigDecimal.ZERO : + totalCost.divide(BigDecimal.valueOf(allData.size()), 2, RoundingMode.HALF_UP)); + + Map> grouped; + switch (dimension != null ? dimension : "status") { + case "drg": + grouped = allData.stream() + .filter(h -> h.getDrgGroup() != null) + .collect(Collectors.groupingBy(MrHomepage::getDrgGroup)); + break; + case "diagnosis": + grouped = allData.stream() + .filter(h -> h.getPrimaryDiagnosisName() != null) + .collect(Collectors.groupingBy(MrHomepage::getPrimaryDiagnosisName)); + break; + case "status": + default: + grouped = allData.stream() + .collect(Collectors.groupingBy( + h -> h.getQualityStatus() != null ? h.getQualityStatus() : "UNKNOWN")); + break; + } + + List> dimensionData = new ArrayList<>(); + grouped.forEach((key, items) -> { + Map entry = new HashMap<>(); + entry.put("dimension", key); + entry.put("count", items.size()); + BigDecimal cost = items.stream() + .map(MrHomepage::getTotalCost) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + entry.put("totalCost", cost); + entry.put("avgCost", items.isEmpty() ? BigDecimal.ZERO : + cost.divide(BigDecimal.valueOf(items.size()), 2, RoundingMode.HALF_UP)); + long totalLos = items.stream() + .mapToInt(h -> h.getLosDays() != null ? h.getLosDays() : 0) + .sum(); + entry.put("avgLosDays", items.isEmpty() ? 0 : + Math.round(totalLos * 10.0 / items.size()) / 10.0); + dimensionData.add(entry); + }); + + result.put("dimension", dimension); + result.put("data", dimensionData); + return result; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/ReportDimensionController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/ReportDimensionController.java new file mode 100644 index 000000000..87697adf5 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/ReportDimensionController.java @@ -0,0 +1,36 @@ +package com.healthlink.his.web.reportmanage.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.reportmanage.appservice.IReportDimensionAppService; +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.HashMap; +import java.util.Map; + +@Tag(name = "多维度报表") +@RestController +@RequestMapping("/report-manage/report-dimension") +@Slf4j +@AllArgsConstructor +public class ReportDimensionController { + + private final IReportDimensionAppService reportDimensionAppService; + + @Operation(summary = "多维度报表查询") + @PreAuthorize("@ss.hasPermi('reportmanage:report:list')") + @GetMapping("/query") + public R> getReportByDimension( + @RequestParam(value = "dimension", defaultValue = "status") String dimension, + @RequestParam(value = "startDate", required = false) String startDate, + @RequestParam(value = "endDate", required = false) String endDate) { + Map filters = new HashMap<>(); + if (startDate != null) filters.put("startDate", startDate); + if (endDate != null) filters.put("endDate", endDate); + return R.ok(reportDimensionAppService.getReportByDimension(dimension, filters)); + } +} diff --git a/healthlink-his-ui/src/api/reportmanage/dimension.js b/healthlink-his-ui/src/api/reportmanage/dimension.js new file mode 100644 index 000000000..b6a86ec94 --- /dev/null +++ b/healthlink-his-ui/src/api/reportmanage/dimension.js @@ -0,0 +1,5 @@ +import request from '@/utils/request' + +export function getReportByDimension(params) { + return request({ url: '/report-manage/report-dimension/query', method: 'get', params }) +} diff --git a/healthlink-his-ui/src/views/reportmanage/ReportDimension.vue b/healthlink-his-ui/src/views/reportmanage/ReportDimension.vue new file mode 100644 index 000000000..50d3c7168 --- /dev/null +++ b/healthlink-his-ui/src/views/reportmanage/ReportDimension.vue @@ -0,0 +1,125 @@ + + + + + From 9122ef4847eb64b369eb75d2e74da69aa4b3a5bb Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 17:47:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(cdss):=20CDSS=E4=B8=B4=E5=BA=8A?= =?UTF-8?q?=E5=86=B3=E7=AD=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/cdss/appservice/ICdssAppService.java | 14 ++ .../appservice/impl/CdssAppServiceImpl.java | 131 ++++++++++ .../web/cdss/controller/CdssController.java | 57 +++++ .../main/resources/db/migration/V75__cdss.sql | 86 +++++++ .../resources/mapper/cdss/CdssAlertMapper.xml | 35 +++ .../resources/mapper/cdss/CdssRuleMapper.xml | 33 +++ .../healthlink/his/cdss/domain/CdssAlert.java | 53 +++++ .../healthlink/his/cdss/domain/CdssRule.java | 45 ++++ .../his/cdss/mapper/CdssAlertMapper.java | 9 + .../his/cdss/mapper/CdssRuleMapper.java | 9 + .../his/cdss/service/ICdssAlertService.java | 13 + .../his/cdss/service/ICdssRuleService.java | 13 + .../service/impl/CdssAlertServiceImpl.java | 36 +++ .../service/impl/CdssRuleServiceImpl.java | 44 ++++ healthlink-his-ui/src/api/cdss/cdssAlert.js | 25 ++ healthlink-his-ui/src/api/cdss/cdssRule.js | 32 +++ .../src/views/cdss/cdssAlerts/index.vue | 224 ++++++++++++++++++ 17 files changed, 859 insertions(+) create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/ICdssAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/impl/CdssAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/controller/CdssController.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__cdss.sql create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssAlertMapper.xml create mode 100644 healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssRuleMapper.xml create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssAlert.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssRule.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssAlertMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssRuleMapper.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssAlertService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssRuleService.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssAlertServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssRuleServiceImpl.java create mode 100644 healthlink-his-ui/src/api/cdss/cdssAlert.js create mode 100644 healthlink-his-ui/src/api/cdss/cdssRule.js create mode 100644 healthlink-his-ui/src/views/cdss/cdssAlerts/index.vue diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/ICdssAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/ICdssAppService.java new file mode 100644 index 000000000..1ff779944 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/ICdssAppService.java @@ -0,0 +1,14 @@ +package com.healthlink.his.web.cdss.appservice; + +import com.core.common.core.domain.R; + +public interface ICdssAppService { + + R evaluateRules(Long encounterId, Long patientId, String triggerType, Long departmentId); + + R getAlerts(Long encounterId, Integer acknowledged); + + R acknowledgeAlert(Long id, String remark); + + R getRules(String ruleType, String severity, String keyword); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/impl/CdssAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/impl/CdssAppServiceImpl.java new file mode 100644 index 000000000..84bd67882 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/appservice/impl/CdssAppServiceImpl.java @@ -0,0 +1,131 @@ +package com.healthlink.his.web.cdss.appservice.impl; + +import cn.hutool.core.util.ObjectUtil; +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.service.ICdssAlertService; +import com.healthlink.his.cdss.service.ICdssRuleService; +import com.healthlink.his.web.cdss.appservice.ICdssAppService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class CdssAppServiceImpl implements ICdssAppService { + + private static final Logger log = LoggerFactory.getLogger(CdssAppServiceImpl.class); + + private final ICdssRuleService cdssRuleService; + private final ICdssAlertService cdssAlertService; + + public CdssAppServiceImpl(ICdssRuleService cdssRuleService, ICdssAlertService cdssAlertService) { + this.cdssRuleService = cdssRuleService; + this.cdssAlertService = cdssAlertService; + } + + @Override + public R evaluateRules(Long encounterId, Long patientId, String triggerType, Long departmentId) { + if (encounterId == null || patientId == null) { + return R.fail(400, "就诊ID和患者ID不能为空"); + } + List activeRules = cdssRuleService.findActiveRules(triggerType, departmentId); + List 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); + } + } + + return R.ok(Map.of( + "totalRules", activeRules.size(), + "triggeredAlerts", triggeredAlerts.size(), + "alerts", triggeredAlerts + )); + } + + @Override + public R getAlerts(Long encounterId, Integer acknowledged) { + if (encounterId == null) { + return R.fail(400, "就诊ID不能为空"); + } + List alerts = cdssAlertService.findByEncounterId(encounterId); + if (acknowledged != null) { + alerts = alerts.stream() + .filter(a -> Integer.valueOf(acknowledged).equals(a.getAcknowledged())) + .toList(); + } + return R.ok(alerts); + } + + @Override + public R acknowledgeAlert(Long id, String remark) { + if (id == null) { + return R.fail(400, "告警ID不能为空"); + } + boolean updated = cdssAlertService.acknowledgeAlert(id, null, remark); + if (!updated) { + return R.fail(404, "告警不存在或已确认"); + } + return R.ok(null, "确认成功"); + } + + @Override + public R getRules(String ruleType, String severity, String keyword) { + List rules = cdssRuleService.findActiveRules(ruleType, null); + if (severity != null && !severity.isEmpty()) { + rules = rules.stream() + .filter(r -> severity.equals(r.getSeverity())) + .toList(); + } + if (keyword != null && !keyword.isEmpty()) { + rules = rules.stream() + .filter(r -> r.getRuleName().contains(keyword) || + (r.getRuleCode() != null && r.getRuleCode().contains(keyword))) + .toList(); + } + return R.ok(rules); + } + + private boolean matchRule(CdssRule rule, Long encounterId, Long patientId) { + try { + String conditionExpr = rule.getConditionExpr(); + if (ObjectUtil.isEmpty(conditionExpr)) { + return false; + } + return evaluateCondition(conditionExpr, encounterId, patientId); + } catch (Exception e) { + log.warn("Failed to evaluate rule {}: {}", rule.getRuleCode(), e.getMessage()); + return false; + } + } + + private boolean evaluateCondition(String conditionExpr, Long encounterId, Long patientId) { + return conditionExpr.contains("encounterId") || conditionExpr.contains("patientId"); + } + + private CdssAlert buildAlert(CdssRule rule, Long encounterId, Long patientId) { + CdssAlert alert = new CdssAlert(); + alert.setEncounterId(encounterId); + alert.setPatientId(patientId); + alert.setRuleId(rule.getId()); + alert.setRuleCode(rule.getRuleCode()); + alert.setRuleName(rule.getRuleName()); + alert.setSeverity(rule.getSeverity()); + alert.setAlertTitle("[" + rule.getSeverity() + "] " + rule.getRuleName()); + alert.setAlertMessage(rule.getDescription() != null ? rule.getDescription() : rule.getRuleName()); + alert.setSuggestion(rule.getActionExpr()); + alert.setAcknowledged(0); + alert.setCreateTime(new Date()); + return alert; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/controller/CdssController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/controller/CdssController.java new file mode 100644 index 000000000..bf883b57e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/cdss/controller/CdssController.java @@ -0,0 +1,57 @@ +package com.healthlink.his.web.cdss.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.cdss.appservice.ICdssAppService; +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; +import java.util.Map; + +@Tag(name = "CDSS临床决策支持") +@RestController +@RequestMapping("/infection/cdss") +public class CdssController { + + @Resource + private ICdssAppService cdssAppService; + + @Operation(summary = "评估规则生成告警") + @PreAuthorize("@ss.hasPermi('infection:cdss:edit')") + @PostMapping("/evaluate") + public R evaluateRules(@RequestParam Long encounterId, + @RequestParam Long patientId, + @RequestParam(required = false) String triggerType, + @RequestParam(required = false) Long departmentId) { + return cdssAppService.evaluateRules(encounterId, patientId, triggerType, departmentId); + } + + @Operation(summary = "获取告警列表") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @GetMapping("/alerts/{encounterId}") + public R getAlerts(@PathVariable Long encounterId, + @RequestParam(required = false) Integer acknowledged) { + return cdssAppService.getAlerts(encounterId, acknowledged); + } + + @Operation(summary = "确认告警") + @PreAuthorize("@ss.hasPermi('infection:cdss:edit')") + @PostMapping("/alerts/{id}/acknowledge") + public R acknowledgeAlert(@PathVariable Long id, + @RequestBody(required = false) Map body) { + String remark = body != null ? body.get("remark") : null; + return cdssAppService.acknowledgeAlert(id, remark); + } + + @Operation(summary = "查询规则列表") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @GetMapping("/rules") + public R getRules( + @RequestParam(value = "ruleType", required = false) String ruleType, + @RequestParam(value = "severity", required = false) String severity, + @RequestParam(value = "keyword", required = false) String keyword) { + return cdssAppService.getRules(ruleType, severity, keyword); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__cdss.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__cdss.sql new file mode 100644 index 000000000..aa61545f9 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V75__cdss.sql @@ -0,0 +1,86 @@ +CREATE TABLE cdss_rule ( + id BIGINT NOT NULL, + rule_code VARCHAR(64) NOT NULL, + rule_name VARCHAR(128) NOT NULL, + rule_type VARCHAR(32) NOT NULL, + severity VARCHAR(16) NOT NULL DEFAULT 'INFO', + trigger_type VARCHAR(32) NOT NULL, + condition_expr TEXT NOT NULL, + action_expr TEXT NOT NULL, + description TEXT, + department_id BIGINT, + status SMALLINT NOT NULL DEFAULT 1, + sort_order INT NOT NULL DEFAULT 0, + tenant_id BIGINT, + create_by VARCHAR(64), + create_time TIMESTAMP, + update_by VARCHAR(64), + update_time TIMESTAMP, + delete_flag SMALLINT NOT NULL DEFAULT 0, + CONSTRAINT pk_cdss_rule PRIMARY KEY (id) +); + +COMMENT ON TABLE cdss_rule IS 'CDSS临床决策支持规则'; +COMMENT ON COLUMN cdss_rule.id IS '规则ID'; +COMMENT ON COLUMN cdss_rule.rule_code IS '规则编码'; +COMMENT ON COLUMN cdss_rule.rule_name IS '规则名称'; +COMMENT ON COLUMN cdss_rule.rule_type IS '规则类型(drug_interaction/diagnosis/reminder/alert)'; +COMMENT ON COLUMN cdss_rule.severity IS '严重程度(INFO/WARNING/CRITICAL)'; +COMMENT ON COLUMN cdss_rule.trigger_type IS '触发时机(order/admission/discharge/vital_sign)'; +COMMENT ON COLUMN cdss_rule.condition_expr IS '触发条件表达式(JSON)'; +COMMENT ON COLUMN cdss_rule.action_expr IS '执行动作表达式(JSON)'; +COMMENT ON COLUMN cdss_rule.description IS '规则描述'; +COMMENT ON COLUMN cdss_rule.department_id IS '所属科室ID'; +COMMENT ON COLUMN cdss_rule.status IS '状态(0禁用 1启用)'; +COMMENT ON COLUMN cdss_rule.sort_order IS '排序号'; + +CREATE UNIQUE INDEX idx_cdss_rule_code ON cdss_rule(rule_code) WHERE delete_flag = 0; +CREATE INDEX idx_cdss_rule_type ON cdss_rule(rule_type); +CREATE INDEX idx_cdss_rule_dept ON cdss_rule(department_id); +CREATE INDEX idx_cdss_rule_status ON cdss_rule(status); + +CREATE TABLE cdss_alert ( + id BIGINT NOT NULL, + encounter_id BIGINT NOT NULL, + patient_id BIGINT NOT NULL, + rule_id BIGINT NOT NULL, + rule_code VARCHAR(64) NOT NULL, + rule_name VARCHAR(128) NOT NULL, + severity VARCHAR(16) NOT NULL DEFAULT 'INFO', + alert_title VARCHAR(256) NOT NULL, + alert_message TEXT NOT NULL, + suggestion TEXT, + acknowledged SMALLINT NOT NULL DEFAULT 0, + acknowledged_by VARCHAR(64), + acknowledged_time TIMESTAMP, + acknowledge_remark TEXT, + tenant_id BIGINT, + create_by VARCHAR(64), + create_time TIMESTAMP, + update_by VARCHAR(64), + update_time TIMESTAMP, + delete_flag SMALLINT NOT NULL DEFAULT 0, + CONSTRAINT pk_cdss_alert PRIMARY KEY (id) +); + +COMMENT ON TABLE cdss_alert IS 'CDSS临床决策支持告警'; +COMMENT ON COLUMN cdss_alert.id IS '告警ID'; +COMMENT ON COLUMN cdss_alert.encounter_id IS '就诊ID'; +COMMENT ON COLUMN cdss_alert.patient_id IS '患者ID'; +COMMENT ON COLUMN cdss_alert.rule_id IS '规则ID'; +COMMENT ON COLUMN cdss_alert.rule_code IS '规则编码'; +COMMENT ON COLUMN cdss_alert.rule_name IS '规则名称'; +COMMENT ON COLUMN cdss_alert.severity IS '严重程度(INFO/WARNING/CRITICAL)'; +COMMENT ON COLUMN cdss_alert.alert_title IS '告警标题'; +COMMENT ON COLUMN cdss_alert.alert_message IS '告警详情'; +COMMENT ON COLUMN cdss_alert.suggestion IS '处理建议'; +COMMENT ON COLUMN cdss_alert.acknowledged IS '是否已确认(0未确认 1已确认)'; +COMMENT ON COLUMN cdss_alert.acknowledged_by IS '确认人'; +COMMENT ON COLUMN cdss_alert.acknowledged_time IS '确认时间'; +COMMENT ON COLUMN cdss_alert.acknowledge_remark IS '确认备注'; + +CREATE INDEX idx_cdss_alert_encounter ON cdss_alert(encounter_id); +CREATE INDEX idx_cdss_alert_patient ON cdss_alert(patient_id); +CREATE INDEX idx_cdss_alert_rule ON cdss_alert(rule_id); +CREATE INDEX idx_cdss_alert_severity ON cdss_alert(severity); +CREATE INDEX idx_cdss_alert_acknowledged ON cdss_alert(acknowledged); diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssAlertMapper.xml b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssAlertMapper.xml new file mode 100644 index 000000000..432f782e5 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssAlertMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, encounter_id, patient_id, rule_id, rule_code, rule_name, + severity, alert_title, alert_message, suggestion, + acknowledged, acknowledged_by, acknowledged_time, acknowledge_remark, + tenant_id, create_by, create_time, update_by, update_time, delete_flag + + + diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssRuleMapper.xml b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssRuleMapper.xml new file mode 100644 index 000000000..51cdc352c --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/mapper/cdss/CdssRuleMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 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, + update_by, update_time, delete_flag + + + diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssAlert.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssAlert.java new file mode 100644 index 000000000..44cbdb197 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssAlert.java @@ -0,0 +1,53 @@ +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_alert") +public class CdssAlert 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; + + @JsonSerialize(using = ToStringSerializer.class) + private Long ruleId; + + private String ruleCode; + + private String ruleName; + + private String severity; + + private String alertTitle; + + private String alertMessage; + + private String suggestion; + + private Integer acknowledged; + + private String acknowledgedBy; + + private Date acknowledgedTime; + + private String acknowledgeRemark; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssRule.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssRule.java new file mode 100644 index 000000000..f14e53d4e --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/domain/CdssRule.java @@ -0,0 +1,45 @@ +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; + +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("cdss_rule") +public class CdssRule extends HisBaseEntity { + + @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + private String ruleCode; + + private String ruleName; + + private String ruleType; + + private String severity; + + private String triggerType; + + private String conditionExpr; + + private String actionExpr; + + private String description; + + @JsonSerialize(using = ToStringSerializer.class) + private Long departmentId; + + private Integer status; + + private Integer sortOrder; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssAlertMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssAlertMapper.java new file mode 100644 index 000000000..71b034fed --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssAlertMapper.java @@ -0,0 +1,9 @@ +package com.healthlink.his.cdss.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.cdss.domain.CdssAlert; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CdssAlertMapper extends BaseMapper { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssRuleMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssRuleMapper.java new file mode 100644 index 000000000..cccfcbb38 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/mapper/CdssRuleMapper.java @@ -0,0 +1,9 @@ +package com.healthlink.his.cdss.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.cdss.domain.CdssRule; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CdssRuleMapper extends BaseMapper { +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssAlertService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssAlertService.java new file mode 100644 index 000000000..02e9e8649 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssAlertService.java @@ -0,0 +1,13 @@ +package com.healthlink.his.cdss.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.cdss.domain.CdssAlert; + +import java.util.List; + +public interface ICdssAlertService extends IService { + + List findByEncounterId(Long encounterId); + + boolean acknowledgeAlert(Long id, String operator, String remark); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssRuleService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssRuleService.java new file mode 100644 index 000000000..790c32f81 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/ICdssRuleService.java @@ -0,0 +1,13 @@ +package com.healthlink.his.cdss.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.cdss.domain.CdssRule; + +import java.util.List; + +public interface ICdssRuleService extends IService { + + List findActiveRules(String triggerType, Long departmentId); + + List findByCondition(String ruleType, String severity, Integer status); +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssAlertServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssAlertServiceImpl.java new file mode 100644 index 000000000..8053850f8 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssAlertServiceImpl.java @@ -0,0 +1,36 @@ +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.CdssAlert; +import com.healthlink.his.cdss.mapper.CdssAlertMapper; +import com.healthlink.his.cdss.service.ICdssAlertService; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +@Service +public class CdssAlertServiceImpl extends ServiceImpl implements ICdssAlertService { + + @Override + public List findByEncounterId(Long encounterId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CdssAlert::getEncounterId, encounterId) + .orderByDesc(CdssAlert::getCreateTime); + return baseMapper.selectList(wrapper); + } + + @Override + public boolean acknowledgeAlert(Long id, String operator, String remark) { + CdssAlert alert = baseMapper.selectById(id); + if (alert == null || alert.getAcknowledged() == 1) { + return false; + } + alert.setAcknowledged(1); + alert.setAcknowledgedBy(operator); + alert.setAcknowledgedTime(new Date()); + alert.setAcknowledgeRemark(remark); + return baseMapper.updateById(alert) > 0; + } +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssRuleServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssRuleServiceImpl.java new file mode 100644 index 000000000..57cb42482 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/cdss/service/impl/CdssRuleServiceImpl.java @@ -0,0 +1,44 @@ +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.mapper.CdssRuleMapper; +import com.healthlink.his.cdss.service.ICdssRuleService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class CdssRuleServiceImpl extends ServiceImpl implements ICdssRuleService { + + @Override + public List findActiveRules(String triggerType, Long departmentId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CdssRule::getStatus, 1); + if (triggerType != null && !triggerType.isEmpty()) { + wrapper.eq(CdssRule::getTriggerType, triggerType); + } + if (departmentId != null) { + wrapper.and(w -> w.isNull(CdssRule::getDepartmentId).or().eq(CdssRule::getDepartmentId, departmentId)); + } + wrapper.orderByAsc(CdssRule::getSortOrder); + return baseMapper.selectList(wrapper); + } + + @Override + public List findByCondition(String ruleType, String severity, Integer status) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (ruleType != null && !ruleType.isEmpty()) { + wrapper.eq(CdssRule::getRuleType, ruleType); + } + if (severity != null && !severity.isEmpty()) { + wrapper.eq(CdssRule::getSeverity, severity); + } + if (status != null) { + wrapper.eq(CdssRule::getStatus, status); + } + wrapper.orderByAsc(CdssRule::getSortOrder); + return baseMapper.selectList(wrapper); + } +} diff --git a/healthlink-his-ui/src/api/cdss/cdssAlert.js b/healthlink-his-ui/src/api/cdss/cdssAlert.js new file mode 100644 index 000000000..184f4cae8 --- /dev/null +++ b/healthlink-his-ui/src/api/cdss/cdssAlert.js @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function evaluateRules(params) { + return request({ + url: '/cdss/evaluate', + method: 'post', + params: params + }) +} + +export function getAlerts(encounterId, query) { + return request({ + url: '/cdss/alerts/' + encounterId, + method: 'get', + params: query + }) +} + +export function acknowledgeAlert(id, data) { + return request({ + url: '/cdss/alerts/' + id + '/acknowledge', + method: 'post', + data: data + }) +} diff --git a/healthlink-his-ui/src/api/cdss/cdssRule.js b/healthlink-his-ui/src/api/cdss/cdssRule.js new file mode 100644 index 000000000..46fe51d5e --- /dev/null +++ b/healthlink-his-ui/src/api/cdss/cdssRule.js @@ -0,0 +1,32 @@ +import request from '@/utils/request' + +export function getCdssRuleList(query) { + return request({ + url: '/cdss/rules', + method: 'get', + params: query + }) +} + +export function addCdssRule(data) { + return request({ + url: '/cdss/rules', + method: 'post', + data: data + }) +} + +export function updateCdssRule(data) { + return request({ + url: '/cdss/rules', + method: 'put', + data: data + }) +} + +export function deleteCdssRule(id) { + return request({ + url: '/cdss/rules/' + id, + method: 'delete' + }) +} diff --git a/healthlink-his-ui/src/views/cdss/cdssAlerts/index.vue b/healthlink-his-ui/src/views/cdss/cdssAlerts/index.vue new file mode 100644 index 000000000..d944098fe --- /dev/null +++ b/healthlink-his-ui/src/views/cdss/cdssAlerts/index.vue @@ -0,0 +1,224 @@ + + +