File Coverage

blib/lib/Sport/Analytics/NHL/Report/BS.pm
Criterion Covered Total %
statement 248 294 84.3
branch 51 90 56.6
condition 45 92 48.9
subroutine 38 39 97.4
pod 20 20 100.0
total 402 535 75.1


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::Report::BS;
2              
3 41     41   74128 use v5.10.1;
  41         247  
4 41     41   232 use strict;
  41         166  
  41         929  
5 41     41   206 use warnings FATAL => 'all';
  41         101  
  41         1398  
6 41     41   20136 use utf8;
  41         514  
  41         235  
7              
8 41     41   1263 use experimental qw(smartmatch);
  41         92  
  41         358  
9              
10 41     41   10022 use Encode;
  41         135724  
  41         2703  
11 41     41   1876 use Storable qw(dclone);
  41         7887  
  41         1621  
12              
13 41     41   1855 use JSON;
  41         29727  
  41         375  
14 41     41   11458 use Try::Tiny;
  41         28139  
  41         2054  
15 41     41   19066 use Text::Unidecode;
  41         51585  
  41         1986  
16              
17 41     41   632 use parent 'Sport::Analytics::NHL::Report';
  41         330  
  41         313  
18              
19 41     41   2535 use Sport::Analytics::NHL::Config;
  41         145  
  41         7302  
20 41     41   321 use Sport::Analytics::NHL::Errors;
  41         84  
  41         7396  
21 41     41   479 use Sport::Analytics::NHL::Tools;
  41         96  
  41         6286  
22 41     41   363 use Sport::Analytics::NHL::Util;
  41         104  
  41         2596  
23              
24 41     41   246 use Data::Dumper;
  41         92  
  41         150880  
