FAILURE MAP
← Case archive

FA-84671 / Betting odds conversion / Open access

Only one of several withdrawals deducted · case 01

Two withdrawals of 4.00 and 6.00 deduct 25p instead of 40p.

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

ROOT CAUSE

The deductions are combined with max instead of summed.

VERIFIED REPAIR

Sum the deductions of all withdrawn runners before capping.

Unsuccessful approach: Using the most recent withdrawal ignores earlier ones.

Case contract

Non-runner deduction for a winning bet. Each withdrawn runner's decimal price maps to pence-in-the-pound from the first band whose limit it does not exceed: [('1.30', 75), ('1.40', 70), ('1.53', 65), ('1.62', 60), ('1.80', 55), ('1.95', 50), ('2.20', 45), ('2.50', 40), ('2.75', 35), ('3.25', 30), ('4.00', 25), ('5.00', 20), ('6.50', 15), ('10.00', 10), ('15.00', 5)]; above 15.00 it is 0. Deductions from several withdrawals are summed and capped at 75. The deduction applies to winnings only: return = stake + floor(stake * (price - 1) * (100 - deduction) / 100). Return [deduction, return cents].

Why this case matters

Racing settlement reduces winnings when a runner is withdrawn after prices were struck.

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(stake_cents, price, withdrawn):
    TABLE = [('1.30', 75), ('1.40', 70), ('1.53', 65), ('1.62', 60), ('1.80', 55), ('1.95', 50), ('2.20', 45), ('2.50', 40), ('2.75', 35), ('3.25', 30), ('4.00', 25), ('5.00', 20), ('6.50', 15), ('10.00', 10), ('15.00', 5)]
    def ded(p):
        p = Fraction(p)
        for limit, pence in TABLE:
            if p <= Fraction(limit):
                return pence
        return 0
    total = min(75, max([ded(w) for w in withdrawn] or [0]))
    profit = stake_cents * (Fraction(price) - 1)
    return [total, stake_cents + math.floor(profit * (100 - total) / 100)]
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 no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (1000, '5.56', ['10.00', '5.46', '16.90']), [25, 4420]),
  ('variant scenario 1', (500, '4.72', []), [0, 2360]),
  ('variant scenario 2', (250, '7.09', []), [0, 1772])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (250, '2.61', ['5.46', '6.50']), [30, 531]),
  ('variant scenario 1', (1000, '9.88', ['18.74']), [0, 9880]),
  ('variant scenario 2', (250, '7.42', []), [0, 1855])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (500, '2.80', ['3.60', '15.00']), [30, 1130]),
  ('variant scenario 1', (500, '11.51', ['2.75', '1.95']), [75, 1813]),
  ('variant scenario 2', (250, '9.39', ['1.40', '15.00']), [75, 774])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (250, '9.89', ['1.62', '15.00']), [65, 1027]),
  ('variant scenario 1', (500, '10.13', ['6.45']), [15, 4380]),
  ('variant scenario 2', (500, '7.65', ['2.20', '6.50', '15.99']), [60, 1830])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (1000, '10.42', ['11.29', '1.95', '13.11']), [60, 4768]),
  ('variant scenario 1', (500, '3.53', []), [0, 1765]),
  ('variant scenario 2', (500, '11.80', ['6.98', '1.53', '3.76']), [75, 1850])]]
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 no withdrawal[0, 5000][0, 5000]Passed
boundary price on band limit[40, 3400][40, 3400]Passed
control long-shot withdrawal[0, 5000][0, 5000]Passed
boundary cap at 75[65, 1700][75, 1500]Failed
control two withdrawals[25, 2500][40, 2200]Failed
regression: multiple withdrawals[15, 4876][25, 4420]Failed
variant scenario 1[0, 2360][0, 2360]Passed
variant scenario 2[0, 1772][0, 1772]Passed

