diff --git a/lotto/app/db.py b/lotto/app/db.py index e840dde..32bae14 100644 --- a/lotto/app/db.py +++ b/lotto/app/db.py @@ -1039,36 +1039,40 @@ def bulk_insert_purchases_from_briefing(draw_no: int, tier_mode: str, amount: in # --- Lotto Briefings --- def save_briefing(data: Dict[str, Any]) -> int: + picks_json = json.dumps(data["picks"], ensure_ascii=False) + narrative_json = json.dumps(data["narrative"], ensure_ascii=False) + tier_rationale_json = json.dumps(data.get("tier_rationale") or {}, ensure_ascii=False) with _conn() as conn: - cur = conn.execute(""" + cur = conn.execute( + """ INSERT INTO lotto_briefings (draw_no, picks, narrative, confidence, model, tokens_input, tokens_output, cache_read, cache_write, - latency_ms, source) - VALUES (?,?,?,?,?,?,?,?,?,?,?) + latency_ms, source, tier_rationale) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(draw_no) DO UPDATE SET - picks=excluded.picks, narrative=excluded.narrative, - confidence=excluded.confidence, model=excluded.model, + picks=excluded.picks, + narrative=excluded.narrative, + confidence=excluded.confidence, + model=excluded.model, tokens_input=excluded.tokens_input, tokens_output=excluded.tokens_output, cache_read=excluded.cache_read, cache_write=excluded.cache_write, latency_ms=excluded.latency_ms, source=excluded.source, + tier_rationale=excluded.tier_rationale, generated_at=datetime('now','localtime') - """, ( - data["draw_no"], - json.dumps(data["picks"], ensure_ascii=False), - json.dumps(data["narrative"], ensure_ascii=False), - int(data["confidence"]), - data["model"], - int(data.get("tokens_input", 0)), - int(data.get("tokens_output", 0)), - int(data.get("cache_read", 0)), - int(data.get("cache_write", 0)), - int(data.get("latency_ms", 0)), - data.get("source", "auto"), - )) + """, + ( + data["draw_no"], picks_json, narrative_json, + data["confidence"], data["model"], + data.get("tokens_input", 0), data.get("tokens_output", 0), + data.get("cache_read", 0), data.get("cache_write", 0), + data.get("latency_ms", 0), data.get("source", "auto"), + tier_rationale_json, + ), + ) return cur.lastrowid @@ -1078,6 +1082,7 @@ def _briefing_row(r) -> Dict[str, Any]: "draw_no": r["draw_no"], "picks": json.loads(r["picks"]), "narrative": json.loads(r["narrative"]), + "tier_rationale": json.loads(r["tier_rationale"]) if r["tier_rationale"] else {}, "confidence": r["confidence"], "model": r["model"], "tokens_input": r["tokens_input"], diff --git a/lotto/app/routers/briefing.py b/lotto/app/routers/briefing.py index b4e0fb4..e6516b0 100644 --- a/lotto/app/routers/briefing.py +++ b/lotto/app/routers/briefing.py @@ -7,10 +7,24 @@ from .. import db router = APIRouter(prefix="/api/lotto") +class TierRationale(BaseModel): + bonus: str = "" + extended: str = "" + pool: str = "" + + +class BriefingPicks(BaseModel): + core: List[Dict[str, Any]] = Field(default_factory=list) + bonus: List[Dict[str, Any]] = Field(default_factory=list) + extended: List[Dict[str, Any]] = Field(default_factory=list) + pool: List[Dict[str, Any]] = Field(default_factory=list) + + class BriefingRequest(BaseModel): draw_no: int - picks: List[Dict[str, Any]] + picks: BriefingPicks narrative: Dict[str, Any] + tier_rationale: TierRationale = Field(default_factory=TierRationale) confidence: int = Field(ge=0, le=100) model: str tokens_input: int = 0 diff --git a/lotto/tests/test_briefing_4tier.py b/lotto/tests/test_briefing_4tier.py new file mode 100644 index 0000000..b8761ef --- /dev/null +++ b/lotto/tests/test_briefing_4tier.py @@ -0,0 +1,52 @@ +import sys, os +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +import pytest +from app import db + + +@pytest.fixture(autouse=True) +def setup_db(tmp_path, monkeypatch): + test_db = tmp_path / "test.db" + monkeypatch.setattr(db, "DB_PATH", str(test_db)) + db.init_db() + yield + + +def test_save_briefing_4tier_roundtrip(): + payload = { + "draw_no": 9999, + "picks": {"core":[{"numbers":[1,2,3,4,5,6],"risk_tag":"안정","reason":"x"}], + "bonus":[], "extended":[], "pool":[]}, + "narrative": {"headline":"H","summary_3lines":["a","b","c"],"retrospective":"r"}, + "tier_rationale": {"bonus":"b1","extended":"e1","pool":"p1"}, + "confidence": 70, + "model": "test", + } + bid = db.save_briefing(payload) + assert bid > 0 + got = db.get_briefing(9999) + assert got["picks"]["core"][0]["numbers"] == [1,2,3,4,5,6] + assert got["tier_rationale"]["bonus"] == "b1" + assert got["narrative"]["retrospective"] == "r" + + +def test_save_briefing_upsert_overwrites(): + db.save_briefing({ + "draw_no": 8888, + "picks": {"core":[], "bonus":[], "extended":[], "pool":[]}, + "narrative": {"headline":"old","summary_3lines":["a","b","c"]}, + "confidence": 50, "model": "v1", + }) + db.save_briefing({ + "draw_no": 8888, + "picks": {"core":[{"numbers":[10,20,30,40,41,42],"risk_tag":"공격","reason":"y"}], + "bonus":[], "extended":[], "pool":[]}, + "narrative": {"headline":"new","summary_3lines":["x","y","z"]}, + "tier_rationale": {"bonus":"","extended":"","pool":""}, + "confidence": 90, "model": "v2", + }) + got = db.get_briefing(8888) + assert got["narrative"]["headline"] == "new" + assert got["confidence"] == 90 + assert got["picks"]["core"][0]["risk_tag"] == "공격"