FAILURE MAP
← Case archive

FA-61971 / Currency rounding and FX conversion / Open access

A rate correction with the same timestamp is ignored · case 01

The originally published rate is used although a correction for the same instant followed it.

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

ROOT CAUSE

The replacement test uses ts > best[0], so the first tick at a timestamp is kept.

VERIFIED REPAIR

Replace on ts >= best[0] while scanning in publication order.

Unsuccessful approach: Sorting the feed first breaks ties by rate text, not publication order.

Case contract

solve(ticks, trade_ts, max_age): ticks is the published rate feed [[ts, rate], ...] in publication order. Use the tick with the greatest ts <= trade_ts; among ticks with equal ts the later-published one is a correction and wins. No eligible tick returns 'ERR:no-rate'. If max_age is not None and trade_ts - ts > max_age the rate is 'ERR:stale' (max_age 0 accepts only a same-instant tick). Return [ts, rate].

Why this case matters

Currency amounts must be rounded at the right stage and in the right unit, or ledgers, quotes and settlements drift by minor units.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(ticks, trade_ts, max_age):
    best = None
    for ts, r in ticks:
        if ts <= trade_ts and (best is None or ts > best[0]):
            best = [ts, r]
    if best is None: return 'ERR:no-rate'
    if max_age is not None and trade_ts - best[0] > max_age: return 'ERR:stale'
    return best
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression correction-tie 1', ([[1, '1.10'], [5, '1.21'], [5, '1.20']], 7, 3), [5, '1.20']),
  ('regression correction-tie 2', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19']),
  ('control: correction wins', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale'),
  ('control: only future', ([[9, '1.30']], 7, None), 'ERR:no-rate'),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40'])],
 [('regression correction-tie 1', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('regression correction-tie 2', ([[11, '1.1481'], [15, '1.1232'], [15, '1.0770']], 19, 8), [15, '1.0770']),
  ('partial repair guard 2',
   ([[12, '1.1568'], [4, '1.1957'], [11, '1.1716'], [12, '1.1097'], [12, '1.2564'], [12, '1.0967']], 12, 3),
   [12, '1.0967']),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05']),
  ('control: age boundary', ([[4, '1.11'], [12, '1.50']], 8, 4), [4, '1.11']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate')],
 [('regression correction-tie 1',
   ([[5, '1.1041'], [16, '1.2924'], [15, '1.0087'], [9, '1.2750'], [15, '1.0502']], 15, 3), [15, '1.0502']),
  ('regression correction-tie 2',
   ([[12, '1.1568'], [4, '1.1957'], [11, '1.1716'], [12, '1.1097'], [12, '1.2564'], [12, '1.0967']], 12, 3),
   [12, '1.0967']),
  ('partial repair guard 1',
   ([[20, '1.0695'], [16, '1.2883'], [6, '1.1478'], [16, '1.2014'], [16, '1.0090']], 19, None), [16, '1.0090']),
  ('partial repair guard 2', ([[0, '1.0555'], [9, '1.2910'], [9, '1.0280']], 19, None), [9, '1.0280']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate'),
  ('control: out of order feed', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19']),
  ('control: correction wins', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale')],
 [('regression correction-tie 1',
   ([[0, '1.1341'], [13, '1.0696'], [19, '1.0299'], [1, '1.2655'], [13, '1.2166'], [12, '1.2746']], 17, 5),
   [13, '1.2166']),
  ('regression correction-tie 2', ([[2, '1.2352'], [14, '1.0590'], [14, '1.1200'], [3, '1.2701']], 14, 8),
   [14, '1.1200']),
  ('partial repair guard 1', ([[16, '1.2855'], [6, '1.2676'], [6, '1.2544'], [0, '1.0664'], [11, '1.1456']], 6, 2),
   [6, '1.2544']),
  ('partial repair guard 2', ([[1, '1.2244'], [14, '1.2485'], [14, '1.0961']], 14, 0), [14, '1.0961']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale'),
  ('control: only future', ([[9, '1.30']], 7, None), 'ERR:no-rate'),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05'])],
 [('regression correction-tie 1',
   ([[20, '1.0695'], [16, '1.2883'], [6, '1.1478'], [16, '1.2014'], [16, '1.0090']], 19, None), [16, '1.0090']),
  ('regression correction-tie 2', ([[0, '1.0555'], [9, '1.2910'], [9, '1.0280']], 19, None), [9, '1.0280']),
  ('partial repair guard 1', ([[18, '1.1694'], [18, '1.1305'], [15, '1.1997'], [19, '1.0173'], [16, '1.0481']], 18, 8),
   [18, '1.1305']),
  ('partial repair guard 2', ([[19, '1.0893'], [0, '1.2923'], [0, '1.2821']], 0, 5), [0, '1.2821']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05']),
  ('control: age boundary', ([[4, '1.11'], [12, '1.50']], 8, 4), [4, '1.11']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate'),
  ('control: out of order feed', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19'])]]
for label, args, expected in cases[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 correction-tie 1[5, '1.21'][5, '1.20']Failed
regression correction-tie 2[6, '1.26'][6, '1.19']Failed
control: correction wins[5, '1.20'][5, '1.21']Failed
control: staleERR:staleERR:stalePassed
control: only futureERR:no-rateERR:no-ratePassed
control: same instant zero age[7, '1.40'][7, '1.40']Passed

SHA-256 / 99472aeb4caa3b406b807f9757bf95a0111536d5dad309720878f242b916a61a

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(ticks, trade_ts, max_age):
    best = None
    for ts, r in sorted(ticks):
        if ts <= trade_ts and (best is None or ts >= best[0]):
            best = [ts, r]
    if best is None: return 'ERR:no-rate'
    if max_age is not None and trade_ts - best[0] > max_age: return 'ERR:stale'
    return best
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression correction-tie 1', ([[1, '1.10'], [5, '1.21'], [5, '1.20']], 7, 3), [5, '1.20']),
  ('regression correction-tie 2', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19']),
  ('control: correction wins', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale'),
  ('control: only future', ([[9, '1.30']], 7, None), 'ERR:no-rate'),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40'])],
 [('regression correction-tie 1', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('regression correction-tie 2', ([[11, '1.1481'], [15, '1.1232'], [15, '1.0770']], 19, 8), [15, '1.0770']),
  ('partial repair guard 2',
   ([[12, '1.1568'], [4, '1.1957'], [11, '1.1716'], [12, '1.1097'], [12, '1.2564'], [12, '1.0967']], 12, 3),
   [12, '1.0967']),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05']),
  ('control: age boundary', ([[4, '1.11'], [12, '1.50']], 8, 4), [4, '1.11']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate')],
 [('regression correction-tie 1',
   ([[5, '1.1041'], [16, '1.2924'], [15, '1.0087'], [9, '1.2750'], [15, '1.0502']], 15, 3), [15, '1.0502']),
  ('regression correction-tie 2',
   ([[12, '1.1568'], [4, '1.1957'], [11, '1.1716'], [12, '1.1097'], [12, '1.2564'], [12, '1.0967']], 12, 3),
   [12, '1.0967']),
  ('partial repair guard 1',
   ([[20, '1.0695'], [16, '1.2883'], [6, '1.1478'], [16, '1.2014'], [16, '1.0090']], 19, None), [16, '1.0090']),
  ('partial repair guard 2', ([[0, '1.0555'], [9, '1.2910'], [9, '1.0280']], 19, None), [9, '1.0280']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate'),
  ('control: out of order feed', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19']),
  ('control: correction wins', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale')],
 [('regression correction-tie 1',
   ([[0, '1.1341'], [13, '1.0696'], [19, '1.0299'], [1, '1.2655'], [13, '1.2166'], [12, '1.2746']], 17, 5),
   [13, '1.2166']),
  ('regression correction-tie 2', ([[2, '1.2352'], [14, '1.0590'], [14, '1.1200'], [3, '1.2701']], 14, 8),
   [14, '1.1200']),
  ('partial repair guard 1', ([[16, '1.2855'], [6, '1.2676'], [6, '1.2544'], [0, '1.0664'], [11, '1.1456']], 6, 2),
   [6, '1.2544']),
  ('partial repair guard 2', ([[1, '1.2244'], [14, '1.2485'], [14, '1.0961']], 14, 0), [14, '1.0961']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale'),
  ('control: only future', ([[9, '1.30']], 7, None), 'ERR:no-rate'),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05'])],
 [('regression correction-tie 1',
   ([[20, '1.0695'], [16, '1.2883'], [6, '1.1478'], [16, '1.2014'], [16, '1.0090']], 19, None), [16, '1.0090']),
  ('regression correction-tie 2', ([[0, '1.0555'], [9, '1.2910'], [9, '1.0280']], 19, None), [9, '1.0280']),
  ('partial repair guard 1', ([[18, '1.1694'], [18, '1.1305'], [15, '1.1997'], [19, '1.0173'], [16, '1.0481']], 18, 8),
   [18, '1.1305']),
  ('partial repair guard 2', ([[19, '1.0893'], [0, '1.2923'], [0, '1.2821']], 0, 5), [0, '1.2821']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05']),
  ('control: age boundary', ([[4, '1.11'], [12, '1.50']], 8, 4), [4, '1.11']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate'),
  ('control: out of order feed', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19'])]]
for label, args, expected in cases[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 correction-tie 1[5, '1.21'][5, '1.20']Failed
regression correction-tie 2[6, '1.26'][6, '1.19']Failed
control: correction wins[5, '1.21'][5, '1.21']Passed
control: staleERR:staleERR:stalePassed
control: only futureERR:no-rateERR:no-ratePassed
control: same instant zero age[7, '1.40'][7, '1.40']Passed

SHA-256 / b8b49156abf5dc0117e811a211c18290fbc60cab558621005af20710cec42b2b

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(ticks, trade_ts, max_age):
    best = None
    for ts, r in ticks:
        if ts <= trade_ts and (best is None or ts >= best[0]):
            best = [ts, r]
    if best is None: return 'ERR:no-rate'
    if max_age is not None and trade_ts - best[0] > max_age: return 'ERR:stale'
    return best
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression correction-tie 1', ([[1, '1.10'], [5, '1.21'], [5, '1.20']], 7, 3), [5, '1.20']),
  ('regression correction-tie 2', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19']),
  ('control: correction wins', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale'),
  ('control: only future', ([[9, '1.30']], 7, None), 'ERR:no-rate'),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40'])],
 [('regression correction-tie 1', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('regression correction-tie 2', ([[11, '1.1481'], [15, '1.1232'], [15, '1.0770']], 19, 8), [15, '1.0770']),
  ('partial repair guard 2',
   ([[12, '1.1568'], [4, '1.1957'], [11, '1.1716'], [12, '1.1097'], [12, '1.2564'], [12, '1.0967']], 12, 3),
   [12, '1.0967']),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05']),
  ('control: age boundary', ([[4, '1.11'], [12, '1.50']], 8, 4), [4, '1.11']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate')],
 [('regression correction-tie 1',
   ([[5, '1.1041'], [16, '1.2924'], [15, '1.0087'], [9, '1.2750'], [15, '1.0502']], 15, 3), [15, '1.0502']),
  ('regression correction-tie 2',
   ([[12, '1.1568'], [4, '1.1957'], [11, '1.1716'], [12, '1.1097'], [12, '1.2564'], [12, '1.0967']], 12, 3),
   [12, '1.0967']),
  ('partial repair guard 1',
   ([[20, '1.0695'], [16, '1.2883'], [6, '1.1478'], [16, '1.2014'], [16, '1.0090']], 19, None), [16, '1.0090']),
  ('partial repair guard 2', ([[0, '1.0555'], [9, '1.2910'], [9, '1.0280']], 19, None), [9, '1.0280']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate'),
  ('control: out of order feed', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19']),
  ('control: correction wins', ([[1, '1.10'], [5, '1.20'], [5, '1.21'], [9, '1.30']], 7, 3), [5, '1.21']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale')],
 [('regression correction-tie 1',
   ([[0, '1.1341'], [13, '1.0696'], [19, '1.0299'], [1, '1.2655'], [13, '1.2166'], [12, '1.2746']], 17, 5),
   [13, '1.2166']),
  ('regression correction-tie 2', ([[2, '1.2352'], [14, '1.0590'], [14, '1.1200'], [3, '1.2701']], 14, 8),
   [14, '1.1200']),
  ('partial repair guard 1', ([[16, '1.2855'], [6, '1.2676'], [6, '1.2544'], [0, '1.0664'], [11, '1.1456']], 6, 2),
   [6, '1.2544']),
  ('partial repair guard 2', ([[1, '1.2244'], [14, '1.2485'], [14, '1.0961']], 14, 0), [14, '1.0961']),
  ('control: stale', ([[1, '1.10'], [9, '1.30']], 7, 3), 'ERR:stale'),
  ('control: only future', ([[9, '1.30']], 7, None), 'ERR:no-rate'),
  ('control: same instant zero age', ([[7, '1.40']], 7, 0), [7, '1.40']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05'])],
 [('regression correction-tie 1',
   ([[20, '1.0695'], [16, '1.2883'], [6, '1.1478'], [16, '1.2014'], [16, '1.0090']], 19, None), [16, '1.0090']),
  ('regression correction-tie 2', ([[0, '1.0555'], [9, '1.2910'], [9, '1.0280']], 19, None), [9, '1.0280']),
  ('partial repair guard 1', ([[18, '1.1694'], [18, '1.1305'], [15, '1.1997'], [19, '1.0173'], [16, '1.0481']], 18, 8),
   [18, '1.1305']),
  ('partial repair guard 2', ([[19, '1.0893'], [0, '1.2923'], [0, '1.2821']], 0, 5), [0, '1.2821']),
  ('control: unlimited age', ([[0, '1.05']], 20, None), [0, '1.05']),
  ('control: age boundary', ([[4, '1.11'], [12, '1.50']], 8, 4), [4, '1.11']),
  ('control: empty feed', ([], 3, 5), 'ERR:no-rate'),
  ('control: out of order feed', ([[6, '1.26'], [2, '1.22'], [6, '1.19']], 6, 1), [6, '1.19'])]]
for label, args, expected in cases[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 correction-tie 1[5, '1.20'][5, '1.20']Passed
regression correction-tie 2[6, '1.19'][6, '1.19']Passed
control: correction wins[5, '1.21'][5, '1.21']Passed
control: staleERR:staleERR:stalePassed
control: only futureERR:no-rateERR:no-ratePassed
control: same instant zero age[7, '1.40'][7, '1.40']Passed

SHA-256 / 3dbd74c046fe8e6be6172c79f8247b8b5f321fb7b82ef95c6933f376f96a72c8

Verification & scope

A deterministic, bounded teaching model with a stipulated toy contract; it makes no claim of conformance to any real regulation, standard, or institution's rules. 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:47:00.173480+00:00.

Case digest / ba8e0ce9d3c45a55377b2cdc2e8560fb7d611506b894602e1ae1af3ecb10601a