- Move FRED credential registration to FastAPI lifespan (was fragile import-order-dependent side-effect) - Add noqa E402 annotations for imports after curl_cffi patch - Fix all return type hints: bare dict -> dict[str, Any] - Move yfinance import to module level (was inline in functions) - Fix datetime.now() -> datetime.now(tz=timezone.utc) in openbb_service - Add try/except error handling to Group B service functions - Fix dict mutation in relative_rotation (immutable pattern) - Extract _classify_rrg_quadrant helper function - Fix type builtin shadow in routes_economy (type -> gdp_type) - Fix falsy int guard (if year: -> if year is not None:) - Remove user input echo from error messages
127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
"""Routes for expanded economy data."""
|
|
|
|
from fastapi import APIRouter, Query
|
|
|
|
from models import ApiResponse
|
|
from route_utils import safe
|
|
import economy_service
|
|
|
|
router = APIRouter(prefix="/api/v1")
|
|
|
|
|
|
# --- Structured macro indicators (Group D) ---
|
|
|
|
|
|
@router.get("/macro/cpi", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_cpi(country: str = Query(default="united_states", max_length=50, pattern=r"^[a-z_]+$")):
|
|
"""Consumer Price Index (multi-country)."""
|
|
data = await economy_service.get_cpi(country=country)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/macro/gdp", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_gdp(
|
|
gdp_type: str = Query(default="real", pattern="^(nominal|real|forecast)$"),
|
|
):
|
|
"""GDP: nominal, real, or forecast."""
|
|
data = await economy_service.get_gdp(gdp_type=gdp_type)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/macro/unemployment", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_unemployment(
|
|
country: str = Query(default="united_states", max_length=50, pattern=r"^[a-z_]+$"),
|
|
):
|
|
"""Unemployment rate (multi-country, with demographic breakdowns)."""
|
|
data = await economy_service.get_unemployment(country=country)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/macro/pce", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_pce():
|
|
"""Personal Consumption Expenditures (Fed preferred inflation measure)."""
|
|
data = await economy_service.get_pce()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/macro/money-measures", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_money_measures():
|
|
"""M1/M2 money supply, currency in circulation."""
|
|
data = await economy_service.get_money_measures()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/macro/cli", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_cli(country: str = Query(default="united_states", max_length=50, pattern=r"^[a-z_]+$")):
|
|
"""Composite Leading Indicator (predicts recessions 6-9 months ahead)."""
|
|
data = await economy_service.get_composite_leading_indicator(country=country)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/macro/house-price-index", response_model=ApiResponse)
|
|
@safe
|
|
async def macro_hpi(country: str = Query(default="united_states", max_length=50, pattern=r"^[a-z_]+$")):
|
|
"""Housing price index (multi-country)."""
|
|
data = await economy_service.get_house_price_index(country=country)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
# --- Economy data endpoints ---
|
|
|
|
|
|
@router.get("/economy/fred-regional", response_model=ApiResponse)
|
|
@safe
|
|
async def economy_fred_regional(
|
|
series_id: str = Query(..., min_length=1, max_length=30),
|
|
region: str = Query(default=None, max_length=20, pattern=r"^[a-z_]+$"),
|
|
):
|
|
"""Regional FRED data by state, county, or MSA."""
|
|
data = await economy_service.get_fred_regional(series_id=series_id, region=region)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/economy/primary-dealer-positioning", response_model=ApiResponse)
|
|
@safe
|
|
async def economy_primary_dealer():
|
|
"""Primary dealer net positions: treasuries, MBS, corporate bonds."""
|
|
data = await economy_service.get_primary_dealer_positioning()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/economy/fred-search", response_model=ApiResponse)
|
|
@safe
|
|
async def economy_fred_search(query: str = Query(..., min_length=1, max_length=100)):
|
|
"""Search FRED series by keyword (800K+ economic series)."""
|
|
data = await economy_service.fred_search(query=query)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/economy/balance-of-payments", response_model=ApiResponse)
|
|
@safe
|
|
async def economy_bop():
|
|
"""Balance of payments: current/capital/financial account."""
|
|
data = await economy_service.get_balance_of_payments()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/economy/central-bank-holdings", response_model=ApiResponse)
|
|
@safe
|
|
async def economy_fed_holdings():
|
|
"""Fed SOMA portfolio: holdings by security type."""
|
|
data = await economy_service.get_central_bank_holdings()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/economy/fomc-documents", response_model=ApiResponse)
|
|
@safe
|
|
async def economy_fomc(year: int = Query(default=None, ge=2000, le=2099)):
|
|
"""FOMC meeting documents: minutes, projections, press conferences."""
|
|
data = await economy_service.get_fomc_documents(year=year)
|
|
return ApiResponse(data=data)
|