feat(P3): 病案管理完善 — DRG/DIP分组+统计分析
- MrDrgController: DRG/DIP分组/无效标记/统计/排名 - MrDrgGrouping: 分组结果实体+V28 Flyway迁移 - 前端drg: 分组列表+DRG排名+统计卡片 - 后端编译通过,前端构建通过
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package com.healthlink.his.web.mrhomepage.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
|
||||
import com.healthlink.his.mrhomepage.service.IMrDrgGroupingService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* DRG/DIP分组 + 病案归档统计 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/mr-drg")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class MrDrgController {
|
||||
|
||||
private final IMrDrgGroupingService drgGroupingService;
|
||||
|
||||
// ==================== DRG/DIP分组 ====================
|
||||
|
||||
@GetMapping("/page")
|
||||
public R<?> getPage(
|
||||
@RequestParam(value = "groupingType", required = false) String groupingType,
|
||||
@RequestParam(value = "drgCode", required = false) String drgCode,
|
||||
@RequestParam(value = "patientName", required = false) String patientName,
|
||||
@RequestParam(value = "isValid", required = false) Boolean isValid,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<MrDrgGrouping> w = new LambdaQueryWrapper<>();
|
||||
w.eq(StringUtils.hasText(groupingType), MrDrgGrouping::getGroupingType, groupingType)
|
||||
.like(StringUtils.hasText(drgCode), MrDrgGrouping::getDrgCode, drgCode)
|
||||
.like(StringUtils.hasText(patientName), MrDrgGrouping::getPatientName, patientName)
|
||||
.eq(isValid != null, MrDrgGrouping::getIsValid, isValid)
|
||||
.orderByDesc(MrDrgGrouping::getDischargeDate);
|
||||
return R.ok(drgGroupingService.page(new Page<>(pageNo, pageSize), w));
|
||||
}
|
||||
|
||||
@PostMapping("/group")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> groupDrg(@RequestBody MrDrgGrouping grouping) {
|
||||
// 简化DRG分组逻辑 — 根据主诊断+主手术推算DRG组
|
||||
String drgCode = calculateDrgCode(grouping);
|
||||
grouping.setDrgCode(drgCode);
|
||||
grouping.setDrgName(getDrgName(drgCode));
|
||||
grouping.setDrgWeight(getDrgWeight(drgCode));
|
||||
grouping.setIsValid(true);
|
||||
grouping.setCreateTime(new Date());
|
||||
drgGroupingService.save(grouping);
|
||||
return R.ok(grouping);
|
||||
}
|
||||
|
||||
@PutMapping("/invalidate/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> invalidate(@PathVariable Long id, @RequestParam("reason") String reason) {
|
||||
MrDrgGrouping g = drgGroupingService.getById(id);
|
||||
if (g == null) return R.fail("分组记录不存在");
|
||||
g.setIsValid(false);
|
||||
g.setInvalidReason(reason);
|
||||
drgGroupingService.updateById(g);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// ==================== 统计分析 ====================
|
||||
|
||||
@GetMapping("/stats/overview")
|
||||
public R<?> getOverview(
|
||||
@RequestParam(value = "startDate", required = false) String startDate,
|
||||
@RequestParam(value = "endDate", required = false) String endDate) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
|
||||
// DRG分组统计
|
||||
LambdaQueryWrapper<MrDrgGrouping> drgW = new LambdaQueryWrapper<>();
|
||||
drgW.eq(MrDrgGrouping::getGroupingType, "DRG");
|
||||
stats.put("drgTotal", drgGroupingService.count(drgW));
|
||||
|
||||
// DIP分组统计
|
||||
LambdaQueryWrapper<MrDrgGrouping> dipW = new LambdaQueryWrapper<>();
|
||||
dipW.eq(MrDrgGrouping::getGroupingType, "DIP");
|
||||
stats.put("dipTotal", drgGroupingService.count(dipW));
|
||||
|
||||
// 无效分组数
|
||||
LambdaQueryWrapper<MrDrgGrouping> invW = new LambdaQueryWrapper<>();
|
||||
invW.eq(MrDrgGrouping::getIsValid, false);
|
||||
stats.put("invalidCount", drgGroupingService.count(invW));
|
||||
|
||||
// 费用统计
|
||||
List<MrDrgGrouping> all = drgGroupingService.list();
|
||||
BigDecimal totalCost = BigDecimal.ZERO;
|
||||
BigDecimal totalInsurance = BigDecimal.ZERO;
|
||||
for (MrDrgGrouping g : all) {
|
||||
if (g.getTotalCost() != null) totalCost = totalCost.add(g.getTotalCost());
|
||||
if (g.getInsurancePayment() != null) totalInsurance = totalInsurance.add(g.getInsurancePayment());
|
||||
}
|
||||
stats.put("totalCost", totalCost);
|
||||
stats.put("totalInsurance", totalInsurance);
|
||||
|
||||
return R.ok(stats);
|
||||
}
|
||||
|
||||
@GetMapping("/stats/top-drg")
|
||||
public R<?> getTopDrg(@RequestParam(value = "limit", defaultValue = "10") Integer limit) {
|
||||
List<MrDrgGrouping> all = drgGroupingService.list();
|
||||
Map<String, Integer> drgCount = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> drgCost = new LinkedHashMap<>();
|
||||
for (MrDrgGrouping g : all) {
|
||||
String code = g.getDrgCode();
|
||||
if (code != null) {
|
||||
drgCount.merge(code, 1, Integer::sum);
|
||||
drgCost.merge(code, g.getTotalCost() != null ? g.getTotalCost() : BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
List<Map<String, Object>> topList = new ArrayList<>();
|
||||
drgCount.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
|
||||
.limit(limit)
|
||||
.forEach(e -> {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("drgCode", e.getKey());
|
||||
item.put("count", e.getValue());
|
||||
item.put("totalCost", drgCost.getOrDefault(e.getKey(), BigDecimal.ZERO));
|
||||
topList.add(item);
|
||||
});
|
||||
return R.ok(topList);
|
||||
}
|
||||
|
||||
// ==================== DRG分组算法(简化版) ====================
|
||||
|
||||
private String calculateDrgCode(MrDrgGrouping g) {
|
||||
// 简化DRG分组: 根据主诊断前3位+主手术确定DRG组
|
||||
String diagCode = g.getPrimaryDiagnosisCode();
|
||||
String procCode = g.getPrimaryProcedureCode();
|
||||
if (diagCode == null || diagCode.isEmpty()) return "ZZ99";
|
||||
String mdc = diagCode.substring(0, Math.min(3, diagCode.length()));
|
||||
if (procCode != null && !procCode.isEmpty()) {
|
||||
return mdc + "-" + procCode.substring(0, Math.min(2, procCode.length()));
|
||||
}
|
||||
return mdc + "-MED";
|
||||
}
|
||||
|
||||
private String getDrgName(String code) {
|
||||
return "DRG组(" + code + ")";
|
||||
}
|
||||
|
||||
private BigDecimal getDrgWeight(String code) {
|
||||
// 简化权重计算
|
||||
return new BigDecimal("1.0000");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
-- V28: 病案管理增强 — DRG/DIP分组+归档统计
|
||||
|
||||
-- DRG/DIP分组结果表
|
||||
CREATE TABLE IF NOT EXISTS mr_drg_grouping (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
patient_name VARCHAR(50),
|
||||
discharge_date DATE,
|
||||
primary_diagnosis VARCHAR(200),
|
||||
primary_diagnosis_code VARCHAR(20),
|
||||
secondary_diagnosis_code VARCHAR(20),
|
||||
primary_procedure VARCHAR(200),
|
||||
primary_procedure_code VARCHAR(20),
|
||||
drg_code VARCHAR(20),
|
||||
drg_name VARCHAR(200),
|
||||
drg_weight DECIMAL(8,4),
|
||||
dip_code VARCHAR(20),
|
||||
dip_name VARCHAR(200),
|
||||
grouping_type VARCHAR(10) NOT NULL,
|
||||
total_cost DECIMAL(12,2),
|
||||
insurance_payment DECIMAL(12,2),
|
||||
patient_payment DECIMAL(12,2),
|
||||
los_days INT,
|
||||
grouping_result TEXT,
|
||||
is_valid BOOLEAN DEFAULT TRUE,
|
||||
invalid_reason VARCHAR(200),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
is_deleted INT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE mr_drg_grouping IS 'DRG/DIP分组结果';
|
||||
COMMENT ON COLUMN mr_drg_grouping.grouping_type IS '分组类型(DRG/DIP)';
|
||||
CREATE INDEX idx_drg_encounter ON mr_drg_grouping(encounter_id);
|
||||
CREATE INDEX idx_drg_code ON mr_drg_grouping(drg_code);
|
||||
CREATE INDEX idx_dip_code ON mr_drg_grouping(dip_code);
|
||||
|
||||
-- 病案归档统计表
|
||||
CREATE TABLE IF NOT EXISTS mr_archive_stats (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
stat_date DATE NOT NULL,
|
||||
department_id BIGINT,
|
||||
department_name VARCHAR(100),
|
||||
total_discharged INT DEFAULT 0,
|
||||
archived_count INT DEFAULT 0,
|
||||
archive_rate DECIMAL(5,2),
|
||||
avg_archive_hours DECIMAL(8,2),
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE mr_archive_stats IS '病案归档统计(每日)';
|
||||
CREATE UNIQUE INDEX idx_mras_date_dept ON mr_archive_stats(stat_date, department_id);
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.healthlink.his.mrhomepage.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* DRG/DIP分组结果
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("mr_drg_grouping")
|
||||
public class MrDrgGrouping extends HisBaseEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long encounterId;
|
||||
private Long patientId;
|
||||
private String patientName;
|
||||
private LocalDate dischargeDate;
|
||||
private String primaryDiagnosis;
|
||||
private String primaryDiagnosisCode;
|
||||
private String secondaryDiagnosisCode;
|
||||
private String primaryProcedure;
|
||||
private String primaryProcedureCode;
|
||||
private String drgCode;
|
||||
private String drgName;
|
||||
private BigDecimal drgWeight;
|
||||
private String dipCode;
|
||||
private String dipName;
|
||||
private String groupingType;
|
||||
private BigDecimal totalCost;
|
||||
private BigDecimal insurancePayment;
|
||||
private BigDecimal patientPayment;
|
||||
private Integer losDays;
|
||||
private String groupingResult;
|
||||
private Boolean isValid;
|
||||
private String invalidReason;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.healthlink.his.mrhomepage.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MrDrgGroupingMapper extends BaseMapper<MrDrgGrouping> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.healthlink.his.mrhomepage.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
|
||||
|
||||
public interface IMrDrgGroupingService extends IService<MrDrgGrouping> {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthlink.his.mrhomepage.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
|
||||
import com.healthlink.his.mrhomepage.mapper.MrDrgGroupingMapper;
|
||||
import com.healthlink.his.mrhomepage.service.IMrDrgGroupingService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MrDrgGroupingServiceImpl
|
||||
extends ServiceImpl<MrDrgGroupingMapper, MrDrgGrouping>
|
||||
implements IMrDrgGroupingService {
|
||||
}
|
||||
Reference in New Issue
Block a user