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_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_compress import Compress
# Initialize Flask extensions
db = SQLAlchemy()
migrate = Migrate()
bcrypt = Bcrypt()
login_manager = LoginManager()
compress = Compress()
login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'info'
@@ -26,6 +28,7 @@ def create_app():
migrate.init_app(app, db)
bcrypt.init_app(app)
login_manager.init_app(app)
compress.init_app(app)
# Import models here to avoid circular imports
from app.models import User # Import the User model

View File

@@ -35,12 +35,7 @@ def dashboard():
# Calculate weekly averages via SQL
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
# 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)
badges = calculate_progress_badges(current_user.id, user_tz)
# We will default to showing the list view on initial load
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],
}
def calculate_progress_badges(readings):
"""Generate badges based on user activity and milestones."""
now = datetime.utcnow().date()
def calculate_progress_badges(user_id, user_tz):
"""Generate badges based on user activity and milestones using optimized queries."""
now_local = datetime.now(user_tz).date()
badges = []
if not readings:
total_readings = Reading.query.filter_by(user_id=user_id).count()
if total_readings == 0:
return badges
# Use reversed() instead of re-sorting — readings come in desc order from DB
sorted_readings = list(reversed(readings))
streak_count = 1
daily_streak = True
# Fetch only timestamps (highly optimized compared to fetching full objects)
timestamps = db.session.query(Reading.timestamp).filter(Reading.user_id == user_id).order_by(Reading.timestamp.desc()).all()
previous_date = sorted_readings[0].timestamp.date()
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
for reading in sorted_readings[1:]:
current_date = reading.timestamp.date()
if distinct_dates:
most_recent_date = distinct_dates[0]
if (now_local - most_recent_date).days <= 1:
streak_count = 1
current_check_date = most_recent_date
for d in distinct_dates[1:]:
if (current_check_date - d).days == 1:
streak_count += 1
current_check_date = d
else:
break
if (current_date - previous_date).days == 1:
streak_count += 1
elif (current_date - previous_date).days > 1:
daily_streak = False
previous_date = current_date
if daily_streak and streak_count >= 1:
if streak_count >= 1:
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")
if daily_streak and streak_count >= 30 and previous_date == now:
if streak_count >= 30:
badges.append("Monthly Streak")
if all(5 <= r.timestamp.hour < 12 for r in sorted_readings[-7:]):
badges.append("Morning Riser: Logged Readings Every Morning for a Week")
if all(18 <= r.timestamp.hour <= 23 for r in sorted_readings[-7:]):
badges.append("Night Owl: Logged Readings Every Night for a Week")
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")
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")
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:
badges.append(f"{highest_milestone} Readings Logged")

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"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": "",
"license": "ISC",

View File

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

View File

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