25              
26             =head1 NAME
27              
28             Sport::Analytics::NHL::Report::BS - Class for the Boxscore JSON report
29              
30             =head1 SYNOPSYS
31              
32             Class for the Boxscore JSON report.
33              
34             use Sport::Analytics::NHL::Report::BS;
35             my $report = Sport::Analytics::NHL::Report::BS->new($json)
36             $report->process();
37              
38             =head1 METHODS
39              
40             =over 2
41              
42             =item C
43              
44             Create the Boxscore object with the JSON.
45              
46             =item C
47              
48             Process the Boxscore into the object compatible with further processing, etc.
49              
50             =item C
51              
52             Assign specific event data
53              
54             Arguments:
55             * the event hashref we're building
56             * the original event from the JSON
57             Returns: void.
58              
59             =item C
60              
61             Assign specific goal event data
62              
63             Arguments:
64             * the goal event hashref we're building
65             * the original goal event from the JSON
66             Returns: void.
67              
68             =item C
69              
70             Assign specific penalty event data
71              
72             Arguments:
73             * the penalty event hashref we're building
74             * the original penalty event from the JSON
75             Returns: void.
76              
77             =item C
78              
79             Checks if the event is marked as 'BROKEN' and assigns default values to it.
80              
81             Arguments:
82             * the event hashref we're building
83             * the original event from the JSON
84             Returns: void.
85              
86             Note: should probably be replaced with fill_broken from the Tools package.
87              
88             =item C
89              
90             Fixes the goalie scored upon in the goal events of old boxscores (pre-1987).
91              
92             Arguments: the goal event
93             Returns: void.
94              
95             =item C
96              
97             Fixes the served by data in a penalty event, when a bench or coach penalty is assigned in error to a player.
98              
99             Arguments: the event
100             Returns: void.
101              
102             =item C
103              
104             Assigns the player1, player2 and servedby players in a variety of events
105              
106             Arguments:
107             * the event hashref we're building
108             * the original event from the JSON
109             Returns: void.
110              
111             =item C
112              
113             Marks a player entry in the boxscore as a broken one beyond repair
114              
115             Arguments: the player hashref
116             Returns: void.
117              
118             =item C
119              
120             Converts the events as specified in the boxscore JSON into the $boxscore->{events} arrayref with adjusted fields.
121              
122             Arguments: the JSON events
123             Returns: void.
124              
125             =item C
126              
127             Sets some extra header data not handled by any explicit methods: status, location, shootout data etc.
128             Arguments: the boxscore JSON hashref
129             Returns: void.
130              
131             =item C
132              
133             Sets the game id data: the season, the stage, the season ID, and the overall 9-digit id.
134              
135             Arguments: the ID field of the boxscore JSON
136             Returns: void.
137              
138             =item C
139              
140             Sets the officials for the game.
141              
142             Arguments: the officials' section in the boxscore JSON hashref
143             Returns: void.
144              
145             =item C
146              
147             Set the game periods data from the boxscore JSON hashref
148              
149             Arguments: the boxscore JSON hashref
150             Returns: void.
151              
152             =item C
153              
154             Sets the player in a team roster from the boxscore JSON hashref in accordance with our data model.
155              
156             Arguments: the player live data entry from the boxscore JSON hashref
157             Returns: void.
158              
159             =item C
160              
161             Parses the game stars. Not in use.
162              
163             =item C
164              
165             Sets the team data and the team rosters from the boxscore JSON hashref in accordance with our data model.
166              
167             Arguments: the boxscore JSON hashref
168             Returns: void.
169              
170             =item C
171              
172             Sets the start, end and last updated timestamps for the game from the date strings in the JSON
173              
174             Argument: the boxscore JSON hashref
175             Returns: void.
176              
177             =item C
178              
179             Builds a resolution cache for the roster, with keys as numbers of players pointing at their whole player record in the game. The players who are not numbered are stored as reference to their record in a special list [names].
180              
181             Arguments: none
182             Returns: void. Sets $self->{resolve_cache}
183              
184             Note: the caches are per team, and valid for one game only.
185              
186             =back
187              
188             =cut
189              
190             our @JS_IGNORED_EVENT_TYPES = qw(
191             PERIOD_READY GAME_SCHEDULED PERIOD_OFFICIAL GAME_OFFICIAL
192             SHOOTOUT_COMPLETE EARLY_INT_END EARLY_INT_START EMERGENCY_GOALTENDER
193             );
194              
195             our %PLAYER_ID_MAP = (
196             Winner => 'player1',
197             Loser => 'player2',
198             Hitter => 'player1',
199             Hittee => 'player2',
200             Shooter => 'player1',
201             Blocker => 'player2',
202             PlayerID => 'player1',
203             PenaltyOn => 'player1',
204             DrewBy => 'player2',
205             ServedBy => 'servedby',
206             Scorer => 'player1',
207             Assist => 'void',
208             Goalie => 'player2',
209             );
210              
211             sub new ($$) {
212              
213 15     15 1 44 my $class = shift;
214 15         58 my $json = shift;
215              
216 15         300 my $code = JSON->new();
217 15         182 $code->utf8(1);
218 15         30 my $self;
219 15 50       61 return undef unless $json;
220 15     15   1172 try { $self = {json => $code->decode(decode "UTF-8", $json)} }
221 15     7   214 catch { $self = {json => $code->decode($json)} };
  7         20391  
222             return undef unless
223             $self->{json}{gameData}{status}{abstractGameState}
224 15 50 33     55548 && $self->{json}{gameData}{status}{abstractGameState} eq 'Final';
225 15         65 bless $self, $class;
226              
227 15         165 $self;
228             }
229              
230             sub set_id_data ($$) {
231              
232 7     7 1 1595 my $self = shift;
233 7         19 my $id_data = shift;
234              
235 7         36 my $season_info = parse_nhl_game_id($id_data);
236 7         28 $self->{season} = $season_info->{season};
237 7         22 $self->{stage} = $season_info->{stage};
238 7         15 $self->{season_id} = $season_info->{season_id};
239 7         46 $self->{_id} = $self->{season} * 100000 + $self->{stage} * 10000 + $self->{season_id};
240             }
241              
242             sub set_timestamps ($$) {
243              
244 6     6 1 16 my $self = shift;
245 6         12 my $json = shift;
246              
247 6         15 my $ts = $json->{metaData}{timeStamp}; $ts =~ s/_/ /;
  6         37  
248 6         33 $self->{last_updated} = str3time($ts);
249 6         98 $self->{start_ts} = str3time($json->{gameData}{datetime}{dateTime});
250             $self->{stop_ts} = $json->{gameData}{datetime}{endDateTime}
251             ? str3time($json->{gameData}{datetime}{endDateTime})
252 6 100       51 : $self->{start_ts} + 9000;
253              
254             }
255              
256             sub set_extra_header_data ($$) {
257              
258 6     6 1 22 my $self = shift;
259 6         14 my $json = shift;
260              
261 6         19 $self->{status} = 'FINAL';
262 6         18 $self->{location} = $json->{gameData}{venue}{name};
263 6 50       160 if ($json->{liveData}{linescore}{hasShootout}) {
264 0         0 $self->{so} = 1;
265 0         0 $self->{shootout} = dclone $json->{liveData}{linescore}{shootoutInfo};
266 0         0 delete $self->{shootout}{startTime};
267             }
268              
269             }
270              
271             sub set_officials ($$) {
272              
273 6     6 1 21 my $self = shift;
274 6         14 my $officials = shift;
275              
276             $self->{officials} = {
277 6         28 referees => [],
278             linesmen => [],
279             };
280 6         14 for my $official (@{$officials}) {
  6         18  
281             my $o = {
282             name => unidecode(uc $official->{official}{fullName}),
283 22   100     85 official_id => $official->{official}{id} || 0,
284             };
285             push(
286 22         390 @{$self->{officials}{
287 22 100       88 $official->{officialType} eq 'Referee' ? 'referees' : 'linesmen'
288             }}, $o
289             );
290             }
291             }
292              
293             sub set_broken_player ($$) {
294              
295 245     245 1 331 my $self = shift;
296 245         284 my $player = shift;
297              
298 245 0 33     738 if (
      0        
299             $BROKEN_PLAYERS{BS}->{$self->{_id}}
300             && $BROKEN_PLAYERS{BS}->{$self->{_id}}->{$player}
301             && $BROKEN_PLAYERS{BS}->{$self->{_id}}->{$player} == -1
302             ) {
303             return {
304             broken => 1,
305             _id => $player,
306 0         0 number => $self->{broken_number}++,
307             };
308             }
309             }
310              
311             sub set_player ($$) {
312              
313 245     245 1 300 my $self = shift;
314 245         279 my $ld_player = shift;
315              
316 245 100 100     476 unless ($ld_player->{stats}{skaterStats} || $ld_player->{stats}{goalieStats}) {
317             $ld_player->{stats}{
318 25 50       87 $ld_player->{position}{code} eq 'G' ? 'goalieStats' : 'skaterStats'
319             } = { broken => 1 },
320             }
321             my $player = {
322             _id => $ld_player->{person}{id},
323             name => uc unidecode($ld_player->{person}{fullName}),
324             number => $ld_player->{jerseyNumber},
325             position => uc $ld_player->{position}{code},
326             $ld_player->{stats}{skaterStats} ?
327 233         8639 %{dclone $ld_player->{stats}{skaterStats}} :
328 245 100 50     544 %{dclone ($ld_player->{stats}{goalieStats} || {})},
  12         559  
329             };
330 245 0 33     1060 if ($BROKEN_PLAYERS{BS}->{$self->{_id}}
      33        
331             && $BROKEN_PLAYERS{BS}->{$self->{_id}}->{$ld_player->{person}{id}}
332             && ref $BROKEN_PLAYERS{BS}->{$self->{_id}}->{$ld_player->{person}{id}}) {
333 0         0 for my $stat (keys %{$BROKEN_PLAYERS{BS}->{$self->{_id}}->{$ld_player->{person}{id}}}) {
  0         0  
334 0         0 $player->{$stat} = $BROKEN_PLAYERS{BS}->{$self->{_id}}->{$ld_player->{person}{id}}{$stat};
335             }
336             }
337 245 100 50     532 $player->{pim} ||= ($player->{penaltyMinutes} || 0) if $player->{position} eq 'G';
      33        
338 245         570 $player;
339             }
340              
341             sub set_teams ($$) {
342              
343 6     6 1 21 my $self = shift;
344 6         13 my $json = shift;
345              
346 6         14 $self->{teams} = [];
347             $self->{_score} = [
348             $json->{liveData}{linescore}{teams}{away}{goals},
349             $json->{liveData}{linescore}{teams}{home}{goals},
350 6         45 ];
351 6         15 my $t = 0;
352 6         22 for my $team_key (qw(away home)) {
353 12         33 $self->{_t} = $t;
354 12         32 my $ldb_team = $json->{liveData}{boxscore}{teams}{$team_key};
355 12         24 my $ldl_team = $json->{liveData}{linescore}{teams}{$team_key};
356             my $team = {
357             orig => $ldb_team->{team}{triCode} || $json->{gameData}{teams}{$team_key}{abbreviation},
358             stats => $ldb_team->{teamStats}{teamSkaterStats},
359             roster => [],
360             scratches => $ldb_team->{scratches},
361             coach => $ldb_team->{coaches}[0]{person}{fullName}
362             ? uc $ldb_team->{coaches}[0]{person}{fullName}
363             : 'UNKNOWN COACH',
364             score => $ldl_team->{goals},
365             shots => $ldl_team->{shotsOnGoal},
366             pull => $ldl_team->{goaliePulled},
367             teamid => $ldl_team->{team}{id},
368 12 100 33     155 };
369 12         58 $team->{name} = resolve_team($team->{orig});
370 12 50       64 $team->{coach} = $BROKEN_COACHES{$team->{coach}} if $BROKEN_COACHES{$team->{coach}};
371 12         23 $self->{broken_number} = 101;
372 12         23 for my $ld_player (values %{$ldb_team->{players}}) {
  12         55  
373             my $player =
374 245   33     504 $self->set_broken_player($ld_player->{person}{id}) ||
375             $self->set_player($ld_player);
376 245         308 push(@{$team->{roster}}, $player);
  245         411  
377 245 100       494 $team->{_decision} = $player->{decision} if ($player->{decision});
378             }
379             push(
380 0         0 @{$team->{roster}},
381 0         0 @{$MISSING_PLAYERS{$self->{_id}}->[$t]}
382             ) if ($MISSING_PLAYERS{$self->{_id}}
383 12 50 33     67 && @{$MISSING_PLAYERS{$self->{_id}}->[$t]});
  0         0  
384 12 50       33 $self->force_decision($team) unless $team->{_decision};
385 12         19 push(@{$self->{teams}}, $team);
  12         28  
386 12         26 $t++;
387             }
388              
389             }
390              
391             sub set_stars ($$) {
392              
393 0     0 1 0 my $self = shift;
394 0         0 my $json = shift;
395              
396 0         0 $self->{stars} = [];
397 0         0 for my $star (qw(firstStar secondStar thirdStar)) {
398 0         0 push(@{$self->{stars}}, $json->{liveData}{decisions}{$star}{id})
399 0 0       0 if $json->{liveData}{decisions}{$star};
400             }
401             }
402              
403             sub set_periods ($$) {
404              
405 6     6 1 44 my $self = shift;
406 6         13 my $json = shift;
407              
408 6         16 $self->{periods} = [];
409 6         18 my $p = 1;
410 6         11 for my $ld_period (@{$json->{liveData}{linescore}{periods}}) {
  6         31  
411             my $period = {
412             id => $p,
413             start_ts => $ld_period->{startTime}
414             ? str3time($ld_period->{startTime})
415             : $self->{start_ts} + 2250*($p-1),
416             stop_ts => $ld_period->{endTime}
417             ? str3time($ld_period->{endTime})
418             : $self->{start_ts} + 2250*$p-1,
419             score => [
420             $ld_period->{away}{goals}, $ld_period->{away}{shotsOnGoal},
421             $ld_period->{home}{shotsOnGoal}, $ld_period->{home}{goals},
422             ],
423             type => $ld_period->{periodType},
424 18 100       71 };
    100          
425 18         33 push(@{$self->{periods}}, $period);
  18         42  
426 18 50 33     116 $self->{ot} ||= $ld_period->{periodType} eq 'OVERTIME' ? 1 : 0;
427 18         38 $p++;
428             }
429             }
430              
431             sub place_players ($$) {
432              
433 756     756 1 927 my $event = shift;
434 756         900 my $ld_event = shift;
435              
436 756         878 for my $player (@{$ld_event->{players}}) {
  756         1945  
437 1537         6191 $event->{$PLAYER_ID_MAP{$player->{playerType}}} = $player->{player}{id};
438             }
439             }
440              
441             sub assign_specific_penalty_data ($$) {
442              
443 100     100 1 142 my $event = shift;
444 100         165 my $ld_event = shift;
445              
446 100         206 place_players($event, $ld_event);
447 100         209 $event->{penalty} = $ld_event->{result}{secondaryType};
448 100   100     306 $event->{severity} = uc($ld_event->{result}{penaltySeverity} || '');
449 100         187 $event->{length} = $ld_event->{result}{penaltyMinutes};
450 100 50 33     288 if ($event->{penalty} =~ /bench/i && $event->{penalty} !~ /leaving/i) {
451 0 0       0 $event->{servedby} = $event->{player1} if $event->{player1};
452 0         0 $event->{player1} = $BENCH_PLAYER_ID;
453             }
454 100 100 66     703 if ($event->{penalty} =~ /too many/i || $event->{description} =~ /^\s+against/i) {
    50          
455 5 50 33     51 $event->{servedby} ||= $event->{player1} if $event->{player1};
456 5         18 $event->{player1} = $BENCH_PLAYER_ID;
457 5         26 $event->{pim_correction} += 2;
458             }
459             elsif ($event->{penalty} =~ /(.*\w)\W*\bcoach$/i) {
460 0 0       0 $event->{servedby} = $event->{player1} if $event->{player1};
461 0         0 $event->{player1} = $COACH_PLAYER_ID;
462 0         0 $event->{penalty} = $1;
463             }
464 100         275 $event->{penalty} = normalize_penalty($event->{penalty});
465             }
466              
467             sub assign_specific_goal_data ($$) {
468              
469 26     26 1 54 my $event = shift;
470 26         45 my $ld_event = shift;
471              
472 26         74 place_players($event, $ld_event);
473             $event->{assists} = [ map {
474 102 100       245 $_->{playerType} eq 'Assist' ? $_->{player}{id} : (),
475 26         46 } @{$ld_event->{players}} ];
  26         64  
476 26   100     128 $event->{shot_type} = vocabulary_lookup('shot_type', $ld_event->{result}{secondaryType} || '');
477 26         97 $event->{en} = $ld_event->{result}{emptyNet};
478 26         82 $event->{gwg} = $ld_event->{result}{gameWinningGoal};
479             }
480              
481             sub assign_specific_event_data ($$) {
482              
483 1616     1616 1 2097 my $event = shift;
484 1616         1945 my $ld_event = shift;
485              
486 1616         2541 for ($event->{type}) {
487 1616         3106 when ('FAC') {
488 325         417 for my $player (@{$ld_event->{players}}) {
  325         721  
489 650         2506 $event->{$PLAYER_ID_MAP{$player->{playerType}}} = $player->{player}{id};
490             }
491             $event->{winning_team} = $ld_event->{team}{triCode},
492 325         871 }
493 1291         1638 when ('GIVE') { $event->{player1} = $ld_event->{players}[0]{player}{id}; }
  50         264  
494 1241         1583 when ('TAKE') { $event->{player1} = $ld_event->{players}[0]{player}{id}; }
  45         275  
495 1196         1886 when ('MISS') {
496 160         818 $event->{player1} = $ld_event->{players}[0]{player}{id};
497 160         579 $event->{description} =~ /\- (.*)/;
498 160   50     661 $event->{miss} = vocabulary_lookup('miss', $1 || '');
499             }
500 1036         1286 when ('STOP') { $event->{stopreason} = [ vocabulary_lookup('stopreason', $event->{description})] }
  245         524  
501 791         1006 when ('HIT') { place_players($event, $ld_event); }
  160         354  
502 631         815 when ('BLOCK') {
503 155         395 place_players($event, $ld_event);
504 155         230 my $x = $event->{player2};
505 155         220 $event->{player2} = $event->{player1};
506 155         279 $event->{player1} = $x;
507             }
508 476         643 when ('SHOT') {
509 315         713 place_players($event, $ld_event);
510 315   50     999 $event->{shot_type} = vocabulary_lookup('shot_type', $ld_event->{result}{secondaryType} || '');
511             }
512 161         233 when ('PENL') { assign_specific_penalty_data($event, $ld_event); }
  100         239  
513 61         99 when ('GOAL') { assign_specific_goal_data($event, $ld_event); }
  26         109  
514             }
515 1616         2568 delete $event->{void};
516             }
517              
518             sub check_broken ($$) {
519              
520 1616     1616 1 1910 my $event = shift;
521 1616         1876 my $ld_event = shift;
522              
523 1616 50 33     6389 if (
524             $BROKEN_EVENTS{BS}->{$event->{game_id}} &&
525             (my $evx = $BROKEN_EVENTS{BS}->{$event->{game_id}}->{$ld_event->{about}{eventIdx}})
526             ) {
527 0         0 for my $key (keys %{$evx}) {
  0         0  
528 0         0 $event->{$key} = $evx->{$key};
529             }
530             }
531             }
532              
533             sub fix_servedby ($) {
534              
535 1616     1616 1 1909 my $event = shift;
536              
537 1616 50 100     3328 if ($event->{type} eq 'PENL'
      66        
538             && $event->{description} =~ /served by/i
539             && ! $event->{servedby}) {
540 0         0 $event->{_servedby} = 1;
541             my @pl = (
542             3,2,
543             ($event->{description} =~ /\b(illegal|official|proceed|dress|refusal|objects|misconduct|ineligible|conduct|bench|coach|delay|abus|leaving)/i && $event->{description} !~ /leaves.*bench/i)
544             || $event->{severity} =~ /BENCH/
545 0 0 0     0 || $event->{description} =~ /minor served by/i
546             ? 1 : ()
547             );
548 0         0 for my $p (@pl) {
549 0         0 my $field = "player$p";
550 0 0       0 if (defined $event->{$field}) {
551 0         0 delete $event->{_servedby};
552 0         0 $event->{servedby} = delete $event->{$field};
553 0 0       0 if ($field eq 'player1') {
554 0 0       0 $event->{$field} = $event->{description} =~ /coach/i ?
555             $COACH_PLAYER_ID : $BENCH_PLAYER_ID;
556             }
557 0         0 return;
558             }
559             }
560             }
561             }
562              
563             sub fix_old_goalie ($$) {
564              
565 1616     1616 1 2068 my $self = shift;
566 1616         1924 my $event = shift;
567              
568 1616 50 66     3333 if ($event->{type} eq 'GOAL'
      66        
569             && ! $event->{en}
570             && ! $event->{player2}
571             ) {
572             my $team = $self->{teams}[
573 0 0       0 $event->{team1} eq $self->{teams}[0]{name} ? 1 : 0,
574             ];
575             my @goalies = sort {
576             get_seconds($a->{timeOnIce}) <=> get_seconds($b->{timeOnIce})
577 0         0 } grep {
578 0         0 $_->{position} eq 'G'
579 0         0 } @{$team->{roster}};
  0         0  
580 0         0 my $goalie = $goalies[0];
581 0         0 $event->{player2} = $goalie->{_id};
582 0         0 $event->{team2} = $team->{name};
583             }
584             }
585              
586             sub set_events ($$) {
587              
588 6     6 1 22 my $self = shift;
589 6         12 my $events = shift;
590              
591 6         17 $self->{events} = [];
592              
593 6         13 for my $ld_event (@{$events}) {
  6         22  
594             next if $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} &&
595 1651 50 33     8380 $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} eq '-1';
596 1651 100       2573 next if grep { $_ eq $ld_event->{result}{eventTypeId} } @JS_IGNORED_EVENT_TYPES;
  13208         21390  
