import asyncio import json from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse from .config import settings from .schemas import PortfolioRequest, PortfolioResponse from .market import DEFAULT_UNIVERSE from .service import build_portfolio_payload app = FastAPI(title=settings.app_name) app.add_middleware( CORSMiddleware, allow_origins=[settings.frontend_origin, "http://localhost:5173"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/health") def health(): return {"ok": True, "service": settings.app_name} @app.get("/api/universe") def universe(): return {"tickers": DEFAULT_UNIVERSE} @app.post("/api/portfolio", response_model=PortfolioResponse) def portfolio(req: PortfolioRequest): try: return build_portfolio_payload( tickers=req.tickers, lookback_days=req.lookback_days, risk_aversion=req.risk_aversion, max_weight=req.max_weight, sector_limit=req.sector_limit, beta_limit=req.beta_limit, ) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/stream") async def stream(tickers: str = Query(default="AAPL,MSFT,NVDA,AMZN"), lookback_days: int = Query(default=365), risk_aversion: float = Query(default=8.0), max_weight: float = Query(default=0.35), sector_limit: float = Query(default=0.70), beta_limit: float = Query(default=1.20)): ticker_list = [x.strip().upper() for x in tickers.split(",") if x.strip()] async def event_generator(): while True: try: payload = build_portfolio_payload( tickers=ticker_list, lookback_days=lookback_days, risk_aversion=risk_aversion, max_weight=max_weight, sector_limit=sector_limit, beta_limit=beta_limit, ) yield f"data: {json.dumps(payload)}\n\n" except Exception as e: yield f"data: {json.dumps({'error': str(e)})}\n\n" await asyncio.sleep(settings.stream_interval_seconds) return StreamingResponse(event_generator(), media_type="text/event-stream")