FAILURE MAP
← Case archive

FA-82141 / Poker hand evaluation / Open access

Space after a comma breaks range parsing · case 01

"QQ+, AKs" fails to parse because the second token starts with a space.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Tokens are used without trimming, so the leading space is read as the first rank.

THE FAILURE

Tokens are used without trimming, so the leading space is read as the first rank.

Unsuccessful approach: Stripping commas leaves the spaces in place.

Case contract

Input: a comma-separated hold'em range string. Tokens: pairs "77", "77+" (up to AA), "TT-77"; non-pairs "AKs"/"AKo"/"AK" (no suffix means both suited and offsuit), "A9s+" (kicker rises to one below the top card), "A9s-A6s" (inclusive). Whitespace around tokens is ignored. Duplicated classes count once. Return total combos: pair 6, suited 4, offsuit 12.

Why this case matters

Range combo counts drive equity, frequency and blocker arithmetic in solvers and trackers.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    R='23456789TJQKA'
    classes=set()
    def add(h, suf):
        if h[0]==h[1]: classes.add(h)
        elif suf: classes.add(h+suf)
        else:
            classes.add(h+'s'); classes.add(h+'o')
    for tok in x.split(','):
        tok=tok
        if not tok: continue
        plus=tok.endswith('+')
        t=tok.rstrip('+')
        if '-' in t:
            a,b=t.split('-')
            if a[0]==a[1]:
                lo,hi=sorted([R.index(a[0]),R.index(b[0])])
                for i in range(lo,hi+1): add(R[i]*2,'')
            else:
                lo,hi=sorted([R.index(a[1]),R.index(b[1])])
                for i in range(lo,hi+1): add(a[0]+R[i],a[2:])
            continue
        if t[0]==t[1]:
            start=R.index(t[0])
            stop=len(R)-1 if plus else start
            for i in range(start,stop+1): add(R[i]*2,'')
        else:
            top=R.index(t[0]); k=R.index(t[1])
            stop=top-1 if plus else k
            for i in range(k,stop+1): add(t[0]+R[i],t[2:])
    total=0
    for c in classes:
        if c[0]==c[1]: total+=6
        elif c.endswith('s'): total+=4
        else: total+=12
    return total
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
  ('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
  ('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
  ('spaced tokens', 'QQ+, AKs', 22)],
 [('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
  ('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
  ('reversed dash pairs', '77-TT', 24), ('spaced tokens', 'QQ+, AKs', 22),
  ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6)],
 [('dash pair range', 'TT-77', 24), ('reversed dash pairs', '77-TT', 24),
  ('spaced tokens', 'QQ+, AKs', 22), ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6),
  ('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4)],
 [('pairs plus', '77+', 48), ('spaced tokens', 'QQ+, AKs', 22), ('single pair', '22', 6),
  ('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4),
  ('trailing comma', 'KK+,', 12), ('reversed kicker dash', 'K6o-K9o', 48)],
 [('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
  ('offsuit king plus', 'KTo+', 36), ('spaced tokens', 'QQ+, AKs', 22),
  ('suited connector plus', 'T9s+', 4), ('trailing comma', 'KK+,', 12),
  ('reversed kicker dash', 'K6o-K9o', 48)]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'error:' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
pairs plus4848Passed
queens plus1818Passed
suited ace plus2020Passed
offsuit king plus3636Passed
unsuffixed class1616Passed
dash kicker range1616Passed
dash pair range2424Passed
spaced tokenserror:ValueError22Failed

SHA-256 / 585fd70fa9bb01d9078632b27a4411ecae8c1545561929317dcc8ec41b6c372f

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    R='23456789TJQKA'
    classes=set()
    def add(h, suf):
        if h[0]==h[1]: classes.add(h)
        elif suf: classes.add(h+suf)
        else:
            classes.add(h+'s'); classes.add(h+'o')
    for tok in x.split(','):
        tok=tok.strip(',')
        if not tok: continue
        plus=tok.endswith('+')
        t=tok.rstrip('+')
        if '-' in t:
            a,b=t.split('-')
            if a[0]==a[1]:
                lo,hi=sorted([R.index(a[0]),R.index(b[0])])
                for i in range(lo,hi+1): add(R[i]*2,'')
            else:
                lo,hi=sorted([R.index(a[1]),R.index(b[1])])
                for i in range(lo,hi+1): add(a[0]+R[i],a[2:])
            continue
        if t[0]==t[1]:
            start=R.index(t[0])
            stop=len(R)-1 if plus else start
            for i in range(start,stop+1): add(R[i]*2,'')
        else:
            top=R.index(t[0]); k=R.index(t[1])
            stop=top-1 if plus else k
            for i in range(k,stop+1): add(t[0]+R[i],t[2:])
    total=0
    for c in classes:
        if c[0]==c[1]: total+=6
        elif c.endswith('s'): total+=4
        else: total+=12
    return total
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
  ('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
  ('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
  ('spaced tokens', 'QQ+, AKs', 22)],
 [('offsuit king plus', 'KTo+', 36), ('unsuffixed class', 'AK', 16),
  ('dash kicker range', 'A9s-A6s', 16), ('dash pair range', 'TT-77', 24),
  ('reversed dash pairs', '77-TT', 24), ('spaced tokens', 'QQ+, AKs', 22),
  ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6)],
 [('dash pair range', 'TT-77', 24), ('reversed dash pairs', '77-TT', 24),
  ('spaced tokens', 'QQ+, AKs', 22), ('overlap dedupe', 'AKs,AK', 16), ('single pair', '22', 6),
  ('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4)],
 [('pairs plus', '77+', 48), ('spaced tokens', 'QQ+, AKs', 22), ('single pair', '22', 6),
  ('mixed', 'JJ+,AQs+,KQo', 44), ('aces only', 'AA', 6), ('suited connector plus', 'T9s+', 4),
  ('trailing comma', 'KK+,', 12), ('reversed kicker dash', 'K6o-K9o', 48)],
 [('pairs plus', '77+', 48), ('queens plus', 'QQ+', 18), ('suited ace plus', 'A9s+', 20),
  ('offsuit king plus', 'KTo+', 36), ('spaced tokens', 'QQ+, AKs', 22),
  ('suited connector plus', 'T9s+', 4), ('trailing comma', 'KK+,', 12),
  ('reversed kicker dash', 'K6o-K9o', 48)]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'error:' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
pairs plus4848Passed
queens plus1818Passed
suited ace plus2020Passed
offsuit king plus3636Passed
unsuffixed class1616Passed
dash kicker range1616Passed
dash pair range2424Passed
spaced tokenserror:ValueError22Failed

SHA-256 / e3238edadf4de333a800aef89abafad54a6eac275053a6c108f1544abb0bb1f7

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

A deterministic toy model with an explicitly stated contract; card strings are rank+suit (for example "As", "Td"); no claim of conformance to any casino or tournament rulebook. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:50:09.464499+00:00.

Case digest / 6fe4fe512b9081c7c08cca5281ef0660a24cf9a2c70c2d008dadf6888e31ffd1