Skip to content

Commit

Permalink
Merge pull request #25 from questionlp/develop
Browse files Browse the repository at this point in the history
Adding Support for Panelist Decimal Scores
  • Loading branch information
questionlp authored Aug 29, 2023
2 parents fb2ffe9 + 6d62b1f commit 34fda60
Show file tree
Hide file tree
Showing 26 changed files with 488 additions and 310 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changes

## 2.3.0

### Application Changes

- Add support for displaying panelist decimal scores stored in a new table column in the Wait Wait Stats Database instead of the standard integer scores. This is handled via version 2.2.0 of the `wwdtm` library and a new `use_decimal_scores` setting in the `config.json` application configuration file. By default, the value will be set to `false` and must be changed to `true`, and the appropriate changes have been deployed to the Wait Wait Stats Database.
- Increase digits after the decimal point in the "Monthly Average Score Heatmap" from 4 to 5 for consistency
- Removed Aggregate Count plot from the "Panelist: Score Breakdown" chart
- Add a new `use_latest_plotly` setting in the `config.json` application configuration file to set whether or not to use the stable version symlinked as `plotly-stable.min.js` or the latest version symlinked as `plotly-latest.min.js`. For backward compatibility, `plotly.min.js` is a symlink that points to `plotly-stable.min.js`

### Component Changes

- Upgrade wwdtm from 2.1.0 to 2.2.0, which also includes the following changes:
- Upgrade NumPy from 1.24.2 to 1.24.3
- Upgrade Plotly.js from 2.23.1 to 2.25.2 (stable) and 2.26.0 (latest)

### Development Changes

- Upgrade black from 23.3.0 to 23.7.0

## 2.2.6

### Component Changes
Expand Down
5 changes: 2 additions & 3 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ representative at an online or offline event.
## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[email protected]. All complaints will be reviewed and investigated promptly and
fairly.
reported to the community leaders responsible for enforcement at [[email protected]](mailto:[email protected]).
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
Expand Down
11 changes: 11 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ def create_app():
app.jinja_env.globals["mastodon_user"] = _config["settings"].get(
"mastodon_user", ""
)
app.jinja_env.globals["use_latest_plotly"] = _config["settings"][
"use_latest_plotly"
]
app.jinja_env.globals["use_decimal_scores"] = _config["settings"][
"use_decimal_scores"
]

# Check to see if panelistscore_decimal column exists and set a flag
app.config["app_settings"][
"has_decimal_scores_column"
] = utility.panelist_decimal_score_exists(database_settings=app.config["database"])

# Register application blueprints
app.register_blueprint(main_bp)
Expand Down
9 changes: 9 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ def load_config(
settings_config["time_zone"] = time_zone_string
database_config["time_zone"] = time_zone_string

# Read in setting on whether to use latest included version of Plotly JS
settings_config["use_latest_plotly"] = bool(
settings_config.get("use_latest_plotly", False)
)
# Read in setting on whether to use decimal scores
settings_config["use_decimal_scores"] = bool(
settings_config.get("use_decimal_scores", False)
)

return {
"database": database_config,
"settings": settings_config,
Expand Down
54 changes: 38 additions & 16 deletions app/panelists/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import json

from flask import Blueprint, current_app, render_template, url_for
import mysql.connector
from mysql.connector import connect
from slugify import slugify

from wwdtm.panelist import (
Panelist,
PanelistAppearances,
PanelistDecimalScores,
PanelistScores,
)

Expand All @@ -31,7 +32,9 @@ def index():
@blueprint.route("/aggregate-scores")
def aggregate_scores():
"""View: Aggregate Scores"""
_aggregate_scores = agg.retrieve_score_spread()
_aggregate_scores = agg.retrieve_score_spread(
use_decimal_scores=current_app.config["app_settings"]["use_decimal_scores"]
)
return render_template(
"panelists/aggregate-scores/graph.html", aggregate_scores=_aggregate_scores
)
Expand All @@ -40,7 +43,7 @@ def aggregate_scores():
@blueprint.route("/appearances-by-year")
def appearances_by_year():
"""View: Appearances by Year"""
database_connection = mysql.connector.connect(**current_app.config["database"])
database_connection = connect(**current_app.config["database"])
_panelist = Panelist(database_connection=database_connection)
all_panelists = _panelist.retrieve_all()
database_connection.close()
Expand All @@ -58,7 +61,7 @@ def appearances_by_year_details(panelist: str):
url_for("panelists.appearances_by_year_details", panelist=panelist_slug)
)

database_connection = mysql.connector.connect(**current_app.config["database"])
database_connection = connect(**current_app.config["database"])
_panelist = Panelist(database_connection=database_connection)
_appearances = PanelistAppearances(database_connection=database_connection)
info = _panelist.retrieve_by_slug(panelist)
Expand All @@ -83,7 +86,7 @@ def appearances_by_year_details(panelist: str):
@blueprint.route("/score-breakdown")
def score_breakdown():
"""View: Score Breakdown"""
database_connection = mysql.connector.connect(**current_app.config["database"])
database_connection = connect(**current_app.config["database"])
_panelist = Panelist(database_connection=database_connection)
panelists = _panelist.retrieve_all()
database_connection.close()
Expand All @@ -99,30 +102,35 @@ def score_breakdown_details(panelist: str):
url_for("panelists.score_breakdown_details", panelist=panelist_slug)
)

