Files
web-page/docs/superpowers/specs/2026-05-11-lotto-curator-evolution-design.md
gahusb 6b2fcda2af docs(spec): Lotto Curator Evolution 설계 문서
매주 같은 시간에 큐레이터가 한 번 더 똑똑해지는 컨셉으로
- 회고 컨텍스트(weekly_review + 자동 채점 잡)
- 4계층 위계(코어/보너스/확장/풀, 5~20세트)
- 결정 카드 단일 화면(브리핑 탭 재구성)
- 분석 탭은 자료실로 강등
- 월요일 09:00 큐레이션 + 텔레그램 푸시
2026-05-11 03:19:58 +09:00

17 KiB
Raw Permalink Blame History

Lotto Curator Evolution — Design Spec

  • 일자: 2026-05-11
  • 범위: web-ui (브리핑 탭 재구성), web-backend/lotto (스키마·잡), web-backend/agent-office (큐레이터·텔레그램)
  • 컨셉 한 줄: 매주 같은 시간에 큐레이터가 한 번 더 똑똑해진다

1. 동기와 문제

현재 /lotto는 3탭(브리핑·분석·구매)으로 구성되어 정보가 풍부하지만, 사용자가 5천~1만원 어치를 즐기며 구매하기에 다음 페인이 있다.

  • 분석·통계·브리핑이 모두 결정용 화면처럼 노출되어 정보 과다.
  • 큐레이터가 매주 5세트를 추천하지만, 5세트의 역할왜 이 분배인지가 와닿지 않는다.
  • 큐레이터·시스템에 시간축이 없다. 매주 동일 알고리즘을 새로 도는 느낌.
  • 1만원어치 구매 시 5세트로는 부족하다. 추가 게임에 대한 설계가 없다.

2. 컨셉

다음 두 축으로 강화한다.

  • 서사적 진화: 큐레이터가 매주 지난 주를 회고하고 이번 주 전략으로 이어간다. 자기 추천 결과 + 사용자 실제 구매 결과를 둘 다 회고 데이터로 사용한다.
  • 포트폴리오 명료성: 5게임이 단순 5장이 아니라 안정/균형/공격 분배가 그 주 데이터에 따라 동적으로 바뀌고, 그 이유가 한 줄로 와닿는다. 5~20세트로 위계적으로 확장된다.

3. 주간 사이클

토 20:35   추첨
 │
일 03:00   추첨결과 sync (기존)
           ↓
           채점 잡 (신규)  →  weekly_review INSERT
                              lotto_purchase auto_graded UPDATE
 │
월 09:00   큐레이션 트리거 (lotto_agent.on_schedule)
           ├─ build_retrospective(target_draw)
           ├─ collect_candidates(N=30)
           ├─ build_context (+retrospective)
           ├─ Claude 호출 (회고+계층 규칙)
           └─ briefings INSERT (4계층 picks)
 │
월 09:05   텔레그램 헤드라인 푸시
 │
월~토      사용자: 사이트 결정 카드 → 모드 선택(5/10/15/20) → 1탭 구매 기록
 │
토 20:35   추첨 → 다음 사이클

cron 시간(일 03:00 / 월 09:00)은 운영하며 조정 가능한 기본값.

4. 결정 카드 (브리핑 탭 메인)

브리핑 탭을 단일 DecisionCard로 재구성한다. 정보 위계는 위→아래로:

  1. 헤더 — 회차 + 한 줄 헤드라인 + 신뢰도(0~100, 큐레이터 자기 평가)
  2. 회고 박스 (▸ 보라색 라벨) — 지난 주 너 + 큐레이터 한 줄 회고. 시간축의 핵심.
  3. 헤드라인 + 3줄 — 이번 주 전망 + 근거 3줄(기존 narrative 유지).
  4. 분배 칩 — 선택 모드까지의 안정/균형/공격 합산 + "왜 이 분배인지" 한 줄.
  5. 모드 토글 — 4단계 칩(코어 5 / +보너스 5 / +확장 5 / +풀 5).
  6. 계층 섹션 × 4 — 각 계층마다 타이틀 + 사유 한 줄 + 5장 PickCard. 코어는 항상 펼침, 그 외는 모드에 따라.
  7. 하단 액션 — "이대로 N세트 구매했음" 한 클릭 → 자동 기록.

