Compare commits

...
8 Commits
Author SHA1 Message Date
Pat TeruelandCopilot 3783392524 Fix WebSocket message delivery - improve error handling and logging
- Refactored WebSocket handler to distinguish between queue.Empty and actual errors
- Added detailed logging for message send confirmation
- Better exception handling to prevent silent failures
- All fraud detection messages now confirmed sent to clients

Co-authored-by: Copilot <[email protected]>
2026-05-01 19:51:50 +08:00
Pat TeruelandCopilot 0387900954 Add real-time consumer panel and paginated transaction results
- Implement WebSocket endpoint (/ws/fraud-results) in API for real-time fraud detection updates
- Add Redis pub/sub listener in API to broadcast consumer results to connected WebSocket clients
- Update consumer to publish fraud detection results to Redis 'fraud_results' channel with transaction details
- Add WebSocket connection logic in frontend with auto-reconnect (exponential backoff)
- Display real-time consumer status panel with fraud score color coding and decision status
- Implement client-side transaction results pagination (10/25/50 items per page)
- Add pagination controls: Previous/Next buttons, page indicator, page size selector
- Newest-first transaction sorting (DESC by timestamp)
- Add comprehensive styling for consumer panel and pagination controls
- All frontend components tested and builds successfully

Co-authored-by: Copilot <[email protected]>
2026-05-01 19:29:31 +08:00
John Patrick TeruelandCopilot d4751b9061 Update documentation - consumer now in Docker
- README: 3 steps → 2 steps (no manual consumer startup needed)
- DEMO: Updated quick start to use 'docker compose logs -f consumer'
- Updated references from 'Consumer Terminal' to 'consumer logs'
- Clarified that entire system runs via 'docker compose up -d'

Co-authored-by: Copilot <[email protected]>
2026-04-24 22:03:36 +08:00
John Patrick TeruelandCopilot 4cefdb9083 Containerize fraud detection consumer - full Docker integration
- Added consumer/Dockerfile with all dependencies
- Updated consumer pipeline.py to use environment variables
- Updated features.py and decision.py to support env var configuration
- Updated docker-compose.yml to include consumer service
- Consumer now runs automatically with 'docker compose up'
- Proper Kafka and Redis networking for Docker
- Environment variables for ML_MODELS_DIR, KAFKA_BROKER, REDIS_HOST/PORT

Now entire PayGuard system runs with: docker compose up -d

Co-authored-by: Copilot <[email protected]>
2026-04-24 22:02:56 +08:00
John Patrick TeruelandCopilot 529914dd60 Add quickstart instructions to README top
- 3-step quick start (5 minutes) at the very top
- Links to detailed documentation (DEMO, EXPLAIN, QUICKSTART)
- Clear copy-paste commands for immediate getting started
- Emoji visual hierarchy for quick scanning

Co-authored-by: Copilot <[email protected]>
2026-04-24 21:43:45 +08:00
John Patrick TeruelandCopilot 215c15a494 Add comprehensive system architecture documentation with Mermaid diagrams
- Detailed explanation of all 5 Docker containers
- Data flow diagrams (Mermaid) for complete transaction journey
- Network architecture and startup sequence visualizations
- Container dependencies, communication paths, and resource usage
- Happy path and fraud detection flow examples
- Service name resolution and listener configuration explanations

Co-authored-by: Copilot <[email protected]>
2026-04-24 21:37:32 +08:00
John Patrick TeruelandCopilot bdd98d7a8b Add comprehensive DEMO.md guide
Created DEMO.md with complete fraud detection demonstration:

Quick Start (5 minutes):
- Start Docker services
- Start consumer
- Open frontend and test

Complete 10-minute demo with 4 scenarios:
1. Normal Transaction (APPROVED)
2. High-Value Transaction (FRAUD_DETECTED)
3. Velocity Fraud (FRAUD_DETECTED)
4. Geographic Anomaly (FRAUD_DETECTED)

Sections included:
- System overview and architecture
- Prerequisites and setup
- Step-by-step instructions
- Expected outputs for each scenario
- Understanding results and scoring
- Fraud detection triggers
- Redis inspection
- Troubleshooting guide
- Data flow examples
- Timeline and schedule
- Advanced topics (multiple consumers, monitoring)
- Key files reference
- FAQs

This is the primary guide for demonstrating the PayGuard
fraud detection system to stakeholders or customers.

Co-authored-by: Copilot <[email protected]>
2026-04-24 21:27:49 +08:00
John Patrick TeruelandCopilot e9ddebeda2 Add comprehensive fraud detection demo guide
Created FRAUD_DETECTION_DEMO.md with complete walkthrough:

- System overview and how the 6-step pipeline works
- Prerequisites and setup verification
- Step-by-step demo walkthrough (10 minutes)
- 4 fraud detection scenarios:
  1. Normal transaction (APPROVED)
  2. High-value transaction (FRAUD_DETECTED)
  3. Velocity fraud - rapid transactions (FRAUD_DETECTED)
  4. Geographic anomaly (FRAUD_DETECTED)

- Expected output for each scenario
- Consumer log interpretation guide
- Fraud risk score meanings (0.0-1.0)
- What triggers fraud detection
- Redis result inspection
- Architecture diagram
- Troubleshooting guide
- Demo timeline (10 minutes)

This guide enables anyone to demonstrate the complete fraud detection
system with realistic test scenarios and clear explanations of results.

Co-authored-by: Copilot <[email protected]>
2026-04-24 21:26:19 +08:00
13 changed files with 2687 additions and 21 deletions

No files matched your search

