Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Migration Script: Fix Ratings and Reviews Count | |
| This script ensures all listings and users have correct rating/reviews_count: | |
| 1. Users: Recalculates rating and reviews_count from actual reviews | |
| 2. Short-stay listings: Recalculates rating and reviews_count from actual reviews | |
| 3. Non-short-stay listings: Will use owner's rating (handled at fetch time) | |
| Run with: python scripts/fix_ratings.py | |
| """ | |
| import asyncio | |
| import sys | |
| import os | |
| # Add parent directory to path for imports | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from motor.motor_asyncio import AsyncIOMotorClient | |
| from bson import ObjectId | |
| from datetime import datetime | |
| # Import app config to get correct MongoDB URL | |
| from app.config import settings | |
| async def get_database(): | |
| """Connect to MongoDB using app settings""" | |
| client = AsyncIOMotorClient( | |
| settings.MONGODB_URL, | |
| serverSelectionTimeoutMS=5000, | |
| ) | |
| # Test connection | |
| await client.admin.command("ping") | |
| print(f" π¦ Database: {settings.MONGODB_DATABASE}") | |
| return client[settings.MONGODB_DATABASE] | |
| async def recalculate_user_ratings(db): | |
| """ | |
| Recalculate rating and reviews_count for all users | |
| based on actual reviews in the reviews collection. | |
| """ | |
| print("\n" + "="*60) | |
| print("STEP 2: Recalculating User Ratings") | |
| print("="*60) | |
| # Get all users | |
| users_cursor = db.users.find({}) | |
| users = await users_cursor.to_list(length=None) | |
| print(f"Found {len(users)} users to process") | |
| updated_count = 0 | |
| for user in users: | |
| user_id = str(user["_id"]) | |
| # Find all reviews where this user is the target | |
| reviews_cursor = db.reviews.find({ | |
| "target_type": "user", | |
| "target_id": user_id | |
| }) | |
| reviews = await reviews_cursor.to_list(length=None) | |
| # Calculate new rating | |
| if reviews: | |
| total_rating = sum(r.get("rating", 0) for r in reviews) | |
| reviews_count = len(reviews) | |
| avg_rating = round(total_rating / reviews_count, 2) | |
| else: | |
| reviews_count = 0 | |
| avg_rating = 0.0 | |
| # Get current values | |
| current_rating = user.get("rating", 0.0) | |
| current_count = user.get("reviews_count", 0) | |
| # Update if different or missing | |
| if current_rating != avg_rating or current_count != reviews_count: | |
| await db.users.update_one( | |
| {"_id": user["_id"]}, | |
| {"$set": { | |
| "rating": avg_rating, | |
| "reviews_count": reviews_count | |
| }} | |
| ) | |
| updated_count += 1 | |
| print(f" β User {user.get('firstName', 'Unknown')} {user.get('lastName', '')}: " | |
| f"{current_rating} β {avg_rating} ({reviews_count} reviews)") | |
| print(f"\nπ Updated {updated_count} users") | |
| return updated_count | |
| async def recalculate_listing_ratings(db): | |
| """ | |
| Recalculate rating and reviews_count for all SHORT-STAY listings | |
| based on actual reviews in the reviews collection. | |
| Non-short-stay listings use the owner's rating (handled at fetch time). | |
| """ | |
| print("\n" + "="*60) | |
| print("STEP 3: Recalculating Short-Stay Listing Ratings") | |
| print("="*60) | |
| # Get all short-stay listings | |
| listings_cursor = db.listings.find({"listing_type": "short-stay"}) | |
| listings = await listings_cursor.to_list(length=None) | |
| print(f"Found {len(listings)} short-stay listings to process") | |
| updated_count = 0 | |
| for listing in listings: | |
| listing_id = str(listing["_id"]) | |
| # Find all reviews where this listing is the target | |
| reviews_cursor = db.reviews.find({ | |
| "target_type": "listing", | |
| "target_id": listing_id | |
| }) | |
| reviews = await reviews_cursor.to_list(length=None) | |
| # Calculate new rating | |
| if reviews: | |
| total_rating = sum(r.get("rating", 0) for r in reviews) | |
| reviews_count = len(reviews) | |
| avg_rating = round(total_rating / reviews_count, 2) | |
| else: | |
| reviews_count = 0 | |
| avg_rating = 0.0 | |
| # Get current values | |
| current_rating = listing.get("rating", 0.0) | |
| current_count = listing.get("reviews_count", 0) | |
| # Update if different or missing | |
| if current_rating != avg_rating or current_count != reviews_count: | |
| await db.listings.update_one( | |
| {"_id": listing["_id"]}, | |
| {"$set": { | |
| "rating": avg_rating, | |
| "reviews_count": reviews_count | |
| }} | |
| ) | |
| updated_count += 1 | |
| print(f" β Listing '{listing.get('title', 'Untitled')[:40]}': " | |
| f"{current_rating} β {avg_rating} ({reviews_count} reviews)") | |
| print(f"\nπ Updated {updated_count} short-stay listings") | |
| return updated_count | |
| async def ensure_fields_exist(db): | |
| """ | |
| Ensure all users and listings have the rating/reviews_count fields. | |
| This initializes them to 0 if missing. | |
| """ | |
| print("\n" + "="*60) | |
| print("STEP 1: Ensuring Fields Exist on All Documents") | |
| print("="*60) | |
| # Update users missing rating field | |
| users_result = await db.users.update_many( | |
| {"rating": {"$exists": False}}, | |
| {"$set": {"rating": 0.0}} | |
| ) | |
| print(f" β Added 'rating' field to {users_result.modified_count} users") | |
| # Update users missing reviews_count field | |
| users_result2 = await db.users.update_many( | |
| {"reviews_count": {"$exists": False}}, | |
| {"$set": {"reviews_count": 0}} | |
| ) | |
| print(f" β Added 'reviews_count' field to {users_result2.modified_count} users") | |
| # Update short-stay listings missing rating field | |
| listings_result = await db.listings.update_many( | |
| {"listing_type": "short-stay", "rating": {"$exists": False}}, | |
| {"$set": {"rating": 0.0}} | |
| ) | |
| print(f" β Added 'rating' field to {listings_result.modified_count} short-stay listings") | |
| # Update short-stay listings missing reviews_count field | |
| listings_result2 = await db.listings.update_many( | |
| {"listing_type": "short-stay", "reviews_count": {"$exists": False}}, | |
| {"$set": {"reviews_count": 0}} | |
| ) | |
| print(f" β Added 'reviews_count' field to {listings_result2.modified_count} short-stay listings") | |
| async def print_summary(db): | |
| """Print a summary of the current state""" | |
| print("\n" + "="*60) | |
| print("SUMMARY") | |
| print("="*60) | |
| # Count users with reviews | |
| users_with_reviews = await db.users.count_documents({"reviews_count": {"$gt": 0}}) | |
| total_users = await db.users.count_documents({}) | |
| print(f" π₯ Users with reviews: {users_with_reviews}/{total_users}") | |
| # Count listings by type | |
| for listing_type in ["rent", "sale", "short-stay", "roommate"]: | |
| count = await db.listings.count_documents({"listing_type": listing_type}) | |
| if listing_type == "short-stay": | |
| with_reviews = await db.listings.count_documents({ | |
| "listing_type": listing_type, | |
| "reviews_count": {"$gt": 0} | |
| }) | |
| print(f" π {listing_type}: {count} listings ({with_reviews} with reviews)") | |
| else: | |
| print(f" π {listing_type}: {count} listings (use owner's rating)") | |
| # Count total reviews | |
| total_reviews = await db.reviews.count_documents({}) | |
| user_reviews = await db.reviews.count_documents({"target_type": "user"}) | |
| listing_reviews = await db.reviews.count_documents({"target_type": "listing"}) | |
| print(f"\n β Total reviews: {total_reviews}") | |
| print(f" - User reviews (for landlords): {user_reviews}") | |
| print(f" - Listing reviews (for properties): {listing_reviews}") | |
| async def main(): | |
| """Main migration function""" | |
| print("\n" + "="*60) | |
| print("π§ RATING MIGRATION SCRIPT") | |
| print("="*60) | |
| print(f"Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| try: | |
| db = await get_database() | |
| print(f"β Connected to MongoDB successfully") | |
| # Step 1: Ensure all fields exist | |
| await ensure_fields_exist(db) | |
| # Step 2: Recalculate user ratings | |
| await recalculate_user_ratings(db) | |
| # Step 3: Recalculate short-stay listing ratings | |
| await recalculate_listing_ratings(db) | |
| # Print summary | |
| await print_summary(db) | |
| print("\n" + "="*60) | |
| print("β MIGRATION COMPLETED SUCCESSFULLY") | |
| print("="*60) | |
| print(f"Finished at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| except Exception as e: | |
| print(f"\nβ ERROR: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| asyncio.run(main()) | |