#!/usr/bin/env elixir # Test script to create sample POS/QR transactions and verify risk rule evaluation # Usage: mix run test_risk_rules_with_transactions.exs alias PlatformCore.Repo alias RiskCore.Context, as: RiskManagement IO.puts("\n๐Ÿงช Risk Rules Testing - Creating Sample Transactions\n") IO.puts("=" |> String.duplicate(70)) # Get or create a test merchant merchant_id = 1 terminal_id = "TEST_TERMINAL_001" current_time = NaiveDateTime.utc_now() # Sample test transactions covering different rule scenarios test_scenarios = [ %{ name: "High Value International Transaction (Should trigger Rule 1)", data: %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "5399****1234", card_type: "international", transaction_amount: 15000.00, # Above 10,000 AED threshold currency: "AED", transaction_type: "pos", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.utc_now(), rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "chip" } }, %{ name: "Multiple International Transactions Low Value (Should trigger Rule 2)", transactions: [ %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "5299****5678", card_type: "international", transaction_amount: 3500.00, # Between 2,000-5,000 AED currency: "AED", transaction_type: "pos", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.utc_now(), rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "chip" }, %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "5299****5678", card_type: "international", transaction_amount: 4000.00, # Second transaction within 30 min currency: "AED", transaction_type: "pos", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.add(Time.utc_now(), 900, :second), # 15 min later rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "chip" } ] }, %{ name: "Round Figure Transaction (Should trigger Rule 32)", data: %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "4111****1111", card_type: "domestic", transaction_amount: 1000.00, # Exact round number currency: "AED", transaction_type: "qr", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.utc_now(), rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5812", entry_mode: "qr_scan" } }, %{ name: "High Velocity Transaction (Should trigger Rule 6)", transactions: Enum.map(1..6, fn i -> %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "4111****2222", card_type: "domestic", transaction_amount: 150.00 + i * 10, currency: "AED", transaction_type: "pos", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.add(Time.utc_now(), i * 60, :second), # 1 min apart rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "contactless" } end) }, %{ name: "Fallback Transaction (Should trigger Rule 13)", data: %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "5399****3333", card_type: "international", transaction_amount: 5500.00, currency: "AED", transaction_type: "pos", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.utc_now(), rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "fallback" # Fallback entry mode } }, %{ name: "Wrong PIN Attempts (Should trigger Rule 34)", transactions: [ %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "4111****4444", card_type: "domestic", transaction_amount: 500.00, currency: "AED", transaction_type: "pos", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.utc_now(), rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "chip" }, # Multiple wrong PIN attempts before the successful one %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "4111****4444", card_type: "domestic", transaction_amount: 500.00, currency: "AED", transaction_type: "pos", response_code: "55", # Wrong PIN transaction_status: "declined", transaction_date: current_time, transaction_time: Time.add(Time.utc_now(), -180, :second), # 3 min before rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "chip" }, %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "4111****4444", card_type: "domestic", transaction_amount: 500.00, currency: "AED", transaction_type: "pos", response_code: "55", # Wrong PIN transaction_status: "declined", transaction_date: current_time, transaction_time: Time.add(Time.utc_now(), -120, :second), # 2 min before rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5411", entry_mode: "chip" } ] }, %{ name: "Fraud-Suspect Declined Transaction (Should trigger Rule 33)", transactions: [ %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "5299****5555", card_type: "international", transaction_amount: 2000.00, currency: "AED", transaction_type: "qr", response_code: "00", transaction_status: "approved", transaction_date: current_time, transaction_time: Time.utc_now(), rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5812", entry_mode: "qr_scan" }, # Fraud-suspect decline shortly after %{ merchant_id: merchant_id, terminal_id: terminal_id, card_number: "5299****5555", card_type: "international", transaction_amount: 1500.00, currency: "AED", transaction_type: "qr", response_code: "07", # Fraud-suspect response code transaction_status: "declined", transaction_date: current_time, transaction_time: Time.add(Time.utc_now(), 300, :second), # 5 min later rrn: "#{:rand.uniform(999999999999)}", stan: "#{:rand.uniform(999999)}", mcc: "5812", entry_mode: "qr_scan" } ] } ] # Insert test transactions IO.puts("\n๐Ÿ“ Creating Test Transactions...") IO.puts("-" |> String.duplicate(70)) inserted_transaction_ids = [] Enum.each(test_scenarios, fn scenario -> IO.puts("\nโœ“ Scenario: #{scenario.name}") if Map.has_key?(scenario, :data) do # Single transaction case Repo.insert(%PlatformCore.PosTransaction{} |> struct(scenario.data)) do {:ok, txn} -> IO.puts(" โ†’ Created transaction ID: #{txn.id} | Amount: #{txn.transaction_amount} AED") inserted_transaction_ids = [txn.id | inserted_transaction_ids] {:error, changeset} -> IO.puts(" โœ— Error: #{inspect(changeset.errors)}") end else # Multiple transactions Enum.each(scenario.transactions, fn txn_data -> case Repo.insert(%PlatformCore.PosTransaction{} |> struct(txn_data)) do {:ok, txn} -> IO.puts(" โ†’ Created transaction ID: #{txn.id} | Amount: #{txn.transaction_amount} AED | Status: #{txn.transaction_status}") inserted_transaction_ids = [txn.id | inserted_transaction_ids] {:error, changeset} -> IO.puts(" โœ— Error: #{inspect(changeset.errors)}") end end) end end) IO.puts("\n" <> "=" |> String.duplicate(70)) IO.puts("๐Ÿ“Š TOTAL TRANSACTIONS CREATED: #{length(inserted_transaction_ids)}") IO.puts("=" |> String.duplicate(70)) # Now evaluate all transactions against all risk rules IO.puts("\n๐Ÿ” Evaluating Transactions Against Risk Rules...") IO.puts("-" |> String.duplicate(70)) # Get all active rules for Cat D (most lenient - good for testing) rules = RiskManagement.list_risk_rules(category: "Cat D", enabled_only: true) IO.puts("\nโœ“ Found #{length(rules)} active risk rules for evaluation") # Evaluate each transaction total_hits = 0 Enum.each(inserted_transaction_ids, fn txn_id -> transaction = Repo.get(PlatformCore.PosTransaction, txn_id) if transaction do IO.puts("\n๐Ÿ“‹ Transaction ID #{txn_id}:") IO.puts(" Amount: #{transaction.transaction_amount} AED") IO.puts(" Type: #{transaction.transaction_type}") IO.puts(" Card: #{transaction.card_type}") IO.puts(" Entry Mode: #{transaction.entry_mode}") IO.puts(" Response: #{transaction.response_code}") hits = Enum.reduce(rules, [], fn rule, acc -> result = RiskManagement.evaluate_single_rule(rule, transaction, merchant_id) case result do {:triggered, reason, severity} -> IO.puts(" ๐Ÿšจ TRIGGERED: #{rule.name} (#{severity})") IO.puts(" Reason: #{reason}") total_hits = total_hits + 1 [{rule, reason, severity} | acc] :passed -> acc end end) if Enum.empty?(hits) do IO.puts(" โœ“ No rules triggered") end end end) IO.puts("\n" <> "=" |> String.duplicate(70)) IO.puts("๐ŸŽฏ RISK EVALUATION COMPLETE") IO.puts("=" |> String.duplicate(70)) IO.puts("Total Transactions: #{length(inserted_transaction_ids)}") IO.puts("Total Rule Hits: #{total_hits}") IO.puts("=" |> String.duplicate(70)) IO.puts("\nโœ… Test completed! Check the results above.") IO.puts("๐Ÿ’ก To view in dashboard: http://localhost:4099/risk-management/supervisor\n")