chore(billing): add backfill script for amount_usd on old purchase transactions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-05-25 17:14:39 +01:00
parent 783a89e825
commit bbbca5bdf8

View file

@ -0,0 +1,90 @@
"""
One-time migration: backfill amount_usd on purchase credit_transactions.
Matches pack by:
1. pack_id extracted from description "Purchased <pack_id> 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())