rewrite: Initial statistics UI.
This commit is contained in:
0
src/modules/statistics/graphics/__init__.py
Normal file
0
src/modules/statistics/graphics/__init__.py
Normal file
91
src/modules/statistics/graphics/goals.py
Normal file
91
src/modules/statistics/graphics/goals.py
Normal 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
|
||||
96
src/modules/statistics/graphics/monthly.py
Normal file
96
src/modules/statistics/graphics/monthly.py
Normal 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
|
||||
63
src/modules/statistics/graphics/stats.py
Normal file
63
src/modules/statistics/graphics/stats.py
Normal 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
|
||||
54
src/modules/statistics/graphics/weekly.py
Normal file
54
src/modules/statistics/graphics/weekly.py
Normal 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
|
||||
Reference in New Issue
Block a user