Improve badge performance
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 = 1
|
|
||||||
daily_streak = True
|
|
||||||
|
|
||||||
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:]:
|
if distinct_dates:
|
||||||
current_date = reading.timestamp.date()
|
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:
|
if streak_count >= 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:
|
|
||||||
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()
|
||||||
badges.append("Morning Riser: Logged Readings Every Morning for a Week")
|
if len(last_7_readings) == 7:
|
||||||
|
if all(5 <= utc.localize(ts).astimezone(user_tz).hour < 12 for (ts,) in last_7_readings):
|
||||||
if all(18 <= r.timestamp.hour <= 23 for r in sorted_readings[-7:]):
|
badges.append("Morning Riser: Logged Readings Every Morning for a Week")
|
||||||
badges.append("Night Owl: Logged Readings Every Night 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]
|
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")
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user