SHA-256 / ef36ed34f64c8e656230fd5f53af38ca624a7c578c62dd84d9e96f3f8559d25b

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(stake_cents, price, withdrawn):
    TABLE = [('1.30', 75), ('1.40', 70), ('1.53', 65), ('1.62', 60), ('1.80', 55), ('1.95', 50), ('2.20', 45), ('2.50', 40), ('2.75', 35), ('3.25', 30), ('4.00', 25), ('5.00', 20), ('6.50', 15), ('10.00', 10), ('15.00', 5)]
    def ded(p):
        p = Fraction(p)
        for limit, pence in TABLE:
            if p <= Fraction(limit):
                return pence
        return 0
    total = min(75, ded(withdrawn[-1]) if withdrawn else 0)
    profit = stake_cents * (Fraction(price) - 1)
    return [total, stake_cents + math.floor(profit * (100 - total) / 100)]
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 no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (1000, '5.56', ['10.00', '5.46', '16.90']), [25, 4420]),
  ('variant scenario 1', (500, '4.72', []), [0, 2360]),
  ('variant scenario 2', (250, '7.09', []), [0, 1772])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (250, '2.61', ['5.46', '6.50']), [30, 531]),
  ('variant scenario 1', (1000, '9.88', ['18.74']), [0, 9880]),
  ('variant scenario 2', (250, '7.42', []), [0, 1855])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (500, '2.80', ['3.60', '15.00']), [30, 1130]),
  ('variant scenario 1', (500, '11.51', ['2.75', '1.95']), [75, 1813]),
  ('variant scenario 2', (250, '9.39', ['1.40', '15.00']), [75, 774])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (250, '9.89', ['1.62', '15.00']), [65, 1027]),
  ('variant scenario 1', (500, '10.13', ['6.45']), [15, 4380]),
  ('variant scenario 2', (500, '7.65', ['2.20', '6.50', '15.99']), [60, 1830])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (1000, '10.42', ['11.29', '1.95', '13.11']), [60, 4768]),
  ('variant scenario 1', (500, '3.53', []), [0, 1765]),
  ('variant scenario 2', (500, '11.80', ['6.98', '1.53', '3.76']), [75, 1850])]]
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 no withdrawal[0, 5000][0, 5000]Passed
boundary price on band limit[40, 3400][40, 3400]Passed
control long-shot withdrawal[0, 5000][0, 5000]Passed
boundary cap at 75[65, 1700][75, 1500]Failed
control two withdrawals[15, 2700][40, 2200]Failed
regression: multiple withdrawals[0, 5560][25, 4420]Failed
variant scenario 1[0, 2360][0, 2360]Passed
variant scenario 2[0, 1772][0, 1772]Passed

SHA-256 / b0448ca4697359b8cb5d9c9b8a40b8f1177ba2a235cd3e6cef93a7418984f0d5

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(stake_cents, price, withdrawn):
    TABLE = [('1.30', 75), ('1.40', 70), ('1.53', 65), ('1.62', 60), ('1.80', 55), ('1.95', 50), ('2.20', 45), ('2.50', 40), ('2.75', 35), ('3.25', 30), ('4.00', 25), ('5.00', 20), ('6.50', 15), ('10.00', 10), ('15.00', 5)]
    def ded(p):
        p = Fraction(p)
        for limit, pence in TABLE:
            if p <= Fraction(limit):
                return pence
        return 0
    total = min(75, sum(ded(w) for w in withdrawn))
    profit = stake_cents * (Fraction(price) - 1)
    return [total, stake_cents + math.floor(profit * (100 - total) / 100)]
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 no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (1000, '5.56', ['10.00', '5.46', '16.90']), [25, 4420]),
  ('variant scenario 1', (500, '4.72', []), [0, 2360]),
  ('variant scenario 2', (250, '7.09', []), [0, 1772])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (250, '2.61', ['5.46', '6.50']), [30, 531]),
  ('variant scenario 1', (1000, '9.88', ['18.74']), [0, 9880]),
  ('variant scenario 2', (250, '7.42', []), [0, 1855])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (500, '2.80', ['3.60', '15.00']), [30, 1130]),
  ('variant scenario 1', (500, '11.51', ['2.75', '1.95']), [75, 1813]),
  ('variant scenario 2', (250, '9.39', ['1.40', '15.00']), [75, 774])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (250, '9.89', ['1.62', '15.00']), [65, 1027]),
  ('variant scenario 1', (500, '10.13', ['6.45']), [15, 4380]),
  ('variant scenario 2', (500, '7.65', ['2.20', '6.50', '15.99']), [60, 1830])],
 [('control no withdrawal', (1000, '5.00', []), [0, 5000]),
  ('boundary price on band limit', (1000, '5.00', ['2.50']), [40, 3400]),
  ('control long-shot withdrawal', (1000, '5.00', ['21.00']), [0, 5000]),
  ('boundary cap at 75', (1000, '3.00', ['1.50', '1.50']), [75, 1500]),
  ('control two withdrawals', (1000, '3.00', ['4.00', '6.00']), [40, 2200]),
  ('regression: multiple withdrawals', (1000, '10.42', ['11.29', '1.95', '13.11']), [60, 4768]),
  ('variant scenario 1', (500, '3.53', []), [0, 1765]),
  ('variant scenario 2', (500, '11.80', ['6.98', '1.53', '3.76']), [75, 1850])]]
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 no withdrawal[0, 5000][0, 5000]Passed
boundary price on band limit[40, 3400][40, 3400]Passed
control long-shot withdrawal[0, 5000][0, 5000]Passed
boundary cap at 75[75, 1500][75, 1500]Passed
control two withdrawals[40, 2200][40, 2200]Passed
regression: multiple withdrawals[25, 4420][25, 4420]Passed
variant scenario 1[0, 2360][0, 2360]Passed
variant scenario 2[0, 1772][0, 1772]Passed

SHA-256 / a6d6bc35509643acfb06706ed90020c4098481bae8c7c0be3b4cfdb798e7220a

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:33.169982+00:00.

Case digest / 68be61359f59ce458faf3a6eef8694a46df79f6054db5289a80b133c51a6ae6a