|
|
|
|
@ -37,6 +37,12 @@ from sqlalchemy import select, and_, func as sql_func
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
logger = logging.getLogger("app.api.v1.filings")
|
|
|
|
|
|
|
|
|
|
# Limit concurrent filing search requests to prevent event-loop saturation
|
|
|
|
|
# during bulk A-Z ticker scans. Requests beyond this wait up to
|
|
|
|
|
# _SEARCH_SEMAPHORE_TIMEOUT seconds, then fail fast with 429.
|
|
|
|
|
_SEARCH_SEMAPHORE = asyncio.Semaphore(8)
|
|
|
|
|
_SEARCH_SEMAPHORE_TIMEOUT = 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get(
|
|
|
|
|
"/search/{ticker}",
|
|
|
|
|
@ -89,37 +95,51 @@ async def search_filings(
|
|
|
|
|
except ValueError:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
|
|
|
|
|
|
|
|
|
|
if force_refresh:
|
|
|
|
|
# Fast-fail when server is overloaded (bulk A-Z scans)
|
|
|
|
|
try:
|
|
|
|
|
await asyncio.wait_for(
|
|
|
|
|
_SEARCH_SEMAPHORE.acquire(), timeout=_SEARCH_SEMAPHORE_TIMEOUT
|
|
|
|
|
)
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=429,
|
|
|
|
|
detail="서버가 바빠서 요청을 처리할 수 없습니다. 잠시 후 다시 시도하세요.",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if force_refresh:
|
|
|
|
|
try:
|
|
|
|
|
await sec_filings_service.index_filings(
|
|
|
|
|
db, ticker, form_types=form_types_set, force_refresh=True
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Force refresh indexing failed for {ticker}: {e}")
|
|
|
|
|
raise HTTPException(status_code=502, detail=f"SEC indexing failed: {e}")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await sec_filings_service.index_filings(
|
|
|
|
|
db, ticker, form_types=form_types_set, force_refresh=True
|
|
|
|
|
filings, total_count = await asyncio.wait_for(
|
|
|
|
|
sec_filings_service.search_filings(
|
|
|
|
|
db,
|
|
|
|
|
ticker,
|
|
|
|
|
form_types=form_types_set,
|
|
|
|
|
start_date=start_dt,
|
|
|
|
|
end_date=end_dt,
|
|
|
|
|
limit=limit,
|
|
|
|
|
offset=offset,
|
|
|
|
|
),
|
|
|
|
|
timeout=120,
|
|
|
|
|
)
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
raise HTTPException(status_code=504, detail="Filing search timed out after 120s.")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Force refresh indexing failed for {ticker}: {e}")
|
|
|
|
|
raise HTTPException(status_code=502, detail=f"SEC indexing failed: {e}")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
filings, total_count = await asyncio.wait_for(
|
|
|
|
|
sec_filings_service.search_filings(
|
|
|
|
|
db,
|
|
|
|
|
ticker,
|
|
|
|
|
form_types=form_types_set,
|
|
|
|
|
start_date=start_dt,
|
|
|
|
|
end_date=end_dt,
|
|
|
|
|
limit=limit,
|
|
|
|
|
offset=offset,
|
|
|
|
|
),
|
|
|
|
|
timeout=120,
|
|
|
|
|
)
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
raise HTTPException(status_code=504, detail="Filing search timed out after 120s.")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Filing search failed for {ticker}: {e}")
|
|
|
|
|
raise HTTPException(status_code=502, detail=f"Filing search failed: {e}")
|
|
|
|
|
logger.error(f"Filing search failed for {ticker}: {e}")
|
|
|
|
|
raise HTTPException(status_code=502, detail=f"Filing search failed: {e}")
|
|
|
|
|
finally:
|
|
|
|
|
_SEARCH_SEMAPHORE.release()
|
|
|
|
|
|
|
|
|
|
summaries = []
|
|
|
|
|
for f in filings:
|
|
|
|
|
|