FA-60576 / Loyalty points ledgers / Open access
Account inactivity expiry: never-qualified reference · case 01
Accounts that never earned or redeemed keep their adjustment points forever.
ROOT CAUSE
With no qualifying activity the reference day defaults to today.
VERIFIED REPAIR
Fall back to the earliest activity day.
Unsuccessful approach: Falling back to day zero expires recently enrolled accounts immediately.
Case contract
Input {activity:[[day,kind,signed pts]], today, window}. Only activity dated <= today counts. Qualifying kinds are earn, redeem and transfer_out; the reference day is the latest qualifying day, or the earliest activity day if none qualifies (today if no activity). The account is inactive when today - reference > window; an inactive account with a positive balance forfeits it all. Return {expired, balance, last_activity}.
Why this case matters
Member point balances, statements and the program liability depend on this ledger rule.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
acts = [a for a in x['activity'] if a[0] <= x['today']]
qual = [d for d, k, p in acts if k in ('earn', 'redeem', 'transfer_out')]
bal = sum(p for d, k, p in acts)
if qual:
ref = max(qual)
else:
ref = x['today']
inactive = x['today'] - ref > x['window']
if inactive and bal > 0:
return {'expired': bal, 'balance': 0, 'last_activity': ref}
return {'expired': 0, 'balance': bal, 'last_activity': ref}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 51]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 351, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 301]], 'today': 700, 'window': 540}, {'expired': 801, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 101]], 'today': 600, 'window': 540}, {'expired': 501, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 251]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 251, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -101]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 799, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 101]], 'today': 600, 'window': 540}, {'expired': 101, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -151]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -51, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 71]], 'today': 950, 'window': 540}, {'expired': 76, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 21]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[801, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 801}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 52]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 352, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 302]], 'today': 700, 'window': 540}, {'expired': 802, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 102]], 'today': 600, 'window': 540}, {'expired': 502, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 252]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 252, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -102]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 798, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 102]], 'today': 600, 'window': 540}, {'expired': 102, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -152]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -52, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 72]], 'today': 950, 'window': 540}, {'expired': 77, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 22]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[802, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 802}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 53]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 353, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 303]], 'today': 700, 'window': 540}, {'expired': 803, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 103]], 'today': 600, 'window': 540}, {'expired': 503, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 253]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 253, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -103]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 797, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 103]], 'today': 600, 'window': 540}, {'expired': 103, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -153]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -53, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 73]], 'today': 950, 'window': 540}, {'expired': 78, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 23]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[803, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 803}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 54]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 354, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 304]], 'today': 700, 'window': 540}, {'expired': 804, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 104]], 'today': 600, 'window': 540}, {'expired': 504, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 254]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 254, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -104]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 796, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 104]], 'today': 600, 'window': 540}, {'expired': 104, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -154]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -54, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 74]], 'today': 950, 'window': 540}, {'expired': 79, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 24]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[804, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 804}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 55]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 355, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 305]], 'today': 700, 'window': 540}, {'expired': 805, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 105]], 'today': 600, 'window': 540}, {'expired': 505, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 255]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 255, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -105]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 795, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 105]], 'today': 600, 'window': 540}, {'expired': 105, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -155]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -55, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 75]], 'today': 950, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 25]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[805, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 805}]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| active member | {'balance': 351, 'expired': 0, 'last_activity': 500} | {'balance': 351, 'expired': 0, 'last_activity': 500} | Passed |
| inactive | {'balance': 0, 'expired': 801, 'last_activity': 100} | {'balance': 0, 'expired': 801, 'last_activity': 100} | Passed |
| bonus does not count | {'balance': 0, 'expired': 501, 'last_activity': 0} | {'balance': 0, 'expired': 501, 'last_activity': 0} | Passed |
| window edge | {'balance': 251, 'expired': 0, 'last_activity': 60} | {'balance': 251, 'expired': 0, 'last_activity': 60} | Passed |
| transfer out counts | {'balance': 799, 'expired': 0, 'last_activity': 300} | {'balance': 799, 'expired': 0, 'last_activity': 300} | Passed |
| never qualified | {'balance': 101, 'expired': 0, 'last_activity': 600} | {'balance': 0, 'expired': 101, 'last_activity': 5} | Failed |
| negative balance | {'balance': -51, 'expired': 0, 'last_activity': 0} | {'balance': -51, 'expired': 0, 'last_activity': 0} | Passed |
| unsorted list | {'balance': 0, 'expired': 76, 'last_activity': 400} | {'balance': 0, 'expired': 76, 'last_activity': 400} | Passed |
| future dated | {'balance': 0, 'expired': 80, 'last_activity': 10} | {'balance': 0, 'expired': 80, 'last_activity': 10} | Passed |
| recent adjust only | {'balance': 30, 'expired': 0, 'last_activity': 900} | {'balance': 30, 'expired': 0, 'last_activity': 801} | Failed |
SHA-256 / 7584a4f912b8c9cd0c799ab24fc785307a55eb8e80fd00239cb5b8d6a97554b1
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
acts = [a for a in x['activity'] if a[0] <= x['today']]
qual = [d for d, k, p in acts if k in ('earn', 'redeem', 'transfer_out')]
bal = sum(p for d, k, p in acts)
if qual:
ref = max(qual)
else:
ref = 0
inactive = x['today'] - ref > x['window']
if inactive and bal > 0:
return {'expired': bal, 'balance': 0, 'last_activity': ref}
return {'expired': 0, 'balance': bal, 'last_activity': ref}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 51]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 351, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 301]], 'today': 700, 'window': 540}, {'expired': 801, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 101]], 'today': 600, 'window': 540}, {'expired': 501, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 251]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 251, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -101]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 799, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 101]], 'today': 600, 'window': 540}, {'expired': 101, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -151]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -51, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 71]], 'today': 950, 'window': 540}, {'expired': 76, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 21]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[801, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 801}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 52]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 352, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 302]], 'today': 700, 'window': 540}, {'expired': 802, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 102]], 'today': 600, 'window': 540}, {'expired': 502, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 252]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 252, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -102]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 798, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 102]], 'today': 600, 'window': 540}, {'expired': 102, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -152]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -52, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 72]], 'today': 950, 'window': 540}, {'expired': 77, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 22]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[802, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 802}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 53]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 353, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 303]], 'today': 700, 'window': 540}, {'expired': 803, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 103]], 'today': 600, 'window': 540}, {'expired': 503, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 253]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 253, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -103]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 797, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 103]], 'today': 600, 'window': 540}, {'expired': 103, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -153]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -53, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 73]], 'today': 950, 'window': 540}, {'expired': 78, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 23]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[803, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 803}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 54]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 354, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 304]], 'today': 700, 'window': 540}, {'expired': 804, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 104]], 'today': 600, 'window': 540}, {'expired': 504, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 254]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 254, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -104]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 796, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 104]], 'today': 600, 'window': 540}, {'expired': 104, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -154]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -54, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 74]], 'today': 950, 'window': 540}, {'expired': 79, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 24]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[804, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 804}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 55]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 355, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 305]], 'today': 700, 'window': 540}, {'expired': 805, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 105]], 'today': 600, 'window': 540}, {'expired': 505, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 255]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 255, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -105]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 795, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 105]], 'today': 600, 'window': 540}, {'expired': 105, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -155]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -55, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 75]], 'today': 950, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 25]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[805, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 805}]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| active member | {'balance': 351, 'expired': 0, 'last_activity': 500} | {'balance': 351, 'expired': 0, 'last_activity': 500} | Passed |
| inactive | {'balance': 0, 'expired': 801, 'last_activity': 100} | {'balance': 0, 'expired': 801, 'last_activity': 100} | Passed |
| bonus does not count | {'balance': 0, 'expired': 501, 'last_activity': 0} | {'balance': 0, 'expired': 501, 'last_activity': 0} | Passed |
| window edge | {'balance': 251, 'expired': 0, 'last_activity': 60} | {'balance': 251, 'expired': 0, 'last_activity': 60} | Passed |
| transfer out counts | {'balance': 799, 'expired': 0, 'last_activity': 300} | {'balance': 799, 'expired': 0, 'last_activity': 300} | Passed |
| never qualified | {'balance': 0, 'expired': 101, 'last_activity': 0} | {'balance': 0, 'expired': 101, 'last_activity': 5} | Failed |
| negative balance | {'balance': -51, 'expired': 0, 'last_activity': 0} | {'balance': -51, 'expired': 0, 'last_activity': 0} | Passed |
| unsorted list | {'balance': 0, 'expired': 76, 'last_activity': 400} | {'balance': 0, 'expired': 76, 'last_activity': 400} | Passed |
| future dated | {'balance': 0, 'expired': 80, 'last_activity': 10} | {'balance': 0, 'expired': 80, 'last_activity': 10} | Passed |
| recent adjust only | {'balance': 0, 'expired': 30, 'last_activity': 0} | {'balance': 30, 'expired': 0, 'last_activity': 801} | Failed |
SHA-256 / f23b873abea4da25fab10e81b981cde0e5830aabc24351d96e66f9ca3a1d0891
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
acts = [a for a in x['activity'] if a[0] <= x['today']]
qual = [d for d, k, p in acts if k in ('earn', 'redeem', 'transfer_out')]
bal = sum(p for d, k, p in acts)
if qual:
ref = max(qual)
else:
ref = min([d for d, k, p in acts] or [x['today']])
inactive = x['today'] - ref > x['window']
if inactive and bal > 0:
return {'expired': bal, 'balance': 0, 'last_activity': ref}
return {'expired': 0, 'balance': bal, 'last_activity': ref}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 51]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 351, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 301]], 'today': 700, 'window': 540}, {'expired': 801, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 101]], 'today': 600, 'window': 540}, {'expired': 501, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 251]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 251, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -101]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 799, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 101]], 'today': 600, 'window': 540}, {'expired': 101, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -151]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -51, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 71]], 'today': 950, 'window': 540}, {'expired': 76, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 21]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[801, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 801}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 52]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 352, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 302]], 'today': 700, 'window': 540}, {'expired': 802, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 102]], 'today': 600, 'window': 540}, {'expired': 502, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 252]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 252, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -102]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 798, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 102]], 'today': 600, 'window': 540}, {'expired': 102, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -152]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -52, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 72]], 'today': 950, 'window': 540}, {'expired': 77, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 22]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[802, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 802}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 53]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 353, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 303]], 'today': 700, 'window': 540}, {'expired': 803, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 103]], 'today': 600, 'window': 540}, {'expired': 503, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 253]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 253, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -103]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 797, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 103]], 'today': 600, 'window': 540}, {'expired': 103, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -153]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -53, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 73]], 'today': 950, 'window': 540}, {'expired': 78, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 23]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[803, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 803}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 54]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 354, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 304]], 'today': 700, 'window': 540}, {'expired': 804, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 104]], 'today': 600, 'window': 540}, {'expired': 504, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 254]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 254, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -104]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 796, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 104]], 'today': 600, 'window': 540}, {'expired': 104, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -154]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -54, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 74]], 'today': 950, 'window': 540}, {'expired': 79, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 24]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[804, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 804}]], [['active member', {'activity': [[10, 'earn', 500], [200, 'redeem', -200], [500, 'earn', 55]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 355, 'last_activity': 500}], ['inactive', {'activity': [[10, 'earn', 500], [100, 'earn', 305]], 'today': 700, 'window': 540}, {'expired': 805, 'balance': 0, 'last_activity': 100}], ['bonus does not count', {'activity': [[0, 'earn', 400], [500, 'bonus_credit', 105]], 'today': 600, 'window': 540}, {'expired': 505, 'balance': 0, 'last_activity': 0}], ['window edge', {'activity': [[60, 'earn', 255]], 'today': 600, 'window': 540}, {'expired': 0, 'balance': 255, 'last_activity': 60}], ['transfer out counts', {'activity': [[0, 'earn', 900], [300, 'transfer_out', -105]], 'today': 800, 'window': 540}, {'expired': 0, 'balance': 795, 'last_activity': 300}], ['never qualified', {'activity': [[5, 'adjust', 105]], 'today': 600, 'window': 540}, {'expired': 105, 'balance': 0, 'last_activity': 5}], ['negative balance', {'activity': [[0, 'earn', 100], [10, 'adjust', -155]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': -55, 'last_activity': 0}], ['unsorted list', {'activity': [[400, 'earn', 10], [20, 'redeem', -5], [30, 'earn', 75]], 'today': 950, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 400}], ['future dated', {'activity': [[10, 'earn', 80], [1000, 'earn', 25]], 'today': 900, 'window': 540}, {'expired': 80, 'balance': 0, 'last_activity': 10}], ['recent adjust only', {'activity': [[805, 'adjust', 30]], 'today': 900, 'window': 540}, {'expired': 0, 'balance': 30, 'last_activity': 805}]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| active member | {'balance': 351, 'expired': 0, 'last_activity': 500} | {'balance': 351, 'expired': 0, 'last_activity': 500} | Passed |
| inactive | {'balance': 0, 'expired': 801, 'last_activity': 100} | {'balance': 0, 'expired': 801, 'last_activity': 100} | Passed |
| bonus does not count | {'balance': 0, 'expired': 501, 'last_activity': 0} | {'balance': 0, 'expired': 501, 'last_activity': 0} | Passed |
| window edge | {'balance': 251, 'expired': 0, 'last_activity': 60} | {'balance': 251, 'expired': 0, 'last_activity': 60} | Passed |
| transfer out counts | {'balance': 799, 'expired': 0, 'last_activity': 300} | {'balance': 799, 'expired': 0, 'last_activity': 300} | Passed |
| never qualified | {'balance': 0, 'expired': 101, 'last_activity': 5} | {'balance': 0, 'expired': 101, 'last_activity': 5} | Passed |
| negative balance | {'balance': -51, 'expired': 0, 'last_activity': 0} | {'balance': -51, 'expired': 0, 'last_activity': 0} | Passed |
| unsorted list | {'balance': 0, 'expired': 76, 'last_activity': 400} | {'balance': 0, 'expired': 76, 'last_activity': 400} | Passed |
| future dated | {'balance': 0, 'expired': 80, 'last_activity': 10} | {'balance': 0, 'expired': 80, 'last_activity': 10} | Passed |
| recent adjust only | {'balance': 30, 'expired': 0, 'last_activity': 801} | {'balance': 30, 'expired': 0, 'last_activity': 801} | Passed |
SHA-256 / 91ec9dcc3f6efc4eadffda7be9a91150c3c9a6d1ca775ffbb757d64fafaa891a
Verification & scope
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.
Observations recorded using Python 3.12.14 at 2026-09-29T14:46:46.929778+00:00.
Case digest / 61e8b6d5d81f6a215fbf02d62192200b95712efd87310931ca953a7a49bded64