Files
web-page-backend/docs/superpowers/specs/2026-05-20-lotto-active-agent-design.md
gahusb 6c5e93f64e docs(spec): LottoAgent 능동성 확장 설계 (능동 시그널·일일 요약)
Why: 매주 1회 무조건 큐레이션만 있는 현 구조를 다중 트리거+적응형
시그널 모니터링으로 확장. 좋은 수치(z≥1.5) 일 때만 텔레그램 보고.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 02:07:39 +09:00

302 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LottoAgent 능동성 확장 설계
- **상태**: Draft (사용자 리뷰 대기)
- **작성일**: 2026-05-20
- **대상 컨테이너**: agent-office
- **영향 외부 도메인**: lotto-lab (read-only API 소비만)
---
## 1. 문제 정의
현재 LottoAgent는 매주 월요일 09:05 cron으로 무조건 큐레이션을 1회 실행하고 헤드라인을 텔레그램으로 푸시한다. "결과가 좋지 않은 회차"도 동일하게 발화되며, **정량적 시그널이 평소보다 강할 때 별도로 알리는 능동성**이 없다.
사용자 의도: 통계·시뮬레이션·전략 가중치를 에이전트가 스스로 모니터링하다가 "좋은 수치"가 나오면 능동적으로 보고하는 패턴.
## 2. 의사결정 요약
| 결정 사항 | 선택 | 비고 |
|---|---|---|
| 분석 주기 | 다중 트리거 혼합 | 매일 정기 + 시뮬레이션 후 + 회차 후 |
| 시그널 종류 | 3종 — Sim Consensus / Strategy Drift / Confidence | Hot/Cold 변화는 제외 (노이즈) |
| 알림 정책 | 일일 요약 + 긴급 즉시 | 2개 동시 발화 OR 단일 z≥2.5 → 긴급 |
| 임계치 전략 | 적응형 (최근 8회 μ + σ) | warmup·보수적 단계 포함 |
| 시뮬 강도 조절 (Layer B) | v1 미포함 | 운영 검증 후 v2에서 도입 검토 |
## 3. 아키텍처
### 3.1 컴포넌트 다이어그램
```
┌─────────────────────────────────────────────────────────────┐
│ agent-office │
│ │
│ cron (scheduler.py) │
│ ├─ lotto_light_check 매일 09:15 │
│ ├─ lotto_sim_check 4시간마다 :15 │
│ ├─ lotto_deep_check 일/수 21:15 │
│ ├─ lotto_daily_digest 매일 09:25 │
│ └─ lotto_curate 월요일 09:05 (기존 유지) │
│ ↓ │
│ curator/signals.py (신규) │
│ ├─ evaluate_sim_consensus() ← lotto_best API │
│ ├─ evaluate_strategy_drift() ← strategy/weights API │
│ ├─ evaluate_confidence() ← deep_check 시 큐레이션 결과 │
│ └─ adaptive_baseline() ← μ, σ 갱신 │
│ ↓ │
│ agent_office.db │
│ ├─ lotto_signals (이벤트 이력) │
│ └─ lotto_baselines (롤링 8회 윈도우) │
│ ↓ │
│ notifiers/telegram_lotto.py │
│ ├─ send_urgent_signal() ← 긴급 │
│ └─ send_signal_summary() ← 일일 요약 │
└─────────────────────────────────────────────────────────────┘
↑ (HTTP GET, 기존 lotto-lab API 재사용, 변경 없음)
lotto:8000
├─ /api/lotto/best
├─ /api/lotto/strategy/weights
└─ /api/lotto/curator/*
```
### 3.2 책임 경계
- **lotto-lab**: 변경 없음. 기존 GET API만 소비.
- **agent-office**: 능동 모니터링 layer 전부 담당. DB도 `agent_office.db` 안에 분리해서 lotto.db와 결합 없음.
- **프론트엔드**: Phase 4 별도 (web-ui repo). 본 spec 범위 밖.
## 4. 시그널 평가 로직
### 4.1 Sim Consensus Score
```
best_picks 20개의 점수 5종 (s1..s5) 사용
normalize(s_k) = (s_k - min_k) / (max_k - min_k) per metric across 20 picks
consensus_i = geomean( normalize(s1_i), ..., normalize(s5_i) )
sim_signal = mean( sorted(consensus_i, desc)[:10] )
```
- 기하평균: 5종 점수가 **동시에** 높을 때만 강한 시그널. 단일 폭주는 감쇠.
- top-10 평균: 전체 20개 분포에서 강한 후보군의 농도 측정.
### 4.2 Strategy Drift Score
```
drift_t = Σ | w_strategy_t - w_strategy_{t-1} | for each strategy in strategy_weights
```
- 회차 단위로 비교. 한 전략이 EMA로 큰 폭 이동했을 때 누적값이 큼.
- 시스템이 "지난 회차에서 의미 있게 학습한" 시그널.
### 4.3 Confidence Score
`curator.pipeline.curate_weekly()` 반환의 `validated.confidence` (0~1) 그대로.
- light_check / sim_check: N/A (LLM 호출 없음)
- deep_check: 직전 큐레이션 confidence를 baseline 윈도우에 push
### 4.4 Adaptive Baseline
```
lotto_baselines.window_values = [v_{t-7}, v_{t-6}, ..., v_t] (FIFO 8)
mu = mean(window_values)
sigma = stddev(window_values, ddof=1)
z_now = (v_now - mu) / sigma
```
- **Cold start**: window 크기 < 4 → fire_level='warmup', 발화 X
- **준비 단계**: window 4~7 → 임계치 z=2.0 (false positive 줄임)
- **정상 운영**: window 8 풀 → z_normal=1.5, z_urgent=2.5
### 4.5 Trigger × Metric 매트릭스
| Trigger | Sim Consensus | Strategy Drift | Confidence |
|---|---|---|---|
| `light_check` (매일 09:15) | ✓ 평가 | ✓ 회차 변경 시만 | — |
| `sim_check` (4h마다) | ✓ 평가 | ✓ 회차 변경 시만 | — |
| `deep_check` (일/수 21:15) | ✓ 평가 | ✓ 회차 변경 시만 | ✓ (큐레이션 후) |
| `lotto_curate` (월 09:05) | — | — | ✓ 큐레이션 결과 직접 push |
**회차 변경 가드**: Strategy Drift / Confidence는 **회차 단위 메트릭**. baseline 윈도우에 push할 때 `last_pushed_draw_no`를 비교, 동일 회차면 skip. 같은 회차 내에서 값 비교는 가능하지만 baseline 갱신은 회차당 1회만.
```
if metric in ('drift', 'confidence'):
if current_draw_no == baselines[metric].last_pushed_draw_no:
# baseline 윈도우는 그대로, z-score만 현재값으로 비교
skip_window_update = True
```
Sim Consensus는 회차 무관 (4시간마다 시뮬 자체가 갱신) → 매 평가 시 window push.
### 4.6 Fire 결정
```
fires = [m for m in [sim, drift, conf] if m.z >= LOTTO_Z_NORMAL]
if len(fires) >= 2 or any(m.z >= LOTTO_Z_URGENT for m in fires):
fire_level = 'urgent'
elif len(fires) == 1:
fire_level = 'normal'
else:
fire_level = 'noop'
```
## 5. 알림 흐름
### 5.1 트리거→발송 다이어그램
```
cron / signal_check
signals.evaluate_all()
lotto_signals INSERT (all results)
fire_level == 'urgent' → send_urgent_signal() → 텔레그램 즉시
fire_level == 'normal' → 09:25 digest 합류
fire_level == 'noop' → 기록만
```
### 5.2 텔레그램 메시지 폼
**Urgent**:
```
🚨 로또 능동 신호
[2026-05-20 16:18]
강한 시그널 2종 동시 발화:
• Sim Consensus 1.84 (μ=1.02, σ=0.21) z=3.9
• Strategy Drift 0.18 (μ=0.06, σ=0.04) z=3.0
요인: gap_focus 전략이 지난 3회차 EMA +22%p
다음 시뮬: 20:05
[자세히 보기] (→ /lotto/agent)
```
**Daily digest** (09:25):
```
📊 로또 일일 요약 (지난 24h)
평가 6회 / 발화 2회
• Sim Consensus normal z=1.7 (16:18)
• Confidence normal z=1.6 (월 09:05)
전략 가중치 추세 (최근 8회 baseline):
gap_focus ↑ +12%
hot_focus → -2%
pair_bias ↓ -8%
```
- 24h 내 발화 0건이면 digest 자체 skip (조용한 날 강제 알림 없음).
### 5.3 Throttle 규칙
| 규칙 | 동작 |
|---|---|
| 같은 metric + 같은 fire_level이 6시간 이내 재발화 | 두 번째는 DB 기록만, 텔레그램 skip |
| urgent 누적 ≥ 3통/day | 4번째부터 normal로 강등 → digest 합류 |
| digest 24h 발화 0건 | digest skip |
| Anthropic / 텔레그램 실패 | 평가는 success로 기록, 메시지만 60초 후 1회 retry |
## 6. 데이터 모델
### 6.1 lotto_signals
```sql
CREATE TABLE IF NOT EXISTS lotto_signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
triggered_at TEXT NOT NULL, -- ISO8601 UTC
source TEXT NOT NULL, -- 'light' | 'sim' | 'deep'
metric TEXT NOT NULL, -- 'sim_signal' | 'drift' | 'confidence'
value REAL NOT NULL,
baseline_mu REAL,
baseline_sigma REAL,
z_score REAL,
fire_level TEXT NOT NULL, -- 'noop' | 'warmup' | 'normal' | 'urgent'
notified_at TEXT, -- 텔레그램 발송 시각 (NULL=미발송)
payload TEXT -- JSON 부가 정보
);
CREATE INDEX idx_ls_triggered ON lotto_signals(triggered_at DESC);
CREATE INDEX idx_ls_fire ON lotto_signals(fire_level, notified_at);
```
### 6.2 lotto_baselines
```sql
CREATE TABLE IF NOT EXISTS lotto_baselines (
metric TEXT PRIMARY KEY,
window_values TEXT NOT NULL, -- JSON: [v1..v8]
mu REAL NOT NULL,
sigma REAL NOT NULL,
last_pushed_draw_no INTEGER, -- 회차 단위 메트릭의 중복 push 방지 (drift, confidence)
updated_at TEXT NOT NULL
);
```
마이그레이션: `agent-office/app/db.py``init_db()``CREATE TABLE IF NOT EXISTS` 추가만으로 idempotent. 기존 테이블 영향 없음.
## 7. API 추가
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | `/api/agent-office/lotto/signals?days=7` | 시그널 이력 (timeline, 차트용) |
| GET | `/api/agent-office/lotto/baselines` | 현재 baseline μ/σ 조회 |
| POST | `/api/agent-office/lotto/signal-check` | 수동 트리거 (디버깅·테스트용) |
## 8. 환경 변수
```bash
LOTTO_SIGNAL_WINDOW=8 # baseline 윈도우 크기
LOTTO_Z_NORMAL=1.5 # normal fire 임계치
LOTTO_Z_URGENT=2.5 # urgent fire 임계치
LOTTO_DIGEST_HOUR=9 # digest cron hour (KST)
LOTTO_DIGEST_MIN=25
LOTTO_THROTTLE_HOURS=6 # 같은 메트릭 재발화 throttle
LOTTO_URGENT_DAILY_MAX=3 # urgent 하루 cap
```
모두 default 있음. `.env` 미설정 시 default로 동작.
## 9. 스케줄러 cron
```python
scheduler.add_job(lotto_light_check, "cron", hour=9, minute=15, id="lotto_light_check")
scheduler.add_job(lotto_sim_check, "cron", minute=15, hour="0,4,8,12,16,20", id="lotto_sim_check")
scheduler.add_job(lotto_deep_check, "cron", day_of_week="sun,wed", hour=21, minute=15, id="lotto_deep_check")
scheduler.add_job(lotto_daily_digest, "cron", hour=9, minute=25, id="lotto_digest")
# 기존: lotto_curate (월 09:05) 유지
```
## 10. 구현 Phase
| Phase | 범위 | 검증 |
|---|---|---|
| 1 | DB 마이그레이션 + `signals.py` (순수 함수, LLM X) | `POST /lotto/signal-check`로 수동 호출 → z-score 계산 확인 |
| 2 | cron 4개 + `lotto_signals` INSERT (텔레그램 X) | 24h 가동 → DB에 시그널 누적 |
| 3 | 텔레그램 urgent / digest + throttle | dry-run env로 메시지 검증 후 실제 발송 |
| 4 | 프론트 (web-ui) — `/lotto/agent` 시그널 timeline UI | 별도 PR (본 spec 범위 외) |
Phase 1~3이 백엔드 능동성 완성. 각 Phase 끝에 commit + 자동 배포.
## 11. 비기능 요구
- **백워드 호환**: 기존 월요일 큐레이션 cron 변경 없음
- **장애 격리**: signals 평가 실패해도 LottoAgent.state는 idle 유지 (try/except + add_log warning)
- **테스트**: `signals.py`의 메트릭 함수는 input/output 순수형 → 단위 테스트 작성 가능
- **관측**: `agent_logs` 테이블 그대로 활용 (별도 로깅 추가 없음)
## 12. 비목표 (Out of scope)
- 자동 구매·자동 픽 갱신 (보고만)
- 시뮬레이션 강도 자동 조절 (Layer B는 v2)
- 텔레그램 인라인 키보드 (v2에서 자동 액션 도입 시 함께)
- 핫넘버/콜드넘버 시그널 (노이즈 위험, v1 제외)
- 프론트 UI (별도 PR)
## 13. v2 후속 검토
- Layer B 시뮬 강도 조절 (모호 시그널 시 deep_simulate)
- 사용자 피드백 루프 (텔레그램 [좋아요/별로] 버튼 → 임계치 가중 조정)
- 회차 retrospective 자동 분석 (당첨번호 vs 추천번호 패턴 학습)