database_connection = mysql.connector.connect(**current_app.config["database"])
database_connection = connect(**current_app.config["database"])
_panelist = Panelist(database_connection=database_connection)
info = _panelist.retrieve_by_slug(panelist)

if not info:
return redirect_url(url_for("panelists.score_breakdown"))

_panelist_scores = PanelistScores(database_connection=database_connection)
scores = _panelist_scores.retrieve_scores_grouped_list_by_slug(panelist)
agg_scores = agg.retrieve_score_spread()
if current_app.config["app_settings"]["use_decimal_scores"]:
_panelist_scores = PanelistDecimalScores(
database_connection=database_connection
)
scores = _panelist_scores.retrieve_scores_grouped_list_by_slug(panelist)
else:
_panelist_scores = PanelistScores(database_connection=database_connection)
scores = _panelist_scores.retrieve_scores_grouped_list_by_slug(panelist)

database_connection.close()

return render_template(
"panelists/score-breakdown/details.html",
info=info,
scores=scores,
aggregate_scores=agg_scores,
)


@blueprint.route("/scores-by-appearance")
def scores_by_appearance():
"""View: Scores by Appearances"""
database_connection = mysql.connector.connect(**current_app.config["database"])
database_connection = connect(**current_app.config["database"])
_panelist = Panelist(database_connection=database_connection)
panelists = _panelist.retrieve_all()
database_connection.close()
Expand All @@ -140,19 +148,33 @@ def scores_by_appearance_details(panelist: str):
url_for("panelists.scores_by_appearance_details", panelist=panelist_slug)
)

database_connection = mysql.connector.connect(**current_app.config["database"])
database_connection = connect(**current_app.config["database"])
_panelist = Panelist(database_connection=database_connection)
_panelist_scores = PanelistScores(database_connection=database_connection)
info = _panelist.retrieve_by_slug(panelist)
scores = _panelist_scores.retrieve_scores_list_by_slug(panelist)

if current_app.config["app_settings"]["use_decimal_scores"]:
_panelist_scores = PanelistDecimalScores(
database_connection=database_connection
)
scores = _panelist_scores.retrieve_scores_list_by_slug(panelist)
else:
_panelist_scores = PanelistScores(database_connection=database_connection)
scores = _panelist_scores.retrieve_scores_list_by_slug(panelist)

database_connection.close()

if not info:
return redirect_url(url_for("panelists.scores_by_appearance"))

shows_json = json.dumps(scores["shows"])
if scores:
shows_json = json.dumps(scores["shows"])
scores_json = json.dumps(scores["scores"])
if current_app.config["app_settings"]["use_decimal_scores"]:
scores_float = []
for score in scores["scores"]:
scores_float.append(round(float(score), 5))
scores_json = json.dumps(scores_float)
else:
scores_json = json.dumps(scores["scores"])

return render_template(
"panelists/scores-by-appearance/details.html",
Expand Down
29 changes: 1 addition & 28 deletions app/panelists/templates/panelists/score-breakdown/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,13 @@

<h1>{{ info.name }}</h1>

{% if scores and aggregate_scores.scores %}
{% if scores %}
<p>
This chart displays the count of how many times
<a href="{{ stats_url }}/panelists/{{ info.slug }}">{{ info.name }}</a>
earned a specific number of points at the end of each show (excluding Best
Ofs and repeats).
</p>
<p>The orange line represents
<a href="{{ url_for('panelists.aggregate_scores') }}">aggregate score
breakdown</a> for all panelists.
</p>

<div id="ww-chart"></div>

Expand Down Expand Up @@ -62,13 +58,6 @@ <h1>{{ info.name }}</h1>
y: scoreCounts,
name: "Count",
type: "bar"
},
{
x: {{ scores.score|safe }},
y: {{ aggregate_scores.counts|safe }},
name: "Aggregrate Count",
type: "scatter",
yaxis: "y2"
}
];

