File Coverage

blib/lib/Sport/Analytics/NHL/Merger.pm
Criterion Covered Total %
statement 235 310 75.8
branch 92 166 55.4
condition 87 230 37.8
subroutine 30 34 88.2
pod 19 19 100.0
total 463 759 61.0


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::Merger;
2              
3 26     26   58679 use v5.10.1;
  26         93  
4 26     26   129 use strict;
  26         50  
  26         584  
5 26     26   119 use warnings FATAL => 'all';
  26         47  
  26         916  
6 26     26   143 use experimental qw(smartmatch);
  26         52  
  26         185  
7              
8 26     26   1443 use Carp;
  26         57  
  26         1848  
9 26     26   758 use Storable qw(dclone);
  26         3152  
  26         1078  
10              
11 26     26   705 use List::MoreUtils qw(firstval uniq);
  26         10774  
  26         205  
12              
13 26     26   18316 use Sport::Analytics::NHL::Config;
  26         57  
  26         4702  
14 26     26   644 use Sport::Analytics::NHL::Errors;
  26         61  
  26         4520  
15 26     26   5897 use Sport::Analytics::NHL::Tools;
  26         68  
  26         4715  
16 26     26   175 use Sport::Analytics::NHL::Util;
  26         61  
  26         2113  
17              
18             =head1 NAME
19              
20             Sport::Analytics::NHL::Merger - Merge the extra (HTML) reports into the master one (JSON).
21              
22             =head1 SYNOPSYS
23              
24             Merge the extra (HTML) reports into the master one (JSON).
25              
26             These are methods that match the data in the extra reports to the master one and merge it, or complement it, where necessary.
27              
28             use Sport::Analytics::NHL::Merger;
29             merge_report($boxscore, $html_report);
30              
31             =head1 GLOBAL VARIABLES
32              
33             The behaviour of the tests is controlled by several global variables:
34             * $CURRENT - the type of the report currently being merged.
35             * $BOXSCORE - the boxscore currently being merged.
36             * $PLAYER_RESOLVE_CACHE - the player roster resolution cache as described in Sport::Analytics::NHL::Report::BS (q.v.)
37              
38             =head1 FUNCTIONS
39              
40             =over 2
41              
42             =item C
43              
44             When trying to resolve a player in the HTML report to his NHL id, look up by player's name in the names section of the resolve cache.
45              
46             Arguments: * event description
47             * resolve cache
48             * player's number
49             Returns: the reference to the player in the boxscore roster
50              
51             =item C
52              
53             Copy the events from a report when the original section of reports in the live boxscore is missing.
54              
55             Arguments: * the boxscore report
56             * the extra report being merged
57             Returns: void, sets $boxscore->{events}
58              
59             =item C
60              
61             Checks if the event in the merged report was expected to be missed (i.e. not matched) within the boxscore
62              
63             Arguments: * the type of the merged report
64             * the event in question
65             * the master boxscore
66             Returns: 0|1
67              
68             =item C
69              
70             Finds the matching event from the extra report in the master boxscore.
71              
72             Arguments: * the event
73             * the master boxscore event list
74             * the type of the extra report
75             Returns: the matched event or -1
76              
77             =item C
78              
79             Finds the matching player from the extra report in the master boxscore.
80              
81             Arguments: * the player data
82             * the roster in the matching boxscore to look in
83             * [optional] list of players on ice to look in
84             Returns: the matched player or undef
85              
86             =item C
87              
88             Used by find_player to find the player by the NHL id.
89              
90             Arguments: * the player data
91             * the roster in the matching boxscore to look in
92             Returns: the matched player or undef
93              
94             =item C
95              
96             Used by find_player to find the player by the name.
97              
98             Arguments: * the player data
99             * the roster in the matching boxscore to look in
100             * [optional] list of players on ice to look in
101             Returns: the matched player or undef
102              
103             =item C
104              
105             Merges the matched events' data. Usually the data in the boxscore is considered correct, so only additional data is added.
106              
107             Arguments: * the boxscore report
108             * the extra report being merged
109             Returns: void, sets $boxscore->{events}
110              
111             =item C
112              
113             Actually performs the merging of the item. Usually the data in the boxscore is considered correct, so only additional data is added.
114              
115             Arguments: * the boxscore item
116             * the extra report item
117             * [optional] list of fields to be merged
118             Returns: void, sets the event's fields.
119              
120             =item C
121              
122             The function to call to merge two reports.
123              
124             Arguments: * the boxscore report
125             * the extra report being merged
126             Returns: void, sets $boxscore and adds $boxscore->{sources}
127              
128             =item C
129              
130             Merges two rosters of a team, from the master boxscore and from the extra report.
131              
132             Arguments: * the boxscore roster
133             * the report roster
134             Returns: void, sets the roster.
135              
136             =item C
137              
138             Merges the teams of the game, from the master boxscore and from the extra report.
139              
140             Arguments: * the boxscore report
141             * the extra report being merged
142             Returns: void, sets $boxscore
143              
144             =item C
145              
146             Pushes the event that is found in the extra report but not in the master boxscore into the master boxscore's event list.
147              
148             Arguments: * the event
149             * the master boxscore
150             * the type of the extra report
151             Returns: void, sets $boxscore->{events}
152              
153             =item C
154              
155             In case find_event (q.v.) is matched with more than one event, refines the candidate list to ultimately find the event.
156              
157             Arguments: * the event
158             * the list of candidates
159             Returns: the refined list of candidates
160              
161             =item C
162              
163             Resolves the extra report players in the roster, in the events and on ice to their NHL ids.
164              
165             Arguments: * the boxscore report
166             * the extra report being merged
167             Returns: void. The extra report is modified.
168              
169             =item C
170              
171             Resolves event fields such as player1, player2, assist1, assist2 and servedby to the NHL ids.
172              
173             Arguments: * the event
174             * the master boxscore
175             Returns: void. The event is modified.
176              
177             =item C
178              
179             Resolves the extra report event teams to their NHL ids.
180              
181             Arguments: * the event
182             * the master boxscore
183             Returns: void. The event is modified.
184              
185             =item C
186              
187             Resolves the players on the ice during the event to their NHL ids.
188              
189             Arguments: * the event
190             * the master boxscore
191             Returns: void. The event is modified.
192              
193             =item C
194              
195             Resolves the players on the rosters of the extra report to their NHL ids.
196              
197             Arguments: * the roster
198             * the master boxscore
199             * the roster index (0 - away, 1 - home)
200             Returns: void. The roster is modified.
201              
202             =back
203              
204             =cut
205              
206 26     26   142 use parent 'Exporter';
  26         49  
  26         150  
