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   62314 use v5.10.1;
  41         203  
4 41     41   177 use strict;
  41         117  
  41         860  
5 41     41   174 use warnings FATAL => 'all';
  41         82  
  41         1210  
6 41     41   18024 use utf8;
  41         491  
  41         186  
7              
8 41     41   1158 use experimental qw(smartmatch);
  41         79  
  41         274  
9              
10 41     41   8402 use Encode;
  41         115070  
  41         2369  
11 41     41   1646 use Storable qw(dclone);
  41         6919  
  41         1440  
12              
13 41     41   1524 use JSON;
  41         26142  
  41         265  
14 41     41   9608 use Try::Tiny;
  41         22797  
  41         1768  
15 41     41   16715 use Text::Unidecode;
  41         44843  
  41         1735  
16              
17 41     41   603 use parent 'Sport::Analytics::NHL::Report';
  41         272  
  41         261  
18              
19 41     41   2265 use Sport::Analytics::NHL::Config;
  41         129  
  41         6406  
20 41     41   258 use Sport::Analytics::NHL::Errors;
  41         72  
  41         6283  
21 41     41   362 use Sport::Analytics::NHL::Tools;
  41         89  
  41         5307  
22 41     41   299 use Sport::Analytics::NHL::Util;
  41         83  
  41         2187  
23              
24 41     41   230 use Data::Dumper;
  41         86  
  41         131583  
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 42 my $class = shift;
214 15         47 my $json = shift;
215              
216 15         257 my $code = JSON->new();
217 15         127 $code->utf8(1);
218 15         30 my $self;
219 15 50       61 return undef unless $json;
220 15     15   1009 try { $self = {json => $code->decode(decode "UTF-8", $json)} }
221 15     7   188 catch { $self = {json => $code->decode($json)} };
  7         20060  
