"""Fundamentals router — fundamental data endpoints.""" import json from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, require_access from app.schemas.common import APIEnvelope from app.schemas.fundamental import FundamentalResponse from app.services.fundamental_service import get_fundamental from app.services.fundamentals_api_service import build_fundamentals_v1 from app.services import fundamentals_quality_service router = APIRouter(tags=["fundamentals"]) def _parse_unavailable_fields(raw_json: str) -> dict[str, str]: """Deserialize unavailable_fields_json, defaulting to {} on invalid JSON.""" try: parsed = json.loads(raw_json) except (json.JSONDecodeError, TypeError): return {} if not isinstance(parsed, dict): return {} return {k: v for k, v in parsed.items() if isinstance(k, str) and isinstance(v, str)} @router.get("/fundamentals/{symbol}", response_model=APIEnvelope) async def read_fundamentals( symbol: str, _user=Depends(require_access), db: AsyncSession = Depends(get_db), ) -> APIEnvelope: """Get latest fundamental data for a symbol (legacy fields + additive v1).""" record = await get_fundamental(db, symbol) v1 = await build_fundamentals_v1(db, symbol) quality = await fundamentals_quality_service.ticker_quality(db, symbol) legacy: dict = {} if record is not None: legacy = dict( pe_ratio=record.pe_ratio, revenue_growth=record.revenue_growth, earnings_surprise=record.earnings_surprise, market_cap=record.market_cap, next_earnings_date=record.next_earnings_date, fetched_at=record.fetched_at, unavailable_fields=_parse_unavailable_fields(record.unavailable_fields_json), ) data = FundamentalResponse( symbol=symbol.strip().upper(), setup_eligible=quality.eligible, setup_block_code=quality.code, setup_block_reason=quality.message, **legacy, **v1, ) return APIEnvelope(status="success", data=data.model_dump())