feat: poker copilot — structured session/hand/villain tracking + stats
The real upgrade over the ChatGPT prose-recap workflow: structured data capture via tools Lyra drives during a live session, with stats computed from real data. - lyra/poker.py: domain pack (separate from core memory) — poker_sessions, poker_hands, persistent poker_players (villain file) + player_reads; functions for session lifecycle (start/buyin/end with net+hours), tolerant hand logging, villain upsert/reads, and session/running stats ($/hr, by stake/venue/game) - tools.py: 8 poker tools wired into the chat tool loop (start_session, add_buyin, log_hand, add_read, end_session, session_stats, running_stats, get_villain_file) — partial/terse input tolerated - import/: Brian's real .md session-log format (reference for the phase-2 recap) - tests: lifecycle/net math, partial hand logging, villain upsert, running stats, tool dispatch Verified live: a full talk-through session persisted as structured rows (session +240, AKs hand, seat-5 read) — she drove the tools from natural chat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""Poker domain: structured session/hand/villain storage + stats, and the tools."""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lyra(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db"))
|
||||
from lyra import llm
|
||||
monkeypatch.setattr(llm, "embed", lambda texts: [[0.1, 0.2, 0.3] for _ in texts])
|
||||
import lyra.memory as memory
|
||||
importlib.reload(memory)
|
||||
import lyra.poker as poker
|
||||
importlib.reload(poker) # rebind to the reloaded memory + reset its schema flag
|
||||
return poker
|
||||
|
||||
|
||||
def test_session_lifecycle_and_net(lyra):
|
||||
poker = lyra
|
||||
sid = poker.start_session(venue="Meadows", stakes="1/3", buy_in=400)
|
||||
assert poker.live_session()["id"] == sid
|
||||
poker.add_buyin(500) # rebuy -> total 900
|
||||
s = poker.end_session(cash_out=627)
|
||||
assert s["buy_in_total"] == 900
|
||||
assert s["net"] == pytest.approx(-273)
|
||||
assert s["status"] == "closed"
|
||||
assert poker.live_session() is None # closed -> no live session
|
||||
|
||||
|
||||
def test_log_hand_partial_fields(lyra):
|
||||
poker = lyra
|
||||
poker.start_session(stakes="1/3", buy_in=300)
|
||||
hid = poker.log_hand(position="BTN", hole_cards="AKs", result=120, tag="confidence")
|
||||
hands = poker.list_hands()
|
||||
assert len(hands) == 1 and hands[0]["id"] == hid
|
||||
assert hands[0]["hole_cards"] == "AKs" and hands[0]["result"] == 120
|
||||
assert hands[0]["board"] is None # unspecified fields stay null
|
||||
|
||||
|
||||
def test_villain_file_upsert_and_read(lyra):
|
||||
poker = lyra
|
||||
poker.start_session(venue="Meadows", stakes="1/3", buy_in=300)
|
||||
poker.add_read("limp-called K4s UTG", name="Sleepy John", seat="3",
|
||||
tendencies="loose-passive, jackpot dreamer", category="feeder", venue="Meadows")
|
||||
# update the same player
|
||||
poker.add_read("cold-called a 3-bet with A2o", name="sleepy john")
|
||||
file = poker.get_villain_file(name="Sleepy")
|
||||
assert len(file) == 1 # matched by name, not duplicated
|
||||
assert file[0]["category"] == "feeder"
|
||||
|
||||
|
||||
def test_running_stats(lyra):
|
||||
poker = lyra
|
||||
s1 = poker.start_session(stakes="1/3", buy_in=300)
|
||||
poker.end_session(540, session_id=s1)
|
||||
s2 = poker.start_session(stakes="1/3", buy_in=400)
|
||||
poker.end_session(300, session_id=s2)
|
||||
rs = poker.running_stats(stakes="1/3")
|
||||
assert rs["sessions"] == 2
|
||||
assert rs["net"] == pytest.approx(140) # +240 then -100
|
||||
assert "1/3" in rs["by_stake"]
|
||||
|
||||
|
||||
def test_poker_tools_dispatch(lyra):
|
||||
from lyra import tools
|
||||
assert "started" in tools.dispatch("start_session", {"stakes": "1/3", "buy_in": 300})
|
||||
assert "logged" in tools.dispatch("log_hand", {"position": "CO", "hole_cards": "QQ"})
|
||||
assert "closed" in tools.dispatch("end_session", {"cash_out": 500})
|
||||
# the poker tools are offered to the model
|
||||
names = {s["function"]["name"] for s in tools.specs()}
|
||||
assert {"start_session", "log_hand", "end_session", "running_stats", "get_villain_file"} <= names
|
||||
Reference in New Issue
Block a user