Compare commits

...

3 Commits

Author SHA1 Message Date
ff54e2b053 tabulated presentation 2022-06-27 15:17:58 -05:00
244bed2c51 ensure historical tables show full length 2022-06-27 15:07:55 -05:00
7dea88a1b3 add leaderboard race lines 2022-06-27 14:18:11 -05:00
3 changed files with 165 additions and 87 deletions

View File

@@ -57,13 +57,17 @@ class WordleDay:
# Designed so that "today" will be the current date in CST # Designed so that "today" will be the current date in CST
# Regardless of where the code is run # Regardless of where the code is run
def get_wordle_today(): def get_today_central():
today = ( today = (
datetime.datetime.now(datetime.timezone.utc) datetime.datetime.now(datetime.timezone.utc)
.astimezone(datetime.timezone(datetime.timedelta(hours=-5), name="US Central")) .astimezone(datetime.timezone(datetime.timedelta(hours=-5), name="US Central"))
.date() .date()
) )
return WordleDay.from_date(today) return today
def get_wordle_today():
return WordleDay.from_date(get_today_central())
WORDLE_TODAY = get_wordle_today() WORDLE_TODAY = get_wordle_today()

View File

@@ -1,4 +1,5 @@
import collections import collections
import itertools
import typing import typing
import wordlinator.db.pg import wordlinator.db.pg
@@ -76,6 +77,15 @@ class UserRow(ScoreRow):
def golf_score(self) -> int: def golf_score(self) -> int:
return self.total - (self.count * 4) return self.total - (self.count * 4)
@property
def progressive_score_list(self) -> typing.List[int]:
score_progress = list(
itertools.accumulate(
self.sorted_scores(), func=lambda t, e: t + (e.score - 4), initial=0
)
)[1:]
return score_progress
def sorted_scores(self): def sorted_scores(self):
yield from sorted(self._scores, key=lambda s: s.hole_id.hole) yield from sorted(self._scores, key=lambda s: s.hole_id.hole)
@@ -189,3 +199,19 @@ class ScoreMatrix(ScoreContainer):
def user_rows(self, wordle_day): def user_rows(self, wordle_day):
hole_no = wordle_day.golf_hole.hole_no hole_no = wordle_day.golf_hole.hole_no
return [u.user_row(hole_no=hole_no) for u in self.by_user().values()] return [u.user_row(hole_no=hole_no) for u in self.by_user().values()]
def top_by_day(self):
user_dict = {u: r.progressive_score_list for u, r in self.by_user().items()}
days = max(map(len, user_dict.values()))
rankings = collections.defaultdict(list)
for day_idx in range(days):
day_scores = {
u: v[day_idx] for u, v in user_dict.items() if len(v) >= day_idx + 1
}
tops = [(u, v) for u, v in sorted(day_scores.items(), key=lambda t: t[1])][
:20
]
for (user, score) in tops:
rankings[user].append((day_idx + 1, score))
return rankings

View File

