@ -27,6 +27,7 @@ class SECFilingsService:
" 8-K " , " 6-K " , " 20-F " , " 40-F " , " 10-K " , " 10-Q " ,
" 8-K/A " , " 6-K/A " , " 20-F/A " , " 40-F/A " ,
}
CHUNK_SIZE = 500
def __init__ ( self ) :
self . _http = SECHttpClient ( " Stock Oracle Filings Service " )
@ -47,7 +48,7 @@ class SECFilingsService:
Returns the number of filings indexed ( inserted or updated ) .
"""
ticker = ticker . upper ( )
self . _http . set_deadline ( 6 0.0)
self . _http . set_deadline ( 12 0.0)
try :
cik = await self . _http . get_company_cik ( ticker )
if not cik :
@ -126,29 +127,48 @@ class SECFilingsService:
if not raw_filings :
return 0
# Upsert into DB
indexed_count = 0
# Pre-fetch existing accession numbers in one query (eliminates N+1)
now = datetime . now ( timezone . utc )
for rf in raw_filings :
acc = rf [ " accession_number " ]
result = await db . execute (
select ( SECFiling ) . where ( SECFiling . accession_number == acc )
indexed_count = 0
if force_refresh :
existing_result = await db . execute (
select ( SECFiling ) . where ( SECFiling . ticker == ticker )
)
existing = result . scalar_one_or_none ( )
if existing :
if force_refresh :
existing . ticker = rf [ " ticker " ]
existing . cik = rf [ " cik " ]
existing . form_type = rf [ " form_type " ]
existing . filing_date = rf [ " filing_date " ]
existing . accepted_at = rf [ " accepted_at " ]
existing . primary_document = rf [ " primary_document " ]
existing . primary_document_url = rf [ " primary_document_url " ]
existing . filing_description = rf [ " filing_description " ]
existing . indexed_at = now
existing . updated_at = now
indexed_count + = 1
else :
existing_map : Dict [ str , SECFiling ] = {
f . accession_number : f for f in existing_result . scalars ( ) . all ( )
}
existing_accs : Set [ str ] = set ( existing_map . keys ( ) )
else :
acc_result = await db . execute (
select ( SECFiling . accession_number ) . where ( SECFiling . ticker == ticker )
)
existing_accs = { row [ 0 ] for row in acc_result . fetchall ( ) }
existing_map = { }
# Upsert in chunks to avoid all-or-nothing transaction failures
for chunk_start in range ( 0 , len ( raw_filings ) , self . CHUNK_SIZE ) :
chunk = raw_filings [ chunk_start : chunk_start + self . CHUNK_SIZE ]
chunk_count = 0
for rf in chunk :
acc = rf [ " accession_number " ]
if acc in existing_accs :
if force_refresh :
existing = existing_map [ acc ]
existing . ticker = rf [ " ticker " ]
existing . cik = rf [ " cik " ]
existing . form_type = rf [ " form_type " ]
existing . filing_date = rf [ " filing_date " ]
existing . accepted_at = rf [ " accepted_at " ]
existing . primary_document = rf [ " primary_document " ]
existing . primary_document_url = rf [ " primary_document_url " ]
existing . filing_description = rf [ " filing_description " ]
existing . indexed_at = now
existing . updated_at = now
chunk_count + = 1
continue
filing = SECFiling (
ticker = rf [ " ticker " ] ,
cik = rf [ " cik " ] ,
@ -164,10 +184,19 @@ class SECFilingsService:
updated_at = now ,
)
db . add ( filing )
indexed_count + = 1
existing_accs . add ( acc ) # prevent duplicates within same run
chunk_count + = 1
if chunk_count :
try :
await db . commit ( )
indexed_count + = chunk_count
except Exception as e :
logger . error (
f " Chunk commit failed at offset { chunk_start } for { ticker } : { e } "
)
await db . rollback ( )
if indexed_count :
await db . commit ( )
logger . info ( f " Indexed { indexed_count } filings for { ticker } " )
return indexed_count
finally :
@ -409,16 +438,19 @@ class SECFilingsService:
missing = [ t for t in tickers if indexed_counts . get ( t , 0 ) == 0 ]
# Index missing tickers in parallel (Semaphore(4) to not overwhelm SEC)
# Each coroutine uses its own session to avoid concurrent-session conflicts
if missing :
sem = asyncio . Semaphore ( 4 )
async def _index_one ( ticker : str ) - > None :
async with sem :
try :
await asyncio . wait_for (
self . index_filings ( db , ticker , form_types = form_types ) ,
timeout = 60 ,
)
from app . core . database import AsyncSessionLocal
async with AsyncSessionLocal ( ) as session :
await asyncio . wait_for (
self . index_filings ( session , ticker , form_types = form_types ) ,
timeout = 120 ,
)
except Exception as e :
logger . warning ( f " Bulk index failed for { ticker } : { e } " )