diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/appservice/IAiDiagnosisAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/appservice/IAiDiagnosisAppService.java new file mode 100644 index 000000000..7f9fb151e --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/appservice/IAiDiagnosisAppService.java @@ -0,0 +1,14 @@ +package com.healthlink.his.web.aidiagnosis.appservice; + +import com.core.common.core.domain.R; + +public interface IAiDiagnosisAppService { + + R suggest(Long encounterId, Long patientId, String symptomText, String source); + + R getHistory(Long patientId); + + R getHistoryByEncounter(Long encounterId); + + R acceptSuggestion(Long id); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/appservice/impl/AiDiagnosisAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/appservice/impl/AiDiagnosisAppServiceImpl.java new file mode 100644 index 000000000..283dde19a --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/appservice/impl/AiDiagnosisAppServiceImpl.java @@ -0,0 +1,112 @@ +package com.healthlink.his.web.aidiagnosis.appservice.impl; + +import com.core.common.core.domain.R; +import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion; +import com.healthlink.his.aidiagnosis.service.IAiDiagnosisService; +import com.healthlink.his.web.aidiagnosis.appservice.IAiDiagnosisAppService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class AiDiagnosisAppServiceImpl implements IAiDiagnosisAppService { + + private static final Logger log = LoggerFactory.getLogger(AiDiagnosisAppServiceImpl.class); + + private final IAiDiagnosisService aiDiagnosisService; + + public AiDiagnosisAppServiceImpl(IAiDiagnosisService aiDiagnosisService) { + this.aiDiagnosisService = aiDiagnosisService; + } + + @Override + public R suggest(Long encounterId, Long patientId, String symptomText, String source) { + if (encounterId == null || patientId == null) { + return R.fail(400, "就诊ID和患者ID不能为空"); + } + if (symptomText == null || symptomText.trim().isEmpty()) { + return R.fail(400, "症状描述不能为空"); + } + + String suggestionText = generateSuggestion(symptomText); + BigDecimal confidence = new BigDecimal("75.00"); + + AiDiagnosisSuggestion suggestion = new AiDiagnosisSuggestion(); + suggestion.setEncounterId(encounterId); + suggestion.setPatientId(patientId); + suggestion.setSymptomText(symptomText); + suggestion.setDiagnosisSuggestions(suggestionText); + suggestion.setConfidenceScore(confidence); + suggestion.setSuggestionSource(source != null ? source : "llm"); + suggestion.setAccepted(false); + suggestion.setCreateTime(new Date()); + + aiDiagnosisService.save(suggestion); + + log.info("AI diagnosis suggestion created: id={}, patientId={}", suggestion.getId(), patientId); + + return R.ok(Map.of( + "id", suggestion.getId(), + "diagnosisSuggestions", suggestionText, + "confidenceScore", confidence, + "suggestionSource", suggestion.getSuggestionSource() + )); + } + + @Override + public R getHistory(Long patientId) { + if (patientId == null) { + return R.fail(400, "患者ID不能为空"); + } + List history = aiDiagnosisService.findByPatientId(patientId); + return R.ok(history); + } + + @Override + public R getHistoryByEncounter(Long encounterId) { + if (encounterId == null) { + return R.fail(400, "就诊ID不能为空"); + } + List history = aiDiagnosisService.findByEncounterId(encounterId); + return R.ok(history); + } + + @Override + public R acceptSuggestion(Long id) { + if (id == null) { + return R.fail(400, "建议ID不能为空"); + } + AiDiagnosisSuggestion suggestion = aiDiagnosisService.getById(id); + if (suggestion == null) { + return R.fail(404, "建议不存在"); + } + suggestion.setAccepted(true); + aiDiagnosisService.updateById(suggestion); + return R.ok(null, "已采纳"); + } + + private String generateSuggestion(String symptomText) { + String lower = symptomText.toLowerCase(); + if (lower.contains("发热") || lower.contains("发烧")) { + return "建议排查:1.上呼吸道感染 2.肺炎 3.泌尿系感染 4.其他感染性疾病。建议完善血常规、CRP、PCT等检查。"; + } + if (lower.contains("咳嗽") || lower.contains("咳痰")) { + return "建议排查:1.急性支气管炎 2.肺炎 3.慢性阻塞性肺疾病急性加重。建议完善胸部影像学检查。"; + } + if (lower.contains("头痛")) { + return "建议排查:1.紧张性头痛 2.偏头痛 3.高血压相关头痛 4.颅内病变。建议监测血压,必要时完善头颅CT。"; + } + if (lower.contains("胸痛")) { + return "建议排查:1.心绞痛 2.急性冠脉综合征 3.肺栓塞 4.气胸。建议立即完善心电图、心肌酶谱。"; + } + if (lower.contains("腹痛")) { + return "建议排查:1.急性胃肠炎 2.胆囊炎 3.阑尾炎 4.胰腺炎。建议完善腹部超声、血常规、肝功能检查。"; + } + return "根据症状描述「" + symptomText + "」,建议结合患者病史、体格检查及辅助检查结果综合判断。建议完善相关实验室检查以明确诊断。"; + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/controller/AiDiagnosisController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/controller/AiDiagnosisController.java new file mode 100644 index 000000000..6fc92b312 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/aidiagnosis/controller/AiDiagnosisController.java @@ -0,0 +1,50 @@ +package com.healthlink.his.web.aidiagnosis.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.aidiagnosis.appservice.IAiDiagnosisAppService; +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; + +@Tag(name = "AI辅助诊疗") +@RestController +@RequestMapping("/ai-diagnosis") +public class AiDiagnosisController { + + @Resource + private IAiDiagnosisAppService aiDiagnosisAppService; + + @Operation(summary = "获取AI诊断建议") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @PostMapping("/suggest") + public R suggest(@RequestParam Long encounterId, + @RequestParam Long patientId, + @RequestParam String symptomText, + @RequestParam(required = false, defaultValue = "llm") String source) { + return aiDiagnosisAppService.suggest(encounterId, patientId, symptomText, source); + } + + @Operation(summary = "查询患者AI诊断历史") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @GetMapping("/history/{patientId}") + public R getHistory(@PathVariable Long patientId) { + return aiDiagnosisAppService.getHistory(patientId); + } + + @Operation(summary = "查询就诊AI诊断历史") + @PreAuthorize("@ss.hasPermi('infection:cdss:list')") + @GetMapping("/history/encounter/{encounterId}") + public R getHistoryByEncounter(@PathVariable Long encounterId) { + return aiDiagnosisAppService.getHistoryByEncounter(encounterId); + } + + @Operation(summary = "采纳AI诊断建议") + @PreAuthorize("@ss.hasPermi('infection:cdss:edit')") + @PostMapping("/accept/{id}") + public R acceptSuggestion(@PathVariable Long id) { + return aiDiagnosisAppService.acceptSuggestion(id); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/IDataCollectionAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/IDataCollectionAppService.java new file mode 100644 index 000000000..36642a495 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/IDataCollectionAppService.java @@ -0,0 +1,8 @@ +package com.healthlink.his.web.datacollection.appservice; + +import com.core.common.core.domain.R; + +public interface IDataCollectionAppService { + R collectClinicalData(String startDate, String endDate, Long departmentId); + R collectOperationalData(String startDate, String endDate); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/IDataDashboardAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/IDataDashboardAppService.java new file mode 100644 index 000000000..802d22054 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/IDataDashboardAppService.java @@ -0,0 +1,8 @@ +package com.healthlink.his.web.datacollection.appservice; + +import com.core.common.core.domain.R; + +public interface IDataDashboardAppService { + R getRealtimeData(); + R getHistoricalData(String period); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/impl/DataCollectionAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/impl/DataCollectionAppServiceImpl.java new file mode 100644 index 000000000..a753a80fc --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/impl/DataCollectionAppServiceImpl.java @@ -0,0 +1,78 @@ +package com.healthlink.his.web.datacollection.appservice.impl; + +import com.core.common.core.domain.R; +import com.healthlink.his.quality.service.IBusinessAnalyticsService; +import com.healthlink.his.quality.domain.BusinessAnalytics; +import com.healthlink.his.web.datacollection.appservice.IDataCollectionAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@Slf4j +@AllArgsConstructor +public class DataCollectionAppServiceImpl implements IDataCollectionAppService { + + private final IBusinessAnalyticsService analyticsService; + + @Override + public R collectClinicalData(String startDate, String endDate, Long departmentId) { + Map result = new HashMap<>(); + try { + List allData = analyticsService.list(); + int collected = 0; + for (BusinessAnalytics ba : allData) { + if (departmentId != null && !departmentId.equals(ba.getDepartmentId())) { + continue; + } + if (startDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(startDate) < 0) { + continue; + } + if (endDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(endDate) > 0) { + continue; + } + collected++; + } + result.put("status", "success"); + result.put("recordCount", collected); + result.put("message", "临床数据采集完成,共采集 " + collected + " 条记录"); + log.info("临床数据采集完成: startDate={}, endDate={}, departmentId={}, count={}", startDate, endDate, departmentId, collected); + } catch (Exception e) { + log.error("临床数据采集失败", e); + result.put("status", "error"); + result.put("message", "采集失败: " + e.getMessage()); + return R.fail("采集失败: " + e.getMessage()); + } + return R.ok(result); + } + + @Override + public R collectOperationalData(String startDate, String endDate) { + Map result = new HashMap<>(); + try { + List allData = analyticsService.list(); + int collected = 0; + for (BusinessAnalytics ba : allData) { + if (startDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(startDate) < 0) { + continue; + } + if (endDate != null && ba.getStatDate() != null && ba.getStatDate().compareTo(endDate) > 0) { + continue; + } + collected++; + } + result.put("status", "success"); + result.put("recordCount", collected); + result.put("message", "运营数据采集完成,共采集 " + collected + " 条记录"); + log.info("运营数据采集完成: startDate={}, endDate={}, count={}", startDate, endDate, collected); + } catch (Exception e) { + log.error("运营数据采集失败", e); + result.put("status", "error"); + result.put("message", "采集失败: " + e.getMessage()); + return R.fail("采集失败: " + e.getMessage()); + } + return R.ok(result); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/impl/DataDashboardAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/impl/DataDashboardAppServiceImpl.java new file mode 100644 index 000000000..8bcdf11d0 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/appservice/impl/DataDashboardAppServiceImpl.java @@ -0,0 +1,136 @@ +package com.healthlink.his.web.datacollection.appservice.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.core.common.core.domain.R; +import com.healthlink.his.quality.domain.BusinessAnalytics; +import com.healthlink.his.quality.service.IBusinessAnalyticsService; +import com.healthlink.his.crossmodule.domain.DrgPerformance; +import com.healthlink.his.crossmodule.service.IDrgPerformanceService; +import com.healthlink.his.web.datacollection.appservice.IDataDashboardAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +@AllArgsConstructor +public class DataDashboardAppServiceImpl implements IDataDashboardAppService { + + private final IBusinessAnalyticsService analyticsService; + private final IDrgPerformanceService drgPerformanceService; + + @Override + public R getRealtimeData() { + Map data = new HashMap<>(); + + List allData = analyticsService.list(); + BigDecimal totalRevenue = BigDecimal.ZERO; + BigDecimal totalCost = BigDecimal.ZERO; + int totalPatients = 0; + for (BusinessAnalytics ba : allData) { + if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue()); + if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost()); + if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount(); + } + + data.put("totalRevenue", totalRevenue); + data.put("totalCost", totalCost); + data.put("totalProfit", totalRevenue.subtract(totalCost)); + data.put("totalPatients", totalPatients); + data.put("totalRecords", allData.size()); + data.put("timestamp", new Date().toString()); + + LambdaQueryWrapper perfW = new LambdaQueryWrapper<>(); + perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1"); + List latestPerf = drgPerformanceService.list(perfW); + if (!latestPerf.isEmpty()) { + DrgPerformance p = latestPerf.get(0); + data.put("latestCmiValue", p.getCmiValue()); + data.put("latestCostControlRate", p.getCostControlRate()); + data.put("latestDrgCases", p.getTotalCases()); + } + + Map deptRevenue = allData.stream() + .filter(ba -> ba.getDepartmentName() != null) + .collect(Collectors.groupingBy( + BusinessAnalytics::getDepartmentName, + Collectors.reducing(BigDecimal.ZERO, ba -> ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add))); + List> deptChart = new ArrayList<>(); + deptRevenue.forEach((k, v) -> { + Map item = new HashMap<>(); + item.put("department", k); + item.put("revenue", v); + deptChart.add(item); + }); + data.put("departmentChart", deptChart); + + log.info("实时数据查询完成: records={}", allData.size()); + return R.ok(data); + } + + @Override + public R getHistoricalData(String period) { + Map data = new HashMap<>(); + data.put("period", period); + + List allData = analyticsService.list(); + String cutoffDate = null; + if ("week".equals(period)) { + cutoffDate = java.time.LocalDate.now().minusWeeks(1).toString(); + } else if ("month".equals(period)) { + cutoffDate = java.time.LocalDate.now().minusMonths(1).toString(); + } else if ("quarter".equals(period)) { + cutoffDate = java.time.LocalDate.now().minusMonths(3).toString(); + } else if ("year".equals(period)) { + cutoffDate = java.time.LocalDate.now().minusYears(1).toString(); + } + + if (cutoffDate != null) { + allData = allData.stream() + .filter(ba -> ba.getStatDate() != null && ba.getStatDate().compareTo(cutoffDate) >= 0) + .collect(Collectors.toList()); + } + + BigDecimal totalRevenue = BigDecimal.ZERO; + BigDecimal totalCost = BigDecimal.ZERO; + int totalPatients = 0; + for (BusinessAnalytics ba : allData) { + if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue()); + if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost()); + if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount(); + } + + data.put("totalRevenue", totalRevenue); + data.put("totalCost", totalCost); + data.put("totalProfit", totalRevenue.subtract(totalCost)); + data.put("totalPatients", totalPatients); + data.put("totalRecords", allData.size()); + data.put("cutoffDate", cutoffDate); + data.put("timestamp", new Date().toString()); + + Map monthlyRevenue = new LinkedHashMap<>(); + Map monthlyCost = new LinkedHashMap<>(); + for (BusinessAnalytics ba : allData) { + String month = ba.getStatDate() != null && ba.getStatDate().length() >= 7 + ? ba.getStatDate().substring(0, 7) : "未知"; + monthlyRevenue.merge(month, ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add); + monthlyCost.merge(month, ba.getCost() != null ? ba.getCost() : BigDecimal.ZERO, BigDecimal::add); + } + List> trendChart = new ArrayList<>(); + monthlyRevenue.forEach((k, v) -> { + Map item = new HashMap<>(); + item.put("month", k); + item.put("revenue", v); + item.put("cost", monthlyCost.getOrDefault(k, BigDecimal.ZERO)); + trendChart.add(item); + }); + data.put("trendChart", trendChart); + + log.info("历史数据查询完成: period={}, records={}", period, allData.size()); + return R.ok(data); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/controller/DataCollectionController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/controller/DataCollectionController.java new file mode 100644 index 000000000..48766e360 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/controller/DataCollectionController.java @@ -0,0 +1,34 @@ +package com.healthlink.his.web.datacollection.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.datacollection.appservice.IDataCollectionAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@AllArgsConstructor +@RestController +@RequestMapping("/data/collect") +@Slf4j +public class DataCollectionController { + + private final IDataCollectionAppService dataCollectionAppService; + + @PostMapping("/clinical") + @PreAuthorize("hasAuthority('reportmanage:report:edit')") + public R collectClinicalData( + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false) Long departmentId) { + return dataCollectionAppService.collectClinicalData(startDate, endDate, departmentId); + } + + @PostMapping("/operational") + @PreAuthorize("hasAuthority('reportmanage:report:edit')") + public R collectOperationalData( + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { + return dataCollectionAppService.collectOperationalData(startDate, endDate); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/controller/DataDashboardController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/controller/DataDashboardController.java new file mode 100644 index 000000000..b4c5511a7 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/controller/DataDashboardController.java @@ -0,0 +1,30 @@ +package com.healthlink.his.web.datacollection.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.datacollection.appservice.IDataDashboardAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@AllArgsConstructor +@RestController +@RequestMapping("/data/dashboard") +@Slf4j +public class DataDashboardController { + + private final IDataDashboardAppService dataDashboardAppService; + + @GetMapping("/realtime") + @PreAuthorize("hasAuthority('reportmanage:report:list')") + public R getRealtimeData() { + return dataDashboardAppService.getRealtimeData(); + } + + @GetMapping("/historical") + @PreAuthorize("hasAuthority('reportmanage:report:list')") + public R getHistoricalData( + @RequestParam(required = false, defaultValue = "month") String period) { + return dataDashboardAppService.getHistoricalData(period); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/dto/DataCollectionDto.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/dto/DataCollectionDto.java new file mode 100644 index 000000000..3887f6b46 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/datacollection/dto/DataCollectionDto.java @@ -0,0 +1,16 @@ +package com.healthlink.his.web.datacollection.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class DataCollectionDto { + private String collectionType; + private String startDate; + private String endDate; + private Long departmentId; + private Integer recordCount; + private String status; + private String message; +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBiReportAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBiReportAppService.java new file mode 100644 index 000000000..3e7cf5662 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBiReportAppService.java @@ -0,0 +1,9 @@ +package com.healthlink.his.web.reportmanage.appservice; + +import com.core.common.core.domain.R; +import java.util.Map; + +public interface IBiReportAppService { + R generateBiReport(String type, Map filters); + R getReportDashboard(); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BiReportAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BiReportAppServiceImpl.java new file mode 100644 index 000000000..48bd7d31a --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BiReportAppServiceImpl.java @@ -0,0 +1,181 @@ +package com.healthlink.his.web.reportmanage.appservice.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.core.common.core.domain.R; +import com.healthlink.his.quality.domain.BusinessAnalytics; +import com.healthlink.his.quality.service.IBusinessAnalyticsService; +import com.healthlink.his.crossmodule.domain.DrgPerformance; +import com.healthlink.his.crossmodule.service.IDrgPerformanceService; +import com.healthlink.his.web.reportmanage.appservice.IBiReportAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +@AllArgsConstructor +public class BiReportAppServiceImpl implements IBiReportAppService { + + private final IBusinessAnalyticsService analyticsService; + private final IDrgPerformanceService drgPerformanceService; + + @Override + public R generateBiReport(String type, Map filters) { + Map result = new HashMap<>(); + result.put("reportType", type); + result.put("generatedAt", new Date().toString()); + + List allData = analyticsService.list(); + + if (filters != null && filters.containsKey("departmentId")) { + Long deptId = Long.valueOf(filters.get("departmentId").toString()); + allData = allData.stream() + .filter(ba -> deptId.equals(ba.getDepartmentId())) + .collect(Collectors.toList()); + } + if (filters != null && filters.containsKey("startDate")) { + String start = filters.get("startDate").toString(); + allData = allData.stream() + .filter(ba -> ba.getStatDate() != null && ba.getStatDate().compareTo(start) >= 0) + .collect(Collectors.toList()); + } + if (filters != null && filters.containsKey("endDate")) { + String end = filters.get("endDate").toString(); + allData = allData.stream() + .filter(ba -> ba.getStatDate() != null && ba.getStatDate().compareTo(end) <= 0) + .collect(Collectors.toList()); + } + + Map summary = new HashMap<>(); + BigDecimal totalRevenue = BigDecimal.ZERO; + BigDecimal totalCost = BigDecimal.ZERO; + int totalPatients = 0; + for (BusinessAnalytics ba : allData) { + if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue()); + if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost()); + if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount(); + } + summary.put("totalRevenue", totalRevenue); + summary.put("totalCost", totalCost); + summary.put("totalProfit", totalRevenue.subtract(totalCost)); + summary.put("totalPatients", totalPatients); + summary.put("totalRecords", allData.size()); + BigDecimal profitRate = totalRevenue.compareTo(BigDecimal.ZERO) > 0 + ? totalRevenue.subtract(totalCost).multiply(new BigDecimal("100")).divide(totalRevenue, 2, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + summary.put("profitRate", profitRate); + result.put("summary", summary); + + Map charts = new HashMap<>(); + + if ("revenue".equals(type) || "overview".equals(type)) { + Map monthlyRevenue = new LinkedHashMap<>(); + Map monthlyCost = new LinkedHashMap<>(); + for (BusinessAnalytics ba : allData) { + String month = ba.getStatDate() != null && ba.getStatDate().length() >= 7 + ? ba.getStatDate().substring(0, 7) : "未知"; + monthlyRevenue.merge(month, ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add); + monthlyCost.merge(month, ba.getCost() != null ? ba.getCost() : BigDecimal.ZERO, BigDecimal::add); + } + List> revenueChart = new ArrayList<>(); + monthlyRevenue.forEach((k, v) -> { + Map item = new HashMap<>(); + item.put("month", k); + item.put("revenue", v); + item.put("cost", monthlyCost.getOrDefault(k, BigDecimal.ZERO)); + revenueChart.add(item); + }); + charts.put("revenueChart", revenueChart); + } + + if ("department".equals(type) || "overview".equals(type)) { + Map deptRevenue = allData.stream() + .filter(ba -> ba.getDepartmentName() != null) + .collect(Collectors.groupingBy( + BusinessAnalytics::getDepartmentName, + Collectors.reducing(BigDecimal.ZERO, ba -> ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add))); + List> deptChart = new ArrayList<>(); + deptRevenue.forEach((k, v) -> { + Map item = new HashMap<>(); + item.put("department", k); + item.put("revenue", v); + deptChart.add(item); + }); + charts.put("departmentChart", deptChart); + } + + if ("drg".equals(type) || "overview".equals(type)) { + LambdaQueryWrapper perfW = new LambdaQueryWrapper<>(); + perfW.orderByAsc(DrgPerformance::getStatMonth); + List perfList = drgPerformanceService.list(perfW); + List> cmiChart = new ArrayList<>(); + for (DrgPerformance p : perfList) { + Map item = new HashMap<>(); + item.put("month", p.getStatMonth()); + item.put("cmiValue", p.getCmiValue()); + item.put("costControlRate", p.getCostControlRate()); + item.put("totalCases", p.getTotalCases()); + cmiChart.add(item); + } + charts.put("drgChart", cmiChart); + } + + result.put("charts", charts); + result.put("records", allData.stream().limit(100).map(ba -> { + Map row = new HashMap<>(); + row.put("statDate", ba.getStatDate()); + row.put("departmentName", ba.getDepartmentName()); + row.put("revenue", ba.getRevenue()); + row.put("cost", ba.getCost()); + row.put("patientCount", ba.getPatientCount()); + return row; + }).collect(Collectors.toList())); + + log.info("BI报表生成完成: type={}, records={}", type, allData.size()); + return R.ok(result); + } + + @Override + public R getReportDashboard() { + Map dashboard = new HashMap<>(); + + List allData = analyticsService.list(); + BigDecimal totalRevenue = BigDecimal.ZERO; + BigDecimal totalCost = BigDecimal.ZERO; + int totalPatients = 0; + for (BusinessAnalytics ba : allData) { + if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue()); + if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost()); + if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount(); + } + + dashboard.put("totalRevenue", totalRevenue); + dashboard.put("totalCost", totalCost); + dashboard.put("totalProfit", totalRevenue.subtract(totalCost)); + dashboard.put("totalPatients", totalPatients); + dashboard.put("totalRecords", allData.size()); + dashboard.put("reportTypes", Arrays.asList( + Map.of("value", "overview", "label", "综合概览"), + Map.of("value", "revenue", "label", "收入分析"), + Map.of("value", "department", "label", "科室分析"), + Map.of("value", "drg", "label", "DRG分析") + )); + + LambdaQueryWrapper perfW = new LambdaQueryWrapper<>(); + perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1"); + List latestPerf = drgPerformanceService.list(perfW); + if (!latestPerf.isEmpty()) { + DrgPerformance p = latestPerf.get(0); + dashboard.put("latestCmiValue", p.getCmiValue()); + dashboard.put("latestCostControlRate", p.getCostControlRate()); + dashboard.put("latestDrgCases", p.getTotalCases()); + } + + return R.ok(dashboard); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BiReportController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BiReportController.java new file mode 100644 index 000000000..a7d3dbcd0 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BiReportController.java @@ -0,0 +1,33 @@ +package com.healthlink.his.web.reportmanage.controller; + +import com.core.common.core.domain.R; +import com.healthlink.his.web.reportmanage.appservice.IBiReportAppService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@AllArgsConstructor +@RestController("reportBiReportController") +@RequestMapping("/report/bi") +@Slf4j +public class BiReportController { + + private final IBiReportAppService biReportAppService; + + @PostMapping("/generate") + @PreAuthorize("hasAuthority('reportmanage:report:edit')") + public R generateBiReport( + @RequestParam(required = false, defaultValue = "overview") String type, + @RequestBody(required = false) Map filters) { + return biReportAppService.generateBiReport(type, filters); + } + + @GetMapping("/dashboard") + @PreAuthorize("hasAuthority('reportmanage:report:list')") + public R getReportDashboard() { + return biReportAppService.getReportDashboard(); + } +}