From 063de7ee3e884b8a8a452d87c0666a395a43d5bc Mon Sep 17 00:00:00 2001 From: Toutsu Date: Thu, 7 May 2026 13:12:39 +0300 Subject: [PATCH] feat(#14): add get_group_attendance_stats SQL function --- .../Migrations/V012__add_attendance_stats.sql | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/GmRelay.Bot/Migrations/V012__add_attendance_stats.sql diff --git a/src/GmRelay.Bot/Migrations/V012__add_attendance_stats.sql b/src/GmRelay.Bot/Migrations/V012__add_attendance_stats.sql new file mode 100644 index 0000000..693115b --- /dev/null +++ b/src/GmRelay.Bot/Migrations/V012__add_attendance_stats.sql @@ -0,0 +1,66 @@ +-- ============================================================= +-- Attendance statistics view for GM analytics +-- Returns per-player aggregated metrics for a given game group. +-- NOTE: waitlist count reflects CURRENT registration_status only. +-- Full historical waitlist tracking will come with #15. +-- ============================================================= + +CREATE OR REPLACE FUNCTION get_group_attendance_stats(p_group_id UUID) +RETURNS TABLE ( + player_id UUID, + display_name VARCHAR, + telegram_username VARCHAR, + total_sessions BIGINT, + confirmed_count BIGINT, + declined_count BIGINT, + no_response_count BIGINT, + waitlisted_count BIGINT, + cancellation_affected_count BIGINT, + attendance_rate NUMERIC +) AS $$ +BEGIN + RETURN QUERY + WITH player_sessions AS ( + SELECT + sp.player_id, + s.id AS session_id, + sp.rsvp_status, + sp.registration_status, + s.status AS session_status, + s.scheduled_at + FROM session_participants sp + JOIN sessions s ON s.id = sp.session_id + WHERE s.group_id = p_group_id + ), + player_totals AS ( + SELECT + ps.player_id, + COUNT(*) FILTER (WHERE ps.session_status <> 'Cancelled') AS total_sessions, + COUNT(*) FILTER (WHERE ps.rsvp_status = 'Confirmed' AND ps.session_status <> 'Cancelled') AS confirmed_count, + COUNT(*) FILTER (WHERE ps.rsvp_status = 'Declined' AND ps.session_status <> 'Cancelled') AS declined_count, + COUNT(*) FILTER (WHERE ps.rsvp_status = 'Pending' AND ps.scheduled_at < NOW() AND ps.session_status <> 'Cancelled') AS no_response_count, + COUNT(*) FILTER (WHERE ps.registration_status = 'Waitlisted' AND ps.session_status <> 'Cancelled') AS waitlisted_count, + COUNT(*) FILTER (WHERE ps.session_status = 'Cancelled') AS cancellation_affected_count + FROM player_sessions ps + GROUP BY ps.player_id + ) + SELECT + pt.player_id, + p.display_name, + p.telegram_username, + pt.total_sessions, + pt.confirmed_count, + pt.declined_count, + pt.no_response_count, + pt.waitlisted_count, + pt.cancellation_affected_count, + ROUND( + 100.0 * pt.confirmed_count + / NULLIF(pt.total_sessions, 0), + 1 + ) AS attendance_rate + FROM player_totals pt + JOIN players p ON p.id = pt.player_id + ORDER BY pt.confirmed_count DESC, pt.total_sessions DESC; +END; +$$ LANGUAGE plpgsql STABLE;