FA-58766 / Loan amortization schedules / Open access
Installment due date calendar: year rollover · case 01
Installments after December keep the starting year.
ROOT CAUSE
The year offset counts installments instead of absolute months.
VERIFIED REPAIR
Derive the year from the zero-based absolute month.
Unsuccessful approach: Adding one before dividing moves November installments into the next year.
Case contract
x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.
Why this case matters
Amortization engines drive borrower statements, payoff quotes and investor remittances; a misplaced rounding step, boundary or ordering rule compounds across hundreds of periods.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
import calendar
N = 1
observations = []
def solve(x):
y, m, d = x['first_due']
hol = set(x['holidays'])
def bad(t):
return t.weekday() >= 5 or t.isoformat() in hol
out = []
for i in range(x['count']):
mm = m - 1 + i
yy = y + i // 12
mm = mm % 12 + 1
dd = min(d, calendar.monthrange(yy, mm)[1])
date = datetime.date(yy, mm, dd)
adj = date
if x['roll'] != 'none':
while bad(adj):
adj += datetime.timedelta(days=1)
if x['roll'] == 'modified_following' and adj.month != date.month:
adj = date
while bad(adj):
adj -= datetime.timedelta(days=1)
out.append(adj.isoformat())
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: year rollover', {'first_due': [2023, 9, 1], 'count': 13, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-05-31']}, ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02']], ['control 1', {'first_due': [2024, 2, 5], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-05-31', '2023-12-25']}, ['2024-02-05', '2024-03-05', '2024-04-05']], ['control 2', {'first_due': [2023, 8, 15], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-02-29', '2023-06-30']}, ['2023-08-15', '2023-09-15', '2023-10-16']], ['control 3', {'first_due': [2023, 3, 31], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-03-29']}, ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31']], ['control 4', {'first_due': [2024, 3, 15], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-05-31']}, ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15']], ['control 5', {'first_due': [2024, 2, 30], 'count': 3, 'roll': 'none', 'holidays': ['2023-12-25']}, ['2024-02-29', '2024-03-30', '2024-04-30']], ['control 6', {'first_due': [2023, 6, 15], 'count': 6, 'roll': 'none', 'holidays': ['2024-12-31']}, ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15']]], [['regression: year rollover', {'first_due': [2024, 9, 5], 'count': 13, 'roll': 'following', 'holidays': []}, ['2024-09-05', '2024-10-07', '2024-11-05', '2024-12-05', '2025-01-06', '2025-02-05', '2025-03-05', '2025-04-07', '2025-05-05', '2025-06-05', '2025-07-07', '2025-08-05', '2025-09-05']], ['control 1', {'first_due': [2023, 4, 5], 'count': 6, 'roll': 'following', 'holidays': ['2024-01-01', '2024-05-31', '2023-06-30']}, ['2023-04-05', '2023-05-05', '2023-06-05', '2023-07-05', '2023-08-07', '2023-09-05']], ['control 2', {'first_due': [2023, 6, 5], 'count': 6, 'roll': 'none', 'holidays': ['2024-01-01']}, ['2023-06-05', '2023-07-05', '2023-08-05', '2023-09-05', '2023-10-05', '2023-11-05']], ['control 3', {'first_due': [2023, 7, 30], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-12-31']}, ['2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30', '2023-11-30', '2023-12-29']], ['control 4', {'first_due': [2023, 3, 28], 'count': 3, 'roll': 'following', 'holidays': ['2024-05-31', '2023-06-30', '2023-12-25']}, ['2023-03-28', '2023-04-28', '2023-05-29']], ['control 5', {'first_due': [2024, 9, 15], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31', '2024-05-31']}, ['2024-09-16', '2024-10-15', '2024-11-15']], ['control 6', {'first_due': [2024, 4, 30], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-01-01', '2024-05-31']}, ['2024-04-30', '2024-05-30', '2024-06-28']]], [['regression: year rollover', {'first_due': [2024, 5, 1], 'count': 13, 'roll': 'none', 'holidays': []}, ['2024-05-01', '2024-06-01', '2024-07-01', '2024-08-01', '2024-09-01', '2024-10-01', '2024-11-01', '2024-12-01', '2025-01-01', '2025-02-01', '2025-03-01', '2025-04-01', '2025-05-01']], ['control 1', {'first_due': [2023, 9, 1], 'count': 3, 'roll': 'modified_following', 'holidays': []}, ['2023-09-01', '2023-10-02', '2023-11-01']], ['control 2', {'first_due': [2024, 10, 30], 'count': 3, 'roll': 'following', 'holidays': ['2024-05-31', '2023-06-30', '2024-03-29']}, ['2024-10-30', '2024-12-02', '2024-12-30']], ['control 3', {'first_due': [2023, 2, 5], 'count': 3, 'roll': 'none', 'holidays': ['2024-03-29']}, ['2023-02-05', '2023-03-05', '2023-04-05']], ['control 4', {'first_due': [2023, 8, 29], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-01-01', '2024-05-31', '2024-03-29']}, ['2023-08-29', '2023-09-29', '2023-10-30']], ['control 5', {'first_due': [2024, 3, 29], 'count': 3, 'roll': 'none', 'holidays': ['2023-12-25', '2024-01-01']}, ['2024-03-29', '2024-04-29', '2024-05-29']], ['control 6', {'first_due': [2024, 6, 15], 'count': 6, 'roll': 'following', 'holidays': ['2024-02-29', '2024-12-31']}, ['2024-06-17', '2024-07-15', '2024-08-15', '2024-09-16', '2024-10-15', '2024-11-15']]], [['regression: year rollover', {'first_due': [2024, 8, 15], 'count': 6, 'roll': 'modified_following', 'holidays': []}, ['2024-08-15', '2024-09-16', '2024-10-15', '2024-11-15', '2024-12-16', '2025-01-15']], ['control 1', {'first_due': [2023, 8, 1], 'count': 3, 'roll': 'following', 'holidays': []}, ['2023-08-01', '2023-09-01', '2023-10-02']], ['control 2', {'first_due': [2023, 2, 28], 'count': 3, 'roll': 'following', 'holidays': ['2023-12-25', '2024-05-31']}, ['2023-02-28', '2023-03-28', '2023-04-28']], ['control 3', {'first_due': [2024, 1, 5], 'count': 3, 'roll': 'none', 'holidays': ['2024-03-29', '2024-05-31']}, ['2024-01-05', '2024-02-05', '2024-03-05']], ['control 4', {'first_due': [2023, 4, 30], 'count': 3, 'roll': 'none', 'holidays': ['2023-06-30']}, ['2023-04-30', '2023-05-30', '2023-06-30']], ['control 5', {'first_due': [2023, 5, 30], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-01-01', '2024-03-29']}, ['2023-05-30', '2023-06-30', '2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30']], ['control 6', {'first_due': [2023, 7, 5], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31', '2024-05-31']}, ['2023-07-05', '2023-08-07', '2023-09-05']]], [['regression: year rollover', {'first_due': [2023, 10, 30], 'count': 13, 'roll': 'none', 'holidays': ['2024-01-01', '2024-03-29']}, ['2023-10-30', '2023-11-30', '2023-12-30', '2024-01-30', '2024-02-29', '2024-03-30', '2024-04-30', '2024-05-30', '2024-06-30', '2024-07-30', '2024-08-30', '2024-09-30', '2024-10-30']], ['control 1', {'first_due': [2024, 1, 29], 'count': 6, 'roll': 'none', 'holidays': []}, ['2024-01-29', '2024-02-29', '2024-03-29', '2024-04-29', '2024-05-29', '2024-06-29']], ['control 2', {'first_due': [2024, 8, 5], 'count': 3, 'roll': 'modified_following', 'holidays': []}, ['2024-08-05', '2024-09-05', '2024-10-07']], ['control 3', {'first_due': [2023, 10, 28], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31']}, ['2023-10-30', '2023-11-28', '2023-12-28']], ['control 4', {'first_due': [2023, 6, 30], 'count': 6, 'roll': 'modified_following', 'holidays': []}, ['2023-06-30', '2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30', '2023-11-30']], ['control 5', {'first_due': [2023, 1, 1], 'count': 3, 'roll': 'none', 'holidays': ['2024-12-31', '2024-02-29', '2024-01-01']}, ['2023-01-01', '2023-02-01', '2023-03-01']], ['control 6', {'first_due': [2023, 2, 15], 'count': 6, 'roll': 'none', 'holidays': ['2024-12-31']}, ['2023-02-15', '2023-03-15', '2023-04-15', '2023-05-15', '2023-06-15', '2023-07-15']]]]
for label, args, expected in fixtures[N-1]:
try:
actual = solve(args)
except Exception as exc:
actual = 'raised ' + type(exc).__name__
check(label, actual, 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: year rollover | ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2023-01-02', '2023-02-01', '2023-03-01', '2023-04-03', '2023-05-01', '2023-06-01', '2023-07-03', '2023-08-01', '2024-09-02'] | ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02'] | Failed |
| control 1 | ['2024-02-05', '2024-03-05', '2024-04-05'] | ['2024-02-05', '2024-03-05', '2024-04-05'] | Passed |
| control 2 | ['2023-08-15', '2023-09-15', '2023-10-16'] | ['2023-08-15', '2023-09-15', '2023-10-16'] | Passed |
| control 3 | ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31'] | ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31'] | Passed |
| control 4 | ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15'] | ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15'] | Passed |
| control 5 | ['2024-02-29', '2024-03-30', '2024-04-30'] | ['2024-02-29', '2024-03-30', '2024-04-30'] | Passed |
| control 6 | ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15'] | ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15'] | Passed |
SHA-256 / 33238ea9e0238235e5509e5df3000d91247df2945a7fe82a1db74225792a7a11
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
import calendar
N = 1
observations = []
def solve(x):
y, m, d = x['first_due']
hol = set(x['holidays'])
def bad(t):
return t.weekday() >= 5 or t.isoformat() in hol
out = []
for i in range(x['count']):
mm = m - 1 + i
yy = y + (mm + 1) // 12
mm = mm % 12 + 1
dd = min(d, calendar.monthrange(yy, mm)[1])
date = datetime.date(yy, mm, dd)
adj = date
if x['roll'] != 'none':
while bad(adj):
adj += datetime.timedelta(days=1)
if x['roll'] == 'modified_following' and adj.month != date.month:
adj = date
while bad(adj):
adj -= datetime.timedelta(days=1)
out.append(adj.isoformat())
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: year rollover', {'first_due': [2023, 9, 1], 'count': 13, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-05-31']}, ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02']], ['control 1', {'first_due': [2024, 2, 5], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-05-31', '2023-12-25']}, ['2024-02-05', '2024-03-05', '2024-04-05']], ['control 2', {'first_due': [2023, 8, 15], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-02-29', '2023-06-30']}, ['2023-08-15', '2023-09-15', '2023-10-16']], ['control 3', {'first_due': [2023, 3, 31], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-03-29']}, ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31']], ['control 4', {'first_due': [2024, 3, 15], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-05-31']}, ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15']], ['control 5', {'first_due': [2024, 2, 30], 'count': 3, 'roll': 'none', 'holidays': ['2023-12-25']}, ['2024-02-29', '2024-03-30', '2024-04-30']], ['control 6', {'first_due': [2023, 6, 15], 'count': 6, 'roll': 'none', 'holidays': ['2024-12-31']}, ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15']]], [['regression: year rollover', {'first_due': [2024, 9, 5], 'count': 13, 'roll': 'following', 'holidays': []}, ['2024-09-05', '2024-10-07', '2024-11-05', '2024-12-05', '2025-01-06', '2025-02-05', '2025-03-05', '2025-04-07', '2025-05-05', '2025-06-05', '2025-07-07', '2025-08-05', '2025-09-05']], ['control 1', {'first_due': [2023, 4, 5], 'count': 6, 'roll': 'following', 'holidays': ['2024-01-01', '2024-05-31', '2023-06-30']}, ['2023-04-05', '2023-05-05', '2023-06-05', '2023-07-05', '2023-08-07', '2023-09-05']], ['control 2', {'first_due': [2023, 6, 5], 'count': 6, 'roll': 'none', 'holidays': ['2024-01-01']}, ['2023-06-05', '2023-07-05', '2023-08-05', '2023-09-05', '2023-10-05', '2023-11-05']], ['control 3', {'first_due': [2023, 7, 30], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-12-31']}, ['2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30', '2023-11-30', '2023-12-29']], ['control 4', {'first_due': [2023, 3, 28], 'count': 3, 'roll': 'following', 'holidays': ['2024-05-31', '2023-06-30', '2023-12-25']}, ['2023-03-28', '2023-04-28', '2023-05-29']], ['control 5', {'first_due': [2024, 9, 15], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31', '2024-05-31']}, ['2024-09-16', '2024-10-15', '2024-11-15']], ['control 6', {'first_due': [2024, 4, 30], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-01-01', '2024-05-31']}, ['2024-04-30', '2024-05-30', '2024-06-28']]], [['regression: year rollover', {'first_due': [2024, 5, 1], 'count': 13, 'roll': 'none', 'holidays': []}, ['2024-05-01', '2024-06-01', '2024-07-01', '2024-08-01', '2024-09-01', '2024-10-01', '2024-11-01', '2024-12-01', '2025-01-01', '2025-02-01', '2025-03-01', '2025-04-01', '2025-05-01']], ['control 1', {'first_due': [2023, 9, 1], 'count': 3, 'roll': 'modified_following', 'holidays': []}, ['2023-09-01', '2023-10-02', '2023-11-01']], ['control 2', {'first_due': [2024, 10, 30], 'count': 3, 'roll': 'following', 'holidays': ['2024-05-31', '2023-06-30', '2024-03-29']}, ['2024-10-30', '2024-12-02', '2024-12-30']], ['control 3', {'first_due': [2023, 2, 5], 'count': 3, 'roll': 'none', 'holidays': ['2024-03-29']}, ['2023-02-05', '2023-03-05', '2023-04-05']], ['control 4', {'first_due': [2023, 8, 29], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-01-01', '2024-05-31', '2024-03-29']}, ['2023-08-29', '2023-09-29', '2023-10-30']], ['control 5', {'first_due': [2024, 3, 29], 'count': 3, 'roll': 'none', 'holidays': ['2023-12-25', '2024-01-01']}, ['2024-03-29', '2024-04-29', '2024-05-29']], ['control 6', {'first_due': [2024, 6, 15], 'count': 6, 'roll': 'following', 'holidays': ['2024-02-29', '2024-12-31']}, ['2024-06-17', '2024-07-15', '2024-08-15', '2024-09-16', '2024-10-15', '2024-11-15']]], [['regression: year rollover', {'first_due': [2024, 8, 15], 'count': 6, 'roll': 'modified_following', 'holidays': []}, ['2024-08-15', '2024-09-16', '2024-10-15', '2024-11-15', '2024-12-16', '2025-01-15']], ['control 1', {'first_due': [2023, 8, 1], 'count': 3, 'roll': 'following', 'holidays': []}, ['2023-08-01', '2023-09-01', '2023-10-02']], ['control 2', {'first_due': [2023, 2, 28], 'count': 3, 'roll': 'following', 'holidays': ['2023-12-25', '2024-05-31']}, ['2023-02-28', '2023-03-28', '2023-04-28']], ['control 3', {'first_due': [2024, 1, 5], 'count': 3, 'roll': 'none', 'holidays': ['2024-03-29', '2024-05-31']}, ['2024-01-05', '2024-02-05', '2024-03-05']], ['control 4', {'first_due': [2023, 4, 30], 'count': 3, 'roll': 'none', 'holidays': ['2023-06-30']}, ['2023-04-30', '2023-05-30', '2023-06-30']], ['control 5', {'first_due': [2023, 5, 30], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-01-01', '2024-03-29']}, ['2023-05-30', '2023-06-30', '2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30']], ['control 6', {'first_due': [2023, 7, 5], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31', '2024-05-31']}, ['2023-07-05', '2023-08-07', '2023-09-05']]], [['regression: year rollover', {'first_due': [2023, 10, 30], 'count': 13, 'roll': 'none', 'holidays': ['2024-01-01', '2024-03-29']}, ['2023-10-30', '2023-11-30', '2023-12-30', '2024-01-30', '2024-02-29', '2024-03-30', '2024-04-30', '2024-05-30', '2024-06-30', '2024-07-30', '2024-08-30', '2024-09-30', '2024-10-30']], ['control 1', {'first_due': [2024, 1, 29], 'count': 6, 'roll': 'none', 'holidays': []}, ['2024-01-29', '2024-02-29', '2024-03-29', '2024-04-29', '2024-05-29', '2024-06-29']], ['control 2', {'first_due': [2024, 8, 5], 'count': 3, 'roll': 'modified_following', 'holidays': []}, ['2024-08-05', '2024-09-05', '2024-10-07']], ['control 3', {'first_due': [2023, 10, 28], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31']}, ['2023-10-30', '2023-11-28', '2023-12-28']], ['control 4', {'first_due': [2023, 6, 30], 'count': 6, 'roll': 'modified_following', 'holidays': []}, ['2023-06-30', '2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30', '2023-11-30']], ['control 5', {'first_due': [2023, 1, 1], 'count': 3, 'roll': 'none', 'holidays': ['2024-12-31', '2024-02-29', '2024-01-01']}, ['2023-01-01', '2023-02-01', '2023-03-01']], ['control 6', {'first_due': [2023, 2, 15], 'count': 6, 'roll': 'none', 'holidays': ['2024-12-31']}, ['2023-02-15', '2023-03-15', '2023-04-15', '2023-05-15', '2023-06-15', '2023-07-15']]]]
for label, args, expected in fixtures[N-1]:
try:
actual = solve(args)
except Exception as exc:
actual = 'raised ' + type(exc).__name__
check(label, actual, 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: year rollover | ['2023-09-01', '2023-10-02', '2023-11-01', '2024-12-02', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02'] | ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02'] | Failed |
| control 1 | ['2024-02-05', '2024-03-05', '2024-04-05'] | ['2024-02-05', '2024-03-05', '2024-04-05'] | Passed |
| control 2 | ['2023-08-15', '2023-09-15', '2023-10-16'] | ['2023-08-15', '2023-09-15', '2023-10-16'] | Passed |
| control 3 | ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31'] | ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31'] | Passed |
| control 4 | ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15'] | ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15'] | Passed |
| control 5 | ['2024-02-29', '2024-03-30', '2024-04-30'] | ['2024-02-29', '2024-03-30', '2024-04-30'] | Passed |
| control 6 | ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15'] | ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15'] | Passed |
SHA-256 / 2e467782840154fe876ca9595329f276b9864f42b2298091661882928e5c88f6
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
import calendar
N = 1
observations = []
def solve(x):
y, m, d = x['first_due']
hol = set(x['holidays'])
def bad(t):
return t.weekday() >= 5 or t.isoformat() in hol
out = []
for i in range(x['count']):
mm = m - 1 + i
yy = y + mm // 12
mm = mm % 12 + 1
dd = min(d, calendar.monthrange(yy, mm)[1])
date = datetime.date(yy, mm, dd)
adj = date
if x['roll'] != 'none':
while bad(adj):
adj += datetime.timedelta(days=1)
if x['roll'] == 'modified_following' and adj.month != date.month:
adj = date
while bad(adj):
adj -= datetime.timedelta(days=1)
out.append(adj.isoformat())
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: year rollover', {'first_due': [2023, 9, 1], 'count': 13, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-05-31']}, ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02']], ['control 1', {'first_due': [2024, 2, 5], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-05-31', '2023-12-25']}, ['2024-02-05', '2024-03-05', '2024-04-05']], ['control 2', {'first_due': [2023, 8, 15], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-02-29', '2023-06-30']}, ['2023-08-15', '2023-09-15', '2023-10-16']], ['control 3', {'first_due': [2023, 3, 31], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-03-29']}, ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31']], ['control 4', {'first_due': [2024, 3, 15], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-05-31']}, ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15']], ['control 5', {'first_due': [2024, 2, 30], 'count': 3, 'roll': 'none', 'holidays': ['2023-12-25']}, ['2024-02-29', '2024-03-30', '2024-04-30']], ['control 6', {'first_due': [2023, 6, 15], 'count': 6, 'roll': 'none', 'holidays': ['2024-12-31']}, ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15']]], [['regression: year rollover', {'first_due': [2024, 9, 5], 'count': 13, 'roll': 'following', 'holidays': []}, ['2024-09-05', '2024-10-07', '2024-11-05', '2024-12-05', '2025-01-06', '2025-02-05', '2025-03-05', '2025-04-07', '2025-05-05', '2025-06-05', '2025-07-07', '2025-08-05', '2025-09-05']], ['control 1', {'first_due': [2023, 4, 5], 'count': 6, 'roll': 'following', 'holidays': ['2024-01-01', '2024-05-31', '2023-06-30']}, ['2023-04-05', '2023-05-05', '2023-06-05', '2023-07-05', '2023-08-07', '2023-09-05']], ['control 2', {'first_due': [2023, 6, 5], 'count': 6, 'roll': 'none', 'holidays': ['2024-01-01']}, ['2023-06-05', '2023-07-05', '2023-08-05', '2023-09-05', '2023-10-05', '2023-11-05']], ['control 3', {'first_due': [2023, 7, 30], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-12-31']}, ['2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30', '2023-11-30', '2023-12-29']], ['control 4', {'first_due': [2023, 3, 28], 'count': 3, 'roll': 'following', 'holidays': ['2024-05-31', '2023-06-30', '2023-12-25']}, ['2023-03-28', '2023-04-28', '2023-05-29']], ['control 5', {'first_due': [2024, 9, 15], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31', '2024-05-31']}, ['2024-09-16', '2024-10-15', '2024-11-15']], ['control 6', {'first_due': [2024, 4, 30], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-03-29', '2024-01-01', '2024-05-31']}, ['2024-04-30', '2024-05-30', '2024-06-28']]], [['regression: year rollover', {'first_due': [2024, 5, 1], 'count': 13, 'roll': 'none', 'holidays': []}, ['2024-05-01', '2024-06-01', '2024-07-01', '2024-08-01', '2024-09-01', '2024-10-01', '2024-11-01', '2024-12-01', '2025-01-01', '2025-02-01', '2025-03-01', '2025-04-01', '2025-05-01']], ['control 1', {'first_due': [2023, 9, 1], 'count': 3, 'roll': 'modified_following', 'holidays': []}, ['2023-09-01', '2023-10-02', '2023-11-01']], ['control 2', {'first_due': [2024, 10, 30], 'count': 3, 'roll': 'following', 'holidays': ['2024-05-31', '2023-06-30', '2024-03-29']}, ['2024-10-30', '2024-12-02', '2024-12-30']], ['control 3', {'first_due': [2023, 2, 5], 'count': 3, 'roll': 'none', 'holidays': ['2024-03-29']}, ['2023-02-05', '2023-03-05', '2023-04-05']], ['control 4', {'first_due': [2023, 8, 29], 'count': 3, 'roll': 'modified_following', 'holidays': ['2024-01-01', '2024-05-31', '2024-03-29']}, ['2023-08-29', '2023-09-29', '2023-10-30']], ['control 5', {'first_due': [2024, 3, 29], 'count': 3, 'roll': 'none', 'holidays': ['2023-12-25', '2024-01-01']}, ['2024-03-29', '2024-04-29', '2024-05-29']], ['control 6', {'first_due': [2024, 6, 15], 'count': 6, 'roll': 'following', 'holidays': ['2024-02-29', '2024-12-31']}, ['2024-06-17', '2024-07-15', '2024-08-15', '2024-09-16', '2024-10-15', '2024-11-15']]], [['regression: year rollover', {'first_due': [2024, 8, 15], 'count': 6, 'roll': 'modified_following', 'holidays': []}, ['2024-08-15', '2024-09-16', '2024-10-15', '2024-11-15', '2024-12-16', '2025-01-15']], ['control 1', {'first_due': [2023, 8, 1], 'count': 3, 'roll': 'following', 'holidays': []}, ['2023-08-01', '2023-09-01', '2023-10-02']], ['control 2', {'first_due': [2023, 2, 28], 'count': 3, 'roll': 'following', 'holidays': ['2023-12-25', '2024-05-31']}, ['2023-02-28', '2023-03-28', '2023-04-28']], ['control 3', {'first_due': [2024, 1, 5], 'count': 3, 'roll': 'none', 'holidays': ['2024-03-29', '2024-05-31']}, ['2024-01-05', '2024-02-05', '2024-03-05']], ['control 4', {'first_due': [2023, 4, 30], 'count': 3, 'roll': 'none', 'holidays': ['2023-06-30']}, ['2023-04-30', '2023-05-30', '2023-06-30']], ['control 5', {'first_due': [2023, 5, 30], 'count': 6, 'roll': 'modified_following', 'holidays': ['2024-01-01', '2024-03-29']}, ['2023-05-30', '2023-06-30', '2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30']], ['control 6', {'first_due': [2023, 7, 5], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31', '2024-05-31']}, ['2023-07-05', '2023-08-07', '2023-09-05']]], [['regression: year rollover', {'first_due': [2023, 10, 30], 'count': 13, 'roll': 'none', 'holidays': ['2024-01-01', '2024-03-29']}, ['2023-10-30', '2023-11-30', '2023-12-30', '2024-01-30', '2024-02-29', '2024-03-30', '2024-04-30', '2024-05-30', '2024-06-30', '2024-07-30', '2024-08-30', '2024-09-30', '2024-10-30']], ['control 1', {'first_due': [2024, 1, 29], 'count': 6, 'roll': 'none', 'holidays': []}, ['2024-01-29', '2024-02-29', '2024-03-29', '2024-04-29', '2024-05-29', '2024-06-29']], ['control 2', {'first_due': [2024, 8, 5], 'count': 3, 'roll': 'modified_following', 'holidays': []}, ['2024-08-05', '2024-09-05', '2024-10-07']], ['control 3', {'first_due': [2023, 10, 28], 'count': 3, 'roll': 'following', 'holidays': ['2024-12-31']}, ['2023-10-30', '2023-11-28', '2023-12-28']], ['control 4', {'first_due': [2023, 6, 30], 'count': 6, 'roll': 'modified_following', 'holidays': []}, ['2023-06-30', '2023-07-31', '2023-08-30', '2023-09-29', '2023-10-30', '2023-11-30']], ['control 5', {'first_due': [2023, 1, 1], 'count': 3, 'roll': 'none', 'holidays': ['2024-12-31', '2024-02-29', '2024-01-01']}, ['2023-01-01', '2023-02-01', '2023-03-01']], ['control 6', {'first_due': [2023, 2, 15], 'count': 6, 'roll': 'none', 'holidays': ['2024-12-31']}, ['2023-02-15', '2023-03-15', '2023-04-15', '2023-05-15', '2023-06-15', '2023-07-15']]]]
for label, args, expected in fixtures[N-1]:
try:
actual = solve(args)
except Exception as exc:
actual = 'raised ' + type(exc).__name__
check(label, actual, 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: year rollover | ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02'] | ['2023-09-01', '2023-10-02', '2023-11-01', '2023-12-01', '2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01', '2024-05-01', '2024-06-03', '2024-07-01', '2024-08-01', '2024-09-02'] | Passed |
| control 1 | ['2024-02-05', '2024-03-05', '2024-04-05'] | ['2024-02-05', '2024-03-05', '2024-04-05'] | Passed |
| control 2 | ['2023-08-15', '2023-09-15', '2023-10-16'] | ['2023-08-15', '2023-09-15', '2023-10-16'] | Passed |
| control 3 | ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31'] | ['2023-03-31', '2023-04-28', '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31'] | Passed |
| control 4 | ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15'] | ['2024-03-15', '2024-04-15', '2024-05-15', '2024-06-17', '2024-07-15', '2024-08-15'] | Passed |
| control 5 | ['2024-02-29', '2024-03-30', '2024-04-30'] | ['2024-02-29', '2024-03-30', '2024-04-30'] | Passed |
| control 6 | ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15'] | ['2023-06-15', '2023-07-15', '2023-08-15', '2023-09-15', '2023-10-15', '2023-11-15'] | Passed |
SHA-256 / 08e885ba938bcc2bbcf64120b52eeed02bd2809d5fbf4f8be2378b14d4c88495
Verification & scope
A deterministic bounded teaching model with stipulated toy lending rules stated in the contract; money is integer cents and rates are basis points; it makes no claim of conformance to any regulation, servicing standard or product. 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:29.790041+00:00.
Case digest / ac525c0eaff438d475f90a05a1e3649b592dab0d0f988cd51465f921f0925f47