feat(lotto): lotto_briefings 테이블 + CRUD 함수
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -277,6 +277,26 @@ def init_db() -> None:
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_purchase_strategy ON purchase_history(source_strategy)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_purchase_checked ON purchase_history(draw_no, checked)")
|
||||
|
||||
# ── lotto_briefings 테이블 ─────────────────────────────────────────────
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS lotto_briefings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
draw_no INTEGER UNIQUE NOT NULL,
|
||||
picks TEXT NOT NULL,
|
||||
narrative TEXT NOT NULL,
|
||||
confidence INTEGER NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
tokens_input INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_output INTEGER NOT NULL DEFAULT 0,
|
||||
cache_read INTEGER NOT NULL DEFAULT 0,
|
||||
cache_write INTEGER NOT NULL DEFAULT 0,
|
||||
latency_ms INTEGER NOT NULL DEFAULT 0,
|
||||
source TEXT NOT NULL DEFAULT 'auto',
|
||||
generated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
||||
)
|
||||
""")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_briefings_draw ON lotto_briefings(draw_no DESC)")
|
||||
|
||||
|
||||
# ── todos CRUD ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1096,3 +1116,104 @@ def update_purchase_results(purchase_id: int, results: list, total_prize: int) -
|
||||
(json.dumps(results, ensure_ascii=False), total_prize, purchase_id),
|
||||
)
|
||||
|
||||
|
||||
# --- Lotto Briefings ---
|
||||
|
||||
def save_briefing(data: Dict[str, Any]) -> int:
|
||||
with _conn() as conn:
|
||||
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 (?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(draw_no) DO UPDATE SET
|
||||
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,
|
||||
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"),
|
||||
))
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
def _briefing_row(r) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": r["id"],
|
||||
"draw_no": r["draw_no"],
|
||||
"picks": json.loads(r["picks"]),
|
||||
"narrative": json.loads(r["narrative"]),
|
||||
"confidence": r["confidence"],
|
||||
"model": r["model"],
|
||||
"tokens_input": r["tokens_input"],
|
||||
"tokens_output": r["tokens_output"],
|
||||
"cache_read": r["cache_read"],
|
||||
"cache_write": r["cache_write"],
|
||||
"latency_ms": r["latency_ms"],
|
||||
"source": r["source"],
|
||||
"generated_at": r["generated_at"],
|
||||
}
|
||||
|
||||
|
||||
def get_latest_briefing() -> Optional[Dict[str, Any]]:
|
||||
with _conn() as conn:
|
||||
r = conn.execute("SELECT * FROM lotto_briefings ORDER BY draw_no DESC LIMIT 1").fetchone()
|
||||
return _briefing_row(r) if r else None
|
||||
|
||||
|
||||
def get_briefing(draw_no: int) -> Optional[Dict[str, Any]]:
|
||||
with _conn() as conn:
|
||||
r = conn.execute("SELECT * FROM lotto_briefings WHERE draw_no=?", (draw_no,)).fetchone()
|
||||
return _briefing_row(r) if r else None
|
||||
|
||||
|
||||
def list_briefings(limit: int = 10) -> List[Dict[str, Any]]:
|
||||
with _conn() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM lotto_briefings ORDER BY draw_no DESC LIMIT ?",
|
||||
(limit,),
|
||||
).fetchall()
|
||||
return [_briefing_row(r) for r in rows]
|
||||
|
||||
|
||||
def get_curator_usage(days: int = 30) -> Dict[str, Any]:
|
||||
with _conn() as conn:
|
||||
r = conn.execute("""
|
||||
SELECT COUNT(*) AS calls,
|
||||
SUM(tokens_input) AS in_tokens,
|
||||
SUM(tokens_output) AS out_tokens,
|
||||
SUM(cache_read) AS cache_read,
|
||||
SUM(cache_write) AS cache_write,
|
||||
AVG(latency_ms) AS avg_latency
|
||||
FROM lotto_briefings
|
||||
WHERE generated_at >= datetime('now', ?, 'localtime')
|
||||
""", (f"-{int(days)} days",)).fetchone()
|
||||
cr = int(r["cache_read"] or 0)
|
||||
cw = int(r["cache_write"] or 0)
|
||||
return {
|
||||
"days": days,
|
||||
"calls": int(r["calls"] or 0),
|
||||
"tokens_input": int(r["in_tokens"] or 0),
|
||||
"tokens_output": int(r["out_tokens"] or 0),
|
||||
"cache_read": cr,
|
||||
"cache_write": cw,
|
||||
"cache_hit_rate": round(cr / (cr + cw), 3) if (cr + cw) > 0 else 0.0,
|
||||
"avg_latency_ms": round(float(r["avg_latency"] or 0), 1),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user