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 49     49   107155 use v5.10.1;
  49         194  
4 49     49   254 use strict;
  49         102  
  49         1206  
5 49     49   255 use warnings FATAL => 'all';
  49         189  
  49         1859  
6 49     49   18014 use utf8;
  49         529  
  49         253  
7              
8 49     49   1484 use experimental qw(smartmatch);
  49         105  
  49         413  
9              
10 49     49   4414 use Encode;
  49         34887  
  49         3453  
11 49     49   2030 use Storable qw(dclone);
  49         9761  
  49         2328  
12              
13 49     49   2183 use JSON;
  49         37999  
  49         379  
14 49     49   6698 use Try::Tiny;
  49         5848  
  49         2417  
15 49     49   15732 use Text::Unidecode;
  49         44016  
  49         2348  
16              
17 49     49   929 use parent 'Sport::Analytics::NHL::Report';
  49         421  
  49         343  
18              
19 49     49   3021 use Sport::Analytics::NHL::Config;
  49         115  
  49         9464  
20 49     49   336 use Sport::Analytics::NHL::Errors;
  49         101  
  49         9172  
21 49     49   349 use Sport::Analytics::NHL::Tools;
  49         98  
  49         7833  
22 49     49   322 use Sport::Analytics::NHL::Util;
  49         100  
  49         3427  
23              
24 49     49   305 use Data::Dumper;
  49         134  
  49         180084  
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 18     18 1 59 my $class = shift;
214 18         61 my $json = shift;
215              
216 18         358 my $code = JSON->new();
217 18         148 $code->utf8(1);
218 18         39 my $self;
219 18 50       89 return undef unless $json;
220 18     18   1359 try { $self = {json => $code->decode(decode "UTF-8", $json)} }
221 18     7   260 catch { $self = {json => $code->decode($json)} };
  7         22985  
