import requests from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel from typing import List, Dict, Optional, Any from data_ingestion.api_loader import get_daily_adjusted_prices, DataIngestionError import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) app = FastAPI(title="API Agent") class MarketDataRequest(BaseModel): tickers: List[str] start_date: Optional[str] = None end_date: Optional[str] = None data_type: Optional[str] = "adjClose" @app.post("/get_market_data") def get_market_data(request: MarketDataRequest): """ Fetches daily adjusted market data by calling the data_ingestion layer (FMP). Returns adjusted close prices per ticker per date. """ result: Dict[str, Dict[str, float]] = {} errors: Dict[str, str] = {} warnings: Dict[str, str] = {} key = ( request.data_type if request.data_type in ["open", "high", "low", "close", "adjClose", "volume"] else "adjClose" ) for ticker in request.tickers: try: raw = get_daily_adjusted_prices(ticker) time_series: Dict[str, Any] = {} if isinstance(raw, dict): time_series = raw elif isinstance(raw, list): logger.warning( f"Loader returned list for {ticker}; filtering dict entries." ) for rec in raw: if isinstance(rec, dict) and "date" in rec: date_val = rec["date"] time_series[date_val] = rec else: logger.warning( f"Skipping non-dict or missing-date entry for {ticker}: {rec}" ) else: raise DataIngestionError( f"Unexpected format from loader for {ticker}: {type(raw)}" ) ticker_prices: Dict[str, float] = {} for date_str, values in time_series.items(): if not isinstance(values, dict): warnings.setdefault(ticker, "") warnings[ticker] += f" Non-dict for {date_str}; skipped." continue if key not in values: warnings.setdefault(ticker, "") warnings[ticker] += f" Missing '{key}' on {date_str}." continue try: ticker_prices[date_str] = float(values[key]) except (TypeError, ValueError): warnings.setdefault(ticker, "") warnings[ticker] += f" Invalid '{key}' value on {date_str}." if ticker_prices: result[ticker] = ticker_prices logger.info(f"Fetched {len(ticker_prices)} points for {ticker}.") else: warnings.setdefault(ticker, "") warnings[ticker] += " No valid data points found." except (requests.RequestException, DataIngestionError) as err: errors[ticker] = str(err) logger.error(f"Error fetching {ticker}: {err}") except Exception as err: errors[ticker] = f"Unexpected error for {ticker}: {err}" logger.error(errors[ticker]) if not result and errors: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to fetch market data for all tickers.", ) return {"result": result, "errors": errors, "warnings": warnings}