{"abstract":"A 30/360 first period that collapses to zero days returns zero interest instead of an error.","category":"Loan amortization schedules","checks":7,"contract":"x = {'principal', 'rate_bp', 'convention': '30/360'|'act/365'|'act/360', 'start': [y,m,d], 'first_due': [y,m,d]}. 30/360: a start day of 31 becomes 30; then a due day of 31 becomes 30 when the (adjusted) start day is 30; days = 360*dy + 30*dm + dd. Actual conventions count calendar days, basis 365 or 360 (365 even in leap years). If days <= 0 return {'error': 'due_not_after_start'}. Interest = round_half_up(P*bp*days/(10000*basis)). Return {'days', 'basis', 'interest'}.","evaluation_group":"w2-loan-amortization-schedules-odd-first-period-day-count","failed_approach":"Exempting 30/360 lets its zero-day periods through.","family":"w2-loan-amortization-schedules-odd-first-period-day-count-non-positive-period-guard","id":"FA-58476","implementations":{"attempt":{"sha256":"acc4c2e7643016be575eb2a551420279c80d7c8103c03006324f77d3543a62e8","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\nimport datetime\nN = 1\nobservations = []\ndef solve(x):\n    def rnd(n, d):\n        q, r = divmod(n, d)\n        return q + (1 if 2 * r >= d else 0)\n    y1, m1, d1 = x['start']\n    y2, m2, d2 = x['first_due']\n    conv = x['convention']\n    if conv == '30/360':\n        if d1 == 31:\n            d1 = 30\n        if d2 == 31 and d1 == 30:\n            d2 = 30\n        days = 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)\n        basis = 360\n    else:\n        days = (datetime.date(y2, m2, d2) - datetime.date(y1, m1, d1)).days\n        basis = 365 if conv == 'act/365' else 360\n    if days <= 0 and conv != '30/360':\n        return {'error': 'due_not_after_start'}\n    interest = rnd(x['principal'] * x['rate_bp'] * days, 10000 * basis)\n    return {'days': days, 'basis': basis, 'interest': interest}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['regression: non-positive period guard', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/365', 'start': [2023, 8, 30], 'first_due': [2023, 8, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 600, 'convention': '30/360', 'start': [2024, 4, 30], 'first_due': [2024, 4, 30]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2023, 3, 15], 'first_due': [2024, 3, 1]}, {'days': 352, 'basis': 360, 'interest': 441}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 7, 28], 'first_due': [2024, 7, 28]}, {'days': 366, 'basis': 360, 'interest': 628}], ['control 3', {'principal': 12345, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 10, 30], 'first_due': [2025, 10, 30]}, {'days': 365, 'basis': 360, 'interest': 751}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'convention': 'act/360', 'start': [2023, 1, 1], 'first_due': [2023, 2, 1]}, {'days': 31, 'basis': 360, 'interest': 1033}], ['control 5', {'principal': 12345, 'rate_bp': 1200, 'convention': '30/360', 'start': [2024, 7, 15], 'first_due': [2024, 9, 30]}, {'days': 75, 'basis': 360, 'interest': 309}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 600, 'convention': '30/360', 'start': [2024, 8, 29], 'first_due': [2024, 8, 29]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 7, 28], 'first_due': [2023, 8, 28]}, {'days': 30, 'basis': 360, 'interest': 62}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 3, 15], 'first_due': [2024, 2, 1]}, {'days': 323, 'basis': 360, 'interest': 554}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'convention': 'act/365', 'start': [2023, 4, 30], 'first_due': [2024, 4, 30]}, {'days': 366, 'basis': 365, 'interest': 60164}], ['control 4', {'principal': 36500, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 6, 30], 'first_due': [2023, 8, 30]}, {'days': 60, 'basis': 360, 'interest': 365}], ['control 5', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/365', 'start': [2024, 7, 30], 'first_due': [2024, 8, 28]}, {'days': 29, 'basis': 365, 'interest': 49}], ['control 6', {'principal': 1000000, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 7, 28], 'first_due': [2024, 6, 15]}, {'days': 317, 'basis': 360, 'interest': 52833}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 11, 30], 'first_due': [2024, 11, 30]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 11, 28], 'first_due': [2024, 10, 31]}, {'days': 338, 'basis': 360, 'interest': 580}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': '30/360', 'start': [2024, 1, 31], 'first_due': [2024, 1, 28]}, {'error': 'due_not_after_start'}], ['control 3', {'principal': 36500, 'rate_bp': 1200, 'convention': 'act/360', 'start': [2024, 3, 29], 'first_due': [2025, 2, 1]}, {'days': 309, 'basis': 360, 'interest': 3760}], ['control 4', {'principal': 12345, 'rate_bp': 500, 'convention': '30/360', 'start': [2023, 4, 30], 'first_due': [2023, 5, 28]}, {'days': 28, 'basis': 360, 'interest': 48}], ['control 5', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/365', 'start': [2023, 12, 1], 'first_due': [2023, 12, 15]}, {'days': 14, 'basis': 365, 'interest': 1400}], ['control 6', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 11, 1], 'first_due': [2024, 12, 1]}, {'days': 30, 'basis': 360, 'interest': 3042}]], [['regression: non-positive period guard', {'principal': 36500, 'rate_bp': 1200, 'convention': 'act/365', 'start': [2023, 4, 30], 'first_due': [2023, 4, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 5, 31], 'first_due': [2024, 5, 15]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 100000, 'rate_bp': 1200, 'convention': '30/360', 'start': [2023, 4, 1], 'first_due': [2024, 3, 1]}, {'days': 330, 'basis': 360, 'interest': 11000}], ['control 2', {'principal': 36500, 'rate_bp': 500, 'convention': 'act/365', 'start': [2023, 4, 28], 'first_due': [2023, 6, 28]}, {'days': 61, 'basis': 365, 'interest': 305}], ['control 3', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 4, 30], 'first_due': [2024, 5, 1]}, {'days': 1, 'basis': 360, 'interest': 1}], ['control 4', {'principal': 12345, 'rate_bp': 1200, 'convention': 'act/365', 'start': [2024, 2, 29], 'first_due': [2024, 4, 29]}, {'days': 60, 'basis': 365, 'interest': 244}], ['control 5', {'principal': 36500, 'rate_bp': 600, 'convention': 'act/365', 'start': [2023, 12, 31], 'first_due': [2024, 1, 28]}, {'days': 28, 'basis': 365, 'interest': 168}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 7, 30], 'first_due': [2024, 7, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 6, 30], 'first_due': [2024, 6, 15]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 6, 28], 'first_due': [2024, 7, 30]}, {'days': 32, 'basis': 360, 'interest': 5333}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 10, 1], 'first_due': [2024, 10, 31]}, {'days': 30, 'basis': 360, 'interest': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 500, 'convention': 'act/360', 'start': [2024, 1, 29], 'first_due': [2024, 1, 30]}, {'days': 1, 'basis': 360, 'interest': 139}], ['control 4', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 2, 29], 'first_due': [2024, 4, 28]}, {'days': 59, 'basis': 360, 'interest': 74}], ['control 5', {'principal': 36500, 'rate_bp': 500, 'convention': 'act/365', 'start': [2024, 2, 29], 'first_due': [2024, 3, 29]}, {'days': 29, 'basis': 365, 'interest': 145}]]]\nfor label, args, expected in fixtures[N-1]:\n    try:\n        actual = solve(args)\n    except Exception as exc:\n        actual = 'raised ' + type(exc).__name__\n    check(label, actual, expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"broken":{"sha256":"9aa831b3d610bf9465b2eb65586d4e5d33bf3958bffef54bddee504a3c64f8e1","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\nimport datetime\nN = 1\nobservations = []\ndef solve(x):\n    def rnd(n, d):\n        q, r = divmod(n, d)\n        return q + (1 if 2 * r >= d else 0)\n    y1, m1, d1 = x['start']\n    y2, m2, d2 = x['first_due']\n    conv = x['convention']\n    if conv == '30/360':\n        if d1 == 31:\n            d1 = 30\n        if d2 == 31 and d1 == 30:\n            d2 = 30\n        days = 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)\n        basis = 360\n    else:\n        days = (datetime.date(y2, m2, d2) - datetime.date(y1, m1, d1)).days\n        basis = 365 if conv == 'act/365' else 360\n    if days < 0:\n        return {'error': 'due_not_after_start'}\n    interest = rnd(x['principal'] * x['rate_bp'] * days, 10000 * basis)\n    return {'days': days, 'basis': basis, 'interest': interest}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['regression: non-positive period guard', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/365', 'start': [2023, 8, 30], 'first_due': [2023, 8, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 600, 'convention': '30/360', 'start': [2024, 4, 30], 'first_due': [2024, 4, 30]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2023, 3, 15], 'first_due': [2024, 3, 1]}, {'days': 352, 'basis': 360, 'interest': 441}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 7, 28], 'first_due': [2024, 7, 28]}, {'days': 366, 'basis': 360, 'interest': 628}], ['control 3', {'principal': 12345, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 10, 30], 'first_due': [2025, 10, 30]}, {'days': 365, 'basis': 360, 'interest': 751}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'convention': 'act/360', 'start': [2023, 1, 1], 'first_due': [2023, 2, 1]}, {'days': 31, 'basis': 360, 'interest': 1033}], ['control 5', {'principal': 12345, 'rate_bp': 1200, 'convention': '30/360', 'start': [2024, 7, 15], 'first_due': [2024, 9, 30]}, {'days': 75, 'basis': 360, 'interest': 309}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 600, 'convention': '30/360', 'start': [2024, 8, 29], 'first_due': [2024, 8, 29]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 7, 28], 'first_due': [2023, 8, 28]}, {'days': 30, 'basis': 360, 'interest': 62}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 3, 15], 'first_due': [2024, 2, 1]}, {'days': 323, 'basis': 360, 'interest': 554}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'convention': 'act/365', 'start': [2023, 4, 30], 'first_due': [2024, 4, 30]}, {'days': 366, 'basis': 365, 'interest': 60164}], ['control 4', {'principal': 36500, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 6, 30], 'first_due': [2023, 8, 30]}, {'days': 60, 'basis': 360, 'interest': 365}], ['control 5', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/365', 'start': [2024, 7, 30], 'first_due': [2024, 8, 28]}, {'days': 29, 'basis': 365, 'interest': 49}], ['control 6', {'principal': 1000000, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 7, 28], 'first_due': [2024, 6, 15]}, {'days': 317, 'basis': 360, 'interest': 52833}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 11, 30], 'first_due': [2024, 11, 30]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 11, 28], 'first_due': [2024, 10, 31]}, {'days': 338, 'basis': 360, 'interest': 580}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': '30/360', 'start': [2024, 1, 31], 'first_due': [2024, 1, 28]}, {'error': 'due_not_after_start'}], ['control 3', {'principal': 36500, 'rate_bp': 1200, 'convention': 'act/360', 'start': [2024, 3, 29], 'first_due': [2025, 2, 1]}, {'days': 309, 'basis': 360, 'interest': 3760}], ['control 4', {'principal': 12345, 'rate_bp': 500, 'convention': '30/360', 'start': [2023, 4, 30], 'first_due': [2023, 5, 28]}, {'days': 28, 'basis': 360, 'interest': 48}], ['control 5', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/365', 'start': [2023, 12, 1], 'first_due': [2023, 12, 15]}, {'days': 14, 'basis': 365, 'interest': 1400}], ['control 6', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 11, 1], 'first_due': [2024, 12, 1]}, {'days': 30, 'basis': 360, 'interest': 3042}]], [['regression: non-positive period guard', {'principal': 36500, 'rate_bp': 1200, 'convention': 'act/365', 'start': [2023, 4, 30], 'first_due': [2023, 4, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 5, 31], 'first_due': [2024, 5, 15]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 100000, 'rate_bp': 1200, 'convention': '30/360', 'start': [2023, 4, 1], 'first_due': [2024, 3, 1]}, {'days': 330, 'basis': 360, 'interest': 11000}], ['control 2', {'principal': 36500, 'rate_bp': 500, 'convention': 'act/365', 'start': [2023, 4, 28], 'first_due': [2023, 6, 28]}, {'days': 61, 'basis': 365, 'interest': 305}], ['control 3', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 4, 30], 'first_due': [2024, 5, 1]}, {'days': 1, 'basis': 360, 'interest': 1}], ['control 4', {'principal': 12345, 'rate_bp': 1200, 'convention': 'act/365', 'start': [2024, 2, 29], 'first_due': [2024, 4, 29]}, {'days': 60, 'basis': 365, 'interest': 244}], ['control 5', {'principal': 36500, 'rate_bp': 600, 'convention': 'act/365', 'start': [2023, 12, 31], 'first_due': [2024, 1, 28]}, {'days': 28, 'basis': 365, 'interest': 168}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 7, 30], 'first_due': [2024, 7, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 6, 30], 'first_due': [2024, 6, 15]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 6, 28], 'first_due': [2024, 7, 30]}, {'days': 32, 'basis': 360, 'interest': 5333}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 10, 1], 'first_due': [2024, 10, 31]}, {'days': 30, 'basis': 360, 'interest': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 500, 'convention': 'act/360', 'start': [2024, 1, 29], 'first_due': [2024, 1, 30]}, {'days': 1, 'basis': 360, 'interest': 139}], ['control 4', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 2, 29], 'first_due': [2024, 4, 28]}, {'days': 59, 'basis': 360, 'interest': 74}], ['control 5', {'principal': 36500, 'rate_bp': 500, 'convention': 'act/365', 'start': [2024, 2, 29], 'first_due': [2024, 3, 29]}, {'days': 29, 'basis': 365, 'interest': 145}]]]\nfor label, args, expected in fixtures[N-1]:\n    try:\n        actual = solve(args)\n    except Exception as exc:\n        actual = 'raised ' + type(exc).__name__\n    check(label, actual, expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"fixed":{"sha256":"212828c98ca72a996651e3609c2cdffda7c2a369ffe48f637b5f8d8c6199fb82","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\nimport datetime\nN = 1\nobservations = []\ndef solve(x):\n    def rnd(n, d):\n        q, r = divmod(n, d)\n        return q + (1 if 2 * r >= d else 0)\n    y1, m1, d1 = x['start']\n    y2, m2, d2 = x['first_due']\n    conv = x['convention']\n    if conv == '30/360':\n        if d1 == 31:\n            d1 = 30\n        if d2 == 31 and d1 == 30:\n            d2 = 30\n        days = 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)\n        basis = 360\n    else:\n        days = (datetime.date(y2, m2, d2) - datetime.date(y1, m1, d1)).days\n        basis = 365 if conv == 'act/365' else 360\n    if days <= 0:\n        return {'error': 'due_not_after_start'}\n    interest = rnd(x['principal'] * x['rate_bp'] * days, 10000 * basis)\n    return {'days': days, 'basis': basis, 'interest': interest}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['regression: non-positive period guard', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/365', 'start': [2023, 8, 30], 'first_due': [2023, 8, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 600, 'convention': '30/360', 'start': [2024, 4, 30], 'first_due': [2024, 4, 30]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2023, 3, 15], 'first_due': [2024, 3, 1]}, {'days': 352, 'basis': 360, 'interest': 441}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 7, 28], 'first_due': [2024, 7, 28]}, {'days': 366, 'basis': 360, 'interest': 628}], ['control 3', {'principal': 12345, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 10, 30], 'first_due': [2025, 10, 30]}, {'days': 365, 'basis': 360, 'interest': 751}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'convention': 'act/360', 'start': [2023, 1, 1], 'first_due': [2023, 2, 1]}, {'days': 31, 'basis': 360, 'interest': 1033}], ['control 5', {'principal': 12345, 'rate_bp': 1200, 'convention': '30/360', 'start': [2024, 7, 15], 'first_due': [2024, 9, 30]}, {'days': 75, 'basis': 360, 'interest': 309}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 600, 'convention': '30/360', 'start': [2024, 8, 29], 'first_due': [2024, 8, 29]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 7, 28], 'first_due': [2023, 8, 28]}, {'days': 30, 'basis': 360, 'interest': 62}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 3, 15], 'first_due': [2024, 2, 1]}, {'days': 323, 'basis': 360, 'interest': 554}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'convention': 'act/365', 'start': [2023, 4, 30], 'first_due': [2024, 4, 30]}, {'days': 366, 'basis': 365, 'interest': 60164}], ['control 4', {'principal': 36500, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 6, 30], 'first_due': [2023, 8, 30]}, {'days': 60, 'basis': 360, 'interest': 365}], ['control 5', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/365', 'start': [2024, 7, 30], 'first_due': [2024, 8, 28]}, {'days': 29, 'basis': 365, 'interest': 49}], ['control 6', {'principal': 1000000, 'rate_bp': 600, 'convention': '30/360', 'start': [2023, 7, 28], 'first_due': [2024, 6, 15]}, {'days': 317, 'basis': 360, 'interest': 52833}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 11, 30], 'first_due': [2024, 11, 30]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 12345, 'rate_bp': 500, 'convention': 'act/360', 'start': [2023, 11, 28], 'first_due': [2024, 10, 31]}, {'days': 338, 'basis': 360, 'interest': 580}], ['control 2', {'principal': 12345, 'rate_bp': 500, 'convention': '30/360', 'start': [2024, 1, 31], 'first_due': [2024, 1, 28]}, {'error': 'due_not_after_start'}], ['control 3', {'principal': 36500, 'rate_bp': 1200, 'convention': 'act/360', 'start': [2024, 3, 29], 'first_due': [2025, 2, 1]}, {'days': 309, 'basis': 360, 'interest': 3760}], ['control 4', {'principal': 12345, 'rate_bp': 500, 'convention': '30/360', 'start': [2023, 4, 30], 'first_due': [2023, 5, 28]}, {'days': 28, 'basis': 360, 'interest': 48}], ['control 5', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/365', 'start': [2023, 12, 1], 'first_due': [2023, 12, 15]}, {'days': 14, 'basis': 365, 'interest': 1400}], ['control 6', {'principal': 1000000, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 11, 1], 'first_due': [2024, 12, 1]}, {'days': 30, 'basis': 360, 'interest': 3042}]], [['regression: non-positive period guard', {'principal': 36500, 'rate_bp': 1200, 'convention': 'act/365', 'start': [2023, 4, 30], 'first_due': [2023, 4, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 5, 31], 'first_due': [2024, 5, 15]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 100000, 'rate_bp': 1200, 'convention': '30/360', 'start': [2023, 4, 1], 'first_due': [2024, 3, 1]}, {'days': 330, 'basis': 360, 'interest': 11000}], ['control 2', {'principal': 36500, 'rate_bp': 500, 'convention': 'act/365', 'start': [2023, 4, 28], 'first_due': [2023, 6, 28]}, {'days': 61, 'basis': 365, 'interest': 305}], ['control 3', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 4, 30], 'first_due': [2024, 5, 1]}, {'days': 1, 'basis': 360, 'interest': 1}], ['control 4', {'principal': 12345, 'rate_bp': 1200, 'convention': 'act/365', 'start': [2024, 2, 29], 'first_due': [2024, 4, 29]}, {'days': 60, 'basis': 365, 'interest': 244}], ['control 5', {'principal': 36500, 'rate_bp': 600, 'convention': 'act/365', 'start': [2023, 12, 31], 'first_due': [2024, 1, 28]}, {'days': 28, 'basis': 365, 'interest': 168}]], [['regression: non-positive period guard', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 7, 30], 'first_due': [2024, 7, 30]}, {'error': 'due_not_after_start'}], ['regression: non-positive period guard, partial-repair probe', {'principal': 36500, 'rate_bp': 365, 'convention': '30/360', 'start': [2024, 6, 30], 'first_due': [2024, 6, 15]}, {'error': 'due_not_after_start'}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 6, 28], 'first_due': [2024, 7, 30]}, {'days': 32, 'basis': 360, 'interest': 5333}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'convention': 'act/360', 'start': [2024, 10, 1], 'first_due': [2024, 10, 31]}, {'days': 30, 'basis': 360, 'interest': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 500, 'convention': 'act/360', 'start': [2024, 1, 29], 'first_due': [2024, 1, 30]}, {'days': 1, 'basis': 360, 'interest': 139}], ['control 4', {'principal': 12345, 'rate_bp': 365, 'convention': 'act/360', 'start': [2024, 2, 29], 'first_due': [2024, 4, 28]}, {'days': 59, 'basis': 360, 'interest': 74}], ['control 5', {'principal': 36500, 'rate_bp': 500, 'convention': 'act/365', 'start': [2024, 2, 29], 'first_due': [2024, 3, 29]}, {'days': 29, 'basis': 365, 'interest': 145}]]]\nfor label, args, expected in fixtures[N-1]:\n    try:\n        actual = solve(args)\n    except Exception as exc:\n        actual = 'raised ' + type(exc).__name__\n    check(label, actual, expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"}},"limitations":"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.","method":"Deterministic executable model with adversarial boundary fixtures.","provenance":{"created_by":"Failure Map","dependencies":"Python standard library","family":"w2-loan-amortization-schedules-odd-first-period-day-count-non-positive-period-guard","generated_at":"2026-09-29T14:46:27.134730+00:00","license":"CC0-1.0","python":"3.12.14","seed":1,"split":"open-access"},"relevance":"Amortization engines drive borrower statements, payoff quotes and investor remittances; a misplaced rounding step, boundary or ordering rule compounds across hundreds of periods.","repair":"Reject any first period with zero or negative days.","root_cause":"Only negative day counts are rejected.","sha256":"17ee078edd818c6939bfa4e029e03b3911338103e7f37908c7f7bf854ad4e757","title":"Odd first period day count: non-positive period guard · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verification":{"attempt":{"elapsed_ms":44.751,"exit_code":1,"observations":[{"actual":{"error":"due_not_after_start"},"check":"regression: non-positive period guard","expected":{"error":"due_not_after_start"},"passed":true},{"actual":{"basis":360,"days":0,"interest":0},"check":"regression: non-positive period guard, partial-repair probe","expected":{"error":"due_not_after_start"},"passed":false},{"actual":{"basis":360,"days":352,"interest":441},"check":"control 1","expected":{"basis":360,"days":352,"interest":441},"passed":true},{"actual":{"basis":360,"days":366,"interest":628},"check":"control 2","expected":{"basis":360,"days":366,"interest":628},"passed":true},{"actual":{"basis":360,"days":365,"interest":751},"check":"control 3","expected":{"basis":360,"days":365,"interest":751},"passed":true},{"actual":{"basis":360,"days":31,"interest":1033},"check":"control 4","expected":{"basis":360,"days":31,"interest":1033},"passed":true},{"actual":{"basis":360,"days":75,"interest":309},"check":"control 5","expected":{"basis":360,"days":75,"interest":309},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"regression: non-positive period guard\", \"actual\": {\"error\": \"due_not_after_start\"}, \"expected\": {\"error\": \"due_not_after_start\"}, \"passed\": true}, {\"check\": \"regression: non-positive period guard, partial-repair probe\", \"actual\": {\"days\": 0, \"basis\": 360, \"interest\": 0}, \"expected\": {\"error\": \"due_not_after_start\"}, \"passed\": false}, {\"check\": \"control 1\", \"actual\": {\"days\": 352, \"basis\": 360, \"interest\": 441}, \"expected\": {\"days\": 352, \"basis\": 360, \"interest\": 441}, \"passed\": true}, {\"check\": \"control 2\", \"actual\": {\"days\": 366, \"basis\": 360, \"interest\": 628}, \"expected\": {\"days\": 366, \"basis\": 360, \"interest\": 628}, \"passed\": true}, {\"check\": \"control 3\", \"actual\": {\"days\": 365, \"basis\": 360, \"interest\": 751}, \"expected\": {\"days\": 365, \"basis\": 360, \"interest\": 751}, \"passed\": true}, {\"check\": \"control 4\", \"actual\": {\"days\": 31, \"basis\": 360, \"interest\": 1033}, \"expected\": {\"days\": 31, \"basis\": 360, \"interest\": 1033}, \"passed\": true}, {\"check\": \"control 5\", \"actual\": {\"days\": 75, \"basis\": 360, \"interest\": 309}, \"expected\": {\"days\": 75, \"basis\": 360, \"interest\": 309}, \"passed\": true}], \"passed\": false}\n"},"broken":{"elapsed_ms":46.246,"exit_code":1,"observations":[{"actual":{"basis":365,"days":0,"interest":0},"check":"regression: non-positive period guard","expected":{"error":"due_not_after_start"},"passed":false},{"actual":{"basis":360,"days":0,"interest":0},"check":"regression: non-positive period guard, partial-repair probe","expected":{"error":"due_not_after_start"},"passed":false},{"actual":{"basis":360,"days":352,"interest":441},"check":"control 1","expected":{"basis":360,"days":352,"interest":441},"passed":true},{"actual":{"basis":360,"days":366,"interest":628},"check":"control 2","expected":{"basis":360,"days":366,"interest":628},"passed":true},{"actual":{"basis":360,"days":365,"interest":751},"check":"control 3","expected":{"basis":360,"days":365,"interest":751},"passed":true},{"actual":{"basis":360,"days":31,"interest":1033},"check":"control 4","expected":{"basis":360,"days":31,"interest":1033},"passed":true},{"actual":{"basis":360,"days":75,"interest":309},"check":"control 5","expected":{"basis":360,"days":75,"interest":309},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"regression: non-positive period guard\", \"actual\": {\"days\": 0, \"basis\": 365, \"interest\": 0}, \"expected\": {\"error\": \"due_not_after_start\"}, \"passed\": false}, {\"check\": \"regression: non-positive period guard, partial-repair probe\", \"actual\": {\"days\": 0, \"basis\": 360, \"interest\": 0}, \"expected\": {\"error\": \"due_not_after_start\"}, \"passed\": false}, {\"check\": \"control 1\", \"actual\": {\"days\": 352, \"basis\": 360, \"interest\": 441}, \"expected\": {\"days\": 352, \"basis\": 360, \"interest\": 441}, \"passed\": true}, {\"check\": \"control 2\", \"actual\": {\"days\": 366, \"basis\": 360, \"interest\": 628}, \"expected\": {\"days\": 366, \"basis\": 360, \"interest\": 628}, \"passed\": true}, {\"check\": \"control 3\", \"actual\": {\"days\": 365, \"basis\": 360, \"interest\": 751}, \"expected\": {\"days\": 365, \"basis\": 360, \"interest\": 751}, \"passed\": true}, {\"check\": \"control 4\", \"actual\": {\"days\": 31, \"basis\": 360, \"interest\": 1033}, \"expected\": {\"days\": 31, \"basis\": 360, \"interest\": 1033}, \"passed\": true}, {\"check\": \"control 5\", \"actual\": {\"days\": 75, \"basis\": 360, \"interest\": 309}, \"expected\": {\"days\": 75, \"basis\": 360, \"interest\": 309}, \"passed\": true}], \"passed\": false}\n"},"fixed":{"elapsed_ms":48.199,"exit_code":0,"observations":[{"actual":{"error":"due_not_after_start"},"check":"regression: non-positive period guard","expected":{"error":"due_not_after_start"},"passed":true},{"actual":{"error":"due_not_after_start"},"check":"regression: non-positive period guard, partial-repair probe","expected":{"error":"due_not_after_start"},"passed":true},{"actual":{"basis":360,"days":352,"interest":441},"check":"control 1","expected":{"basis":360,"days":352,"interest":441},"passed":true},{"actual":{"basis":360,"days":366,"interest":628},"check":"control 2","expected":{"basis":360,"days":366,"interest":628},"passed":true},{"actual":{"basis":360,"days":365,"interest":751},"check":"control 3","expected":{"basis":360,"days":365,"interest":751},"passed":true},{"actual":{"basis":360,"days":31,"interest":1033},"check":"control 4","expected":{"basis":360,"days":31,"interest":1033},"passed":true},{"actual":{"basis":360,"days":75,"interest":309},"check":"control 5","expected":{"basis":360,"days":75,"interest":309},"passed":true}],"passed":true,"stderr":"","stdout":"{\"observations\": [{\"check\": \"regression: non-positive period guard\", \"actual\": {\"error\": \"due_not_after_start\"}, \"expected\": {\"error\": \"due_not_after_start\"}, \"passed\": true}, {\"check\": \"regression: non-positive period guard, partial-repair probe\", \"actual\": {\"error\": \"due_not_after_start\"}, \"expected\": {\"error\": \"due_not_after_start\"}, \"passed\": true}, {\"check\": \"control 1\", \"actual\": {\"days\": 352, \"basis\": 360, \"interest\": 441}, \"expected\": {\"days\": 352, \"basis\": 360, \"interest\": 441}, \"passed\": true}, {\"check\": \"control 2\", \"actual\": {\"days\": 366, \"basis\": 360, \"interest\": 628}, \"expected\": {\"days\": 366, \"basis\": 360, \"interest\": 628}, \"passed\": true}, {\"check\": \"control 3\", \"actual\": {\"days\": 365, \"basis\": 360, \"interest\": 751}, \"expected\": {\"days\": 365, \"basis\": 360, \"interest\": 751}, \"passed\": true}, {\"check\": \"control 4\", \"actual\": {\"days\": 31, \"basis\": 360, \"interest\": 1033}, \"expected\": {\"days\": 31, \"basis\": 360, \"interest\": 1033}, \"passed\": true}, {\"check\": \"control 5\", \"actual\": {\"days\": 75, \"basis\": 360, \"interest\": 309}, \"expected\": {\"days\": 75, \"basis\": 360, \"interest\": 309}, \"passed\": true}], \"passed\": true}\n"}},"verified":true,"visibility":"public"}