"""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 " 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" # --- HAND fragment: route showdowns to the tool + no motivational mush --- def test_hand_fragment_routes_showdowns_to_the_tool(): # A resolved showdown must be verified via analyze_spot, not eyeballed # (the quad-kings-read-as-"kings-full" regression). frag = pp.fragment_for("HAND") assert "SHOWDOWN" in frag assert "analyze_spot" in frag assert "never eyeball" in frag.lower() # names the hand class by street so "flopped quads" actually gets said assert "street it mattered" in frag def test_hand_fragment_bans_motivational_filler(): frag = pp.fragment_for("HAND") assert "variance-evens-out" in frag assert "life-lesson" in frag # if there's no leak, say so instead of inventing a takeaway assert "no leak" in frag.lower()