#!/usr/bin/perl -w # AUTHORS # Sean Forman # Hans Van Slooten # $Date: 2017-03-02 17:30:07 -0500 (Thu, 02 Mar 2017) $ # $Author: hvs $ # $Rev: 49577 $ # $HeadURL: http://svn.sports-reference.com/svn/br_repos/br/trunk/leagues/scripts/split.cgi $ # Copyright 2000-2017, SPORTS REFERENCE, INC. All rights reserved. use Carp; use CGI; use Data::Dumper; use FindBin qw($Bin); use Modern::Perl '2010'; use lib "$Bin/../lib"; use SR::Cache; use SR::Defaults; use SR::Statline; use SRlocal::DB; use SRlocal::Defaults; use SRlocal::Constants; use SRlocal::Stats; use SRlocal::Leagues; use SRlocal::Leagues::Fetch; use SRlocal::Leagues::Model; use SRlocal::Formatting; use SRlocal::Templates; chomp($0); #### [] Starting: "$Bin/$0 " . join(' ',@ARGV) MAIN: { my $do_test_retro = $SRlocal::Constants::TEST_RETRO_SPLITS; # Connect to the database. our $dbh = get_site_db_connection($Bin); our $dbh_retro = get_site_db_connection( $Bin, 'retro' ) if ($do_test_retro); our $site_params_ref = get_site_params($Bin); our $srtemplates = new SRlocal::Templates; my $q = new CGI; my $split_type = url_regex( $q, 't', qr/[pbf]/, 'f' ); my $lg_id = url_regex( $q, 'lg', qr/(AL|NL|ML|MLB)/, 'NL' ); my $year_id = url_integer( $q, 'year', $CURRENT_YEAR ); # check the cache for the data. my $memd_key = "$split_type:$lg_id:$year_id"; my $page_data = cache_get( 'leagues:split', $memd_key ); # if we got data, print it. if ( is_nonempty($page_data) ) { print $q->header( -type => 'text/html', -charset => 'utf-8', -SR_SRC => 'memcached' ); print $page_data; $dbh->disconnect; exit; } my $is_per_162 = is_empty_zero( $q->param('per162') ); # Determine if we have a batting, fielding, or pitching splits, # batting is default. my ( $split_type_long, $split_type_name, $table_id, $per_162_adjustment_note ); $lg_id = $q->param('lg') ? $q->param('lg') : 'NL'; $lg_id = $lg_id eq 'MLB' ? 'ML' : $lg_id; if ( is_nonempty_print( $q->param('t') ) eq 'p' ) { $table_id = 'pitching_splits'; $split_type = 'p'; $split_type_long = 'pitch'; $split_type_name = 'Pitching'; $per_162_adjustment_note = ' (per 162 games or 650 PAs)'; } elsif ( is_nonempty_print( $q->param('t') ) eq 'f' ) { $table_id = 'fielding_splits'; $split_type = 'f'; $split_type_long = 'field'; $split_type_name = 'Fielding'; $per_162_adjustment_note = ' (per 162 games or 1,450 Innings)'; } else { $table_id = 'batting_splits'; $split_type = 'b'; $split_type_long = 'bat'; $split_type_name = 'Batting'; $per_162_adjustment_note = ' (per 162 games or 650 PAs)'; } ######## CUSTOMIZE # for the year, we use the one given or the player's last year if # none if given. If a year is less than the first retro year, we # then use the first retro year. $STATLINE_DEFAULTS{split_name}{span} = $TRUE; $STATLINE_DEFAULTS{split_name}{class} = 'tooltip'; if ( is_empty_zero( $q->param('year') ) < $FIRST_YEAR_RETRO ) { $year_id = $FIRST_YEAR_RETRO; } elsif ( is_empty_zero( $q->param('year') ) > $CURRENT_YEAR ) { $year_id = $CURRENT_YEAR; } my $lgs = fetch_leagues( $dbh, { lg_id => $lg_id, year_id => $year_id } ); my $lg = $lgs->[0]; # this is needed for the js to work for guys like O'Neill. my $lg_name = $SRlocal::Constants::LEAGUE_NAME_OF{$lg_id}; my $lg_link = sprintf qq{/leagues/%s/%s.shtml}, $lg_id eq 'ML' ? 'MLB' : $lg_id, $year_id; ##################################################################################### my $header_end = $split_type_name . ' Splits'; my $page_title = "$year_id " . $header_end; $page_title .= $is_per_162 ? $per_162_adjustment_note : $EMPTY_STR; my $description = 'Complete splits including home/road, lefty/righty, clutch situations, vs. opponent, by month, by role, and many more'; my @yah = ( qq{Leagues}, qq{$year_id $lg_name}, qq{$page_title} ); my $title = $lg_name . ' ' . $page_title; my %model = ( lg_id => $lg_id eq 'ML' ? 'MLB' : $lg_id, year_id => $year_id, first_year_id => $lg->{first_year_ID}, last_year_id => $lg->{last_year_ID}, season => $lg->{year_ID}, # for the page title file_name_suffix => '', header_end => $header_end, ); $model{header} = { page_title => $title, title => $title, page_url => $SRlocal::Constants::SITE_URL . "/leagues/split.cgi?t=$split_type&lg=$lg_id&year=$year_id", you_are_here => generate_you_are_here( \@yah ), qi_section => 'leagues', use_default_keywords => $FALSE, page_description => $description, }; SRlocal::Leagues::_stock_league_other_outputs( $dbh, $lg ); $dbh->disconnect if ($do_test_retro); $dbh = $dbh_retro if ($do_test_retro); ## Populate the model details for the league header build_league_summary_model( $dbh, $srtemplates, \%model, 'split', $split_type_long ); $model{inner_nav} = build_league_year_menu( $dbh, \%model, lc($split_type_name) ); my $base_link = sprintf( qq{split.cgi?t=%s&lg=%s&year=%s}, $split_type, $lg_id, $year_id ); my $incomplete_data_note = $EMPTY_STR; if ( $year_id <= 1973 ) { $incomplete_data_note = <<"END_HTML"; First column (I) indicates when a split may be incomplete due to missing play-by-play data. For all seasons after 1973, the play-by-play data is complete (summary of missing data for pre-1974). END_HTML } # We print out a note showing either a link to convert to 162 # games or a link to convert back. my $per_162_note = <<"END_HTML";

