Skip to content

Commit

Permalink
Implement better stats
Browse files Browse the repository at this point in the history
  • Loading branch information
hackenbergstefan committed Jul 27, 2024
1 parent 118d303 commit 910ee8f
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 84 deletions.
2 changes: 1 addition & 1 deletion coffeebuddy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def create_app(config=None):
else:
logging.getLogger(__name__).info('Using config file "config"')
app.config.from_object("config")
# app.config['SQLALCHEMY_ECHO'] = True
# app.config["SQLALCHEMY_ECHO"] = True
if config:
app.config.update(config)

Expand Down
110 changes: 102 additions & 8 deletions coffeebuddy/model.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import datetime
import calendar
import socket
from typing import Optional
from datetime import date, datetime, timedelta
from typing import List, Optional, Tuple

import flask
import sqlalchemy
from sqlalchemy import text


def db_weekday(column):
"""Helper to extract weekday for different database backends"""
if flask.current_app.db.engine.name == "postgresql":
return sqlalchemy.func.extract("dow", column)
else:
return sqlalchemy.func.strftime("%w", column)


def weekday(number):
"""
Helper to return the name of the weekday for given day number.
0: Sunday
1: Monday
..
"""
if number == 0:
return calendar.day_name[6]
else:
return calendar.day_name[number - 1]


class Serializer:
@staticmethod
def escape(obj):
Expand Down Expand Up @@ -58,9 +80,7 @@ def by_tag(tag):
def drinks_today(self):
return (
Drink.query.filter(Drink.user == self)
.filter(
flask.current_app.db.func.Date(Drink.timestamp) == datetime.date.today()
)
.filter(flask.current_app.db.func.Date(Drink.timestamp) == date.today())
.all()
)

Expand Down Expand Up @@ -131,6 +151,80 @@ def count_selected_manually(self, host: Optional[str] = None) -> int:
host = host or socket.gethostname()
return sum(d.selected_manually for d in self.drinks if d.host == host)

def drinks_this_week(self) -> Tuple[List[str], List[int]]:
db = flask.current_app.db
now = date.today()
start_of_week = datetime.combine(
now - timedelta(now.weekday()), datetime.min.time()
)

data = tuple(
zip(
*db.session.execute(
db.select(
db_weekday(Drink.timestamp).label("weekday"),
db.func.count(db.func.Date(Drink.timestamp)),
)
.where(Drink.userid == self.id)
.where(Drink.timestamp >= start_of_week)
.group_by("weekday")
.order_by("weekday")
).all()
)
)

if not data:
return [], []
return [weekday(int(i)) for i in data[0]], list(data[1])

def drinks_avg_week(self) -> Tuple[List[str], List[int]]:
db = flask.current_app.db

number_of_weeks = (
datetime.now()
- db.session.scalars(
db.select(db.func.min(Drink.timestamp)).filter_by(userid=self.id)
).first()
).days / 7
data = tuple(
zip(
*db.session.execute(
db.select(
db_weekday(Drink.timestamp).label("weekday"),
db.func.count(db.func.Date(Drink.timestamp)) / number_of_weeks,
)
.where(Drink.userid == self.id)
.group_by("weekday")
.order_by("weekday")
).all()
)
)
return [weekday(int(i)) for i in data[0]], list(data[1])

@staticmethod
def drinks_avg_week_all() -> Tuple[List[str], List[int]]:
db = flask.current_app.db

number_of_weeks = (
datetime.now() - db.session.scalars(db.func.min(Drink.timestamp)).first()
).days / 7
number_of_active_users = User.query.filter(User.enabled).count()
data = tuple(
zip(
*db.session.execute(
db.select(
db_weekday(Drink.timestamp).label("weekday"),
db.func.count(db.func.Date(Drink.timestamp))
/ number_of_weeks
/ number_of_active_users,
)
.group_by("weekday")
.order_by("weekday")
).all()
)
)
return [weekday(int(i)) for i in data[0]], list(data[1])


class Drink(flask.current_app.db.Model):
id = flask.current_app.db.Column(flask.current_app.db.Integer, primary_key=True)
Expand All @@ -148,7 +242,7 @@ class Drink(flask.current_app.db.Model):

def __init__(self, *args, **kwargs):
if "timestamp" not in kwargs:
kwargs["timestamp"] = datetime.datetime.now()
kwargs["timestamp"] = datetime.now()
if "host" not in kwargs:
kwargs["host"] = socket.gethostname()
super().__init__(*args, **kwargs)
Expand All @@ -162,7 +256,7 @@ def drinks_vs_days(timedelta):
),
flask.current_app.db.func.Date(Drink.timestamp),
)
.filter(Drink.timestamp > datetime.datetime.now() - timedelta)
.filter(Drink.timestamp > datetime.now() - timedelta)
.order_by(sqlalchemy.asc(flask.current_app.db.func.Date(Drink.timestamp)))
.group_by(flask.current_app.db.func.Date(Drink.timestamp))
.all()
Expand All @@ -184,7 +278,7 @@ class Pay(flask.current_app.db.Model):

def __init__(self, *args, **kwargs):
if "timestamp" not in kwargs:
kwargs["timestamp"] = datetime.datetime.now()
kwargs["timestamp"] = datetime.now()
if "host" not in kwargs:
kwargs["host"] = socket.gethostname()
super().__init__(*args, **kwargs)
Expand Down
40 changes: 1 addition & 39 deletions coffeebuddy/route_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@
from coffeebuddy.model import User, escapefromhex


class Color:
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b

def __str__(self):
return f"rgb({self.r}, {self.g}, {self.b})"