207              
208             our @EXPORT = qw(merge_report);
209              
210 26     26   1735 use Data::Dumper;
  26         61  
  26         90740  
211             $Data::Dumper::Trailingcomma = 1;
212             $Data::Dumper::Deepcopy = 1;
213             $Data::Dumper::Sortkeys = 1;
214             $Data::Dumper::Deparse = 1;
215              
216             our $CURRENT = '';
217             our $BOXSCORE = {};
218             our @MERGE_HEADER = qw(tz month date location attendance);
219              
220             our $PLAYER_RESOLVE_CACHE = {};
221              
222             sub find_player_by_id ($$) {
223              
224 3     3 1 4 my $player = shift;
225 3         4 my $team = shift;
226              
227 3         5 my $bs_player;
228 3     34   11 $bs_player = firstval { $_->{_id} == $player->{_id} } @{$team->{roster}};
  34         38  
  3         14  
229 3 100 66     18 if (!$bs_player) {
    100          
230 1         4 $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}} = \$player;
231 1         2 push(@{$team->{roster}}, $player);
  1         3  
232 1         3 return $player;
233             }
234             elsif (!$bs_player->{number} || $bs_player->{number} != $player->{number}) {
235 1         2 $bs_player->{number} = $player->{number};
236 1         5 $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}} = \$bs_player;
237             }
238 2 50       6 if ($bs_player->{broken}) {
239 0         0 for my $field (keys %{$player}) {
  0         0  
240 0         0 $bs_player->{$field} = $player->{$field};
241 0         0 delete $bs_player->{broken};
242             }
243             }
244 2         4 $bs_player;
245             }
246              
247             sub find_player_by_name ($$$) {
248              
249 14     14 1 24 my $player = shift;
250 14         20 my $team = shift;
251 14         20 my $on_ice = shift;
252              
253 14         34 $player->{name} = uc $player->{name};
254 14         38 my ($name, $fname) = ($player->{name}, '');
255 14 50 33     58 if ($player->{name} =~ /\.\s*(\S+.*)$/ && $name !~ /^st\./i) {
256 0         0 $name = $1;
257 0         0 $fname = substr($player->{name}, 0, 1);
258 0         0 $fname =~ s/\)//g;
259             }
260             my @found_players = grep {
261             $_->{name} =~ /^$fname.*$name$/i
262             || $NAME_VARIATIONS{$_->{name}}
263             && $NAME_VARIATIONS{$_->{name}} eq $player->{name}
264 14 100 33     22 } @{$team->{roster}};
  302         1389  
  14         39  
