diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrHomepageQualityAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrHomepageQualityAppService.java new file mode 100644 index 000000000..30ce9d76b --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/IMrHomepageQualityAppService.java @@ -0,0 +1,13 @@ +package com.healthlink.his.web.mrhomepage.appservice; + +import com.healthlink.his.mrhomepage.domain.MrHomepageQualityCheck; + +import java.util.List; +import java.util.Map; + +public interface IMrHomepageQualityAppService { + + List checkQuality(Long homepageId); + + Map getQualityResults(Long homepageId); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrHomepageQualityAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrHomepageQualityAppServiceImpl.java new file mode 100644 index 000000000..3fb01d193 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/appservice/impl/MrHomepageQualityAppServiceImpl.java @@ -0,0 +1,54 @@ +package com.healthlink.his.web.mrhomepage.appservice.impl; + +import com.healthlink.his.mrhomepage.domain.MrHomepage; +import com.healthlink.his.mrhomepage.domain.MrHomepageQualityCheck; +import com.healthlink.his.mrhomepage.service.IMrHomepageQualityCheckService; +import com.healthlink.his.mrhomepage.service.IMrHomepageService; +import com.healthlink.his.web.mrhomepage.appservice.IMrHomepageQualityAppService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class MrHomepageQualityAppServiceImpl implements IMrHomepageQualityAppService { + + @Resource + private IMrHomepageService mrHomepageService; + + @Resource + private IMrHomepageQualityCheckService mrHomepageQualityCheckService; + + @Override + @Transactional + public List checkQuality(Long homepageId) { + mrHomepageQualityCheckService.clearByHomepageId(homepageId); + return mrHomepageQualityCheckService.executeAutoCheck(homepageId); + } + + @Override + public Map getQualityResults(Long homepageId) { + MrHomepage homepage = mrHomepageService.getById(homepageId); + List checks = mrHomepageQualityCheckService.selectByHomepageId(homepageId); + + long totalChecks = checks.size(); + long passedChecks = checks.stream() + .filter(c -> "PASS".equals(c.getCheckResult())) + .count(); + long failedChecks = totalChecks - passedChecks; + double score = totalChecks > 0 ? (double) passedChecks / totalChecks * 100 : 0; + + Map results = new HashMap<>(); + results.put("homepageId", homepageId); + results.put("qualityStatus", homepage != null ? homepage.getQualityStatus() : null); + results.put("totalChecks", totalChecks); + results.put("passedChecks", passedChecks); + results.put("failedChecks", failedChecks); + results.put("score", Math.round(score * 10.0) / 10.0); + results.put("checks", checks); + return results; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrHomepageQualityController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrHomepageQualityController.java new file mode 100644 index 000000000..9ed08bed0 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/mrhomepage/controller/MrHomepageQualityController.java @@ -0,0 +1,36 @@ +package com.healthlink.his.web.mrhomepage.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.mrhomepage.domain.MrHomepageQualityCheck; +import com.healthlink.his.web.mrhomepage.appservice.IMrHomepageQualityAppService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/mr-homepage/quality") +@Tag(name = "病案首页质量校验") +public class MrHomepageQualityController { + + @Resource + private IMrHomepageQualityAppService mrHomepageQualityAppService; + + @PostMapping("/check") + @PreAuthorize("hasAuthority('inpatient:mrhomepage:edit')") + @Operation(summary = "执行质量校验") + public R> checkQuality(@RequestParam Long homepageId) { + return R.ok(mrHomepageQualityAppService.checkQuality(homepageId)); + } + + @GetMapping("/results/{homepageId}") + @PreAuthorize("hasAuthority('inpatient:mrhomepage:list')") + @Operation(summary = "获取质量校验结果") + public R> getQualityResults(@PathVariable Long homepageId) { + return R.ok(mrHomepageQualityAppService.getQualityResults(homepageId)); + } +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/IMrHomepageQualityCheckService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/IMrHomepageQualityCheckService.java index cb4095060..9e16d260b 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/IMrHomepageQualityCheckService.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/IMrHomepageQualityCheckService.java @@ -10,4 +10,6 @@ public interface IMrHomepageQualityCheckService extends IService selectByHomepageId(Long homepageId); List executeAutoCheck(Long homepageId); + + void clearByHomepageId(Long homepageId); } diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/impl/MrHomepageQualityCheckServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/impl/MrHomepageQualityCheckServiceImpl.java index 9bc4d3b95..8e710e8da 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/impl/MrHomepageQualityCheckServiceImpl.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/mrhomepage/service/impl/MrHomepageQualityCheckServiceImpl.java @@ -1,5 +1,6 @@ package com.healthlink.his.mrhomepage.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.healthlink.his.mrhomepage.domain.MrHomepage; import com.healthlink.his.mrhomepage.domain.MrHomepageQualityCheck; @@ -10,15 +11,19 @@ import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.regex.Pattern; @Service public class MrHomepageQualityCheckServiceImpl extends ServiceImpl implements IMrHomepageQualityCheckService { + private static final Pattern ICD10_PATTERN = Pattern.compile("^[A-Z]\\d{2}(\\.\\d{1,4})?$"); + @Resource private IMrHomepageService mrHomepageService; @@ -27,51 +32,150 @@ public class MrHomepageQualityCheckServiceImpl return baseMapper.selectByHomepageId(homepageId); } + @Override + @Transactional + public void clearByHomepageId(Long homepageId) { + remove(new LambdaQueryWrapper() + .eq(MrHomepageQualityCheck::getHomepageId, homepageId)); + } + @Override @Transactional public List executeAutoCheck(Long homepageId) { MrHomepage homepage = mrHomepageService.getById(homepageId); + if (homepage == null) { + return List.of(); + } List checks = new ArrayList<>(); + Date now = new Date(); - // 主诊断编码检查 - MrHomepageQualityCheck diagnosisCheck = new MrHomepageQualityCheck() - .setHomepageId(homepageId) - .setCheckItem("主诊断编码完整性") - .setCheckCategory("基础信息") - .setCheckResult(homepage.getPrimaryDiagnosisCode() != null ? "PASS" : "FAIL") - .setCheckDetail("主诊断编码: " + homepage.getPrimaryDiagnosisCode()) - .setSuggestion(homepage.getPrimaryDiagnosisCode() == null ? "请填写主诊断编码" : null) - .setChecker("AUTO") - .setCheckTime(new Date()); - save(diagnosisCheck); - checks.add(diagnosisCheck); + checkRequiredField(checks, homepageId, "患者ID", "基础信息", + homepage.getPatientId() != null, "患者ID", now); + checkRequiredField(checks, homepageId, "就诊ID", "基础信息", + homepage.getEncounterId() != null, "就诊ID", now); + checkRequiredField(checks, homepageId, "入院日期", "日期信息", + homepage.getAdmissionDate() != null, "入院日期", now); + checkRequiredField(checks, homepageId, "出院日期", "日期信息", + homepage.getDischargeDate() != null, "出院日期", now); + checkRequiredField(checks, homepageId, "主诊断编码", "诊断信息", + homepage.getPrimaryDiagnosisCode() != null, "主诊断编码", now); + checkRequiredField(checks, homepageId, "主诊断名称", "诊断信息", + homepage.getPrimaryDiagnosisName() != null, "主诊断名称", now); + checkRequiredField(checks, homepageId, "出院情况", "出院信息", + homepage.getDischargeCondition() != null, "出院情况", now); + checkRequiredField(checks, homepageId, "出院去向", "出院信息", + homepage.getDischargeDestination() != null, "出院去向", now); - // 主手术编码检查 - MrHomepageQualityCheck procedureCheck = new MrHomepageQualityCheck() - .setHomepageId(homepageId) - .setCheckItem("主手术编码完整性") - .setCheckCategory("基础信息") - .setCheckResult(homepage.getPrimaryProcedureCode() != null ? "PASS" : "FAIL") - .setCheckDetail("主手术编码: " + homepage.getPrimaryProcedureCode()) - .setSuggestion(homepage.getPrimaryProcedureCode() == null ? "请填写主手术编码" : null) - .setChecker("AUTO") - .setCheckTime(new Date()); - save(procedureCheck); - checks.add(procedureCheck); + if (homepage.getAdmissionDate() != null && homepage.getDischargeDate() != null) { + boolean dateValid = !homepage.getAdmissionDate().after(homepage.getDischargeDate()); + addCheck(checks, homepageId, "入院日期≤出院日期", "逻辑校验", + dateValid ? "PASS" : "FAIL", + "入院: " + homepage.getAdmissionDate() + ", 出院: " + homepage.getDischargeDate(), + dateValid ? null : "入院日期不能晚于出院日期", "AUTO", now); + } - // 入院日期检查 - MrHomepageQualityCheck admissionCheck = new MrHomepageQualityCheck() - .setHomepageId(homepageId) - .setCheckItem("入院日期完整性") - .setCheckCategory("日期信息") - .setCheckResult(homepage.getAdmissionDate() != null ? "PASS" : "FAIL") - .setCheckDetail("入院日期: " + homepage.getAdmissionDate()) - .setSuggestion(homepage.getAdmissionDate() == null ? "请填写入院日期" : null) - .setChecker("AUTO") - .setCheckTime(new Date()); - save(admissionCheck); - checks.add(admissionCheck); + if (homepage.getAdmissionDate() != null && homepage.getDischargeDate() != null + && homepage.getLosDays() != null) { + long diffMs = homepage.getDischargeDate().getTime() - homepage.getAdmissionDate().getTime(); + long expectedDays = Math.max(1, diffMs / (1000 * 60 * 60 * 24) + 1); + boolean losValid = homepage.getLosDays().intValue() == expectedDays; + addCheck(checks, homepageId, "住院天数一致性", "逻辑校验", + losValid ? "PASS" : "FAIL", + "记录天数: " + homepage.getLosDays() + ", 计算天数: " + expectedDays, + losValid ? null : "住院天数应为" + expectedDays + "天", "AUTO", now); + } + + if (homepage.getPrimaryDiagnosisCode() != null) { + boolean icdValid = ICD10_PATTERN.matcher(homepage.getPrimaryDiagnosisCode()).matches(); + addCheck(checks, homepageId, "主诊断ICD编码格式", "ICD编码", + icdValid ? "PASS" : "FAIL", + "编码: " + homepage.getPrimaryDiagnosisCode(), + icdValid ? null : "ICD-10编码格式应为: 字母+2位数字(.可选小数)", "AUTO", now); + } + + if (homepage.getPrimaryProcedureCode() != null) { + boolean icdValid = ICD10_PATTERN.matcher(homepage.getPrimaryProcedureCode()).matches(); + addCheck(checks, homepageId, "主手术ICD编码格式", "ICD编码", + icdValid ? "PASS" : "FAIL", + "编码: " + homepage.getPrimaryProcedureCode(), + icdValid ? null : "ICD-9-CM-3编码格式应为: 字母+2位数字(.可选小数)", "AUTO", now); + } + + checkCostNonNegative(checks, homepageId, "总费用", homepage.getTotalCost(), now); + checkCostNonNegative(checks, homepageId, "自费费用", homepage.getSelfPayCost(), now); + checkCostNonNegative(checks, homepageId, "医保费用", homepage.getInsuranceCost(), now); + checkCostNonNegative(checks, homepageId, "药品费", homepage.getDrugCost(), now); + checkCostNonNegative(checks, homepageId, "检查费", homepage.getExaminationCost(), now); + checkCostNonNegative(checks, homepageId, "化验费", homepage.getLabCost(), now); + checkCostNonNegative(checks, homepageId, "治疗费", homepage.getTreatmentCost(), now); + checkCostNonNegative(checks, homepageId, "材料费", homepage.getMaterialCost(), now); + + if (homepage.getTotalCost() != null && homepage.getTotalCost().compareTo(BigDecimal.ZERO) > 0) { + BigDecimal sum = BigDecimal.ZERO; + sum = safeAdd(sum, homepage.getDrugCost()); + sum = safeAdd(sum, homepage.getExaminationCost()); + sum = safeAdd(sum, homepage.getLabCost()); + sum = safeAdd(sum, homepage.getTreatmentCost()); + sum = safeAdd(sum, homepage.getMaterialCost()); + boolean costValid = homepage.getTotalCost().compareTo(sum) == 0; + addCheck(checks, homepageId, "费用构成一致性", "逻辑校验", + costValid ? "PASS" : "FAIL", + "总费用: " + homepage.getTotalCost() + ", 分项合计: " + sum, + costValid ? null : "总费用应等于药品费+检查费+化验费+治疗费+材料费", "AUTO", now); + } + + if (homepage.getSelfPayCost() != null && homepage.getInsuranceCost() != null + && homepage.getTotalCost() != null && homepage.getTotalCost().compareTo(BigDecimal.ZERO) > 0) { + BigDecimal paySum = safeAdd(homepage.getSelfPayCost(), homepage.getInsuranceCost()); + boolean payValid = homepage.getTotalCost().compareTo(paySum) == 0; + addCheck(checks, homepageId, "费用分担一致性", "逻辑校验", + payValid ? "PASS" : "FAIL", + "总费用: " + homepage.getTotalCost() + ", 自费+医保: " + paySum, + payValid ? null : "总费用应等于自费费用+医保费用", "AUTO", now); + } return checks; } + + private void checkRequiredField(List checks, Long homepageId, + String itemName, String category, boolean isValid, + String fieldName, Date now) { + addCheck(checks, homepageId, itemName + "完整性", category, + isValid ? "PASS" : "FAIL", + fieldName + ": " + (isValid ? "已填写" : "未填写"), + isValid ? null : "请填写" + fieldName, "AUTO", now); + } + + private void checkCostNonNegative(List checks, Long homepageId, + String costName, BigDecimal value, Date now) { + if (value != null) { + boolean valid = value.compareTo(BigDecimal.ZERO) >= 0; + addCheck(checks, homepageId, costName + "非负", "费用信息", + valid ? "PASS" : "FAIL", + costName + ": " + value, + valid ? null : costName + "不能为负数", "AUTO", now); + } + } + + private void addCheck(List checks, Long homepageId, + String checkItem, String category, String result, + String detail, String suggestion, String checker, Date now) { + MrHomepageQualityCheck check = new MrHomepageQualityCheck() + .setHomepageId(homepageId) + .setCheckItem(checkItem) + .setCheckCategory(category) + .setCheckResult(result) + .setCheckDetail(detail) + .setSuggestion(suggestion) + .setChecker(checker) + .setCheckTime(now); + save(check); + checks.add(check); + } + + private BigDecimal safeAdd(BigDecimal a, BigDecimal b) { + if (a == null) return b != null ? b : BigDecimal.ZERO; + if (b == null) return a; + return a.add(b); + } } diff --git a/healthlink-his-ui/src/api/mrhomepage.js b/healthlink-his-ui/src/api/mrhomepage.js index 67526624e..057592e52 100644 --- a/healthlink-his-ui/src/api/mrhomepage.js +++ b/healthlink-his-ui/src/api/mrhomepage.js @@ -1,3 +1,5 @@ import request from "@/utils/request" export function executeQualityCheck(id) { return request({ url: "/api/v1/mr-homepage/quality-check/" + id, method: "post" }) } export function submitHomepage(id) { return request({ url: "/api/v1/mr-homepage/submit/" + id, method: "put" }) } +export function checkQuality(homepageId) { return request({ url: "/api/v1/mr-homepage/quality/check", method: "post", params: { homepageId } }) } +export function getQualityResults(homepageId) { return request({ url: "/api/v1/mr-homepage/quality/results/" + homepageId, method: "get" }) } diff --git a/healthlink-his-ui/src/api/mrhomepage/index.js b/healthlink-his-ui/src/api/mrhomepage/index.js index 370befdd6..f66ceb4b0 100644 --- a/healthlink-his-ui/src/api/mrhomepage/index.js +++ b/healthlink-his-ui/src/api/mrhomepage/index.js @@ -6,3 +6,5 @@ export function executeQualityCheck(id) { return request({ url: '/api/v1/mr-home export function getQualityCheck(homepageId) { return request({ url: '/api/v1/mr-homepage/quality-check/' + homepageId, method: 'get' }) } export function getStatistics(params) { return request({ url: '/api/v1/mr-homepage/statistics', method: 'get', params }) } export function submitHomepage(id) { return request({ url: '/api/v1/mr-homepage/submit/' + id, method: 'put' }) } +export function checkQuality(homepageId) { return request({ url: '/api/v1/mr-homepage/quality/check', method: 'post', params: { homepageId } }) } +export function getQualityResults(homepageId) { return request({ url: '/api/v1/mr-homepage/quality/results/' + homepageId, method: 'get' }) } diff --git a/healthlink-his-ui/src/views/mrhomepage/quality/index.vue b/healthlink-his-ui/src/views/mrhomepage/quality/index.vue new file mode 100644 index 000000000..4f8306dba --- /dev/null +++ b/healthlink-his-ui/src/views/mrhomepage/quality/index.vue @@ -0,0 +1,171 @@ + + + + +