feat: hand-history reconstruction + replayable table viewer
Brian's idea: vomit rough shorthand, Lyra rebuilds it into a structured,
replayable hand history.
- poker.parse_hand(): focused LLM pass turning shorthand into a canonical hand
JSON (positions, stacks, hero cards, chronological actions w/ board reveals,
result); store_hand_history() persists JSON + extracted flat fields;
record_hand() = parse+store; standalone hands attach to a 'Hand Reviews' session
- poker_hands gains a `structured` JSON column (ALTER-migrated for existing DBs)
- record_hand tool wired into chat: "log this hand: ..." -> reconstructed + a
/hand/{id} link
- web: GET /hand/{id} viewer + /hand/{id}/data — a felt table with seats placed
around the oval (hero at bottom), hole cards, progressive board reveal, and
prev/next/end step-through of the action with running pot
- tests: store/get roundtrip, record_hand tool (stubbed parse)
Verified live: parsed a real AKs hand (BTN, 14 actions, full board) end to end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,37 @@ def test_running_stats(lyra):
|
||||
assert "1/3" in rs["by_stake"]
|
||||
|
||||
|
||||
def test_hand_history_store_and_get(lyra):
|
||||
poker = lyra
|
||||
parsed = {"game": "NLH", "stakes": "1/3", "hero_pos": "BTN", "hero_cards": ["As", "Ks"],
|
||||
"players": [{"pos": "BTN", "cards": ["As", "Ks"]}, {"pos": "BB"}],
|
||||
"actions": [{"street": "preflop", "pos": "BTN", "action": "raise", "amount": 12},
|
||||
{"street": "flop", "board": ["As", "7d", "2s"]}],
|
||||
"board": ["As", "7d", "2s"], "result": {"pot": 80, "hero_net": 330, "summary": "won"}}
|
||||
hid = poker.store_hand_history(parsed) # no live session -> attaches to a review session
|
||||
h = poker.get_hand(hid)
|
||||
assert h["position"] == "BTN" and h["hole_cards"] == "As Ks"
|
||||
assert h["result"] == 330
|
||||
assert h["structured"]["actions"][0]["amount"] == 12
|
||||
|
||||
|
||||
def test_record_hand_tool_parses_and_stores(lyra, monkeypatch):
|
||||
import re
|
||||
|
||||
from lyra import llm, tools
|
||||
hand_json = ('{"hero_pos":"CO","hero_cards":["Js","Jd"],'
|
||||
'"players":[{"pos":"CO","cards":["Js","Jd"]},{"pos":"BB","name":"drunk"}],'
|
||||
'"actions":[{"street":"preflop","pos":"CO","action":"raise","amount":45}],'
|
||||
'"board":[],"result":{"hero_net":-300,"summary":"lost to a straight"}}')
|
||||
monkeypatch.setattr(llm, "complete", lambda messages, backend=None, model=None: hand_json)
|
||||
out = tools.dispatch("record_hand", {"shorthand": "JJ in CO, lost to a straight", "stakes": "1/3"})
|
||||
assert "/hand/" in out
|
||||
hid = int(re.search(r"/hand/(\d+)", out).group(1))
|
||||
h = lyra.get_hand(hid)
|
||||
assert h["structured"]["hero_pos"] == "CO"
|
||||
assert h["result"] == -300
|
||||
|
||||
|
||||
def test_poker_tools_dispatch(lyra):
|
||||
from lyra import tools
|
||||
assert "started" in tools.dispatch("start_session", {"stakes": "1/3", "buy_in": 300})
|
||||
|
||||
Reference in New Issue
Block a user