- Complete dual-layer architecture for fraud detection - Phase 1: Docker Compose infrastructure (Kafka, Zookeeper, Redis) - Phase 2: Mock ML models (Layer 1 fast, Layer 2 deep) - Phase 3: FastAPI transaction ingestion with JWT auth - Phase 4: Kafka consumer pipeline with feature extraction - Phase 5: Test transaction producer with demo scenarios - Phase 6: Clean code structure and comprehensive documentation - Phase 7: End-to-end validation and demo readiness Demonstrates key system design patterns: - Microservices architecture with decoupled components - Asynchronous event processing via Kafka - Tiered decision-making (fast path vs deep analysis) - Resilience patterns (circuit breaker for graceful degradation) - Behavioral profiling with Redis caching Ready for academic demonstration with clear pipeline observability. Co-authored-by: Copilot <[email protected]>
229 lines
9.1 KiB
Python
229 lines
9.1 KiB
Python
"""
|
|
Test Transaction Producer
|
|
|
|
Generates demo transactions with controlled scenarios to showcase the pipeline.
|
|
|
|
Scenarios:
|
|
1. Normal transactions: low amounts, regular merchants, regular users
|
|
2. Suspicious transactions: large amounts, unknown merchants
|
|
3. Behavioral anomalies: large deviation from user history
|
|
4. Circuit breaker test: can be triggered manually
|
|
|
|
Usage:
|
|
python producer/simulate.py
|
|
"""
|
|
|
|
import requests
|
|
import json
|
|
import time
|
|
import logging
|
|
from typing import List, Tuple
|
|
from jose import jwt
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# CONFIGURATION
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
API_BASE_URL = "http://localhost:8000"
|
|
SECRET_KEY = "payguard-demo-secret-key-change-in-production"
|
|
ALGORITHM = "HS256"
|
|
|
|
# Logging setup
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s [%(levelname)s] %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# TOKEN GENERATION
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def generate_token(user_id: str) -> str:
|
|
"""Generate a JWT token for the given user"""
|
|
token = jwt.encode({"sub": user_id}, SECRET_KEY, algorithm=ALGORITHM)
|
|
return token
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# TRANSACTION SCENARIOS
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class TransactionScenario:
|
|
"""Represents a test transaction scenario"""
|
|
|
|
def __init__(self, name: str, user_id: str, amount: float,
|
|
merchant: str, description: str = ""):
|
|
self.name = name
|
|
self.user_id = user_id
|
|
self.amount = amount
|
|
self.merchant = merchant
|
|
self.description = description
|
|
|
|
def submit(self):
|
|
"""Submit this scenario to the API"""
|
|
token = generate_token(self.user_id)
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
payload = {
|
|
"amount": self.amount,
|
|
"merchant": self.merchant,
|
|
"description": self.description
|
|
}
|
|
|
|
logger.info(f"\n{'='*80}")
|
|
logger.info(f"[SCENARIO] {self.name}")
|
|
logger.info(f"[SCENARIO] User: {self.user_id} | Amount: ${self.amount:.2f} | Merchant: {self.merchant}")
|
|
logger.info(f"{'='*80}")
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{API_BASE_URL}/transaction",
|
|
headers=headers,
|
|
json=payload,
|
|
timeout=5
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
logger.info(f"[API RESPONSE] ✓ {result['message']}")
|
|
logger.info(f"[TRANSACTION ID] {result['transaction_id']}")
|
|
else:
|
|
logger.error(f"[API RESPONSE] ✗ Status {response.status_code}: {response.text}")
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
logger.error("[API RESPONSE] ✗ Cannot connect to API. Is it running on port 8000?")
|
|
except Exception as e:
|
|
logger.error(f"[API RESPONSE] ✗ Error: {str(e)}")
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# TEST SCENARIOS
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def get_demo_scenarios() -> List[TransactionScenario]:
|
|
"""Get all demo scenarios in a logical order"""
|
|
return [
|
|
# Scenario 1: Normal transaction (should be approved fast)
|
|
TransactionScenario(
|
|
name="1. NORMAL TRANSACTION (Low Risk)",
|
|
user_id="user_001",
|
|
amount=75.50,
|
|
merchant="Starbucks Coffee",
|
|
description="Regular coffee purchase"
|
|
),
|
|
|
|
# Scenario 2: Another normal transaction from same user
|
|
TransactionScenario(
|
|
name="2. NORMAL TRANSACTION #2 (Low Risk)",
|
|
user_id="user_001",
|
|
amount=120.00,
|
|
merchant="Target Retail",
|
|
description="Grocery shopping"
|
|
),
|
|
|
|
# Scenario 3: Large transaction from user with low history (suspicious)
|
|
TransactionScenario(
|
|
name="3. BEHAVIORAL ANOMALY (High Risk)",
|
|
user_id="user_001",
|
|
amount=8500.00,
|
|
merchant="unknown_offshore_merchant.biz",
|
|
description="Large purchase from unknown merchant"
|
|
),
|
|
|
|
# Scenario 4: New user, normal transaction
|
|
TransactionScenario(
|
|
name="4. NEW USER, NORMAL AMOUNT (Low Risk)",
|
|
user_id="user_002",
|
|
amount=45.00,
|
|
merchant="McDonald's Restaurant",
|
|
description="Meal purchase"
|
|
),
|
|
|
|
# Scenario 5: Existing user, normal range
|
|
TransactionScenario(
|
|
name="5. RETURNING USER, NORMAL (Low Risk)",
|
|
user_id="user_002",
|
|
amount=65.00,
|
|
merchant="CVS Pharmacy",
|
|
description="Pharmacy purchase"
|
|
),
|
|
|
|
# Scenario 6: User 3, establish history
|
|
TransactionScenario(
|
|
name="6. NEW USER, SMALL TRANSACTION (Low Risk)",
|
|
user_id="user_003",
|
|
amount=25.00,
|
|
merchant="Uber Eats",
|
|
description="Food delivery"
|
|
),
|
|
|
|
# Scenario 7: User 3, escalating pattern
|
|
TransactionScenario(
|
|
name="7. SAME USER, INCREASED AMOUNT (Medium Risk)",
|
|
user_id="user_003",
|
|
amount=150.00,
|
|
merchant="Amazon.com",
|
|
description="Online shopping"
|
|
),
|
|
|
|
# Scenario 8: User 3, huge spike (very suspicious)
|
|
TransactionScenario(
|
|
name="8. SUSPICIOUS PATTERN (Very High Risk)",
|
|
user_id="user_003",
|
|
amount=12500.00,
|
|
merchant="LuxuryWatches_CN.ru",
|
|
description="Suspicious high-value purchase"
|
|
),
|
|
]
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def main():
|
|
"""Run demo scenarios"""
|
|
|
|
logger.info("="*80)
|
|
logger.info("PayGuard Demo - Transaction Producer")
|
|
logger.info("="*80)
|
|
logger.info(f"API Base URL: {API_BASE_URL}")
|
|
logger.info("="*80)
|
|
|
|
# Check if API is available
|
|
try:
|
|
response = requests.get(f"{API_BASE_URL}/health", timeout=2)
|
|
if response.status_code == 200:
|
|
logger.info("✓ API is healthy and ready\n")
|
|
else:
|
|
logger.error("✗ API health check failed")
|
|
return
|
|
except:
|
|
logger.error("✗ Cannot connect to API. Make sure it's running:")
|
|
logger.error(" uvicorn api.main:app --reload\n")
|
|
return
|
|
|
|
scenarios = get_demo_scenarios()
|
|
|
|
for i, scenario in enumerate(scenarios, 1):
|
|
scenario.submit()
|
|
|
|
# Small delay between scenarios to avoid overwhelming
|
|
if i < len(scenarios):
|
|
logger.info(f"\n[DEMO] Waiting 3 seconds before next scenario...")
|
|
time.sleep(3)
|
|
|
|
logger.info(f"\n{'='*80}")
|
|
logger.info("✓ All demo scenarios submitted!")
|
|
logger.info("="*80)
|
|
logger.info("\nCheck the consumer terminal for detailed pipeline execution logs.")
|
|
logger.info("Open another terminal and query Redis to see the results:")
|
|
logger.info(" redis-cli")
|
|
logger.info(" > KEYS result:*")
|
|
logger.info(" > GET result:<transaction_id>")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|