feat(poker): auto-fill hero's stack from the last stack log
When a hand doesn't state hero's stack, default it to current_stack() (his last logged stack) — the system already knows it from the stack log even when he doesn't restate it every hand. record_hand._fill_hero_stack sets the hero player's stack and marks stack_inferred=True (honest about stated vs inferred); a stack given in the hand text always wins, and observed hands get nothing. 3 tests; suite 226 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-1
@@ -931,15 +931,42 @@ def _recent_duplicate_hand(parsed: dict, session_id: int | None, window_sec: int
|
|||||||
return int(row["id"]) if row else None
|
return int(row["id"]) if row else None
|
||||||
|
|
||||||
|
|
||||||
|
def _fill_hero_stack(parsed: dict, session_id: int | None) -> dict:
|
||||||
|
"""Default hero's starting stack to the last logged stack (current_stack) when the hand
|
||||||
|
didn't state one — the system already knows his stack from the stack log even when he
|
||||||
|
doesn't restate it every hand. Only fills a genuinely missing value; a stack he gave in
|
||||||
|
the hand text always wins. Marks the hero player stack_inferred so it's honest about it."""
|
||||||
|
if not isinstance(parsed, dict) or parsed.get("hero_involved", True) is False:
|
||||||
|
return parsed
|
||||||
|
hero_pos = parsed.get("hero_pos")
|
||||||
|
if not hero_pos:
|
||||||
|
return parsed
|
||||||
|
players = parsed.setdefault("players", [])
|
||||||
|
hero = next((pl for pl in players if pl.get("hero") or pl.get("pos") == hero_pos), None)
|
||||||
|
if hero and hero.get("stack") not in (None, 0):
|
||||||
|
return parsed # he stated a stack — never override it
|
||||||
|
stack = current_stack(session_id)
|
||||||
|
if stack is None:
|
||||||
|
return parsed # nothing logged yet to borrow
|
||||||
|
if hero is None:
|
||||||
|
hero = {"pos": hero_pos}
|
||||||
|
players.append(hero)
|
||||||
|
hero["stack"] = stack
|
||||||
|
hero["stack_inferred"] = True
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
def record_hand(shorthand: str, session_id: int | None = None, stakes: str | None = None,
|
def record_hand(shorthand: str, session_id: int | None = None, stakes: str | None = None,
|
||||||
tag: str | None = None, lesson: str | None = None,
|
tag: str | None = None, lesson: str | None = None,
|
||||||
backend: str | None = None) -> dict:
|
backend: str | None = None) -> dict:
|
||||||
"""Parse shorthand -> structured hand -> store. Returns {id, parsed} (id None on parse fail).
|
"""Parse shorthand -> structured hand -> store. Returns {id, parsed} (id None on parse fail).
|
||||||
Idempotent: if this exact hand was just logged for the session (double turn execution),
|
Idempotent: if this exact hand was just logged for the session (double turn execution),
|
||||||
returns the existing one instead of inserting a duplicate."""
|
returns the existing one instead of inserting a duplicate. Hero's stack is auto-filled
|
||||||
|
from the last stack log when he didn't restate it."""
|
||||||
parsed = parse_hand(shorthand, stakes=stakes, backend=backend)
|
parsed = parse_hand(shorthand, stakes=stakes, backend=backend)
|
||||||
if not parsed:
|
if not parsed:
|
||||||
return {"id": None, "parsed": None}
|
return {"id": None, "parsed": None}
|
||||||
|
parsed = _fill_hero_stack(parsed, session_id)
|
||||||
dup = _recent_duplicate_hand(parsed, session_id)
|
dup = _recent_duplicate_hand(parsed, session_id)
|
||||||
if dup is not None:
|
if dup is not None:
|
||||||
return {"id": dup, "parsed": parsed, "linked": 0, "deduped": True}
|
return {"id": dup, "parsed": parsed, "linked": 0, "deduped": True}
|
||||||
|
|||||||
@@ -65,3 +65,45 @@ def test_parse_prompt_records_straddles():
|
|||||||
p = pk._HAND_PARSE_PROMPT.lower()
|
p = pk._HAND_PARSE_PROMPT.lower()
|
||||||
assert "straddle" in p and "button straddle" in p
|
assert "straddle" in p and "button straddle" in p
|
||||||
assert "acts last preflop" in p or "act last preflop" in p
|
assert "acts last preflop" in p or "act last preflop" in p
|
||||||
|
|
||||||
|
|
||||||
|
# --- hero stack auto-fill from the last logged stack ----------------------
|
||||||
|
|
||||||
|
def test_hero_stack_filled_from_last_stack_log(poker, monkeypatch):
|
||||||
|
sid = poker.start_session(venue="Meadows", stakes="1/3", buy_in=400)
|
||||||
|
poker.log_stack(275) # his last reported stack
|
||||||
|
monkeypatch.setattr(poker, "parse_hand",
|
||||||
|
lambda *a, **k: {"game": "NLH", "hero_involved": True,
|
||||||
|
"hero_pos": "CO", "hero_cards": ["As", "Ks"],
|
||||||
|
"board": ["2c"], "players": [], "actions": [],
|
||||||
|
"result": {"hero_net": 50}})
|
||||||
|
out = poker.record_hand("AKs in the CO, i raise, flop 2c...")
|
||||||
|
stored = poker.get_hand(out["id"])["structured"]
|
||||||
|
hero = next(pl for pl in stored["players"] if pl.get("hero"))
|
||||||
|
assert hero["stack"] == 275 and hero.get("stack_inferred") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_stated_stack_is_never_overridden(poker, monkeypatch):
|
||||||
|
sid = poker.start_session(venue="Meadows", buy_in=400)
|
||||||
|
poker.log_stack(275)
|
||||||
|
monkeypatch.setattr(poker, "parse_hand",
|
||||||
|
lambda *a, **k: {"game": "NLH", "hero_involved": True,
|
||||||
|
"hero_pos": "BTN", "hero_cards": ["Qh", "Qd"],
|
||||||
|
"players": [{"pos": "BTN", "stack": 500}],
|
||||||
|
"board": [], "actions": [], "result": {}})
|
||||||
|
out = poker.record_hand("500 deep on the btn with QQ")
|
||||||
|
hero = next(pl for pl in poker.get_hand(out["id"])["structured"]["players"]
|
||||||
|
if pl.get("pos") == "BTN")
|
||||||
|
assert hero["stack"] == 500 and not hero.get("stack_inferred")
|
||||||
|
|
||||||
|
|
||||||
|
def test_observed_hand_gets_no_hero_stack(poker, monkeypatch):
|
||||||
|
poker.start_session(venue="Meadows", buy_in=400)
|
||||||
|
poker.log_stack(275)
|
||||||
|
monkeypatch.setattr(poker, "parse_hand",
|
||||||
|
lambda *a, **k: {"game": "NLH", "hero_involved": False,
|
||||||
|
"hero_pos": None, "hero_cards": [],
|
||||||
|
"players": [{"pos": "CO", "cards": ["Kx", "Kx"]}],
|
||||||
|
"board": [], "actions": [], "result": {}})
|
||||||
|
out = poker.record_hand("the CO stacked off KK vs the nit")
|
||||||
|
assert all(not pl.get("stack_inferred") for pl in poker.get_hand(out["id"])["structured"]["players"])
|
||||||
|
|||||||
Reference in New Issue
Block a user