FAILURE MAP
← Case archive

FA-84391 / Betting odds conversion / Open access

Odds-on suffix stripped but the price not inverted · case 01

"2/1 on" is priced as 3.00 instead of 1.50.

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

ROOT CAUSE

The " on" suffix is removed but the fraction is never inverted.

VERIFIED REPAIR

Invert the fraction for odds-on prices before deriving the decimal price.

Unsuccessful approach: Inverting the decimal price instead of the fraction produces a sub-1 decimal and leaves the fraction un-inverted.

Case contract

Parse traditional fractional odds text, case-insensitively: "a/b" or "a-b" with positive integers, "evens"/"evs"/"even" for 1/1, and an optional trailing " on" meaning odds-on (the fraction is inverted, "2/1 on" = 1/2). Zero parts or anything else return "invalid". Return [decimal price rounded half up to two decimals, reduced profit fraction "a/b"].

Why this case matters

Racing feeds and manual price entry use traditional fractional notation with odds-on suffixes.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(text):
    t = text.strip().lower()
    on = t.endswith(' on')
    if on:
        t = t[:-3].strip()
    if t in ('evens', 'evs', 'even'):
        frac = Fraction(1)
    else:
        for sep in ('/', '-'):
            if sep in t:
                a, _, b = t.partition(sep)
                break
        else:
            return 'invalid'
        if not a.isdigit() or not b.isdigit() or int(b) == 0 or int(a) == 0:
            return 'invalid'
        frac = Fraction(int(a), int(b))
    dec = 1 + frac
    cents = math.floor(dec * 100 + Fraction(1, 2))
    return ['%d.%02d' % (cents // 100, cents % 100), '%d/%d' % (frac.numerator, frac.denominator)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('3/6 on',), ['3.00', '2/1']),
  ('variant scenario 1', ('evens',), ['2.00', '1/1']),
  ('variant scenario 2', ('12/5',), ['3.40', '12/5'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('15/13 ON',), ['1.87', '13/15']),
  ('variant scenario 1', ('evens',), ['2.00', '1/1']),
  ('variant scenario 2', ('10/8',), ['2.25', '5/4'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('4/1 ON',), ['1.25', '1/4']),
  ('variant scenario 1', ('1/8',), ['1.13', '1/8']),
  ('variant scenario 2', ('3-3',), ['2.00', '1/1'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('11/8 ON',), ['1.73', '8/11']),
  ('variant scenario 1', ('4/3',), ['2.33', '4/3']),
  ('variant scenario 2', ('2/3',), ['1.67', '2/3'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('3-6 ON',), ['3.00', '2/1']),
  ('variant scenario 1', ('2/10',), ['1.20', '1/5']),
  ('variant scenario 2', ('5/4',), ['2.25', '5/4'])]]
for label, args, expected in cases[N - 1]:
    check(label, run(args), 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
control five to two['3.50', '5/2']['3.50', '5/2']Passed
control hyphen separator['3.75', '11/4']['3.75', '11/4']Passed
boundary evens alias['2.00', '1/1']['2.00', '1/1']Passed
boundary odds-on suffix['3.00', '2/1']['1.50', '1/2']Failed
boundary unreduced input['1.67', '2/3']['1.67', '2/3']Passed
boundary zero denominatorinvalidinvalidPassed
control half-up rounding['1.13', '1/8']['1.13', '1/8']Passed
regression: odds on inversion['1.50', '1/2']['3.00', '2/1']Failed
variant scenario 1['2.00', '1/1']['2.00', '1/1']Passed
variant scenario 2['3.40', '12/5']['3.40', '12/5']Passed

SHA-256 / 80a6f1a833a4ddb269497fe643606abbb30594b8d7d0de67571c6a01927d0ed0

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(text):
    t = text.strip().lower()
    on = t.endswith(' on')
    if on:
        t = t[:-3].strip()
    if t in ('evens', 'evs', 'even'):
        frac = Fraction(1)
    else:
        for sep in ('/', '-'):
            if sep in t:
                a, _, b = t.partition(sep)
                break
        else:
            return 'invalid'
        if not a.isdigit() or not b.isdigit() or int(b) == 0 or int(a) == 0:
            return 'invalid'
        frac = Fraction(int(a), int(b))
    dec = 1 + frac
    if on:
        dec = 1 / dec
    cents = math.floor(dec * 100 + Fraction(1, 2))
    return ['%d.%02d' % (cents // 100, cents % 100), '%d/%d' % (frac.numerator, frac.denominator)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('3/6 on',), ['3.00', '2/1']),
  ('variant scenario 1', ('evens',), ['2.00', '1/1']),
  ('variant scenario 2', ('12/5',), ['3.40', '12/5'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('15/13 ON',), ['1.87', '13/15']),
  ('variant scenario 1', ('evens',), ['2.00', '1/1']),
  ('variant scenario 2', ('10/8',), ['2.25', '5/4'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('4/1 ON',), ['1.25', '1/4']),
  ('variant scenario 1', ('1/8',), ['1.13', '1/8']),
  ('variant scenario 2', ('3-3',), ['2.00', '1/1'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('11/8 ON',), ['1.73', '8/11']),
  ('variant scenario 1', ('4/3',), ['2.33', '4/3']),
  ('variant scenario 2', ('2/3',), ['1.67', '2/3'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('3-6 ON',), ['3.00', '2/1']),
  ('variant scenario 1', ('2/10',), ['1.20', '1/5']),
  ('variant scenario 2', ('5/4',), ['2.25', '5/4'])]]
for label, args, expected in cases[N - 1]:
    check(label, run(args), 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
control five to two['3.50', '5/2']['3.50', '5/2']Passed
control hyphen separator['3.75', '11/4']['3.75', '11/4']Passed
boundary evens alias['2.00', '1/1']['2.00', '1/1']Passed
boundary odds-on suffix['0.33', '2/1']['1.50', '1/2']Failed
boundary unreduced input['1.67', '2/3']['1.67', '2/3']Passed
boundary zero denominatorinvalidinvalidPassed
control half-up rounding['1.13', '1/8']['1.13', '1/8']Passed
regression: odds on inversion['0.67', '1/2']['3.00', '2/1']Failed
variant scenario 1['2.00', '1/1']['2.00', '1/1']Passed
variant scenario 2['3.40', '12/5']['3.40', '12/5']Passed

SHA-256 / b69d08886f320ac1a2cbd93850d8e3c6072245c067b4a051de6e0ddd5fdf0c97

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(text):
    t = text.strip().lower()
    on = t.endswith(' on')
    if on:
        t = t[:-3].strip()
    if t in ('evens', 'evs', 'even'):
        frac = Fraction(1)
    else:
        for sep in ('/', '-'):
            if sep in t:
                a, _, b = t.partition(sep)
                break
        else:
            return 'invalid'
        if not a.isdigit() or not b.isdigit() or int(b) == 0 or int(a) == 0:
            return 'invalid'
        frac = Fraction(int(a), int(b))
    if on:
        frac = 1 / frac
    dec = 1 + frac
    cents = math.floor(dec * 100 + Fraction(1, 2))
    return ['%d.%02d' % (cents // 100, cents % 100), '%d/%d' % (frac.numerator, frac.denominator)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('3/6 on',), ['3.00', '2/1']),
  ('variant scenario 1', ('evens',), ['2.00', '1/1']),
  ('variant scenario 2', ('12/5',), ['3.40', '12/5'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('15/13 ON',), ['1.87', '13/15']),
  ('variant scenario 1', ('evens',), ['2.00', '1/1']),
  ('variant scenario 2', ('10/8',), ['2.25', '5/4'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('4/1 ON',), ['1.25', '1/4']),
  ('variant scenario 1', ('1/8',), ['1.13', '1/8']),
  ('variant scenario 2', ('3-3',), ['2.00', '1/1'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('11/8 ON',), ['1.73', '8/11']),
  ('variant scenario 1', ('4/3',), ['2.33', '4/3']),
  ('variant scenario 2', ('2/3',), ['1.67', '2/3'])],
 [('control five to two', ('5/2',), ['3.50', '5/2']),
  ('control hyphen separator', ('11-4',), ['3.75', '11/4']),
  ('boundary evens alias', ('EVS',), ['2.00', '1/1']),
  ('boundary odds-on suffix', ('2/1 on',), ['1.50', '1/2']),
  ('boundary unreduced input', ('4/6',), ['1.67', '2/3']),
  ('boundary zero denominator', ('5/0',), 'invalid'),
  ('control half-up rounding', ('1/8',), ['1.13', '1/8']),
  ('regression: odds on inversion', ('3-6 ON',), ['3.00', '2/1']),
  ('variant scenario 1', ('2/10',), ['1.20', '1/5']),
  ('variant scenario 2', ('5/4',), ['2.25', '5/4'])]]
for label, args, expected in cases[N - 1]:
    check(label, run(args), 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
control five to two['3.50', '5/2']['3.50', '5/2']Passed
control hyphen separator['3.75', '11/4']['3.75', '11/4']Passed
boundary evens alias['2.00', '1/1']['2.00', '1/1']Passed
boundary odds-on suffix['1.50', '1/2']['1.50', '1/2']Passed
boundary unreduced input['1.67', '2/3']['1.67', '2/3']Passed
boundary zero denominatorinvalidinvalidPassed
control half-up rounding['1.13', '1/8']['1.13', '1/8']Passed
regression: odds on inversion['3.00', '2/1']['3.00', '2/1']Passed
variant scenario 1['2.00', '1/1']['2.00', '1/1']Passed
variant scenario 2['3.40', '12/5']['3.40', '12/5']Passed

SHA-256 / 0e1cb4a6b378eba1657023fc1d34a472b6d905f6fc0105a71b7a444d1dd11957

Verification & scope

Stipulated, bounded toy contract stated in the contract field; not a claim of conformance with any operator, exchange or regulator rule set. 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:30.440790+00:00.

Case digest / 276a45b60919fc948277864620b6cf50cbccbd89afdc075ef94137150f99c04a