File Coverage

blib/lib/Sport/Analytics/NHL/Tools.pm
Criterion Covered Total %
statement 215 265 81.1
branch 44 70 62.8
condition 40 74 54.0
subroutine 41 46 89.1
pod 30 30 100.0
total 370 485 76.2


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::Tools;
2              
3 65     65   331703 use v5.10.1;
  65         264  
4 65     65   314 use strict;
  65         129  
  65         1478  
5 65     65   433 use warnings FATAL => 'all';
  65         153  
  65         2261  
6              
7 65     65   336 use File::Find;
  65         141  
  65         4066  
8 65     65   392 use File::Path qw(make_path);
  65         141  
  65         3451  
9 65     65   16560 use POSIX qw(strftime);
  65         218747  
  65         478  
10              
11 65     65   78745 use Date::Parse;
  65         343359  
  65         7663  
12 65     65   13210 use JSON;
  65         231539  
  65         477  
13 65     65   16236 use List::MoreUtils qw(any);
  65         196227  
  65         581  
14              
15 65     65   52251 use Sport::Analytics::NHL::LocalConfig;
  65         334  
  65         8742  
16 65     65   449 use Sport::Analytics::NHL::Config;
  65         138  
  65         10958  
17 65     65   25108 use Sport::Analytics::NHL::DB;
  65         204  
  65         2330  
18 65     65   450 use Sport::Analytics::NHL::Util;
  65         140  
  65         4527  
19              
20 65     65   383 use parent 'Exporter';
  65         150  
  65         409  
