Files
aitbc/apps/compliance-service/main.py
aitbc 9db720add8
Some checks failed
Documentation Validation / validate-docs (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
API Endpoint Tests / test-api-endpoints (push) Has been cancelled
CLI Tests / test-cli (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-agent-sdk path:packages/py/aitbc-agent-sdk]) (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-core path:packages/py/aitbc-core]) (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-crypto path:packages/py/aitbc-crypto]) (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-sdk path:packages/py/aitbc-sdk]) (push) Has been cancelled
Package Tests / test-javascript-packages (map[name:aitbc-sdk-js path:packages/js/aitbc-sdk]) (push) Has been cancelled
Package Tests / test-javascript-packages (map[name:aitbc-token path:packages/solidity/aitbc-token]) (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Systemd Sync / sync-systemd (push) Has been cancelled
docs: add code quality and type checking workflows to master index
- Add Code Quality Module section with pre-commit hooks and quality checks
- Add Type Checking CI/CD Module section with MyPy workflow and coverage
- Update README with code quality achievements and project structure
- Migrate FastAPI apps from deprecated on_event to lifespan context manager
- Update pyproject.toml files to reference consolidated dependencies
- Remove unused app.py import in coordinator-api
- Add type hints to agent
2026-03-31 21:45:43 +02:00

435 lines
15 KiB
Python
Executable File

"""
Production Compliance Service for AITBC
Handles KYC/AML, regulatory compliance, and monitoring
"""
import asyncio
import json
import logging
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, Any, List, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from contextlib import asynccontextmanager
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
logger.info("Starting AITBC Compliance Service")
# Start background compliance checks
asyncio.create_task(periodic_compliance_checks())
yield
# Shutdown
logger.info("Shutting down AITBC Compliance Service")
app = FastAPI(
title="AITBC Compliance Service",
description="Regulatory compliance and monitoring for AITBC operations",
version="1.0.0",
lifespan=lifespan
)
# Data models
class KYCRequest(BaseModel):
user_id: str
name: str
email: str
document_type: str
document_number: str
address: Dict[str, str]
class ComplianceReport(BaseModel):
report_type: str
description: str
severity: str # low, medium, high, critical
details: Dict[str, Any]
class TransactionMonitoring(BaseModel):
transaction_id: str
user_id: str
amount: float
currency: str
counterparty: str
timestamp: datetime
# In-memory storage (in production, use database)
kyc_records: Dict[str, Dict] = {}
compliance_reports: Dict[str, Dict] = {}
suspicious_transactions: Dict[str, Dict] = {}
compliance_rules: Dict[str, Dict] = {}
risk_scores: Dict[str, Dict] = {}
@app.get("/")
async def root():
return {
"service": "AITBC Compliance Service",
"status": "running",
"timestamp": datetime.utcnow().isoformat(),
"version": "1.0.0"
}
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"kyc_records": len(kyc_records),
"compliance_reports": len(compliance_reports),
"suspicious_transactions": len(suspicious_transactions),
"active_rules": len(compliance_rules)
}
@app.post("/api/v1/kyc/submit")
async def submit_kyc(kyc_request: KYCRequest):
"""Submit KYC verification request"""
if kyc_request.user_id in kyc_records:
raise HTTPException(status_code=400, detail="KYC already submitted for this user")
# Create KYC record
kyc_record = {
"user_id": kyc_request.user_id,
"name": kyc_request.name,
"email": kyc_request.email,
"document_type": kyc_request.document_type,
"document_number": kyc_request.document_number,
"address": kyc_request.address,
"status": "pending",
"submitted_at": datetime.utcnow().isoformat(),
"reviewed_at": None,
"approved_at": None,
"risk_score": "medium",
"notes": []
}
kyc_records[kyc_request.user_id] = kyc_record
# Simulate KYC verification process
await asyncio.sleep(2) # Simulate verification delay
# Auto-approve for demo (in production, this would involve actual verification)
kyc_record["status"] = "approved"
kyc_record["reviewed_at"] = datetime.utcnow().isoformat()
kyc_record["approved_at"] = datetime.utcnow().isoformat()
kyc_record["risk_score"] = "low"
logger.info(f"KYC approved for user: {kyc_request.user_id}")
return {
"user_id": kyc_request.user_id,
"status": kyc_record["status"],
"risk_score": kyc_record["risk_score"],
"approved_at": kyc_record["approved_at"]
}
@app.get("/api/v1/kyc/{user_id}")
async def get_kyc_status(user_id: str):
"""Get KYC status for a user"""
if user_id not in kyc_records:
raise HTTPException(status_code=404, detail="KYC record not found")
return kyc_records[user_id]
@app.get("/api/v1/kyc")
async def list_kyc_records():
"""List all KYC records"""
return {
"kyc_records": list(kyc_records.values()),
"total_records": len(kyc_records),
"approved": len([r for r in kyc_records.values() if r["status"] == "approved"]),
"pending": len([r for r in kyc_records.values() if r["status"] == "pending"]),
"rejected": len([r for r in kyc_records.values() if r["status"] == "rejected"])
}
@app.post("/api/v1/compliance/report")
async def create_compliance_report(report: ComplianceReport):
"""Create a compliance report"""
report_id = f"report_{int(datetime.utcnow().timestamp())}"
compliance_record = {
"report_id": report_id,
"report_type": report.report_type,
"description": report.description,
"severity": report.severity,
"details": report.details,
"status": "open",
"created_at": datetime.utcnow().isoformat(),
"assigned_to": None,
"resolved_at": None,
"resolution": None
}
compliance_reports[report_id] = compliance_record
logger.info(f"Compliance report created: {report_id} - {report.report_type}")
return {
"report_id": report_id,
"status": "created",
"severity": report.severity,
"created_at": compliance_record["created_at"]
}
@app.get("/api/v1/compliance/reports")
async def list_compliance_reports():
"""List all compliance reports"""
return {
"reports": list(compliance_reports.values()),
"total_reports": len(compliance_reports),
"open": len([r for r in compliance_reports.values() if r["status"] == "open"]),
"resolved": len([r for r in compliance_reports.values() if r["status"] == "resolved"])
}
@app.post("/api/v1/monitoring/transaction")
async def monitor_transaction(transaction: TransactionMonitoring):
"""Monitor transaction for compliance"""
transaction_id = transaction.transaction_id
# Create transaction monitoring record
monitoring_record = {
"transaction_id": transaction_id,
"user_id": transaction.user_id,
"amount": transaction.amount,
"currency": transaction.currency,
"counterparty": transaction.counterparty,
"timestamp": transaction.timestamp.isoformat(),
"monitored_at": datetime.utcnow().isoformat(),
"risk_score": calculate_transaction_risk(transaction),
"flags": [],
"status": "monitored"
}
suspicious_transactions[transaction_id] = monitoring_record
# Check for suspicious patterns
flags = check_suspicious_patterns(transaction)
if flags:
monitoring_record["flags"] = flags
monitoring_record["status"] = "flagged"
# Create compliance report for suspicious transaction
await create_suspicious_transaction_report(transaction, flags)
return {
"transaction_id": transaction_id,
"risk_score": monitoring_record["risk_score"],
"flags": flags,
"status": monitoring_record["status"]
}
@app.get("/api/v1/monitoring/transactions")
async def list_monitored_transactions():
"""List all monitored transactions"""
return {
"transactions": list(suspicious_transactions.values()),
"total_transactions": len(suspicious_transactions),
"flagged": len([t for t in suspicious_transactions.values() if t["status"] == "flagged"]),
"suspicious": len([t for t in suspicious_transactions.values() if t["risk_score"] == "high"])
}
@app.post("/api/v1/rules/create")
async def create_compliance_rule(rule_data: Dict[str, Any]):
"""Create a new compliance rule"""
rule_id = f"rule_{int(datetime.utcnow().timestamp())}"
rule = {
"rule_id": rule_id,
"name": rule_data.get("name"),
"description": rule_data.get("description"),
"type": rule_data.get("type"),
"conditions": rule_data.get("conditions", {}),
"actions": rule_data.get("actions", []),
"severity": rule_data.get("severity", "medium"),
"active": True,
"created_at": datetime.utcnow().isoformat(),
"trigger_count": 0
}
compliance_rules[rule_id] = rule
logger.info(f"Compliance rule created: {rule_id} - {rule['name']}")
return {
"rule_id": rule_id,
"name": rule["name"],
"status": "created",
"active": rule["active"]
}
@app.get("/api/v1/rules")
async def list_compliance_rules():
"""List all compliance rules"""
return {
"rules": list(compliance_rules.values()),
"total_rules": len(compliance_rules),
"active": len([r for r in compliance_rules.values() if r["active"]])
}
@app.get("/api/v1/dashboard")
async def compliance_dashboard():
"""Get compliance dashboard data"""
total_users = len(kyc_records)
approved_users = len([r for r in kyc_records.values() if r["status"] == "approved"])
pending_reviews = len([r for r in kyc_records.values() if r["status"] == "pending"])
total_reports = len(compliance_reports)
open_reports = len([r for r in compliance_reports.values() if r["status"] == "open"])
total_transactions = len(suspicious_transactions)
flagged_transactions = len([t for t in suspicious_transactions.values() if t["status"] == "flagged"])
return {
"summary": {
"total_users": total_users,
"approved_users": approved_users,
"pending_reviews": pending_reviews,
"approval_rate": (approved_users / total_users * 100) if total_users > 0 else 0,
"total_reports": total_reports,
"open_reports": open_reports,
"total_transactions": total_transactions,
"flagged_transactions": flagged_transactions,
"flag_rate": (flagged_transactions / total_transactions * 100) if total_transactions > 0 else 0
},
"risk_distribution": get_risk_distribution(),
"recent_activity": get_recent_activity(),
"generated_at": datetime.utcnow().isoformat()
}
# Helper functions
def calculate_transaction_risk(transaction: TransactionMonitoring) -> str:
"""Calculate risk score for a transaction"""
risk_score = 0
# Amount-based risk
if transaction.amount > 10000:
risk_score += 3
elif transaction.amount > 1000:
risk_score += 2
elif transaction.amount > 100:
risk_score += 1
# Time-based risk (transactions outside business hours)
hour = transaction.timestamp.hour
if hour < 9 or hour > 17:
risk_score += 1
# Convert to risk level
if risk_score >= 4:
return "high"
elif risk_score >= 2:
return "medium"
else:
return "low"
def check_suspicious_patterns(transaction: TransactionMonitoring) -> List[str]:
"""Check for suspicious transaction patterns"""
flags = []
# High value transaction
if transaction.amount > 50000:
flags.append("high_value_transaction")
# Rapid transactions (check if user has multiple transactions in short time)
user_transactions = [t for t in suspicious_transactions.values()
if t["user_id"] == transaction.user_id]
recent_transactions = [t for t in user_transactions
if datetime.fromisoformat(t["monitored_at"]) >
datetime.utcnow() - timedelta(hours=1)]
if len(recent_transactions) > 5:
flags.append("rapid_transactions")
# Unusual counterparty
if transaction.counterparty in ["high_risk_entity_1", "high_risk_entity_2"]:
flags.append("high_risk_counterparty")
return flags
async def create_suspicious_transaction_report(transaction: TransactionMonitoring, flags: List[str]):
"""Create compliance report for suspicious transaction"""
report_data = ComplianceReport(
report_type="suspicious_transaction",
description=f"Suspicious transaction detected: {transaction.transaction_id}",
severity="high",
details={
"transaction_id": transaction.transaction_id,
"user_id": transaction.user_id,
"amount": transaction.amount,
"flags": flags,
"timestamp": transaction.timestamp.isoformat()
}
)
await create_compliance_report(report_data)
def get_risk_distribution() -> Dict[str, int]:
"""Get distribution of risk scores"""
distribution = {"low": 0, "medium": 0, "high": 0}
for record in kyc_records.values():
distribution[record["risk_score"]] = distribution.get(record["risk_score"], 0) + 1
for transaction in suspicious_transactions.values():
distribution[transaction["risk_score"]] = distribution.get(transaction["risk_score"], 0) + 1
return distribution
def get_recent_activity() -> List[Dict]:
"""Get recent compliance activity"""
activities = []
# Recent KYC approvals
recent_kyc = [r for r in kyc_records.values()
if r.get("approved_at") and
datetime.fromisoformat(r["approved_at"]) >
datetime.utcnow() - timedelta(hours=24)]
for kyc in recent_kyc[:5]:
activities.append({
"type": "kyc_approved",
"description": f"KYC approved for {kyc['name']}",
"timestamp": kyc["approved_at"]
})
# Recent compliance reports
recent_reports = [r for r in compliance_reports.values()
if datetime.fromisoformat(r["created_at"]) >
datetime.utcnow() - timedelta(hours=24)]
for report in recent_reports[:5]:
activities.append({
"type": "compliance_report",
"description": f"Report: {report['description']}",
"timestamp": report["created_at"]
})
# Sort by timestamp
activities.sort(key=lambda x: x["timestamp"], reverse=True)
return activities[:10]
# Background task for periodic compliance checks
async def periodic_compliance_checks():
"""Background task for periodic compliance monitoring"""
while True:
await asyncio.sleep(300) # Check every 5 minutes
# Check for expired KYC records
current_time = datetime.utcnow()
for user_id, kyc_record in kyc_records.items():
if kyc_record["status"] == "approved":
approved_time = datetime.fromisoformat(kyc_record["approved_at"])
if current_time - approved_time > timedelta(days=365):
# Flag for re-verification
kyc_record["status"] = "reverification_required"
logger.info(f"KYC re-verification required for user: {user_id}")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8011, log_level="info")