FA-60996 / Bond day-count conventions / Open access
Business day adjustment conventions: modified preceding never checks the month · case 01
Dates at the start of a month roll back into the previous month.
ROOT CAUSE
The MP branch was copied from P and lacks the month-change guard.
VERIFIED REPAIR
Roll forward from the original date if rolling back changes the month.
Unsuccessful approach: Guarding only a change of year still lets ordinary month starts slip back.
Case contract
Inputs a date [y,m,d], a convention code and a list of holiday dates. Saturdays, Sundays and holidays are non-business days. F rolls forward, P rolls back, MF rolls forward unless that changes the month in which case it rolls back, MP rolls back unless that changes the month in which case it rolls forward, U leaves the date unadjusted; other codes return "unknown convention". Return the adjusted [y,m,d].
Why this case matters
Bond accrual and pricing systems depend on exact day-count arithmetic; a single-day error changes settlement cash.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
from fractions import Fraction
N = 1
observations = []
def solve(d, conv, holidays):
D = datetime.date(*d)
H = {datetime.date(*h) for h in holidays}
def bad(x):
return x.weekday() >= 5 or x in H
def roll(x, step):
while bad(x):
x += datetime.timedelta(days=step)
return x
if conv == 'F':
r = roll(D, 1)
elif conv == 'MF':
r = roll(D, 1)
if r.month != D.month:
r = roll(D, -1)
elif conv == 'P':
r = roll(D, -1)
elif conv == 'MP':
r = roll(D, -1)
elif conv == 'U':
r = D
else:
return 'unknown convention'
return [r.year, r.month, r.day]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression modified preceding guard 1', [[2028, 2, 1], 'MP', [[2028, 2, 1]]], [2028, 2, 2]], ['regression modified preceding guard 2', [[2022, 10, 1], 'MP', [[2022, 10, 1], [2022, 10, 1]]], [2022, 10, 3]], ['partial repair probe 1', [[2030, 3, 3], 'MP', [[2030, 3, 3], [2030, 3, 1], [2030, 3, 4]]], [2030, 3, 5]], ['partial repair probe 2', [[2024, 6, 1], 'MP', []], [2024, 6, 3]], ['boundary control 1', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['boundary control 2', [[2024, 5, 15], 'X', []], 'unknown convention'], ['normal control 1', [[2028, 8, 29], 'MF', [[2028, 8, 30]]], [2028, 8, 29]], ['normal control 2', [[2019, 10, 31], 'MF', [[2019, 10, 28], [2019, 11, 2], [2019, 11, 2]]], [2019, 10, 31]]], [['regression modified preceding guard 1', [[2023, 1, 1], 'MP', [[2023, 1, 5], [2022, 12, 31]]], [2023, 1, 2]], ['regression modified preceding guard 2', [[2019, 1, 2], 'MP', [[2019, 1, 2], [2019, 1, 1], [2019, 1, 3]]], [2019, 1, 4]], ['partial repair probe 1', [[2024, 6, 3], 'MP', [[2024, 6, 3], [2024, 6, 2]]], [2024, 6, 4]], ['partial repair probe 2', [[2025, 10, 1], 'MP', [[2025, 10, 2], [2025, 10, 5], [2025, 10, 1]]], [2025, 10, 3]], ['boundary control 1', [[2024, 5, 15], 'X', []], 'unknown convention'], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2025, 12, 29], 'MF', []], [2025, 12, 29]], ['normal control 2', [[2024, 6, 2], 'F', [[2024, 5, 30], [2024, 5, 31]]], [2024, 6, 3]]], [['regression modified preceding guard 1', [[2020, 3, 1], 'MP', [[2020, 2, 27]]], [2020, 3, 2]], ['regression modified preceding guard 2', [[2026, 3, 1], 'MP', [[2026, 3, 4], [2026, 2, 27], [2026, 3, 2]]], [2026, 3, 3]], ['partial repair probe 1', [[2019, 12, 1], 'MP', [[2019, 12, 1]]], [2019, 12, 2]], ['partial repair probe 2', [[2022, 10, 1], 'MP', [[2022, 10, 4], [2022, 10, 4], [2022, 10, 1]]], [2022, 10, 3]], ['boundary control 1', [[2024, 5, 15], 'X', []], 'unknown convention'], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2026, 10, 31], 'MF', [[2026, 10, 30], [2026, 11, 1], [2026, 11, 2]]], [2026, 10, 29]], ['normal control 2', [[2021, 12, 30], 'MP', [[2022, 1, 2]]], [2021, 12, 30]]], [['regression modified preceding guard 1', [[2029, 6, 3], 'MP', [[2029, 6, 6], [2029, 6, 2], [2029, 6, 1]]], [2029, 6, 4]], ['regression modified preceding guard 2', [[2028, 1, 2], 'MP', [[2028, 1, 1]]], [2028, 1, 3]], ['partial repair probe 1', [[2027, 8, 1], 'MP', [[2027, 8, 5], [2027, 8, 2], [2027, 8, 1]]], [2027, 8, 3]], ['partial repair probe 2', [[2025, 11, 3], 'MP', [[2025, 11, 6], [2025, 11, 3]]], [2025, 11, 4]], ['boundary control 1', [[2024, 5, 15], 'F', [[2024, 5, 15]]], [2024, 5, 16]], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2020, 12, 31], 'MP', [[2021, 1, 1], [2021, 1, 3]]], [2020, 12, 31]], ['normal control 2', [[2028, 4, 30], 'F', [[2028, 5, 3]]], [2028, 5, 1]]], [['regression modified preceding guard 1', [[2029, 12, 2], 'MP', []], [2029, 12, 3]], ['regression modified preceding guard 2', [[2027, 1, 1], 'MP', [[2027, 1, 1], [2026, 12, 31], [2027, 1, 1]]], [2027, 1, 4]], ['partial repair probe 1', [[2025, 10, 1], 'MP', [[2025, 10, 1], [2025, 10, 4], [2025, 10, 4]]], [2025, 10, 2]], ['partial repair probe 2', [[2029, 4, 1], 'MP', [[2029, 4, 2], [2029, 3, 29], [2029, 4, 1]]], [2029, 4, 3]], ['boundary control 1', [[2024, 3, 30], 'MF', []], [2024, 3, 29]], ['boundary control 2', [[2024, 5, 15], 'F', [[2024, 5, 15]]], [2024, 5, 16]], ['normal control 1', [[2021, 6, 2], 'MF', [[2021, 6, 5], [2021, 6, 4]]], [2021, 6, 2]], ['normal control 2', [[2027, 5, 31], 'MF', [[2027, 6, 1]]], [2027, 5, 31]]]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression modified preceding guard 1 | [2028, 1, 31] | [2028, 2, 2] | Failed |
| regression modified preceding guard 2 | [2022, 9, 30] | [2022, 10, 3] | Failed |
| partial repair probe 1 | [2030, 2, 28] | [2030, 3, 5] | Failed |
| partial repair probe 2 | [2024, 5, 31] | [2024, 6, 3] | Failed |
| boundary control 1 | [2024, 12, 30] | [2024, 12, 30] | Passed |
| boundary control 2 | unknown convention | unknown convention | Passed |
| normal control 1 | [2028, 8, 29] | [2028, 8, 29] | Passed |
| normal control 2 | [2019, 10, 31] | [2019, 10, 31] | Passed |
SHA-256 / 70d29ebdf23e155614eb6d04d2471b070eeca493860b6383c33b4fa7bce06b12
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
from fractions import Fraction
N = 1
observations = []
def solve(d, conv, holidays):
D = datetime.date(*d)
H = {datetime.date(*h) for h in holidays}
def bad(x):
return x.weekday() >= 5 or x in H
def roll(x, step):
while bad(x):
x += datetime.timedelta(days=step)
return x
if conv == 'F':
r = roll(D, 1)
elif conv == 'MF':
r = roll(D, 1)
if r.month != D.month:
r = roll(D, -1)
elif conv == 'P':
r = roll(D, -1)
elif conv == 'MP':
r = roll(D, -1)
if r.year != D.year:
r = roll(D, 1)
elif conv == 'U':
r = D
else:
return 'unknown convention'
return [r.year, r.month, r.day]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression modified preceding guard 1', [[2028, 2, 1], 'MP', [[2028, 2, 1]]], [2028, 2, 2]], ['regression modified preceding guard 2', [[2022, 10, 1], 'MP', [[2022, 10, 1], [2022, 10, 1]]], [2022, 10, 3]], ['partial repair probe 1', [[2030, 3, 3], 'MP', [[2030, 3, 3], [2030, 3, 1], [2030, 3, 4]]], [2030, 3, 5]], ['partial repair probe 2', [[2024, 6, 1], 'MP', []], [2024, 6, 3]], ['boundary control 1', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['boundary control 2', [[2024, 5, 15], 'X', []], 'unknown convention'], ['normal control 1', [[2028, 8, 29], 'MF', [[2028, 8, 30]]], [2028, 8, 29]], ['normal control 2', [[2019, 10, 31], 'MF', [[2019, 10, 28], [2019, 11, 2], [2019, 11, 2]]], [2019, 10, 31]]], [['regression modified preceding guard 1', [[2023, 1, 1], 'MP', [[2023, 1, 5], [2022, 12, 31]]], [2023, 1, 2]], ['regression modified preceding guard 2', [[2019, 1, 2], 'MP', [[2019, 1, 2], [2019, 1, 1], [2019, 1, 3]]], [2019, 1, 4]], ['partial repair probe 1', [[2024, 6, 3], 'MP', [[2024, 6, 3], [2024, 6, 2]]], [2024, 6, 4]], ['partial repair probe 2', [[2025, 10, 1], 'MP', [[2025, 10, 2], [2025, 10, 5], [2025, 10, 1]]], [2025, 10, 3]], ['boundary control 1', [[2024, 5, 15], 'X', []], 'unknown convention'], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2025, 12, 29], 'MF', []], [2025, 12, 29]], ['normal control 2', [[2024, 6, 2], 'F', [[2024, 5, 30], [2024, 5, 31]]], [2024, 6, 3]]], [['regression modified preceding guard 1', [[2020, 3, 1], 'MP', [[2020, 2, 27]]], [2020, 3, 2]], ['regression modified preceding guard 2', [[2026, 3, 1], 'MP', [[2026, 3, 4], [2026, 2, 27], [2026, 3, 2]]], [2026, 3, 3]], ['partial repair probe 1', [[2019, 12, 1], 'MP', [[2019, 12, 1]]], [2019, 12, 2]], ['partial repair probe 2', [[2022, 10, 1], 'MP', [[2022, 10, 4], [2022, 10, 4], [2022, 10, 1]]], [2022, 10, 3]], ['boundary control 1', [[2024, 5, 15], 'X', []], 'unknown convention'], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2026, 10, 31], 'MF', [[2026, 10, 30], [2026, 11, 1], [2026, 11, 2]]], [2026, 10, 29]], ['normal control 2', [[2021, 12, 30], 'MP', [[2022, 1, 2]]], [2021, 12, 30]]], [['regression modified preceding guard 1', [[2029, 6, 3], 'MP', [[2029, 6, 6], [2029, 6, 2], [2029, 6, 1]]], [2029, 6, 4]], ['regression modified preceding guard 2', [[2028, 1, 2], 'MP', [[2028, 1, 1]]], [2028, 1, 3]], ['partial repair probe 1', [[2027, 8, 1], 'MP', [[2027, 8, 5], [2027, 8, 2], [2027, 8, 1]]], [2027, 8, 3]], ['partial repair probe 2', [[2025, 11, 3], 'MP', [[2025, 11, 6], [2025, 11, 3]]], [2025, 11, 4]], ['boundary control 1', [[2024, 5, 15], 'F', [[2024, 5, 15]]], [2024, 5, 16]], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2020, 12, 31], 'MP', [[2021, 1, 1], [2021, 1, 3]]], [2020, 12, 31]], ['normal control 2', [[2028, 4, 30], 'F', [[2028, 5, 3]]], [2028, 5, 1]]], [['regression modified preceding guard 1', [[2029, 12, 2], 'MP', []], [2029, 12, 3]], ['regression modified preceding guard 2', [[2027, 1, 1], 'MP', [[2027, 1, 1], [2026, 12, 31], [2027, 1, 1]]], [2027, 1, 4]], ['partial repair probe 1', [[2025, 10, 1], 'MP', [[2025, 10, 1], [2025, 10, 4], [2025, 10, 4]]], [2025, 10, 2]], ['partial repair probe 2', [[2029, 4, 1], 'MP', [[2029, 4, 2], [2029, 3, 29], [2029, 4, 1]]], [2029, 4, 3]], ['boundary control 1', [[2024, 3, 30], 'MF', []], [2024, 3, 29]], ['boundary control 2', [[2024, 5, 15], 'F', [[2024, 5, 15]]], [2024, 5, 16]], ['normal control 1', [[2021, 6, 2], 'MF', [[2021, 6, 5], [2021, 6, 4]]], [2021, 6, 2]], ['normal control 2', [[2027, 5, 31], 'MF', [[2027, 6, 1]]], [2027, 5, 31]]]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression modified preceding guard 1 | [2028, 1, 31] | [2028, 2, 2] | Failed |
| regression modified preceding guard 2 | [2022, 9, 30] | [2022, 10, 3] | Failed |
| partial repair probe 1 | [2030, 2, 28] | [2030, 3, 5] | Failed |
| partial repair probe 2 | [2024, 5, 31] | [2024, 6, 3] | Failed |
| boundary control 1 | [2024, 12, 30] | [2024, 12, 30] | Passed |
| boundary control 2 | unknown convention | unknown convention | Passed |
| normal control 1 | [2028, 8, 29] | [2028, 8, 29] | Passed |
| normal control 2 | [2019, 10, 31] | [2019, 10, 31] | Passed |
SHA-256 / c2691133e311ff32d31def29b648ce6cbc8159158fad489e6309655268275420
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
from fractions import Fraction
N = 1
observations = []
def solve(d, conv, holidays):
D = datetime.date(*d)
H = {datetime.date(*h) for h in holidays}
def bad(x):
return x.weekday() >= 5 or x in H
def roll(x, step):
while bad(x):
x += datetime.timedelta(days=step)
return x
if conv == 'F':
r = roll(D, 1)
elif conv == 'MF':
r = roll(D, 1)
if r.month != D.month:
r = roll(D, -1)
elif conv == 'P':
r = roll(D, -1)
elif conv == 'MP':
r = roll(D, -1)
if r.month != D.month:
r = roll(D, 1)
elif conv == 'U':
r = D
else:
return 'unknown convention'
return [r.year, r.month, r.day]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression modified preceding guard 1', [[2028, 2, 1], 'MP', [[2028, 2, 1]]], [2028, 2, 2]], ['regression modified preceding guard 2', [[2022, 10, 1], 'MP', [[2022, 10, 1], [2022, 10, 1]]], [2022, 10, 3]], ['partial repair probe 1', [[2030, 3, 3], 'MP', [[2030, 3, 3], [2030, 3, 1], [2030, 3, 4]]], [2030, 3, 5]], ['partial repair probe 2', [[2024, 6, 1], 'MP', []], [2024, 6, 3]], ['boundary control 1', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['boundary control 2', [[2024, 5, 15], 'X', []], 'unknown convention'], ['normal control 1', [[2028, 8, 29], 'MF', [[2028, 8, 30]]], [2028, 8, 29]], ['normal control 2', [[2019, 10, 31], 'MF', [[2019, 10, 28], [2019, 11, 2], [2019, 11, 2]]], [2019, 10, 31]]], [['regression modified preceding guard 1', [[2023, 1, 1], 'MP', [[2023, 1, 5], [2022, 12, 31]]], [2023, 1, 2]], ['regression modified preceding guard 2', [[2019, 1, 2], 'MP', [[2019, 1, 2], [2019, 1, 1], [2019, 1, 3]]], [2019, 1, 4]], ['partial repair probe 1', [[2024, 6, 3], 'MP', [[2024, 6, 3], [2024, 6, 2]]], [2024, 6, 4]], ['partial repair probe 2', [[2025, 10, 1], 'MP', [[2025, 10, 2], [2025, 10, 5], [2025, 10, 1]]], [2025, 10, 3]], ['boundary control 1', [[2024, 5, 15], 'X', []], 'unknown convention'], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2025, 12, 29], 'MF', []], [2025, 12, 29]], ['normal control 2', [[2024, 6, 2], 'F', [[2024, 5, 30], [2024, 5, 31]]], [2024, 6, 3]]], [['regression modified preceding guard 1', [[2020, 3, 1], 'MP', [[2020, 2, 27]]], [2020, 3, 2]], ['regression modified preceding guard 2', [[2026, 3, 1], 'MP', [[2026, 3, 4], [2026, 2, 27], [2026, 3, 2]]], [2026, 3, 3]], ['partial repair probe 1', [[2019, 12, 1], 'MP', [[2019, 12, 1]]], [2019, 12, 2]], ['partial repair probe 2', [[2022, 10, 1], 'MP', [[2022, 10, 4], [2022, 10, 4], [2022, 10, 1]]], [2022, 10, 3]], ['boundary control 1', [[2024, 5, 15], 'X', []], 'unknown convention'], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2026, 10, 31], 'MF', [[2026, 10, 30], [2026, 11, 1], [2026, 11, 2]]], [2026, 10, 29]], ['normal control 2', [[2021, 12, 30], 'MP', [[2022, 1, 2]]], [2021, 12, 30]]], [['regression modified preceding guard 1', [[2029, 6, 3], 'MP', [[2029, 6, 6], [2029, 6, 2], [2029, 6, 1]]], [2029, 6, 4]], ['regression modified preceding guard 2', [[2028, 1, 2], 'MP', [[2028, 1, 1]]], [2028, 1, 3]], ['partial repair probe 1', [[2027, 8, 1], 'MP', [[2027, 8, 5], [2027, 8, 2], [2027, 8, 1]]], [2027, 8, 3]], ['partial repair probe 2', [[2025, 11, 3], 'MP', [[2025, 11, 6], [2025, 11, 3]]], [2025, 11, 4]], ['boundary control 1', [[2024, 5, 15], 'F', [[2024, 5, 15]]], [2024, 5, 16]], ['boundary control 2', [[2024, 12, 28], 'F', []], [2024, 12, 30]], ['normal control 1', [[2020, 12, 31], 'MP', [[2021, 1, 1], [2021, 1, 3]]], [2020, 12, 31]], ['normal control 2', [[2028, 4, 30], 'F', [[2028, 5, 3]]], [2028, 5, 1]]], [['regression modified preceding guard 1', [[2029, 12, 2], 'MP', []], [2029, 12, 3]], ['regression modified preceding guard 2', [[2027, 1, 1], 'MP', [[2027, 1, 1], [2026, 12, 31], [2027, 1, 1]]], [2027, 1, 4]], ['partial repair probe 1', [[2025, 10, 1], 'MP', [[2025, 10, 1], [2025, 10, 4], [2025, 10, 4]]], [2025, 10, 2]], ['partial repair probe 2', [[2029, 4, 1], 'MP', [[2029, 4, 2], [2029, 3, 29], [2029, 4, 1]]], [2029, 4, 3]], ['boundary control 1', [[2024, 3, 30], 'MF', []], [2024, 3, 29]], ['boundary control 2', [[2024, 5, 15], 'F', [[2024, 5, 15]]], [2024, 5, 16]], ['normal control 1', [[2021, 6, 2], 'MF', [[2021, 6, 5], [2021, 6, 4]]], [2021, 6, 2]], ['normal control 2', [[2027, 5, 31], 'MF', [[2027, 6, 1]]], [2027, 5, 31]]]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression modified preceding guard 1 | [2028, 2, 2] | [2028, 2, 2] | Passed |
| regression modified preceding guard 2 | [2022, 10, 3] | [2022, 10, 3] | Passed |
| partial repair probe 1 | [2030, 3, 5] | [2030, 3, 5] | Passed |
| partial repair probe 2 | [2024, 6, 3] | [2024, 6, 3] | Passed |
| boundary control 1 | [2024, 12, 30] | [2024, 12, 30] | Passed |
| boundary control 2 | unknown convention | unknown convention | Passed |
| normal control 1 | [2028, 8, 29] | [2028, 8, 29] | Passed |
| normal control 2 | [2019, 10, 31] | [2019, 10, 31] | Passed |
SHA-256 / d97a1f608f160af487f9ea3129abf90ad70212fbdaad78b86602bb298c52132d
Verification & scope
A deterministic toy contract stated explicitly in the contract field; no claim of conformance to any published convention text. 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:46:50.952386+00:00.
Case digest / f3d9b947a49a028ae847d10c4df793881d9108bc900c87342e2b1a31ae3fe057