e482ad591c
Probed the heuristic against live-style messages and fixed 6 real gaps:
- action verbs missed -ing forms ("TAG's been limping") → no READ
- the descriptor-subject regex required words between "the" and the noun, so
"the whale called" missed → now handles bare "the <noun>"
- player departures ("TAG busted", "TAG left") weren't TABLE → added _DEPART
(gated to non-first-person so "I busted, heading home" isn't a roster op)
- the bare word "stack" made strategy questions ("should I stack off?") classify
as LOG → LOG now needs a number/result word AND excludes questions
- thin MENTAL lexicon → added coolered/sick/brutal/run bad/etc.
21-case probe (13 former misses + 8 regression guards) all green; folded into the
suite as 5 hardening tests. Full suite 201 green. classify() stays the swappable
seam for an LLM/MI50 upgrade later.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
"""Poker-mode message classifier + fragment selection (pure, no DB)."""
|
|
from __future__ import annotations
|
|
|
|
from lyra import poker_prompts as pp
|
|
|
|
|
|
def c(msg, roster=()):
|
|
return pp.classify(msg, roster)
|
|
|
|
|
|
# --- the spec's canonical cases ---
|
|
|
|
def test_read_villain_action_beats_hand():
|
|
# A villain's action carries cards+position+verb but is NOT Brian's hand.
|
|
assert c("TAG limped A4o in the SB (UTG straddled)") == "READ"
|
|
assert c("Jonathan called the 3bet") == "READ"
|
|
assert c("the neck-tattoo guy shoved the turn") == "READ"
|
|
|
|
|
|
def test_hand_is_first_person():
|
|
assert c("Button straddle on. I limp UTG with 22. Flop 2d7cjh, I check-raise") == "HAND"
|
|
assert c("I flopped a set with 99 on 9h4c2d and bet the turn") == "HAND"
|
|
|
|
|
|
def test_hand_narrated_without_I_still_hand_not_read():
|
|
# No "I", but leads with a poker verb (not a name) + street/action → his hand.
|
|
assert c("Flopped bottom set with 22, bet $40 on the river, he folded 88") == "HAND"
|
|
|
|
|
|
def test_table_ops():
|
|
assert c("seat the table: TAG, Jonathan, Wheelz") == "TABLE"
|
|
assert c("table broke, I'm at a new table") == "TABLE"
|
|
assert c("I got moved to another table") == "TABLE"
|
|
|
|
|
|
def test_mental():
|
|
assert c("I feel like I'm being mean when I raise") == "MENTAL"
|
|
assert c("ugh I'm so tilted, card dead all night") == "MENTAL"
|
|
|
|
|
|
def test_status_is_not_a_mood():
|
|
assert c("it's 11:50pm, waiting for a seat") == "STATUS"
|
|
assert c("grabbing food, be right back") == "STATUS"
|
|
|
|
|
|
def test_log_bare_money():
|
|
assert c("I'm at 317 now") == "LOG"
|
|
assert c("stack is 540") == "LOG"
|
|
|
|
|
|
def test_chat_default():
|
|
assert c("should I have folded the river?") == "CHAT"
|
|
assert c("what do you think of this table so far") == "CHAT"
|
|
|
|
|
|
# --- the READ vs HAND boundary (the hard one) ---
|
|
|
|
def test_roster_handle_forces_read():
|
|
# A seated handle as the actor → READ even if lowercase / plain.
|
|
assert c("tag opened to 15 from the cutoff", roster=("TAG",)) == "READ"
|
|
|
|
|
|
def test_first_person_action_stays_hand_even_with_roster():
|
|
# Brian is the actor → HAND, not a read on a seated player mentioned nearby.
|
|
assert c("I 3bet TAG's open with AKs", roster=("TAG",)) == "HAND"
|
|
|
|
|
|
def test_all_caps_handle_reads_without_roster():
|
|
assert c("JD min-raised the button") == "READ"
|
|
|
|
|
|
# --- fragment selection ---
|
|
|
|
def test_fragment_for_maps_each_type():
|
|
for t in pp.MSG_TYPES:
|
|
assert pp.fragment_for(t) is pp.FRAGMENTS[t]
|
|
assert pp.fragment_for(None) is pp.FRAGMENTS["CHAT"]
|
|
assert pp.fragment_for("bogus") is pp.FRAGMENTS["CHAT"]
|
|
|
|
|
|
def test_base_is_nonempty_and_names_the_hard_rules():
|
|
assert "LOG FIRST" in pp.BASE
|
|
assert "descriptor" in pp.BASE and "session_state" in pp.BASE
|
|
|
|
|
|
# --- hardening: real-world phrasings that used to miss ---
|
|
|
|
def test_hardening_reads_ing_and_bare_descriptor():
|
|
assert c("TAG's been limping every pot", roster=("TAG",)) == "READ" # -ing form
|
|
assert c("the whale called again") == "READ" # bare "the <noun>"
|
|
assert c("saw JD open utg") == "READ"
|
|
|
|
|
|
def test_hardening_player_departures_are_table():
|
|
assert c("TAG busted") == "TABLE"
|
|
assert c("TAG left the table") == "TABLE"
|
|
assert c("new guy just sat down") == "TABLE"
|
|
|
|
|
|
def test_hardening_questions_never_log():
|
|
# "stack" appears but it's a strategy question, not a stack update.
|
|
assert c("should I stack off top set on that board?") == "CHAT"
|
|
assert c("was I good to call there with AK?") == "CHAT"
|
|
|
|
|
|
def test_hardening_mental_lexicon():
|
|
assert c("im getting coolered every hand, so sick of this") == "MENTAL"
|
|
assert c("this is brutal, run so bad") == "MENTAL"
|
|
|
|
|
|
def test_hardening_log_needs_number_or_result_word():
|
|
assert c("down to 220") == "LOG"
|
|
assert c("sitting on 450 now") == "LOG"
|
|
assert c("rebought for 300") == "LOG"
|
|
# first-person departure is Brian, not a roster op → not TABLE
|
|
assert c("I busted, heading home") != "TABLE"
|