feat(data): 数据中台+BI报表+AI辅助诊疗
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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<AiDiagnosisSuggestion> history = aiDiagnosisService.findByPatientId(patientId);
|
||||
return R.ok(history);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> getHistoryByEncounter(Long encounterId) {
|
||||
if (encounterId == null) {
|
||||
return R.fail(400, "就诊ID不能为空");
|
||||
}
|
||||
List<AiDiagnosisSuggestion> 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 + "」,建议结合患者病史、体格检查及辅助检查结果综合判断。建议完善相关实验室检查以明确诊断。";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
List<BusinessAnalytics> 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<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
List<BusinessAnalytics> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> data = new HashMap<>();
|
||||
|
||||
List<BusinessAnalytics> 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<DrgPerformance> perfW = new LambdaQueryWrapper<>();
|
||||
perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1");
|
||||
List<DrgPerformance> 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<String, BigDecimal> 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<Map<String, Object>> deptChart = new ArrayList<>();
|
||||
deptRevenue.forEach((k, v) -> {
|
||||
Map<String, Object> 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<String, Object> data = new HashMap<>();
|
||||
data.put("period", period);
|
||||
|
||||
List<BusinessAnalytics> 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<String, BigDecimal> monthlyRevenue = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> 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<Map<String, Object>> trendChart = new ArrayList<>();
|
||||
monthlyRevenue.forEach((k, v) -> {
|
||||
Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<String, Object> filters);
|
||||
R<?> getReportDashboard();
|
||||
}
|
||||
@@ -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<String, Object> filters) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("reportType", type);
|
||||
result.put("generatedAt", new Date().toString());
|
||||
|
||||
List<BusinessAnalytics> 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<String, Object> 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<String, Object> charts = new HashMap<>();
|
||||
|
||||
if ("revenue".equals(type) || "overview".equals(type)) {
|
||||
Map<String, BigDecimal> monthlyRevenue = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> 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<Map<String, Object>> revenueChart = new ArrayList<>();
|
||||
monthlyRevenue.forEach((k, v) -> {
|
||||
Map<String, Object> 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<String, BigDecimal> 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<Map<String, Object>> deptChart = new ArrayList<>();
|
||||
deptRevenue.forEach((k, v) -> {
|
||||
Map<String, Object> 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<DrgPerformance> perfW = new LambdaQueryWrapper<>();
|
||||
perfW.orderByAsc(DrgPerformance::getStatMonth);
|
||||
List<DrgPerformance> perfList = drgPerformanceService.list(perfW);
|
||||
List<Map<String, Object>> cmiChart = new ArrayList<>();
|
||||
for (DrgPerformance p : perfList) {
|
||||
Map<String, Object> 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<String, Object> 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<String, Object> dashboard = new HashMap<>();
|
||||
|
||||
List<BusinessAnalytics> 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<DrgPerformance> perfW = new LambdaQueryWrapper<>();
|
||||
perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1");
|
||||
List<DrgPerformance> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> filters) {
|
||||
return biReportAppService.generateBiReport(type, filters);
|
||||
}
|
||||
|
||||
@GetMapping("/dashboard")
|
||||
@PreAuthorize("hasAuthority('reportmanage:report:list')")
|
||||
public R<?> getReportDashboard() {
|
||||
return biReportAppService.getReportDashboard();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user