File Coverage

blib/lib/Sport/Analytics/NHL/Merger.pm
Criterion Covered Total %
statement 234 310 75.4
branch 91 166 54.8
condition 86 230 37.3
subroutine 30 34 88.2
pod 19 19 100.0
total 460 759 60.6


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::Merger;
2              
3 20     20   63473 use v5.10.1;
  20         103  
4 20     20   115 use strict;
  20         35  
  20         414  
5 20     20   78 use warnings FATAL => 'all';
  20         33  
  20         623  
6 20     20   93 use experimental qw(smartmatch);
  20         29  
  20         160  
7              
8 20     20   940 use Carp;
  20         60  
  20         1089  
9 20     20   661 use Storable qw(dclone);
  20         2819  
  20         837  
10              
11 20     20   618 use List::MoreUtils qw(firstval uniq);
  20         10559  
  20         161  
12              
13 20     20   13274 use Sport::Analytics::NHL::Config;
  20         40  
  20         3085  
14 20     20   573 use Sport::Analytics::NHL::Errors;
  20         38  
  20         2942  
15 20     20   5966 use Sport::Analytics::NHL::Tools;
  20         47  
  20         3021  
16 20     20   134 use Sport::Analytics::NHL::Util;
  20         35  
  20         1442  
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 20     20   116 use parent 'Exporter';
  20         39  
  20         98  
207              
208             our @EXPORT = qw(merge_report);
209              
210 20     20   1229 use Data::Dumper;
  20         39  
  20         68721  
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 5 my $player = shift;
225 3         4 my $team = shift;
226              
227 3         5 my $bs_player;
228 3     56   12 $bs_player = firstval { $_->{_id} == $player->{_id} } @{$team->{roster}};
  56         69  
  3         14  
229 3 100 66     28 if (!$bs_player) {
    100          
230 1         6 $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}} = \$player;
231 1         2 push(@{$team->{roster}}, $player);
  1         4  
232 1         2 return $player;
233             }
234             elsif (!$bs_player->{number} || $bs_player->{number} != $player->{number}) {
235 1         4 $bs_player->{number} = $player->{number};
236 1         4 $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}} = \$bs_player;
237             }
238 2 50       8 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         6 $bs_player;
245             }
246              
247             sub find_player_by_name ($$$) {
248              
249 12     12 1 25 my $player = shift;
250 12         28 my $team = shift;
251 12         22 my $on_ice = shift;
252              
253 12         35 $player->{name} = uc $player->{name};
254 12         35 my ($name, $fname) = ($player->{name}, '');
255 12 50 33     47 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 12 100 33     23 } @{$team->{roster}};
  259         1486  
  12         40  
265 12 100       58 return undef unless @found_players;
266             @found_players = grep {
267 1 50       5 ! $_->{broken}
268 0         0 } @found_players if (@found_players > 1);
269 1 50       4 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       6 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 283     283 1 3244 my $player = shift;
289 283         310 my $team = shift;
290 283   50     594 my $on_ice = shift || [];
291              
292 283 50       461 if (! ref $player) {
293 0 0       0 $player = $player =~ /^\d/ ?
294             { number => $player } : { name => $player };
295             }
296 283         309 my $bs_player;
297 283 100 66     649 if ($player->{_id} && $player->{_id} =~ /^8\d{6}/) {
    100          
298             $bs_player = find_player_by_id($player, $team)
299 3 50 33     21 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 278 100       703 } if $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}};
  267         442  
