sba-scouting/rust/src/db/models.rs
Cal Corum 2005307b7a Implement Phase 1 foundation: DB schema, queries, and config integration
Wire up the full data pipeline for the Rust TUI rewrite:
- SQL schema creation for all 9 tables with correct types, FKs, and constraints
- 20 async query functions (teams, players, cards, lineups, sync status, cache)
- Config loading via figment integrated into main.rs startup flow
- App struct now holds SqlitePool and Settings for screen access
- Roster aggregate query and Lineup JSON helper methods
- Added csv, sha2, regex crates for upcoming phases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:17:36 -06:00

289 lines
8.2 KiB
Rust

use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
// =============================================================================
// Core Entities (synced from league API)
// =============================================================================
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct Team {
pub id: i64,
pub abbrev: String,
pub short_name: String,
pub long_name: String,
pub season: i64,
pub manager1_name: Option<String>,
pub manager2_name: Option<String>,
pub gm_discord_id: Option<String>,
pub gm2_discord_id: Option<String>,
pub division_id: Option<i64>,
pub division_name: Option<String>,
pub league_abbrev: Option<String>,
pub thumbnail: Option<String>,
pub color: Option<String>,
pub dice_color: Option<String>,
pub stadium: Option<String>,
pub salary_cap: Option<f64>,
pub synced_at: Option<NaiveDateTime>,
}
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct Player {
pub id: i64,
pub name: String,
pub season: i64,
pub team_id: Option<i64>,
pub swar: Option<f64>,
pub card_image: Option<String>,
pub card_image_alt: Option<String>,
pub headshot: Option<String>,
pub vanity_card: Option<String>,
pub pos_1: Option<String>,
pub pos_2: Option<String>,
pub pos_3: Option<String>,
pub pos_4: Option<String>,
pub pos_5: Option<String>,
pub pos_6: Option<String>,
pub pos_7: Option<String>,
pub pos_8: Option<String>,
pub hand: Option<String>,
pub injury_rating: Option<String>,
pub il_return: Option<String>,
pub demotion_week: Option<i64>,
pub strat_code: Option<String>,
pub bbref_id: Option<String>,
pub sbaplayer_id: Option<i64>,
pub last_game: Option<String>,
pub last_game2: Option<String>,
pub synced_at: Option<NaiveDateTime>,
}
impl Player {
pub fn positions(&self) -> Vec<&str> {
[
self.pos_1.as_deref(),
self.pos_2.as_deref(),
self.pos_3.as_deref(),
self.pos_4.as_deref(),
self.pos_5.as_deref(),
self.pos_6.as_deref(),
self.pos_7.as_deref(),
self.pos_8.as_deref(),
]
.into_iter()
.flatten()
.collect()
}
pub fn is_pitcher(&self) -> bool {
self.positions()
.iter()
.any(|p| matches!(*p, "SP" | "RP" | "CP"))
}
pub fn is_batter(&self) -> bool {
self.positions()
.iter()
.any(|p| matches!(*p, "C" | "1B" | "2B" | "3B" | "SS" | "LF" | "CF" | "RF" | "DH"))
}
}
// =============================================================================
// Card Data (imported from Strat-o-Matic)
// =============================================================================
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct BatterCard {
pub id: i64,
pub player_id: i64,
// vs Left-Handed Pitchers
pub so_vlhp: f64,
pub bb_vlhp: f64,
pub hit_vlhp: f64,
pub ob_vlhp: f64,
pub tb_vlhp: f64,
pub hr_vlhp: f64,
pub dp_vlhp: f64,
// vs Right-Handed Pitchers
pub so_vrhp: f64,
pub bb_vrhp: f64,
pub hit_vrhp: f64,
pub ob_vrhp: f64,
pub tb_vrhp: f64,
pub hr_vrhp: f64,
pub dp_vrhp: f64,
// Ballpark modifiers
pub bphr_vlhp: f64,
pub bphr_vrhp: f64,
pub bp1b_vlhp: f64,
pub bp1b_vrhp: f64,
// Running game
pub stealing: Option<String>,
pub steal_rating: Option<String>,
pub speed: Option<i64>,
// Batting extras
pub bunt: Option<String>,
pub hit_run: Option<String>,
// Fielding
pub fielding: Option<String>,
// Catcher-specific
pub catcher_arm: Option<i64>,
pub catcher_pb: Option<i64>,
pub catcher_t: Option<i64>,
// Computed ratings
pub rating_vl: Option<f64>,
pub rating_vr: Option<f64>,
pub rating_overall: Option<f64>,
// Import metadata
pub imported_at: Option<NaiveDateTime>,
pub source: Option<String>,
}
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct PitcherCard {
pub id: i64,
pub player_id: i64,
// vs Left-Handed Batters
pub so_vlhb: f64,
pub bb_vlhb: f64,
pub hit_vlhb: f64,
pub ob_vlhb: f64,
pub tb_vlhb: f64,
pub hr_vlhb: f64,
pub dp_vlhb: f64,
pub bphr_vlhb: f64,
pub bp1b_vlhb: f64,
// vs Right-Handed Batters
pub so_vrhb: f64,
pub bb_vrhb: f64,
pub hit_vrhb: f64,
pub ob_vrhb: f64,
pub tb_vrhb: f64,
pub hr_vrhb: f64,
pub dp_vrhb: f64,
pub bphr_vrhb: f64,
pub bp1b_vrhb: f64,
// Pitcher attributes
pub hold_rating: Option<i64>,
pub endurance_start: Option<i64>,
pub endurance_relief: Option<i64>,
pub endurance_close: Option<i64>,
pub fielding_range: Option<i64>,
pub fielding_error: Option<i64>,
pub wild_pitch: Option<i64>,
pub balk: Option<i64>,
pub batting_rating: Option<String>,
// Computed ratings
pub rating_vlhb: Option<f64>,
pub rating_vrhb: Option<f64>,
pub rating_overall: Option<f64>,
// Import metadata
pub imported_at: Option<NaiveDateTime>,
pub source: Option<String>,
}
// =============================================================================
// Transactions (synced from league API)
// =============================================================================
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct Transaction {
pub id: i64,
pub season: i64,
pub week: i64,
pub move_id: String,
pub player_id: i64,
pub from_team_id: i64,
pub to_team_id: i64,
pub cancelled: bool,
pub frozen: bool,
pub synced_at: Option<NaiveDateTime>,
}
// =============================================================================
// Roster (aggregate view, not a DB table)
// =============================================================================
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Roster {
pub majors: Vec<Player>,
pub minors: Vec<Player>,
pub il: Vec<Player>,
}
// =============================================================================
// User Data (local only)
// =============================================================================
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct Lineup {
pub id: i64,
pub name: String,
pub description: Option<String>,
pub lineup_type: String,
pub batting_order: Option<String>, // JSON string
pub positions: Option<String>, // JSON string
pub starting_pitcher_id: Option<i64>,
pub created_at: Option<NaiveDateTime>,
pub updated_at: Option<NaiveDateTime>,
}
impl Lineup {
pub fn batting_order_vec(&self) -> Vec<i64> {
self.batting_order
.as_deref()
.and_then(|s| serde_json::from_str(s).ok())
.unwrap_or_default()
}
pub fn positions_map(&self) -> std::collections::HashMap<String, i64> {
self.positions
.as_deref()
.and_then(|s| serde_json::from_str(s).ok())
.unwrap_or_default()
}
pub fn set_batting_order(&mut self, order: &[i64]) {
self.batting_order = serde_json::to_string(order).ok();
}
pub fn set_positions(&mut self, positions: &std::collections::HashMap<String, i64>) {
self.positions = serde_json::to_string(positions).ok();
}
}
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct MatchupCache {
pub id: i64,
pub batter_id: i64,
pub pitcher_id: i64,
pub rating: f64,
pub tier: Option<String>,
pub details: Option<String>, // JSON string
pub computed_at: Option<NaiveDateTime>,
pub weights_hash: Option<String>,
}
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct StandardizedScoreCache {
pub id: i64,
pub batter_card_id: Option<i64>,
pub pitcher_card_id: Option<i64>,
pub split: String,
pub total_score: f64,
pub stat_scores: String, // JSON string
pub computed_at: Option<NaiveDateTime>,
pub weights_hash: Option<String>,
pub league_stats_hash: Option<String>,
}
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct SyncStatus {
pub id: i64,
pub entity_type: String,
pub last_sync: Option<NaiveDateTime>,
pub last_sync_count: Option<i64>,
pub last_error: Option<String>,
}