@@ -1,3 +1,4 @@
import collections
import functools import functools
import os import os
import pathlib import pathlib
@@ -16,7 +17,7 @@ import wordlinator.utils
import wordlinator.utils.scores import wordlinator.utils.scores
import wordlinator.utils.web import wordlinator.utils.web
TTL_TIME = 30 if os.getenv("DEBUG") else 600 TTL_TIME = 30 if os.getenv("DEBUG") else 90
LEADERBOARD_COUNT = 20 LEADERBOARD_COUNT = 20
################### ###################
@@ -64,6 +65,16 @@ def wordle_today():
return _wordle_today(get_ttl_hash()) return _wordle_today(get_ttl_hash())
def round_wordle_day(round_id):
wt = wordle_today()
rounds = games_from_db()
matching_round = [r for r in rounds if r.game_id == round_id][0]
if matching_round.game == wt.golf_hole.game_no:
return wt
return wordlinator.utils.WordleDay.from_date(matching_round.end_date)
@functools.lru_cache(maxsize=3) @functools.lru_cache(maxsize=3)
def _scores_from_db(round_id, ttl_hash=None): def _scores_from_db(round_id, ttl_hash=None):
wordle_db = db.WordleDb() wordle_db = db.WordleDb()
@@ -105,11 +116,12 @@ def get_leaderboard(round_id):
def get_scores(round_id): def get_scores(round_id):
score_matrix = scores_from_db(round_id) score_matrix = scores_from_db(round_id)
table_rows = score_matrix.user_rows(wordle_today()) round_day = round_wordle_day(round_id)
table_rows = score_matrix.user_rows(round_day)
hole_columns = [ hole_columns = [
{"name": f"{i}", "id": f"{i}", "type": "text", "presentation": "markdown"} {"name": f"{i}", "id": f"{i}", "type": "text", "presentation": "markdown"}
for i in range(1, wordle_today().golf_hole.hole_no + 1) for i in range(1, round_day.golf_hole.hole_no + 1)
] ]
columns = [ columns = [
{"name": "Name", "id": "Name", "type": "text"}, {"name": "Name", "id": "Name", "type": "text"},
@@ -190,7 +202,10 @@ def get_daily_stats(round_id):
{"name": n, "id": n} {"name": n, "id": n}
for n in ( for n in (
"Score", "Score",
*[f"{i}" for i in range(1, wordle_today().golf_hole.hole_no + 1)], *[
f"{i}"
for i in range(1, round_wordle_day(round_id).golf_hole.hole_no + 1)
],
) )
] ]
return dash.dash_table.DataTable( return dash.dash_table.DataTable(
@@ -251,6 +266,38 @@ def get_line_graph(round_id):
return dash.dcc.Graph(figure=figure) return dash.dcc.Graph(figure=figure)
#####################
# Line Race Helpers #
#####################
def line_race_graph(round_id):
score_matrix = scores_from_db(round_id)
tops_by_day = score_matrix.top_by_day()
figure = plotly.graph_objs.Figure()
figure.update_yaxes(autorange="reversed")
figure.update_xaxes(tickmode="linear", tick0=1, dtick=1)
annotation_names = collections.defaultdict(list)
for name, entries in tops_by_day.items():
figure.add_trace(
plotly.graph_objs.Scatter(
name=name,
mode="lines+markers",
x=[e[0] for e in entries],
y=[e[1] for e in entries],
)
)
annotation_names[entries[-1]].append(name)
annotations = [
{"x": k[0], "y": k[1], "text": ", ".join(v)}
for k, v in annotation_names.items()
]
figure.update_layout(annotations=annotations)
return dash.dcc.Graph(figure=figure)
############# #############
# App Setup # # App Setup #
############# #############
@@ -265,95 +312,96 @@ app.layout = dash.html.Div(
id="round-selector", id="round-selector",
style={"maxWidth": "300px"}, style={"maxWidth": "300px"},
), ),
dash.html.Div( dash.dcc.Tabs(
[ id="main-tabs",
dash.html.H2( value="leaderboard",
f"Leaderboard - Top {LEADERBOARD_COUNT}", children=[
style={"textAlign": "center"}, dash.dcc.Tab(label="Leaderboard", value="leaderboard"),
), dash.dcc.Tab(label="Statistics", value="statistics"),
dash.dcc.Loading( dash.dcc.Tab(label="User Scores", value="user-scores"),
id="leaderboard-loading", ],
children=dash.html.Div("Loading...", id="leaderboard"),
),
]
),
dash.html.Div(
[
dash.html.H2("User Scores", style={"textAlign": "center"}),
dash.dcc.Loading(
id="user-scores-loading",
children=dash.html.Div("Loading...", id="user-scores"),
),
]
),
dash.html.Div(
[
dash.html.H2("Score Graph", style={"textAlign": "center"}),
dash.dcc.Loading(
id="stats-graph-loading",
children=dash.html.Div("Loading...", id="stats-graph"),
),
]
),
dash.html.Div(
[
dash.html.H2("Daily Stats", style={"textAlign": "center"}),
dash.dcc.Loading(
id="daily-stats-loading",
children=dash.html.Div("Loading...", id="daily-stats"),
),
]
), ),
dash.dcc.Loading(dash.html.Div(id="tab-content"), id="tab-content-loading"),
] ]
) )
@app.long_callback( @app.callback(
output=dash.dependencies.Output("leaderboard", "children"), dash.dependencies.Output("tab-content", "children"),
inputs=[ [
dash.dependencies.Input("title", "children"), dash.dependencies.Input("main-tabs", "value"),
dash.dependencies.Input("round-selector-dropdown", "value"), dash.dependencies.Input("round-selector-dropdown", "value"),
], ],
manager=long_callback_manager,
) )
def get_leaderboard_table(_, round_id): def render_tab(tab, round_id):
return get_leaderboard(round_id) if tab == "leaderboard":
return [
dash.html.Div(
@app.long_callback( [
output=dash.dependencies.Output("user-scores", "children"), dash.html.H2(
inputs=[ f"Leaderboard - Top {LEADERBOARD_COUNT}",
dash.dependencies.Input("title", "children"), style={"textAlign": "center"},
dash.dependencies.Input("round-selector-dropdown", "value"), ),
], dash.dcc.Loading(
manager=long_callback_manager, id="leaderboard-race-loading",
) children=dash.html.Div(
def get_scores_chart(_, round_id): line_race_graph(round_id), id="leaderboard-race"
return get_scores(round_id) ),
),
]
@app.long_callback( ),
output=dash.dependencies.Output("daily-stats", "children"), dash.html.Div(
inputs=[ [
dash.dependencies.Input("title", "children"), dash.html.H2(
dash.dependencies.Input("round-selector-dropdown", "value"), f"Leaderboard - Top {LEADERBOARD_COUNT}",
], style={"textAlign": "center"},
manager=long_callback_manager, ),
) dash.dcc.Loading(
def get_stats_chart(_, round_id): id="leaderboard-loading",
return get_daily_stats(round_id) children=dash.html.Div(
get_leaderboard(round_id), id="leaderboard"
),
@app.long_callback( ),
output=dash.dependencies.Output("stats-graph", "children"), ]
inputs=[ ),
dash.dependencies.Input("title", "children"), ]
dash.dependencies.Input("round-selector-dropdown", "value"), elif tab == "user-scores":
], return [
manager=long_callback_manager, dash.html.Div(
) [
def get_stats_graph(_, round_id): dash.html.H2("User Scores", style={"textAlign": "center"}),
return get_line_graph(round_id) dash.dcc.Loading(
id="user-scores-loading",
children=dash.html.Div(get_scores(round_id), id="user-scores"),
),
]
),
]
elif tab == "statistics":
return [
dash.html.Div(
[
dash.html.H2("Score Graph", style={"textAlign": "center"}),
dash.dcc.Loading(
id="stats-graph-loading",
children=dash.html.Div(
get_line_graph(round_id), id="stats-graph"
),
),
]
),
dash.html.Div(
[
dash.html.H2("Daily Stats", style={"textAlign": "center"}),
dash.dcc.Loading(
id="daily-stats-loading",
children=dash.html.Div(
get_daily_stats(round_id), id="daily-stats"
),
),
]
),
]
server = app.server server = app.server