Spaces:
Sleeping
Sleeping
Ahmed Mostafa
feat: add scripts to migrate and recalculate user analytics and update dependency lockfile
fbfd0f5 | import os | |
| import sys | |
| from pathlib import Path | |
| from datetime import datetime, timezone, timedelta | |
| # Add the server root to the Python path | |
| server_root = Path(__file__).resolve().parent.parent | |
| sys.path.append(str(server_root)) | |
| from firebase_admin import firestore | |
| from src.db.firebase import get_firebase_db | |
| from src.utils.logger import setup_logger | |
| logger = setup_logger(__name__) | |
| def recalculate_analytics(): | |
| """ | |
| Recalculates all KPI fields for every user by iterating through their notes. | |
| """ | |
| db = get_firebase_db() | |
| if not db: | |
| logger.error("Database not initialized") | |
| return | |
| logger.info("Starting comprehensive analytics recalculation...") | |
| users_ref = db.collection('users') | |
| notes_ref = db.collection('notes') | |
| analytics_ref = db.collection('analytics') | |
| try: | |
| users = users_ref.stream() | |
| migrated_count = 0 | |
| now = datetime.now(timezone.utc) | |
| week_ago = now - timedelta(days=7) | |
| current_month = now.month | |
| current_year = now.year | |
| for user_doc in users: | |
| user_id = user_doc.id | |
| logger.info(f"Processing user: {user_id}") | |
| # Fetch all notes for this user | |
| user_notes = list(notes_ref.where('userId', '==', user_id).stream()) | |
| # --- Initialize KPI Counters --- | |
| notes_count = len(user_notes) | |
| category_count = {} | |
| favorite_notes_count = 0 | |
| total_key_points = 0 | |
| total_video_duration_seconds = 0 | |
| this_week_videos = 0 | |
| this_month_saved_hours = 0.0 | |
| # For streak calculation | |
| note_dates = set() | |
| for note_doc in user_notes: | |
| data = note_doc.to_dict() | |
| # Categories | |
| categories = [] | |
| if 'category' in data: | |
| cat_data = data['category'] | |
| if isinstance(cat_data, list): | |
| categories = cat_data | |
| elif isinstance(cat_data, str): | |
| categories = [cat_data] | |
| elif 'video_categories' in data and isinstance(data['video_categories'], list): | |
| categories = data['video_categories'] | |
| elif 'categories' in data and isinstance(data['categories'], list): | |
| categories = data['categories'] | |
| if 'Uncategorized' in categories: | |
| categories.remove('Uncategorized') | |
| if not categories: | |
| categories.append('Technology & AI') | |
| for cat in categories: | |
| category_count[cat] = category_count.get(cat, 0) + 1 | |
| # Favorites | |
| if data.get('isFavorite', False): | |
| favorite_notes_count += 1 | |
| # Key Points | |
| key_points = data.get('keyPoints', []) | |
| total_key_points += len(key_points) | |
| # Duration | |
| video_duration = data.get('videoDuration', data.get('video_duration', 0)) | |
| total_video_duration_seconds += video_duration | |
| # Dates (for streak, weekly, monthly stats) | |
| created_at = data.get('createdAt') | |
| if created_at: | |
| dt = created_at | |
| # Some data might have python datetimes if created directly | |
| if hasattr(created_at, 'timestamp'): | |
| # Handle Google Cloud Timestamp vs Python datetime | |
| dt_val = created_at | |
| # Store date string for streak calculation (YYYY-MM-DD) | |
| note_dates.add(dt.strftime('%Y-%m-%d')) | |
| # This week videos | |
| if dt >= week_ago: | |
| this_week_videos += 1 | |
| # This month saved hours (assuming videoDuration is the saved time roughly) | |
| if dt.month == current_month and dt.year == current_year: | |
| this_month_saved_hours += video_duration / 3600.0 | |
| # --- Finalize Aggregations --- | |
| total_minutes = total_video_duration_seconds // 60 | |
| total_saved_hours = total_video_duration_seconds / 3600.0 | |
| # Find favorite category | |
| favorite_category = 'None' | |
| if category_count: | |
| favorite_category = max(category_count.items(), key=lambda x: x[1])[0] | |
| # Calculate current streak | |
| current_streak = 0 | |
| check_date = now.date() | |
| # If they haven't posted today, check if they posted yesterday (streak is still alive) | |
| if check_date.strftime('%Y-%m-%d') not in note_dates: | |
| check_date = check_date - timedelta(days=1) | |
| while check_date.strftime('%Y-%m-%d') in note_dates: | |
| current_streak += 1 | |
| check_date -= timedelta(days=1) | |
| # Update analytics document | |
| analytics_data = { | |
| 'userId': user_id, | |
| 'notesCount': notes_count, | |
| 'categoryCount': category_count, | |
| 'favoriteCategory': favorite_category, | |
| 'favoriteNotesCount': favorite_notes_count, | |
| 'totalKeyPoints': total_key_points, | |
| 'totalMinutes': total_minutes, | |
| 'totalSavedHours': round(total_saved_hours, 2), | |
| 'thisWeekVideos': this_week_videos, | |
| 'thisMonthSavedHours': round(this_month_saved_hours, 2), | |
| 'currentStreak': current_streak, | |
| 'lastUpdated': firestore.SERVER_TIMESTAMP, | |
| } | |
| analytics_ref.document(user_id).set(analytics_data, merge=True) | |
| logger.info(f"Updated analytics for {user_id}: {notes_count} notes, {current_streak} streak.") | |
| migrated_count += 1 | |
| logger.info(f"Recalculation completed. Processed {migrated_count} users.") | |
| except Exception as e: | |
| logger.error(f"Error during recalculation: {e}", exc_info=True) | |
| if __name__ == "__main__": | |
| recalculate_analytics() | |