222             return undef unless
223             $self->{json}{gameData}{status}{abstractGameState}
224 15 50 33     46785 && $self->{json}{gameData}{status}{abstractGameState} eq 'Final';
225 15         52 bless $self, $class;
226              
227 15         142 $self;
228             }
229              
230             sub set_id_data ($$) {
231              
232 7     7 1 1383 my $self = shift;
233 7         21 my $id_data = shift;
234              
235 7         36 my $season_info = parse_nhl_game_id($id_data);
236 7         22 $self->{season} = $season_info->{season};
237 7         18 $self->{stage} = $season_info->{stage};
238 7         15 $self->{season_id} = $season_info->{season_id};
239 7         42 $self->{_id} = $self->{season} * 100000 + $self->{stage} * 10000 + $self->{season_id};
240             }
241              
242             sub set_timestamps ($$) {
243              
244 6     6 1 14 my $self = shift;
245 6         14 my $json = shift;
246              
247 6         14 my $ts = $json->{metaData}{timeStamp}; $ts =~ s/_/ /;
  6         38  
248 6         34 $self->{last_updated} = str3time($ts);
249 6         118 $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       44 : $self->{start_ts} + 9000;
253              
254             }
255              
256             sub set_extra_header_data ($$) {
257              
258 6     6 1 20 my $self = shift;
259 6         10 my $json = shift;
260              
261 6         16 $self->{status} = 'FINAL';
262 6         18 $self->{location} = $json->{gameData}{venue}{name};
263 6 50       140 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 17 my $self = shift;
274 6         10 my $officials = shift;
275              
276             $self->{officials} = {
277 6         28 referees => [],
278             linesmen => [],
279             };
280 6         11 for my $official (@{$officials}) {
  6         14  
281             my $o = {
282             name => unidecode(uc $official->{official}{fullName}),
283 22   100     87 official_id => $official->{official}{id} || 0,
284             };
285             push(
286 22         317 @{$self->{officials}{
287 22 100       69 $official->{officialType} eq 'Referee' ? 'referees' : 'linesmen'
288             }}, $o
289             );
290             }
291             }
292              
293             sub set_broken_player ($$) {
294              
295 245     245 1 266 my $self = shift;
296 245         260 my $player = shift;
297              
298 245 0 33     640 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 268 my $self = shift;
314 245         267 my $ld_player = shift;
315              
316 245 100 100     488 unless ($ld_player->{stats}{skaterStats} || $ld_player->{stats}{goalieStats}) {
317             $ld_player->{stats}{
318 25 50       76 $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         7710 %{dclone $ld_player->{stats}{skaterStats}} :
328 245 100 50     466 %{dclone ($ld_player->{stats}{goalieStats} || {})},
  12         408  
329             };
330 245 0 33     960 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     469 $player->{pim} ||= ($player->{penaltyMinutes} || 0) if $player->{position} eq 'G';
      33        
338 245         504 $player;
339             }
340              
341             sub set_teams ($$) {
342              
343 6     6 1 17 my $self = shift;
344 6         23 my $json = shift;
345              
346 6         16 $self->{teams} = [];
347             $self->{_score} = [
348             $json->{liveData}{linescore}{teams}{away}{goals},
349             $json->{liveData}{linescore}{teams}{home}{goals},
350 6         41 ];
351 6         11 my $t = 0;
352 6         30 for my $team_key (qw(away home)) {
353 12         31 $self->{_t} = $t;
354 12         26 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     120 };
369 12         46 $team->{name} = resolve_team($team->{orig});
370 12 50       82 $team->{coach} = $BROKEN_COACHES{$team->{coach}} if $BROKEN_COACHES{$team->{coach}};
371 12         25 $self->{broken_number} = 101;
372 12         16 for my $ld_player (values %{$ldb_team->{players}}) {
  12         45  
373             my $player =
374 245   33     432 $self->set_broken_player($ld_player->{person}{id}) ||
375             $self->set_player($ld_player);
376 245         269 push(@{$team->{roster}}, $player);
  245         345  
377 245 100       453 $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     43 && @{$MISSING_PLAYERS{$self->{_id}}->[$t]});
  0         0  
384 12 50       31 $self->force_decision($team) unless $team->{_decision};
385 12         16 push(@{$self->{teams}}, $team);
  12         24  
386 12         22 $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 51 my $self = shift;
406 6         9 my $json = shift;
407              
408 6         16 $self->{periods} = [];
409 6         13 my $p = 1;
410 6         11 for my $ld_period (@{$json->{liveData}{linescore}{periods}}) {
  6         24  
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       66 };
    100          
425 18         32 push(@{$self->{periods}}, $period);
  18         35  
426 18 50 33     101 $self->{ot} ||= $ld_period->{periodType} eq 'OVERTIME' ? 1 : 0;
427 18         33 $p++;
428             }
429             }
430              
431             sub place_players ($$) {
432              
433 756     756 1 844 my $event = shift;
434 756         759 my $ld_event = shift;
435              
436 756         762 for my $player (@{$ld_event->{players}}) {
  756         1320  
437 1537         4616 $event->{$PLAYER_ID_MAP{$player->{playerType}}} = $player->{player}{id};
438             }
439             }
440              
441             sub assign_specific_penalty_data ($$) {
442              
443 100     100 1 115 my $event = shift;
444 100         111 my $ld_event = shift;
445              
446 100         183 place_players($event, $ld_event);
447 100         188 $event->{penalty} = $ld_event->{result}{secondaryType};
448 100   100     282 $event->{severity} = uc($ld_event->{result}{penaltySeverity} || '');
449 100         154 $event->{length} = $ld_event->{result}{penaltyMinutes};
450 100 50 33     259 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     634 if ($event->{penalty} =~ /too many/i || $event->{description} =~ /^\s+against/i) {
    50          
455 5 50 33     66 $event->{servedby} ||= $event->{player1} if $event->{player1};
456 5         11 $event->{player1} = $BENCH_PLAYER_ID;
457 5         24 $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         241 $event->{penalty} = normalize_penalty($event->{penalty});
465             }
466              
467             sub assign_specific_goal_data ($$) {
468              
469 26     26 1 47 my $event = shift;
470 26         36 my $ld_event = shift;
471              
472 26         62 place_players($event, $ld_event);
473             $event->{assists} = [ map {
474 102 100       197 $_->{playerType} eq 'Assist' ? $_->{player}{id} : (),
475 26         37 } @{$ld_event->{players}} ];
  26         49  
476 26   100     99 $event->{shot_type} = vocabulary_lookup('shot_type', $ld_event->{result}{secondaryType} || '');
477 26         71 $event->{en} = $ld_event->{result}{emptyNet};
478 26         62 $event->{gwg} = $ld_event->{result}{gameWinningGoal};
479             }
480              
481             sub assign_specific_event_data ($$) {
482              
483 1616     1616 1 1792 my $event = shift;
484 1616         1650 my $ld_event = shift;
485              
486 1616         2243 for ($event->{type}) {
487 1616         2708 when ('FAC') {
488 325         369 for my $player (@{$ld_event->{players}}) {
  325         556  
489 650         1906 $event->{$PLAYER_ID_MAP{$player->{playerType}}} = $player->{player}{id};
490             }
491             $event->{winning_team} = $ld_event->{team}{triCode},
492 325         771 }
493 1291         1460 when ('GIVE') { $event->{player1} = $ld_event->{players}[0]{player}{id}; }
  50         188  
494 1241         1320 when ('TAKE') { $event->{player1} = $ld_event->{players}[0]{player}{id}; }
  45         205  
495 1196         1266 when ('MISS') {
496 160         477 $event->{player1} = $ld_event->{players}[0]{player}{id};
497 160         496 $event->{description} =~ /\- (.*)/;
498 160   50     558 $event->{miss} = vocabulary_lookup('miss', $1 || '');
499             }
500 1036         1152 when ('STOP') { $event->{stopreason} = [ vocabulary_lookup('stopreason', $event->{description})] }
  245         435  
501 791         839 when ('HIT') { place_players($event, $ld_event); }
  160         281  
502 631         697 when ('BLOCK') {
503 155         333 place_players($event, $ld_event);
504 155         214 my $x = $event->{player2};
505 155         195 $event->{player2} = $event->{player1};
506 155         235 $event->{player1} = $x;
507             }
508 476         540 when ('SHOT') {
509 315         596 place_players($event, $ld_event);
510 315   50     866 $event->{shot_type} = vocabulary_lookup('shot_type', $ld_event->{result}{secondaryType} || '');
511             }
512 161         191 when ('PENL') { assign_specific_penalty_data($event, $ld_event); }
  100         180  
513 61         83 when ('GOAL') { assign_specific_goal_data($event, $ld_event); }
  26         72  
514             }
515 1616         2221 delete $event->{void};
516             }
517              
518             sub check_broken ($$) {
519              
520 1616     1616 1 1663 my $event = shift;
521 1616         1585 my $ld_event = shift;
522              
523 1616 50 33     5531 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 1727 my $event = shift;
536              
537 1616 50 100     2779 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 1734 my $self = shift;
566 1616         1668 my $event = shift;
567              
568 1616 50 66     2919 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         11 my $events = shift;
590              
591 6         16 $self->{events} = [];
592              
593 6         14 for my $ld_event (@{$events}) {
  6         17  
594             next if $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} &&
595 1651 50 33     5199 $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} eq '-1';
596 1651 100       2188 next if grep { $_ eq $ld_event->{result}{eventTypeId} } @JS_IGNORED_EVENT_TYPES;
  13208         18288  
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     4289 ) : (),
    50 50        
    100 33        
