phase 14-15: docker build validation and helm deployment
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""Risk Engine API - FastAPI application for order risk evaluation and approval workflow."""
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import asyncpg
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from services.risk.approval import (
|
||||
expire_stale_approvals,
|
||||
get_approval_by_id,
|
||||
get_pending_approvals,
|
||||
review_approval,
|
||||
)
|
||||
from services.risk.engine import (
|
||||
AccountRiskState,
|
||||
PortfolioRiskConfig,
|
||||
ProposedOrder,
|
||||
RiskEvaluation,
|
||||
evaluate_order,
|
||||
)
|
||||
from services.shared.config import load_config
|
||||
from services.shared.logging import setup_logging
|
||||
|
||||
config = load_config()
|
||||
pool: asyncpg.Pool | None = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global pool
|
||||
setup_logging("risk_engine", level=config.log_level, json_output=config.json_logs)
|
||||
pool = await asyncpg.create_pool(dsn=config.postgres.dsn, min_size=2, max_size=8)
|
||||
yield
|
||||
if pool:
|
||||
await pool.close()
|
||||
|
||||
|
||||
app = FastAPI(title="Stonks Oracle - Risk Engine", lifespan=lifespan)
|
||||
|
||||
|
||||
class EvaluateRequest(BaseModel):
|
||||
order: ProposedOrder
|
||||
config: PortfolioRiskConfig | None = None
|
||||
state: AccountRiskState | None = None
|
||||
|
||||
|
||||
@app.post("/evaluate", response_model=RiskEvaluation)
|
||||
async def evaluate(req: EvaluateRequest) -> RiskEvaluation:
|
||||
risk_config = req.config or PortfolioRiskConfig()
|
||||
return evaluate_order(req.order, risk_config, req.state)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
class ReviewRequest(BaseModel):
|
||||
approved: bool
|
||||
reviewed_by: str = "operator"
|
||||
review_note: str = ""
|
||||
|
||||
|
||||
@app.get("/approvals/pending")
|
||||
async def list_pending():
|
||||
if not pool:
|
||||
raise HTTPException(503, "Database not ready")
|
||||
requests = await get_pending_approvals(pool)
|
||||
return [r.to_dict() for r in requests]
|
||||
|
||||
|
||||
@app.get("/approvals/{approval_id}")
|
||||
async def get_approval(approval_id: str):
|
||||
if not pool:
|
||||
raise HTTPException(503, "Database not ready")
|
||||
req = await get_approval_by_id(pool, approval_id)
|
||||
if not req:
|
||||
raise HTTPException(404, "Approval not found")
|
||||
return req.to_dict()
|
||||
|
||||
|
||||
@app.post("/approvals/{approval_id}/review")
|
||||
async def review(approval_id: str, body: ReviewRequest):
|
||||
if not pool:
|
||||
raise HTTPException(503, "Database not ready")
|
||||
status = await review_approval(
|
||||
pool, approval_id, body.approved, body.reviewed_by, body.review_note,
|
||||
)
|
||||
if status is None:
|
||||
raise HTTPException(404, "Approval not found or no longer pending")
|
||||
return {"approval_id": approval_id, "status": status.value}
|
||||
|
||||
|
||||
@app.post("/approvals/expire")
|
||||
async def expire():
|
||||
if not pool:
|
||||
raise HTTPException(503, "Database not ready")
|
||||
expired = await expire_stale_approvals(pool)
|
||||
return {"expired": expired}
|
||||
Reference in New Issue
Block a user