{"abstract":"Points that are still valid on the cancellation day receive a new grace expiry instead of their original one.","category":"Loyalty points ledgers","checks":8,"contract":"Input {redemption:{day, sources:[[lot_expiry, pts]]}, cancel_day, grace, max_age}. Cancelling more than max_age days after the redemption returns {\"error\":\"too late\"}. Each source lot is reinstated with its original expiry if that expiry is on or after the cancel day, otherwise with expiry cancel_day + grace. Lots with the same resulting expiry are merged. Return {lots:[[expiry,pts]] by expiry, total}.","evaluation_group":"w2-loyalty-points-ledgers-reward-reinstatement","failed_approach":"Keeping original expiries that lapsed the day before the cancel reinstates expired points without grace.","family":"w2-loyalty-points-ledgers-reward-reinstatement-still-valid-test","id":"FA-60666","implementations":{"attempt":{"sha256":"eed475be4d61e301448f4def73bd2769fda6eff9b89e5b5287331ca8a3f904e0","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(x):\n    r = x['redemption']\n    if x['cancel_day'] - r['day'] > x['max_age']:\n        return {'error': 'too late'}\n    lots = {}\n    for exp, p in r['sources']:\n        e = exp if exp >= x['cancel_day'] - 1 else x['cancel_day'] + x['grace']\n        lots[e] = lots.get(e, 0) + p\n    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[31, 9]]}, 'cancel_day': 32, 'grace': 7, 'max_age': 90}, {'lots': [[39, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[32, 9]]}, 'cancel_day': 33, 'grace': 7, 'max_age': 90}, {'lots': [[40, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[33, 9]]}, 'cancel_day': 34, 'grace': 7, 'max_age': 90}, {'lots': [[41, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[34, 9]]}, 'cancel_day': 35, 'grace': 7, 'max_age': 90}, {'lots': [[42, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[35, 9]]}, 'cancel_day': 36, 'grace': 7, 'max_age': 90}, {'lots': [[43, 9]], 'total': 9}]]]\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":"25c959062f82fd61e493db022287a4843c4c8766c7fc5a4c210a338f460b28f3","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(x):\n    r = x['redemption']\n    if x['cancel_day'] - r['day'] > x['max_age']:\n        return {'error': 'too late'}\n    lots = {}\n    for exp, p in r['sources']:\n        e = exp if exp > x['cancel_day'] else x['cancel_day'] + x['grace']\n        lots[e] = lots.get(e, 0) + p\n    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[31, 9]]}, 'cancel_day': 32, 'grace': 7, 'max_age': 90}, {'lots': [[39, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[32, 9]]}, 'cancel_day': 33, 'grace': 7, 'max_age': 90}, {'lots': [[40, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[33, 9]]}, 'cancel_day': 34, 'grace': 7, 'max_age': 90}, {'lots': [[41, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[34, 9]]}, 'cancel_day': 35, 'grace': 7, 'max_age': 90}, {'lots': [[42, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[35, 9]]}, 'cancel_day': 36, 'grace': 7, 'max_age': 90}, {'lots': [[43, 9]], 'total': 9}]]]\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"},"fixed":{"sha256":"58b05f831a7c5e78097a2c0f3f5407b75cc6a42f632e375c9fe03d6cc8ddf455","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(x):\n    r = x['redemption']\n    if x['cancel_day'] - r['day'] > x['max_age']:\n        return {'error': 'too late'}\n    lots = {}\n    for exp, p in r['sources']:\n        e = exp if exp >= x['cancel_day'] else x['cancel_day'] + x['grace']\n        lots[e] = lots.get(e, 0) + p\n    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[31, 9]]}, 'cancel_day': 32, 'grace': 7, 'max_age': 90}, {'lots': [[39, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[32, 9]]}, 'cancel_day': 33, 'grace': 7, 'max_age': 90}, {'lots': [[40, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[33, 9]]}, 'cancel_day': 34, 'grace': 7, 'max_age': 90}, {'lots': [[41, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[34, 9]]}, 'cancel_day': 35, 'grace': 7, 'max_age': 90}, {'lots': [[42, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[35, 9]]}, 'cancel_day': 36, 'grace': 7, 'max_age': 90}, {'lots': [[43, 9]], 'total': 9}]]]\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-reward-reinstatement-still-valid-test","generated_at":"2026-09-29T14:46:47.745500+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.","repair":"Keep the original expiry when it is on or after the cancel day.","root_cause":"Original expiry is kept only when it is strictly after the cancel day.","sha256":"af816619915b009960ea388c123acf5594314997f0ed922680180083a949e521","title":"Cancelled reward reinstatement: still-valid test · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verification":{"attempt":{"elapsed_ms":41.572,"exit_code":1,"observations":[{"actual":{"lots":[[140,300],[150,200],[400,100]],"total":600},"check":"mixed sources","expected":{"lots":[[140,300],[150,200],[400,100]],"total":600},"passed":true},{"actual":{"lots":[[120,51]],"total":51},"check":"expires on cancel day","expected":{"lots":[[120,51]],"total":51},"passed":true},{"actual":{"error":"too late"},"check":"too late","expected":{"error":"too late"},"passed":true},{"actual":{"lots":[[115,5],[500,100]],"total":105},"check":"age edge","expected":{"lots":[[115,5],[500,100]],"total":105},"passed":true},{"actual":{"lots":[[105,31],[300,7]],"total":38},"check":"merge grace lots","expected":{"lots":[[105,31],[300,7]],"total":38},"passed":true},{"actual":{"lots":[[90,6],[150,10],[200,40]],"total":56},"check":"merge with original","expected":{"lots":[[90,6],[150,10],[200,40]],"total":56},"passed":true},{"actual":{"lots":[[91,70]],"total":70},"check":"late cancel grace base","expected":{"lots":[[91,70]],"total":70},"passed":true},{"actual":{"lots":[[31,9]],"total":9},"check":"expired day before","expected":{"lots":[[39,9]],"total":9},"passed":false}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"mixed sources\", \"actual\": {\"lots\": [[140, 300], [150, 200], [400, 100]], \"total\": 600}, \"expected\": {\"lots\": [[140, 300], [150, 200], [400, 100]], \"total\": 600}, \"passed\": true}, {\"check\": \"expires on cancel day\", \"actual\": {\"lots\": [[120, 51]], \"total\": 51}, \"expected\": {\"lots\": [[120, 51]], \"total\": 51}, \"passed\": true}, {\"check\": \"too late\", \"actual\": {\"error\": \"too late\"}, \"expected\": {\"error\": \"too late\"}, \"passed\": true}, {\"check\": \"age edge\", \"actual\": {\"lots\": [[115, 5], [500, 100]], \"total\": 105}, \"expected\": {\"lots\": [[115, 5], [500, 100]], \"total\": 105}, \"passed\": true}, {\"check\": \"merge grace lots\", \"actual\": {\"lots\": [[105, 31], [300, 7]], \"total\": 38}, \"expected\": {\"lots\": [[105, 31], [300, 7]], \"total\": 38}, \"passed\": true}, {\"check\": \"merge with original\", \"actual\": {\"lots\": [[90, 6], [150, 10], [200, 40]], \"total\": 56}, \"expected\": {\"lots\": [[90, 6], [150, 10], [200, 40]], \"total\": 56}, \"passed\": true}, {\"check\": \"late cancel grace base\", \"actual\": {\"lots\": [[91, 70]], \"total\": 70}, \"expected\": {\"lots\": [[91, 70]], \"total\": 70}, \"passed\": true}, {\"check\": \"expired day before\", \"actual\": {\"lots\": [[31, 9]], \"total\": 9}, \"expected\": {\"lots\": [[39, 9]], \"total\": 9}, \"passed\": false}], \"passed\": false}\n"},"broken":{"elapsed_ms":41.286,"exit_code":1,"observations":[{"actual":{"lots":[[140,300],[150,200],[400,100]],"total":600},"check":"mixed sources","expected":{"lots":[[140,300],[150,200],[400,100]],"total":600},"passed":true},{"actual":{"lots":[[150,51]],"total":51},"check":"expires on cancel day","expected":{"lots":[[120,51]],"total":51},"passed":false},{"actual":{"error":"too late"},"check":"too late","expected":{"error":"too late"},"passed":true},{"actual":{"lots":[[115,5],[500,100]],"total":105},"check":"age edge","expected":{"lots":[[115,5],[500,100]],"total":105},"passed":true},{"actual":{"lots":[[105,31],[300,7]],"total":38},"check":"merge grace lots","expected":{"lots":[[105,31],[300,7]],"total":38},"passed":true},{"actual":{"lots":[[90,6],[150,10],[200,40]],"total":56},"check":"merge with original","expected":{"lots":[[90,6],[150,10],[200,40]],"total":56},"passed":true},{"actual":{"lots":[[91,70]],"total":70},"check":"late cancel grace base","expected":{"lots":[[91,70]],"total":70},"passed":true},{"actual":{"lots":[[39,9]],"total":9},"check":"expired day before","expected":{"lots":[[39,9]],"total":9},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"mixed sources\", \"actual\": {\"lots\": [[140, 300], [150, 200], [400, 100]], \"total\": 600}, \"expected\": {\"lots\": [[140, 300], [150, 200], [400, 100]], \"total\": 600}, \"passed\": true}, {\"check\": \"expires on cancel day\", \"actual\": {\"lots\": [[150, 51]], \"total\": 51}, \"expected\": {\"lots\": [[120, 51]], \"total\": 51}, \"passed\": false}, {\"check\": \"too late\", \"actual\": {\"error\": \"too late\"}, \"expected\": {\"error\": \"too late\"}, \"passed\": true}, {\"check\": \"age edge\", \"actual\": {\"lots\": [[115, 5], [500, 100]], \"total\": 105}, \"expected\": {\"lots\": [[115, 5], [500, 100]], \"total\": 105}, \"passed\": true}, {\"check\": \"merge grace lots\", \"actual\": {\"lots\": [[105, 31], [300, 7]], \"total\": 38}, \"expected\": {\"lots\": [[105, 31], [300, 7]], \"total\": 38}, \"passed\": true}, {\"check\": \"merge with original\", \"actual\": {\"lots\": [[90, 6], [150, 10], [200, 40]], \"total\": 56}, \"expected\": {\"lots\": [[90, 6], [150, 10], [200, 40]], \"total\": 56}, \"passed\": true}, {\"check\": \"late cancel grace base\", \"actual\": {\"lots\": [[91, 70]], \"total\": 70}, \"expected\": {\"lots\": [[91, 70]], \"total\": 70}, \"passed\": true}, {\"check\": \"expired day before\", \"actual\": {\"lots\": [[39, 9]], \"total\": 9}, \"expected\": {\"lots\": [[39, 9]], \"total\": 9}, \"passed\": true}], \"passed\": false}\n"},"fixed":{"elapsed_ms":42.895,"exit_code":0,"observations":[{"actual":{"lots":[[140,300],[150,200],[400,100]],"total":600},"check":"mixed sources","expected":{"lots":[[140,300],[150,200],[400,100]],"total":600},"passed":true},{"actual":{"lots":[[120,51]],"total":51},"check":"expires on cancel day","expected":{"lots":[[120,51]],"total":51},"passed":true},{"actual":{"error":"too late"},"check":"too late","expected":{"error":"too late"},"passed":true},{"actual":{"lots":[[115,5],[500,100]],"total":105},"check":"age edge","expected":{"lots":[[115,5],[500,100]],"total":105},"passed":true},{"actual":{"lots":[[105,31],[300,7]],"total":38},"check":"merge grace lots","expected":{"lots":[[105,31],[300,7]],"total":38},"passed":true},{"actual":{"lots":[[90,6],[150,10],[200,40]],"total":56},"check":"merge with original","expected":{"lots":[[90,6],[150,10],[200,40]],"total":56},"passed":true},{"actual":{"lots":[[91,70]],"total":70},"check":"late cancel grace base","expected":{"lots":[[91,70]],"total":70},"passed":true},{"actual":{"lots":[[39,9]],"total":9},"check":"expired day before","expected":{"lots":[[39,9]],"total":9},"passed":true}],"passed":true,"stderr":"","stdout":"{\"observations\": [{\"check\": \"mixed sources\", \"actual\": {\"lots\": [[140, 300], [150, 200], [400, 100]], \"total\": 600}, \"expected\": {\"lots\": [[140, 300], [150, 200], [400, 100]], \"total\": 600}, \"passed\": true}, {\"check\": \"expires on cancel day\", \"actual\": {\"lots\": [[120, 51]], \"total\": 51}, \"expected\": {\"lots\": [[120, 51]], \"total\": 51}, \"passed\": true}, {\"check\": \"too late\", \"actual\": {\"error\": \"too late\"}, \"expected\": {\"error\": \"too late\"}, \"passed\": true}, {\"check\": \"age edge\", \"actual\": {\"lots\": [[115, 5], [500, 100]], \"total\": 105}, \"expected\": {\"lots\": [[115, 5], [500, 100]], \"total\": 105}, \"passed\": true}, {\"check\": \"merge grace lots\", \"actual\": {\"lots\": [[105, 31], [300, 7]], \"total\": 38}, \"expected\": {\"lots\": [[105, 31], [300, 7]], \"total\": 38}, \"passed\": true}, {\"check\": \"merge with original\", \"actual\": {\"lots\": [[90, 6], [150, 10], [200, 40]], \"total\": 56}, \"expected\": {\"lots\": [[90, 6], [150, 10], [200, 40]], \"total\": 56}, \"passed\": true}, {\"check\": \"late cancel grace base\", \"actual\": {\"lots\": [[91, 70]], \"total\": 70}, \"expected\": {\"lots\": [[91, 70]], \"total\": 70}, \"passed\": true}, {\"check\": \"expired day before\", \"actual\": {\"lots\": [[39, 9]], \"total\": 9}, \"expected\": {\"lots\": [[39, 9]], \"total\": 9}, \"passed\": true}], \"passed\": true}\n"}},"verified":true,"visibility":"public"}