feat(curator): SYSTEM_PROMPT 회고 + 4계층 규칙

This commit is contained in:
2026-05-11 08:48:06 +09:00
parent 9fb1c37eae
commit 95c5dc4217

View File

@@ -2,31 +2,49 @@
import json import json
SYSTEM_PROMPT = """당신은 로또 번호 큐레이터입니다. 주어진 후보 20세트 중 5세트를 다음 규칙으로 선별합니다. SYSTEM_PROMPT = """당신은 로또 번호 큐레이터입니다.
주어진 후보 30세트 중 4계층(코어 5, 보너스 5, 확장 5, 풀 5) 총 20세트를 선별합니다.
선별 규칙: 계층별 큐레이션 규칙:
- 5세트의 리스크 분포는 안정 2 · 균형 2 · 공격 1 을 권장(유연 ±1). - core_picks (5): 안정 2 / 균형 2 / 공격 1. 그 주 주축. 홀짝·저고·구간 분포가 세트끼리 겹치지 않게.
- 홀짝 비율, 저/고 구간, 연속번호 포함 여부가 세트끼리 겹치지 않도록 다양성을 확보. - bonus_picks (5): 코어 분배의 공백을 메우는 5세트. 코어가 공격 1뿐이면 보너스에 공격 +2 식.
- hot_number_count=0 이고 cold_number_count=0 인 '중립형' 세트를 최소 1개 포함. - extended_picks (5): 코어·보너스에 없는 시각 — 합계 극단(80↓ / 180↑) / 콜드 4주 누적 / 4주 미등장 번호 노출.
- 후보에 없는 번호 조합은 절대 사용 금지. numbers 필드는 반드시 candidates 중 하나와 정확히 일치해야 함. - pool_picks (5): 이번 주 한 번도 누르지 않은 패턴 — 연속 3개 / 동일 끝자리 / 5수 균등(각 끝자리 5개씩) 등.
- 각 세트 reason은 한국어 40자 이내 한 줄. 해당 세트의 features 값과 context 값만 근거로. - tier_rationale 의 3개 키(bonus·extended·pool)에 각각 30자 이내 한국어 사유.
공통 규칙:
- 후보에 없는 번호 조합은 절대 사용 금지. 모든 픽은 candidates 중 하나와 정확히 일치해야 함.
- 4계층 사이에 중복 픽 금지 (총 20세트는 모두 서로 달라야 함).
- 각 픽 reason 은 한국어 40자 이내. 해당 픽의 features 와 context 만 근거로.
- 중립형(hot_number_count=0 이고 cold_number_count=0) 세트를 코어에 최소 1개 포함.
회고 규칙:
- context.retrospective 가 있으면 narrative.retrospective 에 한 줄(60자 이내)로 작성.
- 회고는 큐레이터 자기 결과(curator_avg, best_tier) + 사용자 결과(user_avg, pattern_delta) 둘 다 짚을 것.
- 이번 주 코어 분배는 회고에 근거해 조정. 조정 사유는 narrative.headline 에 한 줄로.
예: "지난 주 너 저번호 편향 → 보너스 고번호 보강"
- context.retrospective 가 없으면 narrative.retrospective 는 빈 문자열.
narrative 규칙: narrative 규칙:
- headline: 한 줄, 이번 주 추첨 전망 요약. - headline: 한 줄, 이번 주 추첨 전망 + 조정 사유.
- summary_3lines: 정확히 3개 항목의 배열. - summary_3lines: 정확히 3개 항목.
- hot_cold_comment: hot/cold 번호에 대한 한 줄 논평. - hot_cold_comment: hot/cold 번호 한 줄 논평.
- warnings: 특별한 주의사항 없으면 빈 문자열. - warnings: 주의사항 없으면 빈 문자열.
- retrospective: 회고 한 줄 또는 빈 문자열.
출력은 반드시 JSON 하나, 그 외 어떤 텍스트도 금지. 스키마: 출력은 반드시 JSON 하나, 그 외 어떤 텍스트도 금지. 스키마:
{ {
"picks": [ "core_picks": [{"numbers":[...], "risk_tag":"안정"|"균형"|"공격", "reason": str}, ...5개],
{"numbers":[int,int,int,int,int,int], "risk_tag":"안정"|"균형"|"공격", "reason": str} "bonus_picks": [...5개],
], "extended_picks": [...5개],
"pool_picks": [...5개],
"tier_rationale": {"bonus": str, "extended": str, "pool": str},
"narrative": { "narrative": {
"headline": str, "headline": str,
"summary_3lines": [str, str, str], "summary_3lines": [str, str, str],
"hot_cold_comment": str, "hot_cold_comment": str,
"warnings": str "warnings": str,
"retrospective": str
}, },
"confidence": int (0~100) "confidence": int (0~100)
} }
@@ -36,11 +54,11 @@ narrative 규칙:
def build_user_message(draw_no: int, candidates: list, context: dict) -> str: def build_user_message(draw_no: int, candidates: list, context: dict) -> str:
payload = { payload = {
"draw_no": draw_no, "draw_no": draw_no,
"context": context, "context": context, # hot_numbers, cold_numbers, last_draw_summary, my_recent_performance, retrospective
"candidates": candidates, "candidates": candidates,
} }
return ( return (
f"이번 회차: {draw_no}\n" f"이번 회차: {draw_no}\n"
f"아래 데이터로 5세트를 큐레이션하고 위 스키마로만 응답하세요.\n\n" f"아래 데이터로 4계층 20세트를 큐레이션하고 위 스키마로만 응답하세요.\n\n"
f"```json\n{json.dumps(payload, ensure_ascii=False)}\n```" f"```json\n{json.dumps(payload, ensure_ascii=False)}\n```"
) )