""" One-time migration: backfill amount_usd on purchase credit_transactions. Matches pack by: 1. pack_id extracted from description "Purchased pack (...)" 2. Fallback: closest credits count match from app_settings packs 3. Fallback: test pack = $1.00 """ import asyncio import re import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) PACK_PRICES = { "starter": 49.0, "pro": 199.0, "scale": 499.0, "test": 1.0, } CREDITS_TO_PACK = { 50: ("starter", 49.0), 220: ("pro", 199.0), 600: ("scale", 499.0), } def _resolve_price(description: str, credits: int) -> float | None: """Return USD price for a transaction, or None if can't determine.""" # Try to extract pack_id from description m = re.match(r"Purchased\s+(\S+)\s+pack", description or "", re.IGNORECASE) if m: pack_id = m.group(1).lower() if pack_id in PACK_PRICES: return PACK_PRICES[pack_id] # Fallback: exact credits match if credits in CREDITS_TO_PACK: return CREDITS_TO_PACK[credits][1] # Fallback: closest credits match within 10% for c, (_, price) in CREDITS_TO_PACK.items(): if abs(c - credits) / max(c, 1) < 0.1: return price return None async def main(): from motor.motor_asyncio import AsyncIOMotorClient from app.config import Config mongo_uri = os.environ.get("MONGO_URI", Config.MONGO_URI) db_name = mongo_uri.rstrip("/").split("/")[-1].split("?")[0] or "cohorta_db" client = AsyncIOMotorClient(mongo_uri) db = client[db_name] cursor = db.credit_transactions.find({ "type": "purchase", "amount_usd": {"$exists": False}, }) updated = 0 skipped = 0 async for tx in cursor: description = tx.get("description", "") credits = tx.get("amount", 0) price = _resolve_price(description, credits) if price is None: print(f" SKIP id={tx['_id']} desc='{description}' credits={credits} — cannot determine price") skipped += 1 continue await db.credit_transactions.update_one( {"_id": tx["_id"]}, {"$set": {"amount_usd": price}}, ) print(f" SET id={tx['_id']} desc='{description}' credits={credits} → ${price}") updated += 1 print(f"\nDone: {updated} updated, {skipped} skipped.") client.close() if __name__ == "__main__": asyncio.run(main())