countdown-bot

A Discord bot that runs countdown games and generates analytics
git clone https://git.ashermorgan.net/countdown-bot/
Log | Files | Refs | README

commit 96fa19f03bb7f49d9a14944343fafffa9b358517
parent 817e612be7ea6d06d1daa50b53c86ad6066a8c85
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat, 20 Apr 2024 20:31:34 -0700

Implement most analytic functions and procedures

Diffstat:
Mmodels/ddl.sql | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 177 insertions(+), 3 deletions(-)

diff --git a/models/ddl.sql b/models/ddl.sql @@ -1,3 +1,10 @@ +DROP FUNCTION IF EXISTS speedData; +DROP PROCEDURE IF EXISTS progressStats; +DROP FUNCTION IF EXISTS progressData; +DROP FUNCTION IF EXISTS historicalContributorData; +DROP FUNCTION IF EXISTS heatmapData; +DROP FUNCTION IF EXISTS etaData; +DROP FUNCTION IF EXISTS contributorData; DROP PROCEDURE IF EXISTS addMessage; DROP TYPE IF EXISTS addMessageResults; DROP TABLE IF EXISTS messages; @@ -13,7 +20,7 @@ CREATE TABLE messages ( countdownID INT REFERENCES countdowns(countdownID), userID INT NOT NULL, -- Discord user ID value INT NOT NULL, - timestamp TIMESTAMP NOT NULL + timestamp TIMESTAMPTZ NOT NULL ); CREATE TYPE addMessageResults AS ENUM ( @@ -23,7 +30,7 @@ CREATE TYPE addMessageResults AS ENUM ( -- Validate and add a new countdown message CREATE PROCEDURE addMessage (_messageID IN INT, _countdownID IN INT, _userID IN INT, _value IN INT, - _timestamp IN TIMESTAMP, result OUT addMessageResults) + _timestamp IN TIMESTAMPTZ, result OUT addMessageResults) LANGUAGE plpgsql AS $$ DECLARE lastMessage record; @@ -63,4 +70,171 @@ BEGIN result := 'good'; END IF; END -$$ +$$; + +CREATE FUNCTION contributorData (_countdownID INT) +RETURNS TABLE (userID INT, contributions BIGINT) +LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT messages.userID, count(messages.messageID) + FROM messages + WHERE messages.countdownID = _countdownID + GROUP BY messages.userID; +END +$$; + +CREATE FUNCTION etaData (_countdownID INT) +RETURNS TABLE (_timestamp TIMESTAMPTZ, eta TIMESTAMPTZ) +LANGUAGE plpgsql AS $$ +DECLARE + total INT; + startTime INT; +BEGIN + -- Get startTime and total from first message + SELECT value, extract(epoch FROM timestamp) + INTO total, startTime + FROM messages + WHERE countdownID = _countdownID + ORDER BY timestamp ASC + LIMIT 1; + + -- Calculate eta for each message + RETURN QUERY + SELECT timestamp, to_timestamp(startTime + value * + (extract(epoch FROM timestamp) - startTime) / (total - value)) + FROM messages + WHERE countdownID = _countdownID + AND extract(epoch FROM timestamp) != startTime + ORDER BY messageID; +END +$$; + +CREATE FUNCTION heatmapData (_countdownID INT, _userID INT) +RETURNS TABLE (dow NUMERIC, hour NUMERIC, messages BIGINT) +LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT extract(dow FROM timestamp) AS dow, + extract(hour FROM timestamp) AS hour, count(messageID) as messages + FROM messages + WHERE countdownID = _countdownID + AND (userID = _userID OR _userID IS NULL) + GROUP BY dow, hour; +END +$$; + +CREATE FUNCTION historicalContributorData (_countdownID INT) +RETURNS TABLE (progress INT, userID INT, percentage FLOAT) +LANGUAGE plpgsql AS $$ +DECLARE + total INT; +BEGIN + -- Get total from first message + SELECT value + INTO total + FROM messages + WHERE countdownID = _countdownID + ORDER BY timestamp ASC + LIMIT 1; + + -- Calculator percentage for each user for each message + RETURN QUERY + SELECT (total - value) AS progress, users.userID, + ( + SUM(CASE messages.userID WHEN users.userID THEN 1 ELSE 0 END) + OVER (PARTITION BY users.userID ORDER BY timestamp) + )::float / (total - value + 1) + FROM messages, ( + SELECT DISTINCT messages.userID + FROM messages + WHERE countdownID = _countdownID + ) users + WHERE countdownID = _countdownID; +END +$$; + +CREATE FUNCTION progressData (_countdownID INT) +RETURNS TABLE (_timestamp TIMESTAMPTZ, progress INT) +LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT timestamp, value + FROM messages + WHERE countdownID = _countdownID + ORDER BY messageID; +END +$$; + +CREATE PROCEDURE progressStats + (_countdownID IN INT, + total OUT INT, + current OUT INT, + progress OUT INT, + percentage OUT DECIMAL, + startTime OUT TIMESTAMPTZ, + endTime OUT TIMESTAMPTZ, + rate OUT DECIMAL, + longestBreak OUT INTERVAL, + longestBreakStart OUT TIMESTAMPTZ, + longestBreakEnd OUT TIMESTAMPTZ + ) +LANGUAGE plpgsql AS $$ +BEGIN + -- Get startTime and total from first message + SELECT messages.value, messages.timestamp + INTO total, startTime + FROM messages + WHERE messages.countdownID = _countdownID + ORDER BY messages.timestamp ASC + LIMIT 1; + + -- Get endTime and current from last message + SELECT messages.value, messages.timestamp + INTO current, endTime + FROM messages + WHERE messages.countdownID = _countdownID + ORDER BY messages.timestamp DESC + LIMIT 1; + + -- Calculate progress and percent + progress := total - current; + percentage := 100.0 * progress / total; + + -- Calculate rate and update endTime + IF current = 0 THEN + -- Countdown has ended, so endTime is already correct + rate := (total - current) / extract(epoch FROM (endTime - startTime)); + ELSEIF progress = 0 THEN + -- Countdown only has 1 message + rate := 0; + endTime = NOW(); + ELSE + rate := progress / extract(epoch FROM (NOW() - startTime)); + endTime := to_timestamp(extract(epoch FROM NOW()) + (current / rate)); + END IF; + rate := rate * 60 * 60 * 24; + + -- Calculate longestBreak, longestBreakStart, and longestBreakEnd + SELECT timestamp, + LEAD(timestamp, 1, NOW()) OVER (ORDER BY timestamp) - timestamp AS delta + INTO longestBreakStart, longestBreak + FROM messages + WHERE messages.countdownID = _countdownID + ORDER BY delta DESC + LIMIT 1; + longestBreakEnd := longestBreakStart + longestBreak; +END +$$; + +CREATE FUNCTION speedData (_countdownID INT) +RETURNS TABLE (date DATE, messages BIGINT) +LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT timestamp::date AS date, count(messageID) + FROM messages + WHERE countdownID = _countdownID + GROUP BY date; +END +$$;