feat(lotto): briefing API 4계층 picks + tier_rationale 수용

This commit is contained in:
2026-05-11 08:45:21 +09:00
parent b936233e7c
commit 3bd819b5e2
3 changed files with 90 additions and 19 deletions

View File

@@ -1039,36 +1039,40 @@ def bulk_insert_purchases_from_briefing(draw_no: int, tier_mode: str, amount: in
# --- Lotto Briefings --- # --- Lotto Briefings ---
def save_briefing(data: Dict[str, Any]) -> int: 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: with _conn() as conn:
cur = conn.execute(""" cur = conn.execute(
"""
INSERT INTO lotto_briefings INSERT INTO lotto_briefings
(draw_no, picks, narrative, confidence, model, (draw_no, picks, narrative, confidence, model,
tokens_input, tokens_output, cache_read, cache_write, tokens_input, tokens_output, cache_read, cache_write,
latency_ms, source) latency_ms, source, tier_rationale)
VALUES (?,?,?,?,?,?,?,?,?,?,?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(draw_no) DO UPDATE SET ON CONFLICT(draw_no) DO UPDATE SET
picks=excluded.picks, narrative=excluded.narrative, picks=excluded.picks,
confidence=excluded.confidence, model=excluded.model, narrative=excluded.narrative,
confidence=excluded.confidence,
model=excluded.model,
tokens_input=excluded.tokens_input, tokens_input=excluded.tokens_input,
tokens_output=excluded.tokens_output, tokens_output=excluded.tokens_output,
cache_read=excluded.cache_read, cache_read=excluded.cache_read,
cache_write=excluded.cache_write, cache_write=excluded.cache_write,
latency_ms=excluded.latency_ms, latency_ms=excluded.latency_ms,
source=excluded.source, source=excluded.source,
tier_rationale=excluded.tier_rationale,
generated_at=datetime('now','localtime') generated_at=datetime('now','localtime')
""", ( """,
data["draw_no"], (
json.dumps(data["picks"], ensure_ascii=False), data["draw_no"], picks_json, narrative_json,
json.dumps(data["narrative"], ensure_ascii=False), data["confidence"], data["model"],
int(data["confidence"]), data.get("tokens_input", 0), data.get("tokens_output", 0),
data["model"], data.get("cache_read", 0), data.get("cache_write", 0),
int(data.get("tokens_input", 0)), data.get("latency_ms", 0), data.get("source", "auto"),
int(data.get("tokens_output", 0)), tier_rationale_json,
int(data.get("cache_read", 0)), ),
int(data.get("cache_write", 0)), )
int(data.get("latency_ms", 0)),
data.get("source", "auto"),
))
return cur.lastrowid return cur.lastrowid
@@ -1078,6 +1082,7 @@ def _briefing_row(r) -> Dict[str, Any]:
"draw_no": r["draw_no"], "draw_no": r["draw_no"],
"picks": json.loads(r["picks"]), "picks": json.loads(r["picks"]),
"narrative": json.loads(r["narrative"]), "narrative": json.loads(r["narrative"]),
"tier_rationale": json.loads(r["tier_rationale"]) if r["tier_rationale"] else {},
"confidence": r["confidence"], "confidence": r["confidence"],
"model": r["model"], "model": r["model"],
"tokens_input": r["tokens_input"], "tokens_input": r["tokens_input"],

View File

@@ -7,10 +7,24 @@ from .. import db
router = APIRouter(prefix="/api/lotto") 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): class BriefingRequest(BaseModel):
draw_no: int draw_no: int
picks: List[Dict[str, Any]] picks: BriefingPicks
narrative: Dict[str, Any] narrative: Dict[str, Any]
tier_rationale: TierRationale = Field(default_factory=TierRationale)
confidence: int = Field(ge=0, le=100) confidence: int = Field(ge=0, le=100)
model: str model: str
tokens_input: int = 0 tokens_input: int = 0

View File

@@ -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"] == "공격"