41c8a4dd1d
Two issues from live testing:
- Double-logged hand. The chat turn can execute TWICE — the SSE stream and the
blocking fallback both run server-side (two 'chat request' lines, 1s apart) — a
pre-existing double-execution (it also duplicated user messages) that the new
logging guarantee turned into duplicate HANDS. record_hand is now idempotent:
_recent_duplicate_hand returns an identical hand (same session, hole cards, board;
NULL-safe) recorded in the last few minutes, so the second run reuses it instead
of inserting. A system-of-record records an event once.
- Button straddle dropped. The parse prompt had no straddle logic. Added a STRADDLES
rule: record any straddle as a preflop `post` by the straddler with its amount and
respect the action order (button straddle acts last preflop, action opens in the
SB; UTG straddle opens to its left). Verified: a btn-straddle hand now parses the
straddle as {pos: BTN, action: post, amount: 6}.
Note: the underlying double turn-execution (stream + fallback) is a separate web-layer
bug worth fixing at the source — it wastes a full LLM turn and still double-persists
chat messages. Filed for a follow-up. 6 tests; suite 223 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
2.8 KiB
Python
68 lines
2.8 KiB
Python
"""record_hand idempotency + straddle parse coverage.
|
|
|
|
The chat turn can execute twice — the SSE stream and the blocking fallback both run
|
|
server-side (two 'chat request' lines, 1s apart) — which double-logged the same hand
|
|
once logging became guaranteed. A system-of-record must record an event once."""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def poker(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)
|
|
return poker
|
|
|
|
|
|
_PARSED = {
|
|
"game": "NLH", "hero_pos": "SB", "hero_cards": ["Ah", "Kh"],
|
|
"board": ["Kd", "9d", "4c", "2s"], "players": [], "actions": [],
|
|
"result": {"hero_net": -200, "pot": 400},
|
|
}
|
|
|
|
|
|
def test_record_hand_is_idempotent_across_double_execution(poker, monkeypatch):
|
|
sid = poker.start_session(venue="Borgata", stakes="1/3", buy_in=400)
|
|
monkeypatch.setattr(poker, "parse_hand", lambda *a, **k: dict(_PARSED))
|
|
first = poker.record_hand("i have AhKh in the SB, btn straddle, ...")
|
|
second = poker.record_hand("i have AhKh in the SB, btn straddle, ...") # the duplicate turn
|
|
assert first["id"] == second["id"]
|
|
assert second.get("deduped") is True
|
|
assert len(poker.list_hands(sid)) == 1 # ledger holds ONE, not two
|
|
|
|
|
|
def test_record_hand_does_not_dedupe_a_genuinely_different_hand(poker, monkeypatch):
|
|
sid = poker.start_session(venue="Borgata", stakes="1/3", buy_in=400)
|
|
monkeypatch.setattr(poker, "parse_hand", lambda *a, **k: dict(_PARSED))
|
|
poker.record_hand("hand one")
|
|
other = dict(_PARSED, hero_cards=["Qs", "Qd"], board=["Qh", "7c", "2s"])
|
|
monkeypatch.setattr(poker, "parse_hand", lambda *a, **k: dict(other))
|
|
poker.record_hand("a different hand entirely")
|
|
assert len(poker.list_hands(sid)) == 2 # distinct hands both land
|
|
|
|
|
|
def test_dedupe_handles_boardless_hand(poker, monkeypatch):
|
|
# NULL-safe match: a preflop-only hand (no board) still dedupes.
|
|
sid = poker.start_session(venue="Borgata", buy_in=400)
|
|
preflop = {"game": "NLH", "hero_pos": "BTN", "hero_cards": ["As", "Ks"],
|
|
"board": [], "players": [], "actions": [], "result": {"hero_net": 30}}
|
|
monkeypatch.setattr(poker, "parse_hand", lambda *a, **k: dict(preflop))
|
|
a = poker.record_hand("AKs btn, i open everyone folds")
|
|
b = poker.record_hand("AKs btn, i open everyone folds")
|
|
assert a["id"] == b["id"] and len(poker.list_hands(sid)) == 1
|
|
|
|
|
|
def test_parse_prompt_records_straddles():
|
|
from lyra import poker as pk
|
|
p = pk._HAND_PARSE_PROMPT.lower()
|
|
assert "straddle" in p and "button straddle" in p
|
|
assert "acts last preflop" in p or "act last preflop" in p
|