feat(lotto): briefing API 4계층 picks + tier_rationale 수용
This commit is contained in:
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
52
lotto/tests/test_briefing_4tier.py
Normal file
52
lotto/tests/test_briefing_4tier.py
Normal 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"] == "공격"
|
||||
Reference in New Issue
Block a user