feat(lotto): weekly_review 테이블 + CRUD 헬퍼
This commit is contained in:
105
lotto/app/db.py
105
lotto/app/db.py
@@ -259,6 +259,26 @@ def init_db() -> None:
|
|||||||
""")
|
""")
|
||||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_briefings_draw ON lotto_briefings(draw_no DESC)")
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_briefings_draw ON lotto_briefings(draw_no DESC)")
|
||||||
|
|
||||||
|
# ── weekly_review 테이블 (큐레이터 자기 평가 + 사용자 패턴 갭) ────────
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS weekly_review (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
draw_no INTEGER UNIQUE NOT NULL,
|
||||||
|
curator_avg_match REAL,
|
||||||
|
curator_best_tier TEXT,
|
||||||
|
curator_best_match INTEGER,
|
||||||
|
curator_5plus_prizes INTEGER,
|
||||||
|
user_avg_match REAL,
|
||||||
|
user_best_match INTEGER,
|
||||||
|
user_5plus_prizes INTEGER,
|
||||||
|
user_pattern_summary TEXT,
|
||||||
|
draw_pattern_summary TEXT,
|
||||||
|
pattern_delta TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_review_draw ON weekly_review(draw_no DESC)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1052,3 +1072,88 @@ def get_curator_usage(days: int = 30) -> Dict[str, Any]:
|
|||||||
"avg_latency_ms": round(float(r["avg_latency"] or 0), 1),
|
"avg_latency_ms": round(float(r["avg_latency"] or 0), 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def save_review(data: Dict[str, Any]) -> int:
|
||||||
|
with _conn() as conn:
|
||||||
|
cur = conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO weekly_review (
|
||||||
|
draw_no,
|
||||||
|
curator_avg_match, curator_best_tier, curator_best_match, curator_5plus_prizes,
|
||||||
|
user_avg_match, user_best_match, user_5plus_prizes,
|
||||||
|
user_pattern_summary, draw_pattern_summary, pattern_delta
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(draw_no) DO UPDATE SET
|
||||||
|
curator_avg_match=excluded.curator_avg_match,
|
||||||
|
curator_best_tier=excluded.curator_best_tier,
|
||||||
|
curator_best_match=excluded.curator_best_match,
|
||||||
|
curator_5plus_prizes=excluded.curator_5plus_prizes,
|
||||||
|
user_avg_match=excluded.user_avg_match,
|
||||||
|
user_best_match=excluded.user_best_match,
|
||||||
|
user_5plus_prizes=excluded.user_5plus_prizes,
|
||||||
|
user_pattern_summary=excluded.user_pattern_summary,
|
||||||
|
draw_pattern_summary=excluded.draw_pattern_summary,
|
||||||
|
pattern_delta=excluded.pattern_delta
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
data["draw_no"],
|
||||||
|
data.get("curator_avg_match"), data.get("curator_best_tier"),
|
||||||
|
data.get("curator_best_match"), data.get("curator_5plus_prizes"),
|
||||||
|
data.get("user_avg_match"), data.get("user_best_match"),
|
||||||
|
data.get("user_5plus_prizes"),
|
||||||
|
data.get("user_pattern_summary"), data.get("draw_pattern_summary"),
|
||||||
|
data.get("pattern_delta"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return cur.lastrowid
|
||||||
|
|
||||||
|
|
||||||
|
def _review_row(r) -> Optional[Dict[str, Any]]:
|
||||||
|
if not r:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"id": r["id"],
|
||||||
|
"draw_no": r["draw_no"],
|
||||||
|
"curator_avg_match": r["curator_avg_match"],
|
||||||
|
"curator_best_tier": r["curator_best_tier"],
|
||||||
|
"curator_best_match": r["curator_best_match"],
|
||||||
|
"curator_5plus_prizes": r["curator_5plus_prizes"],
|
||||||
|
"user_avg_match": r["user_avg_match"],
|
||||||
|
"user_best_match": r["user_best_match"],
|
||||||
|
"user_5plus_prizes": r["user_5plus_prizes"],
|
||||||
|
"user_pattern_summary": r["user_pattern_summary"],
|
||||||
|
"draw_pattern_summary": r["draw_pattern_summary"],
|
||||||
|
"pattern_delta": r["pattern_delta"],
|
||||||
|
"created_at": r["created_at"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_review(draw_no: int) -> Optional[Dict[str, Any]]:
|
||||||
|
with _conn() as conn:
|
||||||
|
r = conn.execute("SELECT * FROM weekly_review WHERE draw_no=?", (draw_no,)).fetchone()
|
||||||
|
return _review_row(r)
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_review() -> Optional[Dict[str, Any]]:
|
||||||
|
with _conn() as conn:
|
||||||
|
r = conn.execute("SELECT * FROM weekly_review ORDER BY draw_no DESC LIMIT 1").fetchone()
|
||||||
|
return _review_row(r)
|
||||||
|
|
||||||
|
|
||||||
|
def get_reviews_range(start_drw: int, end_drw: int) -> List[Dict[str, Any]]:
|
||||||
|
with _conn() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT * FROM weekly_review WHERE draw_no BETWEEN ? AND ? ORDER BY draw_no ASC",
|
||||||
|
(start_drw, end_drw),
|
||||||
|
).fetchall()
|
||||||
|
return [_review_row(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def list_reviews(limit: int = 10) -> List[Dict[str, Any]]:
|
||||||
|
with _conn() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT * FROM weekly_review ORDER BY draw_no DESC LIMIT ?",
|
||||||
|
(limit,),
|
||||||
|
).fetchall()
|
||||||
|
return [_review_row(r) for r in rows]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user