597             my $event = {
598             season => $self->{season},
599             stage => $self->{stage},
600             game_id => $self->{_id},
601             period => $ld_event->{about}{period},
602             time => $ld_event->{about}{periodTime},
603             coordinates => $ld_event->{coordinates},
604             type => vocabulary_lookup('events', $ld_event->{result}{eventTypeId}),
605             description => uc $ld_event->{result}{description},
606             strength => vocabulary_lookup('strength', $ld_event->{result}{strength}{code} || ''),
607             event_idx => $ld_event->{about}{eventIdx},
608             bsjs_id => $ld_event->{about}{eventId},
609             event_code => uc ($ld_event->{about}{eventCode} || ''),
610             so => $ld_event->{about}{periodType} eq 'SHOOTOUT' ? 1 : 0,
611             $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} ?
612 0         0 %{$BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}}} : (),
613             $ld_event->{team} ? (
614             team1 => resolve_team($ld_event->{team}{triCode} || $ld_event->{team}{name}),
615             teamid => $ld_event->{team}{id},
616 1616 50 100     4915 ) : (),
    50 50        
    100 33        
617             };
618 1616 100       8614 $event->{time} = substr($event->{time}, 1) if $event->{time} =~ /^0/;
619             $event->{special} = 1
620 1616 50 33     3828 if $SPECIAL_EVENTS{$self->{_id}} && $SPECIAL_EVENTS{$self->{_id}}->{$ld_event->{about}{eventIdx}};
621 1616         3556 assign_specific_event_data($event, $ld_event);
622 1616         3653 $self->fix_old_goalie($event);
623 1616         3305 check_broken($event, $ld_event);
624 1616         3158 fix_servedby($event);
625 1616         1890 push(@{$self->{events}}, $event);
  1616         3885  
