Files
gym-manage/gym-manage-api/docs/coach-performance-design.md
T

11 KiB
Raw Blame History

教练业绩统计设计文档

版本: v2.0
日期: 2026-07-26
状态: 已确定


一、功能概述

教练业绩统计为体育馆管理系统提供按教练维度的绩效评估,帮助管理者横向对比教练表现、激励教练提升教学质量。

核心能力

  • 教练排行榜:按综合评分降序排列所有教练
  • 教练详情:查看单个教练的六项指标详情
  • 自查看板:教练查看自己的业绩表现

二、指标体系

2.1 六项指标总览

序号 指标 类型 含义 数据源
1 授课量 基础指标 统计周期内已完成的团课数量 group_course.status IN ('2','6')
2 出席人次 基础指标 学员实际参与的人次 group_course_booking.status IN ('2','4','5')
3 总预约数 基础指标 学员预约该教练课程的次数(仅已预约状态) group_course_booking.status = '0'
4 出勤率 派生指标 出席人次 / 总预约数 * 100 指标2 + 指标3
5 满员率 派生指标 各课程出席人数/满员上限的平均值 group_course.max_members + 指标2明细
6 违规次数 基础指标 统计周期内违规记录数 coach_violation
7 综合评分 派生指标 加权综合得分(详见第三章) 指标1-6

2.2 指标口径详解

授课量

SELECT coach_id, COUNT(*) FROM group_course
WHERE end_time >= :startTime AND end_time < :endTime
  AND status IN ('2', '6') AND deleted_at IS NULL
GROUP BY coach_id
  • status='2': 教练手动结课
  • status='6': 系统自动结课
  • 排除: 已取消(status='1')、教练缺席(status='5')的课程

出席人次

SELECT gc.coach_id, COUNT(*) FROM group_course_booking b
INNER JOIN group_course gc ON b.course_id = gc.id
WHERE b.status IN ('2', '4', '5') AND b.deleted_at IS NULL
  AND gc.deleted_at IS NULL
  AND gc.end_time >= :startTime AND gc.end_time < :endTime
GROUP BY gc.coach_id
  • status='2': 已出席 — 学员正常到课
  • status='4': 教练缺席 — 教练未到,学员仍需记录
  • status='5': 迟到 — 学员迟到但仍到场参与

设计意图: 教练缺席和迟到时,学员仍到达了现场(或尝试参与),责任在教练而非学员,故计入出席人次。学员无故缺席(status='3')不计入,因其既非教练功劳也非教练责任。

总预约数

SELECT gc.coach_id, COUNT(*) FROM group_course_booking b
INNER JOIN group_course gc ON b.course_id = gc.id
WHERE b.status = '0' AND b.deleted_at IS NULL
  AND gc.deleted_at IS NULL
  AND gc.end_time >= :startTime AND gc.end_time < :endTime
GROUP BY gc.coach_id
  • 仅 status='0'(已预约): 作为出勤率分母,表示"承诺来上课的学员"。

满员率

SELECT gc.coach_id, gc.max_members, COUNT(b.id) AS attended
FROM group_course gc
LEFT JOIN group_course_booking b ON gc.id = b.course_id
  AND b.status IN ('2','4','5') AND b.deleted_at IS NULL
WHERE gc.end_time >= :startTime AND gc.end_time < :endTime
  AND gc.status IN ('2', '6') AND gc.deleted_at IS NULL
GROUP BY gc.coach_id, gc.id, gc.max_members
  • 对每个已完成课程,计算 出席人数 / max_members
  • 所有课程的比值取平均值
  • max_members = 0 的课程被跳过(除零防御)

违规次数

SELECT coach_id, COUNT(*) FROM coach_violation
WHERE violation_time >= :startTime AND violation_time < :endTime
  AND deleted_at IS NULL
GROUP BY coach_id
  • 违规类型: COACH_LATE(迟到)、COACH_ABSENT(缺席)、NOT_MANUAL_END(未手动结课)

时间基准

所有指标均基于 group_course.end_time 过滤时间范围。跨月课程归属于结束时间所在的月份。


三、综合评分算法

3.1 最终公式

综合评分 = 授课量归一化分 * 0.35
        + 出勤率 * 0.25
        + 满员率 * 0.25
        + 违规分 * 0.15

3.2 授课量归一化:百分位排名法(方案 B)

对于教练数为 N 的集合:

  1. 将所有教练按授课量升序排列
  2. 统计授课量严格小于当前教练的教练数 C_fewer
  3. normalizedCourses = C_fewer / (N - 1) * 100N=1 时取 100

示例4 位教练):

教练 授课量 小于其的教练数 归一化分
A 20 3 100.0
B 15 2 66.7
C 10 1 33.3
D 5 0 0.0

设计意图: 百分位排名在"相对比较"和"公平性"之间取得平衡。授课量最大的教练得满分,最少的得 0 分,中间按排名线性分布。不受极端值影响——即使第一名开 100 节课、第二名只开 20 节,第二名的排名分数依然是 2/3 * 100 ≈ 66.7

