import json, glob from collections import defaultdict files = sorted(glob.glob('/tmp/ipl_json/*.json')) files = [f for f in files if '/README' not in f and 'people.json' not in f] # For each match's 2nd innings, walk ball-by-ball and record (balls_remaining_bucket, runs_required_bucket, wickets_in_hand) -> did chasing team win # Buckets: balls_remaining in [0,120] step 6 (over-level); runs_required capped/bucketed; wickets_in_hand 0-10 outcomes = defaultdict(lambda: [0, 0]) # key -> [wins, total] matches_processed = 0 skipped = 0 per_over_totals = defaultdict(list) # over_number(0-19) -> list of runs scored that over (for "par score" reference) for f in files: try: d = json.load(open(f)) except Exception: skipped += 1; continue info = d['info'] if info.get('match_type') != 'T20': pass # IPL is all T20, keep anyway outcome = info.get('outcome', {}) winner = outcome.get('winner') if not winner or 'result' in outcome: # skip no-result/tied edge cases for simplicity if outcome.get('result') in ('tie','no result'): skipped += 1; continue innings = d.get('innings', []) if len(innings) < 2: skipped += 1; continue inn1 = innings[0] target = 0 for ov in inn1['overs']: for ball in ov['deliveries']: target += ball['runs']['total'] # first-innings per-over tracking (for par score reference) # recompute target properly and per-over totals for innings 1 (par score reference) target = 0 over_runs = defaultdict(int) for ov in inn1['overs']: for ball in ov['deliveries']: target += ball['runs']['total'] over_runs[ov['over']] += ball['runs']['total'] for ov_num, runs in over_runs.items(): per_over_totals[ov_num].append(runs) target += 1 # runs needed to win inn2 = innings[1] chasing_team = inn2['team'] chasing_won = (winner == chasing_team) balls_bowled = 0 runs_scored = 0 wickets_down = 0 TOTAL_BALLS = 120 # T20 = 20 overs * 6, ignore rare non-6-ball overs for bucketing simplicity for ov in inn2['overs']: for ball in ov['deliveries']: balls_bowled += 1 runs_scored += ball['runs']['total'] is_wicket = 'wickets' in ball # snapshot state BEFORE this ball resolves (i.e., using pre-ball counts) then update balls_remaining = max(0, TOTAL_BALLS - balls_bowled + 1) # state at start of this ball runs_required = max(0, target - (runs_scored - ball['runs']['total'])) wkts_in_hand = 10 - wickets_down # bucket: balls_remaining to nearest 6 (over granularity), runs_required to nearest 5, wkts_in_hand exact br_bucket = round(balls_remaining/6)*6 rr_bucket = round(runs_required/5)*5 key = (br_bucket, rr_bucket, wkts_in_hand) outcomes[key][1] += 1 if chasing_won: outcomes[key][0] += 1 if is_wicket: wickets_down += len(ball['wickets']) matches_processed += 1 print(f"Processed {matches_processed} matches, skipped {skipped}") print(f"Unique game states: {len(outcomes)}") # Save the win-probability lookup table (only keep states with enough samples for reliability) reliable = {f"{k[0]},{k[1]},{k[2]}": {"wins": v[0], "total": v[1], "win_pct": round(100*v[0]/v[1],1)} for k,v in outcomes.items() if v[1] >= 15} print(f"Reliable states (n>=15): {len(reliable)}") json.dump({ 'meta': { 'source': 'Cricsheet ball-by-ball IPL data (cricsheet.org)', 'matches_processed': matches_processed, 'method': 'For every ball of every 2nd innings in 1,243 IPL matches (2008-2026), record game state (balls remaining, runs required, wickets in hand) and whether the chasing team ultimately won. Bucketed to over-level balls-remaining and nearest-5 runs-required for sample density. States with fewer than 15 historical observations excluded as unreliable.', 'total_states': len(outcomes), 'reliable_states': len(reliable), }, 'win_prob_table': reliable, }, open('/tmp/winprob_table.json','w')) print("saved")