9 Commits (964cf2237aa910de505197f52b0ee8a6f71dd831)

Author SHA1 Message Date
I Luk Kim 4e77043671 fix: 백그라운드 reindex 전역 세마포어(3) 추가 — 이벤트 루프 포화 방지
500+ 티커 bulk 스캔 시 ensure_future로 수백 개의 SEC EDGAR 태스크가
동시 생성되어 이벤트 루프 포화 → 서버 응답 불가.
_BACKGROUND_REINDEX_SEMAPHORE(3)으로 동시 실행을 최대 3개로 제한.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim be8dce201d fix: SEC filings 동시 insert 경쟁 조건 수정 (ON CONFLICT + 백그라운드 재인덱스)
동시 요청 시 둘 다 빈 existing_accs 읽고 중복 INSERT → UniqueViolationError 발생.
pg_insert().on_conflict_do_nothing()으로 원자적 upsert 교체,
staleness 재인덱스를 background task로 이동해 요청 차단 제거.
auto-parse는 최근 20개로 제한.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 25603c390c feat: 8-K filing parser — Item 추출 + event 생성
AVGO 8-K (accession 0001193125-26-144028, Item 8.01, Google TPU 공급계약)이
DB에 수집은 되었으나 파싱/event 생성이 불가했던 문제 해결.

구현:
- sec_8k_parser.py: 8-K primary document HTML 파싱
  - extract_items(): regex 기반 Item 헤더 추출, 목차 중복 제거 (last-wins)
  - _strip_ixbrl_viewer(): documents_json의 /ix?doc=... URL → 직접 URL 변환
  - _find_primary_doc_url(): primary_document_url 우선 사용 (iXBRL viewer 회피)
  - Item 8.01 단독 filing: exhibit(9.01) 없이 본문에서 직접 content 추출
  - Item 9.01 skip, 나머지는 ITEM_EVENT_MAP으로 event_type 분류
  - Exhibit enrichment: 2.02/7.01/8.01 + EX-99.1 있을 때 exhibit content 우선
- sec_filing_events 테이블 신설 (UniqueConstraint: accession_number + item_number)
- sec_filings 테이블에 parsed_status / items_json 컬럼 추가
- index_filings() 완료 후 신규 8-K auto-parse 트리거
- GET /filings/events/{ticker}: lazy parse + 조회
- POST /filings/events/parse/bulk: backfill용 일괄 파싱
- FilingSummary에 parsed_status / items 필드 포함
- alembic migration: g8a9b0c1d2e3
- 테스트 22개 추가 (extract_items, strip_ixbrl, find_primary_doc, parse_filing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim fb6a099e50 fix(sec): exhibit timeout 15초 → 25초 (2단계 fetch 고려)
index 페이지 + exhibit 두 단계를 15초에 커버하기엔 너무 빡빡함.
미캐시 시 index fetch 최대 8초 + exhibit fetch 최대 12초 = 최악 20초.
max_bytes 수정으로 메모리 고갈은 이미 해결되었으므로
deadline은 25초로 조정 (원래 30초보다 5초 단축).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim e2c242c7cc fix(sec): 대형 exhibit 파일로 인한 서버 메모리 고갈 방지
- fetch_text에 max_bytes 파라미터 추가: Content-Length 헤더로 다운로드 전 사전 reject,
  헤더 없으면 content.read(max_bytes+1)로 제한적 읽기
- ValueError는 즉시 raise (retry 없음 — 크기는 재시도해도 안 줄어듦)
- in-memory 캐시(_text_cache) 1MB 가드: 대형 응답은 디스크 캐시에만 저장
- MAX_EXHIBIT_SIZE 1MB → 5MB, fetch_text(max_bytes=...) 호출로 다운로드 전 체크
- exhibit deadline 30초 → 15초 (서비스 + 엔드포인트 + bulk)
- 신규 테스트 5개: Content-Length 사전 거부, body 제한 읽기, 메모리 캐시 가드,
  소형 캐시 유지, ValueError no-retry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim f17644e5e8 fix(sec): N+1 쿼리 제거, 청크 커밋, 세션 분리, rate limiter 추가
- sec_filings_service: 루프 전 accession_number 일괄 pre-fetch로 N+1 제거
- sec_filings_service: 500건 단위 청크 커밋으로 all-or-nothing 트랜잭션 방지
- sec_filings_service: bulk 인덱싱 시 코루틴별 독립 세션 생성으로 동시 세션 충돌 해결
- sec_filings_service: index_filings 데드라인 60s → 120s, bulk timeout 동일 적용
- sec_http_client: _TokenBucket(10 req/sec) 추가로 SEC EDGAR 과부하 방지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 45d832ba5c perf: Phase 1-3 Stock Oracle API 성능 개선
Phase 1A - yfinance hang 제거
- _run_with_timeout() 헬퍼 추가 (asyncio.wait_for 래퍼)
- run_in_executor 6곳에 timeout 적용: history(30s), .info(20s), bulk download(60s)

Phase 1B - SEC Filing deadline 설정
- index_filings: 60s deadline + try/finally
- get_filing_documents: 30s deadline (중첩 호출 시 기존 deadline 유지)
- get_exhibit_content: 30s deadline + try/finally

Phase 1C - _get_ticker_max_range async 전환
- sync → async def + run_in_executor + wait_for(20s)
- get_or_create_company_data에서 period=="max" 사전 체크 → await 직접 호출

Phase 1D - Endpoint 레벨 timeout
- POST /price/data/bulk: 300s → 504
- POST /financial/data/bulk: 300s → 504
- GET /filings/search/{ticker}: 120s → 504
- GET /filings/documents/{accession}: 30s → 504
- GET /filings/exhibit/{accession}: 30s → 504

Phase 2 - Filing 캐시 추가
- GET /filings/documents: @with_cache(ttl=86400)
- GET /filings/exhibit: @with_cache(ttl=86400)

Phase 3A - POST /filings/search/bulk 추가
- BulkFilingSearchRequest/Item/Response 스키마
- search_filings_bulk(): 배치 DB 조회 → 미인덱싱 ticker 병렬 인덱싱(Semaphore 4)
- @with_cache(ttl=3600), 600s endpoint timeout

Phase 3B - POST /filings/exhibit/bulk 추가
- BulkExhibitRequest/Item/Response 스키마
- asyncio.gather + 개별 30s timeout, 최대 50건

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 76e154eccd feat(filings): add accepted_at field to filing search response
Expose SEC ACCEPTANCE-DATETIME so downstream consumers (filing_poller)
can populate Document.accepted_at_utc and Event.filed_at_utc.

- SECFiling model: add nullable accepted_at TIMESTAMP column
- main.py startup: ALTER TABLE migration for existing SQLite DBs
- sec_filings_service: extract acceptanceDateTime from SEC JSON, store on
  INSERT and force_refresh UPDATE
- FilingSummary schema: add Optional[str] accepted_at field
- filings endpoint: map accepted_at as ISO 8601 string in response
- CHANGELOG: v3.0.1 entry

Existing rows have accepted_at=NULL; backfill with force_refresh=true.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim d0f1a9a7d0 Add SEC filings indexing, search, and exhibit extraction (8-K, 6-K, 20-F, 40-F)
Extract shared SECHttpClient from ETF fetcher (retry, backoff, cache, throttle)
and apply it to both ETF and core SEC services, fixing missing rate limiting.
Add SECFiling DB model, Pydantic schemas, SECFilingsService with auto-indexing,
and REST endpoints at /filings/search, /filings/documents, /filings/exhibit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago