LifeRPG_v2.0/modern/backend/analytics.py
TLimoges33 7fe4ae5365
🧙‍♂️ Transform LifeRPG into The Wizard's Grimoire - Production-Ready Application
 Major Features Added:
- Complete magical theming and rebranding from LifeRPG to The Wizard's Grimoire
- Production-grade React frontend with Tailwind CSS v4 and magical aesthetics
- Comprehensive analytics dashboard with Recharts integration (ScryingPortal)
- Push notifications system with PWA service worker support
- Drag & drop functionality using @dnd-kit for habit reordering
- Social features with friends system and leaderboards
- Performance optimization tools and monitoring
- Mobile app enhancement with PWA installation support

🏗️ Technical Infrastructure:
- Advanced service worker with offline support and background sync
- Zustand state management for scalable application state
- Production-ready UI component system with enhanced Button, Card, Input
- Progressive Web App (PWA) with manifest and app installation
- FastAPI backend with comprehensive API endpoints
- Docker containerization and CI/CD pipeline setup

📱 Progressive Web App Features:
- Offline functionality with intelligent caching
- Push notification support for habit reminders
- App installation on mobile and desktop platforms
- Background sync for offline data management
- Performance monitoring and optimization tools

🎨 User Experience:
- Magical wizard/grimoire theming throughout application
- Responsive design optimized for all device sizes
- Drag & drop habit management with smooth animations
- Interactive analytics with multiple chart types
- Social connectivity with friends and competitive features
- Comprehensive notification and performance settings

🔧 Developer Experience:
- Modern development stack with Vite and React
- Comprehensive testing setup and CI/CD pipelines
- Code quality tools with pre-commit hooks
- Docker development environment
- Detailed documentation and implementation guides

This represents a complete transformation from prototype to production-ready application with enterprise-grade features and magical user experience.
2025-08-30 17:32:42 +00:00

326 lines
11 KiB
Python

