FAILURE MAP
← Case archive

FA-85716 / Ride-hailing fare and surge pricing / Open access

Cash step applied before currency conversion · case 01

Francs paid in cash come out at odd centimes after conversion.

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

ROOT CAUSE

Rounding happens in the fare currency before converting.

VERIFIED REPAIR

Convert first, then round to the payment currency cash step.

Unsuccessful approach: Hard-coding a 5-unit step after conversion ignores the payment currency step and double-rounds.

Case contract

Convert a fare to the payment currency with the directed rate fx[FROM+TO] = [num, den] (half up), then, for cash payments only, round half up to the payment currency cash step (CHF 5 centimes, SEK 100 ore, others 1). Card and wallet payments are not cash-rounded. Return the amount in minor units of the payment currency.

Why this case matters

Ride-hailing prices are computed per trip at scale; ordering, unit and boundary slips become systematic over- or under-charging.

1 / The failure

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

N = 1
observations = []
def solve(fare_cents, fare_ccy, pay_ccy, method, fx, cash_step):
    amt = fare_cents
    if method == 'cash':
        step = cash_step.get(pay_ccy, 1)
        amt = (amt * 2 + step) // (2 * step) * step
    if pay_ccy != fare_ccy:
        num, den = fx[fare_ccy + pay_ccy]
        amt = (amt * num * 2 + den) // (2 * den)
    return amt
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: convert before rounding',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('partial repair probe: convert before rounding',
   [3492, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   3632),
  ('second regression',
   [1003, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   11500),
  ('normal control 1',
   [1600, 'SEK', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   144),
  ('normal control 2',
   [885, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   920),
  ('normal control 3',
   [2502, 'EUR', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2352),
  ('normal control 4',
   [1999, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2079)],
 [('regression: convert before rounding',
   [1003, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   11500),
  ('partial repair probe: convert before rounding',
   [3472, 'SEK', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   312),
  ('second regression',
   [2573, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2420),
  ('normal control 1',
   [788, 'EUR', 'CHF', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   741),
  ('normal control 2',
   [2502, 'CHF', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2602),
  ('normal control 3',
   [1999, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1999),
  ('normal control 4',
   [1999, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   180)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1160),
  ('partial repair probe: convert before rounding',
   [1234, 'SEK', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   111),
  ('second regression',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('normal control 1',
   [1566, 'CHF', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1565),
  ('normal control 2',
   [1234, 'CHF', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1235),
  ('normal control 3',
   [1003, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   90),
  ('normal control 4',
   [1003, 'EUR', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   943)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1160),
  ('partial repair probe: convert before rounding',
   [2502, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2602),
  ('second regression',
   [1451, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1365),
  ('normal control 1',
   [1003, 'CHF', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1003),
  ('normal control 2',
   [1934, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1934),
  ('normal control 3',
   [1999, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2079),
  ('normal control 4',
   [1531, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1531)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   14200),
  ('partial repair probe: convert before rounding',
   [1003, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1043),
  ('second regression',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('normal control 1',
   [1999, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1999),
  ('normal control 2',
   [1003, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1003),
  ('normal control 3',
   [2630, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   237),
  ('normal control 4',
   [2489, 'SEK', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   224)]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*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
regression: convert before rounding2294022900Failed
partial repair probe: convert before rounding36323632Passed
second regression1147011500Failed
normal control 1144144Passed
normal control 2920920Passed
normal control 323522352Passed
normal control 420792079Passed

SHA-256 / 5202bd0a1deb807eb55ced44e42ff5aa0070d4836110f97faeb48c7490a22070

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(fare_cents, fare_ccy, pay_ccy, method, fx, cash_step):
    amt = fare_cents
    if pay_ccy != fare_ccy:
        num, den = fx[fare_ccy + pay_ccy]
        amt = (amt * num * 2 + den) // (2 * den)
        amt = (amt * 2 + 5) // 10 * 5 if method == 'cash' else amt
    if method == 'cash':
        step = cash_step.get(pay_ccy, 1)
        amt = (amt * 2 + step) // (2 * step) * step
    return amt
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: convert before rounding',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('partial repair probe: convert before rounding',
   [3492, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   3632),
  ('second regression',
   [1003, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   11500),
  ('normal control 1',
   [1600, 'SEK', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   144),
  ('normal control 2',
   [885, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   920),
  ('normal control 3',
   [2502, 'EUR', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2352),
  ('normal control 4',
   [1999, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2079)],
 [('regression: convert before rounding',
   [1003, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   11500),
  ('partial repair probe: convert before rounding',
   [3472, 'SEK', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   312),
  ('second regression',
   [2573, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2420),
  ('normal control 1',
   [788, 'EUR', 'CHF', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   741),
  ('normal control 2',
   [2502, 'CHF', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2602),
  ('normal control 3',
   [1999, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1999),
  ('normal control 4',
   [1999, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   180)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1160),
  ('partial repair probe: convert before rounding',
   [1234, 'SEK', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   111),
  ('second regression',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('normal control 1',
   [1566, 'CHF', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1565),
  ('normal control 2',
   [1234, 'CHF', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1235),
  ('normal control 3',
   [1003, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   90),
  ('normal control 4',
   [1003, 'EUR', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   943)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1160),
  ('partial repair probe: convert before rounding',
   [2502, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2602),
  ('second regression',
   [1451, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1365),
  ('normal control 1',
   [1003, 'CHF', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1003),
  ('normal control 2',
   [1934, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1934),
  ('normal control 3',
   [1999, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2079),
  ('normal control 4',
   [1531, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1531)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   14200),
  ('partial repair probe: convert before rounding',
   [1003, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1043),
  ('second regression',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('normal control 1',
   [1999, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1999),
  ('normal control 2',
   [1003, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1003),
  ('normal control 3',
   [2630, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   237),
  ('normal control 4',
   [2489, 'SEK', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   224)]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*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
regression: convert before rounding2290022900Passed
partial repair probe: convert before rounding36303632Failed
second regression1150011500Passed
normal control 1144144Passed
normal control 2920920Passed
normal control 323522352Passed
normal control 420792079Passed

SHA-256 / aab906411fa43c6801ff433910fe60559cc08009ac118edc9e69dcffe4351b76

3 / The verified repair

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

N = 1
observations = []
def solve(fare_cents, fare_ccy, pay_ccy, method, fx, cash_step):
    amt = fare_cents
    if pay_ccy != fare_ccy:
        num, den = fx[fare_ccy + pay_ccy]
        amt = (amt * num * 2 + den) // (2 * den)
    if method == 'cash':
        step = cash_step.get(pay_ccy, 1)
        amt = (amt * 2 + step) // (2 * step) * step
    return amt
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: convert before rounding',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('partial repair probe: convert before rounding',
   [3492, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   3632),
  ('second regression',
   [1003, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   11500),
  ('normal control 1',
   [1600, 'SEK', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   144),
  ('normal control 2',
   [885, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   920),
  ('normal control 3',
   [2502, 'EUR', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2352),
  ('normal control 4',
   [1999, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2079)],
 [('regression: convert before rounding',
   [1003, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   11500),
  ('partial repair probe: convert before rounding',
   [3472, 'SEK', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   312),
  ('second regression',
   [2573, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2420),
  ('normal control 1',
   [788, 'EUR', 'CHF', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   741),
  ('normal control 2',
   [2502, 'CHF', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2602),
  ('normal control 3',
   [1999, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1999),
  ('normal control 4',
   [1999, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   180)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1160),
  ('partial repair probe: convert before rounding',
   [1234, 'SEK', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   111),
  ('second regression',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('normal control 1',
   [1566, 'CHF', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1565),
  ('normal control 2',
   [1234, 'CHF', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1235),
  ('normal control 3',
   [1003, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   90),
  ('normal control 4',
   [1003, 'EUR', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   943)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1160),
  ('partial repair probe: convert before rounding',
   [2502, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2602),
  ('second regression',
   [1451, 'EUR', 'CHF', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1365),
  ('normal control 1',
   [1003, 'CHF', 'CHF', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1003),
  ('normal control 2',
   [1934, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1934),
  ('normal control 3',
   [1999, 'CHF', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   2079),
  ('normal control 4',
   [1531, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1531)],
 [('regression: convert before rounding',
   [1234, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   14200),
  ('partial repair probe: convert before rounding',
   [1003, 'CHF', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1043),
  ('second regression',
   [1999, 'EUR', 'SEK', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   22900),
  ('normal control 1',
   [1999, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1999),
  ('normal control 2',
   [1003, 'EUR', 'EUR', 'cash',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   1003),
  ('normal control 3',
   [2630, 'SEK', 'EUR', 'card',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   237),
  ('normal control 4',
   [2489, 'SEK', 'EUR', 'wallet',
    {'CHFEUR': [104, 100], 'EURCHF': [94, 100], 'EURSEK': [1147, 100], 'SEKEUR': [9, 100]},
    {'CHF': 5, 'SEK': 100}],
   224)]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*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
regression: convert before rounding2290022900Passed
partial repair probe: convert before rounding36323632Passed
second regression1150011500Passed
normal control 1144144Passed
normal control 2920920Passed
normal control 323522352Passed
normal control 420792079Passed

SHA-256 / d1bd6ce02d4152338651888b99f03b3f49e46465a249c0c7971bbf6cb0a17f65

Verification & scope

A deterministic toy pricing contract stipulated for this example; it does not reproduce the pricing of any real ride-hailing operator or regulator. 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:42.919476+00:00.

Case digest / a2ab18a5b789b2f35d5da87a180f0707c47e2d90e6903318c112a5b5ac46f9dd