feat(cdss): CDSS临床决策支持
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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<CdssRule> activeRules = cdssRuleService.findActiveRules(triggerType, departmentId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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<CdssAlert> 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<CdssRule> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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.CdssAlertMapper">
|
||||
|
||||
<resultMap type="com.healthlink.his.cdss.domain.CdssAlert" id="CdssAlertResult">
|
||||
<id column="id" property="id"/>
|
||||
<result column="encounter_id" property="encounterId"/>
|
||||
<result column="patient_id" property="patientId"/>
|
||||
<result column="rule_id" property="ruleId"/>
|
||||
<result column="rule_code" property="ruleCode"/>
|
||||
<result column="rule_name" property="ruleName"/>
|
||||
<result column="severity" property="severity"/>
|
||||
<result column="alert_title" property="alertTitle"/>
|
||||
<result column="alert_message" property="alertMessage"/>
|
||||
<result column="suggestion" property="suggestion"/>
|
||||
<result column="acknowledged" property="acknowledged"/>
|
||||
<result column="acknowledged_by" property="acknowledgedBy"/>
|
||||
<result column="acknowledged_time" property="acknowledgedTime"/>
|
||||
<result column="acknowledge_remark" property="acknowledgeRemark"/>
|
||||
<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, 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
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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.CdssRuleMapper">
|
||||
|
||||
<resultMap type="com.healthlink.his.cdss.domain.CdssRule" id="CdssRuleResult">
|
||||
<id column="id" property="id"/>
|
||||
<result column="rule_code" property="ruleCode"/>
|
||||
<result column="rule_name" property="ruleName"/>
|
||||
<result column="rule_type" property="ruleType"/>
|
||||
<result column="severity" property="severity"/>
|
||||
<result column="trigger_type" property="triggerType"/>
|
||||
<result column="condition_expr" property="conditionExpr"/>
|
||||
<result column="action_expr" property="actionExpr"/>
|
||||
<result column="description" property="description"/>
|
||||
<result column="department_id" property="departmentId"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="sort_order" property="sortOrder"/>
|
||||
<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, 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
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<CdssAlert> {
|
||||
}
|
||||
@@ -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<CdssRule> {
|
||||
}
|
||||
@@ -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<CdssAlert> {
|
||||
|
||||
List<CdssAlert> findByEncounterId(Long encounterId);
|
||||
|
||||
boolean acknowledgeAlert(Long id, String operator, String remark);
|
||||
}
|
||||
@@ -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<CdssRule> {
|
||||
|
||||
List<CdssRule> findActiveRules(String triggerType, Long departmentId);
|
||||
|
||||
List<CdssRule> findByCondition(String ruleType, String severity, Integer status);
|
||||
}
|
||||
@@ -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<CdssAlertMapper, CdssAlert> implements ICdssAlertService {
|
||||
|
||||
@Override
|
||||
public List<CdssAlert> findByEncounterId(Long encounterId) {
|
||||
LambdaQueryWrapper<CdssAlert> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<CdssRuleMapper, CdssRule> implements ICdssRuleService {
|
||||
|
||||
@Override
|
||||
public List<CdssRule> findActiveRules(String triggerType, Long departmentId) {
|
||||
LambdaQueryWrapper<CdssRule> 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<CdssRule> findByCondition(String ruleType, String severity, Integer status) {
|
||||
LambdaQueryWrapper<CdssRule> 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);
|
||||
}
|
||||
}
|
||||
25
healthlink-his-ui/src/api/cdss/cdssAlert.js
Normal file
25
healthlink-his-ui/src/api/cdss/cdssAlert.js
Normal file
@@ -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
|
||||
})
|
||||
}
|
||||
32
healthlink-his-ui/src/api/cdss/cdssRule.js
Normal file
32
healthlink-his-ui/src/api/cdss/cdssRule.js
Normal file
@@ -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'
|
||||
})
|
||||
}
|
||||
224
healthlink-his-ui/src/views/cdss/cdssAlerts/index.vue
Normal file
224
healthlink-his-ui/src/views/cdss/cdssAlerts/index.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="100px">
|
||||
<el-form-item label="就诊ID" prop="encounterId">
|
||||
<el-input v-model="queryParams.encounterId" placeholder="请输入就诊ID" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="严重程度" prop="severity">
|
||||
<el-select v-model="queryParams.severity" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="INFO" value="INFO" />
|
||||
<el-option label="WARNING" value="WARNING" />
|
||||
<el-option label="CRITICAL" value="CRITICAL" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认状态" prop="acknowledged">
|
||||
<el-select v-model="queryParams.acknowledged" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="未确认" :value="0" />
|
||||
<el-option label="已确认" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
<el-button type="warning" icon="Monitor" @click="handleEvaluate">评估规则</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<vxe-table :data="alertList" :loading="loading" border stripe height="auto">
|
||||
<vxe-column type="seq" title="序号" width="70" />
|
||||
<vxe-column field="alertTitle" title="告警标题" min-width="200" show-overflow />
|
||||
<vxe-column field="severity" title="严重程度" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="severityTagType(row.severity)">{{ row.severity }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="ruleName" title="规则名称" width="160" show-overflow />
|
||||
<vxe-column field="alertMessage" title="告警详情" min-width="240" show-overflow />
|
||||
<vxe-column field="suggestion" title="处理建议" min-width="200" show-overflow />
|
||||
<vxe-column field="acknowledged" title="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.acknowledged === 1 ? 'success' : 'danger'">
|
||||
{{ row.acknowledged === 1 ? '已确认' : '未确认' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="acknowledgedBy" title="确认人" width="120" />
|
||||
<vxe-column field="createTime" title="触发时间" width="180" />
|
||||
<vxe-column title="操作" width="120" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.acknowledged === 0" link type="primary" icon="Check" v-hasPermi="['cdss:alert:acknowledge']" @click="handleAcknowledge(row)">确认</el-button>
|
||||
<span v-else class="text-gray">-</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
|
||||
<el-dialog title="评估CDSS规则" v-model="evaluateDialogVisible" width="500px" append-to-body>
|
||||
<el-form :model="evaluateForm" label-width="100px">
|
||||
<el-form-item label="就诊ID" required>
|
||||
<el-input v-model="evaluateForm.encounterId" placeholder="请输入就诊ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="患者ID" required>
|
||||
<el-input v-model="evaluateForm.patientId" placeholder="请输入患者ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="触发类型">
|
||||
<el-select v-model="evaluateForm.triggerType" placeholder="请选择" clearable>
|
||||
<el-option label="医嘱" value="order" />
|
||||
<el-option label="入院" value="admission" />
|
||||
<el-option label="出院" value="discharge" />
|
||||
<el-option label="生命体征" value="vital_sign" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="科室ID">
|
||||
<el-input v-model="evaluateForm.departmentId" placeholder="请输入科室ID" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="evaluateDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitEvaluate">确认评估</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="确认告警" v-model="acknowledgeDialogVisible" width="450px" append-to-body>
|
||||
<el-form :model="acknowledgeForm" label-width="80px">
|
||||
<el-form-item label="告警标题">
|
||||
<span>{{ acknowledgeForm.alertTitle }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认备注">
|
||||
<el-input v-model="acknowledgeForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="acknowledgeDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAcknowledge">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CdssAlerts" ref="queryFormRef">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { evaluateRules, getAlerts, acknowledgeAlert } from '@/api/cdss/cdssAlert'
|
||||
|
||||
const alertList = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
|
||||
const queryParams = reactive({
|
||||
encounterId: '',
|
||||
severity: '',
|
||||
acknowledged: undefined
|
||||
})
|
||||
|
||||
const evaluateDialogVisible = ref(false)
|
||||
const evaluateForm = reactive({
|
||||
encounterId: '',
|
||||
patientId: '',
|
||||
triggerType: '',
|
||||
departmentId: ''
|
||||
})
|
||||
|
||||
const acknowledgeDialogVisible = ref(false)
|
||||
const acknowledgeForm = reactive({
|
||||
id: null,
|
||||
alertTitle: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const severityTagType = (severity) => {
|
||||
const map = { INFO: 'info', WARNING: 'warning', CRITICAL: 'danger' }
|
||||
return map[severity] || 'info'
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
if (!queryParams.encounterId) {
|
||||
alertList.value = []
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getAlerts(queryParams.encounterId, {
|
||||
acknowledged: queryParams.acknowledged
|
||||
})
|
||||
if (res.code === 200) {
|
||||
let list = res.data || []
|
||||
if (queryParams.severity) {
|
||||
list = list.filter(item => item.severity === queryParams.severity)
|
||||
}
|
||||
alertList.value = list
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
const resetQuery = () => {
|
||||
queryParams.severity = ''
|
||||
queryParams.acknowledged = undefined
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleEvaluate = () => {
|
||||
evaluateForm.encounterId = ''
|
||||
evaluateForm.patientId = ''
|
||||
evaluateForm.triggerType = ''
|
||||
evaluateForm.departmentId = ''
|
||||
evaluateDialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitEvaluate = async () => {
|
||||
if (!evaluateForm.encounterId || !evaluateForm.patientId) {
|
||||
ElMessage.error('就诊ID和患者ID不能为空')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const params = {
|
||||
encounterId: evaluateForm.encounterId,
|
||||
patientId: evaluateForm.patientId
|
||||
}
|
||||
if (evaluateForm.triggerType) params.triggerType = evaluateForm.triggerType
|
||||
if (evaluateForm.departmentId) params.departmentId = evaluateForm.departmentId
|
||||
const res = await evaluateRules(params)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('评估完成,触发 ' + res.data.triggeredAlerts + ' 条告警')
|
||||
evaluateDialogVisible.value = false
|
||||
queryParams.encounterId = evaluateForm.encounterId
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '评估失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('评估失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleAcknowledge = (row) => {
|
||||
acknowledgeForm.id = row.id
|
||||
acknowledgeForm.alertTitle = row.alertTitle
|
||||
acknowledgeForm.remark = ''
|
||||
acknowledgeDialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitAcknowledge = async () => {
|
||||
try {
|
||||
const res = await acknowledgeAlert(acknowledgeForm.id, { remark: acknowledgeForm.remark })
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('确认成功')
|
||||
acknowledgeDialogVisible.value = false
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '确认失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('确认失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user