You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

71 lines
2.6 KiB
Python

"""
Earnings Surprise endpoints — SEC EDGAR XBRL EPS data
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import Response
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.schemas.earnings import EarningsSurpriseEntry, EarningsSurpriseResponse
from app.services.earnings_service import EarningsService
from app.utils.cache import with_cache
router = APIRouter()
logger = logging.getLogger("app.api.v1.earnings")
@router.get(
"/surprise/{symbol}",
response_model=EarningsSurpriseResponse,
summary="Get earnings surprise history",
description=(
"Quarterly EPS surprise data from SEC EDGAR XBRL.\n\n"
"**데이터 소스**: SEC EDGAR companyfacts API (무료, API 키 불필요).\n"
"**커버리지**: 2009년~ (대부분 종목). 첫 조회 시 자동 인덱싱.\n\n"
"**surprise**: 현재 분기 EPS - 이전 분기 EPS (QoQ 변화).\n"
"**estimated_eps**: 이전 분기 EPS (비교 기준).\n"
"**streak**: 연속 QoQ 증가 (양수) 또는 감소 (음수) 횟수."
),
)
@with_cache(namespace="earnings:surprise", ttl=None, key_params=["symbol", "quarters"])
async def get_earnings_surprise(
symbol: str,
response: Response,
quarters: int = Query(8, ge=1, le=80, description="Number of recent quarters (max 80 = ~20 years)"),
force_refresh: bool = Query(False, description="Bypass cache and re-fetch from SEC EDGAR"),
db: AsyncSession = Depends(get_db),
):
svc = EarningsService()
if force_refresh:
try:
await svc.index_earnings(db, symbol, force_refresh=True)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
try:
rows, stats = await svc.get_earnings_surprise(db, ticker=symbol, quarters=quarters)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Earnings surprise error for {symbol}: {e}")
raise HTTPException(status_code=502, detail=f"Failed to fetch earnings data: {e}")
entries = [EarningsSurpriseEntry.from_orm_obj(r) for r in rows]
return EarningsSurpriseResponse(
symbol=symbol.upper(),
quarters=entries,
streak=stats["streak"],
avg_surprise_pct=stats["avg_surprise_pct"],
metadata={
"data_source": "SEC_XBRL",
"quarters_requested": quarters,
"quarters_returned": len(entries),
"note": "surprise = current_eps - previous_quarter_eps (QoQ change)",
},
)