4계층 위계

계층 누적 게임 비용 큐레이터의 의도
코어(필수) 5 5천 안정 2 / 균형 2 / 공격 1, 그 주 주축
+ 보너스 10 1만 코어 분배의 공백 보완
+ 확장 15 1.5만 코어·보너스에 없던 시각(합계 극단·콜드 누적·4주 미등장)
+ 풀 20 2만 한 번도 누르지 않은 패턴(연속·동끝·5수 균등)

각 5세트는 큐레이터가 의도한 한 묶음이며, 늘어날수록 서사가 더해지는 구조. 마지막 모드 선택은 브라우저 localStoragelotto.tier_mode 키로 저장하여 다음 주 진입 시 디폴트로 사용한다(서버 저장 X — 사용자 디바이스 단위 기억).

분석 탭은 "Deep Dive" 자료실로 강등

  • 라벨 변경: 📊 분석·통계📚 자료실 / Deep Dive
  • 첫 진입 시 모든 패널 접힘
  • 기존 패널 모두 보존 (CombinedRecommendPanel, ReportPanel, 시뮬레이션, 통계, 빈도, PersonalAnalysisPanel, 수동 추천, 히스토리)
  • PerformanceBanner는 결정 카드 헤더와 역할 중복 없도록 자료실에만 둠

5. 데이터 모델

신규 테이블 — weekly_review

