# Seed script to create sample POS and QR transactions for Risk Management testing # Run with: mix run priv/repo/seed_risk_transactions.exs alias PlatformCore.Repo alias RiskCore.PosTransaction alias DaProductApp.Transactions.Transaction IO.puts("\nšŸŽ² Creating sample transactions for Risk Management testing...") IO.puts("=" |> String.duplicate(80)) # Use a short merchant ID that fits in varchar(15) for POS transactions pos_merchant_id = "MID12345" qr_merchant_id = "test-merchant-001" IO.puts("Using POS Merchant ID: #{pos_merchant_id}") IO.puts("Using QR Merchant ID: #{qr_merchant_id}") # Helper to insert POS transaction via raw SQL defmodule TransactionSeeder do def insert_pos_transaction(attrs) do sql = """ INSERT INTO pos_transaction ( s_txn_type, s_tid, s_mid, total_amount, masked_card_no, reference_no, approval_code, response_code, mcc_code, currency_code, entry_mode, created_dateTime, updated_dateTime ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW() ) """ Repo.query!(sql, [ attrs[:txn_type], attrs[:terminal_id], attrs[:merchant_id], attrs[:amount], attrs[:card_number], attrs[:reference], attrs[:auth_code], attrs[:response], attrs[:mcc], attrs[:currency], attrs[:entry_mode] ]) end def get_last_insert_id do result = Repo.query!("SELECT LAST_INSERT_ID() as id") id = result.rows |> List.first() |> List.first() # If LAST_INSERT_ID() returns 0, get the max ID instead if id == 0 do max_result = Repo.query!("SELECT MAX(id) as id FROM pos_transaction") max_result.rows |> List.first() |> List.first() else id end end end # ============================================================================ # INSERT POS TRANSACTIONS # ============================================================================ IO.puts("\nšŸ“Š Creating POS Transactions...") IO.puts("-" |> String.duplicate(80)) pos_transactions = [ # High value international transaction (triggers Rule 7/8) %{ txn_type: "SALE", terminal_id: "TID12345", merchant_id: pos_merchant_id, amount: Decimal.new("150000.00"), card_number: "5***********1234", reference: "RRN001", auth_code: "AUTH01", response: "00", mcc: "5411", currency: "AED", entry_mode: "051" }, # High value domestic transaction (triggers Rule 2) %{ txn_type: "SALE", terminal_id: "TID12346", merchant_id: pos_merchant_id, amount: Decimal.new("1600000.00"), card_number: "4***********5678", reference: "RRN002", auth_code: "AUTH02", response: "00", mcc: "5411", currency: "AED", entry_mode: "051" }, # Round figure transaction (triggers round amount rules) %{ txn_type: "SALE", terminal_id: "TID12347", merchant_id: pos_merchant_id, amount: Decimal.new("50000.00"), card_number: "6***********9012", reference: "RRN003", auth_code: "AUTH03", response: "00", mcc: "5411", currency: "AED", entry_mode: "051" }, # Multiple transactions for velocity %{ txn_type: "SALE", terminal_id: "TID12348", merchant_id: pos_merchant_id, amount: Decimal.new("85000.00"), card_number: "5***********3456", reference: "RRN004", auth_code: "AUTH04", response: "00", mcc: "5411", currency: "AED", entry_mode: "051" }, %{ txn_type: "SALE", terminal_id: "TID12348", merchant_id: pos_merchant_id, amount: Decimal.new("95000.00"), card_number: "5***********3456", reference: "RRN005", auth_code: "AUTH05", response: "00", mcc: "5411", currency: "AED", entry_mode: "051" } ] pos_ids = Enum.map(pos_transactions, fn txn_data -> TransactionSeeder.insert_pos_transaction(txn_data) txn_id = TransactionSeeder.get_last_insert_id() IO.puts(" āœ… Created POS Transaction ##{txn_id} - Amount: #{txn_data.amount} AED - Card: #{txn_data.card_number}") txn_id end) # ============================================================================ # INSERT QR TRANSACTIONS # ============================================================================ IO.puts("\nšŸ“± Creating QR Transactions...") IO.puts("-" |> String.duplicate(80)) # Insert QR transactions qr_transaction_attrs = [ %{ transaction_id: "QR#{:os.system_time(:millisecond)}001", merchant_id: qr_merchant_id, device_id: "DEV001", transaction_amount: Decimal.new("125000.00"), status: "completed", pay_mode: "UPI", transaction_location: "Dubai Mall", processing_id: "PROC001" }, %{ transaction_id: "QR#{:os.system_time(:millisecond)}002", merchant_id: qr_merchant_id, device_id: "DEV002", transaction_amount: Decimal.new("75000.00"), status: "completed", pay_mode: "QR", transaction_location: "Mall of Emirates", processing_id: "PROC002" }, %{ transaction_id: "QR#{:os.system_time(:millisecond)}003", merchant_id: qr_merchant_id, device_id: "DEV003", transaction_amount: Decimal.new("100000.00"), status: "completed", pay_mode: "UPI", transaction_location: "Abu Dhabi Mall", processing_id: "PROC003" } ] qr_ids = Enum.map(qr_transaction_attrs, fn attrs -> now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) changeset = %Transaction{} |> Ecto.Changeset.change(attrs) |> Ecto.Changeset.put_change(:inserted_at, now) |> Ecto.Changeset.put_change(:updated_at, now) case Repo.insert(changeset) do {:ok, txn} -> IO.puts(" āœ… Created QR Transaction ##{txn.id} - ID: #{txn.transaction_id} - Amount: #{txn.transaction_amount} AED") txn.id {:error, changeset} -> IO.puts(" āŒ Failed to create QR transaction: #{inspect(changeset.errors)}") nil end end) |> Enum.filter(&(!is_nil(&1))) # ============================================================================ # TRIGGER RISK EVALUATION # ============================================================================ IO.puts("\nšŸ” Triggering Risk Rule Evaluation...") IO.puts("-" |> String.duplicate(80)) # Evaluate POS transactions Enum.each(pos_ids, fn txn_id -> try do # Fetch transaction data directly with SQL result = Repo.query!(""" SELECT id, s_mid as merchant_id, total_amount, masked_card_no as card_number, reference_no, s_tid as terminal_id, currency_code, response_code, mcc_code, entry_mode, created_dateTime FROM pos_transaction WHERE id = ? """, [txn_id]) if result.num_rows > 0 do row = List.first(result.rows) transaction_map = %{ id: Enum.at(row, 0), merchant_id: Enum.at(row, 1), amount: Enum.at(row, 2), card_number: Enum.at(row, 3), reference_number: Enum.at(row, 4), terminal_id: Enum.at(row, 5), currency: Enum.at(row, 6), response_code: Enum.at(row, 7), mcc_code: Enum.at(row, 8), entry_mode: Enum.at(row, 9), transaction_datetime: Enum.at(row, 10) } # Evaluate against risk rules hits = RiskCore.Context.evaluate_transaction_risks(transaction_map, "POS") # Create risk rule hits hit_count = Enum.count(hits, fn hit_data -> case RiskCore.Context.create_risk_rule_hit(hit_data) do {:ok, _} -> true {:error, _} -> false end end) IO.puts(" āœ… POS Transaction ##{txn_id} evaluated - #{hit_count} risk rule(s) triggered") else IO.puts(" āš ļø POS Transaction ##{txn_id} not found in database") end rescue e -> IO.puts(" āŒ Error evaluating POS ##{txn_id}: #{Exception.message(e)}") end end) # Evaluate QR transactions Enum.each(qr_ids, fn txn_id -> try do # Fetch transaction data directly with SQL result = Repo.query!(""" SELECT id, merchant_id, transaction_amount, pay_mode, processing_id, device_id, status, transaction_location, transaction_id as txn_ref, inserted_at FROM transactions WHERE id = ? """, [txn_id]) if result.num_rows > 0 do row = List.first(result.rows) transaction_map = %{ id: Enum.at(row, 0), merchant_id: Enum.at(row, 1), amount: Enum.at(row, 2), payment_mode: Enum.at(row, 3), processing_id: Enum.at(row, 4), device_id: Enum.at(row, 5), status: Enum.at(row, 6), location: Enum.at(row, 7), transaction_id: Enum.at(row, 8), transaction_datetime: Enum.at(row, 9) } # Evaluate against risk rules hits = RiskCore.Context.evaluate_transaction_risks(transaction_map, "QR") # Create risk rule hits hit_count = Enum.count(hits, fn hit_data -> case RiskCore.Context.create_risk_rule_hit(hit_data) do {:ok, _} -> true {:error, _} -> false end end) IO.puts(" āœ… QR Transaction ##{txn_id} evaluated - #{hit_count} risk rule(s) triggered") else IO.puts(" āš ļø QR Transaction ##{txn_id} not found in database") end rescue e -> IO.puts(" āŒ Error evaluating QR ##{txn_id}: #{Exception.message(e)}") end end) # ============================================================================ # SUMMARY # ============================================================================ IO.puts("\n" <> ("=" |> String.duplicate(80))) IO.puts("šŸ“ˆ SUMMARY") IO.puts("=" |> String.duplicate(80)) # Count risk rule hits hit_count = Repo.query!("SELECT COUNT(*) as count FROM risk_rule_hits") total_hits = hit_count.rows |> List.first() |> List.first() IO.puts(" POS Transactions Created: #{length(pos_ids)}") IO.puts(" QR Transactions Created: #{length(qr_ids)}") IO.puts(" Total Risk Rule Hits: #{total_hits}") # Show recent hits recent_hits = Repo.query!(""" SELECT rh.id, rh.transaction_id, rh.transaction_type, rh.status, r.name as rule_name FROM risk_rule_hits rh JOIN risk_rules r ON r.id = rh.rule_id ORDER BY rh.triggered_at DESC LIMIT 10 """) if recent_hits.num_rows > 0 do IO.puts("\n Recent Rule Hits:") Enum.each(recent_hits.rows, fn [id, txn_id, txn_type, status, rule_name] -> IO.puts(" • Hit ##{id} - Txn: #{txn_id} (#{txn_type}) - Rule: #{rule_name} - Status: #{status}") end) end IO.puts("\nāœ… Seed script completed successfully!") IO.puts("🌐 Check the Risk Management Dashboard at: http://localhost:4099/risk-management/supervisor") IO.puts("=" |> String.duplicate(80))