Loading…
diff --git a/lyra/poker.py b/lyra/poker.py index dd345a8..917e575 100644 --- a/lyra/poker.py +++ b/lyra/poker.py @@ -1523,6 +1523,19 @@ def get_villain_file(name: str | None = None, venue: str | None = None) -> list[ return [dict(r) for r in _c().execute(sql, params).fetchall()] +def players_overview() -> list[dict]: + """All villains for the browser: identity + observation count + last seen, + named players first, then most-recently-updated.""" + rows = _c().execute( + "SELECT p.id, p.name, p.named, p.venue, p.category, p.descriptors, " + "p.tendencies, p.adjustment, p.updated_at, " + "(SELECT COUNT(*) FROM player_observations o WHERE o.player_id = p.id) AS obs, " + "(SELECT COUNT(*) FROM player_reads r WHERE r.player_id = p.id) AS reads " + "FROM poker_players p ORDER BY p.named DESC, p.updated_at DESC" + ).fetchall() + return [dict(r) for r in rows] + + def villain_recall(player_id: int) -> dict | None: """Episodic recall for one villain: who, how often/where seen, last seen, the notable hands against him (with ids to link), reads, and stats. This is the diff --git a/lyra/web/server.py b/lyra/web/server.py index 7af398b..d3d892c 100644 --- a/lyra/web/server.py +++ b/lyra/web/server.py @@ -436,6 +436,45 @@ def create_app() -> FastAPI: async def hands_data(limit: int = 60) -> dict: return {"hands": poker.list_recent_hands(limit=limit)} + @app.get("/players") + async def players_page() -> FileResponse: + """Villain file browser + the identity-resolution review queue.""" + return FileResponse(str(_STATIC / "players.html")) + + @app.get("/players/data") + async def players_data() -> dict: + return {"players": poker.players_overview(), + "queue": poker.list_identity_queue()} + + @app.get("/player/{player_id}/data") + async def player_data(player_id: int) -> dict: + return poker.villain_recall(player_id) or {} + + @app.post("/player/{player_id}") + async def player_update(player_id: int, request: Request) -> dict: + body = await request.json() + fields = {k: body[k] for k in ("name", "venue", "category", "tendencies", + "adjustment", "description") if body.get(k) is not None} + if body.get("name"): # naming via the browser flips it to a real handle + poker.name_villain(player_id, body["name"]) + fields.pop("name", None) + row = poker.update_player(player_id, **fields) if fields else None + return {"ok": True, "player": row} + + @app.post("/identity/{task_id}/resolve") + async def identity_resolve(task_id: int, request: Request) -> dict: + body = await request.json() + action = body.get("action") or "dismiss" + kw = {k: v for k, v in body.items() if k != "action"} + ok = await asyncio.to_thread(poker.resolve_identity_task, task_id, action, **kw) + logbus.log("info", "identity task resolved", id=task_id, action=action) + return {"ok": ok} + + @app.post("/players/scan") + async def players_scan() -> dict: + filed = await asyncio.to_thread(poker.scan_merge_candidates) + return {"ok": True, "filed": filed} + @app.get("/recap/{session_id}") async def recap_page() -> FileResponse: return FileResponse(str(_STATIC / "recap.html")) diff --git a/lyra/web/static/nav.js b/lyra/web/static/nav.js index 349b7e9..282836a 100644 --- a/lyra/web/static/nav.js +++ b/lyra/web/static/nav.js @@ -8,6 +8,7 @@ { href: "/session", icon: "♠", label: "Session" }, { href: "/history", icon: "📚", label: "History" }, { href: "/hands", icon: "🃏", label: "Hands" }, + { href: "/players", icon: "👤", label: "Players" }, { href: "/self", icon: "🧠", label: "Mind" }, { href: "/thoughts", icon: "💭", label: "Thoughts" }, { href: "/journal", icon: "📔", label: "Journal" }, diff --git a/lyra/web/static/players.html b/lyra/web/static/players.html new file mode 100644 index 0000000..213b070 --- /dev/null +++ b/lyra/web/static/players.html @@ -0,0 +1,166 @@ + + +
+ + + +Loading…