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

1# core/retry_queue/retry_classifier.py 

2from enum import Enum 

3from common.types import ResCommonResponse, ErrorCode 

4 

5 

6class RequestOutcome(Enum): 

7 DONE = "done" # 성공 

8 RETRY = "retry" # 재시도 가능 실패 

9 FAIL = "fail" # 최종 실패 (재시도 불가) 

10 

11 

12# KIS API msg1에 포함될 경우 재시도가 무의미한 비즈니스 오류 키워드 

13_NON_RETRIABLE_MSG_PATTERNS = [ 

14 "잔고부족", "주문가능금액", "거래정지", "주문불가", "상장폐지", 

15 "매도가능수량", "이미처리", "접수불가", "매매불가능종목", 

16 "종목코드 오류", "유효하지 않은", "입력값 오류", 

17] 

18 

19# KIS API msg1에 포함될 경우 일시적 과부하로 재시도 가능한 키워드 

20_RETRIABLE_MSG_PATTERNS = [ 

21 "초당 거래건수", "분당 거래건수", "잠시 후", "서버 과부하", 

22 "too many", "rate limit", "요청이 많습니다", 

23] 

24 

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}) 

33 

34 

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 

45 

46 if result.rt_cd == ErrorCode.SUCCESS.value: 

47 return RequestOutcome.DONE 

48 

49 try: 

50 code = ErrorCode(result.rt_cd) 

51 except ValueError: 

52 # 우리 ErrorCode 매핑에 없는 KIS 내부 코드 → msg1으로 판단 

53 return _classify_by_msg(result.msg1) 

54 

55 if code in _NON_RETRIABLE_CODES: 

56 return RequestOutcome.FAIL 

57 

58 if code.is_retriable: # NETWORK_ERROR, RETRY_LIMIT 

59 return RequestOutcome.RETRY 

60 

61 # API_ERROR(100), PARSING_ERROR(101), UNKNOWN_ERROR(999) 등 

62 # → msg1 키워드로 세분화 

63 return _classify_by_msg(result.msg1) 

64 

65 

66def _classify_by_msg(msg: str | None) -> RequestOutcome: 

67 if not msg: 

68 return RequestOutcome.RETRY # 메시지 없음 → 일단 재시도 

69 

70 if any(kw in msg for kw in _NON_RETRIABLE_MSG_PATTERNS): 

71 return RequestOutcome.FAIL 

72 

73 if any(kw in msg for kw in _RETRIABLE_MSG_PATTERNS): 

74 return RequestOutcome.RETRY 

75 

76 # 알 수 없는 오류 → 안전하게 FAIL (재시도해도 같은 결과일 가능성 높음) 

77 return RequestOutcome.FAIL