rewrite: Initial statistics UI.

This commit is contained in:
2023-03-02 19:01:14 +02:00
parent 8f127af9d0
commit 7dc361b1b9
19 changed files with 2283 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
from datetime import timedelta
from psycopg.sql import SQL
from data import NULL
from meta import LionBot
from gui.cards import WeeklyGoalCard, MonthlyGoalCard
from gui.base import CardMode
from ..data import StatsData
from ..lib import extract_weekid, extract_monthid, apply_week_offset, apply_month_offset
async def get_goals_card(
bot: LionBot, userid: int, guildid: int, offset: int, weekly: bool, mode: CardMode
):
data: StatsData = bot.get_cog('StatsCog').data
lion = await bot.core.lions.fetch(guildid, userid)
today = lion.today
# Calculate periodid and select the correct model
if weekly:
goal_model = data.WeeklyGoals
tasks_model = data.WeeklyTasks
start = today - timedelta(days=today.weekday())
start, end = apply_week_offset(start, offset), apply_week_offset(start, offset - 1)
periodid = extract_weekid(start)
key = {'guildid': guildid, 'userid': userid, 'weekid': periodid}
else:
goal_model = data.MonthlyGoals
tasks_model = data.MonthlyTasks
start = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
start, end = apply_month_offset(start, offset), apply_month_offset(start, offset - 1)
periodid = extract_monthid(start)
key = {'guildid': guildid, 'userid': userid, 'monthid': periodid}
# Extract goals and tasks
goals = await goal_model.fetch_or_create(*key.values())
task_rows = await tasks_model.fetch_where(**key)
tasks = [(i, row.content, bool(row.completed)) for i, row in enumerate(task_rows)]
# Compute task progress
task_data = bot.get_cog('TasklistCog').data
task_model = task_data.Task
task_query = task_model.table.select_where(
task_model.completed_at != NULL,
task_model.completed_at >= start,
task_model.completed_at <= end,
userid=userid,
).select(total=SQL('COUNT(*)')).with_no_adapter()
results = await task_query
tasks_completed = results[0]['total'] if results else 0
# Set and compute correct middle goal column
# if mode in (CardMode.VOICE, CardMode.STUDY):
if True:
model = data.VoiceSessionStats
middle_completed = (await model.study_times_between(guildid or None, userid, start, end))[0]
middle_goal = goals['study_goal']
# Compute schedule session progress
# TODO
sessions_complete = 0.5
# Get member profile
if member := await lion.get_member():
username = (member.display_name, member.discriminator)
avatar = member.avatar.key
else:
username = (lion.data.display_name, '#????')
avatar = lion.user_data.avatar_hash
# Getch badges
badges = await data.ProfileTag.fetch_tags(guildid, userid)
card_cls = WeeklyGoalCard if weekly else MonthlyGoalCard
card = card_cls(
name=username[0],
discrim=username[1],
avatar=(userid, avatar),
badges=badges,
tasks_done=tasks_completed,
tasks_goal=goals['task_goal'],
studied_hours=middle_completed,
studied_goal=middle_goal,
attendance=sessions_complete,
goals=tasks,
date=today,
skin={'mode': mode}
)
return card

View File

@@ -0,0 +1,96 @@
from typing import Optional
from datetime import timedelta
import calendar
from data import ORDER
from meta import LionBot
from gui.cards import MonthlyStatsCard
from gui.base import CardMode
from ..data import StatsData
from ..lib import apply_month_offset
async def get_monthly_card(bot: LionBot, userid: int, guildid: int, offset: int, mode: CardMode) -> MonthlyStatsCard:
data: StatsData = bot.get_cog('StatsCog').data
lion = await bot.core.lions.fetch(guildid, userid)
today = lion.today
month_start = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
target = apply_month_offset(month_start, offset)
target_end = (target + timedelta(days=40)).replace(day=1, hour=0, minute=0) - timedelta(days=1)
months = [target]
for i in range(0, 3):
months.append((months[-1] - timedelta(days=1)).replace(day=1))
months.reverse()
monthly = [
[0]*(calendar.monthrange(month.year, month.month)[1]) for month in months
]
# TODO: Select model based on card mode
model = data.VoiceSessionStats
# Get first session
query = model.table.select_where().order_by('start_time', ORDER.ASC).limit(1)
if guildid:
query = query.where(userid=userid, guildid=guildid)
else:
query = query.where(userid=userid)
results = await query
first_session = results[0]['start_time'] if results else None
if not first_session:
current_streak = 0
longest_streak = 0
else:
first_day = first_session.replace(hour=0, minute=0, second=0, microsecond=0)
first_month = first_day.replace(day=1)
# Build list of day starts up to now, or end of requested month
requests = []
end_of_req = target_end if offset else today
day = first_day
while day <= end_of_req:
day = day + timedelta(days=1)
requests.append(day)
# Request times between requested days
day_stats = await model.study_times_between(guildid or None, userid, *requests)
# Compute current streak and longest streak
current_streak = 0
longest_streak = 0
for day in day_stats:
if day > 0:
current_streak += 1
longest_streak = max(current_streak, longest_streak)
else:
current_streak = 0
# Populate monthly
offsets = {(month.year, month.month): i for i, month in enumerate(months)}
for day, stat in zip(reversed(requests[:-1]), reversed(day_stats)):
if day < months[0]:
break
i = offsets[(day.year, day.month)]
monthly[i][day.day - 1] = stat / 3600
# Get member profile
if member := await lion.get_member():
username = (member.display_name, member.discriminator)
else:
username = (lion.data.display_name, '#????')
# Request card
card = MonthlyStatsCard(
user=username,
timezone=str(lion.timezone),
now=lion.now.timestamp(),
month=int(target.timestamp()),
monthly=monthly,
current_streak=current_streak,
longest_streak=longest_streak,
skin={'mode': mode}
)
return card