CREATE TABLE weekly_review (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  draw_no INTEGER NOT NULL UNIQUE,

  -- 큐레이터 자기 평가 (briefings.picks vs 추첨)
  curator_avg_match     REAL,
  curator_best_tier     TEXT,    -- 안정 | 균형 | 공격
  curator_best_match    INTEGER,
  curator_5plus_prizes  INTEGER, -- 3개↑ 일치 카운트(5등 이상)

  -- 사용자 구매 평가 (lotto_purchase vs 추첨)
  user_avg_match        REAL,
  user_best_match       INTEGER,
  user_5plus_prizes     INTEGER,

  -- 패턴 갭 (서사 재료)
  user_pattern_summary  TEXT,
  draw_pattern_summary  TEXT,
  pattern_delta         TEXT,    -- "너 저번호 편향 +1.2 / 합계 -18"

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

lotto_purchase 컬럼 추가

ALTER TABLE lotto_purchase ADD COLUMN numbers       TEXT;     -- JSON [3,11,17,25,33,41]
ALTER TABLE lotto_purchase ADD COLUMN match_count   INTEGER;
ALTER TABLE lotto_purchase ADD COLUMN auto_graded   INTEGER DEFAULT 0;
ALTER TABLE lotto_purchase ADD COLUMN curator_tier  TEXT;     -- core | bonus | extended | pool
ALTER TABLE lotto_purchase ADD COLUMN curator_role  TEXT;     -- 안정 | 균형 | 공격

briefings.picks 구조 변경

JSON 컬럼을 4계층 구조로 마이그레이션:

{
  "core":     [/* 5세트 */],
  "bonus":    [/* 5세트 */],
  "extended": [/* 5세트 */],
  "pool":     [/* 5세트 */]
}

기존 단일 배열 데이터는 core 키에만 매핑하고 나머지 키는 빈 배열로 채우는 1회 마이그레이션 스크립트.

6. 큐레이터 변경

출력 스키마 (agent-office/curator/schema.py)

class CuratorOutput(BaseModel):
    core_picks:     List[Pick] = Field(min_length=5, max_length=5)
    bonus_picks:    List[Pick] = Field(min_length=5, max_length=5)
    extended_picks: List[Pick] = Field(min_length=5, max_length=5)
    pool_picks:     List[Pick] = Field(min_length=5, max_length=5)
    tier_rationale: TierRationale  # bonus / extended / pool 각 30자 이내
    narrative:      Narrative      # retrospective(60자 이내) 필드 추가
    confidence:     int            # 0~100

SYSTEM_PROMPT 추가 규칙

회고 규칙:
- context.retrospective 가 있으면 narrative.retrospective 에 한 줄(60자 이내).
- 큐레이터 자기 결과(curator_avg, best_tier) + 사용자 결과(user_avg, pattern_delta) 둘 다 짚을 것.
- 이번 주 코어 분배는 회고에 근거해 조정. 사유는 narrative.headline 에 한 줄로.

계층별 큐레이션 규칙:
- core_picks (5):     안정 2 / 균형 2 / 공격 1. 그 주 주축.
- bonus_picks (5):    코어 분배의 공백을 메움. 코어와 상보적.
- extended_picks (5): 코어·보너스에 없는 시각(합계 극단 / 콜드 누적 / 4주 미등장).
- pool_picks (5):     이번 주 한 번도 누르지 않은 패턴(연속·동끝·5수 균등).
- tier_rationale 의 3개 키(bonus·extended·pool)에 각각 30자 이내 사유.
- 후보에 없는 번호 조합은 절대 사용 금지(기존 규칙 유지).

회고 컨텍스트 — agent-office/curator/retrospective.py (신규)

def build_retrospective(target_draw_no: int) -> dict | None:
    last  = lotto_get_review(target_draw_no - 1)
    prev3 = lotto_get_reviews(target_draw_no - 4, target_draw_no - 2)
    if not last:
        return None
    return {
        "last_draw": {
            "draw_no": last["draw_no"],
            "curator_avg": last["curator_avg_match"],
            "curator_best_tier": last["curator_best_tier"],
            "user_avg": last["user_avg_match"],
            "user_5plus": last["user_5plus_prizes"],
            "pattern_delta": last["pattern_delta"],
        },
        "trend_4w": {
            "curator_avg_4w": mean(curator_avg_match for r in [last, *prev3]),
            "user_avg_4w":    mean(user_avg_match    for r in [last, *prev3] if user_avg_match is not None),
            "user_persistent_bias": _detect_bias([last, *prev3]),  # 3주↑ 유지된 패턴 편향(예: "저번호 편향")
        }
    }

후보 풀 N=30

collect_candidates(n=30) — 20세트 선별 + 다양성 여유. 기존 4개 소스(simulation/heatmap/statistics/meta) 추출량을 비례 확대.

7. 자동 채점 잡 — lotto/app/jobs/grade_weekly_review.py

실행: 매주 일요일 03:00 KST (cron)
입력: 가장 최근 sync된 추첨 회차
처리:
  1) briefings 에서 해당 회차의 4계층 picks 로드 (없으면 curator_* NULL)
  2) lotto_purchase 에서 해당 회차의 사용자 구매 로드 (없으면 user_* NULL)
  3) 각 세트별 일치 수 계산 → 큐레이터/사용자 집계
  4) 패턴 요약(저번호·홀짝·합계 평균) → user/draw_pattern_summary
  5) 패턴 갭 한 줄(가장 큰 격차 1~2개) → pattern_delta
  6) weekly_review UPSERT (draw_no 유니크)
  7) lotto_purchase 채점:
     - 일치 3개 → prize=5000, auto_graded=1
     - 일치 4개 → prize=NULL, note 에 "4등 가능성 — 동행복권 확인" 플래그
     - 일치 5+ → prize=NULL, note 에 "🚨 큰 당첨 가능성 — 즉시 확인" 플래그
                + agent-office HTTP webhook(`POST /api/agent-office/notify/lotto-prize`)
                  호출하여 텔레그램 별도 알림 트리거
     - numbers NULL 인 행은 스킵

8. 텔레그램 알림 — agent-office/notifiers/telegram_lotto.py (신규)

큐레이션 성공 후 lotto_agent 가 호출. 발송 실패는 try/except 로 흡수(briefing 저장과 분리). 4등 이상 당첨 알림은 lotto-backend 채점 잡이 POST /api/agent-office/notify/lotto-prize webhook 으로 트리거(agent-office 측 라우터 신규 추가).

