FAILURE MAP
← Case archive

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.

Verified by executionVariant 1 · 9 checks per implementationDownload source bundle ↓JSON ↗

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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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