FA-60566 / Loyalty points ledgers / Open access
Account inactivity expiry: latest qualifying day · case 01
Accounts with recent activity are expired because the clock runs from their first earn.
ROOT CAUSE
The reference day is the earliest qualifying day instead of the latest.
VERIFIED REPAIR
Use the latest qualifying activity day.
Unsuccessful approach: Taking the last qualifying entry in list order fails when activity is not sorted by day.
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 = min(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}]], [['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}]], [['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}]], [['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}]], [['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}]]]
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': 0, 'expired': 351, 'last_activity': 10} | {'balance': 351, 'expired': 0, 'last_activity': 500} | Failed |
| inactive | {'balance': 0, 'expired': 801, 'last_activity': 10} | {'balance': 0, 'expired': 801, 'last_activity': 100} | Failed |
| 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': 0, 'expired': 799, 'last_activity': 0} | {'balance': 799, 'expired': 0, 'last_activity': 300} | Failed |
| 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': 20} | {'balance': 0, 'expired': 76, 'last_activity': 400} | Failed |
| future dated | {'balance': 0, 'expired': 80, 'last_activity': 10} | {'balance': 0, 'expired': 80, 'last_activity': 10} | Passed |
SHA-256 / 55ecd9b6cb062cba957bdd6e894d1a75c4a99c9ff77e0890108fe856a8172929
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 = qual[-1]
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}]], [['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}]], [['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}]], [['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}]], [['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}]]]
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': 30} | {'balance': 0, 'expired': 76, 'last_activity': 400} | Failed |
| future dated | {'balance': 0, 'expired': 80, 'last_activity': 10} | {'balance': 0, 'expired': 80, 'last_activity': 10} | Passed |
SHA-256 / 4927822d845e06ce1209b74b5319d406de47ee23dee4eddb63d37e695b7877cd
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}]], [['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}]], [['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}]], [['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}]], [['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}]]]
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 |
SHA-256 / d752781b4f0be2f659f4081a74fc167913c4f8c0a40b66eeea8ee401b5509f51
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.756944+00:00.
Case digest / 302b517351e63cbb0fc2efd50e7c245e59a61ec30d0985cf695fd96a0252b055