265 14 100       63 return undef unless @found_players;
266             @found_players = grep {
267 1 50       4 ! $_->{broken}
268 0         0 } @found_players if (@found_players > 1);
269 1 50       5 if (@found_players > 1) {
270             @found_players = $CURRENT eq 'GS'
271             ? ($found_players[0])
272 0 0       0 : grep { $_->{position} eq $player->{position} } @found_players;
  0         0  
273             }
274 1 50       14 return $found_players[0] if (@found_players == 1);
275 0 0       0 if (@found_players > 1) {
276 0         0 for my $o_i (@{$on_ice}) {
  0         0  
277             my $found = firstval {
278 0 0   0   0 $_->{number} == $o_i || $_->{_id} == $o_i
279 0         0 } @found_players;
280 0 0       0 return $found if $found;
281             }
282             }
283 0         0 undef;
284             }
285              
286             sub find_player ($$;$) {
287              
288 361     361 1 2996 my $player = shift;
289 361         398 my $team = shift;
290 361   50     818 my $on_ice = shift || [];
291              
292 361 50       611 if (! ref $player) {
293 0 0       0 $player = $player =~ /^\d/ ?
294             { number => $player } : { name => $player };
295             }
296 361         435 my $bs_player;
297 361 100 66     862 if ($player->{_id} && $player->{_id} =~ /^8\d{6}/) {
    100          
298             $bs_player = find_player_by_id($player, $team)
299 3 50 33     17 if ($player->{_id} && $player->{_id} =~ /^8\d{6}/);
300             }
301             elsif ($player->{number}) {
302             $bs_player = ${
303             $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}}
304 356 100       914 } if $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}};
  343         581  
