feat(health): 新增依从性统计功能

- 新增 MedicationAdherenceController:统计API接口
- 新增 IMedicationAdherenceService:统计服务接口
- 新增 MedicationAdherenceServiceImpl:统计服务实现
- 新增 MedicationAdherenceVo/DailyAdherenceVo:统计VO
- 扩展 HealthMedicationRecordMapper:新增统计SQL
- 新增菜单和统计汇总表SQL
This commit is contained in:
bot5
2026-03-19 08:34:42 +08:00
parent f5c9868a07
commit 8d71009f3a
9 changed files with 878 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -190,4 +190,121 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and a.scheduled_time &lt;= #{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 &lt;= 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) &gt;= #{dto.startDate}</if>
<if test="dto.endDate != null"> and date(a.scheduled_time) &lt;= #{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 &lt;= 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) &gt;= #{dto.startDate}</if>
<if test="dto.endDate != null"> and date(a.scheduled_time) &lt;= #{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 &lt;= 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) &gt;= #{dto.startDate}</if>
<if test="dto.endDate != null"> and date(a.scheduled_time) &lt;= #{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 &lt;= 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) &gt;= #{dto.startDate}</if>
<if test="dto.endDate != null"> and date(a.scheduled_time) &lt;= #{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 &lt;= 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) &gt;= #{dto.startDate}</if>
<if test="dto.endDate != null"> and date(a.scheduled_time) &lt;= #{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) &gt;= #{dto.startDate}</if>
<if test="dto.endDate != null"> and date(a.scheduled_time) &lt;= #{dto.endDate}</if>
group by a.remark
order by count desc
</select>
</mapper>

View 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);