FAILURE MAP
← Case archive

FA-84516 / Betting odds conversion / Open access

Pushed part refunds nothing or the whole stake · case 01

A whole-line push returns 0, or a half push refunds the full stake.

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

ROOT CAUSE

The zero-margin branch does not refund the part stake.

VERIFIED REPAIR

Refund exactly the part stake on a push.

Unsuccessful approach: Refunding the whole stake double-counts the other half of a split bet.

Case contract

Asian handicap bet on the home side. line is a multiple of 0.25 (else "invalid line"). Whole and half lines are a single bet; quarter lines split the stake equally between line - 0.25 and line + 0.25. Each part: margin = home_goals - away_goals + part_line; positive wins stake_part * price, zero refunds stake_part, negative loses. Return [total return in cents rounded down, label] where the label is win, half-win, push, half-lose or lose from the summed part outcomes.

Why this case matters

Asian handicap settlement with split quarter lines is a common source of payout disputes.

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(line, home_goals, away_goals, stake_cents, price):
    L = Fraction(line)
    if (L * 4).denominator != 1:
        return 'invalid line'
    if (L * 2).denominator == 1:
        parts = [L]
    else:
        parts = [L - Fraction(1, 4), L + Fraction(1, 4)]
    d = Fraction(price)
    share = Fraction(stake_cents, len(parts))
    ret = Fraction(0)
    res = []
    for p in parts:
        m = home_goals - away_goals + p
        if m > 0:
            ret += share * d
            res.append(1)
        elif m == 0:
            res.append(0)
        else:
            res.append(-1)
    score = sum(res) * 2 // len(parts)
    label = {2: 'win', 1: 'half-win', 0: 'push', -1: 'half-lose', -2: 'lose'}[score]
    return [math.floor(ret), label]
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 half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('+0.75', 3, 4, 999, '2.05'), [499, 'half-lose']),
  ('variant scenario 1', ('0', 4, 0, 1000, '2.05'), [2050, 'win']),
  ('variant scenario 2', ('+0.75', 3, 0, 999, '2.00'), [1998, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('+2', 2, 4, 500, '1.85'), [500, 'push']),
  ('regression: push refund', ('+1.25', 2, 3, 999, '1.85'), [1423, 'half-win']),
  ('variant scenario 1', ('+0.75', 4, 4, 500, '1.85'), [925, 'win']),
  ('variant scenario 2', ('+1.25', 3, 3, 250, '1.85'), [462, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-1', 3, 2, 1000, '1.90'), [1000, 'push']),
  ('regression: push refund', ('+0.75', 3, 4, 999, '1.90'), [499, 'half-lose']),
  ('variant scenario 1', ('-0.5', 0, 1, 500, '2.05'), [0, 'lose']),
  ('variant scenario 2', ('+0.75', 4, 0, 1000, '2.00'), [2000, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-0.75', 2, 1, 999, '2.05'), [1523, 'half-win']),
  ('variant scenario 1', ('-1.75', 3, 4, 500, '2.00'), [0, 'lose']),
  ('variant scenario 2', ('+0.5', 4, 2, 1000, '2.05'), [2050, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-1', 2, 1, 1000, '2.00'), [1000, 'push']),
  ('regression: push refund', ('+0.25', 3, 3, 500, '1.90'), [725, 'half-win']),
  ('variant scenario 1', ('-0.75', 2, 3, 250, '2.00'), [0, 'lose']),
  ('variant scenario 2', ('-1.5', 2, 0, 1000, '1.85'), [1850, 'win'])]]
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 half line win[1900, 'win'][1900, 'win']Passed
boundary whole line push[0, 'push'][1000, 'push']Failed
boundary quarter half-lose[0, 'half-lose'][500, 'half-lose']Failed
boundary quarter half-win[1000, 'half-win'][1500, 'half-win']Failed
control lose[0, 'lose'][0, 'lose']Passed
boundary off-grid lineinvalid lineinvalid linePassed
regression: push refund[0, 'half-lose'][499, 'half-lose']Failed
variant scenario 1[2050, 'win'][2050, 'win']Passed
variant scenario 2[1998, 'win'][1998, 'win']Passed

SHA-256 / 29b66188a122e835d0c9a129ce9cd91e1a2d14a4c63dc333f29dee0979e77574

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(line, home_goals, away_goals, stake_cents, price):
    L = Fraction(line)
    if (L * 4).denominator != 1:
        return 'invalid line'
    if (L * 2).denominator == 1:
        parts = [L]
    else:
        parts = [L - Fraction(1, 4), L + Fraction(1, 4)]
    d = Fraction(price)
    share = Fraction(stake_cents, len(parts))
    ret = Fraction(0)
    res = []
    for p in parts:
        m = home_goals - away_goals + p
        if m > 0:
            ret += share * d
            res.append(1)
        elif m == 0:
            ret += stake_cents
            res.append(0)
        else:
            res.append(-1)
    score = sum(res) * 2 // len(parts)
    label = {2: 'win', 1: 'half-win', 0: 'push', -1: 'half-lose', -2: 'lose'}[score]
    return [math.floor(ret), label]
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 half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('+0.75', 3, 4, 999, '2.05'), [499, 'half-lose']),
  ('variant scenario 1', ('0', 4, 0, 1000, '2.05'), [2050, 'win']),
  ('variant scenario 2', ('+0.75', 3, 0, 999, '2.00'), [1998, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('+2', 2, 4, 500, '1.85'), [500, 'push']),
  ('regression: push refund', ('+1.25', 2, 3, 999, '1.85'), [1423, 'half-win']),
  ('variant scenario 1', ('+0.75', 4, 4, 500, '1.85'), [925, 'win']),
  ('variant scenario 2', ('+1.25', 3, 3, 250, '1.85'), [462, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-1', 3, 2, 1000, '1.90'), [1000, 'push']),
  ('regression: push refund', ('+0.75', 3, 4, 999, '1.90'), [499, 'half-lose']),
  ('variant scenario 1', ('-0.5', 0, 1, 500, '2.05'), [0, 'lose']),
  ('variant scenario 2', ('+0.75', 4, 0, 1000, '2.00'), [2000, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-0.75', 2, 1, 999, '2.05'), [1523, 'half-win']),
  ('variant scenario 1', ('-1.75', 3, 4, 500, '2.00'), [0, 'lose']),
  ('variant scenario 2', ('+0.5', 4, 2, 1000, '2.05'), [2050, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-1', 2, 1, 1000, '2.00'), [1000, 'push']),
  ('regression: push refund', ('+0.25', 3, 3, 500, '1.90'), [725, 'half-win']),
  ('variant scenario 1', ('-0.75', 2, 3, 250, '2.00'), [0, 'lose']),
  ('variant scenario 2', ('-1.5', 2, 0, 1000, '1.85'), [1850, 'win'])]]
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 half line win[1900, 'win'][1900, 'win']Passed
boundary whole line push[1000, 'push'][1000, 'push']Passed
boundary quarter half-lose[1000, 'half-lose'][500, 'half-lose']Failed
boundary quarter half-win[2000, 'half-win'][1500, 'half-win']Failed
control lose[0, 'lose'][0, 'lose']Passed
boundary off-grid lineinvalid lineinvalid linePassed
regression: push refund[999, 'half-lose'][499, 'half-lose']Failed
variant scenario 1[2050, 'win'][2050, 'win']Passed
variant scenario 2[1998, 'win'][1998, 'win']Passed

SHA-256 / 8bafd6715f0d8f3f171e86128d7c46a4c43fb0c61eb3393e76ed6f1946b02348

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(line, home_goals, away_goals, stake_cents, price):
    L = Fraction(line)
    if (L * 4).denominator != 1:
        return 'invalid line'
    if (L * 2).denominator == 1:
        parts = [L]
    else:
        parts = [L - Fraction(1, 4), L + Fraction(1, 4)]
    d = Fraction(price)
    share = Fraction(stake_cents, len(parts))
    ret = Fraction(0)
    res = []
    for p in parts:
        m = home_goals - away_goals + p
        if m > 0:
            ret += share * d
            res.append(1)
        elif m == 0:
            ret += share
            res.append(0)
        else:
            res.append(-1)
    score = sum(res) * 2 // len(parts)
    label = {2: 'win', 1: 'half-win', 0: 'push', -1: 'half-lose', -2: 'lose'}[score]
    return [math.floor(ret), label]
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 half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('+0.75', 3, 4, 999, '2.05'), [499, 'half-lose']),
  ('variant scenario 1', ('0', 4, 0, 1000, '2.05'), [2050, 'win']),
  ('variant scenario 2', ('+0.75', 3, 0, 999, '2.00'), [1998, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('+2', 2, 4, 500, '1.85'), [500, 'push']),
  ('regression: push refund', ('+1.25', 2, 3, 999, '1.85'), [1423, 'half-win']),
  ('variant scenario 1', ('+0.75', 4, 4, 500, '1.85'), [925, 'win']),
  ('variant scenario 2', ('+1.25', 3, 3, 250, '1.85'), [462, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-1', 3, 2, 1000, '1.90'), [1000, 'push']),
  ('regression: push refund', ('+0.75', 3, 4, 999, '1.90'), [499, 'half-lose']),
  ('variant scenario 1', ('-0.5', 0, 1, 500, '2.05'), [0, 'lose']),
  ('variant scenario 2', ('+0.75', 4, 0, 1000, '2.00'), [2000, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-0.75', 2, 1, 999, '2.05'), [1523, 'half-win']),
  ('variant scenario 1', ('-1.75', 3, 4, 500, '2.00'), [0, 'lose']),
  ('variant scenario 2', ('+0.5', 4, 2, 1000, '2.05'), [2050, 'win'])],
 [('control half line win', ('-0.5', 1, 0, 1000, '1.90'), [1900, 'win']),
  ('boundary whole line push', ('-1', 2, 1, 1000, '1.90'), [1000, 'push']),
  ('boundary quarter half-lose', ('-0.25', 1, 1, 1000, '1.90'), [500, 'half-lose']),
  ('boundary quarter half-win', ('-0.75', 1, 0, 1000, '2.00'), [1500, 'half-win']),
  ('control lose', ('+0.5', 0, 2, 1000, '1.90'), [0, 'lose']),
  ('boundary off-grid line', ('-0.3', 1, 0, 1000, '1.90'), 'invalid line'),
  ('regression: push refund', ('-1', 2, 1, 1000, '2.00'), [1000, 'push']),
  ('regression: push refund', ('+0.25', 3, 3, 500, '1.90'), [725, 'half-win']),
  ('variant scenario 1', ('-0.75', 2, 3, 250, '2.00'), [0, 'lose']),
  ('variant scenario 2', ('-1.5', 2, 0, 1000, '1.85'), [1850, 'win'])]]
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 half line win[1900, 'win'][1900, 'win']Passed
boundary whole line push[1000, 'push'][1000, 'push']Passed
boundary quarter half-lose[500, 'half-lose'][500, 'half-lose']Passed
boundary quarter half-win[1500, 'half-win'][1500, 'half-win']Passed
control lose[0, 'lose'][0, 'lose']Passed
boundary off-grid lineinvalid lineinvalid linePassed
regression: push refund[499, 'half-lose'][499, 'half-lose']Passed
variant scenario 1[2050, 'win'][2050, 'win']Passed
variant scenario 2[1998, 'win'][1998, 'win']Passed

SHA-256 / 27079f05e88db36c2ebbb45c8f6069e58b01cbf46be0f09c0c7dab61dea81743

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

Case digest / af631213e4016b365c908318f162532f677ab8606eee1001f6ce9541fa0dec17