feat(seed): KRX live 실패 시 정적 주요종목 fallback (156개)

배경: 토요일/봇차단 등으로 KRX 가 LOGOUT/JSONDecodeError 를 주면
pykrx 가 빈 리스트만 돌려줘서 SEED 10 종목 외엔 검색이 안 되는
회귀가 있었다 (기아/네이버/엔비디아 같은 비-seed 검색 실패).

해결: 시총/거래대금 상위 + 친숙도 기준으로 KOSPI 104 / KOSDAQ 52
= 156 개를 정적 스냅샷 (app/seed/krx_static.py) 으로 묶었다.
seed_symbols() 가 KRX 라이브 fetch 결과가 0건이면 자동으로 이
스냅샷에서 upsert. KRX 가 정상 돌아오면 다음 호출이 라이브 데이터로
자연 덮어쓰여진다. 응답에 static_fallback_used 필드 노출.

수록 항목은 KRX 공시 종목코드 + 현재 등록명 (공개 사실).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-05-28 01:15:58 +09:00
parent b16f2b578d
commit 6ab3679331
3 changed files with 206 additions and 3 deletions

View File

@@ -11,6 +11,7 @@ from dataclasses import dataclass
from sqlalchemy import text
from app.db.connection import get_engine
from app.seed.krx_static import KRX_STATIC_MAJORS
from app.seed.seed_tickers import SEED_CODES, SEED_TICKERS
logger = logging.getLogger(__name__)
@@ -22,6 +23,7 @@ class SeedReport:
updated: int
seed_marked: int
markets: dict[str, int]
static_fallback_used: bool = False
def _fetch_market_listing(market: str) -> list[tuple[str, str]]:
@@ -99,6 +101,21 @@ def seed_symbols() -> SeedReport:
logger.error("seed_symbols: KRX %s fetch failed — skip market: %s", market, repr(e)[:300])
market_counts[market] = 0
# 2b) KRX 가 0건만 줬으면 정적 스냅샷 (krx_static.KRX_STATIC_MAJORS) 으로 fallback.
# KRX 가 LOGOUT/JSONDecode 로 막혀도 주요 종목 검색은 동작하게 만든다.
# KRX 가 정상으로 돌아오면 다음 호출에서 all_rows 가 채워져 자동으로 덮어쓰여진다.
static_fallback_used = False
if not all_rows:
logger.warning(
"seed_symbols: KRX live fetch empty — fallback to static snapshot (%d majors)",
len(KRX_STATIC_MAJORS),
)
all_rows = list(KRX_STATIC_MAJORS)
static_fallback_used = True
# market_counts 도 fallback 으로 채워 응답에 반영
for _c, _n, m in KRX_STATIC_MAJORS:
market_counts[m] = market_counts.get(m, 0) + 1
inserted = updated = 0
if all_rows:
engine = get_engine()
@@ -129,7 +146,13 @@ def seed_symbols() -> SeedReport:
logger.error("seed_symbols: KRX bulk upsert failed (transaction rolled back): %s", repr(e)[:300])
logger.info(
"seed_symbols done: inserted=%d updated=%d seed_marked=%d markets=%s",
inserted, updated, seed_marked, market_counts,
"seed_symbols done: inserted=%d updated=%d seed_marked=%d markets=%s static_fallback=%s",
inserted, updated, seed_marked, market_counts, static_fallback_used,
)
return SeedReport(
inserted=inserted,
updated=updated,
seed_marked=seed_marked,
markets=market_counts,
static_fallback_used=static_fallback_used,
)
return SeedReport(inserted=inserted, updated=updated, seed_marked=seed_marked, markets=market_counts)