ARF-Sandbox-API / app.py
petter2025's picture
Update app.py
b41928d verified
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel
from typing import Optional
import random
import time
from collections import defaultdict
app = FastAPI(
title="ARF Sandbox API",
description="Mock endpoint – does NOT use the real Bayesian engine. Simulated responses only.",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
)
# ---------- Rate Limiting Configuration ----------
RATE_LIMIT = 10 # max requests per time window
RATE_LIMIT_WINDOW = 60 # seconds
# In-memory store: {ip: [(timestamp,), ...]} or use a deque
request_log = defaultdict(list)
def rate_limit_middleware(ip: str):
now = time.time()
# Remove timestamps older than the window
request_log[ip] = [ts for ts in request_log[ip] if now - ts < RATE_LIMIT_WINDOW]
if len(request_log[ip]) >= RATE_LIMIT:
raise HTTPException(
status_code=429,
detail=f"Rate limit exceeded. Max {RATE_LIMIT} requests per {RATE_LIMIT_WINDOW} seconds."
)
request_log[ip].append(now)
# ---------- Request/Response Models ----------
class Metrics(BaseModel):
latency_ms: Optional[float] = None
error_rate: Optional[float] = None
throughput: Optional[float] = None
cpu_usage: Optional[float] = None
class EvaluateRequest(BaseModel):
service_name: str
event_type: str # e.g., "latency", "error_rate", "cpu_spike"
severity: str # "low", "medium", "high", "critical"
metrics: Optional[Metrics] = None
timestamp: Optional[float] = None
class EvaluateResponse(BaseModel):
status: str
recommendation: str # "APPROVE", "DENY", "ESCALATE"
risk_score: float
confidence: float
justification: str
policy_violations: list
# ---------- Mock Logic ----------
def generate_mock_response(request: EvaluateRequest) -> EvaluateResponse:
# Deterministic randomness based on service name and event type
seed = hash((request.service_name, request.event_type, request.severity)) % 1000
random.seed(seed)
# Simulate risk score based on severity
severity_map = {"low": 0.2, "medium": 0.4, "high": 0.7, "critical": 0.9}
base_risk = severity_map.get(request.severity, 0.5)
risk = min(0.99, max(0.01, base_risk + random.uniform(-0.1, 0.1)))
# Decision logic (mock)
if risk < 0.3:
rec = "APPROVE"
elif risk > 0.8:
rec = "DENY"
else:
rec = "ESCALATE"
confidence = 1.0 - (risk * 0.3) + random.uniform(-0.05, 0.05)
confidence = min(0.99, max(0.5, confidence))
justification = (
f"Simulated evaluation for {request.service_name}: {request.event_type} severity={request.severity}. "
f"Risk score {risk:.2f}{rec}. (Mock response, not real inference.)"
)
return EvaluateResponse(
status="success",
recommendation=rec,
risk_score=round(risk, 4),
confidence=round(confidence, 4),
justification=justification,
policy_violations=[]
)
# ---------- Endpoints ----------
@app.get("/health", tags=["health"])
async def health():
return {"status": "ok", "timestamp": time.time()}
@app.post("/v1/evaluate", response_model=EvaluateResponse, tags=["evaluation"])
async def evaluate(request: EvaluateRequest, req: Request):
# Apply rate limit based on client IP
client_ip = req.client.host if req.client else "unknown"
rate_limit_middleware(client_ip)
if not request.service_name or not request.event_type:
raise HTTPException(status_code=400, detail="Missing service_name or event_type")
return generate_mock_response(request)
@app.get("/", include_in_schema=False)
async def root():
return {"message": "ARF Sandbox API. See /docs for interactive documentation. Rate limit: 10 requests per minute per IP."}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)