Spaces:
Sleeping
Sleeping
| from flask import Flask, render_template, jsonify, request | |
| import random | |
| import time | |
| import uuid | |
| import datetime | |
| import os | |
| import pandas as pd | |
| from werkzeug.utils import secure_filename | |
| app = Flask(__name__) | |
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max limit | |
| app.config['UPLOAD_FOLDER'] = '/tmp' | |
| # --- Mock Data Generators --- | |
| LOCATIONS = [ | |
| {"city": "Beijing", "lat": 39.9042, "lon": 116.4074}, | |
| {"city": "Shanghai", "lat": 31.2304, "lon": 121.4737}, | |
| {"city": "Guangzhou", "lat": 23.1291, "lon": 113.2644}, | |
| {"city": "Shenzhen", "lat": 22.5431, "lon": 114.0579}, | |
| {"city": "Chengdu", "lat": 30.5728, "lon": 104.0668}, | |
| {"city": "Hangzhou", "lat": 30.2741, "lon": 120.1551}, | |
| {"city": "Wuhan", "lat": 30.5928, "lon": 114.3055}, | |
| {"city": "Xian", "lat": 34.3416, "lon": 108.9398}, | |
| {"city": "Overseas_US", "lat": 37.0902, "lon": -95.7129}, | |
| {"city": "Overseas_JP", "lat": 36.2048, "lon": 138.2529}, | |
| ] | |
| DEVICES = ["iPhone 14 Pro", "Huawei Mate 60", "Samsung S24", "Xiaomi 14", "Windows PC", "MacBook Pro", "iPad"] | |
| IPS = ["192.168.1.1", "10.0.0.1", "172.16.0.1", "202.106.0.20", "8.8.8.8"] | |
| USERS = [f"user_{i}" for i in range(1001, 1020)] | |
| # Simulated history for velocity checks | |
| transaction_history = [] | |
| def generate_transaction(): | |
| user = random.choice(USERS) | |
| amount = round(random.uniform(10, 20000), 2) | |
| location = random.choice(LOCATIONS) | |
| device = random.choice(DEVICES) | |
| timestamp = datetime.datetime.now().isoformat() | |
| tx = { | |
| "id": str(uuid.uuid4())[:8], | |
| "user_id": user, | |
| "amount": amount, | |
| "currency": "CNY", | |
| "location": location["city"], | |
| "lat": location["lat"], | |
| "lon": location["lon"], | |
| "device": device, | |
| "ip": f"{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}", | |
| "timestamp": timestamp, | |
| "status": "pending" | |
| } | |
| return tx | |
| # --- Logic --- | |
| def analyze_risk(tx): | |
| score = 0 | |
| reasons = [] | |
| # Ensure amount is float | |
| try: | |
| amount = float(tx.get("amount", 0)) | |
| except: | |
| amount = 0 | |
| location = tx.get("location", "") | |
| device = tx.get("device", "") | |
| # Rule 1: High Amount | |
| if amount > 10000: | |
| score += 40 | |
| reasons.append("大额交易 (>10000 CNY)") | |
| elif amount > 5000: | |
| score += 20 | |
| reasons.append("中额交易 (>5000 CNY)") | |
| # Rule 2: Overseas Location | |
| if "Overseas" in location: | |
| score += 30 | |
| reasons.append("境外IP地址") | |
| # Rule 3: Device Anomaly (Simulated) | |
| if "Windows" in device and amount > 2000: | |
| score += 10 | |
| reasons.append("PC端大额支付风险") | |
| # Rule 4: Velocity (Simulated check against recent history) | |
| # In a real app, we'd check DB. Here we just random for demo if not enough history | |
| user_id = tx.get("user_id", "unknown") | |
| recent_tx_count = sum(1 for t in transaction_history if t.get("user_id") == user_id) | |
| if recent_tx_count > 3: | |
| score += 50 | |
| reasons.append("高频交易 (短时间多次操作)") | |
| # Decision | |
| if score >= 80: | |
| decision = "block" | |
| risk_level = "high" | |
| elif score >= 40: | |
| decision = "review" | |
| risk_level = "medium" | |
| else: | |
| decision = "approve" | |
| risk_level = "low" | |
| return { | |
| "score": score, | |
| "reasons": reasons, | |
| "decision": decision, | |
| "risk_level": risk_level | |
| } | |
| def get_relation_graph(user_id): | |
| # Simulate a knowledge graph for fraud rings | |
| nodes = [] | |
| links = [] | |
| # Center node | |
| nodes.append({"id": user_id, "name": user_id, "category": 0, "symbolSize": 30}) | |
| # Related Devices (Category 1) | |
| device_id = f"dev_{str(uuid.uuid4())[:4]}" | |
| nodes.append({"id": device_id, "name": "常用设备", "category": 1, "symbolSize": 20}) | |
| links.append({"source": user_id, "target": device_id, "name": "uses"}) | |
| # Related IP (Category 2) | |
| ip_addr = f"192.168.1.{random.randint(10,99)}" | |
| nodes.append({"id": ip_addr, "name": ip_addr, "category": 2, "symbolSize": 20}) | |
| links.append({"source": user_id, "target": ip_addr, "name": "login_from"}) | |
| # Risk: If user is high risk, show connections to other fraud users | |
| if random.random() > 0.5: | |
| fraud_user = "user_9999 (黑名单)" | |
| nodes.append({"id": fraud_user, "name": fraud_user, "category": 0, "itemStyle": {"color": "red"}}) | |
| links.append({"source": device_id, "target": fraud_user, "name": "shared_device"}) | |
| return {"nodes": nodes, "links": links} | |
| # --- Routes --- | |
| def page_not_found(e): | |
| return render_template('index.html'), 404 | |
| def internal_server_error(e): | |
| return jsonify(error=str(e)), 500 | |
| def index(): | |
| return render_template('index.html') | |
| def upload_file(): | |
| if 'file' not in request.files: | |
| return jsonify({"error": "No file part"}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({"error": "No selected file"}), 400 | |
| if file: | |
| filename = secure_filename(file.filename) | |
| # Handle non-ascii filenames | |
| if not filename: | |
| filename = 'upload.csv' | |
| # Parse file | |
| try: | |
| if filename.endswith('.csv'): | |
| df = pd.read_csv(file) | |
| elif filename.endswith(('.xls', '.xlsx')): | |
| df = pd.read_excel(file) | |
| else: | |
| return jsonify({"error": "Unsupported file type"}), 400 | |
| # Process data | |
| results = [] | |
| for _, row in df.iterrows(): | |
| # Map columns if necessary or use defaults | |
| tx = { | |
| "id": str(row.get('id', str(uuid.uuid4())[:8])), | |
| "user_id": str(row.get('user_id', f"user_{random.randint(1000,9999)}")), | |
| "amount": float(row.get('amount', random.uniform(100, 5000))), | |
| "location": str(row.get('location', 'Unknown')), | |
| "device": str(row.get('device', 'Unknown Device')), | |
| "ip": str(row.get('ip', '127.0.0.1')), | |
| "timestamp": str(row.get('timestamp', datetime.datetime.now().isoformat())), | |
| } | |
| analysis = analyze_risk(tx) | |
| tx.update(analysis) | |
| results.append(tx) | |
| # Update global history for context | |
| global transaction_history | |
| transaction_history.extend(results) | |
| if len(transaction_history) > 200: | |
| transaction_history = transaction_history[-200:] | |
| return jsonify({"status": "success", "count": len(results), "data": results}) | |
| except Exception as e: | |
| return jsonify({"error": f"Process failed: {str(e)}"}), 500 | |
| def stream_transactions(): | |
| # Return a batch of random transactions | |
| batch = [generate_transaction() for _ in range(5)] | |
| # Store in history (limit size) | |
| global transaction_history | |
| transaction_history.extend(batch) | |
| if len(transaction_history) > 100: | |
| transaction_history = transaction_history[-100:] | |
| # Analyze them | |
| results = [] | |
| for tx in batch: | |
| analysis = analyze_risk(tx) | |
| tx.update(analysis) | |
| results.append(tx) | |
| return jsonify(results) | |
| def graph(user_id): | |
| data = get_relation_graph(user_id) | |
| return jsonify(data) | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860) | |