"""When should a ReAct-style agent refresh its observations?
Deterministic, standard-library experiment. RESULTS contains the full data.
"""
STATES = [(1, 1), (0, 1), (1, 0), (0, 0), (2, 0), (0, 2)]

def choose(observation):
    return next((b for b in ('A', 'B') if observation[b] > 0), None)

def run(refresh):
    totals = dict(completed=0, invalid=0, abstained=0, observations=0)
    for stocks in STATES:
        world = dict(A=1, B=1)
        observed = world.copy()
        totals['observations'] += 1
        world.update(zip(('A', 'B'), stocks))
        if refresh:
            observed = world.copy()
            totals['observations'] += 1
        target = choose(observed)
        if target is None:
            totals['abstained'] += 1
        elif world[target] <= 0:
            totals['invalid'] += 1
        else:
            world[target] -= 1
            totals['completed'] += 1
    assert sum(totals[k] for k in ('completed', 'invalid', 'abstained')) == 6
    return totals

for policy in (False, True):
    print('refresh=' + str(policy), run(policy))
assert run(False)['invalid'] == 3
assert run(True)['completed'] == 5


rows = []
for index, stocks in enumerate(STATES):
    for policy in ('cached', 'refreshed'):
        observed = (1, 1) if policy == 'cached' else stocks
        choice = choose(dict(zip(('A', 'B'), observed)))
        outcome = 'abstained' if choice is None else ('completed' if stocks[('A', 'B').index(choice)] > 0 else 'invalid')
        rows.append(dict(case=index, stock=list(stocks), policy=policy, observation=list(observed), choice=choice, outcome=outcome))
races = []
for middle in STATES:
    for final in STATES:
        choice = choose(dict(zip(('A', 'B'), middle)))
        changed = middle != final
        unguarded = 'abstained' if choice is None else ('completed' if final[('A','B').index(choice)] > 0 else 'invalid')
        guarded = 'abstained' if choice is None else ('canceled' if changed else unguarded)
        races.append(dict(observed=list(middle), final=list(final), choice=choice, refresh_only=unguarded, revision_checked=guarded))
assert all(r['revision_checked'] != 'invalid' for r in races)
RESULTS = dict(cases=rows, totals={p:run(p == 'refreshed') for p in ('cached','refreshed')}, post_observation_changes=races)
print('Post-observation cross product:', len(races), 'cases; revision-check invalid actions:', sum(r['revision_checked']=='invalid' for r in races))
