diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0265fcb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + rust: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Check formatting + run: cargo fmt --check + + - name: Lint + run: cargo clippy --all-targets -- -D warnings + + - name: Test + run: cargo test --all-targets + + - name: Doctest + run: cargo test --doc + + - name: Build docs + run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps + + - name: Verify package + run: cargo package diff --git a/Cargo.toml b/Cargo.toml index b01c070..b7a47e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,12 @@ version = "0.1.0" edition = "2021" license = "MIT" description = "Stateful baseball game replay engine for scoring event streams" +repository = "https://github.com/Jud/diamond-replay" +homepage = "https://github.com/Jud/diamond-replay" +documentation = "https://docs.rs/diamond-replay" +readme = "README.md" +keywords = ["baseball", "replay", "statistics", "gamechanger", "tui"] +categories = ["command-line-utilities", "parser-implementations"] [dependencies] crossterm = { version = "0.29.0", features = ["event-stream"] } diff --git a/README.md b/README.md index 50c1674..4efa5c3 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,18 @@ Four TUI views: Box Score, Batting, Pitching, Little League. Press `?` on any st `--little-league` adds per-team youth stats: run sourcing, pace, baserunning chaos, free bases. -`--no-steal-home` replays the game with steal-of-home attempts blocked. Runners stay at 3B but auto-score on any subsequent ball in play that doesn't end the inning, plus walks, WP, and PB. +`--no-steal-home` replays the game with chaos scoring from 3B blocked. Runners stay at 3B but auto-score on any subsequent ball in play that doesn't end the inning, plus forced advances such as walks and HBP. ## Library ```rust -let result = diamond_replay::replay_from_json(&event_json)?; +use diamond_replay::{replay_from_json, replay_from_json_with_options, ReplayOptions}; + +let result = replay_from_json(&event_json)?; +let simulated = replay_from_json_with_options( + &event_json, + ReplayOptions::no_steal_home(), +)?; for (id, player) in &result.player_stats { let b = &player.batting; @@ -30,11 +36,13 @@ for (id, player) in &result.player_stats { } ``` +The supported library surface is exposed from the crate root: replay functions, `ReplayOptions`, `RuleSet`, `RawApiEvent`, `ReplayError`, `GameResult`, and the result stat structs. + ## Stats ### Batting -PA, AB, H, TB, XBH, AVG, OBP, SLG, OPS, ISO, BABIP, wOBA, K%, BB%, BB/K, GB%, FB%, LD%, HR/FB, RBI, R, SB, CS, SB%, GIDP, QAB%, Competitive AB%, P/PA, Hard Hit%. +PA, AB, H, TB, XBH, AVG, OBP, SLG, OPS, ISO, BABIP, wOBA, K%, BB%, BB/K, GB%, FB%, LD%, HR/FB, RBI, R, SB, CS, SB%, GIDP, CI, QAB%, Competitive AB%, P/PA, Hard Hit%. ### Pitching @@ -76,18 +84,18 @@ See `testdata/` for 14 complete game files from real 10U and 13U games. cargo test ``` -68 tests: 39 unit (stat computation, stat help coverage), 29 integration (full-game linescores, LL balance invariants, undo/redo, simulation). +100 tests: 64 unit (stat computation, stat help coverage, rule compiler, stream traversal, shadow state, lineup mutation handling), 34 integration/artifact tests (full-game linescores, accounting gates, LL balance invariants, undo/redo, simulation), and 2 doctests. ## Architecture -~5,500 lines of Rust. Dependencies: `serde`, `serde_json`, `thiserror`, `ratatui`, `crossterm`. +~6,200 lines of Rust, plus fixture-backed integration tests. Dependencies: `serde`, `serde_json`, `thiserror`, `ratatui`, `crossterm`. ``` src/ - lib.rs public API + lib.rs public API and stable type re-exports event.rs JSON parsing, typed event enums undo.rs stack-based undo/redo resolution - filter.rs EventFilter trait, simulation filters + rules/ rule-set event stream compilers state.rs GameState, BaseState, PAContext replay.rs state machine, event loop, LL stats compute.rs pure stat formulas diff --git a/docs/LEGACY_STATS.md b/docs/LEGACY_STATS.md index c6dce20..873e7e9 100644 --- a/docs/LEGACY_STATS.md +++ b/docs/LEGACY_STATS.md @@ -73,7 +73,8 @@ pub struct RawStats { | `strikes_looking` | `pitch` result = `strike_looking` | | `fouls` | `pitch` result = `foul` | | `bip` | `pitch` result = `ball_in_play` | -| `hbp` | `pitch` result = `hit_by_pitch`, or `end_at_bat` reason = `hit_by_pitch` or `catcher_interference` | +| `hbp` | `pitch` result = `hit_by_pitch`, or `end_at_bat` reason = `hit_by_pitch` | +| `ci` | `end_at_bat` reason = `catcher_interference` | | `k` | Strike count reaches 3 (via strikes), or dropped third strike | | `k_looking` | K where last strike was `strike_looking` | | `k_swinging` | K where last strike was `strike_swinging` | @@ -115,6 +116,7 @@ pub struct BattingStats { pub k_swinging: i32, // strikeouts swinging pub bb: i32, // walks pub hbp: i32, // hit by pitch + pub ci: i32, // catcher interference pub singles: i32, // singles pub doubles: i32, // doubles pub triples: i32, // triples @@ -130,12 +132,13 @@ pub struct BattingStats { | Field | Recorded by | Trigger | |-------|------------|---------| -| `pa` | `record_k`, `record_bb`, `record_hbp`, `record_bip`, `record_dropped_k` | Every completed PA | +| `pa` | `record_k`, `record_bb`, `record_hbp`, `record_ci`, `record_bip`, `record_dropped_k` | Every completed PA | | `k` | `record_k`, `record_dropped_k` | Strikeout (3 strikes or dropped third) | | `k_looking` | `record_k`, `record_dropped_k` | K where `looking=true` (last strike was called) | | `k_swinging` | `record_k`, `record_dropped_k` | K where `looking=false` | | `bb` | `record_bb` | Ball 4 | -| `hbp` | `record_hbp` | Hit by pitch or catcher interference | +| `hbp` | `record_hbp` | Hit by pitch | +| `ci` | `record_ci` | Catcher interference | | `singles` | `record_bip` | `PlayResult::Single` | | `doubles` | `record_bip` | `PlayResult::Double` | | `triples` | `record_bip` | `PlayResult::Triple` | diff --git a/src/bin/diamond-replay.rs b/src/bin/diamond-replay.rs index b77a4e4..4749068 100644 --- a/src/bin/diamond-replay.rs +++ b/src/bin/diamond-replay.rs @@ -1,3 +1,4 @@ +use std::cmp::Reverse; use std::collections::HashMap; use std::io::{self, stdout}; use std::{env, fs, process}; @@ -17,9 +18,10 @@ use ratatui::widgets::{ use ratatui::Terminal; use diamond_replay::stat_help; -use diamond_replay::player::{BattingStats, PitchingStats, PlayerGameStats}; -use diamond_replay::replay::{GameResult, LittleLeagueStats}; -use diamond_replay::{replay_from_json, replay_from_json_no_steal_home}; +use diamond_replay::{ + replay_from_json, replay_from_json_with_options, BattingStats, GameResult, LittleLeagueStats, + PitchingStats, PlayerGameStats, ReplayOptions, +}; // --------------------------------------------------------------------------- // Style constants @@ -168,11 +170,15 @@ impl App { return; } self.sort = match self.sort { - Some(s) if s.col == self.col_cursor && s.descending => { - Some(SortState { col: self.col_cursor, descending: false }) - } + Some(s) if s.col == self.col_cursor && s.descending => Some(SortState { + col: self.col_cursor, + descending: false, + }), Some(s) if s.col == self.col_cursor => None, - _ => Some(SortState { col: self.col_cursor, descending: true }), + _ => Some(SortState { + col: self.col_cursor, + descending: true, + }), }; } @@ -181,13 +187,16 @@ impl App { let home_team = truncate_team(&result.home_id, 20); match self.view { View::BoxScore => { - self.boxscore_content = build_boxscore_lines(result, away_team, home_team, self.sort); + self.boxscore_content = + build_boxscore_lines(result, away_team, home_team, self.sort); } View::Batting => { - self.batting_content = build_adv_batting_lines(result, away_team, home_team, self.sort); + self.batting_content = + build_adv_batting_lines(result, away_team, home_team, self.sort); } View::Pitching => { - self.pitching_content = build_pitching_lines(result, away_team, home_team, self.sort); + self.pitching_content = + build_pitching_lines(result, away_team, home_team, self.sort); } View::LittleLeague => {} } @@ -283,7 +292,7 @@ fn team_batters<'a>( .values() .filter(|p| p.team_id == team_id && !p.player_id.starts_with("__anon_") && p.batting.pa > 0) .collect(); - players.sort_by(|a, b| b.batting.pa.cmp(&a.batting.pa)); + players.sort_by_key(|p| Reverse(p.batting.pa)); players } @@ -549,7 +558,9 @@ fn build_boxscore_lines( ))); lines.push(build_batting_header(&cols)); let mut away = team_batters(&result.player_stats, &result.away_id); - sort_players(&mut away, sort, |p| boxscore_sort_val(&p.batting, sort.map_or(0, |s| s.col))); + sort_players(&mut away, sort, |p| { + boxscore_sort_val(&p.batting, sort.map_or(0, |s| s.col)) + }); for p in &away { lines.push(build_batting_row(p, &cols)); } @@ -567,7 +578,9 @@ fn build_boxscore_lines( ))); lines.push(build_batting_header(&cols)); let mut home = team_batters(&result.player_stats, &result.home_id); - sort_players(&mut home, sort, |p| boxscore_sort_val(&p.batting, sort.map_or(0, |s| s.col))); + sort_players(&mut home, sort, |p| { + boxscore_sort_val(&p.batting, sort.map_or(0, |s| s.col)) + }); for p in &home { lines.push(build_batting_row(p, &cols)); } @@ -645,7 +658,9 @@ fn build_adv_batting_lines( ))); lines.push(build_adv_batting_header()); let mut away = team_batters(&result.player_stats, &result.away_id); - sort_players(&mut away, sort, |p| batting_sort_val(&p.batting, sort.map_or(0, |s| s.col))); + sort_players(&mut away, sort, |p| { + batting_sort_val(&p.batting, sort.map_or(0, |s| s.col)) + }); for p in &away { lines.push(build_adv_batting_row(p)); } @@ -658,7 +673,9 @@ fn build_adv_batting_lines( ))); lines.push(build_adv_batting_header()); let mut home = team_batters(&result.player_stats, &result.home_id); - sort_players(&mut home, sort, |p| batting_sort_val(&p.batting, sort.map_or(0, |s| s.col))); + sort_players(&mut home, sort, |p| { + batting_sort_val(&p.batting, sort.map_or(0, |s| s.col)) + }); for p in &home { lines.push(build_adv_batting_row(p)); } @@ -735,7 +752,9 @@ fn build_pitching_lines( lines.push(build_pitching_header()); let mut away = team_pitchers(&result.player_stats, &result.away_id); sort_players(&mut away, sort, |p| { - p.pitching.as_ref().map_or(-1.0, |ps| pitching_sort_val(ps, sort.map_or(0, |s| s.col))) + p.pitching + .as_ref() + .map_or(-1.0, |ps| pitching_sort_val(ps, sort.map_or(0, |s| s.col))) }); for p in &away { lines.push(build_pitching_row(p)); @@ -750,7 +769,9 @@ fn build_pitching_lines( lines.push(build_pitching_header()); let mut home = team_pitchers(&result.player_stats, &result.home_id); sort_players(&mut home, sort, |p| { - p.pitching.as_ref().map_or(-1.0, |ps| pitching_sort_val(ps, sort.map_or(0, |s| s.col))) + p.pitching + .as_ref() + .map_or(-1.0, |ps| pitching_sort_val(ps, sort.map_or(0, |s| s.col))) }); for p in &home { lines.push(build_pitching_row(p)); @@ -781,19 +802,22 @@ fn build_ll_team_lines(ll: &LittleLeagueStats, team_name: &str) -> Vec 0 { - format!("{:.1}%", f64::from(ll.runs_on_bip) / f64::from(total_runs) * 100.0) + format!( + "{:.1}%", + f64::from(ll.runs_on_bip) / f64::from(total_runs) * 100.0 + ) } else { "-".to_string() }; lines.push(ll_row("Runs on BIP", &ll.runs_on_bip.to_string())); lines.push(Line::from(vec![ - Span::styled(format!(" {:<28}", "Passive Runs"), Style::default().fg(Color::Gray)), + Span::styled( + format!(" {:<28}", "Passive Runs"), + Style::default().fg(Color::Gray), + ), Span::styled(format!("{:>6}", ll.runs_passive), Style::default()), Span::styled(" (BB/HBP/WP/PB)", Style::default().fg(Color::DarkGray)), ])); @@ -821,10 +845,7 @@ fn build_ll_team_lines(ll: &LittleLeagueStats, team_name: &str) -> Vec Vec Vec> { +fn build_ll_lines(result: &GameResult, away_team: &str, home_team: &str) -> Vec> { let mut lines = Vec::new(); lines.extend(build_ll_team_lines(&result.away_little_league, away_team)); lines.extend(build_ll_team_lines(&result.home_little_league, home_team)); @@ -850,7 +867,12 @@ fn build_ll_lines( // --------------------------------------------------------------------------- fn draw_header(frame: &mut ratatui::Frame, area: Rect, current_view: View, game_name: &str) { - let views = [View::BoxScore, View::Batting, View::Pitching, View::LittleLeague]; + let views = [ + View::BoxScore, + View::Batting, + View::Pitching, + View::LittleLeague, + ]; let tab_spans: Vec = views .iter() .enumerate() @@ -905,13 +927,17 @@ fn draw_body(frame: &mut ratatui::Frame, area: Rect, app: &mut App) { // Apply alternating row colors and column highlight at draw time let mut lines = content_lines.clone(); let cols = app.view.stat_columns(); - let highlight_span = if cols.is_empty() { None } else { Some(app.col_cursor + 1) }; + let highlight_span = if cols.is_empty() { + None + } else { + Some(app.col_cursor + 1) + }; let zebra_bg = Style::default().bg(Color::Indexed(236)); let mut data_row: usize = 0; for line in &mut lines { - let is_header = line.spans.len() > 1 - && line.spans.first().is_some_and(|s| s.style == HEADER_STYLE); + let is_header = + line.spans.len() > 1 && line.spans.first().is_some_and(|s| s.style == HEADER_STYLE); let is_data = line.spans.len() > 1 && !is_header; // Highlight selected column in header rows + sort indicator @@ -1027,12 +1053,24 @@ fn draw_help_overlay(frame: &mut ratatui::Frame, help: &stat_help::StatHelp, scr lines.push(Line::from("")); if !help.formula.is_empty() { - let bold_white = Style::default().fg(Color::White).add_modifier(Modifier::BOLD); + let bold_white = Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD); section(&mut lines, "Formula", help.formula, bold_white); } - section(&mut lines, "MLB Benchmarks", help.mlb_benchmark, Style::default()); - section(&mut lines, "Youth Context", help.youth_context, Style::default()); + section( + &mut lines, + "MLB Benchmarks", + help.mlb_benchmark, + Style::default(), + ); + section( + &mut lines, + "Youth Context", + help.youth_context, + Style::default(), + ); section(&mut lines, "Caveats", help.caveats, Style::default()); lines.push(Line::from(Span::styled( @@ -1114,10 +1152,8 @@ fn run_tui(result: &GameResult, game_name: String) -> io::Result<()> { }, _ => match key.code { KeyCode::Char('q') | KeyCode::Esc => break, - KeyCode::Char('?') => { - if !app.view.stat_columns().is_empty() { - app.show_help = true; - } + KeyCode::Char('?') if !app.view.stat_columns().is_empty() => { + app.show_help = true; } KeyCode::Enter => { app.toggle_sort(); @@ -1150,19 +1186,29 @@ fn run_tui(result: &GameResult, game_name: String) -> io::Result<()> { // --------------------------------------------------------------------------- fn pct1(num: i32, den: i32) -> f64 { - if den == 0 { 0.0 } else { (f64::from(num) / f64::from(den) * 100.0 * 10.0).round() / 10.0 } + if den == 0 { + 0.0 + } else { + (f64::from(num) / f64::from(den) * 100.0 * 10.0).round() / 10.0 + } } fn ratio1(num: i32, den: i32) -> f64 { - if den == 0 { 0.0 } else { (f64::from(num) / f64::from(den) * 10.0).round() / 10.0 } + if den == 0 { + 0.0 + } else { + (f64::from(num) / f64::from(den) * 10.0).round() / 10.0 + } } fn median_i32(v: &[i32]) -> f64 { - if v.is_empty() { return 0.0; } + if v.is_empty() { + return 0.0; + } let mut sorted = v.to_vec(); sorted.sort_unstable(); let mid = sorted.len() / 2; - if sorted.len() % 2 == 0 { + if sorted.len().is_multiple_of(2) { f64::from(sorted[mid - 1] + sorted[mid]) / 2.0 } else { f64::from(sorted[mid]) @@ -1171,12 +1217,15 @@ fn median_i32(v: &[i32]) -> f64 { /// Compute per-inning rate using outs recorded (handles partial innings). fn per_inn(num: i32, outs: i32) -> f64 { - if outs == 0 { return 0.0; } + if outs == 0 { + return 0.0; + } let ip = f64::from(outs) / 3.0; (f64::from(num) / ip * 10.0).round() / 10.0 } /// Build the flat per-team stats object matching the stats-2026.html schema. +#[allow(clippy::too_many_arguments)] fn build_ll_team_json( bat: &BattingStats, own_pitch: &PitchingStats, @@ -1187,7 +1236,7 @@ fn build_ll_team_json( innings_bat: i32, innings_field: i32, ) -> serde_json::Value { - use serde_json::{Map, Value, Number}; + use serde_json::{Map, Number, Value}; let runs_total = bat.runs; let bip = opp_pitch.bip; @@ -1233,16 +1282,28 @@ fn build_ll_team_json( m.insert("BB_pct".into(), f(pct1(bat.bb, bat.pa))); m.insert("BIP_pct".into(), f(pct1(bip, bat.pa))); m.insert("HBP_pct".into(), f(pct1(bat.hbp, bat.pa))); - m.insert("pitches_per_PA".into(), f(ratio1(opp_pitch.pitches, bat.pa))); - m.insert("median_pitches_between_bip".into(), f(median_i32(&ll.pitches_between_bip))); + m.insert( + "pitches_per_PA".into(), + f(ratio1(opp_pitch.pitches, bat.pa)), + ); + m.insert( + "median_pitches_between_bip".into(), + f(median_i32(&ll.pitches_between_bip)), + ); m.insert("pitches_per_BIP".into(), f(ratio1(opp_pitch.pitches, bip))); m.insert("K_per_inn".into(), f(per_inn(bat.k, outs_bat))); m.insert("BB_per_inn".into(), f(per_inn(bat.bb, outs_bat))); m.insert("BIP_per_inn".into(), f(per_inn(bip, outs_bat))); m.insert("runs_total".into(), i(runs_total)); - m.insert("runs_on_bip_pct".into(), f(pct1(ll.runs_on_bip, runs_total))); + m.insert( + "runs_on_bip_pct".into(), + f(pct1(ll.runs_on_bip, runs_total)), + ); m.insert("free_bases".into(), i(free_bases)); - m.insert("free_bases_per_inn".into(), f(per_inn(free_bases, outs_bat))); + m.insert( + "free_bases_per_inn".into(), + f(per_inn(free_bases, outs_bat)), + ); // Pitching side m.insert("pitch_pitches".into(), i(own_pitch.pitches)); @@ -1251,20 +1312,44 @@ fn build_ll_team_json( m.insert("pitch_strikes_look".into(), i(own_pitch.strikes_looking)); m.insert("pitch_fouls".into(), i(own_pitch.fouls)); m.insert("pitch_bip".into(), i(own_pitch.bip)); - m.insert("pitch_ball_pct".into(), f(pct1(own_pitch.balls, own_pitch.pitches))); - m.insert("pitch_strike_pct".into(), f(pct1(own_pitch.pitches - own_pitch.balls, own_pitch.pitches))); + m.insert( + "pitch_ball_pct".into(), + f(pct1(own_pitch.balls, own_pitch.pitches)), + ); + m.insert( + "pitch_strike_pct".into(), + f(pct1(own_pitch.pitches - own_pitch.balls, own_pitch.pitches)), + ); m.insert("pitch_K".into(), i(own_pitch.k)); m.insert("pitch_BB".into(), i(own_pitch.bb)); - m.insert("pitch_K_per_inn".into(), f(per_inn(own_pitch.k, outs_field))); - m.insert("pitch_BB_per_inn".into(), f(per_inn(own_pitch.bb, outs_field))); - m.insert("pitch_BIP_per_inn".into(), f(per_inn(own_pitch.bip, outs_field))); - m.insert("pitch_pitches_per_BIP".into(), f(ratio1(own_pitch.pitches, own_pitch.bip))); - m.insert("pitch_median_p_between_bip".into(), f(median_i32(&ll.pitches_between_bip_pitching))); + m.insert( + "pitch_K_per_inn".into(), + f(per_inn(own_pitch.k, outs_field)), + ); + m.insert( + "pitch_BB_per_inn".into(), + f(per_inn(own_pitch.bb, outs_field)), + ); + m.insert( + "pitch_BIP_per_inn".into(), + f(per_inn(own_pitch.bip, outs_field)), + ); + m.insert( + "pitch_pitches_per_BIP".into(), + f(ratio1(own_pitch.pitches, own_pitch.bip)), + ); + m.insert( + "pitch_median_p_between_bip".into(), + f(median_i32(&ll.pitches_between_bip_pitching)), + ); // Defense side m.insert("def_sb".into(), i(opp_bat.sb)); m.insert("def_free_bases".into(), i(def_free)); - m.insert("def_free_bases_per_inn".into(), f(per_inn(def_free, outs_field))); + m.insert( + "def_free_bases_per_inn".into(), + f(per_inn(def_free, outs_field)), + ); Value::Object(m) } @@ -1303,24 +1388,31 @@ fn dump_json(result: &GameResult, game_name: &str, include_ll: bool) { } else { (durations.iter().sum::() / durations.len() as f64 * 10.0).round() / 10.0 }; - obj.insert("inning_durations_min".into(), serde_json::json!( - durations.iter().map(|d| (d * 10.0).round() / 10.0).collect::>() - )); + obj.insert( + "inning_durations_min".into(), + serde_json::json!(durations + .iter() + .map(|d| (d * 10.0).round() / 10.0) + .collect::>()), + ); obj.insert("avg_inning_min".into(), serde_json::json!(avg_inning_min)); - obj.insert("teams".to_string(), serde_json::json!({ - result.away_id.clone(): build_ll_team_json( - &result.away_batting, &result.away_pitching, &result.home_pitching, - &result.home_batting, &result.home_little_league, - &result.away_little_league, - away_inn_bat, home_inn_bat, - ), - result.home_id.clone(): build_ll_team_json( - &result.home_batting, &result.home_pitching, &result.away_pitching, - &result.away_batting, &result.away_little_league, - &result.home_little_league, - home_inn_bat, away_inn_bat, - ), - })); + obj.insert( + "teams".to_string(), + serde_json::json!({ + result.away_id.clone(): build_ll_team_json( + &result.away_batting, &result.away_pitching, &result.home_pitching, + &result.home_batting, &result.home_little_league, + &result.away_little_league, + away_inn_bat, home_inn_bat, + ), + result.home_id.clone(): build_ll_team_json( + &result.home_batting, &result.home_pitching, &result.away_pitching, + &result.away_batting, &result.away_little_league, + &result.home_little_league, + home_inn_bat, away_inn_bat, + ), + }), + ); } println!( "{}", @@ -1348,8 +1440,12 @@ fn main() { let (data, game_name) = if is_stdin { // Reading from stdin — only works with --json (TUI needs a terminal) if !json_mode { - eprintln!("Usage: diamond-replay [--json] [--little-league] [--no-steal-home]"); - eprintln!(" cat game.json | diamond-replay --json [--little-league] [--no-steal-home]"); + eprintln!( + "Usage: diamond-replay [--json] [--little-league] [--no-steal-home]" + ); + eprintln!( + " cat game.json | diamond-replay --json [--little-league] [--no-steal-home]" + ); process::exit(1); } let mut buf = String::new(); @@ -1373,7 +1469,7 @@ fn main() { }; let result = if no_steal_home { - replay_from_json_no_steal_home(&data) + replay_from_json_with_options(&data, ReplayOptions::no_steal_home()) } else { replay_from_json(&data) }; diff --git a/src/compute.rs b/src/compute.rs index 65e558d..f81804f 100644 --- a/src/compute.rs +++ b/src/compute.rs @@ -93,6 +93,7 @@ pub struct BattingRaw { pub home_runs: i32, pub bb: i32, pub hbp: i32, + pub ci: i32, pub k: i32, pub sac_fly: i32, pub sac_bunt: i32, @@ -139,7 +140,7 @@ pub struct BattingDerived { #[must_use] pub fn compute_batting(raw: &BattingRaw, weights: &WobaWeights) -> BattingDerived { let hits = raw.singles + raw.doubles + raw.triples + raw.home_runs; - let ab = raw.pa - raw.bb - raw.hbp - raw.sac_fly - raw.sac_bunt; + let ab = raw.pa - raw.bb - raw.hbp - raw.sac_fly - raw.sac_bunt - raw.ci; let tb = raw.singles + 2 * raw.doubles + 3 * raw.triples + 4 * raw.home_runs; let xbh = raw.doubles + raw.triples + raw.home_runs; @@ -440,6 +441,7 @@ mod tests { home_runs: 1, bb: 2, hbp: 1, + ci: 0, k: 3, sac_fly: 1, sac_bunt: 0, @@ -560,6 +562,7 @@ mod tests { home_runs: 0, bb: 0, hbp: 0, + ci: 0, k: 0, sac_fly: 0, sac_bunt: 0, @@ -614,6 +617,7 @@ mod tests { home_runs: 0, bb: 0, hbp: 0, + ci: 0, k: 4, sac_fly: 0, sac_bunt: 0, diff --git a/src/event.rs b/src/event.rs index dac6979..de01f18 100644 --- a/src/event.rs +++ b/src/event.rs @@ -19,7 +19,7 @@ pub struct RawApiEvent { #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum EventData { - Transaction { code: String, events: Vec }, + Transaction { events: Vec }, Single(SubEvent), } @@ -291,3 +291,17 @@ pub fn attr_i32(attrs: &serde_json::Value, key: &str) -> Option { let v = attrs.get(key)?.as_i64()?; i32::try_from(v).ok() } + +/// Extract the hit location `(x, y)` from the first defender entry that has one. +#[must_use] +pub fn attr_hit_location(attrs: &serde_json::Value) -> Option<(f64, f64)> { + let defenders = attrs.get("defenders")?.as_array()?; + for d in defenders { + if let Some(loc) = d.get("location") { + let x = loc.get("x")?.as_f64()?; + let y = loc.get("y")?.as_f64()?; + return Some((x, y)); + } + } + None +} diff --git a/src/lib.rs b/src/lib.rs index eca31f5..f97ffe5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,58 @@ +//! Replay engine for `GameChanger` scoring event streams. +//! +//! The crate exposes a small, stable API from the crate root. Use +//! [`replay_from_json`] for standard ground-truth replay, or +//! [`replay_from_json_with_options`] with [`ReplayOptions`] for simulations. +//! +//! ```no_run +//! # fn main() -> diamond_replay::Result<()> { +//! let event_json = "[]"; +//! let result = diamond_replay::replay_from_json(event_json)?; +//! println!( +//! "{}-{}", +//! result.linescore_away.iter().sum::(), +//! result.linescore_home.iter().sum::() +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ```no_run +//! # fn main() -> diamond_replay::Result<()> { +//! let event_json = "[]"; +//! let simulated = diamond_replay::replay_from_json_with_options( +//! event_json, +//! diamond_replay::ReplayOptions::no_steal_home(), +//! )?; +//! println!( +//! "steals of home suppressed: {}", +//! simulated.away_little_league.steals_of_home +//! + simulated.home_little_league.steals_of_home +//! ); +//! # Ok(()) +//! # } +//! ``` +//! #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] -pub mod compute; -pub mod error; -pub mod event; -pub mod player; -pub mod replay; -pub mod score; -pub mod sim; +mod compute; +mod error; +mod event; +mod options; +mod player; +mod replay; +mod rules; +mod score; pub mod stat_help; -pub mod state; +mod state; mod undo; -use error::Result; -use event::RawApiEvent; -pub use replay::GameResult; +pub use error::{ReplayError, Result}; +pub use event::RawApiEvent; +pub use options::{ReplayOptions, RuleSet}; +pub use player::{BattingStats, PitchingStats, PlayerGameStats}; +pub use replay::{GameResult, LittleLeagueStats}; /// Replay a complete game from raw scoring event stream data. /// @@ -23,8 +61,22 @@ pub use replay::GameResult; /// Returns an error if the event list is empty, teams are not set, /// or any event data fails to parse as JSON. pub fn replay(raw_events: Vec) -> Result { + replay_with_options(raw_events, ReplayOptions::standard()) +} + +/// Replay a complete game from raw scoring event stream data with options. +/// +/// # Errors +/// +/// Returns an error if the event list is empty, teams are not set, +/// or any event data fails to parse as JSON. +pub fn replay_with_options( + raw_events: Vec, + options: ReplayOptions, +) -> Result { let resolved = undo::resolve_undos(raw_events); - replay::replay_game(&resolved) + let compiled = rules::compile_events(resolved, options.rule_set)?; + replay::replay_game(&compiled) } /// Convenience: parse a JSON array of raw API events and replay. @@ -34,8 +86,18 @@ pub fn replay(raw_events: Vec) -> Result { /// Returns an error if the JSON is invalid, event list is empty, /// teams are not set, or any event data fails to parse. pub fn replay_from_json(json: &str) -> Result { + replay_from_json_with_options(json, ReplayOptions::standard()) +} + +/// Convenience: parse a JSON array of raw API events and replay with options. +/// +/// # Errors +/// +/// Returns an error if the JSON is invalid, event list is empty, +/// teams are not set, or any event data fails to parse. +pub fn replay_from_json_with_options(json: &str, options: ReplayOptions) -> Result { let raw_events: Vec = serde_json::from_str(json)?; - replay(raw_events) + replay_with_options(raw_events, options) } /// Replay with no-steal-home simulation: chaos scoring from 3B @@ -47,8 +109,5 @@ pub fn replay_from_json(json: &str) -> Result { /// Returns an error if the JSON is invalid, event list is empty, /// teams are not set, or any event data fails to parse. pub fn replay_from_json_no_steal_home(json: &str) -> Result { - let raw_events: Vec = serde_json::from_str(json)?; - let resolved = undo::resolve_undos(raw_events); - let compiled = sim::compile_no_steal_home(resolved)?; - replay::replay_game(&compiled) + replay_from_json_with_options(json, ReplayOptions::no_steal_home()) } diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..647868f --- /dev/null +++ b/src/options.rs @@ -0,0 +1,41 @@ +/// Rule policy used to compile a replay scenario before the core engine runs. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum RuleSet { + /// Ground-truth replay rules. + #[default] + Standard, + /// Simulation rules that keep runners from scoring on chaos advances. + NoStealHome, +} + +impl RuleSet { + /// Ground-truth replay rules. + pub const STANDARD: Self = Self::Standard; + + /// Simulation rules that keep runners from scoring on chaos advances. + pub const NO_STEAL_HOME: Self = Self::NoStealHome; +} + +/// Options controlling replay preprocessing and rules. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct ReplayOptions { + pub rule_set: RuleSet, +} + +impl ReplayOptions { + /// Build options for ground-truth replay. + #[must_use] + pub const fn standard() -> Self { + Self { + rule_set: RuleSet::STANDARD, + } + } + + /// Build options for the no-steal-home simulation. + #[must_use] + pub const fn no_steal_home() -> Self { + Self { + rule_set: RuleSet::NO_STEAL_HOME, + } + } +} diff --git a/src/player.rs b/src/player.rs index 6e6fa48..e9b7178 100644 --- a/src/player.rs +++ b/src/player.rs @@ -3,6 +3,31 @@ use std::collections::HashMap; use crate::event::{BipPlayType, BrPlayType, PitchResult, PlayResult}; use crate::state::PAContext; +// --------------------------------------------------------------------------- +// Spray chart entry — one per ball in play with location data +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, serde::Serialize)] +pub struct DefenderInfo { + pub position: String, + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub error: bool, +} + +#[derive(Debug, Clone, serde::Serialize)] +pub struct SprayEntry { + #[serde(skip_serializing_if = "Option::is_none")] + pub x: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub y: Option, + pub result: String, + pub bip_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub hr_location: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub defenders: Vec, +} + // --------------------------------------------------------------------------- // Per-player stat structs // --------------------------------------------------------------------------- @@ -16,6 +41,7 @@ pub struct BattingStats { pub k_swinging: i32, pub bb: i32, pub hbp: i32, + pub ci: i32, pub singles: i32, pub doubles: i32, pub triples: i32, @@ -38,6 +64,10 @@ pub struct BattingStats { pub sb: i32, pub cs: i32, + // Spray chart data (one entry per BIP with location) + #[serde(skip_serializing_if = "Vec::is_empty")] + pub spray_chart: Vec, + // Derived integer fields (computed after replay) pub ab: i32, pub hits: i32, @@ -189,6 +219,8 @@ pub struct PlayerTracker { player_team: HashMap, /// Accumulated stats per player stats: HashMap, + /// Player run credits in scoring order, used for deterministic score corrections. + run_log: Vec, } impl PlayerTracker { @@ -201,6 +233,7 @@ impl PlayerTracker { current_pitcher: HashMap::new(), player_team: HashMap::new(), stats: HashMap::new(), + run_log: Vec::new(), } } @@ -236,6 +269,96 @@ impl PlayerTracker { } } + /// Remove a lineup slot and shift higher indices down by one. + /// GC fires this after `clear_lineup_index` to compact the lineup + /// when a player is removed mid-game. + pub fn handle_squash_lineup(&mut self, team_id: &str, removed_index: usize) { + let size = self.lineup_size.get(team_id).copied().unwrap_or(0); + if removed_index >= size { + return; + } + self.lineup.remove(&(team_id.to_string(), removed_index)); + for i in (removed_index + 1)..size { + if let Some(pid) = self.lineup.remove(&(team_id.to_string(), i)) { + self.lineup.insert((team_id.to_string(), i - 1), pid); + } + } + let new_size = size - 1; + self.lineup_size.insert(team_id.to_string(), new_size); + if let Some(idx) = self.current_index.get_mut(team_id) { + if new_size == 0 { + *idx = 0; + } else if *idx >= size { + *idx %= new_size; + } else if *idx > removed_index { + *idx -= 1; + } else if *idx >= new_size { + *idx = 0; + } + } + } + + /// Move a player from one batting order position to another, shifting + /// intermediate positions. GC fires this when the scorer drags a player + /// in the lineup — it's an insert, not a swap. + pub fn handle_reorder_lineup(&mut self, team_id: &str, from_index: usize, to_index: usize) { + if from_index == to_index { + return; + } + let tid = team_id.to_string(); + let moving = self.lineup.remove(&(tid.clone(), from_index)); + if from_index < to_index { + // Moving down: shift indices from+1..=to up by one + for i in from_index..to_index { + if let Some(pid) = self.lineup.remove(&(tid.clone(), i + 1)) { + self.lineup.insert((tid.clone(), i), pid); + } + } + } else { + // Moving up: shift indices to..from-1 down by one + for i in (to_index..from_index).rev() { + if let Some(pid) = self.lineup.remove(&(tid.clone(), i)) { + self.lineup.insert((tid.clone(), i + 1), pid); + } + } + } + if let Some(pid) = moving { + self.lineup.insert((tid, to_index), pid); + } + } + + /// Substitute one player for another. GC fires `sub_players` when a + /// coach makes a substitution. The incoming player takes the outgoing + /// player's lineup slot, field position, and (optionally) base. + pub fn handle_sub_players( + &mut self, + team_id: &str, + outgoing_id: &str, + incoming_id: &str, + _apply_to_baserunners: bool, + ) { + // Replace in lineup map + for (key, pid) in &mut self.lineup { + if key.0 == team_id && pid == outgoing_id { + *pid = incoming_id.to_string(); + } + } + // Transfer team membership + self.player_team + .insert(incoming_id.to_string(), team_id.to_string()); + // Transfer pitcher role if applicable + if self.current_pitcher.get(team_id).map(String::as_str) == Some(outgoing_id) { + self.current_pitcher + .insert(team_id.to_string(), incoming_id.to_string()); + let stats = self.ensure_stats(incoming_id); + if stats.pitching.is_none() { + stats.pitching = Some(PitchingStats::default()); + } + } + // The apply_to_baserunners flag is returned for the caller to handle + // (base state lives on Replay, not PlayerTracker). + } + pub fn handle_goto(&mut self, team_id: &str, index: usize) { self.current_index.insert(team_id.to_string(), index); } @@ -340,8 +463,23 @@ impl PlayerTracker { self.advance_batter(team_id); } + /// Record catcher interference for the current batter. + pub fn record_ci(&mut self, team_id: &str) { + self.with_batter(team_id, |s| { + s.pa += 1; + s.ci += 1; + }); + self.advance_batter(team_id); + } + /// Record a ball-in-play result for the current batter. - pub fn record_bip(&mut self, team_id: &str, play_result: PlayResult, bip_type: BipPlayType) { + pub fn record_bip( + &mut self, + team_id: &str, + play_result: PlayResult, + bip_type: BipPlayType, + spray: Option, + ) { self.with_batter(team_id, |s| { s.pa += 1; match play_result { @@ -370,6 +508,9 @@ impl PlayerTracker { BipPlayType::PopFly => s.pop_ups += 1, BipPlayType::Other => {} } + if let Some(entry) = spray { + s.spray_chart.push(entry); + } }); self.advance_batter(team_id); } @@ -378,11 +519,15 @@ impl PlayerTracker { pub fn record_run(&mut self, runner_id: &str) { self.ensure_stats(runner_id).batting.runs += 1; + self.run_log.push(runner_id.to_string()); } /// Undo a previously recorded run for a runner. pub fn undo_run(&mut self, runner_id: &str) { self.ensure_stats(runner_id).batting.runs -= 1; + if let Some(pos) = self.run_log.iter().rposition(|id| id == runner_id) { + self.run_log.remove(pos); + } } pub fn record_sb(&mut self, runner_id: &str) { @@ -567,32 +712,29 @@ impl PlayerTracker { } /// Remove runs from players on a team when a score override reduces the total. - /// Removes from the last players first (reverse insertion order approximation). + /// Removes from the latest recorded run credits first. /// /// # Panics /// /// Panics if a player ID found in iteration is missing from the stats map - /// (should never happen since the IDs are drawn from the same map). + /// (should never happen since the IDs are drawn from the run log). pub fn adjust_team_runs(&mut self, team_id: &str, delta: i32) { if delta >= 0 { return; } let mut remaining = -delta; - let mut ids: Vec = self - .stats - .iter() - .filter(|(_, s)| s.team_id == team_id && s.batting.runs > 0) - .map(|(id, _)| id.clone()) - .collect(); - ids.reverse(); - for id in ids { - if remaining == 0 { + while remaining > 0 { + let Some(pos) = self.run_log.iter().rposition(|id| { + self.stats + .get(id) + .is_some_and(|s| s.team_id == team_id && s.batting.runs > 0) + }) else { break; - } + }; + let id = self.run_log.remove(pos); let runs = &mut self.stats.get_mut(&id).unwrap().batting.runs; - let take = (*runs).min(remaining); - *runs -= take; - remaining -= take; + *runs -= 1; + remaining -= 1; } } @@ -619,3 +761,34 @@ impl Default for PlayerTracker { Self::new() } } + +#[cfg(test)] +mod tests { + use super::PlayerTracker; + + #[test] + fn squash_lineup_keeps_shifted_last_batter_due() { + let mut players = PlayerTracker::new(); + for index in 0..5 { + players.handle_fill_lineup("away", &format!("batter-{index}"), index); + } + players.handle_goto("away", 4); + + players.handle_squash_lineup("away", 2); + + assert_eq!(players.current_batter("away"), Some("batter-4")); + } + + #[test] + fn squash_lineup_wraps_when_due_batter_is_removed_last_slot() { + let mut players = PlayerTracker::new(); + for index in 0..3 { + players.handle_fill_lineup("away", &format!("batter-{index}"), index); + } + players.handle_goto("away", 2); + + players.handle_squash_lineup("away", 2); + + assert_eq!(players.current_batter("away"), Some("batter-0")); + } +} diff --git a/src/replay.rs b/src/replay.rs index aa005bc..f5e70cc 100644 --- a/src/replay.rs +++ b/src/replay.rs @@ -3,10 +3,12 @@ use std::collections::HashMap; use crate::compute; use crate::error::{ReplayError, Result}; use crate::event::{ - attr_bool, attr_i32, attr_str, attr_usize, BipCause, BipPlayType, BrPlayType, EventData, - PitchResult, PlayResult, RawApiEvent, + attr_bool, attr_hit_location, attr_i32, attr_str, attr_usize, BipCause, BipPlayType, + BrPlayType, EventData, PitchResult, PlayResult, RawApiEvent, +}; +use crate::player::{ + BattingStats, DefenderInfo, PitchingStats, PlayerGameStats, PlayerTracker, SprayEntry, }; -use crate::player::{BattingStats, PitchingStats, PlayerGameStats, PlayerTracker}; use crate::score; use crate::state::{AutoAdvanceRecord, BaseOccupant, GameState}; @@ -84,8 +86,9 @@ struct Replay { pitch_pitches_since_last_bip_away: i32, /// Pitching-side: pitches thrown since last BIP (home team pitching) pitch_pitches_since_last_bip_home: i32, - /// Pending BIP run snapshot: (parity, runs_before, is_away_batting, is_real_bip) - /// is_real_bip=false for dropped third strikes (runs go to passive instead). + /// Pending BIP run snapshot: + /// `(parity, runs_before, is_away_batting, is_real_bip)`. + /// `is_real_bip=false` for dropped third strikes (runs go to passive instead). pending_bip_snapshot: Option<(usize, i32, bool, bool)>, } @@ -152,32 +155,45 @@ impl Replay { } } - /// Push current count to pitches_between_bip and reset for both batting and pitching sides. + /// Push current count to `pitches_between_bip` and reset for both batting and pitching sides. fn flush_pitches_between_bip(&mut self) { if self.state.offense == self.state.away_id { - self.away_ll.pitches_between_bip.push(self.pitches_since_last_bip_away); + self.away_ll + .pitches_between_bip + .push(self.pitches_since_last_bip_away); self.pitches_since_last_bip_away = 0; - self.home_ll.pitches_between_bip_pitching.push(self.pitch_pitches_since_last_bip_home); + self.home_ll + .pitches_between_bip_pitching + .push(self.pitch_pitches_since_last_bip_home); self.pitch_pitches_since_last_bip_home = 0; } else { - self.home_ll.pitches_between_bip.push(self.pitches_since_last_bip_home); + self.home_ll + .pitches_between_bip + .push(self.pitches_since_last_bip_home); self.pitches_since_last_bip_home = 0; - self.away_ll.pitches_between_bip_pitching.push(self.pitch_pitches_since_last_bip_away); + self.away_ll + .pitches_between_bip_pitching + .push(self.pitch_pitches_since_last_bip_away); self.pitch_pitches_since_last_bip_away = 0; } } /// Resolve the pending BIP run snapshot after all corrections in the - /// transaction have been applied. The delta between runs_before and - /// runs_after (post-correction) is the true number of runs scored. + /// transaction have been applied. The delta between `runs_before` and + /// `runs_after` (post-correction) is the true number of runs scored. /// Uses the stored team identity so half-inning switches don't misattribute. fn resolve_bip_snapshot(&mut self) { - if let Some((parity, runs_before, is_away, is_real_bip)) = self.pending_bip_snapshot.take() { + if let Some((parity, runs_before, is_away, is_real_bip)) = self.pending_bip_snapshot.take() + { let hi = self.hi(); let runs_after = team_run_total(&self.runs_by_half, hi, parity); let delta = runs_after - runs_before; if delta > 0 { - let ll = if is_away { &mut self.away_ll } else { &mut self.home_ll }; + let ll = if is_away { + &mut self.away_ll + } else { + &mut self.home_ll + }; if is_real_bip { ll.runs_on_bip += delta; } else { @@ -533,7 +549,13 @@ fn handle_pitch(r: &mut Replay, attrs: &serde_json::Value) -> bool { PitchResult::Ball => { r.state.ball_count += 1; if r.state.ball_count >= 4 { - complete_walk_or_hbp(r, &offense, &defense, batter_id.as_deref(), ReachCause::Walk); + complete_walk_or_hbp( + r, + &offense, + &defense, + batter_id.as_deref(), + ReachCause::Walk, + ); r.state.reset_count(); r.players.record_bb(&offense); r.players.record_pitch_bb(&defense); @@ -555,7 +577,13 @@ fn handle_pitch(r: &mut Replay, attrs: &serde_json::Value) -> bool { false } PitchResult::HitByPitch => { - complete_walk_or_hbp(r, &offense, &defense, batter_id.as_deref(), ReachCause::HitByPitch); + complete_walk_or_hbp( + r, + &offense, + &defense, + batter_id.as_deref(), + ReachCause::HitByPitch, + ); r.state.reset_count(); r.players.record_hbp(&offense); r.players.record_pitch_hbp(&defense); @@ -606,45 +634,51 @@ fn handle_strike(r: &mut Replay, result: PitchResult, attrs: &serde_json::Value) // Ball-in-play handling (eager auto-advance) // --------------------------------------------------------------------------- +/// Context for recording batting/pitching stats for a BIP. +struct BipStatContext<'a> { + offense: &'a str, + defense: &'a str, + batter_id: Option<&'a str>, + pa_is_qab: bool, + spray: Option, +} + /// Record batting/pitching stats for a BIP and classify as QAB if appropriate. fn record_bip_stats( r: &mut Replay, pr: PlayResult, bip_type: BipPlayType, - offense: &str, - defense: &str, - batter_id: Option<&str>, - pa_is_qab: bool, + ctx: BipStatContext<'_>, ) { if pr.is_dropped_third_strike() { let looking = r.state.last_strike_type.as_deref() == Some("strike_looking"); - r.players.record_k(offense, looking); - r.players.record_pitch_k(defense); + r.players.record_k(ctx.offense, looking); + r.players.record_pitch_k(ctx.defense); if r.state.pa_context.pitches_after_two_strikes >= 3 || r.state.pa_context.pitches_in_pa >= 6 { - r.players.record_qab(offense); + r.players.record_qab(ctx.offense); } } else if !pr.is_batter_out() { - r.players.record_bip(offense, pr, bip_type); + r.players.record_bip(ctx.offense, pr, bip_type, ctx.spray); if pr.is_hit() { - r.players.record_pitch_hit(defense, pr, bip_type); + r.players.record_pitch_hit(ctx.defense, pr, bip_type); } else { - r.players.record_pitch_bip(defense, bip_type); + r.players.record_pitch_bip(ctx.defense, bip_type); } - if pa_is_qab { - r.players.record_qab(offense); + if ctx.pa_is_qab { + r.players.record_qab(ctx.offense); } if pr == PlayResult::Error { - if let Some(bid) = batter_id { + if let Some(bid) = ctx.batter_id { r.state.error_runners.insert(bid.to_string()); } } } else { - r.players.record_bip(offense, pr, bip_type); - r.players.record_pitch_bip(defense, bip_type); - if pa_is_qab { - r.players.record_qab(offense); + r.players.record_bip(ctx.offense, pr, bip_type, ctx.spray); + r.players.record_pitch_bip(ctx.defense, bip_type); + if ctx.pa_is_qab { + r.players.record_qab(ctx.offense); } } } @@ -691,15 +725,49 @@ fn handle_ball_in_play(r: &mut Replay, attrs: &serde_json::Value) -> bool { r.state.auto_advance = None; let pr = PlayResult::parse(attr_str(attrs, "playResult").unwrap_or("")); - let bip_type = attr_str(attrs, "playType").map_or(BipPlayType::Other, BipPlayType::parse); + let play_type_str = attr_str(attrs, "playType").unwrap_or(""); + let bip_type = if play_type_str.is_empty() { + BipPlayType::Other + } else { + BipPlayType::parse(play_type_str) + }; let offense = r.state.offense.clone(); let defense = r.defense_team().to_string(); let batter_id = r.players.current_batter(&offense).map(str::to_string); r.track_hi(); + let location = attr_hit_location(attrs); + let hr_location = attr_str(attrs, "hrLocation").map(str::to_string); + let mut defenders: Vec = Vec::new(); + if let Some(arr) = attrs.get("defenders").and_then(|v| v.as_array()) { + for d in arr { + if let Some(pos) = d.get("position").and_then(|v| v.as_str()) { + defenders.push(DefenderInfo { + position: pos.to_string(), + error: d + .get("error") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false), + }); + } + } + } + let spray = if location.is_some() || hr_location.is_some() { + Some(SprayEntry { + x: location.map(|(x, _)| x), + y: location.map(|(_, y)| y), + result: attr_str(attrs, "playResult").unwrap_or("").to_string(), + bip_type: play_type_str.to_string(), + hr_location, + defenders, + }) + } else { + None + }; + let is_dropped_third = pr.is_dropped_third_strike(); let is_away = offense == r.state.away_id; - let parity = if is_away { 0 } else { 1 }; + let parity = usize::from(!is_away); let runs_before = team_run_total(&r.runs_by_half, r.hi(), parity); // Always snapshot so we can categorize any runs scored during this event. @@ -723,10 +791,13 @@ fn handle_ball_in_play(r: &mut Replay, attrs: &serde_json::Value) -> bool { r, pr, bip_type, - &offense, - &defense, - batter_id.as_deref(), - pa_is_qab, + BipStatContext { + offense: &offense, + defense: &defense, + batter_id: batter_id.as_deref(), + pa_is_qab, + spray, + }, ); if pa_is_competitive { @@ -810,6 +881,7 @@ fn apply_bip_advance( // --------------------------------------------------------------------------- /// Returns true if this play caused a third out. +#[allow(clippy::too_many_lines)] fn handle_base_running(r: &mut Replay, attrs: &serde_json::Value) -> bool { let pt = BrPlayType::parse(attr_str(attrs, "playType").unwrap_or("")); let base = attr_usize(attrs, "base"); @@ -819,16 +891,16 @@ fn handle_base_running(r: &mut Replay, attrs: &serde_json::Value) -> bool { let defense = r.defense_team().to_string(); r.track_hi(); - // Record player baserunning stats + // Record player baserunning stats — defer run credit for base=4 + // to the base-state block below so it stays in sync with score_run. if let (Some(ref rid), Some(b)) = (&runner_id, base) { - r.players.record_baserunning(rid, pt, b); if b == 4 && !pt.is_out() { - r.players.record_pitch_run(&defense); - // Earned run tracking - if !r.state.error_runners.contains(rid) { - r.players.record_pitch_earned_run(&defense); + // SB/CS stats only — run credit handled below with score_run + if pt == BrPlayType::StoleBase { + r.players.record_sb(rid); } - r.state.error_runners.remove(rid); + } else { + r.players.record_baserunning(rid, pt, b); } } @@ -841,50 +913,41 @@ fn handle_base_running(r: &mut Replay, attrs: &serde_json::Value) -> bool { match pt { BrPlayType::WildPitch => { r.ll_for_offense().wp += 1; - if base == Some(4) && !pt.is_out() { - if runner_id.as_ref().is_some_and(|rid| r.state.bases.find_by_id(rid).is_some()) { - r.ll_for_offense().runs_passive += 1; - } + if base == Some(4) && !pt.is_out() && runner_is_on_bases(r, runner_id.as_deref()) { + r.ll_for_offense().runs_passive += 1; } } BrPlayType::PassedBall => { r.ll_for_offense().pb += 1; - if base == Some(4) && !pt.is_out() { - if runner_id.as_ref().is_some_and(|rid| r.state.bases.find_by_id(rid).is_some()) { - r.ll_for_offense().runs_passive += 1; - } + if base == Some(4) && !pt.is_out() && runner_is_on_bases(r, runner_id.as_deref()) { + r.ll_for_offense().runs_passive += 1; } } BrPlayType::CaughtStealing => { r.ll_for_offense().cs += 1; } - BrPlayType::StoleBase => { - if base == Some(4) { - // Only count if runner is actually on bases (not already auto-scored) - if runner_id.as_ref().is_some_and(|rid| r.state.bases.find_by_id(rid).is_some()) { - r.ll_for_offense().steals_of_home += 1; - r.ll_for_offense().runs_passive += 1; - } - } + BrPlayType::StoleBase if base == Some(4) && runner_is_on_bases(r, runner_id.as_deref()) => { + // Only count if runner is actually on bases (not already auto-scored) + r.ll_for_offense().steals_of_home += 1; + r.ll_for_offense().runs_passive += 1; } - BrPlayType::DefensiveIndifference | BrPlayType::OnSamePitch | BrPlayType::OtherAdvance => { + BrPlayType::DefensiveIndifference | BrPlayType::OnSamePitch | BrPlayType::OtherAdvance + if base == Some(4) && !pt.is_out() && runner_is_on_bases(r, runner_id.as_deref()) => + { // Passive run only if runner is still on bases (not already auto-scored) - if base == Some(4) && !pt.is_out() { - if runner_id.as_ref().is_some_and(|rid| r.state.bases.find_by_id(rid).is_some()) { - r.ll_for_offense().runs_passive += 1; - } - } + r.ll_for_offense().runs_passive += 1; } - BrPlayType::AdvancedOnLastPlay | BrPlayType::AdvancedOnError | BrPlayType::OnSameError => { + BrPlayType::AdvancedOnLastPlay | BrPlayType::AdvancedOnError | BrPlayType::OnSameError + if base == Some(4) + && !pt.is_out() + && r.pending_bip_snapshot.is_none() + && runner_is_on_bases(r, runner_id.as_deref()) => + { // BIP run — only count if runner is still on bases (not already // auto-scored) AND there's no pending BIP snapshot (which would // capture this run via the delta). If there IS a pending snapshot, // the delta will handle it when resolve_bip_snapshot runs. - if base == Some(4) && !pt.is_out() && r.pending_bip_snapshot.is_none() { - if runner_id.as_ref().is_some_and(|rid| r.state.bases.find_by_id(rid).is_some()) { - r.ll_for_offense().runs_on_bip += 1; - } - } + r.ll_for_offense().runs_on_bip += 1; } _ => {} } @@ -923,6 +986,16 @@ fn handle_base_running(r: &mut Replay, attrs: &serde_json::Value) -> bool { } else { if was_auto_scored(&r.state, rid) { undo_auto_scored_run(r, rid); + // The auto-advance may have moved a runner INTO base b + // (e.g., 2B→3B after 3B scored). Push them back so the + // remained runner can reclaim the base. + if r.state.bases.is_occupied(b) && b >= 2 { + let displaced = r.state.bases.get(b).cloned(); + r.state.bases.set(b, None); + if let Some(occ) = displaced { + r.state.bases.set(b - 1, Some(occ)); + } + } } r.state.bases.set(b, occupant); } @@ -930,8 +1003,14 @@ fn handle_base_running(r: &mut Replay, attrs: &serde_json::Value) -> bool { if on_bases { r.state.bases.clear_runner(rid, b); score::score_run(hi, &mut r.runs_by_half); + r.players.record_run(rid); + r.players.record_pitch_run(&defense); + if !r.state.error_runners.contains(rid) { + r.players.record_pitch_earned_run(&defense); + } + r.state.error_runners.remove(rid); } - // Not on bases (already auto-scored) -> confirmation, skip + // Not on bases: already auto-scored — skip (confirmation only) } else if (1..=3).contains(&b) { if on_bases { r.state.bases.clear_by_id(rid); @@ -946,6 +1025,10 @@ fn handle_base_running(r: &mut Replay, attrs: &serde_json::Value) -> bool { false } +fn runner_is_on_bases(r: &Replay, runner_id: Option<&str>) -> bool { + runner_id.is_some_and(|rid| r.state.bases.find_by_id(rid).is_some()) +} + /// Runner didn't move -- update tracking from anonymous to their real ID. fn update_remained(state: &mut GameState, base: usize, rid: &str) { for ob in 1..=3 { @@ -983,10 +1066,11 @@ fn handle_end_at_bat(r: &mut Replay, attrs: &serde_json::Value) { if reason == "hit_by_pitch" { r.players.record_hbp(&offense); r.players.record_pitch_hbp(&defense); + } else { + r.players.record_ci(&offense); } - // Catcher interference: batter reaches base but it's not an HBP. - // PA context and base advancement are handled by complete_walk_or_hbp. - // No HBP stat credit. + // Catcher interference: batter reaches base and gets a PA, but no AB, + // HBP, or pitcher HBP credit. } } @@ -1096,6 +1180,7 @@ fn fill_batting_derived(b: &mut BattingStats) { home_runs: b.home_runs, bb: b.bb, hbp: b.hbp, + ci: b.ci, k: b.k, sac_fly: b.sac_fly, sac_bunt: b.sac_bunt, @@ -1195,6 +1280,7 @@ fn merge_batting(dst: &mut BattingStats, src: &BattingStats) { dst.k_swinging += src.k_swinging; dst.bb += src.bb; dst.hbp += src.hbp; + dst.ci += src.ci; dst.singles += src.singles; dst.doubles += src.doubles; dst.triples += src.triples; @@ -1216,6 +1302,7 @@ fn merge_batting(dst: &mut BattingStats, src: &BattingStats) { dst.competitive_ab += src.competitive_ab; dst.sb += src.sb; dst.cs += src.cs; + dst.spray_chart.extend_from_slice(&src.spray_chart); } /// Add raw counts from `src` into `dst` (team-level aggregation). @@ -1352,6 +1439,7 @@ fn build_dead_time(gaps: &[f64]) -> Vec { /// /// Returns an error if the event list is empty, teams are not set, /// or any event data fails to parse as JSON. +#[allow(clippy::too_many_lines)] pub fn replay_game(resolved: &[RawApiEvent]) -> Result { if resolved.is_empty() { return Err(ReplayError::NoEvents); @@ -1416,7 +1504,9 @@ pub fn replay_game(resolved: &[RawApiEvent]) -> Result { } "pitch" => { if let Some(ts) = evt.created_at { - if r.first_pitch_ts.is_none() { r.first_pitch_ts = Some(ts); } + if r.first_pitch_ts.is_none() { + r.first_pitch_ts = Some(ts); + } r.last_pitch_ts = Some(ts); } handle_pitch(&mut r, &evt.attributes) @@ -1432,6 +1522,42 @@ pub fn replay_game(resolved: &[RawApiEvent]) -> Result { handle_override(&mut r, &evt.attributes); false } + "squash_lineup_index" => { + if let (Some(tid), Some(idx)) = ( + attr_str(&evt.attributes, "teamId"), + attr_usize(&evt.attributes, "index"), + ) { + r.players.handle_squash_lineup(tid, idx); + } + false + } + "reorder_lineup" => { + if let (Some(tid), Some(from), Some(to)) = ( + attr_str(&evt.attributes, "teamId"), + attr_usize(&evt.attributes, "fromIndex"), + attr_usize(&evt.attributes, "toIndex"), + ) { + r.players.handle_reorder_lineup(tid, from, to); + } + false + } + "sub_players" => { + if let (Some(tid), Some(out_id), Some(in_id)) = ( + attr_str(&evt.attributes, "teamId"), + attr_str(&evt.attributes, "outgoingPlayerId"), + attr_str(&evt.attributes, "incomingPlayerId"), + ) { + let apply_br = attr_bool(&evt.attributes, "applyToBaserunners", false); + r.players.handle_sub_players(tid, out_id, in_id, apply_br); + if apply_br { + r.state.bases.substitute_runner(out_id, in_id); + if r.state.error_runners.remove(out_id) { + r.state.error_runners.insert(in_id.to_string()); + } + } + } + false + } _ => false, }; } diff --git a/src/rules/mod.rs b/src/rules/mod.rs new file mode 100644 index 0000000..8154a50 --- /dev/null +++ b/src/rules/mod.rs @@ -0,0 +1,72 @@ +//! Event-stream rule compilation. +//! +//! Rule compilers transform an undo-resolved event stream before the core +//! replay engine sees it. Standard replay is an identity compiler. + +mod no_steal_home; +mod shadow_state; +mod standard; +mod stream; + +use crate::error::Result; +use crate::event::RawApiEvent; +use crate::options::RuleSet; + +pub(crate) fn compile_events( + events: Vec, + rule_set: RuleSet, +) -> Result> { + match rule_set { + RuleSet::Standard => Ok(standard::compile(events)), + RuleSet::NoStealHome => no_steal_home::compile(events), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn raw(seq: i64, code: &str) -> RawApiEvent { + RawApiEvent { + id: format!("event-{seq}"), + stream_id: "test".to_string(), + sequence_number: seq, + event_data: serde_json::json!({"code": code, "attributes": {}}).to_string(), + } + } + + #[test] + fn standard_compiler_is_identity() { + let input = vec![raw(1, "set_teams"), raw(2, "end_half")]; + let output = compile_events(input.clone(), RuleSet::Standard).unwrap(); + + assert_eq!(output.len(), input.len()); + for (actual, expected) in output.iter().zip(input) { + assert_eq!(actual.id, expected.id); + assert_eq!(actual.stream_id, expected.stream_id); + assert_eq!(actual.sequence_number, expected.sequence_number); + assert_eq!(actual.event_data, expected.event_data); + } + } + + #[test] + fn no_steal_home_compiler_drops_home_chaos_advance() { + let input = vec![RawApiEvent { + id: "event-1".to_string(), + stream_id: "test".to_string(), + sequence_number: 1, + event_data: serde_json::json!({ + "code": "base_running", + "attributes": { + "playType": "stole_base", + "base": 4, + "runnerId": "runner-1" + } + }) + .to_string(), + }]; + + let output = compile_events(input, RuleSet::NoStealHome).unwrap(); + assert!(output.is_empty()); + } +} diff --git a/src/rules/no_steal_home.rs b/src/rules/no_steal_home.rs new file mode 100644 index 0000000..45aa8e8 --- /dev/null +++ b/src/rules/no_steal_home.rs @@ -0,0 +1,390 @@ +//! No-steal-home rule compiler. +//! +//! Transforms an undo-resolved event stream before feeding it to the +//! core replay engine. The core engine stays pure with zero rule hooks. + +use std::collections::HashSet; + +use serde_json::Value; + +use super::shadow_state::ShadowState; +use super::stream::{self, EventStreamRule}; +use crate::error::Result; +use crate::event::{attr_bool, attr_str, attr_usize, BrPlayType, RawApiEvent}; + +/// Lightweight base/out tracker for the scenario compiler. +/// +/// ```text +/// Decision logic for chaos base_running events: +/// +/// base=4 (home)? → ALWAYS block. Runner stays at 3rd. +/// base=2 or 3? → Block if destination is occupied (no room). +/// Allow if destination is empty. +/// base=1? → Allow (steals to 1st don't happen, but be safe). +/// +/// Everything else (hits, walks, normal advances) passes through +/// unchanged. The core engine's auto-advance and walk force-advance +/// score runs naturally when bases load up. +/// +/// Scorer corrections for diverged runners (whose chaos advance was +/// blocked) are dropped so they don't undo the rule's auto-advance. +/// ``` +struct NoStealHomeState { + shadow: ShadowState, + diverged: HashSet, +} + +impl NoStealHomeState { + fn new() -> Self { + Self { + shadow: ShadowState::new(), + diverged: HashSet::new(), + } + } + + fn handle_set_teams(&mut self, attrs: &Value) { + self.shadow.observe_set_teams(attrs); + } + + fn handle_fill_lineup_index(&mut self, attrs: &Value) { + self.shadow.observe_fill_lineup_index(attrs); + } + + fn handle_fill_lineup(&mut self, attrs: &Value) { + self.shadow.observe_fill_lineup(attrs); + } + + fn handle_goto_lineup_index(&mut self, attrs: &Value) { + self.shadow.observe_goto_lineup_index(attrs); + } + + fn handle_squash_lineup_index(&mut self, attrs: &Value) { + self.shadow.observe_squash_lineup_index(attrs); + } + + fn handle_reorder_lineup(&mut self, attrs: &Value) { + self.shadow.observe_reorder_lineup(attrs); + } + + fn handle_sub_players(&mut self, attrs: &Value) { + if let (Some(outgoing_id), Some(incoming_id)) = ( + attr_str(attrs, "outgoingPlayerId"), + attr_str(attrs, "incomingPlayerId"), + ) { + if attr_bool(attrs, "applyToBaserunners", false) && self.diverged.remove(outgoing_id) { + self.diverged.insert(incoming_id.to_string()); + } + } + self.shadow.observe_sub_players(attrs); + } + + fn handle_pitch(&mut self, attrs: &Value) { + self.shadow.observe_pitch(attrs); + } + + fn handle_ball_in_play(&mut self, attrs: &Value) { + self.shadow.observe_ball_in_play(attrs); + } + + fn handle_base_running(&mut self, attrs: &Value) -> bool { + let pt = attr_str(attrs, "playType").unwrap_or(""); + let br_type = BrPlayType::parse(pt); + let base = attr_usize(attrs, "base"); + let runner_id = attr_str(attrs, "runnerId").map(str::to_string); + + if br_type.is_chaos() { + return self.handle_chaos_base_running(base, runner_id.as_deref()); + } + + if runner_id + .as_ref() + .is_some_and(|rid| self.diverged.contains(rid) && !br_type.is_out()) + { + return false; + } + + if br_type.is_out() { + if let Some(rid) = &runner_id { + self.diverged.remove(rid); + } + self.shadow.record_runner_out(runner_id.as_deref()); + return true; + } + + if let (Some(rid), Some(b)) = (&runner_id, base) { + self.shadow.advance_runner(rid, b); + } + true + } + + fn handle_chaos_base_running(&mut self, base: Option, runner_id: Option<&str>) -> bool { + let blocked = match base { + Some(4) => true, + Some(b @ 1..=3) => self.shadow.is_occupied(b), + _ => false, + }; + + if blocked { + if let Some(rid) = runner_id { + self.diverged.insert(rid.to_string()); + } + return false; + } + + if let (Some(rid), Some(b)) = (runner_id, base) { + self.shadow.advance_runner(rid, b); + } + true + } + + fn handle_end_half(&mut self) { + self.shadow.switch_half(); + self.diverged.clear(); + } +} + +/// Compile the no-steal-home scenario. Chaos base-running events are +/// blocked when they would score (base=4) or when the destination base +/// is already occupied. Runners only score via hits or walk force-advance. +/// +/// # Errors +/// +/// Returns an error if any `event_data` fails to parse as JSON. +pub(super) fn compile(resolved: Vec) -> Result> { + let mut rule = NoStealHomeState::new(); + stream::compile(resolved, &mut rule) +} + +impl EventStreamRule for NoStealHomeState { + fn apply_sub_event(&mut self, sub: Value) -> Result> { + let code = attr_str(&sub, "code").unwrap_or(""); + let null = Value::Null; + let attrs = sub.get("attributes").unwrap_or(&null); + + let keep = match code { + "set_teams" => { + self.handle_set_teams(attrs); + true + } + "fill_lineup_index" => { + self.handle_fill_lineup_index(attrs); + true + } + "fill_lineup" => { + self.handle_fill_lineup(attrs); + true + } + "goto_lineup_index" => { + self.handle_goto_lineup_index(attrs); + true + } + "squash_lineup_index" => { + self.handle_squash_lineup_index(attrs); + true + } + "reorder_lineup" => { + self.handle_reorder_lineup(attrs); + true + } + "sub_players" => { + self.handle_sub_players(attrs); + true + } + "pitch" => { + self.handle_pitch(attrs); + true + } + "ball_in_play" => { + self.handle_ball_in_play(attrs); + true + } + "base_running" => self.handle_base_running(attrs), + "end_half" => { + self.handle_end_half(); + true + } + _ => true, + }; + + Ok(keep.then_some(sub)) + } + + fn finish_raw_event(&mut self) -> Result<()> { + if self.shadow.finish_raw_event() { + self.diverged.clear(); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::needless_pass_by_value)] + fn raw_single(seq: i64, sub: serde_json::Value) -> RawApiEvent { + RawApiEvent { + id: format!("test-{seq}"), + stream_id: "test".into(), + sequence_number: seq, + event_data: serde_json::to_string(&sub).unwrap(), + } + } + + #[allow(clippy::needless_pass_by_value)] + fn raw_tx(seq: i64, subs: Vec) -> RawApiEvent { + let tx = serde_json::json!({"code": "transaction", "events": subs}); + RawApiEvent { + id: format!("test-{seq}"), + stream_id: "test".into(), + sequence_number: seq, + event_data: serde_json::to_string(&tx).unwrap(), + } + } + + fn pitch(result: &str) -> serde_json::Value { + serde_json::json!({"code": "pitch", "attributes": {"result": result, "advancesCount": true}}) + } + + fn bip(play_result: &str) -> serde_json::Value { + serde_json::json!({"code": "ball_in_play", "attributes": {"playResult": play_result}}) + } + + fn base_running(play_type: &str, base: u64, runner_id: &str) -> serde_json::Value { + serde_json::json!({"code": "base_running", "attributes": {"playType": play_type, "base": base, "runnerId": runner_id}}) + } + + fn sub_players(outgoing_id: &str, incoming_id: &str) -> serde_json::Value { + serde_json::json!({ + "code": "sub_players", + "attributes": { + "teamId": "away", + "outgoingPlayerId": outgoing_id, + "incomingPlayerId": incoming_id, + "applyToBaserunners": true + } + }) + } + + fn end_half() -> serde_json::Value { + serde_json::json!({"code": "end_half", "attributes": {}}) + } + + #[test] + fn chaos_scoring_always_blocked() { + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 3, "r1")), + raw_single(2, base_running("passed_ball", 4, "r1")), + ]; + let result = compile(events).unwrap(); + assert_eq!(result.len(), 1, "PB to home dropped"); + } + + #[test] + fn chaos_advance_allowed_when_destination_empty() { + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 1, "r1")), + raw_single(2, base_running("passed_ball", 2, "r1")), // 2B empty, allowed + ]; + let result = compile(events).unwrap(); + assert_eq!(result.len(), 2, "PB to empty 2B allowed"); + } + + #[test] + fn chaos_advance_blocked_when_destination_occupied() { + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 2, "r1")), + raw_single(2, base_running("advanced_on_last_play", 3, "r2")), + // r2 on 3rd, PB to home blocked. r1 on 2nd, PB to 3rd blocked (occupied). + raw_single(3, base_running("passed_ball", 4, "r2")), // blocked + raw_single(4, base_running("passed_ball", 3, "r1")), // blocked (3B occupied) + ]; + let result = compile(events).unwrap(); + assert_eq!(result.len(), 2, "both chaos advances blocked"); + } + + #[test] + fn bases_load_and_walk_forces_run() { + // Simulate: 3 runners get on via advances, PB scoring blocked, + // then walks should force runs via the core engine + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 1, "r1")), + raw_single(2, base_running("advanced_on_last_play", 2, "r2")), + raw_single(3, base_running("advanced_on_last_play", 3, "r3")), + raw_single(4, base_running("stole_base", 4, "r3")), // blocked + // r3 still on 3rd. Bases loaded (r1=1B, r2=2B, r3=3B) + // Walk should force r3 home via core engine + raw_single(5, pitch("ball")), + raw_single(6, pitch("ball")), + raw_single(7, pitch("ball")), + raw_single(8, pitch("ball")), + ]; + let result = compile(events).unwrap(); + // Event 4 (steal to home) should be dropped + assert_eq!(result.len(), 7); + } + + #[test] + fn non_chaos_events_pass_through() { + let events = vec![ + raw_single(1, pitch("ball")), + raw_single(2, bip("single")), + raw_single(3, base_running("advanced_on_last_play", 3, "r1")), + raw_single(4, base_running("caught_stealing", 2, "r2")), + ]; + let result = compile(events).unwrap(); + assert_eq!(result.len(), 4); + } + + #[test] + fn half_inning_switch_clears_state() { + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 3, "r1")), + raw_single(2, end_half()), + // After switch, 3B is empty so PB advance should be allowed + raw_single(3, base_running("advanced_on_last_play", 2, "r2")), + raw_single(4, base_running("passed_ball", 3, "r2")), // 3B empty, allowed + ]; + let result = compile(events).unwrap(); + assert_eq!(result.len(), 4); + } + + #[test] + fn substituted_baserunner_updates_shadow_occupancy() { + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 3, "old")), + raw_single(2, sub_players("old", "new")), + raw_single(3, base_running("advanced_on_last_play", 2, "new")), + raw_single(4, base_running("passed_ball", 3, "other")), + ]; + + let result = compile(events).unwrap(); + + assert_eq!(result.len(), 4, "PB to newly empty 3B should be allowed"); + } + + #[test] + fn chaos_within_transaction() { + let events = vec![ + raw_single(1, base_running("advanced_on_last_play", 3, "r1")), + raw_tx( + 2, + vec![ + base_running("passed_ball", 4, "r1"), // blocked + base_running("passed_ball", 3, "r2"), // blocked (3B occupied) + ], + ), + ]; + let result = compile(events).unwrap(); + let ed: serde_json::Value = serde_json::from_str(&result[1].event_data).unwrap(); + let tx_events = ed["events"].as_array().unwrap(); + assert_eq!(tx_events.len(), 0, "both chaos events in tx dropped"); + } + + #[test] + fn sacrifice_fly_counted_as_out() { + let events = vec![raw_tx(1, vec![bip("sacrifice_fly")])]; + let result = compile(events).unwrap(); + assert_eq!(result.len(), 1); + } +} diff --git a/src/rules/shadow_state.rs b/src/rules/shadow_state.rs new file mode 100644 index 0000000..0cf22fb --- /dev/null +++ b/src/rules/shadow_state.rs @@ -0,0 +1,521 @@ +//! Lightweight game-state mirror for event-stream rule compilers. + +use std::collections::HashMap; + +use serde_json::Value; + +use crate::event::{attr_bool, attr_str, attr_usize, PlayResult}; + +pub(super) struct ShadowState { + bases: [Option; 3], + outs: i32, + balls: i32, + strikes: i32, + need_switch: bool, + away_id: String, + home_id: String, + offense: String, + lineup: HashMap<(String, usize), String>, + lineup_size: HashMap, + current_index: HashMap, +} + +impl ShadowState { + pub(super) fn new() -> Self { + Self { + bases: [None, None, None], + outs: 0, + balls: 0, + strikes: 0, + need_switch: false, + away_id: String::new(), + home_id: String::new(), + offense: String::new(), + lineup: HashMap::new(), + lineup_size: HashMap::new(), + current_index: HashMap::new(), + } + } + + pub(super) fn is_occupied(&self, base: usize) -> bool { + (1..=3).contains(&base) && self.bases[base - 1].is_some() + } + + pub(super) fn remove_runner(&mut self, runner_id: &str) { + for base in 0..3 { + if self.bases[base].as_deref() == Some(runner_id) { + self.bases[base] = None; + break; + } + } + } + + pub(super) fn advance_runner(&mut self, runner_id: &str, base: usize) { + self.remove_runner(runner_id); + if (1..=3).contains(&base) { + self.set(base, Some(runner_id.to_string())); + } + } + + pub(super) fn record_runner_out(&mut self, runner_id: Option<&str>) { + if let Some(runner_id) = runner_id { + self.remove_runner(runner_id); + } + self.outs += 1; + self.mark_switch_if_inning_over(); + } + + pub(super) fn observe_set_teams(&mut self, attrs: &Value) { + if let (Some(away), Some(home)) = (attr_str(attrs, "awayId"), attr_str(attrs, "homeId")) { + self.away_id = away.to_string(); + self.home_id = home.to_string(); + self.offense = away.to_string(); + } + } + + pub(super) fn observe_fill_lineup_index(&mut self, attrs: &Value) { + if let (Some(team_id), Some(player_id), Some(index)) = ( + attr_str(attrs, "teamId"), + attr_str(attrs, "playerId"), + attr_usize(attrs, "index"), + ) { + self.lineup + .insert((team_id.to_string(), index), player_id.to_string()); + let size = self.lineup_size.entry(team_id.to_string()).or_insert(0); + if index + 1 > *size { + *size = index + 1; + } + } + } + + pub(super) fn observe_fill_lineup(&mut self, attrs: &Value) { + if let (Some(team_id), Some(player_id)) = + (attr_str(attrs, "teamId"), attr_str(attrs, "playerId")) + { + let next_index = self.lineup_size.get(team_id).copied().unwrap_or(0); + self.lineup + .insert((team_id.to_string(), next_index), player_id.to_string()); + *self.lineup_size.entry(team_id.to_string()).or_insert(0) = next_index + 1; + } + } + + pub(super) fn observe_goto_lineup_index(&mut self, attrs: &Value) { + if let (Some(team_id), Some(index)) = + (attr_str(attrs, "teamId"), attr_usize(attrs, "index")) + { + self.current_index.insert(team_id.to_string(), index); + } + } + + pub(super) fn observe_squash_lineup_index(&mut self, attrs: &Value) { + if let (Some(team_id), Some(index)) = + (attr_str(attrs, "teamId"), attr_usize(attrs, "index")) + { + self.squash_lineup_index(team_id, index); + } + } + + pub(super) fn observe_reorder_lineup(&mut self, attrs: &Value) { + if let (Some(team_id), Some(from_index), Some(to_index)) = ( + attr_str(attrs, "teamId"), + attr_usize(attrs, "fromIndex"), + attr_usize(attrs, "toIndex"), + ) { + self.reorder_lineup(team_id, from_index, to_index); + } + } + + pub(super) fn observe_sub_players(&mut self, attrs: &Value) { + if let (Some(team_id), Some(outgoing_id), Some(incoming_id)) = ( + attr_str(attrs, "teamId"), + attr_str(attrs, "outgoingPlayerId"), + attr_str(attrs, "incomingPlayerId"), + ) { + self.substitute_lineup_player(team_id, outgoing_id, incoming_id); + if attr_bool(attrs, "applyToBaserunners", false) { + self.substitute_runner(outgoing_id, incoming_id); + } + } + } + + pub(super) fn observe_pitch(&mut self, attrs: &Value) { + if !attr_bool(attrs, "advancesCount", true) { + return; + } + + let result = attr_str(attrs, "result").unwrap_or(""); + match result { + "ball" => { + self.balls += 1; + if self.balls >= 4 { + self.apply_walk(); + self.reset_count(); + } + } + "strike_swinging" | "strike_looking" => { + self.strikes += 1; + if self.strikes >= 3 { + self.outs += 1; + self.reset_count(); + self.advance_batter(); + self.mark_switch_if_inning_over(); + } + } + "foul" if self.strikes < 2 => { + self.strikes += 1; + } + "hit_by_pitch" => { + self.apply_walk(); + self.reset_count(); + } + "ball_in_play" => { + self.reset_count(); + } + _ => {} + } + } + + pub(super) fn observe_ball_in_play(&mut self, attrs: &Value) { + let play_result = attr_str(attrs, "playResult").unwrap_or(""); + if PlayResult::parse(play_result).is_batter_out() { + let added_outs = if play_result == "double_play" { 2 } else { 1 }; + self.outs += added_outs; + self.advance_batter(); + self.mark_switch_if_inning_over(); + } else { + self.apply_hit(play_result); + } + } + + pub(super) fn switch_half(&mut self) { + self.bases = [None, None, None]; + self.outs = 0; + self.balls = 0; + self.strikes = 0; + self.need_switch = false; + self.offense = if self.offense == self.away_id { + self.home_id.clone() + } else { + self.away_id.clone() + }; + } + + pub(super) fn finish_raw_event(&mut self) -> bool { + if self.need_switch { + self.switch_half(); + return true; + } + false + } + + fn current_batter(&self) -> Option { + let index = self.current_index.get(&self.offense).copied().unwrap_or(0); + self.lineup.get(&(self.offense.clone(), index)).cloned() + } + + fn advance_batter(&mut self) { + let size = self + .lineup_size + .get(&self.offense) + .copied() + .unwrap_or(1) + .max(1); + let index = self.current_index.entry(self.offense.clone()).or_insert(0); + *index = (*index + 1) % size; + } + + fn squash_lineup_index(&mut self, team_id: &str, removed_index: usize) { + let size = self.lineup_size.get(team_id).copied().unwrap_or(0); + if removed_index >= size { + return; + } + + let team = team_id.to_string(); + self.lineup.remove(&(team.clone(), removed_index)); + for index in (removed_index + 1)..size { + if let Some(player_id) = self.lineup.remove(&(team.clone(), index)) { + self.lineup.insert((team.clone(), index - 1), player_id); + } + } + + let new_size = size - 1; + self.lineup_size.insert(team.clone(), new_size); + if let Some(current_index) = self.current_index.get_mut(team_id) { + if new_size == 0 { + *current_index = 0; + } else if *current_index >= size { + *current_index %= new_size; + } else if *current_index > removed_index { + *current_index -= 1; + } else if *current_index >= new_size { + *current_index = 0; + } + } + } + + fn reorder_lineup(&mut self, team_id: &str, from_index: usize, to_index: usize) { + if from_index == to_index { + return; + } + + let team = team_id.to_string(); + let moving = self.lineup.remove(&(team.clone(), from_index)); + if from_index < to_index { + for index in from_index..to_index { + if let Some(player_id) = self.lineup.remove(&(team.clone(), index + 1)) { + self.lineup.insert((team.clone(), index), player_id); + } + } + } else { + for index in (to_index..from_index).rev() { + if let Some(player_id) = self.lineup.remove(&(team.clone(), index)) { + self.lineup.insert((team.clone(), index + 1), player_id); + } + } + } + + if let Some(player_id) = moving { + self.lineup.insert((team, to_index), player_id); + } + } + + fn substitute_lineup_player(&mut self, team_id: &str, outgoing_id: &str, incoming_id: &str) { + for ((lineup_team, _), player_id) in &mut self.lineup { + if lineup_team == team_id && player_id == outgoing_id { + *player_id = incoming_id.to_string(); + } + } + } + + fn substitute_runner(&mut self, outgoing_id: &str, incoming_id: &str) { + for runner in &mut self.bases { + if runner.as_deref() == Some(outgoing_id) { + *runner = Some(incoming_id.to_string()); + } + } + } + + fn set(&mut self, base: usize, player_id: Option) { + if (1..=3).contains(&base) { + self.bases[base - 1] = player_id; + } + } + + fn reset_count(&mut self) { + self.balls = 0; + self.strikes = 0; + } + + fn mark_switch_if_inning_over(&mut self) { + if self.outs >= 3 { + self.need_switch = true; + } + } + + fn apply_walk(&mut self) { + let batter = self.current_batter(); + if self.is_occupied(1) && self.is_occupied(2) && self.is_occupied(3) { + self.set(3, None); + } + if self.is_occupied(1) && self.is_occupied(2) { + let runner_2b = self.bases[1].take(); + self.set(3, runner_2b); + } + if self.is_occupied(1) { + let runner_1b = self.bases[0].take(); + self.set(2, runner_1b); + } + self.set(1, batter); + self.advance_batter(); + } + + fn apply_hit(&mut self, play_result: &str) { + let batter = self.current_batter(); + match play_result { + "single" => { + self.set(3, None); + let runner_2b = self.bases[1].take(); + self.set(3, runner_2b); + let runner_1b = self.bases[0].take(); + self.set(2, runner_1b); + self.set(1, batter); + } + "double" => { + self.set(3, None); + self.set(2, None); + let runner_1b = self.bases[0].take(); + self.set(3, runner_1b); + self.set(2, batter); + } + "triple" | "home_run" => { + self.bases = [None, None, None]; + } + _ => {} + } + self.advance_batter(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn set_teams() -> Value { + serde_json::json!({"awayId": "away", "homeId": "home"}) + } + + fn fill_lineup(team_id: &str, player_id: &str, index: usize) -> Value { + serde_json::json!({ + "teamId": team_id, + "playerId": player_id, + "index": index + }) + } + + fn pitch(result: &str) -> Value { + serde_json::json!({"result": result, "advancesCount": true}) + } + + fn ball_in_play(play_result: &str) -> Value { + serde_json::json!({"playResult": play_result}) + } + + fn reorder_lineup(from_index: usize, to_index: usize) -> Value { + serde_json::json!({ + "teamId": "away", + "fromIndex": from_index, + "toIndex": to_index + }) + } + + fn squash_lineup(index: usize) -> Value { + serde_json::json!({ + "teamId": "away", + "index": index + }) + } + + fn sub_players(outgoing_id: &str, incoming_id: &str, apply_to_baserunners: bool) -> Value { + serde_json::json!({ + "teamId": "away", + "outgoingPlayerId": outgoing_id, + "incomingPlayerId": incoming_id, + "applyToBaserunners": apply_to_baserunners + }) + } + + fn state_with_away_lineup() -> ShadowState { + let mut state = ShadowState::new(); + state.observe_set_teams(&set_teams()); + state.observe_fill_lineup_index(&fill_lineup("away", "batter-1", 0)); + state.observe_fill_lineup_index(&fill_lineup("away", "batter-2", 1)); + state + } + + #[test] + fn walk_forces_runner_and_rotates_lineup() { + let mut state = state_with_away_lineup(); + state.advance_runner("runner-1", 1); + state.advance_runner("runner-2", 2); + state.advance_runner("runner-3", 3); + + for _ in 0..4 { + state.observe_pitch(&pitch("ball")); + } + + assert_eq!(state.bases[0].as_deref(), Some("batter-1")); + assert_eq!(state.bases[1].as_deref(), Some("runner-1")); + assert_eq!(state.bases[2].as_deref(), Some("runner-2")); + assert_eq!(state.current_batter().as_deref(), Some("batter-2")); + } + + #[test] + fn single_advances_runners_and_batter() { + let mut state = state_with_away_lineup(); + state.advance_runner("runner-1", 1); + state.advance_runner("runner-2", 2); + + state.observe_ball_in_play(&ball_in_play("single")); + + assert_eq!(state.bases[0].as_deref(), Some("batter-1")); + assert_eq!(state.bases[1].as_deref(), Some("runner-1")); + assert_eq!(state.bases[2].as_deref(), Some("runner-2")); + assert_eq!(state.current_batter().as_deref(), Some("batter-2")); + } + + #[test] + fn strikeout_marks_switch_after_third_out() { + let mut state = state_with_away_lineup(); + state.outs = 2; + + for _ in 0..3 { + state.observe_pitch(&pitch("strike_swinging")); + } + + assert!(state.need_switch); + assert!(state.finish_raw_event()); + assert_eq!(state.outs, 0); + assert_eq!(state.offense, "home"); + } + + #[test] + fn runner_out_removes_runner_and_can_end_half() { + let mut state = state_with_away_lineup(); + state.outs = 2; + state.advance_runner("runner-1", 2); + + state.record_runner_out(Some("runner-1")); + + assert!(!state.is_occupied(2)); + assert!(state.finish_raw_event()); + assert_eq!(state.offense, "home"); + } + + #[test] + fn reorder_lineup_changes_current_batter() { + let mut state = state_with_away_lineup(); + + state.observe_reorder_lineup(&reorder_lineup(1, 0)); + state.observe_ball_in_play(&ball_in_play("single")); + + assert_eq!(state.bases[0].as_deref(), Some("batter-2")); + assert_eq!(state.current_batter().as_deref(), Some("batter-1")); + } + + #[test] + fn squash_lineup_compacts_order() { + let mut state = state_with_away_lineup(); + state.observe_fill_lineup_index(&fill_lineup("away", "batter-3", 2)); + state.observe_goto_lineup_index(&serde_json::json!({"teamId": "away", "index": 1})); + + state.observe_squash_lineup_index(&squash_lineup(1)); + + assert_eq!(state.lineup_size["away"], 2); + assert_eq!(state.current_batter().as_deref(), Some("batter-3")); + } + + #[test] + fn squash_lineup_keeps_shifted_last_batter_due() { + let mut state = state_with_away_lineup(); + state.observe_fill_lineup_index(&fill_lineup("away", "batter-3", 2)); + state.observe_fill_lineup_index(&fill_lineup("away", "batter-4", 3)); + state.observe_fill_lineup_index(&fill_lineup("away", "batter-5", 4)); + state.observe_goto_lineup_index(&serde_json::json!({"teamId": "away", "index": 4})); + + state.observe_squash_lineup_index(&squash_lineup(2)); + + assert_eq!(state.lineup_size["away"], 4); + assert_eq!(state.current_batter().as_deref(), Some("batter-5")); + } + + #[test] + fn sub_players_replaces_lineup_and_baserunner() { + let mut state = state_with_away_lineup(); + state.advance_runner("batter-1", 2); + + state.observe_sub_players(&sub_players("batter-1", "pinch-runner", true)); + + assert_eq!(state.current_batter().as_deref(), Some("pinch-runner")); + assert_eq!(state.bases[1].as_deref(), Some("pinch-runner")); + } +} diff --git a/src/rules/standard.rs b/src/rules/standard.rs new file mode 100644 index 0000000..b4c725c --- /dev/null +++ b/src/rules/standard.rs @@ -0,0 +1,5 @@ +use crate::event::RawApiEvent; + +pub(super) fn compile(events: Vec) -> Vec { + events +} diff --git a/src/rules/stream.rs b/src/rules/stream.rs new file mode 100644 index 0000000..512171f --- /dev/null +++ b/src/rules/stream.rs @@ -0,0 +1,168 @@ +//! Shared event-stream traversal for rule compilers. + +use serde_json::Value; + +use crate::error::Result; +use crate::event::RawApiEvent; + +pub(super) trait EventStreamRule { + fn apply_sub_event(&mut self, sub: Value) -> Result>; + + fn finish_raw_event(&mut self) -> Result<()> { + Ok(()) + } +} + +pub(super) fn compile( + resolved: Vec, + rule: &mut R, +) -> Result> { + let mut output = Vec::with_capacity(resolved.len()); + + for raw in resolved { + let mut event_data: Value = serde_json::from_str(&raw.event_data)?; + let is_transaction = event_data.get("events").is_some(); + let sub_events = if is_transaction { + event_data + .get_mut("events") + .and_then(Value::as_array_mut) + .map(std::mem::take) + .unwrap_or_default() + } else { + vec![event_data.clone()] + }; + + let mut new_sub_events = Vec::with_capacity(sub_events.len()); + for sub in sub_events { + if let Some(new_sub) = rule.apply_sub_event(sub)? { + new_sub_events.push(new_sub); + } + } + + rule.finish_raw_event()?; + + let new_event_data = if is_transaction { + if let Some(obj) = event_data.as_object_mut() { + obj.insert("events".into(), Value::Array(new_sub_events)); + } + serde_json::to_string(&event_data)? + } else if let Some(single) = new_sub_events.into_iter().next() { + serde_json::to_string(&single)? + } else { + continue; + }; + + output.push(RawApiEvent { + id: raw.id, + stream_id: raw.stream_id, + sequence_number: raw.sequence_number, + event_data: new_event_data, + }); + } + + Ok(output) +} + +#[cfg(test)] +mod tests { + use super::*; + + struct DropCodeRule { + code: &'static str, + finished: usize, + } + + impl DropCodeRule { + const fn keep_all() -> Self { + Self { + code: "__never__", + finished: 0, + } + } + + const fn drop_code(code: &'static str) -> Self { + Self { code, finished: 0 } + } + } + + impl EventStreamRule for DropCodeRule { + fn apply_sub_event(&mut self, sub: Value) -> Result> { + let code = sub.get("code").and_then(Value::as_str); + Ok((code != Some(self.code)).then_some(sub)) + } + + fn finish_raw_event(&mut self) -> Result<()> { + self.finished += 1; + Ok(()) + } + } + + fn raw(seq: i64, event_data: &Value) -> RawApiEvent { + RawApiEvent { + id: format!("event-{seq}"), + stream_id: "test".to_string(), + sequence_number: seq, + event_data: event_data.to_string(), + } + } + + fn sub(code: &str) -> Value { + serde_json::json!({"code": code, "attributes": {}}) + } + + fn transaction(events: &[Value]) -> Value { + serde_json::json!({"code": "transaction", "events": events}) + } + + #[test] + fn keep_all_preserves_single_events() { + let input = vec![raw(1, &sub("pitch"))]; + let mut rule = DropCodeRule::keep_all(); + + let output = compile(input.clone(), &mut rule).unwrap(); + + assert_eq!(output.len(), 1); + assert_eq!(output[0].event_data, input[0].event_data); + assert_eq!(rule.finished, 1); + } + + #[test] + fn dropped_single_event_is_removed() { + let input = vec![raw(1, &sub("base_running"))]; + let mut rule = DropCodeRule::drop_code("base_running"); + + let output = compile(input, &mut rule).unwrap(); + + assert!(output.is_empty()); + assert_eq!(rule.finished, 1); + } + + #[test] + fn dropped_transaction_sub_event_is_removed() { + let input = vec![raw(1, &transaction(&[sub("pitch"), sub("base_running")]))]; + let mut rule = DropCodeRule::drop_code("base_running"); + + let output = compile(input, &mut rule).unwrap(); + let event_data: Value = serde_json::from_str(&output[0].event_data).unwrap(); + let events = event_data["events"].as_array().unwrap(); + + assert_eq!(events.len(), 1); + assert_eq!(events[0]["code"], "pitch"); + assert_eq!(rule.finished, 1); + } + + #[test] + fn empty_transaction_is_preserved() { + let input = vec![raw( + 1, + &transaction(&[sub("base_running"), sub("base_running")]), + )]; + let mut rule = DropCodeRule::drop_code("base_running"); + + let output = compile(input, &mut rule).unwrap(); + let event_data: Value = serde_json::from_str(&output[0].event_data).unwrap(); + + assert!(event_data["events"].as_array().unwrap().is_empty()); + assert_eq!(rule.finished, 1); + } +} diff --git a/src/score.rs b/src/score.rs index 4bbdbd7..ca9663c 100644 --- a/src/score.rs +++ b/src/score.rs @@ -37,8 +37,8 @@ pub fn force_advance_walk_score( /// Perform the base-state mutations for a walk/HBP force advance. pub fn apply_walk_bases(bases: &mut BaseState, batter_id: Option<&str>) { - let snap1 = bases.get(1).clone(); - let snap2 = bases.get(2).clone(); + let snap1 = bases.get(1).cloned(); + let snap2 = bases.get(2).cloned(); if snap2.is_some() && snap1.is_some() { bases.set(3, snap2); diff --git a/src/sim.rs b/src/sim.rs deleted file mode 100644 index 39cac5a..0000000 --- a/src/sim.rs +++ /dev/null @@ -1,523 +0,0 @@ -//! Scenario compiler for game simulations. -//! -//! Transforms an undo-resolved event stream before feeding it to the -//! core replay engine. The core engine stays pure with zero simulation hooks. - -use std::collections::{HashMap, HashSet}; - -use crate::error::Result; -use crate::event::{attr_bool, attr_str, attr_usize, BrPlayType, PlayResult, RawApiEvent}; - -/// Lightweight base/out tracker for the scenario compiler. -/// -/// ```text -/// Decision logic for chaos base_running events: -/// -/// base=4 (home)? → ALWAYS block. Runner stays at 3rd. -/// base=2 or 3? → Block if destination is occupied (no room). -/// Allow if destination is empty. -/// base=1? → Allow (steals to 1st don't happen, but be safe). -/// -/// Everything else (hits, walks, normal advances) passes through -/// unchanged. The core engine's auto-advance and walk force-advance -/// score runs naturally when bases load up. -/// -/// Scorer corrections for diverged runners (whose chaos advance was -/// blocked) are dropped so they don't undo the sim's auto-advance. -/// ``` -struct SimState { - bases: [Option; 3], // 0=1B, 1=2B, 2=3B - outs: i32, - balls: i32, - strikes: i32, - diverged: HashSet, - // Lineup tracking for batter ID resolution - away_id: String, - home_id: String, - offense: String, - lineup: HashMap<(String, usize), String>, // (team, slot) → player ID - lineup_size: HashMap, - current_index: HashMap, -} - -impl SimState { - fn new() -> Self { - Self { - bases: [None, None, None], - outs: 0, - balls: 0, - strikes: 0, - diverged: HashSet::new(), - away_id: String::new(), - home_id: String::new(), - offense: String::new(), - lineup: HashMap::new(), - lineup_size: HashMap::new(), - current_index: HashMap::new(), - } - } - - fn current_batter(&self) -> Option { - let idx = self.current_index.get(&self.offense).copied().unwrap_or(0); - self.lineup.get(&(self.offense.clone(), idx)).cloned() - } - - fn advance_batter(&mut self) { - let size = self.lineup_size.get(&self.offense).copied().unwrap_or(1).max(1); - let idx = self.current_index.entry(self.offense.clone()).or_insert(0); - *idx = (*idx + 1) % size; - } - - fn is_occupied(&self, base: usize) -> bool { - (1..=3).contains(&base) && self.bases[base - 1].is_some() - } - - fn set(&mut self, base: usize, id: Option) { - if (1..=3).contains(&base) { - self.bases[base - 1] = id; - } - } - - fn remove_runner(&mut self, rid: &str) { - for b in 0..3 { - if self.bases[b].as_deref() == Some(rid) { - self.bases[b] = None; - break; - } - } - } - - fn switch_half(&mut self) { - self.bases = [None, None, None]; - self.outs = 0; - self.balls = 0; - self.strikes = 0; - self.diverged.clear(); - self.offense = if self.offense == self.away_id { - self.home_id.clone() - } else { - self.away_id.clone() - }; - } - - fn reset_count(&mut self) { - self.balls = 0; - self.strikes = 0; - } - - /// Walk/HBP force-advance: push runners up, place batter on 1B. - fn apply_walk(&mut self) { - let batter = self.current_batter(); - if self.is_occupied(1) && self.is_occupied(2) && self.is_occupied(3) { - self.set(3, None); // runner scores - } - if self.is_occupied(1) && self.is_occupied(2) { - let r2 = self.bases[1].take(); - self.set(3, r2); - } - if self.is_occupied(1) { - let r1 = self.bases[0].take(); - self.set(2, r1); - } - self.set(1, batter); - self.advance_batter(); - } - - /// Auto-advance for hits. Places batter on the appropriate base. - fn apply_hit(&mut self, play_result: &str) { - let batter = self.current_batter(); - match play_result { - "single" => { - self.set(3, None); // 3B scores - let r2 = self.bases[1].take(); - self.set(3, r2); // 2B→3B - let r1 = self.bases[0].take(); - self.set(2, r1); // 1B→2B - self.set(1, batter); - } - "double" => { - self.set(3, None); // 3B scores - self.set(2, None); // 2B scores - let r1 = self.bases[0].take(); - self.set(3, r1); // 1B→3B - self.set(2, batter); - } - "triple" | "home_run" => { - self.bases = [None, None, None]; - } - _ => {} // corrections follow via base_running - } - self.advance_batter(); - } -} - -/// Compile the no-steal-home scenario. Chaos base-running events are -/// blocked when they would score (base=4) or when the destination base -/// is already occupied. Runners only score via hits or walk force-advance. -/// -/// # Errors -/// -/// Returns an error if any `event_data` fails to parse as JSON. -#[allow(clippy::too_many_lines)] -pub fn compile_no_steal_home(resolved: Vec) -> Result> { - let mut state = SimState::new(); - let mut output = Vec::with_capacity(resolved.len()); - - for raw in resolved { - let mut ed: serde_json::Value = serde_json::from_str(&raw.event_data)?; - - let is_transaction = ed.get("events").is_some(); - let sub_events = if is_transaction { - ed.get_mut("events") - .and_then(|v| v.as_array_mut()) - .map(std::mem::take) - .unwrap_or_default() - } else { - vec![ed.clone()] - }; - - let mut new_sub_events: Vec = Vec::with_capacity(sub_events.len()); - let mut need_switch = false; - - for sub in sub_events { - let code = attr_str(&sub, "code").unwrap_or(""); - let null = serde_json::Value::Null; - let attrs = sub.get("attributes").unwrap_or(&null); - - match code { - "set_teams" => { - if let (Some(away), Some(home)) = (attr_str(attrs, "awayId"), attr_str(attrs, "homeId")) { - state.away_id = away.to_string(); - state.home_id = home.to_string(); - state.offense = away.to_string(); - } - new_sub_events.push(sub); - } - - "fill_lineup_index" => { - if let (Some(tid), Some(pid), Some(idx)) = ( - attr_str(attrs, "teamId"), - attr_str(attrs, "playerId"), - attr_usize(attrs, "index"), - ) { - state.lineup.insert((tid.to_string(), idx), pid.to_string()); - let size = state.lineup_size.entry(tid.to_string()).or_insert(0); - if idx + 1 > *size { - *size = idx + 1; - } - } - new_sub_events.push(sub); - } - - "fill_lineup" => { - if let (Some(tid), Some(pid)) = (attr_str(attrs, "teamId"), attr_str(attrs, "playerId")) { - let next_idx = state.lineup_size.get(tid).copied().unwrap_or(0); - state.lineup.insert((tid.to_string(), next_idx), pid.to_string()); - *state.lineup_size.entry(tid.to_string()).or_insert(0) = next_idx + 1; - } - new_sub_events.push(sub); - } - - "goto_lineup_index" => { - if let (Some(tid), Some(idx)) = (attr_str(attrs, "teamId"), attr_usize(attrs, "index")) { - state.current_index.insert(tid.to_string(), idx); - } - new_sub_events.push(sub); - } - - "pitch" => { - if attr_bool(attrs, "advancesCount", true) { - let result = attr_str(attrs, "result").unwrap_or(""); - match result { - "ball" => { - state.balls += 1; - if state.balls >= 4 { - state.apply_walk(); - state.reset_count(); - } - } - "strike_swinging" | "strike_looking" => { - state.strikes += 1; - if state.strikes >= 3 { - state.outs += 1; - state.reset_count(); - state.advance_batter(); - if state.outs >= 3 { - need_switch = true; - } - } - } - "foul" => { - if state.strikes < 2 { - state.strikes += 1; - } - } - "hit_by_pitch" => { - state.apply_walk(); - state.reset_count(); - } - "ball_in_play" => { - state.reset_count(); - } - _ => {} - } - } - new_sub_events.push(sub); - } - - "ball_in_play" => { - let pr = attr_str(attrs, "playResult").unwrap_or(""); - if PlayResult::parse(pr).is_batter_out() { - let added = if pr == "double_play" { 2 } else { 1 }; - state.outs += added; - state.advance_batter(); - if state.outs >= 3 { - need_switch = true; - } - } else { - state.apply_hit(pr); // advances batter internally - } - new_sub_events.push(sub); - } - - "base_running" => { - let pt = attr_str(attrs, "playType").unwrap_or(""); - let br_type = BrPlayType::parse(pt); - let base = attr_usize(attrs, "base"); - let runner_id = attr_str(attrs, "runnerId").map(str::to_string); - - if br_type.is_chaos() { - let blocked = match base { - Some(4) => true, // never score on chaos - Some(b @ 1..=3) => state.is_occupied(b), // block if no room - _ => false, - }; - if blocked { - // Mark runner as diverged from real game state - if let Some(rid) = &runner_id { - state.diverged.insert(rid.clone()); - } - continue; // drop the event - } - // Allowed chaos advance (destination empty): update state - if let (Some(rid), Some(b)) = (&runner_id, base) { - state.remove_runner(rid); - state.set(b, Some(rid.clone())); - } - new_sub_events.push(sub); - continue; - } - - // Drop scorer corrections for diverged runners. - // Their position in our sim differs from the real game, - // so scorer corrections would undo correct auto-advances. - if let Some(rid) = &runner_id { - if state.diverged.contains(rid) && !br_type.is_out() { - continue; // drop stale correction - } - } - - if br_type.is_out() { - if let Some(rid) = &runner_id { - state.remove_runner(rid); - state.diverged.remove(rid); - } - state.outs += 1; - if state.outs >= 3 { - need_switch = true; - } - new_sub_events.push(sub); - continue; - } - - // Non-chaos: normal advance (ground truth correction) - if let (Some(rid), Some(b)) = (&runner_id, base) { - state.remove_runner(rid); - if (1..=3).contains(&b) { - state.set(b, Some(rid.clone())); - } - } - new_sub_events.push(sub); - } - - "end_half" => { - state.switch_half(); - need_switch = false; - new_sub_events.push(sub); - } - - _ => { - new_sub_events.push(sub); - } - } - } - - if need_switch { - state.switch_half(); - } - - // Rebuild event_data JSON - let new_event_data = if is_transaction { - if let Some(obj) = ed.as_object_mut() { - obj.insert("events".into(), serde_json::Value::Array(new_sub_events)); - } - serde_json::to_string(&ed)? - } else if let Some(single) = new_sub_events.into_iter().next() { - serde_json::to_string(&single)? - } else { - continue; - }; - - output.push(RawApiEvent { - id: raw.id, - stream_id: raw.stream_id, - sequence_number: raw.sequence_number, - event_data: new_event_data, - }); - } - - Ok(output) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn raw_single(seq: i64, sub: serde_json::Value) -> RawApiEvent { - RawApiEvent { - id: format!("test-{seq}"), - stream_id: "test".into(), - sequence_number: seq, - event_data: serde_json::to_string(&sub).unwrap(), - } - } - - fn raw_tx(seq: i64, subs: Vec) -> RawApiEvent { - let tx = serde_json::json!({"code": "transaction", "events": subs}); - RawApiEvent { - id: format!("test-{seq}"), - stream_id: "test".into(), - sequence_number: seq, - event_data: serde_json::to_string(&tx).unwrap(), - } - } - - fn pitch(result: &str) -> serde_json::Value { - serde_json::json!({"code": "pitch", "attributes": {"result": result, "advancesCount": true}}) - } - - fn bip(play_result: &str) -> serde_json::Value { - serde_json::json!({"code": "ball_in_play", "attributes": {"playResult": play_result}}) - } - - fn base_running(play_type: &str, base: u64, runner_id: &str) -> serde_json::Value { - serde_json::json!({"code": "base_running", "attributes": {"playType": play_type, "base": base, "runnerId": runner_id}}) - } - - fn end_half() -> serde_json::Value { - serde_json::json!({"code": "end_half", "attributes": {}}) - } - - #[test] - fn chaos_scoring_always_blocked() { - let events = vec![ - raw_single(1, base_running("advanced_on_last_play", 3, "r1")), - raw_single(2, base_running("passed_ball", 4, "r1")), - ]; - let result = compile_no_steal_home(events).unwrap(); - assert_eq!(result.len(), 1, "PB to home dropped"); - } - - #[test] - fn chaos_advance_allowed_when_destination_empty() { - let events = vec![ - raw_single(1, base_running("advanced_on_last_play", 1, "r1")), - raw_single(2, base_running("passed_ball", 2, "r1")), // 2B empty, allowed - ]; - let result = compile_no_steal_home(events).unwrap(); - assert_eq!(result.len(), 2, "PB to empty 2B allowed"); - } - - #[test] - fn chaos_advance_blocked_when_destination_occupied() { - let events = vec![ - raw_single(1, base_running("advanced_on_last_play", 2, "r1")), - raw_single(2, base_running("advanced_on_last_play", 3, "r2")), - // r2 on 3rd, PB to home blocked. r1 on 2nd, PB to 3rd blocked (occupied). - raw_single(3, base_running("passed_ball", 4, "r2")), // blocked - raw_single(4, base_running("passed_ball", 3, "r1")), // blocked (3B occupied) - ]; - let result = compile_no_steal_home(events).unwrap(); - assert_eq!(result.len(), 2, "both chaos advances blocked"); - } - - #[test] - fn bases_load_and_walk_forces_run() { - // Simulate: 3 runners get on via advances, PB scoring blocked, - // then walks should force runs via the core engine - let events = vec![ - raw_single(1, base_running("advanced_on_last_play", 1, "r1")), - raw_single(2, base_running("advanced_on_last_play", 2, "r2")), - raw_single(3, base_running("advanced_on_last_play", 3, "r3")), - raw_single(4, base_running("stole_base", 4, "r3")), // blocked - // r3 still on 3rd. Bases loaded (r1=1B, r2=2B, r3=3B) - // Walk should force r3 home via core engine - raw_single(5, pitch("ball")), - raw_single(6, pitch("ball")), - raw_single(7, pitch("ball")), - raw_single(8, pitch("ball")), - ]; - let result = compile_no_steal_home(events).unwrap(); - // Event 4 (steal to home) should be dropped - assert_eq!(result.len(), 7); - } - - #[test] - fn non_chaos_events_pass_through() { - let events = vec![ - raw_single(1, pitch("ball")), - raw_single(2, bip("single")), - raw_single(3, base_running("advanced_on_last_play", 3, "r1")), - raw_single(4, base_running("caught_stealing", 2, "r2")), - ]; - let result = compile_no_steal_home(events).unwrap(); - assert_eq!(result.len(), 4); - } - - #[test] - fn half_inning_switch_clears_state() { - let events = vec![ - raw_single(1, base_running("advanced_on_last_play", 3, "r1")), - raw_single(2, end_half()), - // After switch, 3B is empty so PB advance should be allowed - raw_single(3, base_running("advanced_on_last_play", 2, "r2")), - raw_single(4, base_running("passed_ball", 3, "r2")), // 3B empty, allowed - ]; - let result = compile_no_steal_home(events).unwrap(); - assert_eq!(result.len(), 4); - } - - #[test] - fn chaos_within_transaction() { - let events = vec![ - raw_single(1, base_running("advanced_on_last_play", 3, "r1")), - raw_tx(2, vec![ - base_running("passed_ball", 4, "r1"), // blocked - base_running("passed_ball", 3, "r2"), // blocked (3B occupied) - ]), - ]; - let result = compile_no_steal_home(events).unwrap(); - let ed: serde_json::Value = serde_json::from_str(&result[1].event_data).unwrap(); - let tx_events = ed["events"].as_array().unwrap(); - assert_eq!(tx_events.len(), 0, "both chaos events in tx dropped"); - } - - #[test] - fn sacrifice_fly_counted_as_out() { - let events = vec![ - raw_tx(1, vec![bip("sacrifice_fly")]), - ]; - let result = compile_no_steal_home(events).unwrap(); - assert_eq!(result.len(), 1); - } -} diff --git a/src/state.rs b/src/state.rs index f34d0d8..069108a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -43,9 +43,9 @@ impl BaseState { /// /// Panics if `base` is not in the range 1..=3. #[must_use] - pub fn get(&self, base: usize) -> &Option { + pub fn get(&self, base: usize) -> Option<&BaseOccupant> { assert!((1..=3).contains(&base), "base must be 1-3"); - &self.bases[base - 1] + self.bases[base - 1].as_ref() } /// # Panics @@ -71,7 +71,7 @@ impl BaseState { /// /// Panics if `from` or `to` is not in the range 1..=3. pub fn advance(&mut self, from: usize, to: usize) { - let occ = self.get(from).clone(); + let occ = self.get(from).cloned(); self.set(from, None); self.set(to, occ); } @@ -143,6 +143,17 @@ impl BaseState { } self.clear_fallback(dest_base) } + + /// Replace one runner with another on the bases (player substitution). + pub fn substitute_runner(&mut self, old_id: &str, new_id: &str) { + for occ in &mut self.bases { + if let Some(BaseOccupant::Player(pid)) = occ { + if pid == old_id { + *pid = new_id.to_string(); + } + } + } + } } /// Tracks which runners were auto-scored during eager auto-advance. diff --git a/src/undo.rs b/src/undo.rs index 5fa4f0d..0a45251 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -35,7 +35,7 @@ pub fn resolve_undos(mut raw_events: Vec) -> Vec { stack } -/// Extract the top-level "code" field from event_data JSON without full parsing. +/// Extract the top-level "code" field from `event_data` JSON without full parsing. fn extract_code(event_data: &str) -> Option { if event_data.contains("\"undo\"") || event_data.contains("\"redo\"") { if let Ok(v) = serde_json::from_str::(event_data) { @@ -54,7 +54,7 @@ mod tests { id: format!("evt-{seq}"), stream_id: "test".into(), sequence_number: seq, - event_data: format!(r#"{{"code":"{}"}}"#, code), + event_data: format!(r#"{{"code":"{code}"}}"#), } } diff --git a/testdata/10U_Mariners_Brewers_Apr12.json b/testdata/10U_Mariners_Brewers_Apr12.json new file mode 100644 index 0000000..cf19cd1 --- /dev/null +++ b/testdata/10U_Mariners_Brewers_Apr12.json @@ -0,0 +1,2432 @@ +[ + { + "id": "201cab5e-7c07-4295-8df0-de770559739b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 0, + "event_data": "{\"attributes\":{\"homeId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"awayId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"aniFT\":true},\"code\":\"set_teams\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"415e8884-2d5a-4b29-ab24-28d1f3081810\"}" + }, + { + "id": "d39a3b4a-a473-4615-9c9b-8e9f3c5ec3eb", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 1, + "event_data": "{\"events\":[{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\",\"index\":0},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"ac07fd82-f534-4835-ab40-e3abd4d69037\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"d4a1ceca-45a9-4f00-9ec0-ba04a406f0c2\",\"index\":1},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"8f13bf93-326a-4697-af30-233b5dc53593\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"931eb801-3e5e-43fb-af0c-690e4bfa58c6\",\"index\":2},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"500233bc-cd74-430d-a3d2-edecc911b5d9\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\",\"index\":3},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"19af3876-1991-4182-bb42-b3027924ae53\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\",\"index\":4},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"c356d800-9aa1-4840-9673-332febd6a466\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\",\"index\":5},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"177dad2c-e679-4440-bf51-104faca76013\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"f1cec113-0970-43dc-9c06-a6c453227f13\",\"index\":6},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"7432fac6-c869-43f5-b878-628f7b58b349\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\",\"index\":7},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"922d5c85-b9d0-4a97-9b4d-43cdbdafe4b3\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\",\"index\":8},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"5d70da23-3266-4786-97d0-0ae13b5c4b34\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\",\"index\":9},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"57a3fb39-5046-439c-b628-13945d44e471\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"f2ec6562-8383-4235-bfe3-f95faf4c0a8a\",\"index\":10},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"189e088e-7223-4697-b1d0-46fae4c86f91\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\",\"position\":\"C\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"f1b4b5ce-04ba-4ca6-8cba-12af6de9edb2\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"d4a1ceca-45a9-4f00-9ec0-ba04a406f0c2\",\"position\":\"3B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"80403037-f16c-4dcf-8729-66e502287033\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"931eb801-3e5e-43fb-af0c-690e4bfa58c6\",\"position\":\"P\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"c9d877a5-6177-48bf-8f5b-efd30cbe6095\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\",\"position\":\"1B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"c0853d95-24d4-434d-9a6f-2f008d437298\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"0623243b-915e-4c07-98aa-8e456405faaf\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\",\"position\":\"2B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"6071a64a-3d17-4bba-8ba0-fdf735166575\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"f1cec113-0970-43dc-9c06-a6c453227f13\",\"position\":\"RF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"41b7ccec-8f78-46a0-a070-c69c10a3314a\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\",\"position\":\"LF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"ad632fb4-f21e-44f9-bbd2-2933628d3f14\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\",\"position\":\"CF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"head\"},\"createdAt\":1776020181535,\"id\":\"aa0bc0e5-3e1f-474d-b5dd-c4e91d5a4ab4\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"head\"},\"id\":\"c45efefb-297c-4b35-9fe2-e45bbe27afb8\"}" + }, + { + "id": "0a678108-78c0-4152-b344-3782d30f96ab", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 2, + "event_data": "{\"events\":[{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\"},\"code\":\"clear_entire_lineup\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"e80b37a5-711b-4ae1-aef9-578d7e614a10\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\"},\"code\":\"clear_all_positions\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"a437cc2e-cf9b-43ba-9d73-e2e86605d38b\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\"},\"code\":\"clear_flex\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"e1ee44cf-8bfc-4e7a-b5f3-b1b45740f1aa\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\"},\"code\":\"clear_dp\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"32d67786-05ed-470d-8d9e-927c5e271dd5\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b04f45dc-73bb-439f-a3c1-da6f6c0a2907\",\"index\":0},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"67e654ce-8da0-4cab-93f6-34e7b050ca94\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\",\"index\":1},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"283d2921-26c0-43b2-a6ea-da9d7744102c\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"01a1bf61-d1fb-46f2-86a6-d848bb025626\",\"index\":2},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"45765efb-6d73-49dd-b7cf-79e4c5a462f0\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"aeda7933-db71-445d-b2d2-f0d895dbfc37\",\"index\":3},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"88670d98-3021-4f5f-87b5-95fd9772ca69\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b0cd8bc9-7c82-4929-8630-78f6584acdd7\",\"index\":4},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"52d84005-e93f-4cdf-a90f-b7bc21634211\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"19e051fb-858e-473f-8f77-94e826de3ee8\",\"index\":5},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"2d86f6ae-f9d5-4f11-a450-1f3ec60d6b73\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"8b65024e-947e-42a1-8686-a8ce98a52941\",\"index\":6},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"869eb0cd-ad77-432f-bb71-b3902f783b48\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"0308b766-757a-4a30-a21a-190a9c98711e\",\"index\":7},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"aeb98da0-41ee-4d93-8c38-d010950ca942\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"c929a2cc-1fb4-4635-8efe-50aebf023136\",\"index\":8},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"2de10ea1-a6ec-46f3-869f-25d449ba1ffa\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"a119857c-54cd-4ad0-86da-2fa65514dcfb\",\"index\":9},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"e35ede72-1ee1-4647-ba5e-4e62e5002152\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"3edbe950-b154-4b24-9010-dd5c0b8572e3\",\"index\":10},\"code\":\"fill_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"15d8e479-1741-45c9-a8fc-46d98e193faf\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b04f45dc-73bb-439f-a3c1-da6f6c0a2907\",\"position\":\"C\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"4e55958b-4546-421f-aa80-9433b866af2e\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\",\"position\":\"P\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"a0018cdd-aa85-41f9-985f-9fb5ce5ba9e0\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"01a1bf61-d1fb-46f2-86a6-d848bb025626\",\"position\":\"1B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"7a5cc3e8-b062-4639-9887-85e48785f50d\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"aeda7933-db71-445d-b2d2-f0d895dbfc37\",\"position\":\"2B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"4ca964c9-a2db-4f60-b25f-5c793a14975b\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b0cd8bc9-7c82-4929-8630-78f6584acdd7\",\"position\":\"CF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"27cbe1da-76b4-45bd-80b9-7efe5a5c50cd\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"19e051fb-858e-473f-8f77-94e826de3ee8\",\"position\":\"LF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"9366de10-c9f6-4c66-a205-ce538ce54cd3\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"8b65024e-947e-42a1-8686-a8ce98a52941\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"65c5421f-dfe4-46a7-9381-3bfef423072c\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"0308b766-757a-4a30-a21a-190a9c98711e\",\"position\":\"3B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"445c9bc7-157b-4ad8-b7f2-5c9199a66571\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"c929a2cc-1fb4-4635-8efe-50aebf023136\",\"position\":\"RF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"96dc625a-d08c-4535-9833-24b3f2b4483e\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"3edbe950-b154-4b24-9010-dd5c0b8572e3\",\"position\":\"EH\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020185762,\"id\":\"51160d9f-776c-4ad0-83ea-5759c9179b66\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"32173d3e-1da2-49de-a144-f522e3408fb8\"}" + }, + { + "id": "12e2ad23-4241-4c61-aef2-8560c299fc1d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 3, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020436781,\"id\":\"c6480a51-b758-4a46-bd24-c7fad5d19930\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":212.0,\"y\":139.0},\"position\":\"2B\"},{\"error\":false,\"location\":{\"x\":263.0,\"y\":186.0},\"position\":\"1B\"}],\"playResult\":\"batter_out_advance_runners\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020436781,\"id\":\"9f2c627c-d62a-4830-8dfd-db43f152dd63\"},{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020443542,\"id\":\"7ff33a3c-2d3d-49c0-9b65-b6a3adff300d\"}],\"code\":\"transaction\",\"id\":\"b57e1b88-1dad-4432-a906-55b5936b530c\"}" + }, + { + "id": "04309b0b-ce67-4ab2-a365-5de85e9f9a9b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 4, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020456936,\"id\":\"19e72aa1-24c0-456b-94d1-259a0ad98491\"}" + }, + { + "id": "998b90a8-1efa-45f9-a4a0-345a982a9a54", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 5, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020469927,\"id\":\"a9c1302d-f729-4e5d-af7d-9b05552b804c\"}" + }, + { + "id": "c3ffce0f-aa08-4fa1-9ed9-018e9ebd5015", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 6, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020491177,\"id\":\"160e2036-2856-4d07-ad4d-51557d390de5\"}" + }, + { + "id": "c3563a8f-4eb9-4975-a1fb-c0b1c6c6b097", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 7, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020508014,\"id\":\"4b5601ec-188e-4953-afb0-e1e497782745\"}" + }, + { + "id": "415ec478-86e6-42ab-bb10-841cc681b898", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 8, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020554844,\"id\":\"c4fea380-457c-453a-a55a-ef07b2ed7a50\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":186.0},\"position\":\"3B\"}],\"playResult\":\"batter_out\",\"playType\":\"pop_fly\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020554844,\"id\":\"f4c56af4-5823-49e8-851e-f30150af9b30\"}],\"code\":\"transaction\",\"id\":\"501e2032-0fd6-448b-abc1-8f3a98877fac\"}" + }, + { + "id": "d016408f-a365-458f-bb65-9e1eb59e0574", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 9, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020692487,\"id\":\"dc5b3aba-caf9-4d1e-b23f-bd366c0e5617\"}" + }, + { + "id": "1ebb48ba-d500-469e-80ec-593f4782c654", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 10, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020716145,\"id\":\"8ca3b3e6-7499-4fd3-9c9a-ed7e9e784c16\"}" + }, + { + "id": "648d9c5e-b269-42f6-bacb-89b5dd6feb33", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 11, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020739730,\"id\":\"25555dad-f556-439f-9ccc-c66a39ed6f37\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":160.0,\"y\":82.0},\"position\":\"CF\"}],\"playResult\":\"single\",\"playType\":\"hard_ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020739730,\"id\":\"08276a74-fe32-4477-bf87-b0840f0adf1e\"}],\"code\":\"transaction\",\"id\":\"be2352a6-c89b-43b9-8591-79cacd28012b\"}" + }, + { + "id": "56f897e7-b564-4d91-a5e0-e73124034274", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 12, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\",\"position\":\"CF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020749929,\"id\":\"14fe84e1-e4d1-4377-9be7-2c0dc66e6775\"}" + }, + { + "id": "d5321da5-6725-478a-b32a-7c46b9bc3600", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 13, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\",\"position\":\"LF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020759408,\"id\":\"a0513951-159a-4ad5-92ef-d852bf289c85\"}" + }, + { + "id": "d9b13ca1-a5fc-480c-8272-acc9895bd17a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 14, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020766313,\"id\":\"144bbeff-4654-462e-8590-6b86ed9fbdc7\"}" + }, + { + "id": "e659ef57-a9c3-4139-a1e1-ec2bccd55f85", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 15, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020766704,\"id\":\"610876a1-8191-4173-b7a5-b5e82678f771\"}" + }, + { + "id": "7d8ef4b6-962b-4dc2-be8d-bbdbfff5fbc1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 16, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020769551,\"id\":\"ed63513a-9edf-4ace-add7-a174b1af41f9\"}" + }, + { + "id": "2b598bb9-f86c-47e9-8d30-7345399dd381", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 17, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020780064,\"id\":\"e64206a3-40df-43d9-b850-90f78efe5083\"}" + }, + { + "id": "a906972c-031a-4d7f-8972-ce81d410c543", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 18, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020792944,\"id\":\"0e84c62d-b202-4299-9d38-f16aa7571919\"}" + }, + { + "id": "1226a386-a166-4240-b96b-ad3eaaeaf76a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 19, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020809515,\"id\":\"f38f5424-7b18-4b22-b580-62cde6f5d907\"}" + }, + { + "id": "0bcd9a04-29e3-4b5a-9cd4-3f48a3497c28", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 20, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020819952,\"id\":\"3d650bf0-facd-4472-9410-a7a31a46bf9c\"}" + }, + { + "id": "c534912b-8aee-4869-81c4-db6b9a9fa634", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 21, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020831378,\"id\":\"15404067-29b2-4eb2-8753-9b5a1cbe97fc\"}" + }, + { + "id": "2bcdcdc3-9423-4a3b-981a-d6ec9983b3f7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 22, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020843053,\"id\":\"14d282fd-4c3e-43ba-838a-a08edb328ed0\"}" + }, + { + "id": "546e4f00-9efa-4ed8-a3ef-3928c45b5d38", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 23, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020855755,\"id\":\"a964e051-33b4-44b6-ab47-559feefdefed\"}" + }, + { + "id": "e69ec487-d2f2-45d3-8b1d-7f24f9389c4b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 24, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020874257,\"id\":\"17c971df-ae0c-433b-8080-1f5da84410a4\"}" + }, + { + "id": "d989a711-1643-4dcf-975a-c8c676a5ddf1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 25, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020890601,\"id\":\"ea644fba-e793-4d58-a758-3852d5474b38\"}" + }, + { + "id": "ef805c36-c2b7-4d29-9e1a-d68a14726a7c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 26, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b04f45dc-73bb-439f-a3c1-da6f6c0a2907\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020893283,\"id\":\"02d68789-cc38-4a4a-860f-ffca18f67619\"}" + }, + { + "id": "ac0d11b3-0616-48d6-a22f-33823763f2de", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 27, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020895000,\"id\":\"98b42ab5-540a-4abf-b451-d68784268fc6\"}" + }, + { + "id": "240dc71b-ee72-4f60-b209-b2e04f1977e6", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 28, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"01a1bf61-d1fb-46f2-86a6-d848bb025626\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020900797,\"id\":\"0a564e35-8be1-4984-91a7-deeb4df16fbf\"}" + }, + { + "id": "1dabbf57-99d4-4b15-ae61-521f0c70a0ac", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 29, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020910171,\"id\":\"db19f407-b5c7-4294-a2ae-d04bec1f3f51\"}" + }, + { + "id": "9304c7e7-3d2b-469d-9f6d-0425b37a2ac2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 30, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020922036,\"id\":\"26f91c7f-8a21-44ba-9dfb-4acd7de194be\"}" + }, + { + "id": "2a038e27-ae29-43ac-bf6f-2803bce21b66", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 31, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020935776,\"id\":\"f56e71eb-8abb-4bf7-90d6-39ae23d8b188\"}" + }, + { + "id": "18a3c885-7715-4205-bdeb-cd1b6ad50598", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 32, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020949257,\"id\":\"3c3b7c9e-2f24-4c2a-bb10-3c48c00ffecd\"}" + }, + { + "id": "417f2425-954f-4d8e-893f-5d4a1e18c165", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 33, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020965878,\"id\":\"a09197da-499e-42c4-b718-29697aa3a61a\"}" + }, + { + "id": "f7b73455-3784-4264-bf17-c93eb4ffd756", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 34, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020976573,\"id\":\"63f993b5-ca8b-4eda-a12d-95d5845c54ac\"}" + }, + { + "id": "8b739018-343a-4aa8-a6e6-8c9d83fcef8e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 35, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020987628,\"id\":\"2c15a5f9-e8eb-4f74-9158-da143fb560b4\"}" + }, + { + "id": "aec56500-c139-4a5b-b598-8e2797bde2d8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 36, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776020999001,\"id\":\"cba4f084-c64d-4927-8d54-a59c81c97c3a\"}" + }, + { + "id": "a74101da-9db7-4d3d-b84a-5ef5f02937eb", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 37, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021012403,\"id\":\"813eaa75-f307-4fb6-ad76-a63452788760\"}" + }, + { + "id": "ea4922d4-2068-4c9a-8ed9-e54cffe1250c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 38, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021035375,\"id\":\"6282097a-f884-4177-a73c-3b06dbc79103\"}" + }, + { + "id": "70077cdc-0010-415b-9e48-e6191f1db377", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 39, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021050123,\"id\":\"80b7bcf4-bab8-49bd-be9c-38d512fca70d\"}" + }, + { + "id": "3ae58fd5-a375-4492-8f1e-7b5a010ff341", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 40, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021060994,\"id\":\"d5b8f5be-71a9-4b08-b151-dff135e87148\"}" + }, + { + "id": "7b167006-1702-4a07-a11a-76e3366bb689", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 41, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021073096,\"id\":\"55832032-a458-4d2a-bd02-507cca377d3d\"}" + }, + { + "id": "33703dad-9e84-4e49-95c2-d5dc10644353", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 42, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021098740,\"id\":\"3b27e703-18c7-4f40-b0ff-4fbfa0cfbddb\"}" + }, + { + "id": "2fe4ca33-6f65-4e36-b652-817ef8f9dfe1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 43, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021101442,\"id\":\"75cf70e9-bc89-41a0-97f0-00f217865d2b\"}" + }, + { + "id": "4bfadf43-fe96-4950-b6ce-877af8f0607e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 44, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"01a1bf61-d1fb-46f2-86a6-d848bb025626\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021103287,\"id\":\"269c8420-09fc-4e26-806d-fd7cb545e98c\"}" + }, + { + "id": "37b1dadd-54c2-47b9-9256-05ba46f5318a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 45, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b0cd8bc9-7c82-4929-8630-78f6584acdd7\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021105217,\"id\":\"7efffc6f-cec6-4118-a2f7-34f3a634730b\"}" + }, + { + "id": "e6cd0af9-77c3-4492-952a-6e01242ada3d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 46, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021122648,\"id\":\"8299dd9f-cd8a-40cc-a6bd-ebe292ee7c69\"}" + }, + { + "id": "b71e4501-4d86-4689-b8a3-4ceca1cf0294", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 47, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021174742,\"id\":\"2c50857e-d61b-4b4b-bd6f-c39cc591d057\"}" + }, + { + "id": "21df9b8c-d674-457a-a3e7-3c67522010bb", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 48, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021186299,\"id\":\"527aa53d-ba99-4723-8660-3a242fe0a075\"}" + }, + { + "id": "25ecf546-270c-4749-a4ab-2be8fbe3f6fb", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 49, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021203243,\"id\":\"b3515bc2-15e7-4603-82ea-3762e5bedf7b\"}" + }, + { + "id": "3849c165-c4e6-4a09-a2b2-5f85bb119f60", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 50, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021217257,\"id\":\"14b8e236-05e5-4bf5-aa88-8f3a3bbde058\"}" + }, + { + "id": "1699c5c8-1633-4c9f-baba-5f41f4a17cde", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 51, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021239927,\"id\":\"dc5ca186-23d8-44a6-9409-546189395bf8\"}" + }, + { + "id": "908a40d4-e898-4c5f-ab34-6a2c4387513a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 52, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"01a1bf61-d1fb-46f2-86a6-d848bb025626\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021243027,\"id\":\"b8534287-2ae4-4131-b94d-f5c28428254d\"}" + }, + { + "id": "4c45c93b-79de-4010-9c5d-5be35c9a56de", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 53, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b0cd8bc9-7c82-4929-8630-78f6584acdd7\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021244790,\"id\":\"57586bdd-ba2a-40dd-9364-5428b41578a0\"}" + }, + { + "id": "3e2d460a-3b80-423b-bdc8-5368e9d3c01d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 54, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"19e051fb-858e-473f-8f77-94e826de3ee8\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021246883,\"id\":\"96ee0f92-8260-4d03-9a42-0d53661c96de\"}" + }, + { + "id": "c85ced76-aee0-4353-9acc-699394546640", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 55, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021259369,\"id\":\"d2fe45b3-38dc-4780-ae4d-d0fabf16f583\"}" + }, + { + "id": "1daf899d-bbf0-4f37-9294-adeb9b5f3669", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 56, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021284771,\"id\":\"cc3e61cd-5909-4964-abac-989c3c1dab2a\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":160.0,\"y\":82.0},\"position\":\"CF\"}],\"playResult\":\"single\",\"playType\":\"line_drive\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021284771,\"id\":\"d2d79666-518d-4c4e-b5de-41decfc3ca15\"}],\"code\":\"transaction\",\"id\":\"06d00d92-c6ff-4b32-ba76-55b9d9c680a5\"}" + }, + { + "id": "951f0a69-1119-4437-b78c-9368cbbfbf35", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 57, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"19e051fb-858e-473f-8f77-94e826de3ee8\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021301131,\"id\":\"f6c38020-4455-4e35-964a-c63966b6871e\"}" + }, + { + "id": "c06b7c3b-811f-43aa-a133-14f268071477", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 58, + "event_data": "{\"code\":\"end_half\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021305380,\"id\":\"e9aa9905-84e1-4285-8e49-3a140d839215\"}" + }, + { + "id": "04245b66-d9fb-48fa-afd0-07c75f661237", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 59, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021463925,\"id\":\"1536b8e4-cfb1-4f26-bba7-84b9e13f2a85\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":160.0,\"y\":82.0},\"position\":\"CF\"}],\"playResult\":\"single\",\"playType\":\"line_drive\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021463925,\"id\":\"28315bfc-cb20-4354-9f68-d306dd473da5\"}],\"code\":\"transaction\",\"id\":\"b241e023-2362-4bfe-8972-424ad22a3e8e\"}" + }, + { + "id": "f16a063f-56e7-465b-a324-0c4f949e1c24", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 60, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021477611,\"id\":\"d81749ec-13e6-480d-a871-e5faf3706964\"}" + }, + { + "id": "6953211b-d605-4915-9b51-853c9e85438e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 61, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021480024,\"id\":\"4916c0ef-67d4-4f1c-bf39-d634011228f2\"}" + }, + { + "id": "6964c698-95fb-4b4f-b768-583b34a8937b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 62, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021494677,\"id\":\"1bb24ffe-82dc-467d-84fc-15c074db0729\"}" + }, + { + "id": "7f0529bc-5629-43de-8b0f-6c6dd2b38bc9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 63, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021504045,\"id\":\"2331de07-de47-49ec-80a4-f3b6a2726593\"}" + }, + { + "id": "607ade2b-378b-43fa-9540-938b97fdbbec", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 64, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021513028,\"id\":\"d0b6e849-3b10-49da-a7b2-b6cf740b4eae\"}" + }, + { + "id": "6577ae7d-fb7f-4947-9735-06303a49b9f1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 65, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021539087,\"id\":\"547d5ad5-52be-4e6f-8ad6-3ecec3f56dc2\"}" + }, + { + "id": "89e3d685-3766-474d-b4cf-6a04a72ae391", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 66, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021542327,\"id\":\"f3853dfd-0b4b-47ae-915b-69774aa591ef\"}" + }, + { + "id": "27922c7b-2233-4cd3-ada8-07e293f7f3c9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 67, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021557183,\"id\":\"d2fed3b9-2264-4e98-82e8-463d6515ec6b\"}" + }, + { + "id": "7fbf7844-7e86-4406-8cc1-60e64ee40823", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 68, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021582802,\"id\":\"0bd5de75-a85e-49d3-8da9-3a7ddb4fcf5a\"}" + }, + { + "id": "bc95950f-0288-47c3-b314-666cb96a7c3e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 69, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021604683,\"id\":\"9d380f58-920d-46c7-9dfe-38bb44bfe16b\"}" + }, + { + "id": "1917eb62-0367-49e3-9911-a110cc019266", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 70, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021607412,\"id\":\"29ac02be-78bc-4a3b-9118-9d77a3fd98f3\"}" + }, + { + "id": "63630d8f-1cc7-450e-91c6-069b2041e2f8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 71, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021667154,\"id\":\"d49e8255-291c-4d35-9827-e99b88300e37\"}" + }, + { + "id": "dfecc369-2476-4515-be0b-c72dbda8ad49", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 72, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021667830,\"id\":\"339c6e2f-9bd2-48d7-b4a7-65d0f0ebd6a2\"}" + }, + { + "id": "dff59c93-8672-4cd5-8b60-e53b34784e7e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 73, + "event_data": "{\"code\":\"undo\",\"id\":\"58023077-ead6-4b8b-b0e7-7f128c007366\"}" + }, + { + "id": "4b25c106-866e-4a1b-9628-634858f5c303", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 74, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021685221,\"id\":\"b160734f-7cde-431b-8239-c955698cc9aa\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":186.0},\"position\":\"3B\"}],\"playResult\":\"single\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021685221,\"id\":\"318a2832-b814-4a50-95de-58fa8650d498\"}],\"code\":\"transaction\",\"id\":\"67d1dabe-9d7c-46d9-9bd9-da1d8207946b\"}" + }, + { + "id": "d3f00726-2fdd-4d56-8b1f-47e0931a2bdc", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 75, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playFlavor\":\"on_the_throw\",\"playType\":\"advanced_on_last_play\",\"runnerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021691306,\"id\":\"6ab1353f-01ca-48f7-9e23-a809b9026adf\"}" + }, + { + "id": "19255660-6d6c-4469-b02d-abdd382a8168", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 76, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playFlavor\":\"on_the_throw\",\"playType\":\"advanced_on_last_play\",\"runnerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021693613,\"id\":\"c0d731b5-e9ec-4a76-96dd-8b1ca29c21f4\"}" + }, + { + "id": "d3feb890-90f6-4477-b8b0-ad238ada8f0f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 77, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021697010,\"id\":\"061c4f8d-f3f8-4791-a0b1-815e7601501e\"}" + }, + { + "id": "083a94d6-95ad-4526-b865-19ed886b5152", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 78, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021697321,\"id\":\"fc9cfa66-30a6-4cdc-8a49-a532a8bf6ae9\"}" + }, + { + "id": "cc3c1304-51a9-42d1-b6ae-e081c048abc5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 79, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021697768,\"id\":\"5aa54646-bcdb-40e0-9b89-599f2dd326f7\"}" + }, + { + "id": "e55c2a0a-99fd-4f8f-9ce3-08c20ef0de40", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 80, + "event_data": "{\"code\":\"undo\",\"id\":\"f3cb2d03-d863-4a78-a648-0de8a1af8415\"}" + }, + { + "id": "3ddf41f4-8017-4b3b-bb81-9bc571ebe548", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 81, + "event_data": "{\"code\":\"undo\",\"id\":\"98b1e433-4e08-43c6-9c0c-1a686efbc8b8\"}" + }, + { + "id": "aa4ac351-9b73-49de-b8fd-ddbe0609441a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 82, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021716744,\"id\":\"0feca76f-0f1e-4f90-8e41-b643ccca1b52\"}" + }, + { + "id": "9bc1d2e9-8e94-43ea-9ce5-d114816794c1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 83, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021718500,\"id\":\"b5e41142-7197-40b4-acf9-3107aa9a591a\"}" + }, + { + "id": "3f780dd0-6fc6-4f4b-b12e-54529816bc0f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 84, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021719343,\"id\":\"42f12fc2-2d8f-4217-82c1-9147284e2db6\"}" + }, + { + "id": "c23a63eb-b614-4d8b-bf4e-9df221a4871e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 85, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021723664,\"id\":\"418ead54-6b53-4f93-96e7-5cd233009062\"}" + }, + { + "id": "d4158877-5123-4399-a878-db24303eddfb", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 86, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021726197,\"id\":\"85a718df-3ce0-4620-9171-4c4074beedb5\"}" + }, + { + "id": "7308a612-7369-45f3-8c14-0f8058ae09e5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 87, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021729634,\"id\":\"b0b251dc-6b22-4011-ad58-132828182a50\"}" + }, + { + "id": "ce07c306-a435-4cb3-bca1-1f02eaa2506f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 88, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021757508,\"id\":\"16ba8081-7c3b-4d97-8805-005b198085bc\"}" + }, + { + "id": "349db981-b9a1-4bd6-9b21-da8466b1d3c1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 89, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021776217,\"id\":\"6f1f6b84-dbfe-4c00-804c-813c861f8849\"}" + }, + { + "id": "adc0874b-adae-45cc-8737-ba4e6ecf504b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 90, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021789330,\"id\":\"6309fe0e-0d4c-4c90-8e13-9b3b03c43e74\"}" + }, + { + "id": "ce39d71b-14b7-4fe4-905a-72c788132f6d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 91, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021809333,\"id\":\"1154ea14-ee62-4e54-b70a-9ad44d23bbf9\"}" + }, + { + "id": "4bc86e8d-d56c-4f5e-983d-a23dfb0f73ae", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 92, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021827371,\"id\":\"bbaf7ac2-37a3-43bf-a59f-60e719efba64\"}" + }, + { + "id": "d4b3ee93-739a-479e-bdb2-9d4137fe4169", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 93, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021846758,\"id\":\"a94a1878-d500-44ae-a027-aea38d859a9a\"}" + }, + { + "id": "127c2483-9734-4f3e-a53b-fba60edec830", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 94, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021871017,\"id\":\"90478964-0c9b-4f0f-8431-138b142d45d9\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":186.0},\"position\":\"3B\"}],\"playResult\":\"batter_out\",\"playType\":\"pop_fly\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021871017,\"id\":\"713c28cd-b6b7-43b1-b0d6-43151a17d65a\"}],\"code\":\"transaction\",\"id\":\"077978b1-3a88-417a-8b34-bcf134572658\"}" + }, + { + "id": "02296389-369f-415a-bc56-ee0ba1e5d057", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 95, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\",\"position\":\"3B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021911529,\"id\":\"3e87db23-770f-40b0-a2f1-3cf38aa9bf77\"}" + }, + { + "id": "b454d930-3722-46ca-bf1e-892c9ec20b03", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 96, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021917304,\"id\":\"78d60371-b402-4b3f-ab0e-0ad0e6ea1cfb\"}" + }, + { + "id": "7c5724e6-b066-46d6-a2d0-aec29429b2e6", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 97, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"f1cec113-0970-43dc-9c06-a6c453227f13\",\"position\":\"2B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021930973,\"id\":\"54fccf66-79d8-43b4-9b41-5addbe4c8b20\"}" + }, + { + "id": "1fba3b97-d719-466d-85d6-fffc3db1216e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 98, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\",\"position\":\"RF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021937663,\"id\":\"5ffe732a-49a1-4717-8813-1fd8418eaae0\"}" + }, + { + "id": "4f6772b5-4167-4190-863d-facf78ee96e7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 99, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\",\"position\":\"CF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021949265,\"id\":\"0cbee578-986b-43e1-9ffe-7005a5146eb5\"}" + }, + { + "id": "3f1f55be-1e51-4a19-83e2-499519013250", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 100, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"d4a1ceca-45a9-4f00-9ec0-ba04a406f0c2\",\"position\":\"LF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021955674,\"id\":\"ac1f94b8-c356-4e7c-a0cf-dc9fe4e04360\"}" + }, + { + "id": "e6f8f8a0-4409-4d1d-846a-74e1a145cc37", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 101, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776021999670,\"id\":\"eb53f395-03c6-465a-ab53-bc8ae914f2f7\"}" + }, + { + "id": "44f6ce5f-8f3d-4330-9c2c-3cd5a2bb759f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 102, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022011327,\"id\":\"375ca345-5aab-4a04-a56b-7eb9c68cc1af\"}" + }, + { + "id": "9f17a17a-af03-4b60-baa6-682bcce2345f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 103, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022029498,\"id\":\"0a9d22f2-c595-4ce4-b5f3-919a6f4603ca\"}" + }, + { + "id": "64e1ba14-4955-48da-bca6-038683ecdf4b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 104, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022042348,\"id\":\"8e3f2844-5df4-4d14-8696-74ceb1419687\"}" + }, + { + "id": "edf5af0d-05cd-402e-910e-9efe1bce81b3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 105, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022055116,\"id\":\"dd59baf5-bb47-4430-a130-8351caeb7b49\"}" + }, + { + "id": "b46b117f-e83b-4e6e-b14d-9e244ea1d7cc", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 106, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022071604,\"id\":\"ef6f1a5f-f7b8-49bd-9bf6-9a9cf4fa249b\"}" + }, + { + "id": "1948979e-7b0e-4087-9b83-d34c74697d33", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 107, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022098501,\"id\":\"03d065bb-30d9-4b4f-b374-87ef5becbde5\"}" + }, + { + "id": "95cda979-78a9-4364-bad7-82520aac11f7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 108, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022100929,\"id\":\"ed3f9656-01bf-43bc-ac62-430338d6ad54\"}" + }, + { + "id": "82f07f45-f9cc-47f6-830b-7b4d57b70012", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 109, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022107574,\"id\":\"374dea7c-8d50-4ce7-a5a3-407fea421b41\"}" + }, + { + "id": "40f99efd-5de3-4dc7-baa4-725c3f766bb0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 110, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022119761,\"id\":\"2151337a-f0a1-47d1-861e-18cedb8992ed\"}" + }, + { + "id": "3510c663-37b7-4176-9a07-ed204df45f2f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 111, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022131145,\"id\":\"b5b37df0-5337-4557-ac6a-2b1e270fda87\"}" + }, + { + "id": "674b50b6-ff0e-4143-b2a0-41ff5b6d1043", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 112, + "event_data": "{\"code\":\"undo\",\"id\":\"321290d1-afbb-4d66-995c-cbf168e9864f\"}" + }, + { + "id": "729ec800-cb00-41b4-a59a-7c6676f5c12d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 113, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022150095,\"id\":\"faa26a7f-1aad-4a93-a74e-a5c0a13d5f8a\"}" + }, + { + "id": "a7e874a6-9898-42e4-8c7b-7f7b7dae5b7c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 114, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022174741,\"id\":\"e19ab832-d91d-449f-bac5-ca67a400ce59\"}" + }, + { + "id": "d785cb74-1ee1-4900-9357-0e0b673561ea", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 115, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022204324,\"id\":\"fbecadaa-d43f-4ee5-a5be-c49425bd2560\"}" + }, + { + "id": "41c14bec-263a-4aa4-9fe6-a1f5577bd1a3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 116, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022236699,\"id\":\"97605393-fc0d-494b-a93a-270daf5f676e\"}" + }, + { + "id": "796b9f9f-70c2-4731-9536-e212ac5c21a3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 117, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022247649,\"id\":\"1b03166b-f70e-4887-a91f-cbf9c8c988b9\"}" + }, + { + "id": "023d34d1-e604-4137-a606-d13c067f47c9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 118, + "event_data": "{\"events\":[{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"index\":0},\"code\":\"goto_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022273820,\"id\":\"1e69fd83-c1d9-427c-82db-a1017b4af80f\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\"},\"code\":\"confirm_end_of_lineup\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022273820,\"id\":\"0ecf0fce-192c-40cb-b1f7-20210e3fd05a\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"ab1f123e-ac0b-4071-af18-b90ddb3c635d\"}" + }, + { + "id": "8faa484b-fc25-4326-abec-6b7b16dbb490", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 119, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"a119857c-54cd-4ad0-86da-2fa65514dcfb\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022277328,\"id\":\"67fc0291-d9f0-498f-9590-677e31c09e49\"}" + }, + { + "id": "f87bc471-3ae6-42eb-9505-d41967cf446d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 120, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022288252,\"id\":\"e92b836a-abc2-4784-be13-ab96a43daa65\"}" + }, + { + "id": "c4de75d8-b4e0-4858-ae1d-1851ac67d176", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 121, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022316893,\"id\":\"bdae82ad-3f00-49f0-ada2-777f9b284617\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":96.0},\"position\":\"LF\"}],\"playResult\":\"double\",\"playType\":\"line_drive\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022316894,\"id\":\"2e8dc690-01b7-413a-b6ff-81d44f724fbd\"}],\"code\":\"transaction\",\"id\":\"6382dc4b-179f-49e1-9b45-3cf5c3bcafb5\"}" + }, + { + "id": "848d5e68-ed7a-451c-a028-837884bd8bc6", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 122, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022355734,\"id\":\"6ecf1ece-2066-4f74-86e1-878d74162df9\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":107.0,\"y\":139.0},\"position\":\"SS\"}],\"playResult\":\"single\",\"playType\":\"hard_ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022355734,\"id\":\"125a6e93-ad5d-4531-85f3-5e709d29c7d0\"}],\"code\":\"transaction\",\"id\":\"6b5f990c-d221-4799-a77c-00e824aaba2a\"}" + }, + { + "id": "4ea5f94a-6240-4f9c-9dde-62659e127feb", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 123, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"advanced_on_last_play\",\"runnerId\":\"b04f45dc-73bb-439f-a3c1-da6f6c0a2907\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022367101,\"id\":\"dc349dd9-8e15-4a08-bf0c-60b2f27e3d1e\"}" + }, + { + "id": "72627a71-d3f9-4870-8d7d-8270b5f92750", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 124, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playFlavor\":\"on_the_throw\",\"playType\":\"advanced_on_last_play\",\"runnerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022448105,\"id\":\"a6fa6b26-e8d1-4f83-afdb-472d3bcb29a5\"}" + }, + { + "id": "69658ca4-77e9-4988-b0d9-71183232210d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 125, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022491626,\"id\":\"9dd1ee3b-12af-490b-b5e8-95a4b953fed8\"}" + }, + { + "id": "a242e295-4dd1-44be-be6d-e2cefc3cfd0c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 126, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022493954,\"id\":\"9aac194e-bcbe-42f8-8248-ecdbe14905b5\"}" + }, + { + "id": "937734bc-14aa-42a0-825e-ebff520d5599", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 127, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022500954,\"id\":\"70eb6ed7-3ab2-475c-a2bf-9ff3fcf25a3a\"}" + }, + { + "id": "a2f741a0-66a8-4a2c-b92a-92f7d13d9291", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 128, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022513829,\"id\":\"474ade41-9378-4a16-9869-8e59fc14b53b\"}" + }, + { + "id": "99768360-4265-407f-b43b-ed3b62fb5f44", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 129, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022525501,\"id\":\"68ace873-2fcf-4acb-9cb9-80a3da94fad8\"}" + }, + { + "id": "cbd88835-4e0b-411a-8af9-bca27731b71b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 130, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022545472,\"id\":\"4696521f-57cc-4d4b-a7d5-acf5966b1a09\"}" + }, + { + "id": "1923569e-4508-4329-9549-340d57176792", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 131, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022562533,\"id\":\"80019c61-302a-4d5a-b925-1ad243328ba5\"}" + }, + { + "id": "ff4a4a62-2df6-4892-972d-6ce183e97f55", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 132, + "event_data": "{\"events\":[{\"beforeId\":\"9aac194e-bcbe-42f8-8248-ecdbe14905b5\",\"events\":[{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"d4a1ceca-45a9-4f00-9ec0-ba04a406f0c2\",\"position\":\"P\"},\"code\":\"fill_position\",\"createdAt\":1776022493954,\"id\":\"92254883-eab6-454a-b1ed-9e0b369a42b9\"}],\"code\":\"insert\",\"id\":\"a80842b2-11dd-4495-9bb4-38248b98bec3\"}],\"code\":\"edit_group\",\"id\":\"978d8ec3-4d63-4137-9856-f48f812a9a6c\"}" + }, + { + "id": "1ef6c9c9-10a6-4e60-9c19-d42130d0ba79", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 133, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022716252,\"id\":\"10f8cada-01e3-4fd7-8b3c-fc6ef7f6a1b4\"}" + }, + { + "id": "a29cdbe7-6437-469a-9c05-4091d6bc18af", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 134, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022716777,\"id\":\"608e851b-763b-447c-998c-0a9d94bd426e\"}" + }, + { + "id": "2c7e044a-e583-4a66-b14c-00dd69534ab0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 135, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022727354,\"id\":\"2a1bb75a-503d-4a9c-8ebd-4426ee762f25\"}" + }, + { + "id": "13a64b32-d815-4d62-af0d-1cf1d2625c08", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 136, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022739623,\"id\":\"084e04c5-b9fb-4fb6-9d13-97c917cc143b\"}" + }, + { + "id": "4537f4e0-9cff-4c31-9df4-10f83fa1031d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 137, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022752724,\"id\":\"f34de268-036d-41e3-b27c-7ad3e94db457\"}" + }, + { + "id": "8fb50c0d-e581-4dc8-a021-881dcbcf925b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 138, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022772518,\"id\":\"8a75799a-135f-49c3-a78b-6c999d52b4a3\"}" + }, + { + "id": "28d4a9d3-7702-4e02-8c7b-70059c12e693", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 139, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022790045,\"id\":\"a512ad48-c2dc-4dfe-90b7-dcc0f472942f\"}" + }, + { + "id": "d391d253-72ad-444d-9765-cd0f1dafbf0c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 140, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022806631,\"id\":\"feb20cfa-b8fc-4784-8664-7e9984929f59\"}" + }, + { + "id": "fe95c6bb-699d-47f8-bc3b-c9e4ecc73798", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 141, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022820285,\"id\":\"cd3eb1d7-d8a6-4435-a755-9c7fb90df8ad\"}" + }, + { + "id": "6940ed98-1214-40fc-bd2d-b234b80ba228", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 142, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022842714,\"id\":\"82af8db0-c567-4522-8ec8-938af2612102\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":186.0},\"position\":\"3B\"}],\"playResult\":\"single\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022842714,\"id\":\"4bbae555-7e91-464e-b102-8090807a90c7\"}],\"code\":\"transaction\",\"id\":\"5dbc9c07-1f29-40fe-97be-2949445e20e8\"}" + }, + { + "id": "f4eeac5c-c0ad-4588-91d0-d4ee2cef5599", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 143, + "event_data": "{\"code\":\"undo\",\"id\":\"134dde20-7eca-44e4-a9e7-d95994a649b2\"}" + }, + { + "id": "3e71e989-71e1-4883-867d-0a177c46792c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 144, + "event_data": "{\"code\":\"undo\",\"id\":\"be7958d7-a1e3-443b-99f1-09f27dbbf85c\"}" + }, + { + "id": "639b2121-f622-422b-b21b-aa924464d340", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 145, + "event_data": "{\"events\":[{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"index\":10},\"code\":\"clear_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022887888,\"id\":\"0a7b921a-1b47-4343-93d9-8cd7d9667724\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"f2ec6562-8383-4235-bfe3-f95faf4c0a8a\"},\"code\":\"clear_position_by_id\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022887888,\"id\":\"a855fe90-879f-4adf-a5d9-4b923215d005\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"03859a45-c786-4e15-aac5-36e14b06ea16\"}" + }, + { + "id": "990674b7-c58e-4f7b-9208-8cf3f5185b21", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 146, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"index\":10},\"code\":\"squash_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022890283,\"id\":\"42910e14-82c1-4f95-869b-ec0d7994560c\"}" + }, + { + "id": "37fd3068-472b-41a0-9540-9d83c953a4b1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 147, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022894780,\"id\":\"576bea31-4bc5-4264-bd30-8afae71d8709\"}" + }, + { + "id": "6d91522d-52fa-4dbf-8b0b-529a616cb804", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 148, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022896320,\"id\":\"a1129e52-a7e8-4cd4-93d9-0782a68c39fa\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":186.0},\"position\":\"3B\"}],\"playResult\":\"single\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022896320,\"id\":\"0ea2a4e8-ffaf-47e0-8000-03719687e1e6\"}],\"code\":\"transaction\",\"id\":\"3353491c-090f-433f-83a4-29a5a60f6f21\"}" + }, + { + "id": "e853a804-5802-420d-b8f4-1e5971a662c5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 149, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022933236,\"id\":\"1908e118-c5fe-497c-8054-a4ed028d0018\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":212.0,\"y\":139.0},\"position\":\"2B\"}],\"playResult\":\"fielders_choice\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022933236,\"id\":\"ab31c12b-efdc-4763-ac71-b03526448b1a\"},{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"out_on_last_play\",\"runnerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\"},\"code\":\"base_running\",\"id\":\"7b8dad37-5c60-45e7-bd90-54a538409506\"}],\"code\":\"transaction\",\"id\":\"d9a19fde-9bc6-4b32-8f55-8a296eb37e6b\"}" + }, + { + "id": "a0fb402b-d768-4afb-a10d-c9e952e4b971", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 150, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022989416,\"id\":\"6646a246-d5aa-45d5-b7eb-75ec72027e32\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":160.0,\"y\":82.0},\"position\":\"CF\"}],\"playResult\":\"single\",\"playType\":\"fly_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776022989416,\"id\":\"c54c92fb-1928-4902-96f4-f823de8868ed\"}],\"code\":\"transaction\",\"id\":\"bb12c76f-49f8-4f95-980b-8cb093becf6d\"}" + }, + { + "id": "b2b87768-c89e-4eb0-8a3d-80f2e18eacc3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 151, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023003900,\"id\":\"e7b7139d-0600-4a5f-92e1-d2784a69e689\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":160.0,\"y\":82.0},\"position\":\"CF\"}],\"hrLocation\":\"in_the_park\",\"playResult\":\"home_run\",\"playType\":\"fly_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023003900,\"id\":\"cfb89540-0fec-4843-9912-605d9969a32a\"}],\"code\":\"transaction\",\"id\":\"536d65c3-30d8-4ffc-bcf4-e66e99c1cb0c\"}" + }, + { + "id": "29486a01-52d9-4e76-8410-18cf6618538c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 152, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023027590,\"id\":\"c1a60648-91c6-40e9-8882-782d3f9f9769\"}" + }, + { + "id": "2e78b4d6-d5c4-4a14-91ac-a2b693df1983", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 153, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023027796,\"id\":\"32c4e6fe-5ae8-41ef-bb39-1f09c5fa63d3\"}" + }, + { + "id": "40ab6b90-1551-4d7b-a92d-07649d4c7ba2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 154, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023030521,\"id\":\"8f4c0938-e2cd-4324-8457-da48be1f9217\"}" + }, + { + "id": "8e922306-81e7-4956-8f1e-867dbcc1f185", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 155, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023030778,\"id\":\"1c34e744-8322-4f7e-863f-68eaac680bc7\"}" + }, + { + "id": "9ad0c6e6-3fd3-4e8a-9402-81d580995f2b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 156, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023031382,\"id\":\"590f8bd9-4547-447c-8d1c-8a6108c4176e\"}" + }, + { + "id": "d5afb45c-4531-4ee7-ab32-205e9b92820a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 157, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023095849,\"id\":\"ac82f1b7-d7a3-4d1e-80ec-014d64313616\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":96.0},\"position\":\"LF\"}],\"playResult\":\"triple\",\"playType\":\"line_drive\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023095849,\"id\":\"a8cf14fb-f890-4344-8b9c-788e9cec1773\"}],\"code\":\"transaction\",\"id\":\"ab6b371f-d3ca-4229-8cce-06fc187c321d\"}" + }, + { + "id": "ce2353cc-f630-44c2-b257-1ead8ded3c09", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 158, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023109767,\"id\":\"b5822ab7-5e20-4909-9da1-df3e319ebe6e\"}" + }, + { + "id": "02863035-bb67-4da0-9a2b-03432c2f98e1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 159, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023146766,\"id\":\"e7a60897-c1f4-414e-80e6-b7dca09984da\"}" + }, + { + "id": "9b2044ca-c314-4a1d-b863-a05e5b482cb0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 160, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023148711,\"id\":\"36815eb6-4545-4083-abdc-e38b49efc138\"}" + }, + { + "id": "c6f88422-a256-4758-aa65-0aacc8b19a92", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 161, + "event_data": "{\"code\":\"end_half\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023256399,\"id\":\"dcd289da-621e-4274-862a-7551b43d3d9e\"}" + }, + { + "id": "3c4fa396-2466-44f2-87c4-4f03f8ff2829", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 162, + "event_data": "{\"events\":[{\"beforeId\":\"10f8cada-01e3-4fd7-8b3c-fc6ef7f6a1b4\",\"events\":[{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"aeda7933-db71-445d-b2d2-f0d895dbfc37\",\"position\":\"P\"},\"code\":\"fill_position\",\"createdAt\":1776022716252,\"id\":\"5164be9d-5b87-4e58-b92e-aea1da2aa9bf\"}],\"code\":\"insert\",\"id\":\"d8c785fb-f076-43ab-9409-d97e74ab9dad\"}],\"code\":\"edit_group\",\"id\":\"3940451a-c7b4-4570-a7ed-42813c8a705c\"}" + }, + { + "id": "f0344ffb-6421-4355-a5f9-b9e321a47a1c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 163, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\",\"position\":\"3B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023286866,\"id\":\"2eed9942-3946-4896-bcdd-bd73494d6479\"}" + }, + { + "id": "f0321f41-1766-458d-b939-a92d9082b90a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 164, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023290642,\"id\":\"db216b2a-e6d5-4815-b429-60a8a43d3487\"}" + }, + { + "id": "3a3a3a6a-2fb9-4afc-9ca2-24f7db201991", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 165, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023294467,\"id\":\"05e1a4f4-99b8-41c3-bca7-8211c1d90d35\"}" + }, + { + "id": "d8825770-6992-432e-a993-9dba10d5da8c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 166, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\",\"position\":\"2B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023314405,\"id\":\"045abfde-4679-4771-8dae-227693c0c17d\"}" + }, + { + "id": "f925c0a5-be63-4507-9393-7b4a15f40f91", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 167, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023316303,\"id\":\"45ad97c6-83aa-4e9b-9a97-2b9c8260a63e\"}" + }, + { + "id": "e26bcda9-c4e9-4540-8d1f-4e012dedc80b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 168, + "event_data": "{\"code\":\"undo\",\"id\":\"e8965ba4-0d58-4109-ac70-daaac637677a\"}" + }, + { + "id": "5adb9340-f4d0-4c84-b7a4-5009d7b23a70", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 169, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023319324,\"id\":\"5b3bf6d9-6fb9-4368-8b27-3d427d5c0dc1\"}" + }, + { + "id": "00d460b7-d4ee-4080-8c16-51911527122a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 170, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023320956,\"id\":\"e6774095-5503-4552-a1b1-ef0802650be7\"}" + }, + { + "id": "de5ec763-c42c-407a-a392-fb9e284dbf16", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 171, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\",\"position\":\"RF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023329305,\"id\":\"4d2e7b44-d81b-477d-9116-e30aaecc6d99\"}" + }, + { + "id": "d321f99b-51da-496c-b2e2-73119ff275f5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 172, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\",\"position\":\"LF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023331051,\"id\":\"17d28d49-7b04-4f24-a00f-566ce9120723\"}" + }, + { + "id": "2aa5d0c3-cd27-43de-b9ff-658b10cfc232", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 173, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"931eb801-3e5e-43fb-af0c-690e4bfa58c6\",\"position\":\"1B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023340664,\"id\":\"9df0ec7c-7efb-45da-8ac8-50c94a3a4e80\"}" + }, + { + "id": "093f598b-07a6-40d4-834d-956eab5d599b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 174, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\",\"position\":\"C\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023348962,\"id\":\"a7b6b5f3-f7a6-49f4-9ba1-81a85f0c1d30\"}" + }, + { + "id": "ec7cbe0e-a290-46cc-aeee-a01384b4dc46", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 175, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023362533,\"id\":\"d660bb44-5304-42a6-ada6-f5d962c91a08\"}" + }, + { + "id": "a2ca200b-b3a4-4db3-8813-179adba71bc8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 176, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023368978,\"id\":\"17b4d009-db63-4380-81ae-3bc7303b1c88\"}" + }, + { + "id": "ccb30ff4-fef2-4584-a7cf-9cf8ee884044", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 177, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023375946,\"id\":\"a6683c96-5dc5-4b2f-b76b-df249265642d\"}" + }, + { + "id": "73cb98a7-854d-413c-8f5a-54cbf4ff4c75", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 178, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023412740,\"id\":\"55368dc5-79f0-40d2-ae2a-a59f12096a5d\"}" + }, + { + "id": "74f67431-f22b-4c55-b1a1-6fa0e20fbe72", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 179, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"aeda7933-db71-445d-b2d2-f0d895dbfc37\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023415156,\"id\":\"3153ebe4-3990-47c5-ac10-bf9952ec7a75\"}" + }, + { + "id": "2fca5721-1d95-4079-96a5-68ff1faf2f53", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 180, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023447839,\"id\":\"5c002cf8-0b13-4512-a447-ce575e6312e2\"}" + }, + { + "id": "06c1a89d-3dfa-47fd-9a61-6896d892613a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 181, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"aeda7933-db71-445d-b2d2-f0d895dbfc37\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023450837,\"id\":\"ed3eb1ba-36c5-4a6e-8c88-3a6f60e75026\"}" + }, + { + "id": "29f70244-edd1-43d4-b9cc-c284a0dbf8d3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 182, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023462555,\"id\":\"624aa99c-b0b6-4d09-bc77-650b3385639e\"}" + }, + { + "id": "7a7a0515-bb2d-4644-b824-19d6cbe8e989", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 183, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023482952,\"id\":\"0f2921f7-8add-44a7-8aa8-cf7e15c728a9\"}" + }, + { + "id": "f4d6079d-75cd-4a16-85b7-5288ff83bbd9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 184, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023487093,\"id\":\"1a6203bd-4c2e-40fe-8ec1-b1c0d1f1a2f2\"}" + }, + { + "id": "aec37a9a-2415-4656-a231-67f8e1adf570", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 185, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023507678,\"id\":\"6f2d70df-8a15-4350-90e0-4cc97aed43dd\"}" + }, + { + "id": "2a6c9899-bfcd-46d3-b34d-904af47fb601", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 186, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b0cd8bc9-7c82-4929-8630-78f6584acdd7\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023510775,\"id\":\"64af4307-1785-4bc9-b4d1-80ab41d61e64\"}" + }, + { + "id": "33cd0ef1-dbfb-4f69-a3f5-22da05642d6d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 187, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023552613,\"id\":\"2bb9ea6d-7e62-4a91-87a0-fec698833304\"}" + }, + { + "id": "0d3c2644-1d7e-401a-ac59-3fba8e048be8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 188, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023573163,\"id\":\"ac87f513-04ad-4813-ad8f-efebd6963e28\"}" + }, + { + "id": "15188d8c-1c59-4cf2-9eb4-1d9ad1376381", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 189, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023584727,\"id\":\"a2956e35-a47b-4aa4-aadb-a574ad61cd16\"}" + }, + { + "id": "016ff986-8a20-4071-b646-44422727ae56", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 190, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023599155,\"id\":\"d26e0203-bb59-4b55-8712-c7bdf5dc0827\"}" + }, + { + "id": "fdbcb229-60fc-4fba-9799-ee2ceb21ee6c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 191, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023613657,\"id\":\"9245ae3a-18aa-4474-b9e3-c9bdb486115d\"}" + }, + { + "id": "ae4f94d9-d461-44a5-afb9-aad0dfc83e40", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 192, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023724554,\"id\":\"7378cf2e-52aa-42a7-9966-5f4da7486ef5\"}" + }, + { + "id": "4d23a1ec-0881-49ea-a777-956f6fd0ec17", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 193, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":false,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023752340,\"id\":\"d370357d-1238-4ca1-bd94-b1acb5ceafb9\"},{\"attributes\":{\"reason\":\"catcher_interference\",\"intentional\":false},\"code\":\"end_at_bat\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023752340,\"id\":\"e5430c98-d430-4b02-92ce-a9f5d1417c3b\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"761f90a1-7655-4957-97bd-f3841eeb5f46\"}" + }, + { + "id": "ba3ded40-7194-4274-bd66-e22aaac376de", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 194, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023797100,\"id\":\"4bc1a2d8-cfed-4a7e-a07c-9ad6db7763eb\"}" + }, + { + "id": "8f47e485-a9c4-45d3-bd5d-ac33479ca2a9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 195, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"b0cd8bc9-7c82-4929-8630-78f6584acdd7\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023806182,\"id\":\"7cb7f9bf-a462-4fbf-aa47-aa5c7591018c\"}" + }, + { + "id": "cae04362-c8d0-4316-939b-d5e6a979834a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 196, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"19e051fb-858e-473f-8f77-94e826de3ee8\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023807880,\"id\":\"465784f2-5526-4d18-9643-ab540299815f\"}" + }, + { + "id": "70286819-3712-433d-aae9-03c28254896e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 197, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"8b65024e-947e-42a1-8686-a8ce98a52941\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023809650,\"id\":\"c271b653-ebc8-41b7-9585-342ba18a1204\"}" + }, + { + "id": "33734286-e706-447a-bbd3-4eeb96fe1a7b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 198, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023815167,\"id\":\"68ab9a40-6a29-46cf-9d51-5d5e6021cc73\"}" + }, + { + "id": "9eb0b5c2-bfe5-40ac-9e33-ff20235ffedd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 199, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023829656,\"id\":\"18d36f20-17b7-4fa9-9678-2dd336a4944d\"}" + }, + { + "id": "ac020c24-61d9-4384-8b81-552d819c1cdd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 200, + "event_data": "{\"code\":\"undo\",\"id\":\"0ac2a1b3-7483-4734-b9bb-f1b3faff0300\"}" + }, + { + "id": "fb988f42-3c77-4319-97eb-faacd4756537", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 201, + "event_data": "{\"code\":\"undo\",\"id\":\"2b10641e-0e7a-4a20-aa7f-69d7cdd120fa\"}" + }, + { + "id": "553d6486-1fe5-41dd-b939-2604931fc0f9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 202, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023840763,\"id\":\"f7df440e-be2d-4a6e-87a0-67f7662075bb\"}" + }, + { + "id": "d6a9216f-f66f-4515-8126-0d68cd3474fa", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 203, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023840928,\"id\":\"f0df05ce-3be8-4bea-8e3d-6a4e6b3603d9\"}" + }, + { + "id": "82bf7de0-42e9-488e-b9e7-1f108cffb5e0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 204, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023841851,\"id\":\"1800b884-20cd-468c-bce2-7aaa646359f0\"}" + }, + { + "id": "9825b280-d38b-4a3a-9fc6-e9c680c1db4d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 205, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023859289,\"id\":\"26af6b63-6749-4b0d-998a-13bc61c752e7\"}" + }, + { + "id": "baa0d71f-d4ac-43d4-b645-4b0f3a9a983e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 206, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023881235,\"id\":\"fbf47627-dfb1-4897-8d2c-644d5b3733fb\"}" + }, + { + "id": "b0ff8520-da2c-45e1-8472-ceba97d5bf46", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 207, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023894145,\"id\":\"9c71bc96-0209-4abc-8b42-d0937ad8d75c\"}" + }, + { + "id": "6d939534-cdb1-42da-a608-500c125e0dc7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 208, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":false,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023908696,\"id\":\"b3cc54d0-8f62-4005-8528-e2a088a3e0dc\"},{\"attributes\":{\"reason\":\"hit_by_pitch\",\"intentional\":false},\"code\":\"end_at_bat\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023908696,\"id\":\"7b85baec-a2e9-4107-8c3f-b383b4caa919\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"6b4bc891-7ff9-4ca6-9122-82d0880cd953\"}" + }, + { + "id": "18efa211-9abe-4e66-98e5-678609106436", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 209, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023945649,\"id\":\"bb061b9c-6055-4f80-9fcd-fc08ca862f98\"}" + }, + { + "id": "d386eaf0-d5b7-405b-8522-39a1d47f5dde", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 210, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023970917,\"id\":\"d5c3d41f-18e5-40e4-94f3-0e3bf968db0d\"}" + }, + { + "id": "17786abc-534c-48a6-a944-79107688c0e0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 211, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776023984355,\"id\":\"7fe97a7a-503e-4d40-bf32-cbdb46aa6bf2\"}" + }, + { + "id": "c650d4d5-88f8-44f6-8532-dc5dc5172c55", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 212, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024010308,\"id\":\"8e45fe43-f7d3-43da-8082-c541b43efa65\"}" + }, + { + "id": "ea708947-3f58-42d0-af27-a091a5c6bde6", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 213, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024023405,\"id\":\"9933b0de-7145-41e4-87bf-40d9de6352f7\"}" + }, + { + "id": "aea79b3c-8d22-4367-bb13-667b9996b1ea", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 214, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024046143,\"id\":\"1893102f-f25e-4b34-8585-ac3cfa472a66\"}" + }, + { + "id": "0e03983e-0e32-47a1-aaec-2d8edab72a4c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 215, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024058605,\"id\":\"998ea95e-7d45-4428-87d4-2df12dd1236e\"}" + }, + { + "id": "18c95cde-3fbe-4ee2-b34f-07f8e6aa765c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 216, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024072692,\"id\":\"12e798fa-9756-43af-90ff-25d65582b36d\"}" + }, + { + "id": "9dc1e0a0-0c55-4021-9ec4-bc4892d53b12", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 217, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024095134,\"id\":\"a9af0d50-e382-4fca-b1a0-5b82026d0a99\"}" + }, + { + "id": "01128e7f-9b5c-4292-82de-635a4cb32cb0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 218, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024121438,\"id\":\"0673ed8e-046f-4240-9212-923f518e0250\"}" + }, + { + "id": "66e5d3b5-aa11-4413-9871-42bc8c08b250", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 219, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024144475,\"id\":\"bc52915a-440e-4bea-8bb6-32ba4cf19986\"}" + }, + { + "id": "237b7c9b-4e99-4f8b-9f47-0d671cef1a30", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 220, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024161570,\"id\":\"25326e9d-be67-4ced-b0d5-8d79763f7fbe\"}" + }, + { + "id": "332af4aa-5f10-417c-bc9c-9240fadbb77a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 221, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024178734,\"id\":\"d296d978-64e5-4174-8688-0c3b9fe82cc4\"}" + }, + { + "id": "c6d08ee8-5972-4b3e-8623-ccbbd3629665", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 222, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024192288,\"id\":\"ce85a825-3a04-4e17-a603-5213fba42521\"}" + }, + { + "id": "42f4a6b3-ebf1-4c70-97ec-76adaefd1bd6", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 223, + "event_data": "{\"events\":[{\"beforeId\":\"7378cf2e-52aa-42a7-9966-5f4da7486ef5\",\"events\":[{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\",\"position\":\"P\"},\"code\":\"fill_position\",\"createdAt\":1776023724554,\"id\":\"c499e9bf-0d80-4aa7-9410-2141e96357b5\"}],\"code\":\"insert\",\"id\":\"f27586c8-b7a6-4f55-83f6-d799ac7d4d6c\"}],\"code\":\"edit_group\",\"id\":\"8e7a1eff-9fb8-43d4-8e8b-f216d2aebeb7\"}" + }, + { + "id": "2a22ccf6-41ec-45d3-ad88-a0c8253832ef", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 224, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024352383,\"id\":\"babdeb17-a321-4e3f-be7b-af0cc85700bd\"}" + }, + { + "id": "1881d8b7-30a7-4f30-821e-02bd33451ae1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 225, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024358701,\"id\":\"54796824-791e-4e9e-bbbc-55d0c39e71f3\"}" + }, + { + "id": "72c712d5-2fb0-46b1-9835-6a10114141e1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 226, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024412993,\"id\":\"ff2d5744-620f-4af2-b5d7-cee3f7bdb102\"}" + }, + { + "id": "de972845-d5bb-46ce-84bf-825890cf63a4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 227, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024413799,\"id\":\"a21ca860-4b5e-4ff5-a3db-df7ff23af796\"}" + }, + { + "id": "832ab900-cb92-486f-a948-c7e54328e5b4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 228, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024423539,\"id\":\"06af68a9-80f4-4d72-aa72-f71d0cf6e40d\"}" + }, + { + "id": "1e585ed3-312d-4508-b18c-b018b28b31e7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 229, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024478653,\"id\":\"5333f7f4-8ee4-46c5-934c-bae9596c58aa\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":52.0,\"y\":96.0},\"position\":\"LF\"}],\"playResult\":\"double\",\"playType\":\"line_drive\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024478653,\"id\":\"9e391c8d-c4cd-4f25-b508-e36b31f810b5\"}],\"code\":\"transaction\",\"id\":\"289a8077-05e3-4d3e-872d-3f017c79c327\"}" + }, + { + "id": "c9e1ffca-9833-4923-b7fc-9fb7dbe82d4f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 230, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024505468,\"id\":\"a941739d-ff68-4bac-b71a-89f4f6c1140f\"}" + }, + { + "id": "a2310d3b-b0f8-48b5-b488-df8b1914e5cd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 231, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024517226,\"id\":\"f1f0439a-1ad6-438f-881c-6d73a9f471d4\"}" + }, + { + "id": "eebbca37-1f11-4678-87b1-d3be593aa9fe", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 232, + "event_data": "{\"code\":\"undo\",\"id\":\"2a61463f-80b7-4cc0-8584-f4edcd570129\"}" + }, + { + "id": "78606c6c-2dfc-4bef-8ba6-c250bf8835c2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 233, + "event_data": "{\"code\":\"undo\",\"id\":\"cebd0d9b-317e-4238-b65e-7ac0c64d5c87\"}" + }, + { + "id": "a0163f77-9801-44fa-bb60-d1181d567b49", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 234, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024523033,\"id\":\"f060f9ed-126b-44f9-9b18-a98f5c067712\"}" + }, + { + "id": "adc8b946-29de-4f17-9bce-1fd0aa1cf4c9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 235, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024530206,\"id\":\"ead7dd7b-f85f-47a0-a81a-2ea4977ef92f\"}" + }, + { + "id": "d659b1ab-575e-4b4e-875b-f1cf7ac9f7a9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 236, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024531442,\"id\":\"b0271f70-a8fb-4926-aac3-58cf7bf6b47e\"}" + }, + { + "id": "e75cf64a-f80e-4883-8207-28e133bb9280", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 237, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024534497,\"id\":\"de6c87e6-2dfb-4df7-94a4-22dcbe69b034\"}" + }, + { + "id": "1dfd551f-54f3-4886-a0e9-3f6e403f2cdc", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 238, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024540222,\"id\":\"38924866-68f4-4e47-a048-e2e58bbbce24\"}" + }, + { + "id": "736ede4d-f085-44d6-960e-753e1e52ae7e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 239, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024540748,\"id\":\"506b37e4-a203-4403-b880-27bc767ba454\"}" + }, + { + "id": "05f8d286-75dc-417c-9a36-d54cad354480", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 240, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024550757,\"id\":\"4b08e41f-aa65-48e2-8578-3df632fe1dd1\"}" + }, + { + "id": "e30d1893-e058-444c-be63-1a5bfcebf16a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 241, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024577736,\"id\":\"a1ab983c-c83b-40e9-8d0f-4410e978c49e\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":107.0,\"y\":139.0},\"position\":\"SS\"},{\"error\":false,\"location\":{\"x\":263.0,\"y\":186.0},\"position\":\"1B\"}],\"playResult\":\"batter_out_advance_runners\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024577736,\"id\":\"b55d3528-ae18-4474-8b75-214b699d733c\"}],\"code\":\"transaction\",\"id\":\"a6b0c59b-cf28-4cae-a325-e64b7e4f9e57\"}" + }, + { + "id": "1ec3281e-b998-4c08-b1a1-e18a0fcd4eb8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 242, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024594337,\"id\":\"b841d3e9-e9e7-4bef-be81-af28db1f41b1\"}" + }, + { + "id": "b44d1337-de93-4d46-a3ef-b9824714efd3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 243, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":false,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024633344,\"id\":\"f8be8f2f-f97c-40b8-bdba-caaf20832d7e\"},{\"attributes\":{\"reason\":\"hit_by_pitch\",\"intentional\":false},\"code\":\"end_at_bat\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024633344,\"id\":\"91d7c8d9-a918-4805-b4bc-7cf64e3a574b\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"8e575bd0-4310-4833-9b66-213c85fd1263\"}" + }, + { + "id": "111409ad-ecd0-4eda-9023-2bee0c11dfd4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 244, + "event_data": "{\"events\":[{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"index\":0},\"code\":\"goto_lineup_index\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024656651,\"id\":\"34026a8c-592d-473d-ac72-0aef9958e1aa\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\"},\"code\":\"confirm_end_of_lineup\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024656651,\"id\":\"e465fb02-1559-406c-9270-af415d486b0a\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"cb0a0ab1-0b8f-4c11-8848-13d872607301\"}" + }, + { + "id": "a6da8b0f-2a09-456c-8618-7cb21bd69c53", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 245, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024657677,\"id\":\"5fcacc3c-82ba-4123-ba2b-efa8f352f885\"}" + }, + { + "id": "0b37cc7c-8eee-46f0-9bdf-ba7ff9642bb2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 246, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024659736,\"id\":\"7c70531b-a7e2-4bde-8f26-cdc62fc40f86\"}" + }, + { + "id": "0ada2bd6-a4dd-4a51-88bb-5728d3e18ebd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 247, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024661465,\"id\":\"589281ff-96f0-478e-8520-745e3c76d26c\"}" + }, + { + "id": "68350cec-7329-43d4-9fe3-9ed4bcbff872", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 248, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024669863,\"id\":\"01c1fa23-b032-4c12-96a1-f9f66f9c4bc3\"}" + }, + { + "id": "97c089d8-c88a-467b-a33b-a6421d8f180e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 249, + "event_data": "{\"code\":\"undo\",\"id\":\"96bcb538-7f72-4ec2-b7ed-a2544ae2ab90\"}" + }, + { + "id": "9c24a32a-12be-4a59-ad97-72650b8769e0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 250, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024676952,\"id\":\"c0fd29fe-3922-44d6-a573-e9af90c17814\"}" + }, + { + "id": "4cbb3fbb-4f12-4ca1-9ee4-db99f8edc78b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 251, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024687334,\"id\":\"2da18843-156a-4c60-807f-6a34c644503b\"}" + }, + { + "id": "ca5230cf-0efe-465e-9934-78e35b463d4e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 252, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024711453,\"id\":\"fd7fa393-0b9f-427d-83f1-723f823066f1\"}" + }, + { + "id": "2a734268-fafb-4c59-8589-d4cbbc970aa8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 253, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024732842,\"id\":\"6514a32d-0336-45fb-b37b-6b566708d3fd\"}" + }, + { + "id": "4bc996b0-8e26-4c23-8eb7-ecd8beeb5bf2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 254, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024747161,\"id\":\"3dea3a2e-fdcc-4d06-9561-563b0c2be987\"}" + }, + { + "id": "d6fd8f15-fc28-4528-846e-ac392dfd7bde", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 255, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024760880,\"id\":\"96759ee5-97ab-44ac-86b9-d44dc39be053\"}" + }, + { + "id": "a09a89f8-ffd3-4ef5-8ca3-66d485b3649e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 256, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024794615,\"id\":\"964d0cee-4f18-4430-917e-49078dac545e\"}" + }, + { + "id": "b5971464-464f-4203-9aa4-b3e4619ea9b9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 257, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024797119,\"id\":\"6c352666-89ec-4241-bad8-7bf73f91f297\"}" + }, + { + "id": "1d1cf5c9-4bc5-4275-b560-238e4ebcfba0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 258, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024803466,\"id\":\"a0cfec5f-7884-48ee-9e5e-6c0a30c59f1d\"}" + }, + { + "id": "47ed8d3a-3699-4c18-9bb6-f85cc36b25ee", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 259, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024823285,\"id\":\"06fb2e20-c368-4637-83cb-9f00f75ae248\"}" + }, + { + "id": "0c0ef5fb-74c0-4c8c-a637-d6b67f077b6d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 260, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"2fc6032f-95d1-4520-b095-521e60cab956\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024948056,\"id\":\"4b6ecc5b-694a-4cb3-9fdd-ae422756281f\"}" + }, + { + "id": "3f88c080-49af-4468-8804-1c7fa9936139", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 261, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024953442,\"id\":\"9b47af76-56c3-480f-ad4a-3677cbb79976\"}" + }, + { + "id": "58950ac5-d3aa-4245-9e28-08b6964823d4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 262, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024959555,\"id\":\"dc34b755-56df-468f-aac9-a293e738d097\"}" + }, + { + "id": "f6865189-5b69-4764-9cbb-fb083b36db46", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 263, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\",\"position\":\"2B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024967066,\"id\":\"683656e8-44ce-496d-b92c-b1aa6eac8638\"}" + }, + { + "id": "8e7b259a-6170-42fb-9c25-f7d131cd3b63", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 264, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024971391,\"id\":\"60e4a4d8-abbb-4059-a274-34a7bb6269e3\"}" + }, + { + "id": "d0ca03c5-c9f8-45c8-b2ce-2ea7fdb8d3df", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 265, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"d4a1ceca-45a9-4f00-9ec0-ba04a406f0c2\",\"position\":\"3B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024975349,\"id\":\"9d77f89c-6e39-40e7-9c3a-289cfb7abb03\"}" + }, + { + "id": "e91167f8-8840-45f9-93d8-5a01b616c5f9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 266, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\",\"position\":\"CF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024981248,\"id\":\"2b36bebd-f2dd-482d-a4c0-606858180f3e\"}" + }, + { + "id": "a4168ff6-92a8-4d36-b886-d8bdec8c80d4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 267, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776024986708,\"id\":\"6cbe8e4a-ef25-4b48-9af1-0acffe2f64f1\"}" + }, + { + "id": "848dc484-6c54-4a62-b674-6c9c8d3916e9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 268, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025000007,\"id\":\"4ef354b6-c1c6-41c6-937f-5dc06005e86d\"}" + }, + { + "id": "162d9522-b9a9-457a-b2f3-653849b34fc7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 269, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025015124,\"id\":\"8b7ee105-bc95-4ef5-9922-761381581aca\"}" + }, + { + "id": "82c57e15-23af-45c7-a81b-170d9b3bc102", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 270, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025028410,\"id\":\"8b0d1d45-b9fb-4745-9342-5e097b45de1a\"}" + }, + { + "id": "bc84f159-9eff-4eaf-bed8-d28d1e0bd421", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 271, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025055722,\"id\":\"9c239227-5355-4c0b-ad49-2314a9fc63b8\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":212.0,\"y\":139.0},\"position\":\"2B\"}],\"playResult\":\"batter_out\",\"playType\":\"pop_fly\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025055722,\"id\":\"da38b5b6-e5a7-4150-a139-2e511cb88eb8\"}],\"code\":\"transaction\",\"id\":\"1871532f-dcac-4457-90c7-adb0802f6152\"}" + }, + { + "id": "c117dadc-7c52-435f-a157-c742dd44d517", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 272, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025074351,\"id\":\"d21c02eb-3a2b-4f96-b69c-6aac7855f562\"}" + }, + { + "id": "16b1d4b4-2e68-45b1-be60-356fb6bd6b66", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 273, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025086417,\"id\":\"29627720-18eb-4149-9b75-60c1557f36f2\"}" + }, + { + "id": "33714460-33a8-42b8-94bc-51ced82b3113", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 274, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025101108,\"id\":\"46ca920e-b814-41c6-be9e-ba9d058d4fb4\"}" + }, + { + "id": "d405e76f-9bc6-4dd6-83bb-b3bad28ba08e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 275, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025117680,\"id\":\"540b475f-bd5f-4ae6-853a-5e8dc6c55c84\"}" + }, + { + "id": "5a371656-e3c1-489f-95e1-ba8bde511359", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 276, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025129356,\"id\":\"49270c59-3ffd-48dd-ab06-140c5274b651\"}" + }, + { + "id": "a0206be5-c35b-41cb-b517-21c96e8f7ab7", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 277, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025154347,\"id\":\"cbd5cd19-2abd-46ba-9f80-8d13b7aaff27\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":263.0,\"y\":96.0},\"position\":\"RF\"}],\"playResult\":\"single\",\"playType\":\"hard_ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025154347,\"id\":\"6101e9bf-9c2c-4b9d-8c82-da37aca3e682\"}],\"code\":\"transaction\",\"id\":\"5e45f157-765f-4f1e-b682-4c64bda69f84\"}" + }, + { + "id": "7265cf01-5c98-41ea-942f-bd81223b24ae", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 278, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025179113,\"id\":\"540d866e-e237-4b8d-a45f-26f0ec2b3c85\"}" + }, + { + "id": "1a189a50-93de-47ad-89a1-5c6a4bdec0bd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 279, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025199863,\"id\":\"4a6e11d5-19e9-49c0-8d5f-7df860d5b1d0\"}" + }, + { + "id": "d0c2eb12-2982-47c2-a016-5451457ce545", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 280, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025221107,\"id\":\"c068afbf-2005-489c-b95a-dff435b0cf23\"}" + }, + { + "id": "45bf91a4-205d-444c-af8c-066c8a3ec67b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 281, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025232779,\"id\":\"a73db9e2-a54f-47da-83b4-fffbff382208\"}" + }, + { + "id": "7f7ee474-adfc-4d22-b51d-f49be64ec7ad", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 282, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025250277,\"id\":\"a77f6d7b-4695-4303-af4b-8e55bb7a40cd\"}" + }, + { + "id": "9a4845db-a0e7-4f44-8788-0385716ade61", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 283, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"aeda7933-db71-445d-b2d2-f0d895dbfc37\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025254311,\"id\":\"16705bbb-bc21-4982-9a7c-307c818d7124\"}" + }, + { + "id": "46e7c9b6-14ad-4fef-a5ca-5dee5b56f4ae", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 284, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025293136,\"id\":\"6d42da2c-525c-469b-aa71-1bc0854f873a\"}" + }, + { + "id": "bc52992b-ac8a-42b7-8cec-726ed1ef5844", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 285, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025305755,\"id\":\"3878d61b-29d5-473e-906c-2a04ce7c9b18\"}" + }, + { + "id": "04ade892-83e8-4971-87a1-0b5ceeafc8bf", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 286, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025320514,\"id\":\"0d90d670-5fcd-4631-a35a-b92284ca3d30\"}" + }, + { + "id": "3a5263da-d3fa-47b7-87b2-643dfe50e1ec", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 287, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025334845,\"id\":\"8c137eed-ff20-4d48-a47b-bcc56bc5553f\"}" + }, + { + "id": "6285dc02-5702-41e9-9411-136b76e76621", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 288, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025349154,\"id\":\"e99f22a2-b408-49ce-b564-b805e5cf1ed1\"}" + }, + { + "id": "7833645d-a22b-47c0-825b-e7172c96453b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 289, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025363713,\"id\":\"956c8769-7b52-4751-81c3-0c546a726ba2\"}" + }, + { + "id": "0a1ed755-7101-444c-a9de-4dcfd679e860", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 290, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025386623,\"id\":\"9ef650b8-3781-4574-be8b-aa09029a4a1d\"}" + }, + { + "id": "d4be0ea9-9b2f-4597-852f-73894fabb37c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 291, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025411996,\"id\":\"aa49de39-1ede-4774-8d93-537f36983179\"}" + }, + { + "id": "1fc0ab5b-a084-4bf2-8afa-ca94dbeaf2aa", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 292, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025417043,\"id\":\"6cdee48b-470d-4bf6-bc6b-a8b47fac210c\"}" + }, + { + "id": "f4405f6b-a949-4736-9415-a6a1869b7359", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 293, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025449228,\"id\":\"d1eef783-d7b9-4242-86b9-a8915c8e2970\"}" + }, + { + "id": "32d40d4e-0ab2-493d-a7ec-2bb7a38da374", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 294, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025457290,\"id\":\"f011cdf3-4046-47e0-8c66-e016f5da7ea9\"}" + }, + { + "id": "704a2fe0-8fc6-4542-87db-f139b5cdc839", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 295, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025468153,\"id\":\"802fedc6-25bf-4316-b624-b4bc4e567c10\"}" + }, + { + "id": "1cc07e9a-f343-44c9-ab76-6242030b2d2b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 296, + "event_data": "{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b04f45dc-73bb-439f-a3c1-da6f6c0a2907\",\"position\":\"P\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025530811,\"id\":\"30bece42-3797-474f-a162-b30de5cb4fbb\"}" + }, + { + "id": "78eefc8f-2f6a-467d-86a7-5aacf01bd582", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 297, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025636950,\"id\":\"403fd074-ab1c-4980-bae8-5effa3e33d7a\"}" + }, + { + "id": "1215a2e3-2bb6-4653-8870-7b78791ebea4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 298, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025653397,\"id\":\"0351ae88-1d62-48a0-9a35-1791c735d50a\"}" + }, + { + "id": "d475a0f1-9833-41de-80c9-eb1fd27e97b9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 299, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025671157,\"id\":\"d70d216b-9834-441c-a6dd-886e86b17e2f\"}" + }, + { + "id": "06783ebd-417c-43b4-8bb8-4c3c8392d6de", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 300, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025692459,\"id\":\"6223a9d6-c2f6-4e89-8f26-17a11fefc381\"}" + }, + { + "id": "e764ae1c-fc0e-428d-9d30-e7cc49432a3c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 301, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025717987,\"id\":\"98ac9fc4-8754-4387-b6cc-ff94f3b5f2f7\"}" + }, + { + "id": "6cbadd67-4278-4d7c-a876-78436c6e35ca", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 302, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025743407,\"id\":\"b47f64f1-677b-4b38-81e3-2ae86b175f3d\"}" + }, + { + "id": "98e9f6df-397a-4a7f-bd75-d0395b024cd5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 303, + "event_data": "{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"01a1bf61-d1fb-46f2-86a6-d848bb025626\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025755732,\"id\":\"44373954-8d25-45d8-b512-461b3d532c77\"}" + }, + { + "id": "470fbe71-a8ab-4afc-9ff2-6cf9c0e82d2a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 304, + "event_data": "{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"c929a2cc-1fb4-4635-8efe-50aebf023136\",\"position\":\"1B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025762465,\"id\":\"ddf3d886-8203-420a-9189-3e9e5550b543\"}" + }, + { + "id": "652dc880-8ed4-4ae9-a1dd-bd05a82d5164", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 305, + "event_data": "{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"b30df446-9f7d-444c-a0cb-59b24ec672e5\",\"position\":\"1B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025765307,\"id\":\"65ed252e-2caf-45a7-93bc-2689765e9fc5\"}" + }, + { + "id": "9d3ee865-e1d2-41e2-8355-ff7bdcec7691", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 306, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025767148,\"id\":\"8ce12b82-0872-49c0-9d10-2ea026f19768\"}" + }, + { + "id": "3ab1cd36-6aaa-4efa-8883-72a72f2b3cc0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 307, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025786025,\"id\":\"db1f4b8e-d323-47f4-9aa4-6932bb87961d\"}" + }, + { + "id": "76885b04-fdf6-4fe0-8c79-fb6b4ecb40ca", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 308, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025794967,\"id\":\"448c29a7-6717-4da9-a6d4-b3ef872e4122\"}" + }, + { + "id": "fb208751-182e-4fcb-9f9c-645cf5a29383", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 309, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025815675,\"id\":\"f1305b4e-e3ee-41fc-9de2-2a9bcaaa8f6f\"}" + }, + { + "id": "0229c9a8-c4ee-4ac4-90ae-c12bf4b1d3da", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 310, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025831303,\"id\":\"27c836b4-4efe-4ddf-a18d-8c2dd9565780\"}" + }, + { + "id": "9b090506-0649-40b1-9172-4edef9890816", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 311, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"931eb801-3e5e-43fb-af0c-690e4bfa58c6\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025836482,\"id\":\"197fd356-55ef-4566-ac60-37ad0965d040\"}" + }, + { + "id": "1a8d39fa-cd53-47f5-a31d-05b2c158823c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 312, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025838252,\"id\":\"afd0e3cc-a797-4370-94f8-d6f8937c8a03\"}" + }, + { + "id": "191d3b4c-933b-4119-aeb5-f27cbe0f581c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 313, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025851368,\"id\":\"6d903fea-83d6-45ee-a600-41ee01173fda\"}" + }, + { + "id": "23a8bc72-1835-4940-b3ea-62fdba3e91ee", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 314, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025865657,\"id\":\"dd30a000-87c5-4dae-9bba-327ca596f172\"}" + }, + { + "id": "6d4c917a-bc87-4fd7-84c2-a8b53b8ae5f5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 315, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025891945,\"id\":\"62adf9b3-aa5c-4cd1-87bc-ccd2866f2970\"}" + }, + { + "id": "4fe12279-4601-4f83-a8e6-4a4eedcf509b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 316, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025910839,\"id\":\"f952284d-c613-4811-b7b5-d2ccce3da39e\"}" + }, + { + "id": "aabf19ec-b0b9-483f-999d-09cc8a1fef1e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 317, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025924067,\"id\":\"b2295520-19cc-452a-b651-6cf859f688af\"}" + }, + { + "id": "c20e3619-71da-4add-8692-28ee3bece435", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 318, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"931eb801-3e5e-43fb-af0c-690e4bfa58c6\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025975338,\"id\":\"8fc1f6eb-8ea6-435d-a0b0-d564e5c250c6\"}" + }, + { + "id": "c87a4134-da23-4aa9-8878-4fbffd900c58", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 319, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025980727,\"id\":\"8f5a0d43-46a5-4c9b-b84a-6d3c7fb51029\"}" + }, + { + "id": "5e312509-06f9-40ca-8771-b609ab0eb624", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 320, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"40617428-c9e0-4049-8857-b8855cb9f36b\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025982649,\"id\":\"a8699fa2-16db-4577-a5dc-6679a8346006\"}" + }, + { + "id": "e60c2d19-fd4e-4f58-911b-aed02b6632ab", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 321, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025986351,\"id\":\"76693361-c2b4-439a-81bd-76216865cfe1\"}" + }, + { + "id": "a6bf215b-59f0-4819-a469-15604bc5eef2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 322, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025992879,\"id\":\"c056edd4-9a66-49fa-b2b6-9c51792a28ad\"}" + }, + { + "id": "22e9efa0-4403-4e23-a176-280c1115f94c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 323, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025993328,\"id\":\"98816a7d-5aa0-4aa4-9e19-bd5b0ee3491e\"}" + }, + { + "id": "98ebf4e3-0b4c-4990-b666-21f4a3072c22", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 324, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776025994104,\"id\":\"a0a41518-f901-4255-94e6-518862e7d8f7\"}" + }, + { + "id": "1e7672c6-47ba-4ac9-96e8-5ea0b32b5239", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 325, + "event_data": "{\"code\":\"undo\",\"id\":\"ba56ea1f-dce5-41b3-957a-0712f52d3d8f\"}" + }, + { + "id": "4a1dc8bd-e81d-411f-b50d-91ae7286294b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 326, + "event_data": "{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"playerId\":\"c929a2cc-1fb4-4635-8efe-50aebf023136\",\"position\":\"P\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026121172,\"id\":\"0b90e47d-bb04-48a8-b09e-c5eed203e6f1\"}" + }, + { + "id": "6ffff9d3-b1ec-468a-8a22-49c7c62d5bf2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 327, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026123718,\"id\":\"cc9692d4-377d-4b28-a502-0ec776969a91\"}" + }, + { + "id": "9c65bbd5-0200-470b-aa64-5b2395d496b8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 328, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026127284,\"id\":\"83970b3a-df1b-4a27-a81e-589353459926\"}" + }, + { + "id": "41183861-4cc0-4d4d-8fd2-f43fd26c1366", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 329, + "event_data": "{\"attributes\":{\"base\":2,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026137490,\"id\":\"9ce014e6-57f3-43f5-ab40-b38f8f63a38d\"}" + }, + { + "id": "0cd3398a-1196-418f-aef0-060374b5f868", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 330, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026142418,\"id\":\"925c7868-5656-46d5-bf92-21066b71ce64\"}" + }, + { + "id": "f4b0c4f3-fdf3-4184-8faa-2dee329cc127", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 331, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026156504,\"id\":\"79bbecfb-8cb5-4972-befe-3d3031a25d7a\"}" + }, + { + "id": "96bc08d9-3adb-45fd-b121-d2cdc89e3ecc", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 332, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026174327,\"id\":\"2d31228f-6259-4182-80aa-c405a190d1bf\"}" + }, + { + "id": "93b11cd2-ca4e-46be-b1b9-d07b9eb1b326", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 333, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026185769,\"id\":\"340aa4e7-8924-43ad-9e4a-eab59f1ed63c\"}" + }, + { + "id": "0da40eb6-4760-4041-8714-2a72eda49ed9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 334, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026197399,\"id\":\"3373d45d-792f-4df2-bc0e-6a74274fe174\"}" + }, + { + "id": "2d4336a3-ee3d-4df2-b3c1-15807bcc841a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 335, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026210325,\"id\":\"8f09ff36-af71-4309-a9da-067447d30c73\"}" + }, + { + "id": "e00bfab6-e1bf-4871-976e-152f0c19bd14", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 336, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026226424,\"id\":\"80732476-aa15-4d88-be45-f19756833afb\"}" + }, + { + "id": "0fc5dfb7-842d-4643-90c6-2f4fee7bbd33", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 337, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026238781,\"id\":\"9aa13681-6f60-46ae-bc5f-47341327a378\"}" + }, + { + "id": "1b76584f-b7b3-4818-acca-513db6bb670d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 338, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026253340,\"id\":\"3cf92e41-f84c-49f5-815d-4197d4214955\"}" + }, + { + "id": "49d9925b-2cc3-47ee-80a3-5c3e6d327b65", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 339, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026267853,\"id\":\"747abb93-9e50-4cc8-ab8b-d7b3576651d9\"}" + }, + { + "id": "42ac3fa3-1328-46b3-b6ee-e9c256f6c561", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 340, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026282434,\"id\":\"630eb21d-c2dc-43d3-8b57-347b4f745454\"}" + }, + { + "id": "00aa8792-7cef-4184-a44a-7eb254b09e3b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 341, + "event_data": "{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"ac6f3303-07be-4149-abc6-a2e09e4fe885\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026312497,\"id\":\"4c72fe47-3b08-4def-84c6-bb4da31ed2e9\"}" + }, + { + "id": "e671bac3-da8e-42ee-8fe3-3833f8bd6883", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 342, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026315476,\"id\":\"5c676f96-9146-41ba-9c2d-07ec814a88f4\"}" + }, + { + "id": "54043a8e-9abb-4235-a80f-f15d756cfd9e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 343, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026342440,\"id\":\"d11ad86d-1ad1-418f-95eb-1458fd31eae6\"}" + }, + { + "id": "6e07a26b-2626-4318-8def-b4dfdae3b995", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 344, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026347875,\"id\":\"669dd8b3-f359-4821-879d-33f5b1dc7dba\"}" + }, + { + "id": "0353dc53-f04c-4ce1-a3a3-67d5b137455a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 345, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026359778,\"id\":\"592f0271-a665-42da-bd31-cb4a60b01d84\"}" + }, + { + "id": "7c976378-908d-4935-8aaf-d9db6fb09461", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 346, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026370662,\"id\":\"c798bcc5-cd05-4272-a4d3-9b1d75aa7be2\"}" + }, + { + "id": "33f7b4e0-c109-4f4b-be0f-40c42bd03387", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 347, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026383503,\"id\":\"8b9d1144-5846-4b82-b91b-7900d594bf38\"}" + }, + { + "id": "2d162088-f4c2-4ddd-ba69-9ae72c4da795", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 348, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026407335,\"id\":\"d0889b69-c530-4ab3-92f7-2a0562b1ea55\"}" + }, + { + "id": "cdfcbaf6-67dc-4e5a-8a3a-0235a40c4164", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 349, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026420515,\"id\":\"047d9996-a11e-4772-bfce-30a3ee7210a5\"}" + }, + { + "id": "fdb92ce1-7235-4490-898b-81fb1e1d53d6", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 350, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026434977,\"id\":\"df252da4-60b7-43ac-9744-776890e5b036\"}" + }, + { + "id": "585d44da-b443-4c50-9e5a-1964ee62329e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 351, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026449367,\"id\":\"1f607768-8273-4151-a9c3-587b74c02c50\"}" + }, + { + "id": "a8105b95-c630-4d12-b7d4-b0fd671098e3", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 352, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026462179,\"id\":\"950faebe-03fe-4b92-8fc7-b6d8c6772d91\"}" + }, + { + "id": "b6267ae2-ab70-48e4-8836-985e35ef232e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 353, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026510030,\"id\":\"98f2ebde-d435-4214-a9d6-c04715775e2f\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":263.0,\"y\":96.0},\"position\":\"RF\"}],\"playResult\":\"double\",\"playType\":\"hard_ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026510030,\"id\":\"0be2171d-3027-421a-ba2b-0cbe4975866d\"},{\"attributes\":{\"base\":4,\"defenders\":[],\"playType\":\"out_on_last_play\",\"runnerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\"},\"code\":\"base_running\",\"id\":\"530dd4eb-1034-4cdd-8292-65794df33ba5\"}],\"code\":\"transaction\",\"id\":\"f74ac105-16df-42ea-ad2a-bc928ad48648\"}" + }, + { + "id": "b986a58a-be88-4e13-9478-b9056026e3bd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 354, + "event_data": "{\"code\":\"end_half\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026545402,\"id\":\"146a84bc-652f-479c-95f8-da3f588a2fac\"}" + }, + { + "id": "2f257665-5564-4547-821e-ca0d6d298914", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 355, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\",\"position\":\"P\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026575553,\"id\":\"cafb7178-1638-4c54-a580-9dff8dc8e464\"}" + }, + { + "id": "c15d7025-68ff-4d64-91c7-8020912f0ed8", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 356, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\",\"position\":\"SS\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026622483,\"id\":\"c143892d-a2b5-45db-bca4-dd5aaa4961a8\"}" + }, + { + "id": "dc7d3645-9745-4a9c-8d17-bf5cdcb135a4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 357, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"fc88ba8a-f8f5-4a5d-8850-af4af2f455a1\",\"position\":\"2B\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026642227,\"id\":\"c9437c8d-6513-4c60-9923-298578227f15\"}" + }, + { + "id": "3464a6f4-d2be-43be-85ea-d08369fbd494", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 358, + "event_data": "{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"playerId\":\"ca0b4812-c40b-440a-89b7-527cda165062\",\"position\":\"LF\"},\"code\":\"fill_position\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026666432,\"id\":\"6ec2da14-1bb8-4a15-91b6-ff37d3d1f7a5\"}" + }, + { + "id": "45045cd8-825a-409b-85c6-83f437c3d2c2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 359, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026676008,\"id\":\"da9ffb85-40f4-47ca-9f80-6b945dd31f23\"}" + }, + { + "id": "e31b17f4-9cd3-4529-a618-dca9f308cba5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 360, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026683322,\"id\":\"9489e159-7daa-413f-b509-f8e1773c78f1\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":107.0,\"y\":139.0},\"position\":\"SS\"},{\"error\":false,\"location\":{\"x\":263.0,\"y\":186.0},\"position\":\"1B\"}],\"playResult\":\"batter_out_advance_runners\",\"playType\":\"hard_ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026683322,\"id\":\"19b6a4bd-efa8-4c88-aa25-699d6187abf2\"}],\"code\":\"transaction\",\"id\":\"6f6a2caa-4575-4c08-a20f-f5cc2dd5e33f\"}" + }, + { + "id": "aac60d42-b6fe-43ab-8868-3355d7b6dcbd", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 361, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026696459,\"id\":\"ce55c7b8-5aca-4128-9153-4757ea238db1\"}" + }, + { + "id": "d46634dd-846d-4538-a366-ce9b4fc282c4", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 362, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026705813,\"id\":\"98e8553e-3300-4d8e-a281-969b35a4c4c1\"}" + }, + { + "id": "45fd5d42-1da3-4459-9777-e1f96d27f6b5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 363, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"foul\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026716673,\"id\":\"f7c42fdf-98e6-4e96-9a13-612c4a7e650c\"}" + }, + { + "id": "b86ce6df-0854-40e7-a4ca-219d7a003e13", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 364, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026739388,\"id\":\"5949e00e-8e09-4848-9901-37099d9f1f25\"}" + }, + { + "id": "350239d5-c251-459f-a1d6-f40358953981", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 365, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026749696,\"id\":\"3719f744-057c-4b13-a4c2-f7771fe09b7a\"}" + }, + { + "id": "34aaf9f4-ee12-4946-a697-c90df6627d9d", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 366, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_swinging\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026764228,\"id\":\"aacdb6f6-3d34-45ef-b5db-76c844504f7a\"}" + }, + { + "id": "59cccda8-57fc-4161-b65d-6a1af655e0c0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 367, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026776999,\"id\":\"f43d5d98-3cc0-448b-bfdd-d00776481155\"}" + }, + { + "id": "6f2d572f-2453-4410-b940-66b7c0db05e0", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 368, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026790293,\"id\":\"9764fe3d-f5da-454c-bfe9-be0a551a86d9\"}" + }, + { + "id": "aba2002e-fcfc-4ea4-b884-f86b4b8d224a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 369, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026801995,\"id\":\"2bee9197-8bfe-4819-b233-f71331b61ba5\"}" + }, + { + "id": "7e6a22c9-f768-4055-bcfc-6a16acc24ed9", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 370, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026812434,\"id\":\"f8bbdff4-1adb-47ff-a622-1d0fb747ee9e\"}" + }, + { + "id": "5c40196b-199e-445b-b058-cc4ad02ff472", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 371, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026823639,\"id\":\"08aa86f3-cb18-475c-bfc0-850a5ed8b20e\"}" + }, + { + "id": "b3f0992d-70f4-44b5-b3c7-4bafc93d3f67", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 372, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026841253,\"id\":\"b5fb5a63-1b4a-493b-a7fa-ed2f04fdeb0a\"}" + }, + { + "id": "e0d94770-22f3-478a-bfe8-2780522a69e2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 373, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026868990,\"id\":\"bc7c063c-3df1-4e8a-9fe1-88f5e0c581f6\"}" + }, + { + "id": "709e9cab-b84d-4efb-8a7f-e17ab4e8d47e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 374, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026886472,\"id\":\"b11c9bd4-50b5-4adb-8a68-e67be33114e1\"}" + }, + { + "id": "6f331df2-9bd1-43be-84ba-21e60180a24f", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 375, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026897028,\"id\":\"23cd7caf-8007-4b14-8c76-f176c3a81255\"}" + }, + { + "id": "a531b589-a2fd-4985-892c-72196d20c41e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 376, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026914114,\"id\":\"9efe628a-26fe-407d-8256-4ce8664d156d\"}" + }, + { + "id": "34450946-a46d-4532-a1f8-34f5486fb14c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 377, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026931442,\"id\":\"298d3b77-8222-4cdd-be84-78be7e0844de\"}" + }, + { + "id": "76d2d2e1-79a5-4e84-b435-e868a7df2b12", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 378, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026943815,\"id\":\"ff1c148f-315c-4c8e-82a2-4c95e4e2fba5\"}" + }, + { + "id": "27d6992c-8834-4f71-9fc1-305cc74c5e84", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 379, + "event_data": "{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"stole_base\",\"runnerId\":\"a119857c-54cd-4ad0-86da-2fa65514dcfb\"},\"code\":\"base_running\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776026946623,\"id\":\"c918e881-364c-46a0-8a6a-12b6982bed9d\"}" + }, + { + "id": "ae0dc4c1-a144-482e-97c6-0ec7f308212e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 380, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball_in_play\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027063091,\"id\":\"bd0e83bd-c14f-4706-9686-0d0edde0a3b1\"},{\"attributes\":{\"defenders\":[{\"error\":false,\"location\":{\"x\":160.0,\"y\":316.0},\"position\":\"C\"}],\"playResult\":\"single\",\"playType\":\"ground_ball\"},\"code\":\"ball_in_play\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027063091,\"id\":\"c3416a11-c828-43d8-ae10-70165a449ac0\"},{\"attributes\":{\"base\":3,\"defenders\":[],\"playType\":\"remained_on_last_play\",\"runnerId\":\"a119857c-54cd-4ad0-86da-2fa65514dcfb\"},\"code\":\"base_running\",\"id\":\"992c1301-9153-4cd4-9c81-f2d3198357d2\"}],\"code\":\"transaction\",\"id\":\"efbbbf8e-f7f5-4761-8045-1e6b604ae101\"}" + }, + { + "id": "5badb8b9-cfdd-4be2-af77-eb89eb5ce749", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 381, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027084301,\"id\":\"aded2615-ac82-4a4e-b5f1-c7b44ef788ee\"}" + }, + { + "id": "b747d4be-55dd-48d5-984e-b8f9ad83ff24", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 382, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027097315,\"id\":\"d9789cc0-93e4-4368-8985-8251ddb0c0a5\"}" + }, + { + "id": "d8f57231-53e5-4096-b6d9-f16d75d8d325", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 383, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027107602,\"id\":\"577baec6-ed4e-4b8b-8741-0e721e529b94\"}" + }, + { + "id": "dc7bf740-89ee-468f-b68c-acef0e6739a5", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 384, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027119514,\"id\":\"7755b68d-1f4a-4ddf-9375-3b8a69cebf3d\"}" + }, + { + "id": "2e41b25a-f954-4cfb-8dbe-afa4a446882c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 385, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027132780,\"id\":\"29469413-d607-4f07-ba3d-c241b120bf5b\"}" + }, + { + "id": "20609d44-c340-49ef-874b-34e080ccffaf", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 386, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027146729,\"id\":\"b18f01b8-ab77-41c8-b694-55e48f365cc0\"}" + }, + { + "id": "96c6cecb-342a-4a95-8e5e-ce5225529a58", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 387, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027193145,\"id\":\"9a1184a4-a35b-4811-8117-3f6a3118308d\"}" + }, + { + "id": "4556b248-51be-41ff-8fa9-99a30eb3944e", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 388, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027202406,\"id\":\"49dce37e-7619-4a7b-8ba2-862dcc944b2a\"}" + }, + { + "id": "5f1e1e36-b8d3-4a79-80c2-dd3557f99bec", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 389, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027223135,\"id\":\"8b3b1d40-a208-44b9-92bc-282a867d8410\"}" + }, + { + "id": "95624b11-f5da-4bd1-84a4-4298d1955a8b", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 390, + "event_data": "{\"events\":[{\"attributes\":{\"advancesCount\":false,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027235024,\"id\":\"e4813bcd-3afe-4e03-a6a5-7e6bd046029d\"},{\"attributes\":{\"reason\":\"hit_by_pitch\",\"intentional\":false},\"code\":\"end_at_bat\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027235024,\"id\":\"f170fe7b-bf29-4eac-8349-e76f186ea7c4\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"main\"},\"id\":\"0264f3d4-f7e2-41c1-9df6-10b6f9364a8e\"}" + }, + { + "id": "8226cca7-e152-4096-ae44-3682bbed6101", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 391, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027280999,\"id\":\"b91513fa-59d0-4673-93c2-29ec232d4e43\"}" + }, + { + "id": "627936df-a7d2-4047-a022-0c68323e25c2", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 392, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027284440,\"id\":\"41b1a314-1b38-443a-a1da-05a757df40ee\"}" + }, + { + "id": "1fb4ba77-4393-4217-a67c-f2e90dc91075", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 393, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027297397,\"id\":\"5456ebe6-bc13-4236-a665-36d7aede0836\"}" + }, + { + "id": "e1994b4d-d636-4f98-87c6-a2e0cb167f46", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 394, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027309552,\"id\":\"fcf5a43c-0210-4b30-884f-163da0afefdc\"}" + }, + { + "id": "e611f4d8-f73f-4ea2-8910-202fb5941079", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 395, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027377096,\"id\":\"5c9d9a31-e18f-4300-a6fd-5471c80d7ebc\"}" + }, + { + "id": "fa5003cd-bf79-4c72-b1f3-b3a6acb0e168", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 396, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027416094,\"id\":\"e14fba23-8855-4a30-8045-33c516d12f25\"}" + }, + { + "id": "4bba44b9-ddec-4965-8197-9d357af4f395", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 397, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027428908,\"id\":\"786e3e35-a9e6-4457-84ab-d58a393c2dc8\"}" + }, + { + "id": "a5a524a7-03c6-4fa3-84c0-d0902f79cc19", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 398, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027439071,\"id\":\"13ff08b3-3952-45cf-882f-d26b7a2a6c67\"}" + }, + { + "id": "898362c8-9538-4baa-99ca-6919f10f48b1", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 399, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027448855,\"id\":\"8cb644bd-3b2b-46c9-b613-8e79ceac609b\"}" + }, + { + "id": "f1cb015d-f1f0-47c2-987b-5d15a1096d9c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 400, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"ball\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027458479,\"id\":\"c86d8495-2137-4549-a61a-2eb20d663d7b\"}" + }, + { + "id": "0f389671-e9ec-4ba2-b5b9-dfc9a577287a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 401, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027478482,\"id\":\"e71be7b9-38d1-4396-8d12-570eb34958ea\"}" + }, + { + "id": "84a68f24-b833-4cf3-8810-b36c8126568c", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 402, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027487129,\"id\":\"e21181cc-5b05-4df3-83ed-92dd7e9774fe\"}" + }, + { + "id": "71c0d586-971b-4c54-9bfb-046612433c51", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 403, + "event_data": "{\"attributes\":{\"advancesCount\":true,\"advancesRunners\":false,\"pitchSpeedProvider\":\"user\",\"result\":\"strike_looking\"},\"code\":\"pitch\",\"compactorAttributes\":{\"stream\":\"main\"},\"createdAt\":1776027509709,\"id\":\"f9bc7912-7315-4eed-8606-f8075cd70071\"}" + }, + { + "id": "c3a0ae89-d6c9-4a00-9b95-711888bc793a", + "stream_id": "d9ee51a5-d5ed-4a47-97d2-1395a44dacaa", + "sequence_number": 404, + "event_data": "{\"events\":[{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"pitcherId\":\"95ad9636-c0e0-4bca-9daf-6ed3762a1a74\",\"decision\":\"win\"},\"code\":\"pitcher_decision\",\"compactorAttributes\":{\"stream\":\"tail\"},\"createdAt\":1776027676133,\"id\":\"f8fbcc48-81bf-414e-86c5-feb75e826733\"},{\"attributes\":{\"teamId\":\"ec1f3c69-0931-4ce5-939e-082f59fa16b2\",\"pitcherId\":\"c929a2cc-1fb4-4635-8efe-50aebf023136\",\"decision\":\"loss\"},\"code\":\"pitcher_decision\",\"compactorAttributes\":{\"stream\":\"tail\"},\"createdAt\":1776027676133,\"id\":\"cd47cfe9-61eb-4355-93b4-53ee545fb616\"},{\"attributes\":{\"teamId\":\"0cd14a81-363e-4ddc-aff3-90983693e8b7\",\"pitcherId\":\"e0965c76-8d40-48ae-a3c6-e6ea5a23f898\",\"decision\":\"save\"},\"code\":\"pitcher_decision\",\"compactorAttributes\":{\"stream\":\"tail\"},\"createdAt\":1776027676133,\"id\":\"c5b316ce-cc24-4d1e-a974-cb2dc2c99d94\"}],\"code\":\"transaction\",\"compactorAttributes\":{\"stream\":\"tail\"},\"id\":\"ae0a7e9a-6459-4f0a-b523-40d90ebfd932\"}" + } +] \ No newline at end of file diff --git a/testdata/box_scores.json b/testdata/box_scores.json index 945398d..ec94600 100644 --- a/testdata/box_scores.json +++ b/testdata/box_scores.json @@ -191,5 +191,11 @@ "home": [0, 0, 3, 3], "awayTotal": 8, "homeTotal": 6 + }, + "10U_Mariners_Brewers_Apr12": { + "away": [0, 3, 5, 2, 5], + "home": [5, 2, 3, 0, 4], + "awayTotal": 15, + "homeTotal": 14 } } \ No newline at end of file diff --git a/tests/integration.rs b/tests/integration.rs index d5ba037..5e7f3a1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -74,11 +74,12 @@ macro_rules! game_test { if ps.batting.pa > 0 { assert_eq!( ps.batting.ab + ps.batting.bb + ps.batting.hbp - + ps.batting.sac_fly + ps.batting.sac_bunt, + + ps.batting.ci + ps.batting.sac_fly + ps.batting.sac_bunt, ps.batting.pa, - "{} player {} PA invariant failed: ab({}) + bb({}) + hbp({}) + sf({}) + sac({}) != pa({})", + "{} player {} PA invariant failed: ab({}) + bb({}) + hbp({}) + ci({}) + sf({}) + sac({}) != pa({})", $game_key, ps.player_id, ps.batting.ab, ps.batting.bb, ps.batting.hbp, + ps.batting.ci, ps.batting.sac_fly, ps.batting.sac_bunt, ps.batting.pa ); } @@ -164,6 +165,129 @@ game_test!( "mariners_vs_tigers_apr1" ); +game_test!( + test_10u_mariners_brewers_apr12, + "10U_Mariners_Brewers_Apr12.json", + "10U_Mariners_Brewers_Apr12" +); + +/// Regression: auto-scored runners must not be double-counted when the +/// confirming base_running event arrives in a later transaction. +#[test] +fn test_auto_score_no_double_count() { + let json = include_str!("../testdata/10U_Mariners_Brewers_Apr12.json"); + let result = replay_from_json(json).expect("replay should succeed"); + + let away_linescore: i32 = result.linescore_away.iter().sum(); + let home_linescore: i32 = result.linescore_home.iter().sum(); + let away_player_runs: i32 = result + .player_stats + .values() + .filter(|p| p.team_id == result.away_id) + .map(|p| p.batting.runs) + .sum(); + let home_player_runs: i32 = result + .player_stats + .values() + .filter(|p| p.team_id == result.home_id) + .map(|p| p.batting.runs) + .sum(); + + assert_eq!( + away_player_runs, away_linescore, + "away player runs ({}) != linescore ({})", + away_player_runs, away_linescore + ); + assert_eq!( + home_player_runs, home_linescore, + "home player runs ({}) != linescore ({})", + home_player_runs, home_linescore + ); +} + +#[test] +fn test_substituted_error_runner_stays_unearned() { + fn raw_event(seq: i64, event_data: serde_json::Value) -> serde_json::Value { + serde_json::json!({ + "id": format!("evt-{seq}"), + "stream_id": "test", + "sequence_number": seq, + "event_data": event_data.to_string(), + }) + } + + let events = vec![ + raw_event( + 1, + serde_json::json!({ + "code": "set_teams", + "attributes": {"awayId": "away", "homeId": "home"} + }), + ), + raw_event( + 2, + serde_json::json!({ + "code": "fill_lineup_index", + "attributes": {"teamId": "away", "playerId": "p1", "index": 0} + }), + ), + raw_event( + 3, + serde_json::json!({ + "code": "fill_lineup_index", + "attributes": {"teamId": "away", "playerId": "p2", "index": 1} + }), + ), + raw_event( + 4, + serde_json::json!({ + "code": "fill_position", + "attributes": {"teamId": "home", "playerId": "hp", "position": "P"} + }), + ), + raw_event( + 5, + serde_json::json!({ + "code": "transaction", + "events": [ + {"code": "pitch", "attributes": {"result": "ball_in_play", "advancesCount": true}}, + {"code": "ball_in_play", "attributes": {"playResult": "error", "playType": "ground_ball"}} + ] + }), + ), + raw_event( + 6, + serde_json::json!({ + "code": "sub_players", + "attributes": { + "teamId": "away", + "outgoingPlayerId": "p1", + "incomingPlayerId": "pr", + "applyToBaserunners": true + } + }), + ), + raw_event( + 7, + serde_json::json!({ + "code": "transaction", + "events": [ + {"code": "pitch", "attributes": {"result": "ball_in_play", "advancesCount": true}}, + {"code": "ball_in_play", "attributes": {"playResult": "home_run", "playType": "fly_ball"}} + ] + }), + ), + ]; + let json = serde_json::to_string(&events).unwrap(); + + let result = replay_from_json(&json).expect("replay should succeed"); + + assert_eq!(result.linescore_away.iter().sum::(), 2); + assert_eq!(result.home_pitching.runs_allowed, 2); + assert_eq!(result.home_pitching.earned_runs_allowed, 1); + assert_eq!(result.player_stats["pr"].batting.runs, 1); +} + #[test] fn test_player_stats_populated() { let json = include_str!("../testdata/13U_Braves_Padres.json"); @@ -229,6 +353,7 @@ fn test_player_stats_populated() { ps.batting.ab + ps.batting.bb + ps.batting.hbp + + ps.batting.ci + ps.batting.sac_fly + ps.batting.sac_bunt, ps.batting.pa, @@ -266,34 +391,54 @@ macro_rules! ll_balance_test { let home_ll = &result.home_little_league; assert_eq!( - away_ll.runs_on_bip + away_ll.runs_passive, away_total, + away_ll.runs_on_bip + away_ll.runs_passive, + away_total, "{} away LL balance: bip({}) + passive({}) = {} != linescore({})", - $file, away_ll.runs_on_bip, away_ll.runs_passive, - away_ll.runs_on_bip + away_ll.runs_passive, away_total + $file, + away_ll.runs_on_bip, + away_ll.runs_passive, + away_ll.runs_on_bip + away_ll.runs_passive, + away_total ); assert_eq!( - home_ll.runs_on_bip + home_ll.runs_passive, home_total, + home_ll.runs_on_bip + home_ll.runs_passive, + home_total, "{} home LL balance: bip({}) + passive({}) = {} != linescore({})", - $file, home_ll.runs_on_bip, home_ll.runs_passive, - home_ll.runs_on_bip + home_ll.runs_passive, home_total + $file, + home_ll.runs_on_bip, + home_ll.runs_passive, + home_ll.runs_on_bip + home_ll.runs_passive, + home_total ); } }; } -ll_balance_test!(test_ll_balance_mariners_cardinals, "10U_Mariners_Cardinals.json"); +ll_balance_test!( + test_ll_balance_mariners_cardinals, + "10U_Mariners_Cardinals.json" +); ll_balance_test!(test_ll_balance_mets_brewers, "10U_Mets_Brewers.json"); ll_balance_test!(test_ll_balance_braves_yankees, "10U_Braves_Yankees.json"); ll_balance_test!(test_ll_balance_tigers_dodgers, "10U_Tigers_Dodgers.json"); ll_balance_test!(test_ll_balance_13u_braves_padres, "13U_Braves_Padres.json"); -ll_balance_test!(test_ll_balance_13u_mariners_brewers, "13U_Mariners_Brewers.json"); -ll_balance_test!(test_ll_balance_13u_phillies_cardinals, "13U_Phillies_Cardinals.json"); +ll_balance_test!( + test_ll_balance_13u_mariners_brewers, + "13U_Mariners_Brewers.json" +); +ll_balance_test!( + test_ll_balance_13u_phillies_cardinals, + "13U_Phillies_Cardinals.json" +); ll_balance_test!(test_ll_balance_mccabe_reds, "McCabe_Tigers_Reds.json"); ll_balance_test!(test_ll_balance_mccabe_angels, "McCabe_Tigers_Angels.json"); ll_balance_test!(test_ll_balance_mccabe_yankees, "McCabe_Tigers_Yankees.json"); ll_balance_test!(test_ll_balance_mccabe_mets, "McCabe_Tigers_Mets.json"); ll_balance_test!(test_ll_balance_stars_tigers, "stars_vs_tigers_mar31.json"); -ll_balance_test!(test_ll_balance_mariners_tigers_apr1, "mariners_vs_tigers_apr1.json"); +ll_balance_test!( + test_ll_balance_mariners_tigers_apr1, + "mariners_vs_tigers_apr1.json" +); // --------------------------------------------------------------------------- // Undo/redo: Stars vs Tigers has 32 undos and 1 redo that restores a @@ -346,8 +491,12 @@ fn test_no_steal_home_reduces_runs() { for ps in simulated.player_stats.values() { if ps.batting.pa > 0 { assert_eq!( - ps.batting.ab + ps.batting.bb + ps.batting.hbp - + ps.batting.sac_fly + ps.batting.sac_bunt, + ps.batting.ab + + ps.batting.bb + + ps.batting.hbp + + ps.batting.ci + + ps.batting.sac_fly + + ps.batting.sac_bunt, ps.batting.pa, "Player {} PA invariant failed in simulation", ps.player_id diff --git a/tests/replay_artifacts.rs b/tests/replay_artifacts.rs new file mode 100644 index 0000000..d2d86d5 --- /dev/null +++ b/tests/replay_artifacts.rs @@ -0,0 +1,637 @@ +use std::collections::BTreeMap; +use std::fs; +use std::path::PathBuf; + +use diamond_replay::{ + replay_from_json, replay_from_json_no_steal_home, replay_from_json_with_options, BattingStats, + GameResult, LittleLeagueStats, PitchingStats, PlayerGameStats, ReplayOptions, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +struct ExpectedBoxScore { + away: Vec, + home: Vec, + #[serde(rename = "awayTotal")] + away_total: i32, + #[serde(rename = "homeTotal")] + home_total: i32, +} + +fn testdata_path(file_name: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("testdata") + .join(file_name) +} + +fn load_box_scores() -> BTreeMap { + let path = testdata_path("box_scores.json"); + let json = fs::read_to_string(&path) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display())); + serde_json::from_str(&json) + .unwrap_or_else(|err| panic!("failed to parse {}: {err}", path.display())) +} + +fn load_game(game_key: &str) -> String { + let path = testdata_path(&format!("{game_key}.json")); + fs::read_to_string(&path) + .unwrap_or_else(|err| panic!("failed to read fixture {}: {err}", path.display())) +} + +#[test] +fn ground_truth_games_pass_standard_replay_gates() { + for (game_key, expected) in load_box_scores() { + let json = load_game(&game_key); + let legacy = replay_from_json(&json) + .unwrap_or_else(|err| panic!("{game_key}: standard replay failed: {err}")); + let result = replay_from_json_with_options(&json, ReplayOptions::standard()) + .unwrap_or_else(|err| panic!("{game_key}: options replay failed: {err}")); + + assert_same_replay(&game_key, "standard options replay", &legacy, &result); + assert_box_score(&game_key, &result, &expected); + assert_core_replay_invariants(&game_key, &result); + assert_standard_team_totals(&game_key, &result); + assert_serializes(&game_key, &result); + } +} + +#[test] +fn ground_truth_games_pass_no_steal_home_simulation_gates() { + for (game_key, _expected) in load_box_scores() { + let json = load_game(&game_key); + let standard = replay_from_json_with_options(&json, ReplayOptions::standard()) + .unwrap_or_else(|err| panic!("{game_key}: standard options replay failed: {err}")); + let legacy_simulated = replay_from_json_no_steal_home(&json) + .unwrap_or_else(|err| panic!("{game_key}: no-steal-home replay failed: {err}")); + let simulated = replay_from_json_with_options(&json, ReplayOptions::no_steal_home()) + .unwrap_or_else(|err| panic!("{game_key}: no-steal-home options replay failed: {err}")); + + assert_same_replay( + &game_key, + "no-steal-home options replay", + &legacy_simulated, + &simulated, + ); + assert_core_replay_invariants(&game_key, &simulated); + assert_standard_team_totals(&game_key, &simulated); + assert_serializes(&game_key, &simulated); + + assert_eq!( + simulated.away_little_league.steals_of_home, 0, + "{game_key}: away steals of home should be fully suppressed" + ); + assert_eq!( + simulated.home_little_league.steals_of_home, 0, + "{game_key}: home steals of home should be fully suppressed" + ); + + let standard_away_runs: i32 = standard.linescore_away.iter().sum(); + let standard_home_runs: i32 = standard.linescore_home.iter().sum(); + let simulated_away_runs: i32 = simulated.linescore_away.iter().sum(); + let simulated_home_runs: i32 = simulated.linescore_home.iter().sum(); + + assert!( + simulated_away_runs <= standard_away_runs, + "{game_key}: no-steal-home away runs ({simulated_away_runs}) exceeded standard runs ({standard_away_runs})" + ); + assert!( + simulated_home_runs <= standard_home_runs, + "{game_key}: no-steal-home home runs ({simulated_home_runs}) exceeded standard runs ({standard_home_runs})" + ); + } +} + +fn assert_same_replay(game_key: &str, label: &str, expected: &GameResult, actual: &GameResult) { + assert_eq!(actual.home_id, expected.home_id, "{game_key}: {label} home"); + assert_eq!(actual.away_id, expected.away_id, "{game_key}: {label} away"); + assert_eq!( + actual.linescore_away, expected.linescore_away, + "{game_key}: {label} away linescore" + ); + assert_eq!( + actual.linescore_home, expected.linescore_home, + "{game_key}: {label} home linescore" + ); + assert_eq!( + actual.first_timestamp, expected.first_timestamp, + "{game_key}: {label} first timestamp" + ); + assert_eq!( + actual.first_pitch_timestamp, expected.first_pitch_timestamp, + "{game_key}: {label} first pitch timestamp" + ); + assert_eq!( + actual.last_pitch_timestamp, expected.last_pitch_timestamp, + "{game_key}: {label} last pitch timestamp" + ); + assert_eq!( + actual.last_timestamp, expected.last_timestamp, + "{game_key}: {label} last timestamp" + ); + assert_eq!( + actual.transition_gaps, expected.transition_gaps, + "{game_key}: {label} transition gaps" + ); + assert_eq!( + actual.dead_time_per_inning, expected.dead_time_per_inning, + "{game_key}: {label} dead time" + ); + assert_eq!( + actual.inning_durations, expected.inning_durations, + "{game_key}: {label} inning durations" + ); + + assert_same_json( + game_key, + label, + "away batting", + &expected.away_batting, + &actual.away_batting, + ); + assert_same_json( + game_key, + label, + "home batting", + &expected.home_batting, + &actual.home_batting, + ); + assert_same_json( + game_key, + label, + "away pitching", + &expected.away_pitching, + &actual.away_pitching, + ); + assert_same_json( + game_key, + label, + "home pitching", + &expected.home_pitching, + &actual.home_pitching, + ); + assert_same_json( + game_key, + label, + "away little league", + &expected.away_little_league, + &actual.away_little_league, + ); + assert_same_json( + game_key, + label, + "home little league", + &expected.home_little_league, + &actual.home_little_league, + ); + + let mut expected_players = expected.player_stats.keys().collect::>(); + let mut actual_players = actual.player_stats.keys().collect::>(); + expected_players.sort_unstable(); + actual_players.sort_unstable(); + assert_eq!( + actual_players, expected_players, + "{game_key}: {label} player keys" + ); + + for player_id in expected_players { + let expected_player = expected + .player_stats + .get(player_id) + .expect("expected player key came from map"); + let actual_player = actual + .player_stats + .get(player_id) + .expect("actual player key should exist after key comparison"); + assert_same_json( + game_key, + label, + &format!("player {player_id}"), + expected_player, + actual_player, + ); + } +} + +fn assert_same_json( + game_key: &str, + label: &str, + section: &str, + expected: &T, + actual: &T, +) { + let mut expected_json = serde_json::to_value(expected) + .unwrap_or_else(|err| panic!("{game_key}: failed to serialize expected {section}: {err}")); + let mut actual_json = serde_json::to_value(actual) + .unwrap_or_else(|err| panic!("{game_key}: failed to serialize actual {section}: {err}")); + normalize_order_insensitive_arrays(&mut expected_json); + normalize_order_insensitive_arrays(&mut actual_json); + + assert_eq!( + actual_json, expected_json, + "{game_key}: {label} {section} diverged from legacy path" + ); +} + +fn normalize_order_insensitive_arrays(value: &mut serde_json::Value) { + match value { + serde_json::Value::Object(map) => { + for (key, child) in map { + if key == "spray_chart" { + if let serde_json::Value::Array(entries) = child { + for entry in entries.iter_mut() { + normalize_order_insensitive_arrays(entry); + } + entries.sort_by_key(|entry| entry.to_string()); + } + } else { + normalize_order_insensitive_arrays(child); + } + } + } + serde_json::Value::Array(items) => { + for item in items { + normalize_order_insensitive_arrays(item); + } + } + serde_json::Value::Null + | serde_json::Value::Bool(_) + | serde_json::Value::Number(_) + | serde_json::Value::String(_) => {} + } +} + +fn assert_box_score(game_key: &str, result: &GameResult, expected: &ExpectedBoxScore) { + assert_eq!( + result.linescore_away, expected.away, + "{game_key}: away linescore mismatch" + ); + assert_eq!( + result.linescore_home, expected.home, + "{game_key}: home linescore mismatch" + ); + assert_eq!( + result.linescore_away.iter().sum::(), + expected.away_total, + "{game_key}: away total mismatch" + ); + assert_eq!( + result.linescore_home.iter().sum::(), + expected.home_total, + "{game_key}: home total mismatch" + ); +} + +fn assert_core_replay_invariants(game_key: &str, result: &GameResult) { + let away_runs: i32 = result.linescore_away.iter().sum(); + let home_runs: i32 = result.linescore_home.iter().sum(); + + assert_little_league_balance(game_key, "away", away_runs, &result.away_little_league); + assert_little_league_balance(game_key, "home", home_runs, &result.home_little_league); + + for player in result.player_stats.values() { + assert_batting_invariants(game_key, player); + if let Some(pitching) = &player.pitching { + assert_pitching_invariants(game_key, &player.player_id, pitching); + } + } + + assert_non_negative_batting(game_key, "away team", &result.away_batting); + assert_non_negative_batting(game_key, "home team", &result.home_batting); + assert_non_negative_pitching(game_key, "away team", &result.away_pitching); + assert_non_negative_pitching(game_key, "home team", &result.home_pitching); + assert_non_negative_little_league(game_key, "away team", &result.away_little_league); + assert_non_negative_little_league(game_key, "home team", &result.home_little_league); +} + +fn assert_standard_team_totals(game_key: &str, result: &GameResult) { + assert_team_batting_totals( + game_key, + "away", + &result.away_id, + &result.away_batting, + &result.player_stats, + ); + assert_team_batting_totals( + game_key, + "home", + &result.home_id, + &result.home_batting, + &result.player_stats, + ); + assert_team_pitching_totals( + game_key, + "away", + &result.away_id, + &result.away_pitching, + &result.player_stats, + ); + assert_team_pitching_totals( + game_key, + "home", + &result.home_id, + &result.home_pitching, + &result.player_stats, + ); + + assert_eq!( + result.away_pitching.bf, result.home_batting.pa, + "{game_key}: away pitching BF should equal home batting PA" + ); + assert_eq!( + result.home_pitching.bf, result.away_batting.pa, + "{game_key}: home pitching BF should equal away batting PA" + ); +} + +fn assert_team_batting_totals( + game_key: &str, + side: &str, + team_id: &str, + totals: &BattingStats, + player_stats: &BTreeComparablePlayerStats, +) { + let mut summed = BattingStats::default(); + for player in player_stats + .values() + .filter(|player| player.team_id == team_id) + { + summed.pa += player.batting.pa; + summed.ab += player.batting.ab; + summed.hits += player.batting.hits; + summed.bb += player.batting.bb; + summed.hbp += player.batting.hbp; + summed.ci += player.batting.ci; + summed.k += player.batting.k; + summed.runs += player.batting.runs; + summed.rbi += player.batting.rbi; + } + + assert_eq!(summed.pa, totals.pa, "{game_key}: {side} batting PA total"); + assert_eq!(summed.ab, totals.ab, "{game_key}: {side} batting AB total"); + assert_eq!( + summed.hits, totals.hits, + "{game_key}: {side} batting hits total" + ); + assert_eq!(summed.bb, totals.bb, "{game_key}: {side} batting BB total"); + assert_eq!( + summed.hbp, totals.hbp, + "{game_key}: {side} batting HBP total" + ); + assert_eq!(summed.ci, totals.ci, "{game_key}: {side} batting CI total"); + assert_eq!(summed.k, totals.k, "{game_key}: {side} batting K total"); + assert_eq!( + summed.runs, totals.runs, + "{game_key}: {side} batting runs total" + ); + assert_eq!( + summed.rbi, totals.rbi, + "{game_key}: {side} batting RBI total" + ); +} + +fn assert_team_pitching_totals( + game_key: &str, + side: &str, + team_id: &str, + totals: &PitchingStats, + player_stats: &BTreeComparablePlayerStats, +) { + let mut summed = PitchingStats::default(); + for pitching in player_stats + .values() + .filter(|player| player.team_id == team_id) + .filter_map(|player| player.pitching.as_ref()) + { + summed.pitches += pitching.pitches; + summed.balls += pitching.balls; + summed.k += pitching.k; + summed.bb += pitching.bb; + summed.hbp += pitching.hbp; + summed.hits_allowed += pitching.hits_allowed; + summed.hr_allowed += pitching.hr_allowed; + summed.runs_allowed += pitching.runs_allowed; + summed.earned_runs_allowed += pitching.earned_runs_allowed; + summed.outs_recorded += pitching.outs_recorded; + summed.bf += pitching.bf; + summed.bip += pitching.bip; + } + + assert_eq!( + summed.pitches, totals.pitches, + "{game_key}: {side} pitching pitches total" + ); + assert_eq!( + summed.balls, totals.balls, + "{game_key}: {side} pitching balls total" + ); + assert_eq!(summed.k, totals.k, "{game_key}: {side} pitching K total"); + assert_eq!(summed.bb, totals.bb, "{game_key}: {side} pitching BB total"); + assert_eq!( + summed.hbp, totals.hbp, + "{game_key}: {side} pitching HBP total" + ); + assert_eq!( + summed.hits_allowed, totals.hits_allowed, + "{game_key}: {side} pitching hits allowed total" + ); + assert_eq!( + summed.hr_allowed, totals.hr_allowed, + "{game_key}: {side} pitching HR allowed total" + ); + assert_eq!( + summed.runs_allowed, totals.runs_allowed, + "{game_key}: {side} pitching runs allowed total" + ); + assert_eq!( + summed.earned_runs_allowed, totals.earned_runs_allowed, + "{game_key}: {side} pitching earned runs allowed total" + ); + assert_eq!( + summed.outs_recorded, totals.outs_recorded, + "{game_key}: {side} pitching outs total" + ); + assert_eq!(summed.bf, totals.bf, "{game_key}: {side} pitching BF total"); + assert_eq!( + summed.bip, totals.bip, + "{game_key}: {side} pitching BIP total" + ); +} + +type BTreeComparablePlayerStats = std::collections::HashMap; + +fn assert_batting_invariants(game_key: &str, player: &PlayerGameStats) { + let b = &player.batting; + assert_non_negative_batting(game_key, &player.player_id, b); + + assert_eq!( + b.ab + b.bb + b.hbp + b.ci + b.sac_fly + b.sac_bunt, + b.pa, + "{game_key}: player {} PA invariant failed", + player.player_id + ); + assert_eq!( + b.hits, + b.singles + b.doubles + b.triples + b.home_runs, + "{game_key}: player {} hit invariant failed", + player.player_id + ); + assert_eq!( + b.tb, + b.singles + 2 * b.doubles + 3 * b.triples + 4 * b.home_runs, + "{game_key}: player {} total bases invariant failed", + player.player_id + ); + assert_eq!( + b.xbh, + b.doubles + b.triples + b.home_runs, + "{game_key}: player {} extra-base hits invariant failed", + player.player_id + ); +} + +fn assert_pitching_invariants(game_key: &str, player_id: &str, pitching: &PitchingStats) { + assert_non_negative_pitching(game_key, player_id, pitching); + let expected_ip = expected_ip_display(pitching.outs_recorded); + assert_eq!( + pitching.ip_display.as_deref(), + Some(expected_ip.as_str()), + "{game_key}: pitcher {player_id} IP display mismatch" + ); + if let Some(ip) = pitching.ip { + let expected = f64::from(pitching.outs_recorded) / 3.0; + assert!( + (ip - expected).abs() < f64::EPSILON, + "{game_key}: pitcher {player_id} IP value mismatch: got {ip}, expected {expected}" + ); + } + assert!( + pitching.strikes_swinging + pitching.strikes_looking + pitching.fouls <= pitching.pitches, + "{game_key}: pitcher {player_id} has more tracked strikes/fouls than pitches" + ); +} + +fn assert_little_league_balance( + game_key: &str, + side: &str, + runs_total: i32, + ll: &LittleLeagueStats, +) { + assert_eq!( + ll.runs_on_bip + ll.runs_passive, + runs_total, + "{game_key}: {side} LL run balance failed" + ); +} + +fn assert_serializes(game_key: &str, result: &GameResult) { + serde_json::to_value(result) + .unwrap_or_else(|err| panic!("{game_key}: GameResult JSON serialization failed: {err}")); +} + +fn expected_ip_display(outs: i32) -> String { + format!("{}.{}", outs / 3, outs % 3) +} + +fn assert_non_negative_batting(game_key: &str, label: &str, b: &BattingStats) { + for (field, value) in [ + ("pa", b.pa), + ("ab", b.ab), + ("k", b.k), + ("k_looking", b.k_looking), + ("k_swinging", b.k_swinging), + ("bb", b.bb), + ("hbp", b.hbp), + ("ci", b.ci), + ("singles", b.singles), + ("doubles", b.doubles), + ("triples", b.triples), + ("home_runs", b.home_runs), + ("sac_fly", b.sac_fly), + ("sac_bunt", b.sac_bunt), + ("fc", b.fc), + ("roe", b.roe), + ("gidp", b.gidp), + ("rbi", b.rbi), + ("runs", b.runs), + ("ground_balls", b.ground_balls), + ("fly_balls", b.fly_balls), + ("line_drives", b.line_drives), + ("pop_ups", b.pop_ups), + ("hard_hit_balls", b.hard_hit_balls), + ("pitches_seen", b.pitches_seen), + ("qab", b.qab), + ("competitive_ab", b.competitive_ab), + ("sb", b.sb), + ("cs", b.cs), + ("hits", b.hits), + ("tb", b.tb), + ("xbh", b.xbh), + ] { + assert!( + value >= 0, + "{game_key}: {label} batting field {field} was negative: {value}" + ); + } +} + +fn assert_non_negative_pitching(game_key: &str, label: &str, p: &PitchingStats) { + for (field, value) in [ + ("pitches", p.pitches), + ("balls", p.balls), + ("strikes_swinging", p.strikes_swinging), + ("strikes_looking", p.strikes_looking), + ("fouls", p.fouls), + ("k", p.k), + ("bb", p.bb), + ("hbp", p.hbp), + ("hits_allowed", p.hits_allowed), + ("hr_allowed", p.hr_allowed), + ("runs_allowed", p.runs_allowed), + ("earned_runs_allowed", p.earned_runs_allowed), + ("outs_recorded", p.outs_recorded), + ("bf", p.bf), + ("bip", p.bip), + ("ground_balls", p.ground_balls), + ("fly_balls", p.fly_balls), + ("line_drives", p.line_drives), + ("pop_ups", p.pop_ups), + ("first_pitch_strikes", p.first_pitch_strikes), + ("wp", p.wp), + ] { + assert!( + value >= 0, + "{game_key}: {label} pitching field {field} was negative: {value}" + ); + } +} + +fn assert_non_negative_little_league(game_key: &str, label: &str, ll: &LittleLeagueStats) { + for (field, value) in [ + ("runs_on_bip", ll.runs_on_bip), + ("runs_passive", ll.runs_passive), + ("wp", ll.wp), + ("pb", ll.pb), + ("cs", ll.cs), + ("steals_of_home", ll.steals_of_home), + ("bb_loaded", ll.bb_loaded), + ("hbp_loaded", ll.hbp_loaded), + ] { + assert!( + value >= 0, + "{game_key}: {label} little-league field {field} was negative: {value}" + ); + } + + for value in &ll.pitches_between_bip { + assert!( + *value >= 0, + "{game_key}: {label} pitches_between_bip contained a negative value: {value}" + ); + } + for value in &ll.pitches_between_bip_pitching { + assert!( + *value >= 0, + "{game_key}: {label} pitches_between_bip_pitching contained a negative value: {value}" + ); + } +}