View File

@@ -0,0 +1,63 @@
from typing import Optional
from datetime import datetime, timedelta
import discord
from meta import LionBot
from gui.cards import StatsCard
from ..data import StatsData
async def get_stats_card(bot: LionBot, userid: int, guildid: int):
data: StatsData = bot.get_cog('StatsCog').data
# TODO: Workouts
# TODO: Leaderboard rankings
guildid = guildid or 0
lion = await bot.core.lions.fetch(guildid, userid)
# Calculate the period timestamps, i.e. start time for each summary period
# TODO: Don't do the alltime one like this, not efficient anymore
# TODO: Unless we rewrite study_time_since again?
today = lion.today
month_start = today.replace(day=1)
period_timestamps = (
datetime(1970, 1, 1),
month_start,
today - timedelta(days=today.weekday()),
today
)
# Extract the study times for each period
study_times = await data.VoiceSessionStats.study_times_since(guildid, userid, *period_timestamps)
print("Study times", study_times)
# Calculate streak data by requesting times per day
# First calculate starting timestamps for each day
days = list(range(0, today.day + 2))
day_timestamps = [month_start + timedelta(days=day - 1) for day in days]
study_times = await data.VoiceSessionStats.study_times_between(guildid, userid, *day_timestamps)
print("Study times", study_times)
# Then extract streak tuples
streaks = []
streak_start = None
for day, stime in zip(days, study_times):
stime = stime or 0
if stime > 0 and streak_start is None:
streak_start = day
elif stime == 0 and streak_start is not None:
streaks.append((streak_start, day-1))
streak_start = None
if streak_start is not None:
streaks.append((streak_start, today.day))
card = StatsCard(
(0, 0),
list(reversed(study_times)),
100,
streaks,
)
return card

View File

@@ -0,0 +1,54 @@
from typing import Optional
from datetime import timedelta
from data import ORDER
from meta import LionBot
from gui.cards import WeeklyStatsCard
from gui.base import CardMode
from ..data import StatsData
async def get_weekly_card(bot: LionBot, userid: int, guildid: int, offset: int, mode: CardMode) -> WeeklyStatsCard:
data: StatsData = bot.get_cog('StatsCog').data
lion = await bot.core.lions.fetch(guildid, userid)
today = lion.today
week_start = today - timedelta(days=today.weekday()) - timedelta(weeks=offset)
days = [week_start + timedelta(i) for i in range(-7, 7 if offset else (today.weekday() + 1))]
# TODO: Select statistics model based on mode
model = data.VoiceSessionStats
# Get user session rows
query = model.table.select_where()
if guildid:
query = query.where(userid=userid, guildid=guildid).order_by('start_time', ORDER.ASC)
else:
query = query.where(userid=userid)
sessions = await query
# Extract quantities per-day
day_stats = await model.study_times_between(guildid or None, userid, *days)
for i in range(14 - len(day_stats)):
day_stats.append(0)
# Get member profile
if member := await lion.get_member():
username = (member.display_name, member.discriminator)
else:
username = (lion.data.display_name, '#????')
card = WeeklyStatsCard(
user=username,
timezone=str(lion.timezone),
now=lion.now.timestamp(),
week=week_start.timestamp(),
daily=tuple(map(lambda n: n/3600, day_stats)),
sessions=[
(int(session['start_time'].timestamp()), int(session['end_time'].timestamp()))
for session in sessions
],
skin={'mode': mode}
)
return card