From e94d0a7714d755b1466308e920f4df01862fec98 Mon Sep 17 00:00:00 2001 From: Brad Brown Date: Mon, 20 Jun 2022 10:05:33 -0500 Subject: [PATCH] add multi-round display with dropdown --- wordlinator/app/__init__.py | 2 +- wordlinator/db/pg.py | 26 ++++++++++++- wordlinator/utils/web.py | 25 +++++++++++++ wordlinator/web/__init__.py | 75 ++++++++++++++++++++++++------------- 4 files changed, 99 insertions(+), 29 deletions(-) diff --git a/wordlinator/app/__init__.py b/wordlinator/app/__init__.py index 76b41b0..8c4216a 100644 --- a/wordlinator/app/__init__.py +++ b/wordlinator/app/__init__.py @@ -116,7 +116,7 @@ async def main_update( wordle_day: wordlinator.utils.WordleDay = wordlinator.utils.WORDLE_TODAY, ): if not wordle_day.golf_hole: - rich.print("[yellow]Today isn't a #WordleGolf day!") + rich.print(f"[yellow]{wordle_day.date} isn't a #WordleGolf day!") exit() sheets_client = wordlinator.sheets.SheetsClient(wordle_day=wordle_day) diff --git a/wordlinator/db/pg.py b/wordlinator/db/pg.py index eb47eca..5dbbc79 100644 --- a/wordlinator/db/pg.py +++ b/wordlinator/db/pg.py @@ -1,3 +1,4 @@ +import datetime import os import typing @@ -25,6 +26,9 @@ class User(BaseModel): twitter_id = peewee.CharField(unique=True) check_twitter = peewee.BooleanField(default=True) + def __repr__(self): + return f"" + class Meta: table_name = "user_tbl" @@ -34,6 +38,13 @@ class Game(BaseModel): game = peewee.IntegerField(null=False) start_date = peewee.DateField(null=False) + def __repr__(self): + return f"" + + @property + def end_date(self): + return self.start_date + datetime.timedelta(days=17) + class Player(BaseModel): user_id = peewee.ForeignKeyField(User, "user_id", null=False) @@ -45,6 +56,9 @@ class Hole(BaseModel): hole = peewee.IntegerField(null=False) game_id = peewee.ForeignKeyField(Game, "game_id", null=False) + def __repr__(self): + return f"" + class Score(BaseModel): score = peewee.IntegerField(null=False) @@ -74,6 +88,9 @@ class WordleDb: def add_user(self, username, user_id): return User.create(username=username, twitter_id=user_id) + def get_rounds(self): + return list(sorted(Game.select(), key=lambda d: d.start_date)) + def get_or_create_round(self, round_no, start_date=None): try: return Game.get(Game.game == round_no) @@ -127,8 +144,13 @@ class WordleDb: hole_id=hole.hole_id, ) - def get_scores(self, round_no): - round = self.get_or_create_round(round_no) + def get_scores(self, round_no=None, round_id=None): + if round_no: + round = self.get_or_create_round(round_no) + elif round_id: + round = Game.get_by_id(round_id) + else: + raise ValueError("Must provide Round Number or Round ID") res = ( Score.select( Score, diff --git a/wordlinator/utils/web.py b/wordlinator/utils/web.py index e21c57b..e957961 100644 --- a/wordlinator/utils/web.py +++ b/wordlinator/utils/web.py @@ -1,3 +1,28 @@ +from dash import dcc + +############### +# Date Helper # +############### + + +def _date_range(game): + return f"{game.start_date} to {game.end_date}" + + +def get_date_dropdown(dates): + options = [ + {"label": f"Round {d.game} ({_date_range(d)})", "value": d.game_id} + for d in dates + ] + + return dcc.Dropdown( + id="round-selector-dropdown", + options=options, + value=dates[-1].game_id, + clearable=False, + ) + + ###################### # Formatting Helpers # ###################### diff --git a/wordlinator/web/__init__.py b/wordlinator/web/__init__.py index 8a71567..2fd901f 100644 --- a/wordlinator/web/__init__.py +++ b/wordlinator/web/__init__.py @@ -36,17 +36,25 @@ long_callback_manager = dash.long_callback.DiskcacheLongCallbackManager( ) +@functools.lru_cache() +def _games_from_db(ttl_hash=None): + return db.WordleDb().get_rounds() + + +def games_from_db(): + return _games_from_db() + + @functools.lru_cache() def _wordle_today(ttl_hash=None): today = wordlinator.utils.get_wordle_today() if today.golf_hole: return today last_completed_round = [ - dt for dt in wordlinator.utils.WORDLE_GOLF_ROUND_DATES[::-1] if dt <= today.date + game for game in games_from_db()[::-1] if game.start_date <= today.date ] - last_round_start = last_completed_round[0] - last_round_end = last_round_start + datetime.timedelta(days=17) - return wordlinator.utils.WordleDay.from_date(last_round_end) + last_round = last_completed_round[0] + return wordlinator.utils.WordleDay.from_date(last_round.end_date) def wordle_today(): @@ -54,13 +62,14 @@ def wordle_today(): @functools.lru_cache() -def _scores_from_db(ttl_hash=None): - wordle_day = wordle_today() - return db.WordleDb().get_scores(wordle_day.golf_hole.game_no) +def _scores_from_db(round_id, ttl_hash=None): + return db.WordleDb().get_scores(round_id=round_id) -def scores_from_db(): - return wordlinator.utils.scores.ScoreMatrix(_scores_from_db(get_ttl_hash())) +def scores_from_db(round_id): + return wordlinator.utils.scores.ScoreMatrix( + _scores_from_db(round_id, get_ttl_hash()) + ) ################# @@ -68,8 +77,8 @@ def scores_from_db(): ################# -def get_scores(): - score_matrix = scores_from_db() +def get_scores(round_id): + score_matrix = scores_from_db(round_id) table_rows = score_matrix.user_rows(wordle_today()) hole_columns = [ @@ -141,15 +150,15 @@ def _get_summary_rows(score_matrix): return [totals, averages] -def _stats_dict(): - score_matrix = scores_from_db() +def _stats_dict(round_id): + score_matrix = scores_from_db(round_id) table_rows = [{"Score": k, **v} for k, v in score_matrix.score_breakdown().items()] table_rows.extend(_get_summary_rows(score_matrix)) return table_rows -def get_daily_stats(): - table_rows = _stats_dict() +def get_daily_stats(round_id): + table_rows = _stats_dict(round_id) columns = [ {"name": n, "id": n} @@ -186,8 +195,8 @@ SCORE_COLOR_DICT = { } -def get_line_graph(): - rows = _stats_dict() +def get_line_graph(round_id): + rows = _stats_dict(round_id) figure = plotly.graph_objs.Figure() total = [r for r in rows if r["Score"] == "Total"][0] rows = [r for r in rows if r["Score"] not in ("Total", "Daily Average")] @@ -223,6 +232,11 @@ def get_line_graph(): app.layout = dash.html.Div( children=[ dash.html.H1("#WordleGolf", style={"textAlign": "center"}, id="title"), + dash.html.Div( + wordlinator.utils.web.get_date_dropdown(games_from_db()), + id="round-selector", + style={"maxWidth": "300px"}, + ), dash.html.Div( [ dash.html.H2("User Scores", style={"textAlign": "center"}), @@ -247,29 +261,38 @@ app.layout = dash.html.Div( @app.long_callback( output=dash.dependencies.Output("user-scores", "children"), - inputs=dash.dependencies.Input("title", "children"), + inputs=[ + dash.dependencies.Input("title", "children"), + dash.dependencies.Input("round-selector-dropdown", "value"), + ], manager=long_callback_manager, ) -def get_scores_chart(_): - return get_scores() +def get_scores_chart(_, round_id): + return get_scores(round_id) @app.long_callback( output=dash.dependencies.Output("daily-stats", "children"), - inputs=dash.dependencies.Input("title", "children"), + inputs=[ + dash.dependencies.Input("title", "children"), + dash.dependencies.Input("round-selector-dropdown", "value"), + ], manager=long_callback_manager, ) -def get_stats_chart(_): - return get_daily_stats() +def get_stats_chart(_, round_id): + return get_daily_stats(round_id) @app.long_callback( output=dash.dependencies.Output("stats-graph", "children"), - inputs=dash.dependencies.Input("title", "children"), + inputs=[ + dash.dependencies.Input("title", "children"), + dash.dependencies.Input("round-selector-dropdown", "value"), + ], manager=long_callback_manager, ) -def get_stats_graph(_): - return get_line_graph() +def get_stats_graph(_, round_id): + return get_line_graph(round_id) server = app.server