🎟 1154회 · 큐레이션 떴음

"이번 주는 안정 +1, 콜드 누적 보강."
신뢰도 72 · 분배 안정 3·균형 1·공격 1

▸ 회고: 너 2.0 / 나 1.8
  너 저번호 편향 → 보너스 고번호 보강

👉 결정 카드 보러가기  (https://gahusb.synology.me/lotto)

회고 단락은 retrospective 가 있을 때만(첫 주 생략).

9. 프론트 변경

파일 변경 맵

파일 종류 내용
pages/lotto/Functions.jsx 수정 분석탭 라벨 변경
pages/lotto/tabs/BriefingTab.jsx 수정 DecisionCard 단일로 재구성
pages/lotto/components/decision/DecisionCard.jsx 신규 결정 카드 메인
pages/lotto/components/decision/RetrospectiveBox.jsx 신규 회고 박스
pages/lotto/components/decision/TierModeToggle.jsx 신규 4단계 칩 토글
pages/lotto/components/decision/TierSection.jsx 신규 한 계층 영역(타이틀+사유+5장)
pages/lotto/components/decision/PickCard.jsx 신규 한 세트 카드(역할+번호+사유)
pages/lotto/components/decision/BulkPurchaseButton.jsx 신규 원클릭 구매
pages/lotto/components/briefing/* 삭제·이동 DecisionCard 하위로 흡수, CuratorUsageFooter 는 자료실 이동
pages/lotto/components/PurchasePanel.jsx 수정 auto_graded 표시 + 4등 이상 플래그
pages/lotto/components/PurchaseTrendChart.jsx 신규 4주 추세 라인(너 vs 큐레이터 평균 일치)
pages/lotto/hooks/useBriefing.js 수정 4계층 + retrospective 수용
pages/lotto/hooks/useReview.js 신규 weekly_review 로드
pages/lotto/hooks/usePurchases.js 수정 bulkPurchase 추가
api.js 수정 getLatestReview, getReviewHistory, bulkPurchase 헬퍼

컴포넌트 격리 원칙

  • DecisionCardbriefing + review 두 객체만 props 로 받음(내부 hook 호출 X).
  • TierSectiontier, picks, rationale 만 받아 4번 재사용.
  • BulkPurchaseButtondraw_no, tier_mode, sets, amount 4개로 작동.

10. 백엔드 변경

web-backend/lotto/

파일 종류 내용
app/db/migrations/00X_weekly_review.sql 신규 테이블 생성
app/db/migrations/00X_purchase_grading.sql 신규 lotto_purchase 컬럼 추가
app/db/migrations/00X_briefings_tiers.sql 신규 briefings.picks 4계층 마이그레이션
app/jobs/grade_weekly_review.py 신규 채점 잡
app/curator_helpers.py 수정 collect_candidates(N=30) 기본값, build_context 에 retrospective 합치기
app/routers/briefing.py 수정 BriefingRequest 4계층 + narrative.retrospective 수용
app/routers/review.py 신규 GET /api/lotto/review/latest, GET /api/lotto/review/history?limit=N
app/routers/purchase.py 수정 POST /api/lotto/purchase/bulk
app/cron.py (또는 compose 스케줄러) 수정 채점 잡 일 03:00 등록

web-backend/agent-office/

파일 종류 내용
app/curator/retrospective.py 신규 build_retrospective
app/curator/schema.py 수정 4계층 + tier_rationale + narrative.retrospective
app/curator/prompt.py 수정 회고·계층 규칙 추가
app/curator/pipeline.py 수정 retrospective 빌드 호출, 4계층 직렬화
app/agents/lotto.py 수정 on_schedule 월 09:00, 성공 시 텔레그램 호출
app/notifiers/telegram_lotto.py 신규 알림 포맷·발송(큐레이션 완료, 4등 이상 당첨 알림 둘 다)
app/routers/notify.py 신규 POST /api/agent-office/notify/lotto-prize — lotto-backend 채점 잡이 호출
app/service_proxy.py 수정 review 헬퍼 추가

11. API 추가·변경

메서드 경로 설명
GET /api/lotto/review/latest 최신 weekly_review 1건
GET /api/lotto/review/history?limit=N 최근 N건 (4주 추세 차트용)
POST /api/lotto/purchase/bulk 결정 카드 원클릭 — body: { draw_no, tier_mode, sets, amount }
POST /api/agent-office/notify/lotto-prize 4등 이상 당첨 시 lotto-backend 가 트리거 — body: { draw_no, match_count, numbers, purchase_id }

기존 엔드포인트는 그대로 유지(스키마 호환).

12. 에러 처리 / 격리

단계 실패 처리
추첨결과 sync 동행복권 API down 기존 정책(재시도). 채점 잡은 자동 지연만.
채점 — 큐레이터 picks 없음 첫 주, 큐레이션 실패 회차 curator_* NULL 로 INSERT
채점 — 사용자 구매 없음 그 주 미구매 user_* NULL
채점 — numbers NULL 행 마이그레이션 이전 데이터 스킵, auto_graded=0 유지
build_retrospective — review 없음 첫 주 None 반환 → 프롬프트 분기 자연 처리
Claude 스키마 실패 4계층 미준수 등 기존 1회 retry, 2회 실패 시 텔레그램 에러 알림
텔레그램 발송 실패 봇/네트워크 try/except, 로그만. briefing 저장은 영향 없음
bulk purchase — briefing 없음 큐레이션 실패 회차 400 + 토스트
bulk purchase — 중복 호출 더블클릭 (draw_no, tier_mode) 유니크 → idempotent
자동채점 — 4등 이상 큰 당첨 prize NULL + 메모 플래그 + 텔레그램 별도 알림

13. 테스트

백엔드 (lotto/)

  • grade_weekly_review: (a) 정상 (b) user 구매 없음 (c) numbers NULL 스킵 (d) 일치 3개 → prize 5000 (e) 일치 4개 → 메모 플래그
  • 마이그레이션: 빈 DB → 더미 → 잡 실행 → 행 정확
  • briefings 마이그레이션: 구 단일 picks → core 매핑, 나머지 빈 배열
  • POST /purchase/bulk: 정상 / 잘못된 tier_mode / briefing 없음 / 중복 호출
  • GET /review/latest: 데이터 있음 / 빈 DB → 404

큐레이터 (agent-office/curator/)

  • build_retrospective: review 1건 / 4건 / 0건
  • validate_response: 정상 / 계층 누락 / 후보 외 번호 / tier_rationale 누락
  • curate_weekly (Claude API mock): retrospective 있음·없음 / 1차 실패 → 2차 성공 / 2회 실패
  • telegram_lotto.format: retrospective 있음·없음

프론트

  • DecisionCard 수동: retrospective 있음·없음 / 모드 토글 5/10/15/20 / confidence 색
  • TierModeToggle 단위: onChange 콜백 정확
  • BulkPurchaseButton 수동 E2E: 클릭 → POST → 토스트 → 구매탭 갱신
  • 자료실 탭 수동: 첫 진입 모두 접힘
  • 모바일: DecisionCard 좁은 화면에서 깨짐 없음

14. 운영 점검 (배포 후 1주차)

수동으로 확인:

  1. 일 03:00 채점 잡 1회 실행(weekly_review 1행 추가)
  2. 월 09:00 큐레이션 실행(briefings 1행, 4계층 5×4=20개)
  3. 텔레그램 알림 도착(회고 단락 정확 포함/생략)
  4. 결정 카드 렌더링 정상(모바일 + PC)
  5. 원클릭 구매 정확 N건 INSERT
  6. cron 시간(03:00 / 09:00) 운영 패턴에 맞게 조정

15. Out of Scope

  • 4등 이상 당첨금 자동 입력(회차별 변동, 사용자 PUT 으로 갱신)
  • 큐레이터 호출 재무 비용 모니터링 강화(기존 curator_usage 그대로)
  • 분석 탭 패널 자체의 리팩토링(라벨·디폴트 접힘만 변경)
  • 1만원 외 임의 분량(7세트 등) 토글(4계층 5단위로 고정)