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_strategy ON purchase_history(source_strategy)")
|
||||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_purchase_checked ON purchase_history(draw_no, checked)")
|
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 ───────────────────────────────────────────────────────────────
|
# ── 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),
|
(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