222             return undef unless
223             $self->{json}{gameData}{status}{abstractGameState}
224 18 50 33     76986 && $self->{json}{gameData}{status}{abstractGameState} eq 'Final';
225 18         88 bless $self, $class;
226              
227 18         283 $self;
228             }
229              
230             sub set_id_data ($$) {
231              
232 10     10 1 2368 my $self = shift;
233 10         22 my $id_data = shift;
234              
235 10         89 my $season_info = parse_nhl_game_id($id_data);
236 10         127 $self->{season} = $season_info->{season};
237 10         72 $self->{stage} = $season_info->{stage};
238 10         30 $self->{season_id} = $season_info->{season_id};
239 10         64 $self->{_id} = $self->{season} * 100000 + $self->{stage} * 10000 + $self->{season_id};
240             }
241              
242             sub set_timestamps ($$) {
243              
244 9     9 1 26 my $self = shift;
245 9         24 my $json = shift;
246              
247 9         31 my $ts = $json->{metaData}{timeStamp}; $ts =~ s/_/ /;
  9         56  
248 9         54 $self->{last_updated} = str3time($ts);
249 9         34 $self->{start_ts} = str3time($json->{gameData}{datetime}{dateTime});
250             $self->{stop_ts} = $json->{gameData}{datetime}{endDateTime}
251             ? str3time($json->{gameData}{datetime}{endDateTime})
252 9 100       67 : $self->{start_ts} + 9000;
253              
254             }
255              
256             sub set_extra_header_data ($$) {
257              
258 9     9 1 32 my $self = shift;
259 9         16 my $json = shift;
260              
261 9         23 $self->{status} = 'FINAL';
262 9         32 $self->{location} = $json->{gameData}{venue}{name};
263 9 50       593 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 9     9 1 32 my $self = shift;
274 9         22 my $officials = shift;
275              
276             $self->{officials} = {
277 9         46 referees => [],
278             linesmen => [],
279             };
280 9         19 for my $official (@{$officials}) {
  9         25  
281             my $o = {
282             name => unidecode(uc $official->{official}{fullName}),
283 32   100     147 official_id => $official->{official}{id} || 0,
284             };
285             push(
286 32         586 @{$self->{officials}{
287 32 100       119 $official->{officialType} eq 'Referee' ? 'referees' : 'linesmen'
288             }}, $o
289             );
290             }
291             }
292              
293             sub set_broken_player ($$) {
294              
295 361     361 1 443 my $self = shift;
296 361         401 my $player = shift;
297              
298 361 0 33     1022 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 361     361 1 443 my $self = shift;
314 361         410 my $ld_player = shift;
315              
316 361 100 100     761 unless ($ld_player->{stats}{skaterStats} || $ld_player->{stats}{goalieStats}) {
317             $ld_player->{stats}{
318 35 50       170 $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 343         12569 %{dclone $ld_player->{stats}{skaterStats}} :
328 361 100 50     873 %{dclone ($ld_player->{stats}{goalieStats} || {})},
  18         652  
329             };
330 361 0 33     1546 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 361 100 50     752 $player->{pim} ||= ($player->{penaltyMinutes} || 0) if $player->{position} eq 'G';
      33        
338 361         825 $player;
339             }
340              
341             sub set_teams ($$) {
342              
343 9     9 1 28 my $self = shift;
344 9         20 my $json = shift;
345              
346 9         45 $self->{teams} = [];
347             $self->{_score} = [
348             $json->{liveData}{linescore}{teams}{away}{goals},
349             $json->{liveData}{linescore}{teams}{home}{goals},
350 9         60 ];
351 9         20 my $t = 0;
352 9         25 for my $team_key (qw(away home)) {
353 18         51 $self->{_t} = $t;
354 18         58 my $ldb_team = $json->{liveData}{boxscore}{teams}{$team_key};
355 18         41 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 18 100 33     229 };
369 18         80 $team->{name} = resolve_team($team->{orig});
370 18 50       99 $team->{coach} = $BROKEN_COACHES{$team->{coach}} if $BROKEN_COACHES{$team->{coach}};
371 18         50 $self->{broken_number} = 101;
372 18         38 for my $ld_player (values %{$ldb_team->{players}}) {
  18         92  
373             my $player =
374 361   33     809 $self->set_broken_player($ld_player->{person}{id}) ||
375             $self->set_player($ld_player);
376 361         457 push(@{$team->{roster}}, $player);
  361         575  
377 361 100       798 $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 18 50 33     70 && @{$MISSING_PLAYERS{$self->{_id}}->[$t]});
  0         0  
384 18 50       55 $self->force_decision($team) unless $team->{_decision};
385 18         33 push(@{$self->{teams}}, $team);
  18         38  
386 18         42 $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 9     9 1 42 my $self = shift;
406 9         20 my $json = shift;
407              
408 9         23 $self->{periods} = [];
409 9         21 my $p = 1;
410 9         16 for my $ld_period (@{$json->{liveData}{linescore}{periods}}) {
  9         40  
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 27 100       115 };
    100          
425 27         52 push(@{$self->{periods}}, $period);
  27         59  
426 27 50 33     178 $self->{ot} ||= $ld_period->{periodType} eq 'OVERTIME' ? 1 : 0;
427 27         53 $p++;
428             }
429             }
430              
431             sub place_players ($$) {
432              
433 1071     1071 1 1452 my $event = shift;
434 1071         1336 my $ld_event = shift;
435              
436 1071         1324 for my $player (@{$ld_event->{players}}) {
  1071         2333  
437 2165         8813 $event->{$PLAYER_ID_MAP{$player->{playerType}}} = $player->{player}{id};
438             }
439             }
440              
441             sub assign_specific_penalty_data ($$) {
442              
443 152     152 1 261 my $event = shift;
444 152         270 my $ld_event = shift;
445              
446 152         406 place_players($event, $ld_event);
447 152         409 $event->{penalty} = $ld_event->{result}{secondaryType};
448 152   100     588 $event->{severity} = uc($ld_event->{result}{penaltySeverity} || '');
449 152         369 $event->{length} = $ld_event->{result}{penaltyMinutes};
450 152 50 33     626 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 152 100 66     1340 if ($event->{penalty} =~ /too many/i || $event->{description} =~ /^\s+against/i) {
    50          
455 7 50 33     69 $event->{servedby} ||= $event->{player1} if $event->{player1};
456 7         20 $event->{player1} = $BENCH_PLAYER_ID;
457 7         20 $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 152         547 $event->{penalty} = normalize_penalty($event->{penalty});
465             }
466              
467             sub assign_specific_goal_data ($$) {
468              
469 37     37 1 75 my $event = shift;
470 37         60 my $ld_event = shift;
471              
472 37         127 place_players($event, $ld_event);
473             $event->{assists} = [ map {
474 144 100       379 $_->{playerType} eq 'Assist' ? $_->{player}{id} : (),
475 37         70 } @{$ld_event->{players}} ];
  37         91  
476 37   100     214 $event->{shot_type} = vocabulary_lookup('shot_type', $ld_event->{result}{secondaryType} || '');
477 37         143 $event->{en} = $ld_event->{result}{emptyNet};
478 37         137 $event->{gwg} = $ld_event->{result}{gameWinningGoal};
479             }
480              
481             sub assign_specific_event_data ($$) {
482              
483 2275     2275 1 3187 my $event = shift;
484 2275         2939 my $ld_event = shift;
485              
486 2275         3860 for ($event->{type}) {
487 2275         4890 when ('FAC') {
488 455         687 for my $player (@{$ld_event->{players}}) {
  455         1019  
489 910         3547 $event->{$PLAYER_ID_MAP{$player->{playerType}}} = $player->{player}{id};
490             }
491             $event->{winning_team} = $ld_event->{team}{triCode},
492 455         1498 }
493 1820         2590 when ('GIVE') { $event->{player1} = $ld_event->{players}[0]{player}{id}; }
  70         337  
494 1750         2401 when ('TAKE') { $event->{player1} = $ld_event->{players}[0]{player}{id}; }
  63         314  
495 1687         2310 when ('MISS') {
496 224         1006 $event->{player1} = $ld_event->{players}[0]{player}{id};
497 224         1014 $event->{description} =~ /\- (.*)/;
498 224   50     1307 $event->{miss} = vocabulary_lookup('miss', $1 || '');
499             }
500 1463         2015 when ('STOP') { $event->{stopreason} = [ vocabulary_lookup('stopreason', $event->{description})] }
  343         935  
501 1120         1606 when ('HIT') { place_players($event, $ld_event); }
  224         574  
502 896         1271 when ('BLOCK') {
503 217         677 place_players($event, $ld_event);
504 217         436 my $x = $event->{player2};
505 217         378 $event->{player2} = $event->{player1};
506 217         457 $event->{player1} = $x;
507             }
508 679         995 when ('SHOT') {
509 441         1129 place_players($event, $ld_event);
510 441   50     1636 $event->{shot_type} = vocabulary_lookup('shot_type', $ld_event->{result}{secondaryType} || '');
511             }
512 238         382 when ('PENL') { assign_specific_penalty_data($event, $ld_event); }
  152         450  
513 86         157 when ('GOAL') { assign_specific_goal_data($event, $ld_event); }
  37         147  
514             }
515 2275         4218 delete $event->{void};
516             }
517              
518             sub check_broken ($$) {
519              
520 2275     2275 1 2790 my $event = shift;
521 2275         2795 my $ld_event = shift;
522              
523 2275 50 33     10563 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 2275     2275 1 3040 my $event = shift;
536              
537 2275 50 100     5092 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 2275     2275 1 3064 my $self = shift;
566 2275         2857 my $event = shift;
567              
568 2275 50 66     5419 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 9     9 1 37 my $self = shift;
589 9         18 my $events = shift;
590              
591 9         29 $self->{events} = [];
592              
593 9         20 for my $ld_event (@{$events}) {
  9         33  
594             next if $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} &&
595 2324 50 33     9532 $BROKEN_EVENTS{BS}->{$self->{_id}}->{$ld_event->{about}{eventIdx}} eq '-1';
596 2324 100       4188 next if grep { $_ eq $ld_event->{result}{eventTypeId} } @JS_IGNORED_EVENT_TYPES;
  18592         33358  
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 2275 50 100     7814 ) : (),
    50 50        
    100 33        
617             };
618 2275 100       18150 $event->{time} = substr($event->{time}, 1) if $event->{time} =~ /^0/;
619             $event->{special} = 1
620 2275 50 33     6300 if $SPECIAL_EVENTS{$self->{_id}} && $SPECIAL_EVENTS{$self->{_id}}->{$ld_event->{about}{eventIdx}};
621 2275         5518 assign_specific_event_data($event, $ld_event);
622 2275         5895 $self->fix_old_goalie($event);
623 2275         5157 check_broken($event, $ld_event);
624 2275         4762 fix_servedby($event);
625 2275         2841 push(@{$self->{events}}, $event);
  2275         5917  