def brighter(self, factor):
r = self.r + (255 - self.r) * factor
g = self.g + (255 - self.g) * factor
b = self.b + (255 - self.b) * factor
return Color(r, g, b)


def init():
@flask.current_app.route("/stats.html", methods=["GET", "POST"])
def chart():
Expand All @@ -33,26 +17,4 @@ def chart():
return flask.redirect(f'coffee.html?tag={flask.request.args["tag"]}')
return flask.redirect("/")

berry = Color(171, 55, 122)

x = list(user.drink_days)
n = user.max_drinks_per_day
datasets = [
{
"x": x,
"y": [
f"1970-01-01T{user.nth_drink(date, i).timestamp.time().isoformat()}"
for date in x
],
"fill": "tozeroy",
"name": f"{i}. Coffee",
"mode": "markers",
"fillcolor": str(berry.brighter(1 - i / n)),
"line": {
"color": str(berry),
},
}
for i in range(n, 0, -1)
]

return flask.render_template("stats.html", user=user, datasets=datasets)
return flask.render_template("stats.html", user=user)
2 changes: 2 additions & 0 deletions coffeebuddy/route_coffee.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ def init():
def coffee():
flask.current_app.events.fire("route_coffee")
user: User = User.by_tag(escapefromhex(flask.request.args["tag"]))
print("drinks_this_week", user.drinks_this_week())
print("drinks_avg_week", user.drinks_avg_week())
if user is None:
return flask.render_template(
"cardnotfound.html", uuid=flask.request.args["tag"]
Expand Down
23 changes: 2 additions & 21 deletions coffeebuddy/templates/coffee.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
width: 300px;
height: 300px;
}

</style>
</head>

Expand Down Expand Up @@ -157,6 +156,7 @@ <h1 id="user-name">{{ user.prename }} {{ user.name }}</h1>
<code class="h5">{{ hexstr(user.tag) }}</code>
</div>
<div class="my-auto">
<div id="stats" class="flex-grow-1"></div>
<form method="post">
<button id="btn-coffee" type="submit" class="btn-coffee" name="coffee" value="coffee">
<div id="btn-coffee-icon" class="display-1 fas fa-coffee"></div>
Expand All @@ -165,26 +165,7 @@ <h1 id="user-name">{{ user.prename }} {{ user.name }}</h1>
</form>
</div>
</div>
<div class="d-flex m-4" style="width: 100px;">
<div class="position-relative h-100 w-100">
<div class="coffeemeter-title">
COFFEEMETER
</div>
<div class="coffeemeter-tag">
{{ len(user.drinks_today) }}
</div>
<div class="d-flex flex-column h-100">
<div class="display-2 fas {{ coffee_state[min(len(coffee_state) - 1, len(user.drinks_today))] }}"
style="{{ 'color: var(--danger);' if len(user.drinks_today) >= len(coffee_state) }}">
</div>
<div class="d-flex flex-column justify-content-end h-100 border mt-2 px-2">
<div id="coffeemeter-bar" class="bg-engineering-dark m-1 rounded"
style="height: {{ len(user.drinks_today) / len(coffee_state) * 100 | round }}%;
{{ 'background: var(--danger); color: white;' if len(user.drinks_today) >= len(coffee_state) }}">
</div>
</div>
</div>
</div>
<div class="d-flex m-4" style="width: 500px;">
</div>
</div>
<canvas id="matrix"></canvas>
Expand Down
45 changes: 30 additions & 15 deletions coffeebuddy/templates/stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="{{ url_for('static', filename='coffeebuddy.css') }}">

<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/2.3.0/plotly-basic.min.js" integrity="sha512-94titNdENTKSvhQZbcdBcW6kMstfKZsWqGBRCAF0UUmzPx2DKYRHIsAxXJnXxiyvbBD3eMxwotX9rccAjgkVPg==" crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/2.34.0/plotly.min.js" integrity="sha512-pH45RLZKz14g8UuQC8days10TDtKG3vXHpDH0UHjnF9HKxGYGzKELe1ship9QXFekhfRfRFEcpemtSvqT5E4oQ==" crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
<script src="{{ url_for('static', filename='autologout.js') }}" defer></script>
<script>
{% if not config['NOTIMEOUT'] %}
Expand All @@ -18,22 +18,37 @@
});
{% endif %}

$.getScript("https://cdnjs.cloudflare.com/ajax/libs/plotly.js/2.3.0/plotly-basic.min.js", () => {
data = {{ datasets|tojson }};
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/plotly.js/2.34.0/plotly.min.js", () => {
const drinks_this_week = {{ user.drinks_this_week() | tojson }};
const drinks_avg_week = {{ user.drinks_avg_week() | tojson }};
const drinks_avg_week_all = {{ user.drinks_avg_week_all() | tojson }};
const data = [
{
x: drinks_avg_week_all[0],
y: drinks_avg_week_all[1],
type: 'bar',
name: 'All avg week',
},
{
x: drinks_avg_week[0],
y: drinks_avg_week[1],
type: 'bar',
name: 'Yours avg week',
},
{
x: drinks_this_week[0],
y: drinks_this_week[1],
type: 'bar',
name: 'Yours this week',
},
];
layout = {
title: "Your Coffee Stats",
showlegend: false,
xaxis: {
tickfont: {
size: 16,
}
},
yaxis: {
tickformat: '%H:%M',
tickfont: {
size: 16,
}
title: 'Your Coffee Stats',
showlegend: true,
legend: {
orientation: 'h',
},
barcornerradius: 10,
plot_bgcolor: 'rgba(0,0,0,0)',
paper_bgcolor: 'rgba(0,0,0,0)',
margin: {
Expand Down

0 comments on commit 910ee8f

Please sign in to comment.