305 356 100 100     611 return undef unless $bs_player || $player->{name};
306             }
307 360   100     559 $bs_player ||= find_player_by_name($player, $team, $on_ice);
308 360 100       535 return undef unless $bs_player;
309 347 50 33     1251 $player->{number} = $bs_player->{number} if defined $bs_player->{number} && ! $bs_player->{broken};
310 347         605 $player->{_id} = $bs_player->{_id};
311 347         547 $player;
312             }
313              
314             sub refine_candidates ($@) {
315              
316 18     18 1 34 my $event = shift;
317 18         40 my @candidates = @_;
318              
319             grep {
320 18 50       39 if ($event->{type} eq 'PENL') {
  36 50       100  
321             ($event->{length} == $_->{length} || $event->{length} == 10 && $_->{length} == 2)
322             && $event->{penalty} eq $_->{penalty}
323             && (($event->{player1} || 0) == ($_->{player1} || 0)
324             || $event->{player1} == ($_->{servedby} || 0)
325             || ($event->{servedby} || 0) == $_->{player1})
326 0 0 0     0 }
      0        
      0        
      0        
      0        
      0        
      0        
      0        
327             elsif ($event->{type} eq 'STOP') {
328 36         52 my $s = 0;
329 36         41 for my $stopreason (@{$_->{stopreason}}) {
  36         77  
330 36 100 33     335 if (
      66        
331             $event->{stopreason} =~ /$stopreason/i
332             || $event->{stopreason} =~ /CHLG/i && $stopreason =~ /challenge/i
333             ) {
334 2         4 $s = 1;
335 2         2 last;
336             }
337             }
338 36         81 $s;
339             }
340             else {
341             ($event->{player1} || 0) == ($_->{player1} || 0)
342             || ($event->{player2} || 0) == ($_->{player1} || 0)
343 0 0 0     0 && ($event->{player1} || 0) == ($_->{player2} || 0)
      0        
      0        
      0        
      0        
      0        
      0        
344             }
345             } @candidates;
346             }
347              
348             sub find_event ($$$) {
349              
350 1271     1271 1 4893 my $event = shift;
351 1271         1435 my $bs_events = shift;
352 1271         1584 my $type = shift;
353              
354 1271 100       2361 return -1 if $event->{special};
355 1270 100 100     3051 return -1 if ! $event->{player1} && $type ne 'PL';
356             my @candidates = grep {
357             $_->{t} == $event->{t}
358             && $_->{period} == $event->{period}
359             && $_->{type} eq $event->{type}
360             && ($BROKEN_TIMES{$_->{game_id}}
361             && ($event->{player1} || 0) == ($_->{player1} || 0)
362 404811 100 0     938925 || ($event->{ts} == $_->{ts}))
      0        
      33        
      66        
      100        
      100        
363 1269         1479 } @{$bs_events};
  1269         2480  
364 1269 0 33     2748 if (! @candidates && ($event->{type} eq 'MISS' || $event->{type} eq 'SHOT') && $event->{so}) {
      66        
      33        
365             @candidates = grep {
366             $_->{t} == $event->{t}
367             && $_->{player1} == $event->{player1}
368 0 0       0 } @{$bs_events};
  0         0  
  0         0  
369             }
370 1269 100       3349 return $candidates[0] if @candidates == 1;
371 20 100       86 return -1 unless @candidates;
372 18         62 @candidates = refine_candidates($event, @candidates);
373 18 100       57 return $candidates[0] if @candidates;
374 17 50       63 return -1 unless @candidates;
375             }
376              
377             sub resolve_report_on_ice ($$) {
378              
379 1906     1906 1 4109 my $event = shift;
380 1906         2170 my $bs = shift;
381              
382 1906 50 66     4877 return if $event->{sources}{GS} && $event->{period} == 5 && $event->{stage} == $REGULAR;
      33        
383 1906         2614 for my $t (0,1) {
384 3812         4513 for my $on_ice (@{$event->{on_ice}[$t]}) {
  3812         7388  
385 21996 50       48328 next unless $on_ice =~ /^\d{1,2}$/;
386             my $new_on_ice =
387             $PLAYER_RESOLVE_CACHE->{$bs->{teams}[$t]{name}}{$on_ice} ||
388             check_player_names(
389             $event->{description},
390             $PLAYER_RESOLVE_CACHE->{$bs->{teams}[$t]{name}},
391 21996   33     43326 $on_ice,
392             );
393 21996 50       31560 if (! ref $new_on_ice) {
394 0 0       0 if ($CURRENT eq 'GS') {
395 0         0 $on_ice += 8400000;
396 0         0 next;
397             }
398             }
399 21996         22798 $on_ice = ${$new_on_ice}->{_id};
  21996         41039  
400             }
401             }
402             }
403              
404             sub resolve_report_roster ($$$) {
405              
406 34     34 1 84 my $roster = shift;
407 34         59 my $bs = shift;
408 34         48 my $t = shift;
409              
410 34         59 for my $player (@{$roster}) {
  34         91  
411 372 50       607 next if $player->{error};
412 372 100 100     1541 if (($player->{timeOnIce} || defined $player->{start} && $player->{start} != 2) && !($player->{_id} && $player->{_id} eq $EMPTY_NET_ID)) {
      33        
      66        
413 354         972 my $bs_player = find_player($player, $bs->{teams}[$t]);
414 354 50 66     707 if (! $bs_player && $CURRENT eq 'GS') {
415 0         0 $player->{error} = 1;
416 0         0 next;
417             }
418             die ("Can't resolve player ($CURRENT): " . Dumper $player)
419 354 50 33     693 unless $bs_player || ($player->{position} eq 'G' && $player->{start} != 1 || ! $player->{timeOnIce});
      33        
      66        
420             }
421             }
422             }
423              
424             sub resolve_report_event_teams ($$) {
425              
426 1911     1911 1 3379 my $event = shift;
427 1911         2254 my $report = shift;
428              
429 1911 100       4063 if ($event->{team1}) {
430 1593 50       2990 if ($event->{team1} eq 'OTH') {
431             $event->{team1} =
432 0 0       0 $report->{teams}[$event->{team2} eq $report->{teams}[0]{name} ? 1 : 0]{name};
433             }
434 1593         3697 $event->{team1} = resolve_team($event->{team1});
435             }
436 1911 100       5568 if ($event->{team2}) {
437 858 50       1714 if ($event->{team2} eq 'OTH') {
438             $event->{team2} =
439 0 0       0 $report->{teams}[$event->{team1} eq $report->{teams}[0]{name} ? 1 : 0]{name};
440             }
441 858         1865 $event->{team2} = resolve_team($event->{team2});
442             }
443             }
444              
445             sub check_player_names ($$$) {
446              
447 0   0 0 1 0 my $description = shift || '';
448 0         0 my $cache = shift;
449 0         0 my $number = shift;
450              
451 0         0 for my $player_ref (@{$cache->{names}}) {
  0         0  
452 0         0 my $player = ${$player_ref};
  0         0  
453 0         0 my ($last_name) = ($player->{name} =~ /\b(\S+)$/);
454 0 0       0 $last_name = $REVERSE_NAME_TYPOS{$last_name} if $REVERSE_NAME_TYPOS{$last_name};
455 0 0       0 if ($description =~ /\b$last_name\b/i) {
456 0         0 debug "Matched $description with $last_name";
457 0         0 $cache->{$number} = $player_ref;
458 0         0 return $player_ref;
459             }
460             }
461             }
462              
463             sub resolve_report_event_fields ($$) {
464              
465 1911     1911 1 3160 my $event = shift;
466 1911         2133 my $bs = shift;
467              
468 1911         2808 for my $field (qw(player1 player2 assist1 assist2 servedby)) {
469 9555 100 100     22168 next if ! $event->{$field} || $event->{$field} =~ /^8\d{6}/;
470 2541 100       4493 my $team = $field eq 'player2' ? 'team2' : 'team1';
471 2541 100       3763 my $team2 = $field eq 'player2' ? 'team1' : 'team2';
472 2541 50 33     8150 if ($event->{$field} && $event->{$field} =~ /\D/) {
473 0         0 my $player = find_player($event->{$field}, $bs->{teams}[$event->{t}], $event->{on_ice}[$event->{t}]);
474 0 0 0     0 if ($player) {
    0          
475 0         0 $event->{$field} = $player->{_id};
476             }
477             elsif (!($CURRENT eq 'GS' && $event->{type} eq 'GOAL')) {
478 0         0 die "Can't resolve player for event: " . Dumper $player, $event, $field;
479             }
480 0 0 0     0 if ($event->{player1} && $event->{servedby} && $event->{player1} == $event->{servedby}) {
      0        
481 0         0 delete $event->{servedby};
482             }
483             }
484             else {
485             my $matched_player =
486             $PLAYER_RESOLVE_CACHE->{$event->{$team}}{$event->{$field}}
487             || check_player_names(
488             $event->{description},
489             $PLAYER_RESOLVE_CACHE->{$event->{$team}},
490             $event->{$field},
491 2541   33     7001 ) || $PLAYER_RESOLVE_CACHE->{$event->{$team2}}{$event->{$field}};
492 2541         2959 $event->{$field} = ${$matched_player}->{_id};
  2541         6030  
493             }
494             }
495             }
496              
497             sub resolve_report ($$) {
498              
499 16     16 1 12034 my $bs = shift;
500 16         39 my $rp = shift;
501              
502 16         42 for my $t (0,1) {
503 32         158 $rp->{teams}[$t]{name} = resolve_team($rp->{teams}[$t]{name});
504 32         161 resolve_report_roster($rp->{teams}[$t]{roster}, $bs, $t);
505             }
506 16 100       73 if ($rp->{events}) {
507 8         85 $rp->set_event_extra_data();
508 8         19 for my $event (@{$rp->{events}}) {
  8         51  
509 1595         3400 resolve_report_event_teams($event, $rp);
510 1595         4215 resolve_report_event_fields($event, $bs);
511 1595 100       3787 resolve_report_on_ice($event, $bs) if ($event->{on_ice});
512             }
513             }
514             }
515              
516             sub merge_me ($$;$$) {
517              
518 1547     1547 1 2097 my $bs_event = shift;
519 1547         1656 my $rp_event = shift;
520             my $fields = shift || [ grep {
521             $_ ne 'name'
522             && $_ ne 'decision'
523             && defined $rp_event->{$_}
524             && (! defined $bs_event->{$_} || $bs_event->{$_} eq 'XX' || $bs_event->{$_} =~ /^unk$/i)
525             && $rp_event->{$_} ne 'XX' && $rp_event->{$_} !~ /^Unk/i
526 1547   100     2710 } keys %{$rp_event}];
527 1547 100       4214 push(@{$fields}, 'stopreason') if $rp_event->{stopreason};
  180         447  
528 1547         1951 for (@{$fields}) {
  1547         2634  
529 7916         11736 when ('stopreason') {
530 180         277 $bs_event->{$_} = [ uniq (@{$bs_event->{stopreason}}, @{$rp_event->{stopreason}}) ];
  180         392  
  180         1367  
531             }
532 7736         9011 when ('position') {
533             $bs_event->{$_} = $rp_event->{$_}
534 14 50 33     94 if (!$bs_event->{$_} || $bs_event->{$_} eq 'N/A');
535             }
536 7722         8805 when ('on_ice') {
537             $bs_event->{$_} = $rp_event->{$_}
538 1244 50 0     4335 if (! $bs_event->{on_ice} || !$bs_event->{on_ice}[0] || ! @{$bs_event->{on_ice}[0]})
539             }
540 6478         7530 when ('strength') {
541             $bs_event->{$_} = $rp_event->{$_}
542 1032 50 33     6108 if ($bs_event->{$_} !~ /\S/ || $bs_event->{$_} eq 'XX');
543             }
544 5446         6094 default {
545 5446         12098 $bs_event->{$_} = $rp_event->{$_};
546             }
547             }
548 1547 100       7308 if (defined $bs_event->{position}) {
549 286         341 for my $field (keys %{$rp_event}) {
  286         950  
550 5122 50 66     9477 if (! defined $bs_event->{$field}
      33        
      66        
551             && defined $rp_event->{$field}
552             && ($rp_event->{$field} eq '' || $rp_event->{$field} eq 0)) {
553 0         0 $bs_event->{$field} = 0;
554             }
555             }
556             }
557             }
558              
559              
560             sub merge_roster ($$;$) {
561              
562 20     20 1 36 my $bs_team = shift;
563 20         31 my $rp_team = shift;
564              
565 20         30 for my $rp_player (@{$rp_team->{roster}}) {
  20         47  
566 292 50       521 next if $rp_player->{error};
567 292 100 100     700 next unless $rp_player->{timeOnIce} || defined $rp_player->{start};
568 286 50 66     823 next if $rp_player->{_id} && $rp_player->{_id} == $EMPTY_NET_ID;
569             merge_me(
570 286         795 ${$PLAYER_RESOLVE_CACHE->{$bs_team->{name}}{$rp_player->{number}}},
571             $rp_player, 0
572 286 50       537 ) if $rp_player->{number};
573             }
574             }
575              
576             sub merge_teams ($$) {
577              
578 10     10 1 2326 my $boxscore = shift;
579 10         30 my $report = shift;
580              
581 10         33 for my $t (0,1) {
582 20         51 my $bs_team = $boxscore->{teams}[$t];
583 20         47 my $rp_team = $report->{teams}[$t];
584 20 50       69 unless ($bs_team->{name} eq $rp_team->{name}) {
585 0         0 die "$bs_team->{name} vs $rp_team->{name} how did I get here?";
586             }
587 20   33     59 $bs_team->{coach} ||= $rp_team->{coach};
588 20         62 merge_roster($bs_team, $rp_team, $report->{type} eq 'BH');
589             }
590             }
591              
592             sub copy_events ($$) {
593              
594 0     0 1 0 my $boxscore = shift;
595 0         0 my $report = shift;
596              
597 0         0 $boxscore->{events} = dclone $report->{events};
598 0         0 for my $event (@{$boxscore->{events}}) {
  0         0  
599 0         0 $event->{sources}{$report->{type}} = 1;
600 0         0 $event->{sources}{BS} = 0;
601 0 0       0 if ($event->{assist1}) {
602             $event->{assists} = [
603             $event->{assist1} || (),
604 0   0     0 $event->{assist2} || (),
      0        
605             ]
606             }
607             }
608             }
609              
610             sub expected_miss ($$$) {
611              
612 16     16 1 30 my $type = shift;
613 16         24 my $event = shift;
614 16         26 my $boxscore = shift;
615 16         37 my $game_id = $boxscore->{_id};
616              
617             $boxscore->{no_events}
618             || (
619             $type eq 'PL' && $event->{season} < 2010
620             && $event->{type} ne 'PENL' && $event->{type} ne 'GOAL'
621             )
622             || (
623             ref($FORCED_PUSH{$type}{$game_id})
624             && $FORCED_PUSH{$type}{$game_id}->{$event->{id}}
625             )
626             || $event->{type} eq 'PENL' && $event->{length} == 0
627             || $event->{type} eq 'PEND'
628             || $event->{type} eq 'GEND'
629             || $event->{type} eq 'STOP' && $event->{description} =~ /CHL/i
630 16 50 33     381 || $event->{type} eq 'MISS' && ($type eq 'GS')
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
631             }
632              
633             sub push_event ($$$) {
634              
635 0     0 1 0 my $event = shift;
636 0         0 my $boxscore = shift;
637 0         0 my $type = shift;
638              
639 0         0 $event->{game_id} = $boxscore->{_id};
640 0         0 $event->{sources}{$type} = 1;
641 0 0 0     0 $event->{description} ||= 'Missed Penalty Shot' if $event->{type} eq 'MISS';
642 0         0 push(@{$boxscore->{events}}, $event);
  0         0  
643              
644             }
645              
646             sub merge_events ($$) {
647              
648 7     7 1 27 my $boxscore = shift;
649 7         16 my $report = shift;
650              
651 7         22 my $type = $report->{type};
652 7         17 while (my $rp_event = shift @{$report->{events}}) {
  1286         3255  
653 1279 50 66     2444 next if $type eq 'GS' && $boxscore->{sources}{PL} && $boxscore->{season} >= 2007;
      33        
654 1264         2663 my $e = find_event($rp_event, $boxscore->{events}, $type);
655 1264 100 33     4112 if (! ref $e) {
    50          
656 16 50       53 if (expected_miss($type, $rp_event, $boxscore)) {
657 0         0 push_event($rp_event, $boxscore, $type);
658 0         0 next;
659             }
660             }
661             elsif ($type eq 'GS' && $rp_event->{type} eq 'MISS') {
662 0         0 $rp_event->{type} = 'SHOT';
663 0         0 $e = find_event($rp_event, $boxscore->{events}, $type);
664 0 0       0 if (! ref $e) {
665 0         0 push_event($rp_event, $boxscore, $type);
666 0         0 next;
667             }
668             }
669 1264 50       2213 die "UNDEF e " . Dumper($rp_event) unless defined $e;
670 1264 100       2433 next if $e == -1;
671 1248         2592 $e->{sources}{$type} = 1;
672 1248         2406 merge_me($e, $rp_event);
673             }
674             }
675              
676             sub merge_report ($$) {
677              
678 12     12 1 5194 my $boxscore = shift;
679 12         23 my $report = shift;
680              
681 12         38 my $type = $report->{type};
682              
683 12         39 $CURRENT = $type;
684 12         30 $BOXSCORE = $boxscore;
685 12         38 $PLAYER_RESOLVE_CACHE = $boxscore->{resolve_cache};
686 12         66 debug "Merging $type";
687 12         67 resolve_report($boxscore, $report);
688              
689 12         52 for ($type) {
690 12         96 when ([qw(RO ES GS PL)]) {
691 12         67 merge_me($boxscore, $report, \@MERGE_HEADER);
692 12         28 continue;
693             }
694 12         53 when ([qw(RO ES GS)]) {
695 9         43 merge_teams($boxscore, $report);
696 9         120 continue;
697             }
698 12         62 when ([qw(GS PL)]) {
699 6 50       12 @{$boxscore->{events}} ?
  6         57  
700             merge_events($boxscore, $report) : copy_events($boxscore, $report);
701 6         23 continue;
702             }
703             }
704 12         960 $boxscore->{sources}{$type} = 1;
705             }
706              
707             1;
708              
709             =head1 AUTHOR
710              
711             More Hockey Stats, C<< >>
712              
713             =head1 BUGS
714              
715             Please report any bugs or feature requests to C, or through
716             the web interface at L. I will be notified, and then you'll
717             automatically be notified of progress on your bug as I make changes.
718              
719              
720             =head1 SUPPORT
721              
722             You can find documentation for this module with the perldoc command.
723              
724             perldoc Sport::Analytics::NHL::Merger
725              
726             You can also look for information at:
727              
728             =over 4
729              
730             =item * RT: CPAN's request tracker (report bugs here)
731              
732             L
733              
734             =item * AnnoCPAN: Annotated CPAN documentation
735              
736             L
737              
738             =item * CPAN Ratings
739              
740             L
741              
742             =item * Search CPAN
743              
744             L
745              
746             =back