[STK3-04] /recommendations API

작업 내용 (설계 의도)

변경 사항

  • GET /recommendations?limit=.
  • 후보 = watchlist(BE GET /api/v1/watchlist, timeout 5s) ∪ CURATED_SYMBOLS.
  • ThreadPoolExecutor(max_workers=5) 병렬 _safe_signal(실패 종목 제외).
  • BE 실패 시 큐레이션만으로 graceful. STK3-01·02 통합.

의존

  • 선행: STK3-01, STK3-02
  • 후행: STK3-06

롤백

  • 라우터 비활성.

다이어그램

처리 흐름

sequenceDiagram
    participant C as Client
    participant M as main.py
    participant BE as backend watchlist
    participant P as ThreadPoolExecutor(5)
    participant R as recommendation.py
    C->>M: GET /recommendations?limit
    M->>BE: GET /api/v1/watchlist (timeout 5s)
    alt BE 성공
        BE-->>M: symbols[]
    else BE 실패 / 타임아웃
        BE-->>M: [] (graceful)
    end
    Note over M: 후보 = watchlist ∪ CURATED_SYMBOLS
    M->>P: _safe_signal(symbol) ×N 병렬
    P-->>M: signals[] (실패 종목 제외)
    M->>R: rank_recommendations(signals, limit)
    M->>R: suggest_entry(each)
    M-->>C: recommendations[]

클래스 의존

flowchart LR
    subgraph Presentation["Presentation"]
        router["main.py\n/recommendations 라우터"]
    end
    subgraph Application["Application"]
        pool["ThreadPoolExecutor\n_safe_signal()"]
    end
    subgraph Domain["ML 도메인"]
        rec["recommendation.py\nrank + suggest_entry"]
        sig["signal.py"]
    end
    subgraph External["External"]
        BE["BE /api/v1/watchlist"]
    end
    router --> pool
    pool --> sig
    router --> rec
    router --> BE

테스트 케이스

  • BE watchlist 조회 실패(타임아웃) 시 큐레이션 종목만으로 추천 목록이 반환된다.
  • 일부 종목 _safe_signal 실패 시 해당 종목이 결과에서 제외되고 나머지가 정상 반환된다.
  • limit=5이면 최대 5개 종목만 반환된다.
  • 후보 전원이 점수 0.15 이하면 빈 리스트가 반환된다.
  • BE와 큐레이션 중복 종목이 있어도 한 번만 스코어링된다.