Initial PayGuard fraud detection system implementation

- 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]>
This commit is contained in:
John Patrick TeruelandCopilot committed 2026-04-24 19:47:18 +08:00
commit aa4dd497e2
24 files changed
+2343

No files matched your search

+227
View File
@@ -0,0 +1,227 @@
"""
Kafka Consumer Pipeline Orchestrator
Responsibilities:
- Connect to Kafka and consume transactions
- Orchestrate the entire fraud detection pipeline
- Coordinate all steps: feature extraction → scoring → decision → storage
Academic Point:
This is where the architecture comes alive. By clearly separating concerns
(features, models, decisions), the code becomes testable and maintainable.
Each step is independently verifiable, making it suitable for academic demonstration.
"""
import json
import logging
from kafka import KafkaConsumer
from kafka.errors import KafkaError
from features import FeatureExtractor
from models import ModelScorer
from decision import DecisionEngine
# ══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ══════════════════════════════════════════════════════════════════════════════
KAFKA_BROKER = "localhost:9092"
KAFKA_TOPIC = "transactions"
KAFKA_GROUP = "fraud-detection-consumer-group"
# Logging setup
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
logger = logging.getLogger(__name__)
# ══════════════════════════════════════════════════════════════════════════════
# PIPELINE INITIALIZATION
# ══════════════════════════════════════════════════════════════════════════════
def initialize_pipeline():
"""Initialize all components of the fraud detection pipeline"""
logger.info("="*80)
logger.info("PayGuard Fraud Detection Consumer - INITIALIZATION")
logger.info("="*80)
# Feature extraction
feature_extractor = FeatureExtractor(
redis_host="localhost",
redis_port=6379
)
# Model scoring
model_scorer = ModelScorer(
layer1_path="models/layer1.pkl",
layer2_path="models/layer2.pkl"
)
# Decision engine with circuit breaker
decision_engine = DecisionEngine(
redis_host="localhost",
redis_port=6379
)
# Kafka consumer
consumer = KafkaConsumer(
KAFKA_TOPIC,
bootstrap_servers=KAFKA_BROKER,
group_id=KAFKA_GROUP,
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
auto_offset_reset='earliest',
enable_auto_commit=True
)
logger.info(f"[KAFKA CONSUMER] Connected to {KAFKA_BROKER}")
logger.info(f"[KAFKA CONSUMER] Subscribed to topic '{KAFKA_TOPIC}'")
logger.info(f"[KAFKA CONSUMER] Consumer group: {KAFKA_GROUP}")
logger.info("="*80)
logger.info("Waiting for transactions...\n")
return {
'consumer': consumer,
'feature_extractor': feature_extractor,
'model_scorer': model_scorer,
'decision_engine': decision_engine
}
# ══════════════════════════════════════════════════════════════════════════════
# PIPELINE EXECUTION
# ══════════════════════════════════════════════════════════════════════════════
def process_transaction(transaction, components):
"""
Execute the complete fraud detection pipeline for one transaction.
Steps:
1. Feature extraction (with Redis behavioral profiling)
2. Layer 1 fast model scoring
3. Behavioral anomaly detection
4. Layer 2 deep model scoring (if suspicious)
5. Final decision with circuit breaker
6. Update user profile
7. Store result
"""
transaction_id = transaction['transaction_id']
user_id = transaction['user_id']
amount = transaction['amount']
merchant = transaction['merchant']
logger.info(f"\n{'='*80}")
logger.info(f"[PIPELINE START] Transaction: {transaction_id[:8]}...")
logger.info(f"[TRANSACTION] User: {user_id} | Amount: ${amount:.2f} | Merchant: {merchant}")
logger.info(f"{'='*80}\n")
# ─────────────────────────────────────────────────────────────────────────
# STEP 1: FEATURE EXTRACTION
# ─────────────────────────────────────────────────────────────────────────
logger.info("[STEP 1/6] Feature Extraction & Behavioral Profiling")
logger.info("-" * 80)
features = components['feature_extractor'].extract_features(transaction)
# ─────────────────────────────────────────────────────────────────────────
# STEP 2: LAYER 1 FAST MODEL
# ─────────────────────────────────────────────────────────────────────────
logger.info("\n[STEP 2/6] Layer 1 - Fast Model Scoring")
logger.info("-" * 80)
layer1_score = components['model_scorer'].score_layer1(features)
# ─────────────────────────────────────────────────────────────────────────
# STEP 3: BEHAVIORAL ANOMALY DETECTION
# ─────────────────────────────────────────────────────────────────────────
logger.info("\n[STEP 3/6] Behavioral Anomaly Detection")
logger.info("-" * 80)
user_profile = components['feature_extractor'].get_user_profile(user_id)
if user_profile['avg_amount'] > 0:
ratio = amount / user_profile['avg_amount']
logger.info(f"[ANOMALY] Amount/Avg Ratio: {ratio:.2f}x")
if ratio > 3.0:
logger.warning(f"[ANOMALY] ⚠ Significant deviation detected (>{3.0}x)")
# ─────────────────────────────────────────────────────────────────────────
# STEP 4: LAYER 2 DEEP MODEL (if needed)
# ─────────────────────────────────────────────────────────────────────────
layer2_score = None
if layer1_score >= 0.4:
logger.info("\n[STEP 4/6] Layer 2 - Deep Model Scoring")
logger.info("-" * 80)
layer2_score = components['model_scorer'].score_layer2(features)
else:
logger.info("\n[STEP 4/6] Layer 2 - Deep Model Scoring")
logger.info("-" * 80)
logger.info("[LAYER 2 SCORING] Skipped (Layer 1 score indicates low risk)")
# ─────────────────────────────────────────────────────────────────────────
# STEP 5: FINAL DECISION
# ─────────────────────────────────────────────────────────────────────────
logger.info("\n[STEP 5/6] Final Decision Engine")
logger.info("-" * 80)
decision = components['decision_engine'].make_decision(
transaction_id=transaction_id,
layer1_score=layer1_score,
layer2_scorer=components['model_scorer'] if layer1_score >= 0.4 else None,
features=features if layer1_score >= 0.4 else None
)
# ─────────────────────────────────────────────────────────────────────────
# STEP 6: UPDATE PROFILE & STORE RESULT
# ─────────────────────────────────────────────────────────────────────────
logger.info("\n[STEP 6/6] Profile Update & Result Storage")
logger.info("-" * 80)
components['feature_extractor'].update_user_profile(transaction)
components['decision_engine'].store_result(
transaction_id=transaction_id,
decision=decision,
layer1_score=layer1_score,
layer2_score=layer2_score
)
# ─────────────────────────────────────────────────────────────────────────
# PIPELINE COMPLETE
# ─────────────────────────────────────────────────────────────────────────
logger.info(f"\n{'='*80}")
logger.info(f"[PIPELINE COMPLETE] Transaction {transaction_id[:8]}... → {decision.upper()}")
logger.info(f"{'='*80}\n")
# ══════════════════════════════════════════════════════════════════════════════
# MAIN LOOP
# ══════════════════════════════════════════════════════════════════════════════
def run_consumer():
"""Main loop: consume and process transactions from Kafka"""
components = initialize_pipeline()
try:
logger.info("Consumer ready. Processing messages...\n")
for message in components['consumer']:
if message.value is None:
continue
transaction = message.value
try:
process_transaction(transaction, components)
except Exception as e:
logger.error(f"[ERROR] Failed to process transaction: {str(e)}", exc_info=True)
except KeyboardInterrupt:
logger.info("\nShutting down consumer...")
finally:
components['consumer'].close()
logger.info("Consumer closed.")
if __name__ == "__main__":
run_consumer()