626             }
627             }
628              
629             sub build_resolve_cache ($) {
630              
631 8     8 1 16743 my $self = shift;
632              
633 8         36 $self->{resolve_cache} = {};
634 8         29 for my $t (0,1) {
635 16         29 my $unknown_number = 100;
636 16     331   77 $self->{teams}[$t]{roster} = [ my_uniq { $_->{_id} } @{$self->{teams}[$t]{roster}} ];
  331         947  
  16         103  
637 16         70 for my $player (@{$self->{teams}[$t]{roster}}) {
  16         57  
638 331 100       476 if (! $player->{broken}) {
639 296 100       463 $player->{penaltyMinutes} = delete $player->{pim} if defined $player->{pim};
640 296   33     464 $player->{number} ||= $unknown_number++;
641 296         580 while ($self->{resolve_cache}{$self->{teams}[$t]{name}}->{$player->{number}}) {
642 0         0 $player->{number} += 100;
643             }
644 296         721 $self->{resolve_cache}{$self->{teams}[$t]{name}}->{$player->{number}} = \$player;
645             }
646             else {
647             $self->{resolve_cache}{$self->{teams}[$t]{name}}->{names}
648 35   100     127 ||= [];
649 35         45 push(@{$self->{resolve_cache}{$self->{teams}[$t]{name}}->{names}}, \$player);
  35         81  
650             }
651             }
652             }
653             }
654              
655             sub process ($) {
656              
657 5     5 1 692 my $self = shift;
658              
659 5         34 $self->set_id_data($self->{json}{gamePk});
660 5         23 $self->set_timestamps($self->{json});
661 5         33 $self->set_extra_header_data($self->{json});
662 5         71 $self->set_periods($self->{json});
663 5         50 $self->set_officials($self->{json}{liveData}{boxscore}{officials});
664 5         55 $self->set_teams($self->{json});
665 5 50       9 if (@{$self->{json}{liveData}{plays}{allPlays}}) {
  5         32  
666 5         40 $self->set_events($self->{json}{liveData}{plays}{allPlays});
667             }
668             else {
669 0         0 $BROKEN_FILES{$self->{_id}}->{BS} = $NO_EVENTS;
670 0         0 $self->{events} = [];
671             }
672 5         29 $self->{type} = 'BS';
673 5         5644 delete $self->{json};
674 5         105 1;
675             }
676              
677             1;
678              
679             =head1 AUTHOR
680              
681             More Hockey Stats, C<< >>
682              
683             =head1 BUGS
684              
685             Please report any bugs or feature requests to C, or through
686             the web interface at L. I will be notified, and then you'll
687             automatically be notified of progress on your bug as I make changes.
688              
689              
690             =head1 SUPPORT
691              
692             You can find documentation for this module with the perldoc command.
693              
694             perldoc Sport::Analytics::NHL::Report::BS
695              
696             You can also look for information at:
697              
698             =over 4
699              
700             =item * RT: CPAN's request tracker (report bugs here)
701              
702             L
703              
704             =item * AnnoCPAN: Annotated CPAN documentation
705              
706             L
707              
708             =item * CPAN Ratings
709              
710             L
711              
712             =item * Search CPAN
713              
714             L
715              
716             =back