21              
22             =head1 NAME
23              
24             Sport::Analytics::NHL::Tools - Commonly used routines that are system-dependent
25              
26             =head1 SYNOPSIS
27              
28             Commonly used routines that are specific to the Sport::Analytics::NHL ecosystem. For the independent stuff see Sport::Analytics::NHL::Util .
29              
30             use Sport::Analytics::NHL::Tools;
31             my $game = parse_nhl_game_id(2011020001);
32             my $season = get_season_from_date(20110202); # returns 2010
33             my $team = resolve('NY Rangers'); # returns NYR
34             #and so on
35              
36             Provides global variable $DB that can be used to store the MongoDB handle.
37              
38             =head1 GLOBAL VARIABLES
39              
40             =over 2
41              
42             =item $DB
43              
44             This global exported variable is used to hold an instance of a MongoDB connection.
45              
46             =item $CACHES
47              
48             This global exported hash reference is used to hold various information for caching purposes
49              
50             =back
51              
52             =head1 FUNCTIONS
53              
54             =over 2
55              
56             =item C
57              
58             Parses the SSSSTTNNNN nhl id
59             Arguments: the nhl game id
60             Returns: hashref with season, stage, season id and our SSSSTNNNN id
61              
62             =item C
63              
64             Parses the SSSSTNNNN our id
65             Arguments: our game id
66             Returns: hashref with season, stage, season id and our SSSSTNNNN id
67              
68             =item C
69              
70             Figures out the NHL season (start year) the given YYYYMMDD date refers to
71             Arguments: the YYYYMMDD date
72             Returns: the YYYY or YYYY-1 season
73              
74             =item C
75              
76             Returns the path to the schedule file in the filesystem
77             Arguments: the season and the root of the data (optional)
78             Returns: the path to the schedule file
79              
80             =item C
81              
82             Attempts to resolve the name of a team to the normalized 3-letter id
83             Arguments: the name of a team, optional no-db force flag
84             Returns: the 3-letter normalized id
85              
86             =item C
87              
88             Converts a game record obtained from the 'live' interface to a normalized form
89              
90             Arguments: the game record
91             Returns: the normalized game
92              
93             =item C
94              
95             Arranges the schedule obtained from the 'live' interface by dates
96              
97             Arguments: the schedule
98             Returns: hashref with keys of dates,
99             values of lists of normalized game records
100              
101             =item C
102              
103             Converts a game record obtained from the API interface to a normalized form
104              
105             Arguments: the game record
106             Returns: the normalized game
107              
108             =item C
109              
110             Arranges the schedule obtained from the API interface by dates
111              
112             Arguments: the schedule
113             Returns: hashref with keys of dates,
114             values of lists of normalized game records
115              
116             =item C
117              
118             Converts a game record obtained scraping the schedules to a normalized form
119              
120             Arguments: the game record
121             Returns: the normalized game
122              
123             =item C
124              
125             Arranges the schedule obtained by the scraper by dates
126              
127             Arguments: the schedule
128             Returns: hashref with keys of dates,
129             values of lists of normalized game records
130              
131             =item C
132              
133             Gets the list of the games scheduled for given dates using the file storage
134              
135             Arguments: the list of dates
136             Returns: the list of normalized game records
137              
138             =item C
139              
140             Gets the list of the games scheduled for given dates using the database
141              
142             Arguments: the list of dates
143             Returns: the list of normalized game records
144              
145             =item C
146              
147             Gets the list of the games scheduled for given dates
148              
149             Arguments: the list of dates
150             Returns: the list of normalized game records
151              
152             =item C
153              
154             Gets the earliest possible start and latest possible end for a season in format YYYY-MM-DD
155              
156             Arguments: the season
157             Returns: (YYYY-09-02,YYYY+1-09-01)
158              
159             =item C
160              
161             Creates and/or returns the game path for a given season, stage, season_id
162              
163             Arguments: season, stage, season_id, root storage dir (optional)
164             Returns: the storage path (created if necessary)
165              
166             =item C
167              
168             Reads the existing schedules for the given range of seasons
169              
170             Arguments: the hashref with first and last season of the range
171             Returns: the schedule data, hashref by season
172              
173             =item C
174              
175             Given the game path, produces our SSSSTNNNN game id
176              
177             Arguments: the game path
178             Returns the SSSSTNNNN id, or undef if the matching of the path failed
179              
180             =item C
181              
182             Find games already scraped into the filesystem and returns the game ids of them.
183              
184             Arguments: the season to look for
185             Returns: hashref of game ids as keys and 1s as values
186              
187             =back
188              
189             =cut
190              
191             our @EXPORT = qw(
192             $DB $CACHES
193             parse_nhl_game_id parse_our_game_id
194             resolve_team get_games_for_dates
195             get_season_from_date get_start_stop_date str3time
196             get_schedule_json_file make_game_path get_game_id_from_path
197             get_game_files_by_id
198             arrange_schedule_by_date convert_schedule_game read_schedules
199             read_existing_game_ids get_game_path_from_id
200             vocabulary_lookup normalize_penalty
201             is_noplay_event
202             set_roster_positions set_player_stat fix_playergoals
203             create_player_id_hash
204             print_events
205             );
206              
207             our $DB;
208             our $CACHES = {};
209              
210             sub parse_nhl_game_id ($) {
211              
212 13     13 1 1090 my $nhl_id = shift;
213              
214 13         151 $nhl_id =~ /^(\d{4})(\d{2})(\d{4})$/;
215             {
216 13         227 season => $1,
217             stage => $2 + 0,
218             season_id => $3,
219             game_id => $1*100000 + $2*10000 + $3
220             };
221             }
222              
223             sub parse_our_game_id ($) {
224              
225 26     26 1 60 my $our_id = shift;
226              
227 26         147 $our_id =~ /^(\d{4})(\d{1})(\d{4})/;
228             {
229 26         333 season => $1,
230             stage => $2 + 0,
231             season_id => $3,
232             game_id => $our_id,
233             };
234             }
235              
236             sub get_season_from_date ($) {
237              
238 8     8 1 19 my $date = shift;
239              
240 8         44 $date =~ /^(\d{4})(\d{2})(\d{2})/;
241 8 100       73 $2 > 8 ? $1 : $1 - 1;
242             }
243              
244             sub get_schedule_json_file ($;$) {
245              
246 18     18 1 4941 my $season = shift;
247 18   66     135 my $data_dir = shift || $ENV{HOCKEYDB_DATA_DIR} || $DATA_DIR;
248              
249 18         124 sprintf("%s/%s/schedule.json", $data_dir, $season);
250             }
251              
252             sub get_games_for_dates_from_db (@) {
253              
254 0     0 1 0 my @dates = @_;
255              
256 0   0     0 $DB ||= Sport::Analytics::NHL::DB->new();
257 0         0 my @games = $DB->{dbh}->get_collection('schedule')->find(
258             { date => {
259             '$in' => [map($_+0, @dates)],
260             }},
261             {_id => 0, season => 1, stage => 1, season_id => 1}
262             )->all();
263 0 0       0 if (! @games) {
264 0         0 print STDERR "No matching games found in the database, trying files\n";
265 0         0 @games = get_games_for_dates_from_fs(@dates);
266             }
267 0         0 @games;
268             }
269              
270             sub resolve_team ($;$) {
271              
272 30906     30906 1 49064 my $team = shift;
273 30906   100     77954 my $force_no_db = shift || 0;
274              
275 30906 50 66     89117 if (! $force_no_db && $ENV{MONGO_DB}) {
276 0   0     0 $DB ||= Sport::Analytics::NHL::DB->new();
277 0         0 my $team_id = $DB->resolve_team_db($team);
278 0 0       0 return $team_id if $team_id;
279             }
280 30906 100 66     86389 return 'MTL' if ($team =~ /MONTR.*CAN/i || $team =~ /CAN.*MONTR/);
281 30559 100 66     75472 return 'NHL' if ($team eq 'League' || $team eq 'NHL');
282 30558         155510 for my $team_id (keys %TEAMS) {
283 711313 100       1141576 return $team_id if $team_id eq $team;
284 684940         830162 for my $type (qw(short long full)) {
285 2052508 100       2193085 return $team_id if grep { uc($_) eq uc($team) } @{$TEAMS{$team_id}->{$type}};
  3006409         5503535  
  2052508         3092347  
286             }
287             }
288 0         0 die "Couldn't resolve team $team";
289             }
290              
291             =over 2
292              
293             =item C
294              
295             Wraps around str2time to fix its parsing the pre-1969 dates to the same timestamp as their 100 years laters.
296             Arguments: the str2time argument string
297             Returns: the correct timestamp (negative for pre-1969)
298              
299             =back
300              
301             =cut
302              
303             sub str3time ($) {
304              
305 10994     10994 1 16851 my $str = shift;
306              
307 10994         27600 my $time = str2time($str);
308 10994         1820593 my $year = substr($str, 0, 4);
309              
310 10994 100       27685 $time -= (31536000 + 3124224000) if $year < 1969;
311 10994         33315 $time;
312             }
313              
314             sub convert_new_schedule_game ($) {
315              
316 9446     9446 1 12492 my $schedule_game = shift;
317 9446         14265 my $game = {};
318 9446         29415 $game->{stage} = substr($schedule_game->{id},5,1)+0;
319 9446 100 100     24132 return undef if $game->{stage} ne $REGULAR && $game->{stage} ne $PLAYOFF;
320 9257         20072 $game->{season} = substr($schedule_game->{id},0,4)+0;
321 9257         16826 $game->{season_id} = $schedule_game->{id} % 10000+0;
322 9257         14683 $game->{_id} = (delete $schedule_game->{id})+0;
323             $game->{game_id} = sprintf(
324             "%04d%d%04d",$game->{season},$game->{stage},$game->{season_id}
325 9257         35890 )+0;
326 9257         18680 $game->{ts} = str3time(delete $schedule_game->{est})+0;
327 9257         306996 $game->{date} = strftime("%Y%m%d", localtime($game->{ts}))+0;
328 9257         36656 $game->{away} = resolve_team(delete $schedule_game->{a});
329 9257         28801 $game->{home} = resolve_team(delete $schedule_game->{h});
330 9257         27521 $game;
331             }
332              
333             sub arrange_new_schedule_by_date ($$) {
334              
335 4     4 1 35 my $schedule_by_date = shift;
336 4         11 my $schedule_json_data = shift;
337              
338              
339 4         10 for my $schedule_game (@{$schedule_json_data}) {
  4         13  
340 5392         9856 my $game = convert_new_schedule_game($schedule_game);
341 5392 100       11099 next unless $game;
342 5268   100     16706 $schedule_by_date->{$game->{date}} ||= [];
343 5268         6686 push(@{$schedule_by_date->{$game->{date}}}, $game);
  5268         13304  
344             }
345             }
346              
347             sub convert_old_schedule_game ($) {
348              
349 1671     1671 1 2299 my $schedule_game = shift;
350              
351 1671         4344 my $stage = substr($schedule_game->{gamePk},5,1);
352 1671 100 100     3887 return undef if $stage != $REGULAR && $stage != $PLAYOFF;
353             my $game = {
354             away => resolve_team($schedule_game->{teams}{away}{team}{name}),
355             home => resolve_team($schedule_game->{teams}{home}{team}{name}),
356             _id => $schedule_game->{gamePk} + 0,
357             stage => $stage + 0,
358             season => substr($schedule_game->{gamePk}, 0, 4) + 0,
359             season_id => $schedule_game->{gamePk} % 10000 + 0,
360             ts => str3time($schedule_game->{gameDate}),
361 1670         6007 year => substr($schedule_game->{gameDate}, 0, 4) + 0,
362             };
363             $game->{game_id} = sprintf(
364             "%04d%d%04d",$game->{season},$game->{stage},$game->{season_id}
365 1670         9652 )+0;
366 1670         60148 $game->{date} = strftime("%Y%m%d", localtime($game->{ts}))+0;
367 1670         5928 $game;
368             }
369              
370             sub arrange_old_schedule_by_date ($$) {
371              
372 5     5 1 22 my $schedule_by_date = shift;
373 5         14 my $schedule_json_data = shift;
374              
375 5         17 for my $schedule_date (@{$schedule_json_data->{dates}}) {
  5         28  
376 506         714 for my $schedule_game (@{$schedule_date->{games}}) {
  506         1524  
377 1435         2721 my $game = convert_old_schedule_game($schedule_game);
378 1435 100       3327 if ($game) {
379 1434   100     5749 $schedule_by_date->{$game->{date}} ||= [];
380 1434         1834 push(@{$schedule_by_date->{$game->{date}}}, $game);
  1434         4301  
381             }
382             }
383             }
384             }
385              
386             sub convert_schedule_game ($) {
387              
388 4290     4290 1 10006 my $game = shift;
389              
390             $game->{gamePk}
391 4290 100       10751 ? convert_old_schedule_game($game)
392             : convert_new_schedule_game($game);
393             }
394              
395             sub arrange_schedule_by_date ($$) {
396 9     9 1 169 my $schedule_by_date = shift;
397 9         21 my $schedule_json_data = shift;
398              
399 9 100       73 ref $schedule_json_data eq 'ARRAY' ?
400             arrange_new_schedule_by_date($schedule_by_date, $schedule_json_data) :
401             arrange_old_schedule_by_date($schedule_by_date, $schedule_json_data);
402             }
403              
404             sub get_games_for_dates_from_fs(@) {
405              
406 1     1 1 1491 my @dates = @_;
407              
408 1         4 my %jsons = ();
409 1         4 my $schedule_by_date = {};
410 1         4 my @games = ();
411 1         3 for my $date (@dates) {
412 2         12 my $season = get_season_from_date($date);
413 2   33     23 my $schedule_file = sprintf("%s/%d/schedule.json", $ENV{HOCKEYDB_DATA_DIR} || $DATA_DIR, $season);
414 2 50       57 if (! -f $schedule_file) {
415 0         0 print STDERR
416             "[ERROR] No schedule crawl specified, and no schedule file $schedule_file present for $date\n";
417 0         0 next;
418             }
419 2 50       12 unless ($jsons{$season}) {
420 2         16 my $json = read_file($schedule_file);
421 2         7771 $jsons{$season} = decode_json($json);
422 2         22 arrange_schedule_by_date($schedule_by_date, $jsons{$season});
423             }
424 2 100       30 unless ($schedule_by_date->{$date}) {
425 1         168 print STDERR "No games scheduled for $date, skipping...\n";
426 1         7 next;
427             }
428 1         3 push(@games, @{$schedule_by_date->{$date}})
  1         11  
429             }
430 1         3008 @games;
431             }
432              
433             sub get_games_for_dates (@) {
434              
435 0     0 1 0 my @dates = @_;
436              
437             $ENV{MONGO_DB} ?
438 0 0       0 get_games_for_dates_from_db(@dates) :
439             get_games_for_dates_from_fs(@dates);
440             }
441              
442             sub get_start_stop_date ($) {
443              
444 3     3 1 10 my $season = shift;
445              
446             (
447 3         31 sprintf("%04d-%02d-%02d", $season+0, 9, 2),
448             sprintf("%04d-%02d-%02d", $season+1, 9, 1),
449             );
450             }
451              
452             sub make_game_path ($$$;$) {
453              
454 40     40 1 22166 my $season = shift;
455 40         88 my $stage = shift;
456 40         97 my $season_id = shift;
457 40   33     247 my $base_dir = shift || $ENV{HOCKEYDB_DATA_DIR} || $DATA_DIR;
458              
459 40         261 my $path = sprintf("%s/%04d/%04d/%04d", $base_dir, $season, $stage, $season_id);
460 40 100 66     1461 return $path if -d $path && -w $path;
461 9 50       3446 make_path($path) or die "Couldn't create path $path\n";
462              
463 9         60 $path;
464             }
465              
466             sub read_schedules ($) {
467              
468 2     2 1 726 my $opts = shift;
469              
470 2   33     7 my $start_season = $opts->{start_season} || $FIRST_SEASON;
471 2   33     6 my $stop_season = $opts->{stop_season} || $CURRENT_SEASON;
472 2         4 my $schedules = {};
473              
474 2         8 for my $season ($start_season .. $stop_season) {
475 4         14 my $json_file = get_schedule_json_file($season);
476 4         29 debug "Using schedule from file $json_file";
477 4 50       96 next unless -f $json_file;
478 4         19 my $json = read_file($json_file);
479 4         5089 $schedules->{$season} = decode_json($json);
480             }
481 2         11 $schedules;
482             }
483              
484             sub get_game_id_from_path ($) {
485              
486 3     3 1 3696 my $path = shift;
487              
488 3         91 $path =~ m|^$ENV{HOCKEYDB_DATA_DIR}/(\d{4})/(\d{4})/(\d{4})|;
489 3 50 33     116 $1 && $2 && $3 ? $1*100000 + $2*10000 + $3 : undef;
490             }
491              
492             =over 2
493              
494             =item C
495              
496             Gets the expected SSSS/TTTT/NNNN path for our 9-digit game id.
497             Arguments: our 9-digit game id
498             Returns: the path (creates it if necessary)
499              
500             =back
501              
502             =cut
503              
504             sub get_game_path_from_id ($;$) {
505              
506 20     20 1 60 my $id = shift;
507 20   33     152 my $data_dir = shift || $ENV{HOCKEYDB_DATA_DIR} || $DATA_DIR;
508              
509 20         106 my $game = parse_our_game_id($id);
510 20         106 make_game_path($game->{season}, $game->{stage}, $game->{season_id}, $data_dir);
511             }
512              
513             sub read_existing_game_ids ($) {
514              
515 4     4 1 1221 my $season = shift;
516              
517 4         10 my $game_ids = {};
518             find(
519             sub {
520 18 100 66 18   705 if ($_ eq $MAIN_GAME_FILE || $_ eq $SECONDARY_GAME_FILE) {
521 2         6 $game_ids->{get_game_id_from_path($File::Find::dir)} = 1;
522             }
523             },
524 4         316 "$ENV{HOCKEYDB_DATA_DIR}/$season",
525             );
526 4         28 $game_ids;
527             }
528              
529             =over 2
530              
531             =item C
532              
533             Gets existing game files for the given game Id. Assumes SSSS/TTTT/NNNN file tree structure under the root data directory.
534             Arguments:
535             * our 9-digit game id
536             * (optional) root data directory
537             Returns: The list of html/json reports from the game directory
538              
539             =back
540              
541             =cut
542              
543             sub get_game_files_by_id ($;$) {
544              
545 3     3 1 1780 my $game_id = shift;
546 3   33     35 my $data_dir = shift || $ENV{HOCKEYDB_DATA_DIR} || $DATA_DIR;
547              
548 3         15 my $path = get_game_path_from_id($game_id, $data_dir);
549 3         29 debug "Using path $path";
550 3         74 opendir(DIR, $path);
551 13         32 my @game_files = map { "$path/$_" } grep {
552 3 100 100     93 -f "$path/$_" && (/html$/ || /json$/)
  24         328  
553             } readdir(DIR);
554 3         37 closedir(DIR);
555              
556 3         17 @game_files;
557             }
558              
559             =over 2
560              
561             =item C
562              
563             Normalizes one of the following event properties from different variants:
564             * penalty
565             * shot_type
566             * miss
567             * strength
568             * stoppage reason
569              
570             Arguments: the property name and the original string
571             Returns: the normalized, vocabulary-matched string
572              
573             =back
574              
575             =cut
576              
577             sub vocabulary_lookup ($$) {
578              
579 7995     7995 1 17564 my $vocabulary = shift;
580 7995         10492 my $string = shift;
581              
582 7995         11143 $string =~ tr/ / /;
583 7995         14836 $string =~ s/^\s+//;
584 7995         12321 $string =~ s/\s+$//;
585 7995         11127 $string = uc $string;
586 7995 100       23947 return $string if $VOCABULARY{$vocabulary}->{$string};
587 4534         5569 for my $word (keys %{$VOCABULARY{$vocabulary}}) {
  4534         12755  
588 21172         29493 my $alternatives = $VOCABULARY{$vocabulary}->{$word};
589 21172 100       42673 if (any {
590 16955     16955   38443 $string eq $_
591 21172         41784 } @{$alternatives}) {
592 4533         35307 return $word;
593             }
594             }
595 1         13 die "Unknown word $string for vocabulary $vocabulary";
596             }
597              
598             =over 2
599              
600             =item C
601              
602             Normalizes an NHL Report penalty string including a vocabulary lookup
603             Arguments: the original string
604             Returns: the normalized, vocabulary-matched string
605              
606             =back
607              
608             =cut
609              
610             sub normalize_penalty ($) {
611              
612 216     216 1 350 my $penalty = shift;
613              
614 216         350 $penalty =~ s/(\- double minor)//i;
615 216         303 $penalty =~ s/(\- obstruction)//i;
616 216         388 $penalty =~ s/(\-\s*bench\b)//i;
617 216         366 $penalty =~ s/(PS \- )//i;
618 216         400 vocabulary_lookup('penalty', $penalty);
619              
620             }
621              
622             =over 2
623              
624             =item C
625              
626             Prepares a hash with positions of each player id in the boxscore for future caching and resolving purposes.
627              
628             Arguments: the boxscore
629             Returns: the positions hash.
630              
631             =back
632              
633             =cut
634              
635             sub set_roster_positions ($) {
636              
637 5     5 1 93 my $boxscore = shift;
638 5         13 my $positions = {};
639              
640 5         18 for my $t (0,1) {
641 10         22 my $team = $boxscore->{teams}[$t];
642 10         19 for my $player (@{$team->{roster}}) {
  10         24  
643 165         435 $positions->{$player->{_id}} = $player->{position};
644             }
645             }
646 5         15 $positions;
647             }
648              
649             =over 2
650              
651             =item C
652              
653             A testing helper that sets the player stats the way they seem to appear in the event summary rather than in the boxscore, or finds a way to arbitrate the discrepancies.
654              
655             Arguments:
656             * The boxscore
657             * The NHL id of the player being fixed
658             * The stat to fix
659             * The value of the stat in the event summary
660             * The possible arbitration delta
661              
662             Returns: void. The boxscore is updated.
663              
664             =back
665              
666             =cut
667              
668             sub set_player_stat ($$$$;$) {
669              
670 0     0 1 0 my $boxscore = shift;
671 0         0 my $player_id = shift;
672 0         0 my $stat = shift;
673 0         0 my $value = shift;
674 0   0     0 my $delta = shift || 0;
675              
676 0         0 for my $t (0,1) {
677 0         0 for my $player (@{$boxscore->{teams}[$t]{roster}}) {
  0         0  
678 0 0       0 if ($player->{_id} == $player_id) {
679 0 0 0     0 if ($stat eq 'goalsAgainst' && defined $player->{saves}) {
    0          
    0          
680 0         0 $player->{saves} = $player->{shots} - $value;
681 0         0 debug "Setting $player->{_id} $stat to $value";
682 0         0 $player->{$stat} = $value;
683             }
684             elsif ($stat eq 'penaltyMinutes') {
685 0 0       0 if($delta) {
686 0         0 debug "Setting $player->{_id} $stat to $value+$delta";
687 0         0 $player->{$stat} = $delta;
688             }
689             }
690             elsif (defined $player->{$stat}) {
691 0         0 debug "Setting $player->{_id} $stat to $value";
692 0         0 $player->{$stat} = $value;
693             }
694 0         0 return;
695             }
696             }
697             }
698 0         0 die "Couldn't find $player_id / $stat\n";
699 0         0 1;
700             }
701              
702             =over 2
703              
704             =item C
705              
706             Fixes the number of goals and assists for players in the boxscore as shown by the summary.
707              
708             Arguments:
709             * The boxscore
710             * The index of the team of the player (0 - away, 1 - home)
711             * The event summary
712              
713             Returns: void. Boxscore is modified.
714              
715             =back
716              
717             =cut
718              
719             sub fix_playergoals ($$$) {
720              
721 0     0 1 0 my $boxscore = shift;
722 0         0 my $t = shift;
723 0         0 my $event_summary = shift;
724              
725 0         0 for my $player (@{$boxscore->{teams}[$t]{roster}}) {
  0         0  
726 0 0       0 if (my $es = $event_summary->{$player->{_id}}) {
727 0         0 $player->{goals} = $es->{goals};
728 0         0 $player->{assists} = $es->{assists};
729             }
730             }
731             }
732              
733             =over 2
734              
735             =item C
736              
737             Check if the event is not a played one (PEND, GEND, PSTR, STOP)
738              
739             =back
740              
741             =cut
742              
743             sub is_noplay_event ($) {
744 7191     7191 1 7094019 my $event = shift;
745              
746             $event->{type} eq 'PEND' || $event->{type} eq 'PSTR'
747 7191 100 100     54350 || $event->{type} eq 'GEND' || $event->{type} eq 'STOP';
      100        
748             }
749              
750              
751              
752             =over 2
753              
754             =item C
755              
756             Prints the list of parsed events in a compact for. Work in progress. Do not use.
757              
758             =back
759              
760             =cut
761              
762             sub print_events ($) {
763              
764 0     0 1 0 my $events = shift;
765              
766 0         0 for (@{$events}) {
  0         0  
767 0 0       0 $_->{t} = -1 unless exists $_->{t};
768 0   0     0 $_->{ts} ||= get_seconds($_->{time});
769 0         0 print "$_->{period}\t$_->{t}\t$_->{ts}\t$_->{type}\n";
770             }
771             }
772              
773             =over 4
774              
775             =item C
776              
777             Creates a hash of player ids from the boxscore as keys and references to their stat entries as values
778              
779             Argument: the boxscore
780             Returns: the hash of player ids
781              
782             =back
783              
784             =cut
785              
786             sub create_player_id_hash ($) {
787              
788 1     1 1 78 my $boxscore = shift;
789              
790 1         2 my $player_ids;
791 1         2 for my $t (0,1) {
792 2         3 my $team = $boxscore->{teams}[$t];
793 2         3 for my $player (@{$team->{roster}}) {
  2         4  
794 6         12 $player_ids->{$player->{_id}} = \$player;
795             }
796             }
797 1         2 $player_ids;
798             }
799              
800             1;
801              
802             =head1 AUTHOR
803              
804             More Hockey Stats, C<< >>
805              
806             =head1 BUGS
807              
808             Please report any bugs or feature requests to C, or through
809             the web interface at L. I will be notified, and then you'll
810             automatically be notified of progress on your bug as I make changes.
811              
812              
813             =head1 SUPPORT
814              
815             You can find documentation for this module with the perldoc command.
816              
817             perldoc Sport::Analytics::NHL::Tools
818              
819             You can also look for information at:
820              
821             =over 4
822              
823             =item * RT: CPAN's request tracker (report bugs here)
824              
825             L
826              
827             =item * AnnoCPAN: Annotated CPAN documentation
828              
829             L
830              
831             =item * CPAN Ratings
832              
833             L
834              
835             =item * Search CPAN
836              
837             L
838              
839             =back