{"abstract":"Refunding an order in thirds claws back more or fewer points than refunding it at once.","category":"Loyalty points ledgers","checks":7,"contract":"Input {earned, order_total, refunds:[cents], balance}. Cumulative refunds are capped at the order total. After each refund the cumulative clawback target is round-half-up(earned*cumulative refund/order total); the refund claws target minus already clawed. A clawback is taken from the balance only down to zero; the shortfall accumulates as debt. Return {claws, balance, debt}.","contract_signature":"x","evaluation_group":"w2-loyalty-points-ledgers-refund-clawback","failed_approach":"Using a floored cumulative target avoids drift but rounds half-points the wrong way.","family":"w2-loyalty-points-ledgers-refund-clawback-cumulative-rounding","id":"FA-60461","implementations":{"attempt":{"sha256":"72ecf88b63122e40b8e47f93bbe3613a5d7d173930cfa3c1aebb07c8837c2900","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(x):\n    cum = 0\n    clawed = 0\n    bal = x['balance']\n    debt = 0\n    out = []\n    for r in x['refunds']:\n        cum = min(x['order_total'], cum + r)\n        target = x['earned'] * cum // x['order_total']\n        c = target - clawed\n        clawed = target\n        take = min(c, bal)\n        bal -= take\n        debt += c - take\n        out.append(c)\n    return {'claws': out, 'balance': bal, 'debt': debt}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2501], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 51}, {'claws': [3, 4, 3], 'balance': 41, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3001], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 31}, {'claws': [100, 100], 'balance': 0, 'debt': 169}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 1}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 89}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 11}, {'claws': [2], 'balance': 9, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [2], 'balance': 5}, {'claws': [0], 'balance': 5, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2502], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 52}, {'claws': [3, 4, 3], 'balance': 42, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3002], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 32}, {'claws': [100, 100], 'balance': 0, 'debt': 168}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 2}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 88}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 12}, {'claws': [2], 'balance': 10, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [3], 'balance': 5}, {'claws': [0], 'balance': 5, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2503], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 53}, {'claws': [3, 4, 3], 'balance': 43, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3003], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 33}, {'claws': [100, 100], 'balance': 0, 'debt': 167}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 3}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 87}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 13}, {'claws': [2], 'balance': 11, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [4], 'balance': 5}, {'claws': [0], 'balance': 5, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2504], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 54}, {'claws': [3, 4, 3], 'balance': 44, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3004], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 34}, {'claws': [100, 100], 'balance': 0, 'debt': 166}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 4}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 86}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 14}, {'claws': [2], 'balance': 12, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [5], 'balance': 5}, {'claws': [1], 'balance': 4, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2505], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 55}, {'claws': [3, 4, 3], 'balance': 45, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3005], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 35}, {'claws': [100, 100], 'balance': 0, 'debt': 165}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 5}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 85}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 15}, {'claws': [2], 'balance': 13, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [6], 'balance': 5}, {'claws': [1], 'balance': 4, 'debt': 0}]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(args), 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":"9ab25f045a71e3eb55637bdd84dda45426763bf72ceac862397719b449dab870","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(x):\n    cum = 0\n    clawed = 0\n    bal = x['balance']\n    debt = 0\n    out = []\n    for r in x['refunds']:\n        cum = min(x['order_total'], cum + r)\n        target = (2 * x['earned'] * cum + x['order_total']) // (2 * x['order_total'])\n        c = (2 * x['earned'] * r + x['order_total']) // (2 * x['order_total'])\n        clawed = target\n        take = min(c, bal)\n        bal -= take\n        debt += c - take\n        out.append(c)\n    return {'claws': out, 'balance': bal, 'debt': debt}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2501], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 51}, {'claws': [3, 4, 3], 'balance': 41, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3001], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 31}, {'claws': [100, 100], 'balance': 0, 'debt': 169}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 1}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 89}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 11}, {'claws': [2], 'balance': 9, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [2], 'balance': 5}, {'claws': [0], 'balance': 5, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2502], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 52}, {'claws': [3, 4, 3], 'balance': 42, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3002], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 32}, {'claws': [100, 100], 'balance': 0, 'debt': 168}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 2}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 88}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 12}, {'claws': [2], 'balance': 10, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [3], 'balance': 5}, {'claws': [0], 'balance': 5, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2503], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 53}, {'claws': [3, 4, 3], 'balance': 43, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3003], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 33}, {'claws': [100, 100], 'balance': 0, 'debt': 167}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 3}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 87}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 13}, {'claws': [2], 'balance': 11, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [4], 'balance': 5}, {'claws': [0], 'balance': 5, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2504], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 54}, {'claws': [3, 4, 3], 'balance': 44, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3004], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 34}, {'claws': [100, 100], 'balance': 0, 'debt': 166}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 4}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 86}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 14}, {'claws': [2], 'balance': 12, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [5], 'balance': 5}, {'claws': [1], 'balance': 4, 'debt': 0}]], [['two partial refunds', {'earned': 125, 'order_total': 10000, 'refunds': [2500, 2505], 'balance': 1000}, {'claws': [31, 32], 'balance': 937, 'debt': 0}], ['rounding drift', {'earned': 10, 'order_total': 300, 'refunds': [100, 100, 100], 'balance': 55}, {'claws': [3, 4, 3], 'balance': 45, 'debt': 0}], ['over refund', {'earned': 80, 'order_total': 4000, 'refunds': [3000, 3005], 'balance': 500}, {'claws': [60, 20], 'balance': 420, 'debt': 0}], ['spent points', {'earned': 200, 'order_total': 2000, 'refunds': [1000, 1000], 'balance': 35}, {'claws': [100, 100], 'balance': 0, 'debt': 165}], ['debt grows', {'earned': 90, 'order_total': 900, 'refunds': [300, 300, 300], 'balance': 5}, {'claws': [30, 30, 30], 'balance': 0, 'debt': 85}], ['single half', {'earned': 3, 'order_total': 2, 'refunds': [1], 'balance': 15}, {'claws': [2], 'balance': 13, 'debt': 0}], ['refund one of many', {'earned': 999, 'order_total': 9990, 'refunds': [6], 'balance': 5}, {'claws': [1], 'balance': 4, 'debt': 0}]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(args), 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":"Stipulated bounded loyalty-program model with explicit toy rules; not any real program terms. 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-loyalty-points-ledgers-refund-clawback-cumulative-rounding","generated_at":"2026-09-29T14:46:46.000651+00:00","license":"CC0-1.0","python":"3.12.14","seed":1,"split":"open-access"},"relevance":"Member point balances, statements and the program liability depend on this ledger rule.","root_cause":"Each refund is rounded independently instead of rounding the cumulative target.","sha256":"e223e35b918b7f49f6adfef1d94a3ecf3722575d88a095c2518e894ca9212cd2","title":"Refund points clawback: cumulative rounding · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verified":true,"visibility":"public","verification":{"attempt":{"elapsed_ms":40.425,"exit_code":1,"observations":[{"actual":{"balance":938,"claws":[31,31],"debt":0},"check":"two partial refunds","expected":{"balance":937,"claws":[31,32],"debt":0},"passed":false},{"actual":{"balance":41,"claws":[3,3,4],"debt":0},"check":"rounding drift","expected":{"balance":41,"claws":[3,4,3],"debt":0},"passed":false},{"actual":{"balance":420,"claws":[60,20],"debt":0},"check":"over refund","expected":{"balance":420,"claws":[60,20],"debt":0},"passed":true},{"actual":{"balance":0,"claws":[100,100],"debt":169},"check":"spent points","expected":{"balance":0,"claws":[100,100],"debt":169},"passed":true},{"actual":{"balance":0,"claws":[30,30,30],"debt":89},"check":"debt grows","expected":{"balance":0,"claws":[30,30,30],"debt":89},"passed":true},{"actual":{"balance":10,"claws":[1],"debt":0},"check":"single half","expected":{"balance":9,"claws":[2],"debt":0},"passed":false},{"actual":{"balance":5,"claws":[0],"debt":0},"check":"refund one of many","expected":{"balance":5,"claws":[0],"debt":0},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"two partial refunds\", \"actual\": {\"claws\": [31, 31], \"balance\": 938, \"debt\": 0}, \"expected\": {\"claws\": [31, 32], \"balance\": 937, \"debt\": 0}, \"passed\": false}, {\"check\": \"rounding drift\", \"actual\": {\"claws\": [3, 3, 4], \"balance\": 41, \"debt\": 0}, \"expected\": {\"claws\": [3, 4, 3], \"balance\": 41, \"debt\": 0}, \"passed\": false}, {\"check\": \"over refund\", \"actual\": {\"claws\": [60, 20], \"balance\": 420, \"debt\": 0}, \"expected\": {\"claws\": [60, 20], \"balance\": 420, \"debt\": 0}, \"passed\": true}, {\"check\": \"spent points\", \"actual\": {\"claws\": [100, 100], \"balance\": 0, \"debt\": 169}, \"expected\": {\"claws\": [100, 100], \"balance\": 0, \"debt\": 169}, \"passed\": true}, {\"check\": \"debt grows\", \"actual\": {\"claws\": [30, 30, 30], \"balance\": 0, \"debt\": 89}, \"expected\": {\"claws\": [30, 30, 30], \"balance\": 0, \"debt\": 89}, \"passed\": true}, {\"check\": \"single half\", \"actual\": {\"claws\": [1], \"balance\": 10, \"debt\": 0}, \"expected\": {\"claws\": [2], \"balance\": 9, \"debt\": 0}, \"passed\": false}, {\"check\": \"refund one of many\", \"actual\": {\"claws\": [0], \"balance\": 5, \"debt\": 0}, \"expected\": {\"claws\": [0], \"balance\": 5, \"debt\": 0}, \"passed\": true}], \"passed\": false}\n"},"broken":{"elapsed_ms":38.821,"exit_code":1,"observations":[{"actual":{"balance":938,"claws":[31,31],"debt":0},"check":"two partial refunds","expected":{"balance":937,"claws":[31,32],"debt":0},"passed":false},{"actual":{"balance":42,"claws":[3,3,3],"debt":0},"check":"rounding drift","expected":{"balance":41,"claws":[3,4,3],"debt":0},"passed":false},{"actual":{"balance":380,"claws":[60,60],"debt":0},"check":"over refund","expected":{"balance":420,"claws":[60,20],"debt":0},"passed":false},{"actual":{"balance":0,"claws":[100,100],"debt":169},"check":"spent points","expected":{"balance":0,"claws":[100,100],"debt":169},"passed":true},{"actual":{"balance":0,"claws":[30,30,30],"debt":89},"check":"debt grows","expected":{"balance":0,"claws":[30,30,30],"debt":89},"passed":true},{"actual":{"balance":9,"claws":[2],"debt":0},"check":"single half","expected":{"balance":9,"claws":[2],"debt":0},"passed":true},{"actual":{"balance":5,"claws":[0],"debt":0},"check":"refund one of many","expected":{"balance":5,"claws":[0],"debt":0},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"two partial refunds\", \"actual\": {\"claws\": [31, 31], \"balance\": 938, \"debt\": 0}, \"expected\": {\"claws\": [31, 32], \"balance\": 937, \"debt\": 0}, \"passed\": false}, {\"check\": \"rounding drift\", \"actual\": {\"claws\": [3, 3, 3], \"balance\": 42, \"debt\": 0}, \"expected\": {\"claws\": [3, 4, 3], \"balance\": 41, \"debt\": 0}, \"passed\": false}, {\"check\": \"over refund\", \"actual\": {\"claws\": [60, 60], \"balance\": 380, \"debt\": 0}, \"expected\": {\"claws\": [60, 20], \"balance\": 420, \"debt\": 0}, \"passed\": false}, {\"check\": \"spent points\", \"actual\": {\"claws\": [100, 100], \"balance\": 0, \"debt\": 169}, \"expected\": {\"claws\": [100, 100], \"balance\": 0, \"debt\": 169}, \"passed\": true}, {\"check\": \"debt grows\", \"actual\": {\"claws\": [30, 30, 30], \"balance\": 0, \"debt\": 89}, \"expected\": {\"claws\": [30, 30, 30], \"balance\": 0, \"debt\": 89}, \"passed\": true}, {\"check\": \"single half\", \"actual\": {\"claws\": [2], \"balance\": 9, \"debt\": 0}, \"expected\": {\"claws\": [2], \"balance\": 9, \"debt\": 0}, \"passed\": true}, {\"check\": \"refund one of many\", \"actual\": {\"claws\": [0], \"balance\": 5, \"debt\": 0}, \"expected\": {\"claws\": [0], \"balance\": 5, \"debt\": 0}, \"passed\": true}], \"passed\": false}\n"}},"member_only":{"stages":["fixed"],"fields":["implementations.fixed","verification.fixed","harness","repair"],"note":"The verified repair, its recorded checks, the repair description, and the scoring harness are available to members."}}