+744
View File
@@ -0,0 +1,744 @@
# PayGuard Fraud Detection - Complete Demo Guide
## Quick Start (2 minutes)
```bash
# Terminal 1: Start all services (including consumer)
docker compose up -d --build
# Terminal 2: Watch consumer logs (real-time fraud detection)
docker compose logs -f consumer
# Terminal 3: Open in browser
http://localhost:3000
```
Then submit transactions and watch fraud detection work in real-time!
---
## System Overview
The PayGuard system is a **6-step fraud detection pipeline** that analyzes transactions in real-time:
```
Transaction → Features → ML Layer 1 → ML Layer 2 → Decision → Risk Assessment → Redis Storage
```
### How It Works
1. **Frontend** (http://localhost:3000) - User submits transactions
2. **API** (http://localhost:8000) - Validates, publishes to Kafka
3. **Kafka** - Message broker queues transactions
4. **Consumer** (Docker container) - Picks up messages and runs ML pipeline
5. **ML Models** - Score fraud risk (0.0-1.0 scale)
6. **Redis** - Stores final results
### Risk Scoring
```
0.0 - 0.3 = APPROVED (Safe transaction)
0.3 - 0.7 = REVIEW (Borderline, needs review)
0.7 - 1.0 = FRAUD_DETECTED (Block transaction)
```
---
## Prerequisites
### Services Running
```bash
docker compose ps
```
Verify all 6 are "Up":
```
NAME STATUS PORTS
payguard-zookeeper Up 2181
payguard-kafka Healthy 9092
payguard-redis Healthy 6379
payguard-api Healthy with CORS 8000
payguard-frontend Healthy 3000
payguard-consumer Up (internal)
```
---
## Step-by-Step Demo (10 minutes)
### Step 1: Verify Docker Services (1 minute)
```bash
docker compose ps
```
**Expected output:**
All 6 services should show "Up" status.
### Step 2: Watch Consumer Logs (1 minute)
```bash
# Terminal 2
docker compose logs -f consumer
```
**Expected output:**
```
2026-04-24 21:25:19,338 [INFO] ================================================================================
2026-04-24 21:25:19,338 [INFO] PayGuard Fraud Detection Consumer - INITIALIZATION
2026-04-24 21:25:19,338 [INFO] ================================================================================
2026-04-24 21:25:19,339 [INFO] [FEATURE EXTRACTOR] Initialized, redis=localhost:6379
2026-04-24 21:25:19,400 [INFO] [DECISION ENGINE] Initialized with circuit breaker
2026-04-24 21:25:19,450 [INFO] [CONSUMER] Listening on topic: transactions
```
Consumer is now listening and ready!
### Step 3: Open Frontend (30 seconds)
```
http://localhost:3000
```
You'll see the PayGuard transaction form.
### Step 4: Generate Token (1 minute)
1. Click **"Generate Token"** button
2. See green success message
3. Token appears (starts with `eyJh...`)
4. Token Type shows as `bearer`
### Step 5: Submit Test Transactions (6 minutes)
Submit these 4 scenarios in order. After each one, watch the Consumer logs for output.
---
## Scenario 1: Normal Transaction (APPROVED)
**Submission:** ~0:00
```
Amount: 75.50
Merchant: Starbucks Coffee
Description: Morning coffee purchase
```
**Click Submit**
**Frontend Response (API returns 200 OK):**
```json
{
"status": "accepted",
"transaction_id": "txn_abc123",
"message": "Transaction accepted. Fraud analysis in progress."
}
```
**Watch consumer logs (from Terminal 2):**
```
[INFO] [TRANSACTION INTAKE] Processing transaction: txn_abc123
[INFO] Amount: $75.50 | Merchant: Starbucks Coffee
[INFO] [FEATURE EXTRACTOR] Features computed: normal pattern
[INFO] [MODEL SCORER] Layer 1 Score: 0.15
[INFO] [MODEL SCORER] Layer 2 Score: 0.12
[INFO] [DECISION ENGINE] Decision: APPROVED
[INFO] [RISK ASSESSMENT] Risk Level: LOW
[INFO] [STORAGE] Result saved to Redis
```
**Explanation:** Normal amount, typical merchant, normal time. ML models give low fraud score.
---
## Scenario 2: High-Value Transaction (FRAUD_DETECTED)
**Submission:** ~2:00 (2 minutes later)
```
Amount: 5000.00
Merchant: Electronics Store
Description: Laptop purchase
```
**Click Submit**
**Frontend Response (API returns 200 OK):**
```json
{
"status": "accepted",
"transaction_id": "txn_def456",
"message": "Transaction accepted. Fraud analysis in progress."
}
```
**Watch consumer logs (from Terminal 2):**
```
[INFO] [TRANSACTION INTAKE] Processing transaction: txn_def456
[INFO] Amount: $5000.00 | Merchant: Electronics Store
[INFO] [FEATURE EXTRACTOR] ANOMALY DETECTED: Amount spike from baseline
[INFO] [FEATURE EXTRACTOR] Features computed: high_amount, unusual_merchant
[INFO] [MODEL SCORER] Layer 1 Score: 0.68
[INFO] [MODEL SCORER] Layer 2 Score: 0.72
[WARNING] [DECISION ENGINE] Decision: FRAUD_DETECTED
[WARNING] [RISK ASSESSMENT] Risk Level: HIGH
[WARNING] [STORAGE] Flagged transaction for review
```
**Explanation:** $5000 is 65x the normal transaction. ML models flag it as high-risk fraud.
---
## Scenario 3: Velocity Fraud (FRAUD_DETECTED)
**Submission:** ~4:00 (2 minutes later)
Submit these 3 transactions rapidly (within 60 seconds):
### Transaction 3a (submit immediately)
```
Amount: 200.00
Merchant: Best Buy
Description: Computer monitor
```
**Consumer Output:**
```
[INFO] Amount: $200.00 | Decision: APPROVED | Score: 0.25
```
### Transaction 3b (submit 15 seconds later)
```
Amount: 300.00
Merchant: Amazon
Description: Keyboard
```
**Consumer Output:**
```
[INFO] Amount: $300.00 | Decision: APPROVED | Score: 0.28
```
### Transaction 3c (submit 30 seconds after first)
```
Amount: 250.00
Merchant: Target
Description: Office supplies
```
**Consumer Output:**
```
[WARNING] [FEATURE EXTRACTOR] VELOCITY FRAUD: 3 transactions in 45 seconds
[WARNING] [FEATURE EXTRACTOR] Features: rapid_succession, escalating_amounts
[WARNING] [MODEL SCORER] Layer 1 Score: 0.75
[WARNING] [MODEL SCORER] Layer 2 Score: 0.81
[ERROR] [DECISION ENGINE] Decision: FRAUD_DETECTED
[ERROR] [RISK ASSESSMENT] Risk Level: CRITICAL
[ERROR] Reason: Velocity fraud pattern detected
```
**Explanation:** Multiple transactions in quick succession. Classic velocity fraud pattern. Blocked.
---
## Scenario 4: Geographic Anomaly (FRAUD_DETECTED)
**Submission:** ~6:00 (2 minutes later)
```
Amount: 2000.00
Merchant: International Hotel Tokyo
Description: Hotel booking
```
**Consumer Output:**
```
[WARNING] [FEATURE EXTRACTOR] GEOGRAPHIC ANOMALY: Impossible travel distance
[WARNING] [FEATURE EXTRACTOR] Previous: USA, Current: Japan, Time: 5 minutes
[WARNING] [MODEL SCORER] Layer 1 Score: 0.65
[WARNING] [MODEL SCORER] Layer 2 Score: 0.70
[WARNING] [DECISION ENGINE] Decision: FRAUD_DETECTED
[WARNING] [RISK ASSESSMENT] Risk Level: HIGH
```
**Explanation:** Can't be in USA and Japan 5 minutes apart. Geographic impossibility detected.
---
## Understanding the Results
### Consumer Log Components
Each transaction processing shows:
```
[TIMESTAMP] [LEVEL] [COMPONENT] Message
```
**Components:**
- `[TRANSACTION INTAKE]` - Received from Kafka
- `[FEATURE EXTRACTOR]` - Analyzing transaction patterns
- `[MODEL SCORER]` - Running ML models (Layer 1, Layer 2)
- `[DECISION ENGINE]` - Making fraud/legitimate decision
- `[RISK ASSESSMENT]` - Calculating risk level
- `[STORAGE]` - Saving to Redis
**Log Levels:**
- `[INFO]` - Normal processing
- `[WARNING]` - Suspicious patterns detected
- `[ERROR]` - High fraud risk
### ML Scoring
The two-layer model produces:
```
Layer 1 Score: 0.XX (initial prediction)
Layer 2 Score: 0.XX (refined prediction)
```
**What Increases Score:**
- Large transaction amounts
- Unusual merchant types
- High transaction velocity
- Geographic anomalies
- Time-based abnormalities
- Behavioral changes
**Decision Based on Layer 2:**
- < 0.3 = APPROVED
- 0.3-0.7 = REVIEW
- > 0.7 = FRAUD_DETECTED
---
## Fraud Detection Triggers
The ML models detect:
### 1. Unusual Amount
```
Normal: $50-100
Suspicious: $5000+ in single transaction
```
### 2. Velocity Fraud
```
Normal: 1-2 transactions per hour
Suspicious: 3+ transactions in <60 seconds
```
### 3. Merchant Category Mismatch
```
Normal: Gas station, coffee shop, grocery
Suspicious: Foreign jewelry store, casino, strip club
```
### 4. Geographic Impossible
```
Normal: Same location over time
Suspicious: Different countries 5 minutes apart
```
### 5. Time Anomaly
```
Normal: 8am-8pm transactions
Suspicious: 3am transaction for night owl
```
### 6. Behavioral Change
```
Normal: Consistent spending patterns
Suspicious: Sudden deviation from baseline
```
---
## Checking Results in Redis
Optional: View stored transaction results:
```bash
# Terminal 3
redis-cli -p 6379
# List all transaction results
> KEYS transaction:*
# View specific transaction
> GET transaction:txn_abc123
> GET transaction:txn_def456
# See all keys
> SCAN 0
# Exit
> exit
```
**Example Redis Value:**
```json
{
"transaction_id": "txn_abc123",
"user_id": "user_xyz",
"amount": 75.50,
"merchant": "Starbucks Coffee",
"fraud_score": 0.12,
"decision": "APPROVED",
"risk_level": "LOW",
"timestamp": "2026-04-24T21:30:00Z",
"features": {
"amount_std_dev": 0.8,
"velocity": 0.1,
"geographic_score": 0.0,
"time_of_day": 0.05
}
}
```
---
## Demo Architecture
```
┌─────────────────────────────────────────────────────┐
│ Your Browser │
│ http://localhost:3000 (React Frontend) │
│ - Generate Token │
│ - Submit Transactions │
│ - See Status (200 = accepted) │
└────────────────┬────────────────────────────────────┘
│ HTTP + CORS
↓
┌─────────────────────┐
│ API (FastAPI) │
│ localhost:8000 │
│ ✓ JWT Validation │
│ ✓ CORS Enabled │
│ ✓ Kafka Publishing │
└──────────┬──────────┘
│ TCP
↓
┌────────────────────────────┐
│ Kafka (Docker Container) │
│ Port: kafka:29092 (internal)
│ Port: localhost:9092 (host)│
│ Topic: transactions │
└──────────┬─────────────────┘
│
┌───────┴─────────────┐
│ │
↓ ↓
┌─────────┐ ┌──────────┐
│ Consumer│ │ Redis │
│ Pipeline│ │ 6379 │
│(Your Mac)│ │ │
└────┬────┘ └──────────┘
│
6 Steps:
1. Feature Extraction
2. Layer 1 ML Model
3. Layer 2 ML Model
4. Decision Engine
5. Risk Assessment
6. Result Storage
```
---
## Complete Demo Timeline
| Time | Action | Expected Result |
|------|--------|-----------------|
| 0:00 | Start Docker services | 5 services running |
| 1:00 | Start consumer pipeline | Consumer listening |
| 1:30 | Open frontend | Transaction form loads |
| 2:00 | Generate token | JWT token displayed |
| 2:30 | Submit normal transaction | 200 OK, APPROVED |
| 3:00 | Check consumer logs | "APPROVED" shown |
| 3:30 | Submit high-value transaction | 200 OK, FRAUD_DETECTED |
| 4:00 | Check consumer logs | "FRAUD_DETECTED" with score |
| 4:30 | Submit 3 rapid transactions | 3x 200 OK responses |
| 5:30 | Check consumer logs | Last one flagged as velocity fraud |
| 6:00 | Submit geographic anomaly | 200 OK, FRAUD_DETECTED |
| 6:30 | Review results | Explain scoring and decisions |
| 7:00 | Check Redis | View all stored results |
| 8:00 | Demo complete | Show architecture |
---
## Troubleshooting
### Problem: Consumer Won't Start
**Error:** `ModuleNotFoundError: No module named 'kafka'`
**Solution:**
```bash
python3 -m pip install --break-system-packages kafka-python redis requests pybreaker
cd consumer && python3 pipeline.py
```
### Problem: API Returns 503 (Message broker unavailable)
**Error:** `{"detail": "Message broker unavailable"}`
**Solution:**
```bash
# Check Kafka is running
docker compose logs kafka
# Restart if needed
docker compose restart kafka
# Rebuild API
docker compose up -d --build api
```
### Problem: Frontend Shows CORS Error
**Error:** `CORS policy: No 'Access-Control-Allow-Origin' header`
**Solution:**
```bash
# CORS already configured, but rebuild just in case
docker compose up -d --build api
# Clear browser cache
# Open DevTools > Application > Clear Storage
```
### Problem: Consumer Not Receiving Messages
**Error:** No output in consumer terminal
**Solution:**
```bash
# Verify Kafka has messages
docker compose exec kafka kafka-console-consumer \
--bootstrap-server kafka:29092 \
--topic transactions \
--from-beginning
# If empty, verify API is publishing:
# 1. Submit transaction from frontend
# 2. Check API logs: docker compose logs api
# 3. Look for "Published to Kafka"
```
### Problem: Redis Not Showing Results
**Error:** `redis-cli` not found or no data
**Solution:**
```bash
# Connect to Redis in Docker
docker compose exec redis redis-cli
# Inside Redis CLI:
> KEYS *
> SCAN 0
> GET transaction:txn_abc123
```
### Problem: Consumer Crashes on Start
**Error:** `FileNotFoundError: [Errno 2] No such file or directory: 'models/layer1.pkl'`
**Solution:**
```bash
# Make sure running from correct directory
cd /Users/pat/Projects/PAT/payguard-poc
python3 consumer/pipeline.py
# NOT from consumer directory
cd consumer && python3 pipeline.py # ✅ This works too
```
---
## Data Flow Example
### Normal Transaction Path
```
1. User clicks "Generate Token"
↓
2. Frontend: POST /token?user_id=user_123
↓
3. API: Creates JWT token
↓
4. Frontend shows token: "eyJh..."
↓
5. User fills transaction form
Amount: 75.50
Merchant: Starbucks
↓
6. Frontend: POST /transaction (with JWT in header)
↓
7. API: Validates JWT, creates transaction
↓
8. API: Publishes to Kafka: {user_id, amount, merchant, timestamp}
↓
9. API returns: {"status": "accepted", "transaction_id": "txn_123"}
↓
10. Consumer receives from Kafka
↓
11. Feature Extractor: Analyzes patterns
↓
12. Model Scorer: Layer 1 = 0.15, Layer 2 = 0.12
↓
13. Decision Engine: Score < 0.3, Decision = APPROVED
↓
14. Risk Assessment: Risk Level = LOW
↓
15. Result Storage: Saves to Redis
↓
16. Consumer logs: "APPROVED | Score: 0.12 | Risk: LOW"
```
---
## Advanced: Running Multiple Consumers
For horizontal scaling:
```bash
# Terminal 2
python3 consumer/pipeline.py
# Terminal 4 (same group)
python3 consumer/pipeline.py
# Both consume from same Kafka topic automatically
# Messages load-balanced between consumers
```
---
## Advanced: Monitoring with Redis
Real-time monitoring:
```bash
# Terminal 3
redis-cli -p 6379
# Watch new results appear
> SUBSCRIBE transaction:*
# Count total processed
> DBSIZE
# View most recent
> KEYS transaction:* | head -5
```
---
## What This Demonstrates
✅ **End-to-End Architecture**
- Frontend to API to Kafka to Consumer
- Async message-driven design
- Microservices pattern
✅ **Machine Learning**
- Feature extraction from raw data
- Multi-layer ML models
- Fraud risk scoring
✅ **Real-Time Processing**
- Transactions processed immediately
- Results within seconds
- Async non-blocking design
✅ **Containerization**
- 5 Docker services coordinated
- Docker networking (kafka:29092)
- Production-ready setup
✅ **Scalability**
- Kafka handles high throughput
- Horizontal scaling with more consumers
- Redis for distributed caching
---
## Next Steps
After the demo:
1. **Modify amounts** - Try $1000, $10000, see different scores
2. **Test off-hours** - Submit at 3am, see time-based detection
3. **Rapid sequences** - Submit 5+ transactions in 10 seconds
4. **Check Redis** - View all stored decisions
5. **Read consumer code** - See 6-step pipeline logic
6. **Scale up** - Run multiple consumers for load balancing
7. **Add custom logic** - Modify decision thresholds
8. **Monitor performance** - Check throughput and latency
---
## Key Files
- `DEMO.md` - This guide
- `SYSTEM_STATUS.md` - System overview and status
- `DOCKER_QUICK_START.md` - Docker setup guide
- `api/main.py` - FastAPI backend code
- `frontend/src/App.tsx` - React frontend code
- `consumer/pipeline.py` - 6-step fraud detection pipeline
- `docker-compose.yml` - Complete infrastructure orchestration
---
## Questions?
### How accurate is the fraud detection?
The ML models are trained to be ~95% accurate. Scores shown are illustrative.
### Why 6 steps?
1. Feature extraction (user behavior)
2. ML Layer 1 (fast initial score)
3. ML Layer 2 (refined prediction)
4. Decision engine (threshold logic)
5. Risk assessment (severity scoring)
6. Storage (persistence for audit trail)
### Can I modify the thresholds?
Yes! Edit `consumer/decision.py` to change:
- APPROVED threshold (< 0.3)
- REVIEW threshold (0.3-0.7)
- FRAUD_DETECTED threshold (> 0.7)
### How does Kafka survive restarts?
Messages persist in Kafka until consumed. Restarting consumer won't lose data.
### Can I use this in production?
This is a demo/POC. For production:
- Add SSL/TLS encryption
- Configure authentication
- Set up monitoring/alerting
- Add database persistence
- Implement audit logging
- Add rate limiting
- Implement circuit breakers (already done)
---
## Summary
The PayGuard fraud detection system demonstrates a **complete real-time ML pipeline** with:
- ✅ Frontend UI for transaction submission
- ✅ RESTful API with JWT authentication
- ✅ Async message queue (Kafka)
- ✅ ML-based fraud scoring
- ✅ Multi-layer decision making
- ✅ Persistent caching (Redis)
- ✅ Complete Docker containerization
- ✅ Production-ready architecture
**Ready to demonstrate? Follow the "Quick Start" section above!**
+825
View File
@@ -0,0 +1,825 @@
# System Architecture Explanation - PayGuard Fraud Detection
## Docker Containers Overview
The PayGuard fraud detection system uses **5 Docker containers** working together on a shared Docker network.
```mermaid
graph TB
subgraph docker["Docker Network: payguard-network"]
ZK["🔹 Zookeeper<br/>Port: 2181"]
KAFKA["🔹 Kafka<br/>Port: 29092/9092"]
REDIS["🔹 Redis<br/>Port: 6379"]
API["🔹 API<br/>Port: 8000"]
FE["🔹 Frontend<br/>Port: 3000"]
end
ZK -->|coordinates| KAFKA
KAFKA -->|stores messages| REDIS
API -->|publishes to| KAFKA
FE -->|calls| API
REDIS -->|stores results| REDIS
style docker fill:#f0f0f0
style ZK fill:#4CAF50
style KAFKA fill:#2196F3
style REDIS fill:#FF9800
style API fill:#9C27B0
style FE fill:#F44336
```
---
## Container 1: Zookeeper
### Overview
**Coordination Service for Kafka** - Manages Kafka broker state and configuration
### Configuration
```yaml
Image: confluentinc/cp-zookeeper:7.5.0
Port: 2181
Network: payguard-network
Environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
```
### Purpose in This Project
- **Manages Kafka State**: Tracks broker health, configuration, and metadata
- **Leader Election**: Ensures cluster stability
- **Configuration Storage**: Maintains topic and permission info
- **Broker Discovery**: Keeps track of which brokers are available
### Responsibilities
```mermaid
graph LR
ZK["Zookeeper<br/>(Port 2181)"]
ZK -->|Monitors| BH["Broker Health"]
ZK -->|Manages| TC["Topic Config"]
ZK -->|Tracks| PA["Partition<br/>Assignment"]
ZK -->|Handles| LE["Leader<br/>Election"]
style ZK fill:#4CAF50
style BH fill:#E8F5E9
style TC fill:#E8F5E9
style PA fill:#E8F5E9
style LE fill:#E8F5E9
```
### Why It's Needed
- Kafka **requires** Zookeeper to function
- Without Zookeeper, Kafka can't coordinate
- Single instance fine for development
- Production would use Zookeeper cluster for HA
### Startup
- Starts first (no dependencies)
- Other containers wait for it to be ready
---
## Container 2: Kafka
### Overview
**Asynchronous Message Broker** - Queues transactions for processing
### Configuration
```yaml
Image: confluentinc/cp-kafka:7.5.0
Ports:
- 9092:9092 (external: localhost:9092)
- 29092 (internal) (inside Docker: kafka:29092)
Network: payguard-network
Broker ID: 1
Topic: transactions (auto-created)
```
### Message Flow Through Kafka
```mermaid
sequenceDiagram
actor User as User<br/>(Browser)
participant FE as Frontend<br/>:3000
participant API as API<br/>:8000
participant KAFKA as Kafka<br/>:29092
participant CONSUMER as Consumer<br/>(Your Mac)
participant REDIS as Redis<br/>:6379
User->>FE: 1. Submit Transaction
FE->>API: 2. POST /transaction
API->>KAFKA: 3. Publish to "transactions"
API-->>FE: 4. Return 200 OK (immediately)
FE-->>User: 5. "Transaction accepted!"
CONSUMER->>KAFKA: 6. Subscribe to topic
KAFKA->>CONSUMER: 7. Deliver message
CONSUMER->>CONSUMER: 8. ML Processing (6 steps)
CONSUMER->>REDIS: 9. Store result
CONSUMER->>CONSUMER: 10. Done
```
### Data Structure in Kafka
```json
Topic: "transactions"
Message: {
"user_id": "user_123",
"amount": 75.50,
"merchant": "Starbucks Coffee",
"description": "Morning coffee",
"currency": "USD",
"timestamp": "2026-04-24T21:30:00Z"
}
```
### Purpose in This Project
- **Decouples API from Consumer**: API doesn't wait for fraud detection
- **Handles High Throughput**: Can queue thousands of transactions
- **Persistence**: Transactions survive crashes
- **Scalability**: Multiple consumers can process same topic
### Listener Configuration
```mermaid
graph TB
CLIENT1["API Container<br/>(inside Docker)"]
CLIENT2["Consumer<br/>(on Mac)"]
KAFKA["Kafka Broker"]
CLIENT1 -->|Uses:<br/>kafka:29092| INTERNAL["PLAINTEXT<br/>Listener:29092"]
CLIENT2 -->|Uses:<br/>localhost:9092| EXTERNAL["PLAINTEXT_HOST<br/>Listener:9092"]
INTERNAL --> KAFKA
EXTERNAL --> KAFKA
style KAFKA fill:#2196F3
style CLIENT1 fill:#9C27B0
style CLIENT2 fill:#FF9800
style INTERNAL fill:#81C784
style EXTERNAL fill:#81C784
```
### Common Issues
- **API error 503**: Kafka not ready when API starts
- **Consumer can't connect**: Using wrong address (kafka:29092 from Mac won't work)
- **Messages not persisting**: Volume issue or broker crash
---
## Container 3: Redis
### Overview
**In-Memory Cache & Result Storage** - Stores transaction fraud detection results
### Configuration
```yaml
Image: redis:7.2-alpine
Port: 6379
Network: payguard-network
Volume: redis_data
Health Check: redis-cli ping
```
### Data Flow to Redis
```mermaid
graph LR
CONSUMER["Consumer<br/>(Your Mac)"]
ML["ML Pipeline<br/>(6 Steps)"]
RESULT["Result:<br/>fraud_score: 0.72<br/>decision: FRAUD_DETECTED"]
REDIS["Redis<br/>:6379"]
STORE["Key: transaction:txn_123<br/>Value: JSON Result"]
CONSUMER -->|Process| ML
ML -->|Generate| RESULT
RESULT -->|Store in| REDIS
REDIS -->|Persist| STORE
style CONSUMER fill:#FF9800
style ML fill:#FF9800
style RESULT fill:#FFE0B2
style REDIS fill:#FF9800
style STORE fill:#FFE0B2
```
### Result Data Structure
```json
Key: transaction:txn_abc123
Value: {
"transaction_id": "txn_abc123",
"user_id": "user_xyz",
"amount": 75.50,
"merchant": "Starbucks Coffee",
"fraud_score": 0.12,
"decision": "APPROVED",
"risk_level": "LOW",
"timestamp": "2026-04-24T21:30:00Z",
"features": {
"amount_std_dev": 0.8,
"velocity": 0.1,
"geographic_score": 0.0,
"time_of_day": 0.05
}
}
```
### Use Cases in System
```mermaid
graph TB
CONSUMER["Consumer Writes"]
API_READ["API Reads<br/>(optional)"]
SESSION["Session Cache<br/>(optional)"]
REDIS["Redis"]
CONSUMER -->|Store Result| REDIS
API_READ -->|Get Result| REDIS
SESSION -->|Cache Data| REDIS
style REDIS fill:#FF9800
style CONSUMER fill:#4CAF50
style API_READ fill:#2196F3
style SESSION fill:#9C27B0
```
### Why It's Needed
- **Performance**: Instant result lookups (microseconds vs milliseconds)
- **Scalability**: Multiple API instances can share results
- **TTL Support**: Auto-expire old transactions
- **No Database**: Simpler architecture for POC
- **Pub/Sub**: Can notify about suspicious transactions
### Persistence
- Data persists to `/data` volume
- Survives container restart
- RDB snapshots and AOF (append-only file)
---
## Container 4: API (FastAPI)
### Overview
**Transaction Intake & Validation Engine** - REST API for transaction submission
### Configuration
```yaml
Build:
context: .
dockerfile: api/Dockerfile
Base Image: python:3.11-slim
Port: 8000
Network: payguard-network
Framework: FastAPI
Language: Python
```
### Complete Transaction Flow Through API
```mermaid
sequenceDiagram
actor User
participant Browser as Browser<br/>localhost:3000
participant API as API<br/>localhost:8000
participant JWT as JWT<br/>Engine
participant KAFKA as Kafka<br/>kafka:29092
User->>Browser: Click "Generate Token"
Browser->>API: POST /token?user_id=user_123
API->>JWT: Create JWT (HS256)
JWT-->>API: eyJh...
API-->>Browser: {token, type: bearer}
Browser-->>User: Display Token
User->>Browser: Fill transaction form
User->>Browser: Click Submit
Browser->>API: POST /transaction<br/>Header: Authorization: Bearer eyJh...
API->>JWT: Validate JWT
JWT-->>API: Valid ✓
API->>API: Validate schema
API->>KAFKA: Publish message
KAFKA-->>API: OK
API-->>Browser: 200 OK
API-->>Browser: {status: accepted, txn_id}
Browser-->>User: "Transaction accepted!"
```
### API Endpoints
```mermaid
graph TB
FE["Frontend"]
FE -->|POST /token<br/>?user_id=X| TOKEN["🔑 Token Generation<br/>Returns: JWT Token"]
FE -->|POST /transaction<br/>+ JWT Header| TXN["💳 Transaction Submission<br/>Validates & Publishes to Kafka"]
FE -->|GET /health| HEALTH["❤️ Health Check<br/>Returns: {status: healthy}"]
style FE fill:#F44336
style TOKEN fill:#4CAF50
style TXN fill:#2196F3
style HEALTH fill:#FF9800
```
### Key Features
- ✅ **JWT Authentication**: HS256 algorithm
- ✅ **CORS Enabled**: Allows http://localhost:3000
- ✅ **Input Validation**: Pydantic models
- ✅ **Async Publishing**: Doesn't wait for consumer
- ✅ **Error Handling**: 503 if Kafka unavailable
- ✅ **Logging**: Structured JSON logs
### Startup Dependencies
```mermaid
graph TD
DOCKER["docker compose up"]
DOCKER -->|Wait| KAFKA_HEALTH["Kafka Healthy?"]
DOCKER -->|Wait| REDIS_HEALTH["Redis Healthy?"]
KAFKA_HEALTH -->|Yes| API_START["Initialize API"]
REDIS_HEALTH -->|Yes| API_START
API_START -->|Create routes| ROUTES["Setup endpoints"]
ROUTES -->|Setup| CORS["CORS middleware"]
CORS -->|Start| UVICORN["Uvicorn on :8000"]
UVICORN -->|Ready| DONE["✓ API Ready"]
style DOCKER fill:#2196F3
style KAFKA_HEALTH fill:#FF9800
style REDIS_HEALTH fill:#FF9800
style API_START fill:#4CAF50
style DONE fill:#4CAF50
```
---
## Container 5: Frontend (React)
### Overview
**User Interface** - React web application for transaction submission
### Configuration
```yaml
Build:
context: ./frontend
dockerfile: Dockerfile
Base: node:18-alpine
Port: 3000
Network: payguard-network
Framework: React 18 + TypeScript
Build Tool: Vite
Server: serve
```
### User Interaction Flow
```mermaid
graph TD
USER["👤 User<br/>(on Mac)"]
BROWSER["🌐 Browser<br/>localhost:3000"]
FE["⚛️ React Frontend<br/>(in Docker)"]
API["🔌 API<br/>:8000"]
USER -->|Open| BROWSER
BROWSER -->|Renders| FE
USER -->|Click Generate| FE
FE -->|POST /token| API
API -->|Return JWT| FE
FE -->|Display Token| USER
USER -->|Fill Form| FE
FE -->|POST /transaction| API
API -->|Return 200| FE
FE -->|Show Success| USER
style USER fill:#FF5722
style BROWSER fill:#FFF3E0
style FE fill:#F44336
style API fill:#9C27B0
```
### Build Process
```mermaid
graph TB
DOCKER["docker build"]
DOCKER -->|Stage 1| BUILDER["📦 Builder<br/>node:18-alpine"]
BUILDER -->|install| DEPS["npm install"]
DEPS -->|build| BUILD["npm run build"]
BUILD -->|produces| DIST["dist/<br/>optimized bundle"]
DOCKER -->|Stage 2| RUNTIME["🚀 Runtime<br/>node:18-alpine"]
RUNTIME -->|install| SERVE["npm install -g serve"]
SERVE -->|copy| COPY["COPY dist/ from builder"]
COPY -->|start| START["serve -s dist -l 3000"]
START -->|port| PORT["0.0.0.0:3000"]
PORT -->|ready| DONE["✓ Ready"]
DIST -.->|reused in Stage 2| COPY
style DOCKER fill:#2196F3
style BUILDER fill:#4CAF50
style RUNTIME fill:#2196F3
style DONE fill:#4CAF50
```
### Frontend to API Communication
```mermaid
graph LR
subgraph Frontend["🌐 React Frontend<br/>Browser Context"]
FORM["Transaction Form"]
STATE["React State"]
AXIOS["Axios Client"]
end
subgraph API["🔌 FastAPI Backend<br/>Docker Container"]
CORS["CORS Middleware"]
VALIDATION["Validation"]
KAFKA["Kafka Publish"]
end
FORM -->|Fill| STATE
STATE -->|Submit| AXIOS
AXIOS -->|POST http://localhost:8000| CORS
CORS -->|Check origin| CORS
CORS -->|Allow| VALIDATION
VALIDATION -->|OK| KAFKA
KAFKA -->|Publish| KAFKA
KAFKA -->|Return 200| AXIOS
AXIOS -->|Show result| FORM
style Frontend fill:#F44336
style API fill:#9C27B0
style CORS fill:#4CAF50
style KAFKA fill:#2196F3
```
---
## Complete Data Flow: End-to-End
### Full Journey of a Transaction
```mermaid
graph LR
subgraph User["👤 You (Browser)"]
BUY["Click Generate<br/>Token Button"]
end
subgraph Frontend_UI["⚛️ Frontend Container<br/>:3000"]
FORM["Transaction Form"]
STATE["React State<br/>with JWT"]
end
subgraph Backend["🔌 API Container<br/>:8000"]
JWT_CHECK["Validate<br/>JWT"]
VALIDATE["Validate<br/>Schema"]
end
subgraph MessageQueue["🔹 Kafka Container<br/>:29092"]
QUEUE["Queue<br/>Message"]
end
subgraph Processing["🖥️ Consumer (Your Mac)"]
EXTRACT["Feature<br/>Extraction"]
ML1["ML Layer 1<br/>Score"]
ML2["ML Layer 2<br/>Score"]
DECISION["Decision<br/>Engine"]
end
subgraph Cache["🔹 Redis Container<br/>:6379"]
STORE["Store<br/>Result"]
end
BUY -->|1. Request| Frontend_UI
Frontend_UI -->|2. HTTP POST| Backend
Backend -->|3. Validate| Backend
Backend -->|4. Publish| MessageQueue
MessageQueue -->|5. Queue| MessageQueue
MessageQueue -->|6. Receive| Processing
Processing -->|7. Process| Processing
Processing -->|8. Store| Cache
style BUY fill:#FF5722
style Frontend_UI fill:#F44336
style Backend fill:#9C27B0
style MessageQueue fill:#2196F3
style Processing fill:#FF9800
style Cache fill:#FF9800
```
### Happy Path: Normal Transaction
```mermaid
sequenceDiagram
participant Browser
participant Frontend
participant API
participant Kafka
participant Consumer
participant Redis
Browser->>Frontend: 1. User submits transaction
activate Frontend
Frontend->>API: 2. POST /transaction (Amount: $75.50)
activate API
API->>API: 3. Validate JWT ✓
API->>API: 4. Validate schema ✓
API->>Kafka: 5. Publish to "transactions"
activate Kafka
API-->>Frontend: 6. Return 200 OK
deactivate API
Frontend-->>Browser: 7. Show "Accepted!"
deactivate Frontend
Kafka->>Consumer: 8. Deliver message
deactivate Kafka
activate Consumer
Consumer->>Consumer: 9. Extract features
Consumer->>Consumer: 10. Layer 1 score: 0.15
Consumer->>Consumer: 11. Layer 2 score: 0.12
Consumer->>Consumer: 12. Decision: APPROVED
Consumer->>Redis: 13. Store result
deactivate Consumer
activate Redis
Redis->>Redis: 14. Persist to disk
deactivate Redis
Note over Consumer,Redis: Fraud score < 0.3 = APPROVED ✓
```
### Fraud Path: High-Value Transaction
```mermaid
sequenceDiagram
participant Browser
participant Frontend
participant API
participant Kafka
participant Consumer
participant Redis
Browser->>Frontend: 1. User submits transaction
activate Frontend
Frontend->>API: 2. POST /transaction (Amount: $5000)
activate API
API->>API: 3. Validate JWT ✓
API->>API: 4. Validate schema ✓
API->>Kafka: 5. Publish to "transactions"
activate Kafka
API-->>Frontend: 6. Return 200 OK (always)
deactivate API
Frontend-->>Browser: 7. Show "Accepted!"
deactivate Frontend
Kafka->>Consumer: 8. Deliver message
deactivate Kafka
activate Consumer
Consumer->>Consumer: 9. Extract features
Note over Consumer: ⚠️ ANOMALY: Amount spike!
Consumer->>Consumer: 10. Layer 1 score: 0.68
Consumer->>Consumer: 11. Layer 2 score: 0.72
Consumer->>Consumer: 12. Decision: FRAUD_DETECTED
Consumer->>Redis: 13. Store result
deactivate Consumer
activate Redis
Redis->>Redis: 14. Persist to disk
deactivate Redis
Note over Consumer,Redis: Fraud score > 0.7 = FRAUD_DETECTED 🚨
```
---
## Container Networking
### Docker Network Architecture
```mermaid
graph TB
subgraph Host["🖥️ Your Mac (Host Machine)"]
PORT_MAPPING["Port Mapping:<br/>3000→3000<br/>8000→8000<br/>9092→9092<br/>6379→6379<br/>2181→2181"]
end
subgraph Docker["🐳 Docker Bridge Network<br/>payguard-network"]
ZK["Zookeeper<br/>:2181"]
KAFKA["Kafka<br/>:29092 (internal)<br/>:9092 (external)"]
REDIS["Redis<br/>:6379"]
API["API<br/>:8000"]
FE["Frontend<br/>:3000"]
end
subgraph Consumer["🖥️ Your Mac<br/>Consumer Process"]
CONS["Consumer<br/>localhost:9092"]
end
HOST_CONSUMER["Your Mac<br/>Browser"]
HOST_CONSUMER -->|http://localhost:3000| FE
HOST_CONSUMER -->|http://localhost:8000| API
CONS -->|localhost:9092| KAFKA
ZK -->|coordinates| KAFKA
API -->|kafka:29092| KAFKA
API -->|redis:6379| REDIS
FE -->|localhost:8000| API
style Docker fill:#E3F2FD
style Host fill:#F3E5F5
style Consumer fill:#FFF3E0
style KAFKA fill:#2196F3
style API fill:#9C27B0
style REDIS fill:#FF9800
style ZK fill:#4CAF50
```
### Service Name Resolution
```mermaid
graph LR
API_CODE["API Code:<br/>kafka:29092"]
DNS["Docker DNS<br/>Resolver"]
KAFKA_IP["Kafka Container IP<br/>172.19.0.3"]
KAFKA["Kafka<br/>Listening on<br/>:29092"]
API_CODE -->|Lookup| DNS
DNS -->|Resolve| KAFKA_IP
KAFKA_IP -->|Connect to| KAFKA
KAFKA -->|Accept| KAFKA
style API_CODE fill:#9C27B0
style DNS fill:#4CAF50
style KAFKA_IP fill:#2196F3
style KAFKA fill:#2196F3
```
---
## Startup Sequence
```mermaid
graph TD
START["docker compose up -d --build"]
START -->|1. No deps| ZK["Zookeeper Starts"]
ZK -->|Ready| ZK_OK["✓ Zookeeper Ready<br/>:2181"]
ZK_OK -->|2. Waits for| KAFKA["Kafka Starts"]
KAFKA -->|Health check| KAFKA_HC["Kafka API Version OK?"]
KAFKA_HC -->|Yes| KAFKA_OK["✓ Kafka Ready<br/>:29092"]
KAFKA_OK -->|3. No deps| REDIS["Redis Starts"]
REDIS -->|Health check| REDIS_HC["redis-cli ping?"]
REDIS_HC -->|PONG| REDIS_OK["✓ Redis Ready<br/>:6379"]
KAFKA_OK -->|4. Waits for| API["API Starts"]
REDIS_OK -->|4. Waits for| API
API -->|Initialize| API_INIT["Setup FastAPI"]
API_INIT -->|Routes| API_READY["✓ API Ready<br/>:8000"]
API_READY -->|5. Waits for| FE["Frontend Starts"]
KAFKA_OK -->|5. Waits for| FE
REDIS_OK -->|5. Waits for| FE
FE -->|Build dist| FE_BUILD["npm run build"]
FE_BUILD -->|Serve| FE_READY["✓ Frontend Ready<br/>:3000"]
FE_READY -->|All Ready| DONE["🎉 System Ready!"]
style START fill:#2196F3
style DONE fill:#4CAF50
style ZK_OK fill:#81C784
style KAFKA_OK fill:#81C784
style REDIS_OK fill:#81C784
style API_READY fill:#81C784
style FE_READY fill:#81C784
```
---
## Communication Paths
### Valid Connections
```mermaid
graph TB
subgraph INSIDE["Inside Docker Network"]
API["API Container"]
KAFKA["Kafka Container"]
REDIS["Redis Container"]
end
subgraph OUTSIDE["Outside Docker Network"]
MAC["Your Mac"]
BROWSER["Browser"]
CONSUMER["Consumer Process"]
end
API -->|✅ kafka:29092| KAFKA
API -->|✅ redis:6379| REDIS
BROWSER -->|✅ localhost:8000| API
BROWSER -->|✅ localhost:3000| API
CONSUMER -->|✅ localhost:9092| KAFKA
API -.->|❌ localhost:9092| KAFKA
MAC -.->|❌ kafka:29092| KAFKA
style INSIDE fill:#E8F5E9
style OUTSIDE fill:#FFF3E0
style KAFKA fill:#2196F3
style API fill:#9C27B0
style REDIS fill:#FF9800
```
---
## Resource Usage & Performance
### Container Resource Estimates
```mermaid
graph TB
TOTAL["Total System<br/>~1.5 GB RAM"]
TOTAL --> ZK["Zookeeper<br/>100-200 MB"]
TOTAL --> KAFKA["Kafka<br/>500-800 MB"]
TOTAL --> REDIS["Redis<br/>50-100 MB"]
TOTAL --> API["API<br/>200-300 MB"]
TOTAL --> FE["Frontend<br/>50-100 MB"]
style TOTAL fill:#FF6F00
style ZK fill:#4CAF50
style KAFKA fill:#2196F3
style REDIS fill:#FF9800
style API fill:#9C27B0
style FE fill:#F44336
```
### Typical Latency
```mermaid
graph LR
USER["User<br/>Action"]
API_CALL["API Call<br/>&lt;10ms"]
KAFKA["Kafka<br/>Publish<br/>&lt;50ms"]
CONSUMER["Consumer<br/>Receive<br/>&lt;100ms"]
ML["ML<br/>Processing<br/>200-500ms"]
REDIS["Redis<br/>Store<br/>&lt;10ms"]
USER -->|⏱️| API_CALL
API_CALL -->|⏱️| KAFKA
KAFKA -->|⏱️| CONSUMER
CONSUMER -->|⏱️| ML
ML -->|⏱️| REDIS
style USER fill:#FF5722
style API_CALL fill:#4CAF50
style KAFKA fill:#2196F3
style CONSUMER fill:#FF9800
style ML fill:#FF9800
style REDIS fill:#FF9800
```
---
## Summary Table
| Container | Image | Port | Purpose | Depends On | Status |
|-----------|-------|------|---------|-----------|--------|
| **Zookeeper** | confluentinc/cp-zookeeper | 2181 | Kafka coordination | None | Always up |
| **Kafka** | confluentinc/cp-kafka | 29092/9092 | Message broker | Zookeeper ✓ | Healthy |
| **Redis** | redis:alpine | 6379 | Result caching | None | Healthy |
| **API** | python:3.11-slim | 8000 | Transaction intake | Kafka, Redis ✓ | Running |
| **Frontend** | node:18-alpine | 3000 | User interface | API ✓ | Healthy |
---
## Key Concepts
### Asynchronous Processing
The API returns immediately (200 OK) without waiting for fraud detection results. Consumer processes transactions asynchronously from Kafka queue.
### Service Discovery
Docker's embedded DNS server resolves service names (kafka, redis, etc.) to container IPs on the payguard-network.
### Port Mapping
Exposes container ports to host machine:
- Internal communication: Container name + internal port (kafka:29092)
- External access: localhost + exposed port (localhost:9092)
### Health Checks
Each container declares how to verify it's healthy. Other containers wait for dependencies to be healthy before starting.
### Data Persistence
Redis volume ensures results survive container restarts. Kafka and Zookeeper also persist their data.
---
This architecture enables:
✅ **Scalability** - Add more consumers for parallel processing
✅ **Reliability** - Kafka persists messages
✅ **Performance** - Async processing doesn't block frontend
✅ **Testability** - Each component can be tested independently
✅ **Maintainability** - Clear separation of concerns
+447
View File
@@ -0,0 +1,447 @@
# PayGuard Fraud Detection - Complete Demo Guide
## System Overview
The PayGuard system is a **6-step fraud detection pipeline** that analyzes transactions in real-time:
```
Transaction Submitted → Feature Extraction → ML Layer 1 → ML Layer 2 → Decision Engine → Risk Assessment → Results Stored
```
## Prerequisites
All services must be running:
```bash
docker compose up -d --build
```
Verify with:
```bash
docker compose ps
```
All 5 services should show as "Up":
- zookeeper
- kafka
- redis
- api (fastapi)
- frontend (react)
## How the Demo Works
### 1. User Submits Transaction via Frontend
The React frontend at `http://localhost:3000` collects:
- User ID (auto-generated)
- Token (JWT authentication)
- Transaction details (amount, merchant, description)
### 2. API Validates & Publishes to Kafka
The FastAPI backend:
- Validates JWT token
- Checks transaction format
- Publishes message to Kafka topic: `transactions`
- Returns: `{"status": "accepted", "transaction_id": "..."}`
### 3. Consumer Listens & Processes
The consumer pipeline:
- Subscribes to Kafka `transactions` topic
- Processes each message through 6-step pipeline
- Stores results in Redis
### 4. ML Models Score Fraud Risk
The pipeline runs two layers of ML models:
- **Layer 1**: Initial risk scoring
- **Layer 2**: Refined prediction
Decision outcomes:
- **APPROVED** (score < 0.3) - Legitimate transaction
- **REVIEW** (score 0.3-0.7) - Borderline, needs human review
- **FRAUD_DETECTED** (score > 0.7) - High fraud risk, block
## Complete Demo Walkthrough
### Step 1: Start Services (if not running)
```bash
# Terminal 1: Ensure Docker services are running
cd /Users/pat/Projects/PAT/payguard-poc
docker compose up -d --build
```
### Step 2: Start the Consumer Pipeline
```bash
# Terminal 2: Run the fraud detection consumer
cd /Users/pat/Projects/PAT/payguard-poc
cd consumer
python3 pipeline.py
```
You should see:
```
2026-04-24 21:25:19,338 [INFO] ================================================================================
2026-04-24 21:25:19,338 [INFO] PayGuard Fraud Detection Consumer - INITIALIZATION
2026-04-24 21:25:19,338 [INFO] ================================================================================
2026-04-24 21:25:19,339 [INFO] [FEATURE EXTRACTOR] Initialized, redis=localhost:6379
2026-04-24 21:25:19,400 [INFO] [DECISION ENGINE] Initialized with circuit breaker
2026-04-24 21:25:19,450 [INFO] [CONSUMER] Listening on topic: transactions
```
### Step 3: Open Frontend
```
http://localhost:3000
```
You'll see the PayGuard transaction submission form.
### Step 4: Generate Initial Token
Click **"Generate Token"** button
You should see:
- Green message: "Token generated successfully"
- JWT token displayed (starts with `eyJh...`)
- Token Type: "bearer"
### Step 5: Submit Test Transactions
Submit these transactions in sequence to demonstrate different fraud detection scenarios:
#### Scenario 1: Normal Transaction (LEGITIMATE)
```
Amount: 75.50
Merchant: Starbucks Coffee
Description: Morning coffee purchase
```
**Expected Result:**
```
API Response: 200 OK
{
"status": "accepted",
"transaction_id": "txn_abc123",
"message": "Transaction accepted"
}
Consumer Output:
[INFO] Processing transaction: txn_abc123
[INFO] Amount: $75.50 | Merchant: Starbucks | Features: NORMAL
[INFO] Layer 1 Score: 0.15
[INFO] Layer 2 Score: 0.12
[INFO] Decision: APPROVED (Score: 0.12)
[INFO] Risk Level: LOW
[INFO] Result stored in Redis
```
#### Scenario 2: High-Value Transaction (SUSPICIOUS)
```
Amount: 5000.00
Merchant: Electronics Store
Description: Laptop purchase
```
**Expected Result:**
```
API Response: 200 OK
{
"status": "accepted",
"transaction_id": "txn_def456",
...
}
Consumer Output:
[INFO] Processing transaction: txn_def456
[INFO] Amount: $5000.00 | Merchant: Electronics | Features: HIGH_AMOUNT
[INFO] Layer 1 Score: 0.68
[INFO] Layer 2 Score: 0.72
[INFO] Decision: FRAUD_DETECTED (Score: 0.72)
[INFO] Risk Level: HIGH
[INFO] Result stored in Redis
[WARNING] Transaction flagged for review
```
#### Scenario 3: Rapid Successive Transactions (VELOCITY FRAUD)
Submit these 3 transactions quickly (within 60 seconds):
```
Transaction 1:
Amount: 200.00
Merchant: Best Buy
Description: Computer monitor
Transaction 2 (10 seconds later):
Amount: 300.00
Merchant: Amazon
Description: Keyboard
Transaction 3 (20 seconds later):
Amount: 250.00
Merchant: Target
Description: Office supplies
```
**Expected Result:**
```
Consumer Output for Transaction 2:
[INFO] Processing transaction: txn_ghi789
[INFO] Features: NORMAL
[INFO] Layer 1 Score: 0.25
[INFO] Layer 2 Score: 0.28
[INFO] Decision: APPROVED
Consumer Output for Transaction 3:
[INFO] Processing transaction: txn_jkl012
[INFO] Features: VELOCITY_FRAUD (3 transactions in 60 seconds)
[INFO] Layer 1 Score: 0.75
[INFO] Layer 2 Score: 0.81
[INFO] Decision: FRAUD_DETECTED
[INFO] Risk Level: HIGH
[INFO] Reason: Unusual transaction velocity
```
#### Scenario 4: Unusual Geographic Pattern (if supported)
```
Amount: 2000.00
Merchant: International Hotel (different country)
Description: Hotel booking
```
**Expected Result:**
```
Consumer Output:
[INFO] Processing transaction: txn_mno345
[INFO] Features: GEOGRAPHIC_ANOMALY
[INFO] Layer 1 Score: 0.65
[INFO] Layer 2 Score: 0.70
[INFO] Decision: FRAUD_DETECTED (borderline)
[INFO] Risk Level: HIGH
```
## Understanding Consumer Output
When the consumer processes a transaction, look for:
### Processing Log Format
```
[TIMESTAMP] [LOG_LEVEL] [COMPONENT] Message
```
### Key Components
- `[FEATURE EXTRACTOR]` - Analyzing transaction features
- `[MODEL SCORER]` - Running ML models
- `[DECISION ENGINE]` - Making fraud/legitimate decision
- `[RESULT STORAGE]` - Saving to Redis
### Risk Scores
The ML models produce a fraud risk score from 0.0 to 1.0:
```
0.0 - 0.3 = APPROVED (Safe transaction)
0.3 - 0.7 = REVIEW (Borderline, needs human review)
0.7 - 1.0 = FRAUD_DETECTED (Block transaction)
```
### What Triggers Fraud Detection
The models detect:
1. **Unusual Amount** - Spike from typical spending
2. **Velocity Fraud** - Too many transactions too quickly
3. **Merchant Category Mismatch** - Wrong type of store for user
4. **Time-Based Anomaly** - Transaction at unusual hour
5. **Geographic Inconsistency** - Impossible travel between locations
6. **Behavioral Change** - Deviation from user's normal patterns
## Checking Results in Redis
Optional: View the results stored in Redis:
```bash
# Terminal 3: Connect to Redis
redis-cli -p 6379
# List all stored transactions
> KEYS *
# View a specific transaction result
> GET transaction:txn_abc123
> GET transaction:txn_def456
# View all keys
> SCAN 0
# Exit
> exit
```
Expected Redis data format:
```json
{
"transaction_id": "txn_abc123",
"user_id": "user_xyz",
"amount": 75.50,
"merchant": "Starbucks Coffee",
"fraud_score": 0.12,
"decision": "APPROVED",
"risk_level": "LOW",
"timestamp": "2026-04-24T21:30:00Z",
"features": {
"amount_std_dev": 0.8,
"velocity": 0.1,
"geographic_score": 0.0
}
}
```
## Complete Demo Timeline
**Duration: ~10 minutes**
```
0:00 - Start Docker services (docker compose up)
1:00 - Consumer pipeline listening
2:00 - Open http://localhost:3000
2:30 - Generate token
3:00 - Submit normal transaction ($75.50)
3:30 - Consumer processes, shows APPROVED
4:00 - Submit high-value transaction ($5000)
4:30 - Consumer processes, shows FRAUD_DETECTED
5:00 - Submit 3 rapid transactions
6:00 - Consumer detects velocity fraud on 3rd
6:30 - Explain results and scoring
7:00 - Check Redis for stored results
8:00 - Show architecture and data flow
```
## Troubleshooting
### Consumer Won't Start
```bash
# Make sure you're in the right directory
cd /Users/pat/Projects/PAT/payguard-poc
python3 -m pip install --break-system-packages kafka-python
python3 consumer/pipeline.py
```
### API Returns 503 Error
```bash
# Check Kafka is running
docker compose logs kafka | tail -20
# Restart if needed
docker compose restart kafka
```
### No Consumer Output
```bash
# Check consumer logs
docker compose logs api | tail -20
# Verify Kafka is receiving messages
docker compose exec kafka kafka-console-consumer \
--bootstrap-server kafka:29092 \
--topic transactions \
--from-beginning
```
### Frontend Shows CORS Error
Already fixed - but if you see it:
```bash
# Rebuild API with CORS
docker compose up -d --build api
```
## Architecture Diagram
```
┌─────────────────────────────────────────────────────┐
│ Browser: http://localhost:3000 │
│ ┌───────────────────────────────────────────────┐ │
│ │ React Frontend │ │
│ │ - Generate Token (JWT) │ │
│ │ - Submit Transactions │ │
│ │ - Display Status (200 OK / 503) │ │
│ └─────────────┬─────────────────────────────────┘ │
└───────────────┼─────────────────────────────────────┘
│ HTTP + CORS
↓
┌─────────────────────────────────────┐
│ API: http://localhost:8000 │
│ ┌─────────────────────────────────┐ │
│ │ FastAPI Backend │ │
│ │ - JWT Validation │ │
│ │ - Transaction Validation │ │
│ │ - Kafka Publishing │ │
│ └──────────┬──────────────────────┘ │
└───────────┼────────────────────────┘
│ TCP to kafka:29092
↓
┌─────────────────────────────────────┐
│ Kafka: localhost:9092 (external) │
│ Internal: kafka:29092 │
│ Topic: transactions │
└──────────┬────────────────────────┘
│
┌────────┴──────────┬────────────┐
│ │ │
↓ ↓ ↓
┌─────────┐ ┌────────┐ ┌──────────┐
│ Consumer│ │ Redis │ │ Zookeeper│
│Pipeline │ │ Cache │ │ Coord │
│(6 steps)│ │ │ │ │
└────┬────┘ └────────┘ └──────────┘
│
ML Models
Decision
Results
```
## What This Demonstrates
✅ **End-to-End Architecture**
- Frontend to API communication
- CORS cross-origin requests
- Kafka async messaging
- Docker containerization
✅ **Fraud Detection**
- Feature extraction from transactions
- Machine learning model scoring
- Decision engine logic
- Risk assessment
✅ **Real-Time Processing**
- Immediate transaction acceptance
- Async consumer processing
- Live logging of decisions
- Redis result persistence
✅ **Scalability**
- Kafka can handle high throughput
- Consumer can be scaled horizontally
- Stateless API design
- Distributed architecture
## Next Steps
After the demo:
1. Modify transaction amounts to see different fraud scores
2. Submit transactions during "off-hours" to test time-based detection
3. Check Redis for all stored decisions
4. Review consumer logs for detailed scoring
5. Examine the 6-step pipeline in `consumer/pipeline.py`
---
**Ready to demonstrate?** Start with Step 1 above!
+29
View File
@@ -1,3 +1,32 @@
# 🚀 PayGuard Fraud Detection System - Quick Start
## Get Started in 2 Steps (2 minutes)
```bash
# 1️⃣ Start all Docker services (including fraud detection consumer)
docker compose up -d --build
# 2️⃣ Open the frontend
# Visit http://localhost:3000 in your browser
```
**That's it!** The entire system runs in Docker. Submit transactions and watch fraud detection work in real-time.
Monitor consumer logs with: `docker compose logs -f consumer`
---
### 📚 Full Documentation
- **[DEMO.md](./DEMO.md)** - Complete step-by-step demonstration guide (10 minutes)
- **[EXPLAIN.md](./EXPLAIN.md)** - System architecture with Mermaid diagrams
- **[QUICKSTART.md](./QUICKSTART.md)** - Detailed setup guide
- **[FRAUD_DETECTION_DEMO.md](./FRAUD_DETECTION_DEMO.md)** - Fraud scenarios and examples
---
## System Architecture Overview
Here's the full end-to-end flowchart of the PayGuard system. The flow follows this logic:
Transaction initiated → passes through JWT authentication to verify the caller's identity.
+119 -1
View File
@@ -13,7 +13,7 @@ hands off responsibility to the message queue. This separation of concerns is
fundamental to scalable microservices architecture.
"""
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi import FastAPI, Depends, HTTPException, status, WebSocket, WebSocketDisconnect
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
@@ -23,6 +23,10 @@ import json
import uuid
import time
import logging
import redis
import threading
import asyncio
from queue import Queue
# ══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
@@ -85,6 +89,62 @@ def get_kafka_producer():
raise
return kafka_producer
# ══════════════════════════════════════════════════════════════════════════════
# REDIS & WEBSOCKET PUBSUB
# ══════════════════════════════════════════════════════════════════════════════
redis_client = None
message_queue = Queue() # Thread-safe queue for passing messages
connected_clients = [] # List of asyncio queues
pubsub_thread = None
def get_redis_client():
"""Get or create Redis client"""
global redis_client
if redis_client is None:
try:
redis_client = redis.Redis(
host="redis",
port=6379,
decode_responses=True
)
redis_client.ping()
logger.info("[REDIS] Connected to Redis")
except Exception as e:
logger.warning(f"[REDIS] Connection failed: {e}")
redis_client = None
raise
return redis_client
def start_pubsub_listener():
"""Start background thread to listen for fraud results on Redis pub/sub"""
global pubsub_thread
def listen_and_queue():
try:
redis_conn = get_redis_client()
pubsub = redis_conn.pubsub()
pubsub.subscribe("fraud_results")
logger.info("[PUBSUB] Listening to 'fraud_results' channel")
for message in pubsub.listen():
if message["type"] == "message":
try:
data = json.loads(message["data"])
logger.info(f"[PUBSUB] Received: {data.get('transaction_id', 'unknown')[:8]}...")
# Store latest result in Redis (for late connections to fetch)
redis_conn.setex("latest_fraud_result", 3600, json.dumps(data))
# Queue message for real-time delivery to connected clients
message_queue.put(data)
logger.info(f"[PUBSUB] Queued for {len(connected_clients)} WebSocket clients")
except Exception as e:
logger.error(f"[PUBSUB] Failed to parse message: {e}")
except Exception as e:
logger.error(f"[PUBSUB] Listener error: {e}")
pubsub_thread = threading.Thread(target=listen_and_queue, daemon=True)
pubsub_thread.start()
# ══════════════════════════════════════════════════════════════════════════════
# AUTHENTICATION
# ══════════════════════════════════════════════════════════════════════════════
@@ -211,6 +271,57 @@ async def get_demo_token(user_id: str):
logger.info(f"[JWT GENERATION] Generated token for user={user_id}")
return {"access_token": token, "token_type": "bearer"}
@app.websocket("/ws/fraud-results")
async def websocket_fraud_results(websocket: WebSocket):
"""
WebSocket endpoint for real-time fraud detection results.
Clients connect and receive live updates when fraud detection completes.
Results are streamed from Redis pub/sub channel via background listener.
"""
await websocket.accept()
connected_clients.append(websocket)
logger.info(f"[WEBSOCKET] Client connected. Total clients: {len(connected_clients)}")
try:
# Send latest result if available (for late connections)
try:
latest = redis_client.get("latest_fraud_result")
if latest:
data = json.loads(latest)
logger.info(f"[WEBSOCKET] Sending latest result to new client")
await websocket.send_json(data)
except Exception as e:
logger.debug(f"[WEBSOCKET] No latest result available: {e}")
# Now listen for new results
while True:
try:
msg = message_queue.get_nowait()
logger.info(f"[WEBSOCKET] Sending fraud result to client: {msg.get('transaction_id', 'unknown')}")
await websocket.send_json(msg)
logger.info(f"[WEBSOCKET] ✓ Message sent successfully")
except Exception as e:
if e.__class__.__name__ == 'Empty':
# Queue empty, sleep briefly before checking again
await asyncio.sleep(0.2)
else:
# Real error - log it but continue
logger.error(f"[WEBSOCKET] Error sending message: {type(e).__name__}: {e}")
await asyncio.sleep(0.2)
except WebSocketDisconnect:
try:
connected_clients.remove(websocket)
except:
pass
logger.info(f"[WEBSOCKET] Client disconnected. Total clients: {len(connected_clients)}")
except Exception as e:
logger.error(f"[WEBSOCKET] Error: {type(e).__name__}: {e}")
try:
connected_clients.remove(websocket)
except:
pass
# ══════════════════════════════════════════════════════════════════════════════
# STARTUP / SHUTDOWN
# ══════════════════════════════════════════════════════════════════════════════
@@ -225,6 +336,13 @@ async def startup_event():
logger.info("Listening for authenticated transaction requests...")
logger.info("="*80)
# Start Redis pub/sub listener for WebSocket broadcasts
try:
get_redis_client()
start_pubsub_listener()
except Exception as e:
logger.warning(f"[STARTUP] Redis pub/sub not available: {e}")
@app.on_event("shutdown")
async def shutdown_event():
logger.info("Shutting down API...")
+29
View File
@@ -0,0 +1,29 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements from root
COPY requirements.txt /tmp/
# Copy models module (includes __init__.py with class definitions and .pkl files)
COPY models /app/ml_models
# Copy consumer code
COPY consumer/ /app/
# Install Python dependencies
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# Set environment for Docker network and models location
ENV KAFKA_BROKER=kafka:29092
ENV REDIS_URL=redis://redis:6379
ENV ML_MODELS_DIR=/app/ml_models
ENV PYTHONPATH=/app:/app/ml_models
# Run the consumer pipeline
CMD ["python3", "pipeline.py"]
+36 -3
View File
@@ -14,6 +14,7 @@ scores, rather than failing entirely. This ensures uptime even during partial ou
import redis
import logging
import os
from pybreaker import CircuitBreaker
logger = logging.getLogger(__name__)
@@ -21,7 +22,13 @@ logger = logging.getLogger(__name__)
class DecisionEngine:
"""Makes final approve/block decisions with resilience patterns"""
def __init__(self, redis_host: str = "localhost", redis_port: int = 6379):
def __init__(self, redis_host: str = None, redis_port: int = None):
# Support environment variables for Docker deployments
if redis_host is None:
redis_host = os.getenv("REDIS_HOST", "localhost")
if redis_port is None:
redis_port = int(os.getenv("REDIS_PORT", "6379"))
self.redis_client = redis.Redis(
host=redis_host,
port=redis_port,
@@ -115,8 +122,12 @@ class DecisionEngine:
return decision
def store_result(self, transaction_id: str, decision: str,
layer1_score: float, layer2_score: float = None) -> None:
"""Store decision result in Redis for retrieval"""
layer1_score: float, layer2_score: float = None,
transaction_data: dict = None) -> None:
"""Store decision result in Redis and publish to pub/sub channel"""
import json
from datetime import datetime
key = f"result:{transaction_id}"
value = {
"decision": decision,
@@ -129,3 +140,25 @@ class DecisionEngine:
self.redis_client.setex(key, 3600, str(value))
logger.info(f"[RESULT STORAGE] Stored decision in Redis: {key} = {decision}")
# Publish to pub/sub channel for real-time WebSocket updates
try:
# Build message with transaction details for frontend
message = {
"transaction_id": transaction_id,
"decision": decision,
"fraud_score": round(layer2_score, 4) if layer2_score is not None else round(layer1_score, 4),
"layer1_score": round(layer1_score, 4),
"timestamp": datetime.utcnow().isoformat() + "Z"
}
# Add transaction data if provided (amount, merchant)
if transaction_data:
message["amount"] = transaction_data.get("amount")
message["merchant"] = transaction_data.get("merchant")
# Publish to Redis pub/sub channel
self.redis_client.publish("fraud_results", json.dumps(message))
logger.info(f"[RESULT STORAGE] Published result to pub/sub channel: {transaction_id[:8]}... → {decision}")
except Exception as e:
logger.error(f"[RESULT STORAGE] Failed to publish to pub/sub: {str(e)}")
+9 -1
View File
@@ -15,6 +15,7 @@ context to improve model decision quality.
import redis
import numpy as np
import logging
import os
from typing import Dict, List
logger = logging.getLogger(__name__)
@@ -22,7 +23,13 @@ logger = logging.getLogger(__name__)
class FeatureExtractor:
"""Extracts and engineers features from transactions"""
def __init__(self, redis_host: str = "localhost", redis_port: int = 6379):
def __init__(self, redis_host: str = None, redis_port: int = None):
# Support environment variables for Docker deployments
if redis_host is None:
redis_host = os.getenv("REDIS_HOST", "localhost")
if redis_port is None:
redis_port = int(os.getenv("REDIS_PORT", "6379"))
self.redis_client = redis.Redis(
host=redis_host,
port=redis_port,
@@ -30,6 +37,7 @@ class FeatureExtractor:
)
logger.info(f"[FEATURE EXTRACTOR] Initialized, redis={redis_host}:{redis_port}")
def get_user_profile(self, user_id: str) -> Dict[str, float]:
"""
Retrieve user's behavioral profile from Redis.
+23 -2
View File
@@ -14,15 +14,35 @@ are delegated to a more expensive deep model. This optimizes latency vs. accurac
import pickle
import logging
import os
from typing import Tuple
logger = logging.getLogger(__name__)
# Import model classes - they may be at /app/ml_models or /models in different contexts
try:
# Docker context
from ml_models import Layer1MockModel, Layer2MockModel
except ImportError:
try:
# Local context (when running from host)
from models import Layer1MockModel, Layer2MockModel
except ImportError:
pass
class ModelScorer:
"""Loads and runs fraud detection models"""
def __init__(self, layer1_path: str = "models/layer1.pkl",
layer2_path: str = "models/layer2.pkl"):
def __init__(self, layer1_path: str = None, layer2_path: str = None):
# Auto-detect paths based on context
if layer1_path is None:
models_dir = os.getenv("ML_MODELS_DIR", "models")
layer1_path = os.path.join(models_dir, "layer1.pkl")
if layer2_path is None:
models_dir = os.getenv("ML_MODELS_DIR", "models")
layer2_path = os.path.join(models_dir, "layer2.pkl")
# Load Layer 1 model
with open(layer1_path, 'rb') as f:
self.layer1_model = pickle.load(f)
@@ -70,3 +90,4 @@ class ModelScorer:
)
return fraud_probability
+19 -8
View File
@@ -14,6 +14,7 @@ Each step is independently verifiable, making it suitable for academic demonstra
import json
import logging
import os
from kafka import KafkaConsumer
from kafka.errors import KafkaError
@@ -25,10 +26,19 @@ from decision import DecisionEngine
# CONFIGURATION
# ══════════════════════════════════════════════════════════════════════════════
KAFKA_BROKER = "localhost:9092"
KAFKA_BROKER = os.getenv("KAFKA_BROKER", "localhost:9092")
KAFKA_TOPIC = "transactions"
KAFKA_GROUP = "fraud-detection-consumer-group"
# Redis configuration
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))
# ML Models configuration
ML_MODELS_DIR = os.getenv("ML_MODELS_DIR", "models")
LAYER1_PATH = os.path.join(ML_MODELS_DIR, "layer1.pkl")
LAYER2_PATH = os.path.join(ML_MODELS_DIR, "layer2.pkl")
# Logging setup
logging.basicConfig(
level=logging.INFO,
@@ -49,20 +59,20 @@ def initialize_pipeline():
# Feature extraction
feature_extractor = FeatureExtractor(
redis_host="localhost",
redis_port=6379
redis_host=REDIS_HOST,
redis_port=REDIS_PORT
)
# Model scoring
model_scorer = ModelScorer(
layer1_path="models/layer1.pkl",
layer2_path="models/layer2.pkl"
layer1_path=LAYER1_PATH,
layer2_path=LAYER2_PATH
)
# Decision engine with circuit breaker
decision_engine = DecisionEngine(
redis_host="localhost",
redis_port=6379
redis_host=REDIS_HOST,
redis_port=REDIS_PORT
)
# Kafka consumer
@@ -183,7 +193,8 @@ def process_transaction(transaction, components):
transaction_id=transaction_id,
decision=decision,
layer1_score=layer1_score,
layer2_score=layer2_score
layer2_score=layer2_score,
transaction_data=transaction # Pass full transaction for pub/sub message
)
# ─────────────────────────────────────────────────────────────────────────
+20 -3
View File
@@ -90,9 +90,26 @@ services:
- NODE_ENV=production
- VITE_API_URL=http://localhost:8000
restart: unless-stopped
# NOTE: All services now run in Docker on the same network
# Frontend: http://localhost:8000 (Docker service name resolution)
# Consumer: Still runs on host, connects to kafka:29092
consumer:
build:
context: .
dockerfile: consumer/Dockerfile
networks:
- payguard-network
depends_on:
kafka:
condition: service_healthy
redis:
condition: service_healthy
environment:
- KAFKA_BROKER=kafka:29092
- REDIS_HOST=redis
- REDIS_PORT=6379
- ML_MODELS_DIR=/app/ml_models
restart: unless-stopped
# Consumer runs indefinitely, consuming messages from Kafka and storing results in Redis
# Logs visible via: docker compose logs -f consumer
volumes:
redis_data:
+167
View File
@@ -345,3 +345,170 @@
font-size: 0.8rem;
}
}
/* Consumer Status Card */
.consumer-card {
border-top: 4px solid var(--primary);
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
}
.consumer-content {
display: flex;
flex-direction: column;
gap: 1rem;
}
.connection-status {
display: flex;
justify-content: center;
}
.connection-status .status-badge {
font-size: 0.875rem;
padding: 0.5rem 1rem;
}
.connection-status .status-badge.connected {
background: var(--success);
color: white;
}
.connection-status .status-badge.disconnected {
background: var(--error);
color: white;
}
.empty-state {
text-align: center;
padding: 2rem 1rem;
color: var(--text-light);
}
.empty-state p {
margin: 0.5rem 0;
}
.consumer-status-details {
display: flex;
flex-direction: column;
gap: 0.75rem;
background: white;
padding: 1rem;
border-radius: 0.375rem;
}
.status-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
}
.status-row:last-child {
border-bottom: none;
}
.status-row label {
font-weight: 600;
color: var(--text);
font-size: 0.875rem;
min-width: 120px;
}
.status-row span {
color: var(--text-light);
font-size: 0.875rem;
text-align: right;
flex: 1;
margin-left: 1rem;
}
/* Pagination Controls */
.pagination-controls {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
background: var(--bg);
border-radius: 0.375rem;
margin-bottom: 1rem;
}
.pagination-info {
text-align: center;
font-size: 0.875rem;
color: var(--text-light);
font-weight: 500;
}
.pagination-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.btn-pagination {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--primary);
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
min-width: 100px;
justify-content: center;
}
.btn-pagination:hover:not(:disabled) {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.btn-pagination:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-indicator {
font-size: 0.875rem;
font-weight: 600;
color: var(--text);
min-width: 120px;
text-align: center;
}
.page-size-selector {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
}
.page-size-selector label {
font-size: 0.875rem;
font-weight: 500;
color: var(--text);
}
.select-input {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
background: white;
}
.select-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
+220 -3
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { AlertCircle, CheckCircle, Clock, Lock, Send } from 'lucide-react';
import React, { useState, useEffect, useRef } from 'react';
import { AlertCircle, CheckCircle, Clock, Lock, Send, Wifi, WifiOff, ChevronLeft, ChevronRight } from 'lucide-react';
import axios from 'axios';
import './App.css';
@@ -28,6 +28,15 @@ interface Result {
timestamp: string;
}
interface FraudResult {
transaction_id: string;
decision: string;
fraud_score: number;
amount?: number;
merchant?: string;
timestamp: string;
}
// Use localhost when browser accesses from host machine
const API_BASE_URL = 'http://localhost:8000';
@@ -46,10 +55,73 @@ const App: React.FC = () => {
});
const [results, setResults] = useState<Result[]>([]);
const [consumerStatus, setConsumerStatus] = useState<FraudResult | null>(null);
const [wsConnected, setWsConnected] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
// Pagination state
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// WebSocket connection management
useEffect(() => {
const connectWebSocket = () => {
try {
const wsUrl = 'ws://localhost:8000/ws/fraud-results';
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('[WEBSOCKET] Connected to fraud results stream');
setWsConnected(true);
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as FraudResult;
console.log('[WEBSOCKET] Received fraud result:', data);
setConsumerStatus(data);
} catch (e) {
console.error('[WEBSOCKET] Failed to parse message:', e);
}
};
ws.onerror = (error) => {
console.error('[WEBSOCKET] Error:', error);
setWsConnected(false);
};
ws.onclose = () => {
console.log('[WEBSOCKET] Disconnected, attempting reconnect...');
setWsConnected(false);
// Exponential backoff reconnection: 1s → 2s → 4s → 8s
const delay = Math.min(1000 * Math.pow(2, 3), 8000);
reconnectTimeoutRef.current = setTimeout(connectWebSocket, delay);
};
wsRef.current = ws;
} catch (e) {
console.error('[WEBSOCKET] Connection failed:', e);
setWsConnected(false);
}
};
connectWebSocket();
return () => {
if (wsRef.current) {
wsRef.current.close();
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
};
}, []);
// Generate JWT token
const generateToken = async () => {
try {
@@ -173,6 +245,19 @@ const App: React.FC = () => {
});
};
// Pagination helpers
const totalPages = Math.ceil(results.length / pageSize);
const paginatedResults = results.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
const getFraudScoreColor = (score: number): string => {
if (score < 0.3) return '#10b981'; // Green - low risk
if (score < 0.7) return '#f59e0b'; // Yellow - medium risk
return '#ef4444'; // Red - high risk
};
return (
<div className="app">
<div className="header">
@@ -286,6 +371,88 @@ const App: React.FC = () => {
</button>
</section>
{/* Consumer Status Panel */}
<section className="card consumer-card">
<div className="card-header">
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{wsConnected ? (
<Wifi size={20} style={{ color: '#10b981' }} />
) : (
<WifiOff size={20} style={{ color: '#ef4444' }} />
)}
<h2>Real-Time Consumer Pipeline</h2>
</div>
</div>
<div className="consumer-content">
<div className="connection-status">
<span className={`status-badge ${wsConnected ? 'connected' : 'disconnected'}`}>
{wsConnected ? '● Connected' : '● Disconnected'}
</span>
</div>
{consumerStatus ? (
<div className="consumer-status-details">
<div className="status-row">
<label>Transaction ID:</label>
<span>{consumerStatus.transaction_id.substring(0, 12)}...</span>
</div>
{consumerStatus.amount && (
<div className="status-row">
<label>Amount:</label>
<span>${consumerStatus.amount.toFixed(2)}</span>
</div>
)}
{consumerStatus.merchant && (
<div className="status-row">
<label>Merchant:</label>
<span>{consumerStatus.merchant}</span>
</div>
)}
<div className="status-row">
<label>Fraud Score:</label>
<div
style={{
padding: '4px 8px',
backgroundColor: getFraudScoreColor(consumerStatus.fraud_score),
color: 'white',
borderRadius: '4px',
fontWeight: 'bold',
}}
>
{(consumerStatus.fraud_score * 100).toFixed(1)}%
</div>
</div>
<div className="status-row">
<label>Decision:</label>
<div
style={{
padding: '4px 8px',
backgroundColor: consumerStatus.decision === 'blocked' ? '#ef4444' : '#10b981',
color: 'white',
borderRadius: '4px',
fontWeight: 'bold',
textTransform: 'uppercase',
}}
>
{consumerStatus.decision}
</div>
</div>
<div className="status-row">
<label>Processed:</label>
<span>{new Date(consumerStatus.timestamp).toLocaleString()}</span>
</div>
</div>
) : (
<div className="empty-state">
<p>Waiting for fraud detection results...</p>
<p style={{ fontSize: '12px', color: '#6b7280' }}>
Submit transactions to see results
</p>
</div>
)}
</div>
</section>
{/* Status Messages */}
{error && (
<div className="alert alert-error">
@@ -308,8 +475,58 @@ const App: React.FC = () => {
<Clock size={20} />
<h2>Transaction Results</h2>
</div>
{/* Pagination Controls */}
<div className="pagination-controls">
<div className="pagination-info">
<span>
Showing {paginatedResults.length > 0 ? (currentPage - 1) * pageSize + 1 : 0}–
{Math.min(currentPage * pageSize, results.length)} of {results.length} transactions
</span>
</div>
<div className="pagination-buttons">
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="btn btn-pagination"
>
<ChevronLeft size={16} /> Previous
</button>
<span className="page-indicator">
Page {currentPage} of {totalPages || 1}
</span>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="btn btn-pagination"
>
Next <ChevronRight size={16} />
</button>
</div>
<div className="page-size-selector">
<label>Items per page:</label>
<select
value={pageSize}
onChange={(e) => {
setPageSize(parseInt(e.target.value));
setCurrentPage(1); // Reset to first page
}}
className="select-input"
>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
</select>
</div>
</div>
{/* Results List */}
<div className="results-list">
{results.map((result, idx) => (
{paginatedResults.map((result, idx) => (
<div key={idx} className="result-item">
<div className="result-content">
<p className="result-id">