617             };
618 1616 100       7680 $event->{time} = substr($event->{time}, 1) if $event->{time} =~ /^0/;
619             $event->{special} = 1
620 1616 50 33     3310 if $SPECIAL_EVENTS{$self->{_id}} && $SPECIAL_EVENTS{$self->{_id}}->{$ld_event->{about}{eventIdx}};
621 1616         3036 assign_specific_event_data($event, $ld_event);
622 1616         3223 $self->fix_old_goalie($event);
623 1616         2864 check_broken($event, $ld_event);
624 1616         2748 fix_servedby($event);
625 1616         1610 push(@{$self->{events}}, $event);
  1616         3539  
626             }
627             }
628              
629             sub build_resolve_cache ($) {
630              
631 8     8 1 14899 my $self = shift;
632              
633 8         30 $self->{resolve_cache} = {};
634 8         25 for my $t (0,1) {
635 16         26 my $unknown_number = 100;
636 16     331   62 $self->{teams}[$t]{roster} = [ my_uniq { $_->{_id} } @{$self->{teams}[$t]{roster}} ];
  331         853  
  16         86  
637 16         65 for my $player (@{$self->{teams}[$t]{roster}}) {
  16         52  
638 331 100       479 if (! $player->{broken}) {
639 296 100       423 $player->{penaltyMinutes} = delete $player->{pim} if defined $player->{pim};
640 296   33     417 $player->{number} ||= $unknown_number++;
641 296         540 while ($self->{resolve_cache}{$self->{teams}[$t]{name}}->{$player->{number}}) {
642 0         0 $player->{number} += 100;
643             }
644 296         619 $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     154 ||= [];
649 35         42 push(@{$self->{resolve_cache}{$self->{teams}[$t]{name}}->{names}}, \$player);
  35         75  
650             }
651             }
652             }
653             }
654              
655             sub process ($) {
656              
657 5     5 1 643 my $self = shift;
658              
659 5         36 $self->set_id_data($self->{json}{gamePk});
660 5         22 $self->set_timestamps($self->{json});
661 5         27 $self->set_extra_header_data($self->{json});
662 5         66 $self->set_periods($self->{json});
663 5         27 $self->set_officials($self->{json}{liveData}{boxscore}{officials});
664 5         45 $self->set_teams($self->{json});
665 5 50       8 if (@{$self->{json}{liveData}{plays}{allPlays}}) {
  5         23  
666 5         30 $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         22 $self->{type} = 'BS';
673 5         4798 delete $self->{json};
674 5         80 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