"""
Analytics module for LifeRPG - habit tracking insights and visualizations.
"""
from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func, and_
import models
import json
def get_habit_heatmap(db: Session, user_id: int, days: int = 365) -> Dict:
"""Generate habit completion heatmap data for the last N days."""
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(days=days)
# Get all completions in the date range
completions = db.query(
func.date(models.Log.timestamp).label('date'),
func.count(models.Log.id).label('count')
).filter(
models.Log.user_id == user_id,
models.Log.action == 'complete',
models.Log.timestamp >= start_date
).group_by(
func.date(models.Log.timestamp)
).all()
# Create a map of date -> completion count
completion_map = {str(comp.date): comp.count for comp in completions}
# Generate full date range with completion counts
heatmap_data = []
current_date = start_date.date()
end_date_only = end_date.date()
while current_date <= end_date_only:
date_str = current_date.isoformat()
count = completion_map.get(date_str, 0)
heatmap_data.append({
'date': date_str,
'count': count,
'level': min(4, count) # 0-4 intensity levels for visualization
})
current_date += timedelta(days=1)
return {
'data': heatmap_data,
'total_days': days,
'completion_days': len(completion_map),
'total_completions': sum(completion_map.values()),
'start_date': start_date.date().isoformat(),
'end_date': end_date.date().isoformat()
}
def get_habit_trends(db: Session, user_id: int, habit_id: Optional[int] = None, days: int = 30) -> Dict:
"""Get habit completion trends over time."""
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(days=days)
# Base query
query = db.query(
func.date(models.Log.timestamp).label('date'),
func.count(models.Log.id).label('completions')
).filter(
models.Log.user_id == user_id,
models.Log.action == 'complete',
models.Log.timestamp >= start_date
)
# Filter by specific habit if provided
if habit_id:
query = query.filter(models.Log.habit_id == habit_id)
trends = query.group_by(
func.date(models.Log.timestamp)
).order_by(
func.date(models.Log.timestamp)
).all()
# Fill in missing dates with 0
trend_data = []
current_date = start_date.date()
trend_map = {str(trend.date): trend.completions for trend in trends}
while current_date <= end_date.date():
date_str = current_date.isoformat()
trend_data.append({
'date': date_str,
'completions': trend_map.get(date_str, 0)
})
current_date += timedelta(days=1)
# Calculate some basic stats
total_completions = sum(trend_map.values())
active_days = len([d for d in trend_data if d['completions'] > 0])
avg_per_day = total_completions / days if days > 0 else 0
return {
'data': trend_data,
'stats': {
'total_completions': total_completions,
'active_days': active_days,
'average_per_day': round(avg_per_day, 2),
'completion_rate': round((active_days / days) * 100, 1) if days > 0 else 0
},
'period': {
'days': days,
'start_date': start_date.date().isoformat(),
'end_date': end_date.date().isoformat()
}
}
def get_habit_breakdown(db: Session, user_id: int, days: int = 30) -> Dict:
"""Get breakdown of completions by habit."""
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(days=days)
# Get completions by habit
results = db.query(
models.Habit.id,
models.Habit.title,
func.count(models.Log.id).label('completions')
).join(
models.Log, models.Habit.id == models.Log.habit_id
).filter(
models.Habit.user_id == user_id,
models.Log.action == 'complete',
models.Log.timestamp >= start_date
).group_by(
models.Habit.id, models.Habit.title
).order_by(
func.count(models.Log.id).desc()
).all()
habit_data = []
total_completions = 0
for result in results:
completions = result.completions
total_completions += completions
habit_data.append({
'habit_id': result.id,
'habit_title': result.title,
'completions': completions
})
# Calculate percentages
for habit in habit_data:
habit['percentage'] = round((habit['completions'] / total_completions) * 100, 1) if total_completions > 0 else 0
return {
'habits': habit_data,
'total_completions': total_completions,
'period': {
'days': days,
'start_date': start_date.date().isoformat(),
'end_date': end_date.date().isoformat()
}
}
def get_streak_history(db: Session, user_id: int, days: int = 90) -> Dict:
"""Calculate streak history over time."""
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(days=days)
# Get all completion dates
completion_dates = db.query(
func.date(models.Log.timestamp).label('date')
).filter(
models.Log.user_id == user_id,
models.Log.action == 'complete',
models.Log.timestamp >= start_date
).group_by(
func.date(models.Log.timestamp)
).order_by(
func.date(models.Log.timestamp)
).all()
# Convert to set for fast lookup
completion_dates_set = {comp.date for comp in completion_dates}
# Calculate streak for each day
streak_data = []
current_date = start_date.date()
current_streak = 0
while current_date <= end_date.date():
if current_date in completion_dates_set:
current_streak += 1
else:
current_streak = 0
streak_data.append({
'date': current_date.isoformat(),
'streak': current_streak,
'completed': current_date in completion_dates_set
})
current_date += timedelta(days=1)
# Find longest streak in period
max_streak = max((day['streak'] for day in streak_data), default=0)
return {
'data': streak_data,
'max_streak': max_streak,
'current_streak': streak_data[-1]['streak'] if streak_data else 0,
'period': {
'days': days,
'start_date': start_date.date().isoformat(),
'end_date': end_date.date().isoformat()
}
}
def get_weekly_summary(db: Session, user_id: int, weeks: int = 12) -> Dict:
"""Get weekly completion summary."""
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(weeks=weeks)
# Get completions grouped by week
results = db.query(
func.strftime('%Y-%W', models.Log.timestamp).label('week'),
func.count(models.Log.id).label('completions')
).filter(
models.Log.user_id == user_id,
models.Log.action == 'complete',
models.Log.timestamp >= start_date
).group_by(
func.strftime('%Y-%W', models.Log.timestamp)
).order_by(
func.strftime('%Y-%W', models.Log.timestamp)
).all()
weekly_data = []
for result in results:
# Parse week string (YYYY-WW format)
year_week = result.week
completions = result.completions
weekly_data.append({
'week': year_week,
'completions': completions
})
return {
'data': weekly_data,
'total_weeks': weeks,
'period': {
'weeks': weeks,
'start_date': start_date.date().isoformat(),
'end_date': end_date.date().isoformat()
}
}
def get_performance_insights(db: Session, user_id: int) -> Dict:
"""Generate performance insights and recommendations."""
# Get basic stats
total_habits = db.query(models.Habit).filter(models.Habit.user_id == user_id).count()
active_habits = db.query(models.Habit).filter(
models.Habit.user_id == user_id,
models.Habit.status == 'active'
).count()
# Get completion data for last 30 days
thirty_days_ago = datetime.now(timezone.utc) - timedelta(days=30)
recent_completions = db.query(models.Log).filter(
models.Log.user_id == user_id,
models.Log.action == 'complete',
models.Log.timestamp >= thirty_days_ago
).count()
# Calculate completion rate
expected_completions = active_habits * 30 # Assuming daily habits
completion_rate = (recent_completions / expected_completions) * 100 if expected_completions > 0 else 0
# Get streak info
from . import gamification
current_streak = gamification.calculate_current_streak(db, user_id)
longest_streak = gamification.calculate_longest_streak(db, user_id)
# Generate insights
insights = []
if completion_rate < 50:
insights.append({
'type': 'warning',
'title': 'Low Completion Rate',
'message': f'Your completion rate is {completion_rate:.1f}%. Consider reducing the number of active habits or adjusting your routine.',
'action': 'Review your habits and focus on the most important ones.'
})
elif completion_rate > 80:
insights.append({
'type': 'success',
'title': 'Excellent Performance',
'message': f'Great job! You have a {completion_rate:.1f}% completion rate.',
'action': 'Consider adding new challenges or increasing habit difficulty.'
})
if current_streak == 0 and longest_streak > 0:
insights.append({
'type': 'motivation',
'title': 'Get Back on Track',
'message': f'You had a {longest_streak}-day streak before. You can do it again!',
'action': 'Start with one small habit to rebuild momentum.'
})
if current_streak >= 7:
insights.append({
'type': 'celebration',
'title': 'Great Streak!',
'message': f'You\'re on a {current_streak}-day streak. Keep it up!',
'action': 'Maintain consistency to reach the next milestone.'
})
return {
'stats': {
'total_habits': total_habits,
'active_habits': active_habits,
'completion_rate': round(completion_rate, 1),
'recent_completions': recent_completions,
'current_streak': current_streak,
'longest_streak': longest_streak
},
'insights': insights
}