View splits adjusted to be per 162 player games

END_HTML if ($is_per_162) { $per_162_note = <<"END_HTML";

Splits are prorated to 162 player games.
View unadjusted splits

END_HTML } push @{ $model{pagecontent} }, <<"END_HTML";
Please note that GS refers to the number of team games played in this split (for full-game splits like Day/Night, Home/Away), and G is the number of player games that went into this split.
$per_162_note $incomplete_data_note
END_HTML # no hit in the cache, so we build the page in a # subroutine and return it to be printed here. _get_lg_splits( $dbh, \%model, $split_type_long, $lg_id, $year_id, $is_per_162 ); ## Generate the page $page_data = $srtemplates->process( 'Pages/League.tt2', \%model ); ## open file for output print $q->header( -type => 'text/html', -charset => 'utf-8', -SR_SRC => 'build' ); print $page_data; cache_set( 'leagues:split', $memd_key, $page_data ); $dbh->disconnect; } ################################################################### # Usage : _get_lg_splits # Purpose : returns tables of splits for a particular lg or league # Returns : a large set of html tables. # Parameters : dbh, lg_id, year_id (YYYY or Career) # Throws : like Johnny Damon # See Also : # Comments : We are going to change the bsplit_bit calls here to be # tooltips like the rest of the site rather than in place divs. This # is more consistent with the rest of the site and also should make it # a bit easier to do here. ################################################################### sub _get_lg_splits { my ( $dbh, $model, $type, $lg_id, $year_id, $is_per_162 ) = @_; my $return = $EMPTY_STR; my ( $query, @stats, @stats_extra ); if ( $type eq 'bat' ) { $query = <<"END_SQL"; SELECT group_id, subgroup_id, split_name, $defn_of_statlines{batting_simple}{select}, GS, ROE, "$year_id" AS year_id, tOPS as onbase_plus_slugging_vs_total, sOPS as onbase_plus_slugging_vs_lg, ROUND($SRlocal::Stats::batting_avg_bip,3) AS batting_avg_bip, IF(incomplete=1,'I','') AS incomplete_split FROM tbsplit AS split_table WHERE lg_id=? and year_game=? and batting_team_id='TOT' ORDER BY spo ASC END_SQL $query =~ s/(AB + IFNULL(BB,0) + IFNULL(HBP,0) + IFNULL(SF,0) + IFNULL(SH,0))/PA/; @stats = ( qw(incomplete_split split_name), @{ $defn_of_statlines{batting_simple}{list} }, qw(batting_avg_bip onbase_plus_slugging_vs_total onbase_plus_slugging_vs_lg) ); @stats_extra = (); delete_stat_from_statline( 'onbase_plus_slugging_vs_lg', \@stats ) if ( $year_id eq 'Career' ); add_stat_to_statline_after_stat( 'GS', \@stats, 'G' ); add_stat_to_statline_after_stat( 'ROE', \@stats, 'IBB' ); } elsif ( $type eq 'pitch' ) { # Set up the queries for a single year or a career. $query = <<"END_SQL"; SELECT group_id, subgroup_id, split_name, WP, ROE, ER, IPouts, $defn_of_statlines{pitching_splits}{select}, $defn_of_statlines{batting_simple}{select}, "$year_id" AS year_id, tOPS as onbase_plus_slugging_vs_total, sOPS as onbase_plus_slugging_vs_lg, ROUND($SRlocal::Stats::batting_avg_bip,3) AS batting_avg_bip, IF(incomplete=1,'I','') AS incomplete_split FROM tpsplit AS split_table WHERE lg_id=? AND year_game=? AND pitching_team_id='TOT' ORDER BY spo ASC END_SQL $query =~ s/SV/S AS SV/; $query =~ s/BFP/BF/; $query =~ s/PA/BF/; $query =~ s/ RBI/ R/; $query =~ s/ WP/ NULL AS WP/; $query =~ s/(AB + IFNULL(BB,0) + IFNULL(HBP,0) + IFNULL(SF,0) + IFNULL(SH,0))/BF/; @stats_extra = ( qw(incomplete_split split_name), @{ $defn_of_statlines{pitching_splits}{list} }, ); @stats = ( qw(incomplete_split split_name), @{ $defn_of_statlines{batting_simple}{list} }, qw(batting_avg_bip onbase_plus_slugging_vs_total onbase_plus_slugging_vs_lg) ); add_stat_to_statline_after_stat( 'ROE', \@stats, 'IBB' ); delete_stat_from_statline( 'RBI', \@stats ); delete_stat_from_statline( 'onbase_plus_slugging_vs_lg', \@stats ) if ( $year_id eq 'Career' ); add_stat_to_statline_after_stat( 'strikeouts_per_base_on_balls', \@stats, 'SO' ); } else { return $EMPTY_STR; } # remove teh incomplete column for pre1974 seasons if ( $year_id > 1973 ) { delete_stat_from_statline( 'incomplete_split', \@stats ); delete_stat_from_statline( 'incomplete_split', \@stats_extra ); } # We have to get the joint stats here, so we can do the adjusted # to 162 games output. my %stats_joint; foreach my $stat (@stats) { $stats_joint{$stat}++; } foreach my $stat (@stats_extra) { $stats_joint{$stat}++; } my @stats_joint = keys %stats_joint; my $sth = db_prepare_and_execute( $dbh, $query, $lg_id, $year_id ); my $line_count = 0; my $last_subgroup_id = $EMPTY_STR; my $have_as_dh = $FALSE; my $have_close_and_late = $FALSE; my $is_per_162_type = $EMPTY_STR; my $return_extra = $EMPTY_STR; my $last_gf = $EMPTY_STR; my @stats_default = @stats; my $table_defn; my $table_defn_extra; while ( my $tmp = $sth->fetchrow_hashref ) { @stats = @stats_default; if (( $type eq 'pitch' ) && ( ( $tmp->{group_id} =~ m/(catch)/ ) || ( $tmp->{subgroup_id} eq 'innng' ) ) ) { add_stat_to_statline_after_stat( 'earned_run_avg', \@stats, 'G' ); add_stat_to_statline_after_stat( 'ER', \@stats, 'G' ); add_stat_to_statline_after_stat( 'IP', \@stats, 'G' ); $tmp->{IP} = sprintf qq{%5.1f}, $tmp->{IPouts} / 3; $tmp->{earned_run_avg} = sprintf qq{%5.2f}, 27 * $tmp->{ER} / $tmp->{IPouts} if ( is_empty_zero( $tmp->{IPouts} ) ); } # for the per 162, we need to adjust the columns, so that they # properly handle the true G or PA value shown. if ($is_per_162) { $is_per_162_type = is_empty( $tmp->{GS} ) ? 'per_650_PA' : 'per_162_G'; delete_stat_from_statline( 'PA_actual', \@stats ); delete_stat_from_statline( 'G_actual', \@stats ); add_stat_to_statline_after_stat( 'PA_actual', \@stats, 'PA' ) if ( $is_per_162_type eq 'per_650_PA' ); add_stat_to_statline_after_stat( 'G_actual', \@stats, 'G' ) if ( $is_per_162_type eq 'per_162_G' ); } # We have changed subgroups which means we need to close the # last table and open up the next one. if ( $tmp->{subgroup_id} ne $last_subgroup_id ) { if ( $line_count > 0 ) { # We print out a note after the table if needed. my $note = SRlocal::Constants::_get_split_subgroup_note_after( $last_subgroup_id, { have_as_dh => $have_as_dh, have_close_and_late => $have_close_and_late, is_running_team => $FALSE } ); if (@stats_extra) { $table_defn_extra->{footer} = $note; } else { $table_defn->{footer} = $note; } # Close the last table, but only if we've seen lines of splits. $return .= generate_statline_footer_wrap($table_defn) if ( $line_count > 0 ); $return_extra .= generate_statline_footer_wrap($table_defn_extra) if (@stats_extra); push @{ $model->{pagecontent} }, $return; push @{ $model->{pagecontent} }, $return_extra if ( (@stats_extra) && ( is_nonempty($last_gf) ) ); } $return = $return_extra = $EMPTY_STR; my $table_id = $tmp->{subgroup_id}; my $table_title = is_nonempty( $SRlocal::Constants::SPLIT_SUBGROUP_NAME{ $tmp->{subgroup_id} } ) ? $SRlocal::Constants::SPLIT_SUBGROUP_NAME{ $tmp->{subgroup_id} } : $tmp->{subgroup_id}; my @other_links = (); push( @other_links, $SRlocal::Constants::SPLIT_SUBGROUP_NOTE{ $tmp->{subgroup_id} } ) if is_nonempty( $SRlocal::Constants::SPLIT_SUBGROUP_NOTE{ $tmp->{subgroup_id} } ); $table_defn = { table_id => $table_id, title => $table_title, section_heading_list => \@other_links, table_cols_to_freeze => 2, }; $return .= generate_statline_header_wrap($table_defn); $return .= generate_statline_header( \@stats ); if (@stats_extra) { $table_defn_extra = { table_id => $table_id . '_extra', title => $table_title . ' -- Game-Level', section_heading_list => \@other_links, table_cols_to_freeze => 2, }; $return_extra .= generate_statline_header_wrap($table_defn_extra); $return_extra .= generate_statline_header( \@stats_extra ); } $have_as_dh = $FALSE; $have_close_and_late = $FALSE; } # We store these because we may need to print out an explanation later. $have_as_dh = $TRUE if ( $tmp->{split_name} =~ /as DH/ ); $have_close_and_late = $TRUE if ( $tmp->{split_name} =~ /Late.*Close/ ); # If this is a per 162 game split, we need to modify all of # the columns. if ($is_per_162) { # our adjustment_factor depends on whether GS is null or # not. If gs is null we use 650 PA's if not we use 162 # games. my $adjustment_factor = $is_per_162_type eq 'per_162_G' && ( $tmp->{GS} > 0 ) ? sprintf( "%.4f", 162.0 / $tmp->{GS} ) : $is_per_162_type eq 'per_650_PA' && ( $tmp->{PA} > 0 ) ? sprintf( "%.4f", 650.0 / $tmp->{PA} ) : 1.0; if ( $type eq 'pitch' ) { # For pitching we use our 68/(G+GS) factor. $adjustment_factor = $is_per_162_type eq 'per_162_G' && ( $tmp->{GS} + $tmp->{GS} > 0 ) ? sprintf( "%.4f", 24.0 / ( $tmp->{GS} ) ) : $is_per_162_type eq 'per_650_PA' && ( $tmp->{PA} > 0 ) ? sprintf( "%.4f", 650.0 / $tmp->{PA} ) : 1.0; } $tmp->{PA_actual} = $tmp->{PA}; $tmp->{G_actual} = $tmp->{G}; foreach my $stat (@stats_joint) { # If this isn't a ratio stat and if it has only # numbers, we when multiply it by the adjustment # factor. if ( ( !$STATLINE_DEFAULTS{$stat}{ratio} ) && is_nonempty( $tmp->{$stat} ) && ( $tmp->{$stat} =~ /^[0-9\.]+$/ ) && ( $stat ne 'PA_actual' ) && ( $stat ne 'G_actual' ) && ( $stat ne 'year_ID' ) && ( $stat ne 'split_name' ) ) { $tmp->{$stat} = sprintf( qq{%d}, $tmp->{$stat} * $adjustment_factor ); } } } $tmp->{split_name_original} = $tmp->{split_name}; $tmp->{split_name_endpoint} = qq{/play-index/split_stats_lg.cgi?full=0¶ms=$tmp->{subgroup_id}|$tmp->{split_name_original}|$lg_id|$year_id|$type|AB|$is_per_162_type}; $tmp->{split_name} = SRlocal::Constants::_clean_split_name( $dbh, $tmp, $year_id ); # We divide the GS by 9 to get the team's actual games played # if this is a batting split. if ( ( $type eq 'bat' ) && ( is_nonempty( $tmp->{GS} ) ) && ( $tmp->{split_name_original} !~ /^Bat/ ) && ( $tmp->{split_name_original} !~ /^Ages/ ) ) { $tmp->{GS} = sprintf "%d", $tmp->{GS} / 9; } $return .= generate_statline( \@stats, $tmp ); $tmp->{split_name_endpoint} = qq{/play-index/split_stats_lg.cgi?full=0¶ms=$tmp->{subgroup_id}|$tmp->{split_name_original}|$lg_id|$year_id|$type|IP|$is_per_162_type}; $return_extra .= generate_statline( \@stats_extra, $tmp ); $line_count++; $last_subgroup_id = $tmp->{subgroup_id}; $last_gf = $tmp->{GF}; } # Close the last table, but only if we've seen lines of splits. $return .= generate_statline_footer_wrap($table_defn) if ( $line_count > 0 ); $return_extra .= generate_statline_footer_wrap($table_defn_extra) if (@stats_extra); push @{ $model->{pagecontent} }, $return; push @{ $model->{pagecontent} }, $return_extra if ( (@stats_extra) && ( is_nonempty($last_gf) ) ); return $model; }