Loading…
diff --git a/lyra/modes.py b/lyra/modes.py index 0214061..49e4ccb 100644 --- a/lyra/modes.py +++ b/lyra/modes.py @@ -31,9 +31,10 @@ class Mode: tools: tuple[str, ...] # tool names offered in this mode (must exist in tools.TOOLS) -# Read-only poker lookups — safe in any mode, so "how am I running this year?" or -# "what do we have on Round Mike?" works even when we're just talking. -_LOOKUPS = ("player_profile", "get_villain_file", "running_stats") +# Read-only poker lookups — safe in any mode, so "how am I running this year?", +# "what do we have on Round Mike?", or "how'd my last few sessions go?" all work +# even when we're just talking. +_LOOKUPS = ("player_profile", "get_villain_file", "running_stats", "recent_sessions") # Always-available core tools (her own agency: journaling/notes). _BASE = ("journal_write", "note") diff --git a/lyra/poker.py b/lyra/poker.py index 8c2a9a1..d0bea6b 100644 --- a/lyra/poker.py +++ b/lyra/poker.py @@ -205,6 +205,43 @@ def clear_all() -> dict: return counts +def list_sessions(limit: int | None = None, include_review: bool = False) -> list[dict]: + """Past + live sessions (newest first), each with a hand count + recap flag. + Excludes the standing 'Hand Reviews' bucket unless include_review.""" + sql = "SELECT * FROM poker_sessions" + if not include_review: + sql += " WHERE status != 'review'" + sql += " ORDER BY started_at DESC, id DESC" + if limit: + sql += f" LIMIT {int(limit)}" + rows = [dict(r) for r in _c().execute(sql).fetchall()] + for r in rows: + r["hands"] = _c().execute( + "SELECT COUNT(*) n FROM poker_hands WHERE session_id = ?", (r["id"],) + ).fetchone()["n"] + r["has_recap"] = bool(r.get("recap_md")) + return rows + + +def delete_session(session_id: int) -> dict: + """Delete one session and its hands/reads/observations/stack/rituals. Leaves the + persistent villain file (poker_players) intact. Returns rows removed per table.""" + conn = _c() + counts: dict[str, int] = {} + with conn: + for t in ("poker_hands", "player_observations", "player_reads", + "poker_stack_log", "poker_rituals"): + counts[t] = conn.execute( + f"SELECT COUNT(*) n FROM {t} WHERE session_id = ?", (session_id,) + ).fetchone()["n"] + conn.execute(f"DELETE FROM {t} WHERE session_id = ?", (session_id,)) + counts["poker_sessions"] = conn.execute( + "SELECT COUNT(*) n FROM poker_sessions WHERE id = ?", (session_id,) + ).fetchone()["n"] + conn.execute("DELETE FROM poker_sessions WHERE id = ?", (session_id,)) + return counts + + def live_session() -> dict | None: """The current open session, if any.""" r = _c().execute( diff --git a/lyra/tools.py b/lyra/tools.py index 1afb114..8326f39 100644 --- a/lyra/tools.py +++ b/lyra/tools.py @@ -221,6 +221,27 @@ def _session_stats(args: dict, ctx: dict) -> str: f"{st['hands_logged']} hands logged (tags: {tags}).") +def _recent_sessions(args: dict, ctx: dict) -> str: + try: + n = int(args.get("limit") or 8) + except (TypeError, ValueError): + n = 8 + rows = poker.list_sessions(limit=n) + if not rows: + return "No sessions logged yet." + out = [] + for s in rows: + net = s.get("net") + netstr = (f"{net:+.0f}" if net is not None + else "live" if s.get("status") == "live" else "—") + hrs = f", {s['hours']:g}h" if s.get("hours") else "" + recap = " · recap" if s.get("has_recap") else "" + out.append(f"#{s['id']} {(s.get('started_at') or '')[:10]} " + f"{s.get('stakes') or '?'} {s.get('game') or ''} @ {s.get('venue') or '?'} " + f"— net {netstr}{hrs} ({s.get('hands', 0)} hands){recap}") + return "\n".join(out) + + def _running_stats(args: dict, ctx: dict) -> str: rs = poker.running_stats(stakes=args.get("stakes"), venue=args.get("venue"), game=args.get("game"), since=args.get("since")) @@ -432,6 +453,13 @@ TOOLS.update({ "confidence-bank entries so far. Use whenever he asks where he's at, what's in " "the bank, his stack or net, or if gator mode is on — answer from THIS, not memory.", {}, [])}, + "recent_sessions": {"handler": _recent_sessions, "spec": _f( + "recent_sessions", + "List Brian's recent poker sessions — date, stakes, venue, net, hours, hand " + "count. Use when he asks about past sessions, how recent ones went, or to find " + "a session to review. Answer from this, not memory.", + {"limit": {**_N, "description": "How many recent sessions (default 8)"}}, + [])}, "running_stats": {"handler": _running_stats, "spec": _f( "running_stats", "Cumulative results across closed sessions (net, $/hr, by stake). Optionally filter.", diff --git a/lyra/web/server.py b/lyra/web/server.py index 1f41377..444d1fc 100644 --- a/lyra/web/server.py +++ b/lyra/web/server.py @@ -113,6 +113,21 @@ def create_app() -> FastAPI: bundle = await asyncio.to_thread(poker.hud) return bundle or {"session": None} + @app.get("/history") + async def history_page() -> FileResponse: + """Browsable list of past poker sessions.""" + return FileResponse(str(_STATIC / "history.html")) + + @app.get("/history/data") + async def history_data(limit: int = 100, include_review: bool = False) -> dict: + return {"sessions": poker.list_sessions(limit=limit, include_review=include_review)} + + @app.delete("/history/{session_id}") + async def history_delete(session_id: int) -> dict: + removed = await asyncio.to_thread(poker.delete_session, session_id) + logbus.log("info", "poker session deleted", id=session_id, removed=removed) + return {"ok": True, "removed": removed} + @app.post("/v1/chat/completions") async def chat_completions(request: Request) -> dict: body = await request.json() diff --git a/lyra/web/static/history.html b/lyra/web/static/history.html new file mode 100644 index 0000000..167cf0c --- /dev/null +++ b/lyra/web/static/history.html @@ -0,0 +1,104 @@ + + +
+ + + +Loading…