feat(data): 数据中台+BI报表+AI辅助诊疗

This commit is contained in:
2026-06-19 07:02:44 +08:00
parent 8b2b47b71c
commit 8bc80efe2c
13 changed files with 709 additions and 0 deletions

View File

@@ -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);
}

View File

@@ -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 + "」,建议结合患者病史、体格检查及辅助检查结果综合判断。建议完善相关实验室检查以明确诊断。";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}