Coverage for core / retry_queue / retry_classifier.py: 100%
31 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
1# core/retry_queue/retry_classifier.py
2from enum import Enum
3from common.types import ResCommonResponse, ErrorCode
6class RequestOutcome(Enum):
7 DONE = "done" # 성공
8 RETRY = "retry" # 재시도 가능 실패
9 FAIL = "fail" # 최종 실패 (재시도 불가)
12# KIS API msg1에 포함될 경우 재시도가 무의미한 비즈니스 오류 키워드
13_NON_RETRIABLE_MSG_PATTERNS = [
14 "잔고부족", "주문가능금액", "거래정지", "주문불가", "상장폐지",
15 "매도가능수량", "이미처리", "접수불가", "매매불가능종목",
16 "종목코드 오류", "유효하지 않은", "입력값 오류",
17]
19# KIS API msg1에 포함될 경우 일시적 과부하로 재시도 가능한 키워드
20_RETRIABLE_MSG_PATTERNS = [
21 "초당 거래건수", "분당 거래건수", "잠시 후", "서버 과부하",
22 "too many", "rate limit", "요청이 많습니다",
23]
25# 재시도 불가 ErrorCode (비즈니스 오류, 입력 오류)
26_NON_RETRIABLE_CODES = frozenset({
27 ErrorCode.MARKET_CLOSED,
28 ErrorCode.INVALID_INPUT,
29 ErrorCode.MISSING_KEY,
30 ErrorCode.EMPTY_VALUES,
31 ErrorCode.WRONG_RET_TYPE,
32})
35def classify(result: ResCommonResponse | None) -> RequestOutcome:
36 """
37 API 응답을 분석하여 요청 결과를 분류합니다.
38 - DONE : 성공
39 - RETRY: 일시적 오류 (네트워크, 과부하 등) → 재시도 가능
40 - FAIL : 비즈니스 오류 (잔고부족, 주문불가 등) → 재시도 무의미
41 """
42 if result is None:
43 # 응답 자체가 없으면 네트워크 레벨 문제 → 재시도
44 return RequestOutcome.RETRY
46 if result.rt_cd == ErrorCode.SUCCESS.value:
47 return RequestOutcome.DONE
49 try:
50 code = ErrorCode(result.rt_cd)
51 except ValueError:
52 # 우리 ErrorCode 매핑에 없는 KIS 내부 코드 → msg1으로 판단
53 return _classify_by_msg(result.msg1)
55 if code in _NON_RETRIABLE_CODES:
56 return RequestOutcome.FAIL
58 if code.is_retriable: # NETWORK_ERROR, RETRY_LIMIT
59 return RequestOutcome.RETRY
61 # API_ERROR(100), PARSING_ERROR(101), UNKNOWN_ERROR(999) 등
62 # → msg1 키워드로 세분화
63 return _classify_by_msg(result.msg1)
66def _classify_by_msg(msg: str | None) -> RequestOutcome:
67 if not msg:
68 return RequestOutcome.RETRY # 메시지 없음 → 일단 재시도
70 if any(kw in msg for kw in _NON_RETRIABLE_MSG_PATTERNS):
71 return RequestOutcome.FAIL
73 if any(kw in msg for kw in _RETRIABLE_MSG_PATTERNS):
74 return RequestOutcome.RETRY
76 # 알 수 없는 오류 → 안전하게 FAIL (재시도해도 같은 결과일 가능성 높음)
77 return RequestOutcome.FAIL