feat(cdss): upgrade rule engine with priority, category, execution history and stats
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -42,4 +42,8 @@ public class CdssRule extends HisBaseEntity {
|
||||
private Integer status;
|
||||
|
||||
private Integer sortOrder;
|
||||
|
||||
private Integer priority;
|
||||
|
||||
private String category;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -18,16 +18,71 @@
|
||||
<el-option label="INFO" value="INFO" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级" prop="priority">
|
||||
<el-select v-model="queryParams.priority" placeholder="请选择" clearable style="width: 120px">
|
||||
<el-option label="最高" :value="2" />
|
||||
<el-option label="紧急" :value="1" />
|
||||
<el-option label="普通" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-input v-model="queryParams.category" placeholder="规则分类" clearable style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则名称" prop="keyword">
|
||||
<el-input v-model="queryParams.keyword" placeholder="搜索规则名称" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||||
</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="info" icon="DataAnalysis" @click="handleToggleStats">{{ showStats ? '返回列表' : '统计概览' }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<vxe-table :data="ruleList" :loading="loading" border stripe height="auto">
|
||||
<div v-if="showStats" class="stats-panel">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-value">{{ ruleStats.totalRules || 0 }}</div>
|
||||
<div class="stat-label">规则总数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-active">
|
||||
<div class="stat-value">{{ ruleStats.activeRules || 0 }}</div>
|
||||
<div class="stat-label">启用规则</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-exec">
|
||||
<div class="stat-value">{{ ruleStats.totalExecutions || 0 }}</div>
|
||||
<div class="stat-label">执行总次数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-hit">
|
||||
<div class="stat-value">{{ ruleStats.hitRate || 0 }}%</div>
|
||||
<div class="stat-label">命中率</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">执行历史</el-divider>
|
||||
<vxe-table :data="executionHistory" :loading="historyLoading" border stripe height="auto" size="small">
|
||||
<vxe-column type="seq" title="序号" width="60" />
|
||||
<vxe-column field="ruleCode" title="规则编码" width="120" />
|
||||
<vxe-column field="encounterId" title="就诊ID" width="100" />
|
||||
<vxe-column field="matched" title="是否命中" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.matched ? 'success' : 'info'" size="small">{{ row.matched ? '命中' : '未命中' }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="executionResult" title="执行结果" min-width="150" show-overflow />
|
||||
<vxe-column field="durationMs" title="耗时(ms)" width="100" align="center" />
|
||||
<vxe-column field="executionTime" title="执行时间" width="170" />
|
||||
</vxe-table>
|
||||
</div>
|
||||
|
||||
<vxe-table v-else :data="ruleList" :loading="loading" border stripe height="auto">
|
||||
<vxe-column type="seq" title="序号" width="70" />
|
||||
<vxe-column field="ruleCode" title="规则编码" width="120" />
|
||||
<vxe-column field="ruleName" title="规则名称" min-width="180" show-overflow />
|
||||
@@ -41,6 +96,12 @@
|
||||
<el-tag :type="severityTagType(row.severity)" effect="dark">{{ row.severity }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="priority" title="优先级" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="priorityTagType(row.priority)" size="small">{{ priorityLabel(row.priority) }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="category" title="分类" width="110" show-overflow />
|
||||
<vxe-column field="conditionExpr" title="条件表达式" min-width="200" show-overflow />
|
||||
<vxe-column field="actionExpr" title="执行动作" min-width="200" show-overflow />
|
||||
<vxe-column field="status" title="状态" width="80" align="center">
|
||||
@@ -55,16 +116,22 @@
|
||||
|
||||
<script setup name="CdssRules">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { getCdssRuleList } from '@/api/cdss/cdssRule'
|
||||
import { getCdssRuleListEnhanced, getCdssRuleStats, getCdssRuleHistory } from '@/api/cdss/cdssRule'
|
||||
|
||||
const ruleList = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const showStats = ref(false)
|
||||
const ruleStats = ref({})
|
||||
const executionHistory = ref([])
|
||||
const historyLoading = ref(false)
|
||||
|
||||
const queryParams = reactive({
|
||||
ruleType: '',
|
||||
severity: '',
|
||||
keyword: ''
|
||||
keyword: '',
|
||||
category: '',
|
||||
priority: undefined
|
||||
})
|
||||
|
||||
const severityTagType = (severity) => {
|
||||
@@ -72,6 +139,16 @@ const severityTagType = (severity) => {
|
||||
return map[severity] || 'info'
|
||||
}
|
||||
|
||||
const priorityTagType = (priority) => {
|
||||
const map = { 2: 'danger', 1: 'warning', 0: 'info' }
|
||||
return map[priority] || 'info'
|
||||
}
|
||||
|
||||
const priorityLabel = (priority) => {
|
||||
const map = { 2: '最高', 1: '紧急', 0: '普通' }
|
||||
return map[priority] || '普通'
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -79,7 +156,9 @@ const getList = async () => {
|
||||
if (queryParams.ruleType) params.ruleType = queryParams.ruleType
|
||||
if (queryParams.severity) params.severity = queryParams.severity
|
||||
if (queryParams.keyword) params.keyword = queryParams.keyword
|
||||
const res = await getCdssRuleList(params)
|
||||
if (queryParams.category) params.category = queryParams.category
|
||||
if (queryParams.priority !== undefined && queryParams.priority !== '') params.priority = queryParams.priority
|
||||
const res = await getCdssRuleListEnhanced(params)
|
||||
if (res.code === 200) {
|
||||
ruleList.value = res.data || []
|
||||
}
|
||||
@@ -88,6 +167,29 @@ const getList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getStats = async () => {
|
||||
try {
|
||||
const res = await getCdssRuleStats()
|
||||
if (res.code === 200) {
|
||||
ruleStats.value = res.data || {}
|
||||
}
|
||||
} catch (e) {
|
||||
ruleStats.value = {}
|
||||
}
|
||||
}
|
||||
|
||||
const getHistory = async () => {
|
||||
historyLoading.value = true
|
||||
try {
|
||||
const res = await getCdssRuleHistory({ page: 1, size: 50 })
|
||||
if (res.code === 200) {
|
||||
executionHistory.value = res.data || []
|
||||
}
|
||||
} finally {
|
||||
historyLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
@@ -96,10 +198,43 @@ const resetQuery = () => {
|
||||
queryParams.ruleType = ''
|
||||
queryParams.severity = ''
|
||||
queryParams.keyword = ''
|
||||
queryParams.category = ''
|
||||
queryParams.priority = undefined
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleToggleStats = () => {
|
||||
showStats.value = !showStats.value
|
||||
if (showStats.value) {
|
||||
getStats()
|
||||
getHistory()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stats-panel {
|
||||
padding: 16px 0;
|
||||
}
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
}
|
||||
.stat-card .stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.stat-card .stat-label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.stat-active .stat-value { color: #67c23a; }
|
||||
.stat-exec .stat-value { color: #409eff; }
|
||||
.stat-hit .stat-value { color: #e6a23c; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user