#!/usr/bin/env python3 """ Seed script: Populate audit.financial_ledger with 20 dummy records. Uses psycopg2 (sync) to bypass asyncpg prepared statement enum validation issues. """ import uuid import random import os from datetime import datetime, timedelta, timezone import psycopg2 TRANSACTION_TYPES = [ "SUBSCRIPTION", "COMMISSION", "REFUND", "MANUAL_ADJUSTMENT", "PURCHASE", "WITHDRAWAL", "BONUS", "FEE", "REWARD", ] DB_ENTRY_TYPES = ["CREDIT", "DEBIT"] DB_WALLET_TYPES = ["EARNED", "PURCHASED", "SERVICE_COINS", "VOUCHER"] DB_STATUSES = ["PENDING", "SUCCESS", "FAILED", "REFUNDED"] TEST_USER_IDS = [1, 2, 28, 29, 55, 75, 79, 85, 86, 88, 100, 102, 103, 104, 105, 106] NOW = datetime.now(timezone.utc) def get_dsn_params(): """Extract connection parameters from DATABASE_URL.""" db_url = os.environ.get("DATABASE_URL", "postgresql+asyncpg://service_finder_app:AppSafePass_2026@shared-postgres:5432/service_finder") db_url = db_url.replace("postgresql+asyncpg://", "postgresql://") # Parse URL parts = db_url.replace("postgresql://", "").split("@") user_pass = parts[0].split(":") host_db = parts[1].split("/") host_port = host_db[0].split(":") return { "user": user_pass[0], "password": user_pass[1], "host": host_port[0], "port": int(host_port[1]) if len(host_port) > 1 else 5432, "dbname": host_db[1].split("?")[0], } def seed(): params = get_dsn_params() conn = psycopg2.connect(**params) conn.autocommit = False try: with conn.cursor() as cur: # Count existing cur.execute("SELECT COUNT(*) FROM audit.financial_ledger") print(f"Existing records: {cur.fetchone()[0]}") # Get person_ids person_map = {} for uid in TEST_USER_IDS: cur.execute("SELECT person_id FROM identity.users WHERE id = %s", (uid,)) pid = cur.fetchone() if pid and pid[0]: person_map[uid] = pid[0] if not person_map: print("No users with person_id found!") return print(f"Users with person records: {list(person_map.keys())}") inserted = 0 for i in range(20): uid = random.choice(list(person_map.keys())) pid = person_map[uid] entry_t = random.choice(DB_ENTRY_TYPES) wallet_t = random.choice(DB_WALLET_TYPES) stat = random.choice(DB_STATUSES) txn_t = random.choice(TRANSACTION_TYPES) cur_r = random.choice(["HUF", "EUR", "USD", "GBP"]) amt = round(random.uniform(5.0, 5000.0), 2) days_ago = random.randint(0, 30) created_ts = NOW - timedelta( days=random.randint(0, days_ago), hours=random.randint(0, 23), minutes=random.randint(0, 59), ) ref = "TXN-" + uuid.uuid4().hex[:8].upper() desc = txn_t.lower().replace("_", " ").title() + f" for user {uid}" net_amt = round(amt / 1.27, 2) if random.random() > 0.3 else None tax_amt = round(amt - net_amt, 2) if net_amt else None balance = round(random.uniform(100, 50000), 2) inv_stat = "PAID" if stat == "completed" else stat.upper() txn_id = str(uuid.uuid4()) # psycopg2 handles text → enum cast correctly with simple execute cur.execute( """ INSERT INTO audit.financial_ledger (user_id, person_id, amount, currency, transaction_type, details, created_at, entry_type, balance_after, wallet_type, status, transaction_id, net_amount, tax_amount, gross_amount, invoice_status) VALUES ( %s, %s, %s, %s, %s, %s::jsonb, %s::timestamptz, %s::audit.ledger_entry_type, %s, %s::audit.wallet_type, %s::audit.ledger_status, %s::uuid, %s, %s, %s, %s ) """, ( uid, pid, amt, cur_r, txn_t, f'{{"description": "{desc}", "reference": "{ref}"}}', created_ts.isoformat(), entry_t, balance, wallet_t, stat, txn_id, net_amt, tax_amt, amt, inv_stat, ), ) inserted += 1 conn.commit() print(f"āœ… Inserted {inserted} records successfully!") cur.execute("SELECT COUNT(*) FROM audit.financial_ledger") print(f"Total records now: {cur.fetchone()[0]}") cur.execute(""" SELECT fl.entry_type::text, fl.wallet_type::text, fl.status::text, COUNT(*)::int, ROUND(AVG(fl.amount)::numeric, 2), u.email FROM audit.financial_ledger fl LEFT JOIN identity.users u ON u.id = fl.user_id GROUP BY fl.entry_type, fl.wallet_type, fl.status, u.email ORDER BY fl.entry_type, fl.wallet_type """) rows = cur.fetchall() total_sum = sum(r[3] for r in rows) if rows else 0 print(f"\nšŸ“Š Summary (total {total_sum} records):") print(f" {'Entry':8} {'Wallet':15} {'Status':10} {'Cnt':5} {'Avg Amt':10} {'User'}") print(f" {'-'*8} {'-'*15} {'-'*10} {'-'*5} {'-'*10} {'-'*30}") for r in rows: print(f" {r[0]:8} {r[1]:15} {r[2]:10} {r[3]:5} {r[4]:10} {r[5] or 'N/A':30}") finally: conn.close() if __name__ == "__main__": seed()