Update main. #6
+21
-2
@@ -662,8 +662,9 @@ Schema:
|
|||||||
{
|
{
|
||||||
"game": "NLH" | "PLO" | ...,
|
"game": "NLH" | "PLO" | ...,
|
||||||
"stakes": "<e.g. 1/3, or null>",
|
"stakes": "<e.g. 1/3, or null>",
|
||||||
"hero_pos": "<UTG|UTG1|MP|LJ|HJ|CO|BTN|SB|BB, hero's position>",
|
"hero_involved": true | false, // is the narrator actually IN this hand? (see HERO rule)
|
||||||
"hero_cards": ["As","Ax", ...], // rank+suit (s/h/d/c); 'x' suit if unknown e.g. "Ax"; "x" for a fully unknown card
|
"hero_pos": "<UTG|UTG1|MP|LJ|HJ|CO|BTN|SB|BB, hero's position — null if hero not involved>",
|
||||||
|
"hero_cards": ["As","Ax", ...], // hero's cards; [] / null if hero not involved. rank+suit (s/h/d/c); 'x' suit if unknown e.g. "Ax"; "x" for a fully unknown card
|
||||||
"players": [ // every player mentioned, incl. hero
|
"players": [ // every player mentioned, incl. hero
|
||||||
{"pos": "<position>", "stack": <number|null>, "name": <string|null>, "cards": [".."]|null}
|
{"pos": "<position>", "stack": <number|null>, "name": <string|null>, "cards": [".."]|null}
|
||||||
],
|
],
|
||||||
@@ -676,6 +677,17 @@ Schema:
|
|||||||
"result": {"pot": <number|null>, "hero_net": <number|null>, "summary": "<one line>"}
|
"result": {"pot": <number|null>, "hero_net": <number|null>, "summary": "<one line>"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HERO — who the hand belongs to. The narrator writes as "I/me/my". Set \
|
||||||
|
hero_involved=true and fill hero_pos + hero_cards ONLY if he was actually dealt in and \
|
||||||
|
playing THIS hand. If he's describing a hand he WATCHED between other players — he wasn't \
|
||||||
|
in it (e.g. "two guys got it in", "the neck-tattoo reg stacked the whale", it's all names/\
|
||||||
|
seats with no I/me/my holding cards) — set hero_involved=false, hero_pos=null, hero_cards=null, \
|
||||||
|
and just record those players in players[] and their action. NEVER attribute another player's \
|
||||||
|
cards, position, or result to the hero. When hero isn't involved, result.hero_net=null (a pot \
|
||||||
|
he wasn't in didn't win or lose him anything). Example: "the lag in the CO stacked off KK vs \
|
||||||
|
the nit's AA" -> hero_involved=false, hero_pos=null, hero_cards=null, players=[{pos:CO,cards:\
|
||||||
|
["Kx","Kx"]},{pos:?,cards:["Ax","Ax"]}].
|
||||||
|
|
||||||
Rules: infer positions and street order sensibly. Amounts are plain numbers (no $). \
|
Rules: infer positions and street order sensibly. Amounts are plain numbers (no $). \
|
||||||
NEVER invent suits or cards. A card is rank+suit where suit is one of s/h/d/c; if the suit \
|
NEVER invent suits or cards. A card is rank+suit where suit is one of s/h/d/c; if the suit \
|
||||||
wasn't stated, use 'x' for the suit (e.g. "Ax","Kx","4x"); if a whole card wasn't stated, \
|
wasn't stated, use 'x' for the suit (e.g. "Ax","Kx","4x"); if a whole card wasn't stated, \
|
||||||
@@ -792,6 +804,13 @@ def normalize_structured(parsed: dict) -> dict:
|
|||||||
return parsed
|
return parsed
|
||||||
p = dict(parsed)
|
p = dict(parsed)
|
||||||
p["schema_version"] = HAND_SCHEMA_VERSION
|
p["schema_version"] = HAND_SCHEMA_VERSION
|
||||||
|
# Observed hand (Brian watched it, wasn't in it): never pin cards/position/result
|
||||||
|
# to the hero. Explicit false is the signal; a null hero_pos is treated the same.
|
||||||
|
if p.get("hero_involved") is False:
|
||||||
|
p["hero_pos"] = None
|
||||||
|
p["hero_cards"] = []
|
||||||
|
if isinstance(p.get("result"), dict):
|
||||||
|
p["result"] = {**p["result"], "hero_net": None}
|
||||||
p["hero_cards"] = [_norm_card(c) for c in (p.get("hero_cards") or [])]
|
p["hero_cards"] = [_norm_card(c) for c in (p.get("hero_cards") or [])]
|
||||||
p["board"] = [_norm_card(c) for c in (p.get("board") or [])]
|
p["board"] = [_norm_card(c) for c in (p.get("board") or [])]
|
||||||
|
|
||||||
|
|||||||
+9
-1
@@ -425,8 +425,16 @@ def _record_hand(args: dict, ctx: dict) -> str:
|
|||||||
if not out["id"]:
|
if not out["id"]:
|
||||||
return "I couldn't parse that hand — give it to me again with a little more detail?"
|
return "I couldn't parse that hand — give it to me again with a little more detail?"
|
||||||
p = out["parsed"]
|
p = out["parsed"]
|
||||||
|
hero_in = p.get("hero_involved") is not False and bool(p.get("hero_pos"))
|
||||||
|
logbus.log("info", "hand reconstructed", id=out["id"], hero=p.get("hero_pos"),
|
||||||
|
hero_involved=hero_in)
|
||||||
|
if not hero_in:
|
||||||
|
# A hand Brian watched between other players — not his.
|
||||||
|
who = ", ".join(pl.get("name") or pl.get("pos") or "?"
|
||||||
|
for pl in (p.get("players") or [])[:3]) or "the table"
|
||||||
|
return (f"Logged hand #{out['id']} — an observed hand ({who}), not yours. "
|
||||||
|
f"View it at /hand/{out['id']}")
|
||||||
cards = " ".join(p.get("hero_cards") or [])
|
cards = " ".join(p.get("hero_cards") or [])
|
||||||
logbus.log("info", "hand reconstructed", id=out["id"], hero=p.get("hero_pos"))
|
|
||||||
return (f"Hand #{out['id']} reconstructed — {p.get('hero_pos') or '?'} "
|
return (f"Hand #{out['id']} reconstructed — {p.get('hero_pos') or '?'} "
|
||||||
f"{cards}. View/replay it at /hand/{out['id']}")
|
f"{cards}. View/replay it at /hand/{out['id']}")
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,29 @@ def test_stamps_version(poker):
|
|||||||
assert out["schema_version"] == poker.HAND_SCHEMA_VERSION
|
assert out["schema_version"] == poker.HAND_SCHEMA_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
def test_observed_hand_never_attributed_to_hero(poker):
|
||||||
|
# Brian narrated a hand between two other players — hero_involved=false.
|
||||||
|
out = poker.normalize_structured({
|
||||||
|
"hero_involved": False,
|
||||||
|
"hero_pos": "CO", "hero_cards": ["Kx", "Kx"], # model slipped these in
|
||||||
|
"players": [{"pos": "CO", "cards": ["Kx", "Kx"]}, {"pos": "BB", "cards": ["Ax", "Ax"]}],
|
||||||
|
"result": {"pot": 600, "hero_net": 300},
|
||||||
|
})
|
||||||
|
assert out["hero_pos"] is None # not pinned to Brian
|
||||||
|
assert out["hero_cards"] == []
|
||||||
|
assert out["result"]["hero_net"] is None # a pot he wasn't in
|
||||||
|
assert not any(pl.get("hero") for pl in out["players"]) # nobody flagged hero
|
||||||
|
|
||||||
|
|
||||||
|
def test_hero_hand_still_attributed(poker):
|
||||||
|
out = poker.normalize_structured({
|
||||||
|
"hero_involved": True, "hero_pos": "BTN", "hero_cards": ["As", "Ks"],
|
||||||
|
"players": [{"pos": "BTN"}]})
|
||||||
|
assert out["hero_pos"] == "BTN"
|
||||||
|
hero = next(pl for pl in out["players"] if pl.get("pos") == "BTN")
|
||||||
|
assert hero.get("hero") and hero["cards"] == ["As", "Ks"]
|
||||||
|
|
||||||
|
|
||||||
def test_card_normalization(poker):
|
def test_card_normalization(poker):
|
||||||
out = poker.normalize_structured(_full_hand())
|
out = poker.normalize_structured(_full_hand())
|
||||||
assert out["hero_cards"] == ["Ah", "Kh"] # lowercased input -> canonical
|
assert out["hero_cards"] == ["Ah", "Kh"] # lowercased input -> canonical
|
||||||
|
|||||||
Reference in New Issue
Block a user