305 278 100 100     472 return undef unless $bs_player || $player->{name};
306             }
307 282   100     439 $bs_player ||= find_player_by_name($player, $team, $on_ice);
308 282 100       415 return undef unless $bs_player;
309 271 50 33     1065 $player->{number} = $bs_player->{number} if defined $bs_player->{number} && ! $bs_player->{broken};
310 271         450 $player->{_id} = $bs_player->{_id};
311 271         377 $player;
312             }
313              
314             sub refine_candidates ($@) {
315              
316 14     14 1 30 my $event = shift;
317 14         37 my @candidates = @_;
318              
319             grep {
320 14 50       28 if ($event->{type} eq 'PENL') {
  28 50       84  
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 28         45 my $s = 0;
329 28         34 for my $stopreason (@{$_->{stopreason}}) {
  28         67  
330 28 100 33     335 if (
      66        
331             $event->{stopreason} =~ /$stopreason/i
332             || $event->{stopreason} =~ /CHLG/i && $stopreason =~ /challenge/i
333             ) {
334 2         5 $s = 1;
335 2         6 last;
336             }
337             }
338 28         74 $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 955     955 1 4604 my $event = shift;
351 955         1109 my $bs_events = shift;
352 955         1142 my $type = shift;
353              
354 955 100       1785 return -1 if $event->{special};
355 954 100 100     2653 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 304007 100 0     717050 || ($event->{ts} == $_->{ts}))
      0        
      33        
      66        
      100        
      100        
363 953         1166 } @{$bs_events};
  953         1774  
364 953 0 33     2013 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 953 100       2549 return $candidates[0] if @candidates == 1;
371 16 100       61 return -1 unless @candidates;
372 14         63 @candidates = refine_candidates($event, @candidates);
373 14 100       41 return $candidates[0] if @candidates;
374 13 50       48 return -1 unless @candidates;
375             }
376              
377             sub resolve_report_on_ice ($$) {
378              
379 1586     1586 1 3678 my $event = shift;
380 1586         1891 my $bs = shift;
381              
382 1586 50 66     5728 return if $event->{sources}{GS} && $event->{period} == 5 && $event->{stage} == $REGULAR;
      33        
383 1586         2079 for my $t (0,1) {
384 3172         3385 for my $on_ice (@{$event->{on_ice}[$t]}) {
  3172         7487  
385 18300 50       41791 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 18300   33     35093 $on_ice,
392             );
393 18300 50       26348 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 18300         19072 $on_ice = ${$new_on_ice}->{_id};
  18300         32700  
400             }
401             }
402             }
403              
404             sub resolve_report_roster ($$$) {
405              
406 26     26 1 69 my $roster = shift;
407 26         40 my $bs = shift;
408 26         43 my $t = shift;
409              
410 26         40 for my $player (@{$roster}) {
  26         69  
411 288 50       455 next if $player->{error};
412 288 100 100     1107 if (($player->{timeOnIce} || defined $player->{start} && $player->{start} != 2) && !($player->{_id} && $player->{_id} eq $EMPTY_NET_ID)) {
      33        
      66        
413 276         437 my $bs_player = find_player($player, $bs->{teams}[$t]);
414 276 50 66     478 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 276 50 33     511 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 1590     1590 1 3005 my $event = shift;
427 1590         1910 my $report = shift;
428              
429 1590 100       4577 if ($event->{team1}) {
430 1325 50       2275 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 1325         2584 $event->{team1} = resolve_team($event->{team1});
435             }
436 1590 100       4838 if ($event->{team2}) {
437 715 50       1258 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 715         1299 $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 1590     1590 1 2832 my $event = shift;
466 1590         1883 my $bs = shift;
467              
468 1590         2321 for my $field (qw(player1 player2 assist1 assist2 servedby)) {
469 7950 100 100     18962 next if ! $event->{$field} || $event->{$field} =~ /^8\d{6}/;
470 2110 100       3633 my $team = $field eq 'player2' ? 'team2' : 'team1';
471 2110 100       3073 my $team2 = $field eq 'player2' ? 'team1' : 'team2';
472 2110 50 33     7077 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 2110   33     5359 ) || $PLAYER_RESOLVE_CACHE->{$event->{$team2}}{$event->{$field}};
492 2110         2337 $event->{$field} = ${$matched_player}->{_id};
  2110         4588  
493             }
494             }
495             }
496              
497             sub resolve_report ($$) {
498              
499 12     12 1 11988 my $bs = shift;
500 12         26 my $rp = shift;
501              
502 12         31 for my $t (0,1) {
503 24         94 $rp->{teams}[$t]{name} = resolve_team($rp->{teams}[$t]{name});
504 24         111 resolve_report_roster($rp->{teams}[$t]{roster}, $bs, $t);
505             }
506 12 100       48 if ($rp->{events}) {
507 6         72 $rp->set_event_extra_data();
508 6         17 for my $event (@{$rp->{events}}) {
  6         43  
509 1274         2580 resolve_report_event_teams($event, $rp);
510 1274         2908 resolve_report_event_fields($event, $bs);
511 1274 100       2933 resolve_report_on_ice($event, $bs) if ($event->{on_ice});
512             }
513             }
514             }
515              
516             sub merge_me ($$;$$) {
517              
518 1149     1149 1 1491 my $bs_event = shift;
519 1149         1399 my $rp_event = shift;
520             my $fields = shift || [ grep {
521             $_ ne 'name'
522             && $_ ne 'decision'
523             && defined $rp_event->{$_}
524             && (! defined $bs_event->{$_})
525             && $rp_event->{$_} ne 'XX' && $rp_event->{$_} !~ /^Unk/i
526 1149   100     2040 } keys %{$rp_event}];
527 1149 100       2888 push(@{$fields}, 'stopreason') if $rp_event->{stopreason};
  135         340  
528 1149         1426 for (@{$fields}) {
  1149         1958  
529 5053         7272 when ('stopreason') {
530 135         174 $bs_event->{$_} = [ uniq (@{$bs_event->{stopreason}}, @{$rp_event->{stopreason}}) ];
  135         266  
  135         980  
531             }
532 4918         5620 when ('position') {
533             $bs_event->{$_} = $rp_event->{$_}
534 10 50 33     71 if (!$bs_event->{$_} || $bs_event->{$_} eq 'N/A');
535             }
536 4908         5752 when ('on_ice') {
537             $bs_event->{$_} = $rp_event->{$_}
538 933 50 0     3171 if (! $bs_event->{on_ice} || !$bs_event->{on_ice}[0] || ! @{$bs_event->{on_ice}[0]})
539             }
540 3975         4566 when ('strength') {
541             $bs_event->{$_} = $rp_event->{$_}
542 0 0 0     0 if ($bs_event->{$_} !~ /\S/ || $bs_event->{$_} eq 'XX');
543             }
544 3975         4279 default {
545 3975         8618 $bs_event->{$_} = $rp_event->{$_};
546             }
547             }
548 1149 100       6335 if (defined $bs_event->{position}) {
549 204         286 for my $field (keys %{$rp_event}) {
  204         554  
550 3494 50 66     5638 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 14     14 1 20 my $bs_team = shift;
563 14         25 my $rp_team = shift;
564              
565 14         20 for my $rp_player (@{$rp_team->{roster}}) {
  14         33  
566 208 50       340 next if $rp_player->{error};
567 208 100 100     457 next unless $rp_player->{timeOnIce} || defined $rp_player->{start};
568 204 50 66     549 next if $rp_player->{_id} && $rp_player->{_id} == $EMPTY_NET_ID;
569             merge_me(
570 204         453 ${$PLAYER_RESOLVE_CACHE->{$bs_team->{name}}{$rp_player->{number}}},
571             $rp_player, 0
572 204 50       302 ) if $rp_player->{number};
573             }
574             }
575              
576             sub merge_teams ($$) {
577              
578 7     7 1 2700 my $boxscore = shift;
579 7         11 my $report = shift;
580              
581 7         19 for my $t (0,1) {
582 14         24 my $bs_team = $boxscore->{teams}[$t];
583 14         26 my $rp_team = $report->{teams}[$t];
584 14 50       40 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 14   33     47 $bs_team->{coach} ||= $rp_team->{coach};
588 14         40 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 12     12 1 28 my $type = shift;
613 12         20 my $event = shift;
614 12         32 my $boxscore = shift;
615 12         29 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 12 50 33     335 || $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 5     5 1 38 my $boxscore = shift;
649 5         18 my $report = shift;
650              
651 5         22 my $type = $report->{type};
652 5         12 while (my $rp_event = shift @{$report->{events}}) {
  963         2299  
653 958 50 66     1702 next if $type eq 'GS' && $boxscore->{sources}{PL} && $boxscore->{season} >= 2007;
      33        
654 948         2032 my $e = find_event($rp_event, $boxscore->{events}, $type);
655 948 100 33     3022 if (! ref $e) {
    50          
656 12 50       45 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 948 50       1668 die "UNDEF e " . Dumper($rp_event) unless defined $e;
670 948 100       1784 next if $e == -1;
671 936         2082 $e->{sources}{$type} = 1;
672 936         1834 merge_me($e, $rp_event);
673             }
674             }
675              
676             sub merge_report ($$) {
677              
678 8     8 1 5961 my $boxscore = shift;
679 8         15 my $report = shift;
680              
681 8         27 my $type = $report->{type};
682              
683 8         22 $CURRENT = $type;
684 8         27 $BOXSCORE = $boxscore;
685 8         22 $PLAYER_RESOLVE_CACHE = $boxscore->{resolve_cache};
686 8         45 debug "Merging $type";
687 8         49 resolve_report($boxscore, $report);
688              
689 8         28 for ($type) {
690 8         55 when ([qw(RO ES GS PL)]) {
691 8         38 merge_me($boxscore, $report, \@MERGE_HEADER);
692 8         16 continue;
693             }
694 8         39 when ([qw(RO ES GS)]) {
695 6         27 merge_teams($boxscore, $report);
696 6         15 continue;
697             }
698 8         36 when ([qw(GS PL)]) {
699 4 50       11 @{$boxscore->{events}} ?
  4         34  
700             merge_events($boxscore, $report) : copy_events($boxscore, $report);
701 4         13 continue;
702             }
703             }
704 8         363 $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