[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)는 “없음”으로 표기한다
- 스냅샷·즉석 계산 모두 실패하면 시그널 없이 질문만으로 진행한다