feat(health): 新增依从性统计功能
- 新增 MedicationAdherenceController:统计API接口 - 新增 IMedicationAdherenceService:统计服务接口 - 新增 MedicationAdherenceServiceImpl:统计服务实现 - 新增 MedicationAdherenceVo/DailyAdherenceVo:统计VO - 扩展 HealthMedicationRecordMapper:新增统计SQL - 新增菜单和统计汇总表SQL
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package com.intc.health.controller;
|
||||
|
||||
import com.intc.common.core.web.domain.AjaxResult;
|
||||
import com.intc.health.domain.dto.MedicationAdherenceDto;
|
||||
import com.intc.health.domain.vo.DailyAdherenceVo;
|
||||
import com.intc.health.domain.vo.MedicationAdherenceVo;
|
||||
import com.intc.health.service.IMedicationAdherenceService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 依从性统计Controller
|
||||
*
|
||||
* @author bot5
|
||||
* @date 2026-03-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/health/adherence")
|
||||
@Api(tags = "依从性统计")
|
||||
public class MedicationAdherenceController {
|
||||
|
||||
@Resource
|
||||
private IMedicationAdherenceService adherenceService;
|
||||
|
||||
/**
|
||||
* 获取总体依从性统计
|
||||
*/
|
||||
@GetMapping("/overall")
|
||||
@ApiOperation("获取总体依从性统计")
|
||||
public AjaxResult getOverallAdherence(MedicationAdherenceDto dto) {
|
||||
MedicationAdherenceVo vo = adherenceService.getOverallAdherence(dto);
|
||||
return AjaxResult.success(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每日依从性统计列表(趋势图)
|
||||
*/
|
||||
@GetMapping("/daily")
|
||||
@ApiOperation("获取每日依从性统计列表")
|
||||
public AjaxResult getDailyAdherenceList(MedicationAdherenceDto dto) {
|
||||
List<DailyAdherenceVo> list = adherenceService.getDailyAdherenceList(dto);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日历视图数据
|
||||
*/
|
||||
@GetMapping("/calendar")
|
||||
@ApiOperation("获取日历视图数据")
|
||||
public AjaxResult getCalendarData(
|
||||
@RequestParam(required = false) Long personId,
|
||||
@RequestParam String yearMonth) {
|
||||
List<DailyAdherenceVo> list = adherenceService.getCalendarData(personId, yearMonth);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取各时段服药统计
|
||||
*/
|
||||
@GetMapping("/timeSlot")
|
||||
@ApiOperation("获取各时段服药统计")
|
||||
public AjaxResult getTimeSlotAdherence(MedicationAdherenceDto dto) {
|
||||
Map<String, MedicationAdherenceVo> map = adherenceService.getTimeSlotAdherence(dto);
|
||||
return AjaxResult.success(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取各药品依从性统计
|
||||
*/
|
||||
@GetMapping("/medicine")
|
||||
@ApiOperation("获取各药品依从性统计")
|
||||
public AjaxResult getMedicineAdherenceList(MedicationAdherenceDto dto) {
|
||||
List<MedicationAdherenceVo> list = adherenceService.getMedicineAdherenceList(dto);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取各人员依从性统计
|
||||
*/
|
||||
@GetMapping("/person")
|
||||
@ApiOperation("获取各人员依从性统计")
|
||||
public AjaxResult getPersonAdherenceList(MedicationAdherenceDto dto) {
|
||||
List<MedicationAdherenceVo> list = adherenceService.getPersonAdherenceList(dto);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取漏服原因统计
|
||||
*/
|
||||
@GetMapping("/missedReason")
|
||||
@ApiOperation("获取漏服原因统计")
|
||||
public AjaxResult getMissedReasonStats(MedicationAdherenceDto dto) {
|
||||
Map<String, Integer> map = adherenceService.getMissedReasonStats(dto);
|
||||
return AjaxResult.success(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取仪表盘概览数据
|
||||
*/
|
||||
@GetMapping("/dashboard")
|
||||
@ApiOperation("获取仪表盘概览数据")
|
||||
public AjaxResult getDashboard(@RequestParam(required = false) Long personId) {
|
||||
// 获取最近7天的统计数据
|
||||
MedicationAdherenceDto dto = new MedicationAdherenceDto();
|
||||
dto.setPersonId(personId);
|
||||
dto.setStartDate(java.time.LocalDate.now().minusDays(7).toString());
|
||||
dto.setEndDate(java.time.LocalDate.now().toString());
|
||||
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
|
||||
// 总体统计
|
||||
result.put("overall", adherenceService.getOverallAdherence(dto));
|
||||
|
||||
// 每日趋势
|
||||
result.put("dailyTrend", adherenceService.getDailyAdherenceList(dto));
|
||||
|
||||
// 时段统计
|
||||
result.put("timeSlot", adherenceService.getTimeSlotAdherence(dto));
|
||||
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.intc.health.domain.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 依从性统计查询DTO
|
||||
*
|
||||
* @author bot5
|
||||
* @date 2026-03-19
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("依从性统计查询DTO")
|
||||
public class MedicationAdherenceDto {
|
||||
|
||||
@ApiModelProperty("人员ID")
|
||||
private Long personId;
|
||||
|
||||
@ApiModelProperty("计划ID")
|
||||
private Long planId;
|
||||
|
||||
@ApiModelProperty("开始日期(yyyy-MM-dd)")
|
||||
private String startDate;
|
||||
|
||||
@ApiModelProperty("结束日期(yyyy-MM-dd)")
|
||||
private String endDate;
|
||||
|
||||
@ApiModelProperty("统计类型:day-日,week-周,month-月,quarter-季度")
|
||||
private String statType;
|
||||
|
||||
@ApiModelProperty("药品ID")
|
||||
private Long medicineId;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.intc.health.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 每日依从性统计VO
|
||||
*
|
||||
* @author bot5
|
||||
* @date 2026-03-19
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("每日依从性统计VO")
|
||||
public class DailyAdherenceVo {
|
||||
|
||||
@ApiModelProperty("日期")
|
||||
private String date;
|
||||
|
||||
@ApiModelProperty("总任务数")
|
||||
private Integer totalTasks;
|
||||
|
||||
@ApiModelProperty("已服药数")
|
||||
private Integer completedTasks;
|
||||
|
||||
@ApiModelProperty("漏服数")
|
||||
private Integer missedTasks;
|
||||
|
||||
@ApiModelProperty("跳过数")
|
||||
private Integer skippedTasks;
|
||||
|
||||
@ApiModelProperty("准时服药数")
|
||||
private Integer ontimeTasks;
|
||||
|
||||
@ApiModelProperty("服药率(%)")
|
||||
private BigDecimal adherenceRate;
|
||||
|
||||
@ApiModelProperty("准时率(%)")
|
||||
private BigDecimal ontimeRate;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.intc.health.domain.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 依从性统计VO
|
||||
*
|
||||
* @author bot5
|
||||
* @date 2026-03-19
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("依从性统计VO")
|
||||
public class MedicationAdherenceVo {
|
||||
|
||||
@ApiModelProperty("总任务数")
|
||||
private Integer totalTasks;
|
||||
|
||||
@ApiModelProperty("已服药数")
|
||||
private Integer completedTasks;
|
||||
|
||||
@ApiModelProperty("漏服数")
|
||||
private Integer missedTasks;
|
||||
|
||||
@ApiModelProperty("跳过数")
|
||||
private Integer skippedTasks;
|
||||
|
||||
@ApiModelProperty("待服用数")
|
||||
private Integer pendingTasks;
|
||||
|
||||
@ApiModelProperty("准时服药数")
|
||||
private Integer ontimeTasks;
|
||||
|
||||
@ApiModelProperty("服药率(%)")
|
||||
private BigDecimal adherenceRate;
|
||||
|
||||
@ApiModelProperty("准时率(%)")
|
||||
private BigDecimal ontimeRate;
|
||||
|
||||
@ApiModelProperty("统计开始日期")
|
||||
private String startDate;
|
||||
|
||||
@ApiModelProperty("统计结束日期")
|
||||
private String endDate;
|
||||
|
||||
@ApiModelProperty("人员ID")
|
||||
private Long personId;
|
||||
|
||||
@ApiModelProperty("人员名称")
|
||||
private String personName;
|
||||
|
||||
@ApiModelProperty("计划ID")
|
||||
private Long planId;
|
||||
|
||||
@ApiModelProperty("计划名称")
|
||||
private String planName;
|
||||
}
|
||||
@@ -129,4 +129,54 @@ public interface HealthMedicationRecordMapper
|
||||
* @return 服药记录集合
|
||||
*/
|
||||
public List<HealthMedicationRecordVo> selectPendingRemindRecords(@Param("startTime") java.time.LocalDateTime startTime, @Param("endTime") java.time.LocalDateTime endTime);
|
||||
|
||||
// ========== 依从性统计相关 ==========
|
||||
|
||||
/**
|
||||
* 获取总体依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 统计结果Map
|
||||
*/
|
||||
public java.util.Map<String, Object> getAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取每日依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 每日统计列表
|
||||
*/
|
||||
public java.util.List<java.util.Map<String, Object>> getDailyAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取时段依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 时段统计列表
|
||||
*/
|
||||
public java.util.List<java.util.Map<String, Object>> getTimeSlotAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取药品依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 药品统计列表
|
||||
*/
|
||||
public java.util.List<java.util.Map<String, Object>> getMedicineAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取人员依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 人员统计列表
|
||||
*/
|
||||
public java.util.List<java.util.Map<String, Object>> getPersonAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取漏服原因统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 原因统计列表
|
||||
*/
|
||||
public java.util.List<java.util.Map<String, Object>> getMissedReasonStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.intc.health.service;
|
||||
|
||||
import com.intc.health.domain.dto.MedicationAdherenceDto;
|
||||
import com.intc.health.domain.vo.DailyAdherenceVo;
|
||||
import com.intc.health.domain.vo.MedicationAdherenceVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 依从性统计Service接口
|
||||
*
|
||||
* @author bot5
|
||||
* @date 2026-03-19
|
||||
*/
|
||||
public interface IMedicationAdherenceService {
|
||||
|
||||
/**
|
||||
* 获取总体依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 统计结果
|
||||
*/
|
||||
MedicationAdherenceVo getOverallAdherence(MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取每日依从性统计列表(用于趋势图)
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 每日统计列表
|
||||
*/
|
||||
List<DailyAdherenceVo> getDailyAdherenceList(MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取日历视图数据(某月的每日统计)
|
||||
*
|
||||
* @param personId 人员ID
|
||||
* @param yearMonth 年月(yyyy-MM)
|
||||
* @return 每日统计列表
|
||||
*/
|
||||
List<DailyAdherenceVo> getCalendarData(Long personId, String yearMonth);
|
||||
|
||||
/**
|
||||
* 获取各时段服药统计(早中晚)
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 时段统计Map
|
||||
*/
|
||||
Map<String, MedicationAdherenceVo> getTimeSlotAdherence(MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取各药品依从性统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 药品统计列表
|
||||
*/
|
||||
List<MedicationAdherenceVo> getMedicineAdherenceList(MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取各人员依从性统计(家属视角)
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 人员统计列表
|
||||
*/
|
||||
List<MedicationAdherenceVo> getPersonAdherenceList(MedicationAdherenceDto dto);
|
||||
|
||||
/**
|
||||
* 获取漏服原因统计
|
||||
*
|
||||
* @param dto 查询参数
|
||||
* @return 原因统计Map
|
||||
*/
|
||||
Map<String, Integer> getMissedReasonStats(MedicationAdherenceDto dto);
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package com.intc.health.service.impl;
|
||||
|
||||
import com.intc.health.domain.dto.MedicationAdherenceDto;
|
||||
import com.intc.health.domain.vo.DailyAdherenceVo;
|
||||
import com.intc.health.domain.vo.MedicationAdherenceVo;
|
||||
import com.intc.health.mapper.HealthMedicationRecordMapper;
|
||||
import com.intc.health.service.IMedicationAdherenceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 依从性统计Service业务层处理
|
||||
*
|
||||
* @author bot5
|
||||
* @date 2026-03-19
|
||||
*/
|
||||
@Service
|
||||
public class MedicationAdherenceServiceImpl implements IMedicationAdherenceService {
|
||||
|
||||
@Resource
|
||||
private HealthMedicationRecordMapper recordMapper;
|
||||
|
||||
@Override
|
||||
public MedicationAdherenceVo getOverallAdherence(MedicationAdherenceDto dto) {
|
||||
Map<String, Object> stats = recordMapper.getAdherenceStats(dto);
|
||||
|
||||
MedicationAdherenceVo vo = new MedicationAdherenceVo();
|
||||
vo.setTotalTasks(getIntValue(stats, "total_tasks"));
|
||||
vo.setCompletedTasks(getIntValue(stats, "completed_tasks"));
|
||||
vo.setMissedTasks(getIntValue(stats, "missed_tasks"));
|
||||
vo.setSkippedTasks(getIntValue(stats, "skipped_tasks"));
|
||||
vo.setPendingTasks(getIntValue(stats, "pending_tasks"));
|
||||
vo.setOntimeTasks(getIntValue(stats, "ontime_tasks"));
|
||||
|
||||
// 计算服药率
|
||||
if (vo.getTotalTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setAdherenceRate(rate);
|
||||
} else {
|
||||
vo.setAdherenceRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算准时率
|
||||
if (vo.getCompletedTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getOntimeTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getCompletedTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setOntimeRate(rate);
|
||||
} else {
|
||||
vo.setOntimeRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
vo.setStartDate(dto.getStartDate());
|
||||
vo.setEndDate(dto.getEndDate());
|
||||
vo.setPersonId(dto.getPersonId());
|
||||
vo.setPlanId(dto.getPlanId());
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DailyAdherenceVo> getDailyAdherenceList(MedicationAdherenceDto dto) {
|
||||
List<Map<String, Object>> dailyStats = recordMapper.getDailyAdherenceStats(dto);
|
||||
|
||||
List<DailyAdherenceVo> result = new ArrayList<>();
|
||||
for (Map<String, Object> stat : dailyStats) {
|
||||
DailyAdherenceVo vo = new DailyAdherenceVo();
|
||||
vo.setDate(getStringValue(stat, "stat_date"));
|
||||
vo.setTotalTasks(getIntValue(stat, "total_tasks"));
|
||||
vo.setCompletedTasks(getIntValue(stat, "completed_tasks"));
|
||||
vo.setMissedTasks(getIntValue(stat, "missed_tasks"));
|
||||
vo.setSkippedTasks(getIntValue(stat, "skipped_tasks"));
|
||||
vo.setOntimeTasks(getIntValue(stat, "ontime_tasks"));
|
||||
|
||||
// 计算服药率
|
||||
if (vo.getTotalTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setAdherenceRate(rate);
|
||||
} else {
|
||||
vo.setAdherenceRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算准时率
|
||||
if (vo.getCompletedTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getOntimeTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getCompletedTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setOntimeRate(rate);
|
||||
} else {
|
||||
vo.setOntimeRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
result.add(vo);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DailyAdherenceVo> getCalendarData(Long personId, String yearMonth) {
|
||||
// 解析年月
|
||||
YearMonth ym = YearMonth.parse(yearMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||
LocalDate startDate = ym.atDay(1);
|
||||
LocalDate endDate = ym.atEndOfMonth();
|
||||
|
||||
MedicationAdherenceDto dto = new MedicationAdherenceDto();
|
||||
dto.setPersonId(personId);
|
||||
dto.setStartDate(startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
dto.setEndDate(endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
|
||||
List<DailyAdherenceVo> dailyList = getDailyAdherenceList(dto);
|
||||
|
||||
// 补全缺失的日期
|
||||
List<DailyAdherenceVo> result = new ArrayList<>();
|
||||
Map<String, DailyAdherenceVo> dateMap = new HashMap<>();
|
||||
for (DailyAdherenceVo vo : dailyList) {
|
||||
dateMap.put(vo.getDate(), vo);
|
||||
}
|
||||
|
||||
long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
|
||||
for (int i = 0; i < days; i++) {
|
||||
LocalDate date = startDate.plusDays(i);
|
||||
String dateStr = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
|
||||
if (dateMap.containsKey(dateStr)) {
|
||||
result.add(dateMap.get(dateStr));
|
||||
} else {
|
||||
// 没有数据的日期补零
|
||||
DailyAdherenceVo vo = new DailyAdherenceVo();
|
||||
vo.setDate(dateStr);
|
||||
vo.setTotalTasks(0);
|
||||
vo.setCompletedTasks(0);
|
||||
vo.setMissedTasks(0);
|
||||
vo.setSkippedTasks(0);
|
||||
vo.setOntimeTasks(0);
|
||||
vo.setAdherenceRate(BigDecimal.ZERO);
|
||||
vo.setOntimeRate(BigDecimal.ZERO);
|
||||
result.add(vo);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, MedicationAdherenceVo> getTimeSlotAdherence(MedicationAdherenceDto dto) {
|
||||
List<Map<String, Object>> slotStats = recordMapper.getTimeSlotAdherenceStats(dto);
|
||||
|
||||
Map<String, MedicationAdherenceVo> result = new LinkedHashMap<>();
|
||||
result.put("morning", new MedicationAdherenceVo()); // 6:00-12:00
|
||||
result.put("afternoon", new MedicationAdherenceVo()); // 12:00-18:00
|
||||
result.put("evening", new MedicationAdherenceVo()); // 18:00-24:00
|
||||
result.put("night", new MedicationAdherenceVo()); // 0:00-6:00
|
||||
|
||||
for (Map<String, Object> stat : slotStats) {
|
||||
int hour = getIntValue(stat, "hour");
|
||||
String slotKey = getTimeSlot(hour);
|
||||
|
||||
MedicationAdherenceVo vo = result.get(slotKey);
|
||||
if (vo.getTotalTasks() == null) {
|
||||
vo.setTotalTasks(0);
|
||||
vo.setCompletedTasks(0);
|
||||
vo.setMissedTasks(0);
|
||||
vo.setOntimeTasks(0);
|
||||
}
|
||||
|
||||
vo.setTotalTasks(vo.getTotalTasks() + getIntValue(stat, "total_tasks"));
|
||||
vo.setCompletedTasks(vo.getCompletedTasks() + getIntValue(stat, "completed_tasks"));
|
||||
vo.setMissedTasks(vo.getMissedTasks() + getIntValue(stat, "missed_tasks"));
|
||||
vo.setOntimeTasks(vo.getOntimeTasks() + getIntValue(stat, "ontime_tasks"));
|
||||
}
|
||||
|
||||
// 计算各时段的服药率
|
||||
for (Map.Entry<String, MedicationAdherenceVo> entry : result.entrySet()) {
|
||||
MedicationAdherenceVo vo = entry.getValue();
|
||||
if (vo.getTotalTasks() != null && vo.getTotalTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setAdherenceRate(rate);
|
||||
} else {
|
||||
vo.setAdherenceRate(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MedicationAdherenceVo> getMedicineAdherenceList(MedicationAdherenceDto dto) {
|
||||
List<Map<String, Object>> medicineStats = recordMapper.getMedicineAdherenceStats(dto);
|
||||
|
||||
List<MedicationAdherenceVo> result = new ArrayList<>();
|
||||
for (Map<String, Object> stat : medicineStats) {
|
||||
MedicationAdherenceVo vo = new MedicationAdherenceVo();
|
||||
vo.setTotalTasks(getIntValue(stat, "total_tasks"));
|
||||
vo.setCompletedTasks(getIntValue(stat, "completed_tasks"));
|
||||
vo.setMissedTasks(getIntValue(stat, "missed_tasks"));
|
||||
vo.setOntimeTasks(getIntValue(stat, "ontime_tasks"));
|
||||
|
||||
// 药品信息
|
||||
// planId 和 planName 需要从查询结果获取
|
||||
|
||||
// 计算服药率
|
||||
if (vo.getTotalTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setAdherenceRate(rate);
|
||||
} else {
|
||||
vo.setAdherenceRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
result.add(vo);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MedicationAdherenceVo> getPersonAdherenceList(MedicationAdherenceDto dto) {
|
||||
List<Map<String, Object>> personStats = recordMapper.getPersonAdherenceStats(dto);
|
||||
|
||||
List<MedicationAdherenceVo> result = new ArrayList<>();
|
||||
for (Map<String, Object> stat : personStats) {
|
||||
MedicationAdherenceVo vo = new MedicationAdherenceVo();
|
||||
vo.setPersonId(getLongValue(stat, "person_id"));
|
||||
vo.setPersonName(getStringValue(stat, "person_name"));
|
||||
vo.setTotalTasks(getIntValue(stat, "total_tasks"));
|
||||
vo.setCompletedTasks(getIntValue(stat, "completed_tasks"));
|
||||
vo.setMissedTasks(getIntValue(stat, "missed_tasks"));
|
||||
vo.setOntimeTasks(getIntValue(stat, "ontime_tasks"));
|
||||
|
||||
// 计算服药率
|
||||
if (vo.getTotalTasks() > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks())
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP);
|
||||
vo.setAdherenceRate(rate);
|
||||
} else {
|
||||
vo.setAdherenceRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
result.add(vo);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Integer> getMissedReasonStats(MedicationAdherenceDto dto) {
|
||||
List<Map<String, Object>> reasonStats = recordMapper.getMissedReasonStats(dto);
|
||||
|
||||
Map<String, Integer> result = new LinkedHashMap<>();
|
||||
for (Map<String, Object> stat : reasonStats) {
|
||||
String reason = getStringValue(stat, "remark");
|
||||
if (reason == null || reason.isEmpty()) {
|
||||
reason = "未填写原因";
|
||||
}
|
||||
int count = getIntValue(stat, "count");
|
||||
result.merge(reason, count, Integer::sum);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
private String getTimeSlot(int hour) {
|
||||
if (hour >= 6 && hour < 12) {
|
||||
return "morning";
|
||||
} else if (hour >= 12 && hour < 18) {
|
||||
return "afternoon";
|
||||
} else if (hour >= 18 && hour < 24) {
|
||||
return "evening";
|
||||
} else {
|
||||
return "night";
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getIntValue(Map<String, Object> map, String key) {
|
||||
if (map == null || map.get(key) == null) {
|
||||
return 0;
|
||||
}
|
||||
Object value = map.get(key);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Long getLongValue(Map<String, Object> map, String key) {
|
||||
if (map == null || map.get(key) == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = map.get(key);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getStringValue(Map<String, Object> map, String key) {
|
||||
if (map == null || map.get(key) == null) {
|
||||
return null;
|
||||
}
|
||||
return map.get(key).toString();
|
||||
}
|
||||
}
|
||||
@@ -190,4 +190,121 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and a.scheduled_time <= #{endTime}
|
||||
order by a.scheduled_time asc
|
||||
</select>
|
||||
|
||||
<!-- ========== 依从性统计相关 ========== -->
|
||||
|
||||
<!-- 总体依从性统计 -->
|
||||
<select id="getAdherenceStats" resultType="java.util.Map">
|
||||
select
|
||||
count(1) as total_tasks,
|
||||
sum(case when a.status = '2' then 1 else 0 end) as completed_tasks,
|
||||
sum(case when a.status = '4' then 1 else 0 end) as missed_tasks,
|
||||
sum(case when a.status = '3' then 1 else 0 end) as skipped_tasks,
|
||||
sum(case when a.status = '1' then 1 else 0 end) as pending_tasks,
|
||||
sum(case when a.status = '2' and a.actual_time is not null
|
||||
and extract(epoch from (a.actual_time - a.scheduled_time))/3600 <= 1 then 1 else 0 end) as ontime_tasks
|
||||
from health_medication_record a
|
||||
where a.del_flag = '0'
|
||||
<if test="dto.personId != null"> and a.person_id = #{dto.personId}</if>
|
||||
<if test="dto.planId != null"> and a.plan_id = #{dto.planId}</if>
|
||||
<if test="dto.medicineId != null"> and a.medicine_id = #{dto.medicineId}</if>
|
||||
<if test="dto.startDate != null"> and date(a.scheduled_time) >= #{dto.startDate}</if>
|
||||
<if test="dto.endDate != null"> and date(a.scheduled_time) <= #{dto.endDate}</if>
|
||||
</select>
|
||||
|
||||
<!-- 每日依从性统计 -->
|
||||
<select id="getDailyAdherenceStats" resultType="java.util.Map">
|
||||
select
|
||||
to_char(a.scheduled_time, 'YYYY-MM-DD') as stat_date,
|
||||
count(1) as total_tasks,
|
||||
sum(case when a.status = '2' then 1 else 0 end) as completed_tasks,
|
||||
sum(case when a.status = '4' then 1 else 0 end) as missed_tasks,
|
||||
sum(case when a.status = '3' then 1 else 0 end) as skipped_tasks,
|
||||
sum(case when a.status = '2' and a.actual_time is not null
|
||||
and extract(epoch from (a.actual_time - a.scheduled_time))/3600 <= 1 then 1 else 0 end) as ontime_tasks
|
||||
from health_medication_record a
|
||||
where a.del_flag = '0'
|
||||
<if test="dto.personId != null"> and a.person_id = #{dto.personId}</if>
|
||||
<if test="dto.planId != null"> and a.plan_id = #{dto.planId}</if>
|
||||
<if test="dto.medicineId != null"> and a.medicine_id = #{dto.medicineId}</if>
|
||||
<if test="dto.startDate != null"> and date(a.scheduled_time) >= #{dto.startDate}</if>
|
||||
<if test="dto.endDate != null"> and date(a.scheduled_time) <= #{dto.endDate}</if>
|
||||
group by to_char(a.scheduled_time, 'YYYY-MM-DD')
|
||||
order by stat_date asc
|
||||
</select>
|
||||
|
||||
<!-- 时段依从性统计 -->
|
||||
<select id="getTimeSlotAdherenceStats" resultType="java.util.Map">
|
||||
select
|
||||
extract(hour from a.scheduled_time) as hour,
|
||||
count(1) as total_tasks,
|
||||
sum(case when a.status = '2' then 1 else 0 end) as completed_tasks,
|
||||
sum(case when a.status = '4' then 1 else 0 end) as missed_tasks,
|
||||
sum(case when a.status = '2' and a.actual_time is not null
|
||||
and extract(epoch from (a.actual_time - a.scheduled_time))/3600 <= 1 then 1 else 0 end) as ontime_tasks
|
||||
from health_medication_record a
|
||||
where a.del_flag = '0'
|
||||
<if test="dto.personId != null"> and a.person_id = #{dto.personId}</if>
|
||||
<if test="dto.planId != null"> and a.plan_id = #{dto.planId}</if>
|
||||
<if test="dto.startDate != null"> and date(a.scheduled_time) >= #{dto.startDate}</if>
|
||||
<if test="dto.endDate != null"> and date(a.scheduled_time) <= #{dto.endDate}</if>
|
||||
group by extract(hour from a.scheduled_time)
|
||||
order by hour asc
|
||||
</select>
|
||||
|
||||
<!-- 药品依从性统计 -->
|
||||
<select id="getMedicineAdherenceStats" resultType="java.util.Map">
|
||||
select
|
||||
a.plan_id,
|
||||
pl.plan_name,
|
||||
a.medicine_id,
|
||||
m.name as medicine_name,
|
||||
count(1) as total_tasks,
|
||||
sum(case when a.status = '2' then 1 else 0 end) as completed_tasks,
|
||||
sum(case when a.status = '4' then 1 else 0 end) as missed_tasks,
|
||||
sum(case when a.status = '2' and a.actual_time is not null
|
||||
and extract(epoch from (a.actual_time - a.scheduled_time))/3600 <= 1 then 1 else 0 end) as ontime_tasks
|
||||
from health_medication_record a
|
||||
left join health_medication_plan pl on a.plan_id = pl.id
|
||||
left join health_medicine_basic m on a.medicine_id = m.id
|
||||
where a.del_flag = '0'
|
||||
<if test="dto.personId != null"> and a.person_id = #{dto.personId}</if>
|
||||
<if test="dto.startDate != null"> and date(a.scheduled_time) >= #{dto.startDate}</if>
|
||||
<if test="dto.endDate != null"> and date(a.scheduled_time) <= #{dto.endDate}</if>
|
||||
group by a.plan_id, pl.plan_name, a.medicine_id, m.name
|
||||
order by total_tasks desc
|
||||
</select>
|
||||
|
||||
<!-- 人员依从性统计 -->
|
||||
<select id="getPersonAdherenceStats" resultType="java.util.Map">
|
||||
select
|
||||
a.person_id,
|
||||
p.name as person_name,
|
||||
count(1) as total_tasks,
|
||||
sum(case when a.status = '2' then 1 else 0 end) as completed_tasks,
|
||||
sum(case when a.status = '4' then 1 else 0 end) as missed_tasks,
|
||||
sum(case when a.status = '2' and a.actual_time is not null
|
||||
and extract(epoch from (a.actual_time - a.scheduled_time))/3600 <= 1 then 1 else 0 end) as ontime_tasks
|
||||
from health_medication_record a
|
||||
left join health_person p on a.person_id = p.id
|
||||
where a.del_flag = '0'
|
||||
<if test="dto.startDate != null"> and date(a.scheduled_time) >= #{dto.startDate}</if>
|
||||
<if test="dto.endDate != null"> and date(a.scheduled_time) <= #{dto.endDate}</if>
|
||||
group by a.person_id, p.name
|
||||
order by total_tasks desc
|
||||
</select>
|
||||
|
||||
<!-- 漏服原因统计 -->
|
||||
<select id="getMissedReasonStats" resultType="java.util.Map">
|
||||
select
|
||||
a.remark,
|
||||
count(1) as count
|
||||
from health_medication_record a
|
||||
where a.del_flag = '0' and a.status in ('3', '4')
|
||||
<if test="dto.personId != null"> and a.person_id = #{dto.personId}</if>
|
||||
<if test="dto.startDate != null"> and date(a.scheduled_time) >= #{dto.startDate}</if>
|
||||
<if test="dto.endDate != null"> and date(a.scheduled_time) <= #{dto.endDate}</if>
|
||||
group by a.remark
|
||||
order by count desc
|
||||
</select>
|
||||
</mapper>
|
||||
53
sql/20250319-medication-adherence.sql
Normal file
53
sql/20250319-medication-adherence.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
-- ========================================
|
||||
-- 依从性统计功能 - SQL脚本
|
||||
-- ========================================
|
||||
|
||||
-- 用药统计菜单
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES (
|
||||
nextval('seq_sys_menu'),
|
||||
'用药统计',
|
||||
(SELECT menu_id FROM sys_menu WHERE menu_name = '健康管理' LIMIT 1),
|
||||
12,
|
||||
'medicationStatistic',
|
||||
'health/medicationStatistic/index',
|
||||
NULL,
|
||||
1,
|
||||
0,
|
||||
'C',
|
||||
'0',
|
||||
'0',
|
||||
'health:medicationStatistic:query',
|
||||
'chart',
|
||||
'admin',
|
||||
NOW(),
|
||||
'用药统计菜单'
|
||||
);
|
||||
|
||||
-- 用药统计按钮权限
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT nextval('seq_sys_menu'), '用药统计查询', m.menu_id, 1, '', NULL, NULL, 1, 0, 'F', '0', '0', 'health:medicationStatistic:query', '#', 'admin', NOW(), '' FROM sys_menu m WHERE m.perms = 'health:medicationStatistic:query';
|
||||
|
||||
-- ========================================
|
||||
-- 添加依从性统计汇总表(可选,用于预计算性能优化)
|
||||
-- ========================================
|
||||
DROP TABLE IF EXISTS health_medication_adherence_stats;
|
||||
CREATE TABLE health_medication_adherence_stats (
|
||||
id BIGINT NOT NULL,
|
||||
person_id BIGINT NOT NULL,
|
||||
plan_id BIGINT NULL,
|
||||
stat_date DATE NOT NULL,
|
||||
total_tasks INT DEFAULT 0,
|
||||
completed_tasks INT DEFAULT 0,
|
||||
missed_tasks INT DEFAULT 0,
|
||||
skipped_tasks INT DEFAULT 0,
|
||||
ontime_tasks INT DEFAULT 0,
|
||||
adherence_rate DECIMAL(5,2) DEFAULT 0.00,
|
||||
ontime_rate DECIMAL(5,2) DEFAULT 0.00,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB COMMENT='依从性统计汇总表';
|
||||
|
||||
CREATE UNIQUE INDEX idx_hmas_person_date ON health_medication_adherence_stats(person_id, stat_date, plan_id);
|
||||
CREATE INDEX idx_hmas_stat_date ON health_medication_adherence_stats(stat_date);
|
||||
Reference in New Issue
Block a user