Improve badge performance

This commit is contained in:
Peter Stockings
2026-03-10 19:23:56 +11:00
parent dcef99c3bf
commit 808143f92b
5 changed files with 45 additions and 38 deletions

View File

@@ -4,12 +4,14 @@ from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_login import LoginManager from flask_login import LoginManager
from flask_compress import Compress
# Initialize Flask extensions # Initialize Flask extensions
db = SQLAlchemy() db = SQLAlchemy()
migrate = Migrate() migrate = Migrate()
bcrypt = Bcrypt() bcrypt = Bcrypt()
login_manager = LoginManager() login_manager = LoginManager()
compress = Compress()
login_manager.login_view = 'auth.login' login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'info' login_manager.login_message_category = 'info'
@@ -26,6 +28,7 @@ def create_app():
migrate.init_app(app, db) migrate.init_app(app, db)
bcrypt.init_app(app) bcrypt.init_app(app)
login_manager.init_app(app) login_manager.init_app(app)
compress.init_app(app)
# Import models here to avoid circular imports # Import models here to avoid circular imports
from app.models import User # Import the User model from app.models import User # Import the User model

View File

@@ -35,12 +35,7 @@ def dashboard():
# Calculate weekly averages via SQL # Calculate weekly averages via SQL
systolic_avg, diastolic_avg, heart_rate_avg = calculate_weekly_summary_sql(current_user.id) systolic_avg, diastolic_avg, heart_rate_avg = calculate_weekly_summary_sql(current_user.id)
# Badges need paginated or all readings. We'll fetch all readings for badges badges = calculate_progress_badges(current_user.id, user_tz)
# Note: To avoid huge queries, we might want a specific badge query in the future.
# For now, let's fetch current month readings + previous as a rough approximation or keep behavior.
# A generic query for badges:
all_readings = Reading.query.filter_by(user_id=current_user.id).order_by(Reading.timestamp.desc()).all()
badges = calculate_progress_badges(all_readings)
# We will default to showing the list view on initial load # We will default to showing the list view on initial load
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)
@@ -248,47 +243,58 @@ def prepare_graph_data(readings):
'heart_rate': [r.heart_rate for r in readings], 'heart_rate': [r.heart_rate for r in readings],
} }
def calculate_progress_badges(readings): def calculate_progress_badges(user_id, user_tz):
"""Generate badges based on user activity and milestones.""" """Generate badges based on user activity and milestones using optimized queries."""
now = datetime.utcnow().date() now_local = datetime.now(user_tz).date()
badges = [] badges = []
if not readings: total_readings = Reading.query.filter_by(user_id=user_id).count()
if total_readings == 0:
return badges return badges
# Use reversed() instead of re-sorting — readings come in desc order from DB # Fetch only timestamps (highly optimized compared to fetching full objects)
sorted_readings = list(reversed(readings)) timestamps = db.session.query(Reading.timestamp).filter(Reading.user_id == user_id).order_by(Reading.timestamp.desc()).all()
streak_count = 0
if timestamps:
distinct_dates = []
last_date = None
for (ts,) in timestamps:
local_date = utc.localize(ts).astimezone(user_tz).date()
if local_date != last_date:
distinct_dates.append(local_date)
last_date = local_date
if distinct_dates:
most_recent_date = distinct_dates[0]
if (now_local - most_recent_date).days <= 1:
streak_count = 1 streak_count = 1
daily_streak = True current_check_date = most_recent_date
previous_date = sorted_readings[0].timestamp.date() for d in distinct_dates[1:]:
if (current_check_date - d).days == 1:
for reading in sorted_readings[1:]:
current_date = reading.timestamp.date()
if (current_date - previous_date).days == 1:
streak_count += 1 streak_count += 1
elif (current_date - previous_date).days > 1: current_check_date = d
daily_streak = False else:
break
previous_date = current_date if streak_count >= 1:
if daily_streak and streak_count >= 1:
badges.append(f"Current Streak: {streak_count} Days") badges.append(f"Current Streak: {streak_count} Days")
if daily_streak and streak_count >= 7: if streak_count >= 7:
badges.append("Logged Every Day for a Week") badges.append("Logged Every Day for a Week")
if streak_count >= 30:
if daily_streak and streak_count >= 30 and previous_date == now:
badges.append("Monthly Streak") badges.append("Monthly Streak")
if all(5 <= r.timestamp.hour < 12 for r in sorted_readings[-7:]): last_7_readings = db.session.query(Reading.timestamp).filter(Reading.user_id == user_id).order_by(Reading.timestamp.desc()).limit(7).all()
if len(last_7_readings) == 7:
if all(5 <= utc.localize(ts).astimezone(user_tz).hour < 12 for (ts,) in last_7_readings):
badges.append("Morning Riser: Logged Readings Every Morning for a Week") badges.append("Morning Riser: Logged Readings Every Morning for a Week")
if all(18 <= r.timestamp.hour <= 23 for r in sorted_readings[-7:]): if all(18 <= utc.localize(ts).astimezone(user_tz).hour <= 23 for (ts,) in last_7_readings):
badges.append("Night Owl: Logged Readings Every Night for a Week") badges.append("Night Owl: Logged Readings Every Night for a Week")
milestones = [10, 50, 100, 500, 1000, 5000, 10000] milestones = [10, 50, 100, 500, 1000, 5000, 10000]
highest_milestone = max((m for m in milestones if len(readings) >= m), default=None) highest_milestone = max((m for m in milestones if total_readings >= m), default=None)
if highest_milestone: if highest_milestone:
badges.append(f"{highest_milestone} Readings Logged") badges.append(f"{highest_milestone} Readings Logged")

View File

@@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "npx tailwindcss -o ./app/static/css/tailwind.css --minify", "build": "npx tailwindcss -o ./app/static/css/tailwind.css --minify",
"serve": "npx tailwindcss -o ./app/static/css/tailwind.css --watch" "serve": "npx tailwindcss -o ./app/static/css/tailwind.css --minify --watch"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",

View File

@@ -8,6 +8,7 @@ email-validator==2.2.0
exceptiongroup==1.2.2 exceptiongroup==1.2.2
flask==3.1.0 flask==3.1.0
Flask-Bcrypt==1.0.1 Flask-Bcrypt==1.0.1
Flask-Compress==1.14
Flask-Login==0.6.3 Flask-Login==0.6.3
Flask-Migrate==4.0.7 Flask-Migrate==4.0.7
flask-sqlalchemy==3.1.1 flask-sqlalchemy==3.1.1

View File

@@ -3,9 +3,6 @@ module.exports = {
content: ["./app/templates/**/*.html"], content: ["./app/templates/**/*.html"],
theme: { theme: {
extend: { extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
colors: { colors: {
primary: { primary: {
50: '#f0fdfa', 50: '#f0fdfa',