""" Insider Transaction endpoints — SEC Form 4 data """ import logging from typing import Optional 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.insider import ( InsiderTransactionEntry, InsiderTransactionResponse, InsiderSummaryPeriod, InsiderSummaryResponse, ) from app.services.insider_transaction_service import InsiderTransactionService from app.utils.cache import with_cache router = APIRouter() logger = logging.getLogger("app.api.v1.insider") @router.get( "/transactions/{symbol}", response_model=InsiderTransactionResponse, summary="Get insider transactions for a symbol", description=( "Query SEC Form 4 insider trading data. Auto-fetches from SEC EDGAR if data is missing.\n\n" "**데이터 소스**: SEC EDGAR (무료, API 키 불필요). 첫 조회 시 자동 인덱싱.\n\n" "**Transaction codes**: P=Purchase, S=Sale, A=Award, M=Exercise, G=Gift, F=Tax Withholding" ), ) @with_cache(namespace="insider:transactions", ttl=None, key_params=["symbol", "days", "transaction_type", "insider_title", "limit"]) async def get_insider_transactions( symbol: str, response: Response, days: int = Query(90, ge=1, le=3650, description="Days to look back (max ~10 years)"), transaction_type: Optional[str] = Query(None, description="Filter: P=Purchase, S=Sale, A=Award, M=Exercise"), insider_title: Optional[str] = Query(None, description="Filter by title keyword (e.g., CEO, CFO, Director)"), limit: int = Query(50, ge=1, le=500, description="Max entries to return"), force_refresh: bool = Query(False, description="Bypass cache and re-fetch from SEC"), db: AsyncSession = Depends(get_db), ): svc = InsiderTransactionService() try: rows, total = await svc.get_transactions( db, ticker=symbol, days=days, transaction_type=transaction_type, insider_title=insider_title, limit=limit, ) entries = [InsiderTransactionEntry.from_orm_obj(r) for r in rows] return InsiderTransactionResponse( symbol=symbol.upper(), transactions=entries, total_count=total, metadata={ "days_requested": days, "transaction_type_filter": transaction_type, "insider_title_filter": insider_title, }, ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: logger.error(f"Insider transactions error for {symbol}: {e}") raise HTTPException(status_code=502, detail=f"Failed to fetch insider data: {e}") @router.get( "/summary/{symbol}", response_model=InsiderSummaryResponse, summary="Get insider trading summary", description=( "Aggregated insider buy/sell activity for 3, 6, and 12 month periods.\n\n" "Includes net buy/sell shares and values, plus top 5 notable transactions by value." ), ) @with_cache(namespace="insider:summary", ttl=None, key_params=["symbol"]) async def get_insider_summary( symbol: str, response: Response, force_refresh: bool = Query(False, description="Bypass cache"), db: AsyncSession = Depends(get_db), ): svc = InsiderTransactionService() try: result = await svc.get_summary(db, ticker=symbol) periods = [InsiderSummaryPeriod(**p) for p in result["periods"]] notable = [InsiderTransactionEntry.from_orm_obj(r) for r in result["notable"]] return InsiderSummaryResponse( symbol=symbol.upper(), periods=periods, notable_transactions=notable, metadata={"data_source": "SEC EDGAR Form 4"}, ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: logger.error(f"Insider summary error for {symbol}: {e}") raise HTTPException(status_code=502, detail=f"Failed to fetch insider summary: {e}")