From 8ad4bc4ce00b9fe33134b26225ea9cc83f744b69 Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 29 Jun 2026 00:01:16 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20hands=20API=20=E2=80=94=20log=5Fhand=20?= =?UTF-8?q?endpoint,=20update=5Fhand=20store=20fn,=20edit/delete=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01G796GsLCvJQKVN7hwV2cDx --- lyra/poker.py | 16 ++++++++++++++++ lyra/web/server.py | 25 +++++++++++++++++++++++++ tests/test_poker_api.py | 14 ++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/lyra/poker.py b/lyra/poker.py index f4f83d8..f06428a 100644 --- a/lyra/poker.py +++ b/lyra/poker.py @@ -558,6 +558,22 @@ def log_hand(session_id: int | None = None, **fields) -> int: return int(cur.lastrowid) +def update_hand(hand_id: int, **fields) -> dict | None: + """Edit a logged hand's flat fields (fix a mislabeled board, result, villain). + Only known columns are touched. Returns the updated hand row or None.""" + sets, vals = [], [] + for k, v in fields.items(): + if k in _HAND_FIELDS and v is not None: + sets.append(f"{k} = ?") + vals.append(v) + if sets: + conn = _c() + with conn: + conn.execute(f"UPDATE poker_hands SET {', '.join(sets)} WHERE id = ?", + (*vals, hand_id)) + return get_hand(hand_id) + + def list_hands(session_id: int | None = None) -> list[dict]: sid = _resolve(session_id) if sid is None: diff --git a/lyra/web/server.py b/lyra/web/server.py index 0d024b3..931b9f2 100644 --- a/lyra/web/server.py +++ b/lyra/web/server.py @@ -164,6 +164,31 @@ def create_app() -> FastAPI: logbus.log("info", "poker session started (direct)", id=sid) return {"ok": True, "id": sid} + @app.post("/session/hand") + async def session_log_hand(request: Request) -> dict: + """Log a hand directly with flat fields (no LLM parse).""" + body = await request.json() + try: + hid = await asyncio.to_thread(lambda: poker.log_hand(**body)) + except ValueError as exc: + return {"ok": False, "error": str(exc)} + logbus.log("info", "hand logged (direct)", id=hid) + return {"ok": True, "id": hid} + + @app.patch("/hand/{hand_id}") + async def hand_update(hand_id: int, request: Request) -> dict: + """Edit a logged hand's flat fields.""" + body = await request.json() + h = await asyncio.to_thread(lambda: poker.update_hand(hand_id, **body)) + logbus.log("info", "hand edited", id=hand_id, fields=list(body)) + return {"ok": h is not None, "hand": h} + + @app.delete("/hand/{hand_id}") + async def hand_delete(hand_id: int) -> dict: + """Delete a logged hand.""" + ok = await asyncio.to_thread(poker.delete_entry, "hand", hand_id) + return {"ok": ok} + @app.delete("/session/entry/{kind}/{entry_id}") async def delete_entry(kind: str, entry_id: int) -> dict: """Delete one HUD entry (hand | stack | read | ritual) by id.""" diff --git a/tests/test_poker_api.py b/tests/test_poker_api.py index 2fc3adc..34d49d3 100644 --- a/tests/test_poker_api.py +++ b/tests/test_poker_api.py @@ -50,3 +50,17 @@ def test_post_session_starts_live(client): r = c.post("/session", json={"venue": "Wheeling", "stakes": "1/3", "buy_in": 400}) sid = r.json()["id"] assert poker.live_session()["id"] == sid + + +def test_post_hand_edit_and_delete(client): + c, poker = client + poker.start_session(buy_in=400) + r = c.post("/session/hand", json={"position": "BTN", "hole_cards": "22", "result": 120}) + assert r.json()["ok"] is True + hid = r.json()["id"] + r2 = c.patch(f"/hand/{hid}", json={"hole_cards": "2c2d"}) + assert r2.json()["ok"] is True + assert r2.json()["hand"]["hole_cards"] == "2c2d" + r3 = c.delete(f"/hand/{hid}") + assert r3.json()["ok"] is True + assert poker.get_hand(hid) is None