626             }
627             }
628              
629             sub build_resolve_cache ($) {
630              
631 10     10 1 15322 my $self = shift;
632              
633 10         38 $self->{resolve_cache} = {};
634 10         36 for my $t (0,1) {
635 20         33 my $unknown_number = 100;
636 20     404   82 $self->{teams}[$t]{roster} = [ my_uniq { $_->{_id} } @{$self->{teams}[$t]{roster}} ];
  404         962  
  20         104  
637 20         73 for my $player (@{$self->{teams}[$t]{roster}}) {
  20         59  
638 404 100       528 if (! $player->{broken}) {
639 364 100       528 $player->{penaltyMinutes} = delete $player->{pim} if defined $player->{pim};
640 364   33     492 $player->{number} ||= $unknown_number++;
641 364         606 while ($self->{resolve_cache}{$self->{teams}[$t]{name}}->{$player->{number}}) {
642 0         0 $player->{number} += 100;
643             }
644 364         740 $self->{resolve_cache}{$self->{teams}[$t]{name}}->{$player->{number}} = \$player;
645             }
646             else {
647             $self->{resolve_cache}{$self->{teams}[$t]{name}}->{names}
648 40   100     152 ||= [];
649 40         43 push(@{$self->{resolve_cache}{$self->{teams}[$t]{name}}->{names}}, \$player);
  40         86  
650             }
651             }
652             }
653             }
654              
655             sub process ($) {
656              
657 8     8 1 714 my $self = shift;
658              
659 8         51 $self->set_id_data($self->{json}{gamePk});
660 8         40 $self->set_timestamps($self->{json});
661 8         34 $self->set_extra_header_data($self->{json});
662 8         126 $self->set_periods($self->{json});
663 8         50 $self->set_officials($self->{json}{liveData}{boxscore}{officials});
664 8         44 $self->set_teams($self->{json});
665 8 50       18 if (@{$self->{json}{liveData}{plays}{allPlays}}) {
  8         38  
666 8         60 $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 8         34 $self->{type} = 'BS';
673 8         13733 delete $self->{json};
674 8         126 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