[ML-05] 챗봇 답변에 캐시된 4팩터 시그널 사용

작업 내용 (설계 의도)

문제 (AS-IS)

유즈케이스 캡쳐에서 챗봇에 “삼성전자 어때?”를 물으면 수급·재무가 “없음” 으로 답합니다. 같은 종목의 관심종목 카드는 4팩터를 모두 표시하는데도 그렇습니다.

원인은 두 경로가 서로 다른 데이터를 본다는 것입니다.

경로데이터 출처팩터 수
관심종목 카드aggregator SignalDomainService#getSignal(live=false) → Redis(L1)/MySQL 스냅샷(L2)4 (감성·모멘텀·수급·재무)
챗봇ml main.py#_chat_signal_block_compute_signal_fresh(symbol) (즉석 계산)2 (감성·모멘텀)

_compute_signal_fresh는 조회 시점에 KRX 수급·OpenDART 재무를 수집하지 않습니다(ADR-003). 수급·재무는 worker가 비동기로 계산해 스냅샷에 적재하는 값이라, 즉석 계산 경로에서는 구조적으로 채워질 수 없습니다.

결과적으로 챗봇 답변의 composite·현재가가 화면 카드와 다른 값이 됩니다. 같은 종목을 두 곳에서 다르게 말하는 상태입니다.

변경 (TO-BE)

챗봇이 화면 카드와 같은 데이터를 근거로 답하게 합니다.

  • ml이 backend 시그널 스냅샷(GET /signal-snapshots/{symbol})을 먼저 조회합니다. 스냅샷 본문은 ml 형식(snake_case)이라 그대로 읽습니다.
  • 스냅샷이 있으면 그 4팩터·composite·현재가·뉴스 요약을 프롬프트 사실 블록으로 주입합니다. 기준 시각(refreshedAt)을 함께 넣어 답변이 “언제 기준 값인지” 밝히게 합니다.
  • 스냅샷이 없으면(미적재 종목) 기존 즉석 계산(2팩터)으로 폴백합니다 — 답변 자체가 막히지 않게 합니다.
  • 조회 실패·타임아웃은 None으로 흡수해 폴백합니다 (news_snapshot_client와 동일한 graceful 규약).

부수 효과로 스냅샷 히트 시 챗봇 경로에서 뉴스 수집·캔들 조회·감성 분석이 사라집니다 — 응답이 빨라지고 LLM 호출이 답변 1회로 줄어듭니다.

근거 문서

  • 프로젝트/주식앱/유즈케이스 캡쳐/주식앱-갤러리.md “캡쳐 중 확인된 사항 — 수급·재무 팩터 결측”
  • ADR-003 (조회 시점 KRX/재무 미수집), STK-ARCH-08/09 (캐시 우선 서빙)

의존

  • 없음 (ML-01~04 머지 완료 상태의 main 기준)

다이어그램

처리 흐름

sequenceDiagram
    participant User
    participant ML as ml /chat/message
    participant BE as backend
    participant LLM as claude opus
    User->>ML: 삼성전자 어때?
    ML->>BE: GET /api/v1/stocks/search (종목 해석)
    BE-->>ML: 005930
    ML->>BE: GET /signal-snapshots/005930
    alt 스냅샷 있음
        BE-->>ML: 4팩터 + refreshedAt
        ML->>LLM: 가이던스 + 시그널 사실 블록(4팩터·기준 시각)
    else 스냅샷 없음
        BE-->>ML: 404
        ML->>ML: _compute_signal_fresh (2팩터)
        ML->>LLM: 가이던스 + 시그널 사실 블록(2팩터)
    end
    LLM-->>ML: 답변
    ML-->>User: SSE message

클래스 의존

flowchart LR
    Main[main._chat_signal_block] --> Client[signal_snapshot_client.fetch_signal_snapshot]
    Main --> Fresh[_compute_signal_fresh]
    Client --> Backend[backend /signal-snapshots]
    Main --> Prompt[_build_chat_prompt]

테스트 케이스

  • 스냅샷이 있으면 프롬프트에 수급·재무 점수가 숫자로 들어간다
  • 스냅샷이 있으면 프롬프트에 기준 시각이 포함된다
  • 스냅샷이 있으면 즉석 계산(_compute_signal_fresh)을 호출하지 않는다
  • 스냅샷이 없으면(404) 즉석 계산으로 폴백해 2팩터 블록을 만든다
  • 스냅샷 조회가 예외를 던져도 폴백해 답변이 막히지 않는다
  • 스냅샷의 결측 팩터(null)는 “없음”으로 표기한다
  • 스냅샷·즉석 계산 모두 실패하면 시그널 없이 질문만으로 진행한다