From 8d71009f3a8371e8791cba1db6c2077ecaa3df76 Mon Sep 17 00:00:00 2001 From: bot5 Date: Thu, 19 Mar 2026 08:34:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(health):=20=E6=96=B0=E5=A2=9E=E4=BE=9D?= =?UTF-8?q?=E4=BB=8E=E6=80=A7=E7=BB=9F=E8=AE=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 MedicationAdherenceController:统计API接口 - 新增 IMedicationAdherenceService:统计服务接口 - 新增 MedicationAdherenceServiceImpl:统计服务实现 - 新增 MedicationAdherenceVo/DailyAdherenceVo:统计VO - 扩展 HealthMedicationRecordMapper:新增统计SQL - 新增菜单和统计汇总表SQL --- .../MedicationAdherenceController.java | 127 +++++++ .../domain/dto/MedicationAdherenceDto.java | 34 ++ .../health/domain/vo/DailyAdherenceVo.java | 42 +++ .../domain/vo/MedicationAdherenceVo.java | 60 ++++ .../mapper/HealthMedicationRecordMapper.java | 50 +++ .../service/IMedicationAdherenceService.java | 74 ++++ .../impl/MedicationAdherenceServiceImpl.java | 321 ++++++++++++++++++ .../health/HealthMedicationRecordMapper.xml | 117 +++++++ sql/20250319-medication-adherence.sql | 53 +++ 9 files changed, 878 insertions(+) create mode 100644 intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java create mode 100644 intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java create mode 100644 intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java create mode 100644 intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java create mode 100644 intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java create mode 100644 intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java create mode 100644 sql/20250319-medication-adherence.sql diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java new file mode 100644 index 0000000..3ea6897 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java @@ -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 list = adherenceService.getDailyAdherenceList(dto); + return AjaxResult.success(list); + } + + /** + * 获取日历视图数据 + */ + @GetMapping("/calendar") + @ApiOperation("获取日历视图数据") + public AjaxResult getCalendarData( + @RequestParam(required = false) Long personId, + @RequestParam String yearMonth) { + List list = adherenceService.getCalendarData(personId, yearMonth); + return AjaxResult.success(list); + } + + /** + * 获取各时段服药统计 + */ + @GetMapping("/timeSlot") + @ApiOperation("获取各时段服药统计") + public AjaxResult getTimeSlotAdherence(MedicationAdherenceDto dto) { + Map map = adherenceService.getTimeSlotAdherence(dto); + return AjaxResult.success(map); + } + + /** + * 获取各药品依从性统计 + */ + @GetMapping("/medicine") + @ApiOperation("获取各药品依从性统计") + public AjaxResult getMedicineAdherenceList(MedicationAdherenceDto dto) { + List list = adherenceService.getMedicineAdherenceList(dto); + return AjaxResult.success(list); + } + + /** + * 获取各人员依从性统计 + */ + @GetMapping("/person") + @ApiOperation("获取各人员依从性统计") + public AjaxResult getPersonAdherenceList(MedicationAdherenceDto dto) { + List list = adherenceService.getPersonAdherenceList(dto); + return AjaxResult.success(list); + } + + /** + * 获取漏服原因统计 + */ + @GetMapping("/missedReason") + @ApiOperation("获取漏服原因统计") + public AjaxResult getMissedReasonStats(MedicationAdherenceDto dto) { + Map 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 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); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java new file mode 100644 index 0000000..422edae --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java @@ -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; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java new file mode 100644 index 0000000..94598c5 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java @@ -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; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java new file mode 100644 index 0000000..88db068 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java @@ -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; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/mapper/HealthMedicationRecordMapper.java b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/HealthMedicationRecordMapper.java index 1248135..5dee335 100644 --- a/intc-modules/intc-health/src/main/java/com/intc/health/mapper/HealthMedicationRecordMapper.java +++ b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/HealthMedicationRecordMapper.java @@ -129,4 +129,54 @@ public interface HealthMedicationRecordMapper * @return 服药记录集合 */ public List selectPendingRemindRecords(@Param("startTime") java.time.LocalDateTime startTime, @Param("endTime") java.time.LocalDateTime endTime); + + // ========== 依从性统计相关 ========== + + /** + * 获取总体依从性统计 + * + * @param dto 查询参数 + * @return 统计结果Map + */ + public java.util.Map getAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto); + + /** + * 获取每日依从性统计 + * + * @param dto 查询参数 + * @return 每日统计列表 + */ + public java.util.List> getDailyAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto); + + /** + * 获取时段依从性统计 + * + * @param dto 查询参数 + * @return 时段统计列表 + */ + public java.util.List> getTimeSlotAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto); + + /** + * 获取药品依从性统计 + * + * @param dto 查询参数 + * @return 药品统计列表 + */ + public java.util.List> getMedicineAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto); + + /** + * 获取人员依从性统计 + * + * @param dto 查询参数 + * @return 人员统计列表 + */ + public java.util.List> getPersonAdherenceStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto); + + /** + * 获取漏服原因统计 + * + * @param dto 查询参数 + * @return 原因统计列表 + */ + public java.util.List> getMissedReasonStats(@Param("dto") com.intc.health.domain.dto.MedicationAdherenceDto dto); } \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java new file mode 100644 index 0000000..77aaa98 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java @@ -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 getDailyAdherenceList(MedicationAdherenceDto dto); + + /** + * 获取日历视图数据(某月的每日统计) + * + * @param personId 人员ID + * @param yearMonth 年月(yyyy-MM) + * @return 每日统计列表 + */ + List getCalendarData(Long personId, String yearMonth); + + /** + * 获取各时段服药统计(早中晚) + * + * @param dto 查询参数 + * @return 时段统计Map + */ + Map getTimeSlotAdherence(MedicationAdherenceDto dto); + + /** + * 获取各药品依从性统计 + * + * @param dto 查询参数 + * @return 药品统计列表 + */ + List getMedicineAdherenceList(MedicationAdherenceDto dto); + + /** + * 获取各人员依从性统计(家属视角) + * + * @param dto 查询参数 + * @return 人员统计列表 + */ + List getPersonAdherenceList(MedicationAdherenceDto dto); + + /** + * 获取漏服原因统计 + * + * @param dto 查询参数 + * @return 原因统计Map + */ + Map getMissedReasonStats(MedicationAdherenceDto dto); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java new file mode 100644 index 0000000..e87e31d --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java @@ -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 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 getDailyAdherenceList(MedicationAdherenceDto dto) { + List> dailyStats = recordMapper.getDailyAdherenceStats(dto); + + List result = new ArrayList<>(); + for (Map 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 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 dailyList = getDailyAdherenceList(dto); + + // 补全缺失的日期 + List result = new ArrayList<>(); + Map 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 getTimeSlotAdherence(MedicationAdherenceDto dto) { + List> slotStats = recordMapper.getTimeSlotAdherenceStats(dto); + + Map 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 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 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 getMedicineAdherenceList(MedicationAdherenceDto dto) { + List> medicineStats = recordMapper.getMedicineAdherenceStats(dto); + + List result = new ArrayList<>(); + for (Map 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 getPersonAdherenceList(MedicationAdherenceDto dto) { + List> personStats = recordMapper.getPersonAdherenceStats(dto); + + List result = new ArrayList<>(); + for (Map 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 getMissedReasonStats(MedicationAdherenceDto dto) { + List> reasonStats = recordMapper.getMissedReasonStats(dto); + + Map result = new LinkedHashMap<>(); + for (Map 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 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 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 map, String key) { + if (map == null || map.get(key) == null) { + return null; + } + return map.get(key).toString(); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/resources/mapper/health/HealthMedicationRecordMapper.xml b/intc-modules/intc-health/src/main/resources/mapper/health/HealthMedicationRecordMapper.xml index d12edd9..bdc58a9 100644 --- a/intc-modules/intc-health/src/main/resources/mapper/health/HealthMedicationRecordMapper.xml +++ b/intc-modules/intc-health/src/main/resources/mapper/health/HealthMedicationRecordMapper.xml @@ -190,4 +190,121 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and a.scheduled_time <= #{endTime} order by a.scheduled_time asc + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sql/20250319-medication-adherence.sql b/sql/20250319-medication-adherence.sql new file mode 100644 index 0000000..ff539f4 --- /dev/null +++ b/sql/20250319-medication-adherence.sql @@ -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); \ No newline at end of file