Expand Down Expand Up @@ -131,22 +120,6 @@ <h1>{{ info.name }}</h1>
font: { size: 18 },
text: "Appearances"
}
},
yaxis2: {
color: axisColor,
overlaying: "y",
rangemode: "tozero",
showgrid: false,
side: "right",
tickfont: {
size: 16
},
title: {
font: {
size: 18
},
text: "Aggregate Count"
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ <h1>{{ info.name }}</h1>
dtick: 2,
fixedrange: true,
tickfont: { size: 16 },
range: [0, 22],
range: [0, 28],
title: {
font: { size: 18 },
text: "Score"
Expand Down
95 changes: 75 additions & 20 deletions app/reports/panel/aggregate_scores.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,84 @@
# Copyright (c) 2018-2023 Linh Pham
# graphs.wwdt.me is released under the terms of the Apache License 2.0
"""WWDTM Panel Aggregate Scores Data Retrieval Functions"""

from typing import List, Dict
from math import floor
from typing import Dict

from flask import current_app
import mysql.connector
from mysql.connector import connect


def empty_score_spread(use_decimal_scores: bool = False) -> Dict:
"""Generate an empty score spread dictionary of scores as keys
and a zero value for each key"""
if (
use_decimal_scores
and not current_app.config["app_settings"]["has_decimal_scores_column"]
):
return None

def retrieve_score_spread() -> List[Dict]:
"""Retrieve a list of grouped panelist scores from non-Best Of and
non-Repeat shows"""
database_connection = mysql.connector.connect(**current_app.config["database"])
cursor = database_connection.cursor(dictionary=False)
database_connection = connect(**current_app.config["database"])
query = (
"SELECT pm.panelistscore, COUNT(pm.panelistscore) "
"FROM ww_showpnlmap pm "
"JOIN ww_shows s ON s.showid = pm.showid "
"WHERE pm.panelistscore IS NOT NULL "
"AND s.bestof = 0 AND s.repeatshowid IS NULL "
"GROUP BY pm.panelistscore "
"ORDER BY pm.panelistscore ASC;"
"SELECT MIN(pm.panelistscore_decimal) AS min, "
"MAX(pm.panelistscore_decimal) AS max "
"FROM ww_showpnlmap pm;"
)
cursor = database_connection.cursor(named_tuple=True)
cursor.execute(query)
result = cursor.fetchone()

if not result:
None

min_score = result.min
max_score = result.max

if use_decimal_scores:
score_spread = {}
for score in range(floor(min_score), floor(max_score) + 1):
score_plus_half = score + 0.5
score_spread[score] = 0
score_spread[score_plus_half] = 0
else:
score_spread = {}
for score in range(min_score, max_score + 1):
score_spread[score] = 0

return score_spread


def retrieve_score_spread(use_decimal_scores: bool = False) -> Dict:
"""Retrieve a dictionary of grouped panelist scores from non-Best Of
and non-Repeat shows"""
if (
use_decimal_scores
and not current_app.config["app_settings"]["has_decimal_scores_column"]
):
return None

database_connection = connect(**current_app.config["database"])
if use_decimal_scores:
query = """
SELECT pm.panelistscore_decimal AS score,
COUNT(pm.panelistscore_decimal) AS count
FROM ww_showpnlmap pm
JOIN ww_shows s ON s.showid = pm.showid
WHERE pm.panelistscore_decimal IS NOT NULL
AND s.bestof = 0 AND s.repeatshowid IS NULL
GROUP BY pm.panelistscore_decimal
ORDER BY pm.panelistscore_decimal ASC;
"""
else:
query = """
SELECT pm.panelistscore AS score, COUNT(pm.panelistscore) AS count
FROM ww_showpnlmap pm
JOIN ww_shows s ON s.showid = pm.showid
WHERE pm.panelistscore IS NOT NULL
AND s.bestof = 0 AND s.repeatshowid IS NULL
GROUP BY pm.panelistscore
ORDER BY pm.panelistscore ASC;
"""
cursor = database_connection.cursor(named_tuple=True)
cursor.execute(query)
result = cursor.fetchall()
cursor.close()
Expand All @@ -33,10 +90,8 @@ def retrieve_score_spread() -> List[Dict]:
if not result:
return None

scores = []
counts = []
score_spread = empty_score_spread(use_decimal_scores=use_decimal_scores)
for row in result:
scores.append(row[0])
counts.append(row[1])
score_spread[row.score] = row.count

return {"scores": scores, "counts": counts}
return {"scores": list(score_spread.keys()), "counts": list(score_spread.values())}
Loading

0 comments on commit 34fda60

Please sign in to comment.