3.3 违规分:线性扣分法(方案 A

violationScore = max(0, 100 - violations * 20)
违规次数 违规分
0 100
1 80
2 60
3 40
4 20
5+ 0

设计意图: 线性扣分简单直观,每次违规固定扣 20 分,累计 5 次后清零。在 15% 的权重下,每次违规对综合评分的影响约为 20 * 0.15 = 3 分

3.4 出勤率 & 满员率

这两项直接使用原始百分比(0-100),无需归一化——它们天然在 0-100 范围内且具有绝对含义。

出勤率 = 出席人次 / 总预约数 * 100(分母为 0 时取 0)
满员率 = avg(单个课程出席人数 / max_members) * 100(跳过 max_members=0 的课程)

四、方案选择记录

4.1 授课量归一化方案

方案 公式 优点 缺点 决定
B: 百分位排名 C_fewer / (N-1) * 100 直观、不受极端值影响 对教练总数敏感(N<3 时分布粗糙) 采纳
A: 最大值归一化 courses / max(courses) * 100 数学简洁 极端值压缩中游得分 初版方案,已废弃
C: 对数归一化 ln(x+1)/ln(m+1)*100 压制极端值 解释性弱 已拒绝
D: 固定目标 min(x/target*100, 100) 达标制、不互卷 target 难校准 已拒绝

4.2 违规扣分方案

方案 公式 优点 缺点 决定
A: 线性扣分 max(0, 100 - v*20) 简单直白,每次等量扣分 多次违规后惩罚不再加剧 采纳
B: 阶梯扣分 0→100, 1→70, 2→40, 3→10 首次违规惩罚重,有威慑力 第 1 到第 2 次差距 30 分,太激进 已拒绝
C: 归一化扣分 (1 - v/max)*100 相对最差教练 依赖数据集,全员无违规则无意义 已拒绝

4.3 权重分配方案

方案 授课量 出勤率 满员率 违规 决定
A: 轻违规 35% 25% 25% 15% 采纳
B: 中违规 30% 25% 25% 20% 已拒绝
C: 重违规 30% 23% 22% 25% 已拒绝

4.4 出勤率口径方案

方案 分子 分母 决定
当前 status IN ('2','4','5') status='0' 采纳
初版 status='2' status!='1' 已废弃
替代方案1 status='2' status IN ('0','2','3') 已拒绝(无故缺席应排除)
替代方案2 status='2' status IN ('0','2') 已拒绝(不能区分取消预约)

五、数据模型

5.1 API 响应模型

public class CoachPerformance {
    Long coachId;          // 教练ID
    String coachName;      // 教练昵称
    String avatar;         // 头像URL
    Long completedCourses; // 授课量
    Long attendedStudents; // 出席人次
    Long totalBookings;    // 总预约数
    Double attendanceRate; // 出勤率 (%)
    Double fillRate;       // 满员率 (%)
    Long violationCount;   // 违规次数
    Double compositeScore; // 综合评分
}

5.2 API 接口

方法 路径 说明
GET /api/datacount/coach-performance/ranking 全部教练业绩排行榜
GET /api/datacount/coach-performance/{coachId} 单个教练业绩详情
GET /api/datacount/coach-performance/mine?coachId= 教练自查看板

查询参数: statType, periodTypeDAY/WEEK/MONTH/LAST_30_DAYS/LAST_90_DAYS/YEAR, startTime, endTime


六、代码架构

CoachPerformanceHandler     ── Reactive Router Function,解析请求
        │
IDataStatisticsService      ── 接口定义
        │
DataStatisticsServiceImpl   ── 6 并行查询 + 聚合计算
        │
DataStatisticsDao           ── DatabaseClient SQL 聚合
        │
    ┌───┼───┬───┬───┬───┐
    ▼   ▼   ▼   ▼   ▼   ▼
 sys_user  group_course  group_course_booking  coach_violation

查询执行流程

1. Flux: getAllCoachesWithInfo()        ─→ Map<coachId, 基本信息>
2. Flux: countCompletedCoursesByCoach() ─→ Map<coachId, 授课量>        ┐
3. Flux: countAttendedStudentsByCoach() ─→ Map<coachId, 出席人次>      │
4. Flux: countTotalBookingsByCoach()    ─→ Map<coachId, 总预约数>      ├─ Mono.zip
5. Flux: getFillRateDetailByCoach()     ─→ Map<coachId, List<明细>>    │
6. Flux: countViolationsByCoach()       ─→ Map<coachId, 违规次数>      ┘
                                                      │
                                          flatMapMany: 逐教练计算指标
                                                      │
                                          sorted: 按综合评分降序
                                                      │
                                          Flux<CoachPerformance>

七、边界情况处理

场景 行为
教练无任何已完成课程 所有指标为 0,综合评分 = 0 + 0 + 0 + 15 = 15(违规分满分 100 * 0.15
教练有课程但无人预约 授课量 > 0,出勤率/满员率 = 0
课程 max_members = 0 该课程跳过,不参与满员率计算
仅有 1 位教练 百分位排名直接返回 100
所有教练授课量相同 所有教练 C_fewer = 0,授课量归一化分均为 0
跨月课程 以 end_time 所在月份归类
查询单个教练不存在 返回零值 CoachPerformancecoachName="未知教练"

八、变更历史

日期 版本 变更内容
2026-07-22 v1.0 初版:最大值归一化 + 三维度评分(4:3:3),违规仅展示
2026-07-26 v2.0 时间基准改为 end_time;出席人次扩展为(2,4,5);出勤率分母改为仅 status='0';授课量归一化改为百分位排名;违规纳入综合评分(权重 15%);满员率防御除零