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   50259 use v5.10.1;
  20         67  
4 20     20   93 use strict;
  20         29  
  20         444  
5 20     20   75 use warnings FATAL => 'all';
  20         33  
  20         582  
6 20     20   83 use experimental qw(smartmatch);
  20         32  
  20         143  
7              
8 20     20   943 use Carp;
  20         34  
  20         977  
9 20     20   554 use Storable qw(dclone);
  20         2347  
  20         722  
10              
11 20     20   504 use List::MoreUtils qw(firstval uniq);
  20         8666  
  20         143  
12              
13 20     20   12844 use Sport::Analytics::NHL::Config;
  20         36  
  20         3000  
14 20     20   514 use Sport::Analytics::NHL::Errors;
  20         39  
  20         2872  
15 20     20   5729 use Sport::Analytics::NHL::Tools;
  20         46  
  20         2944  
16 20     20   138 use Sport::Analytics::NHL::Util;
  20         33  
  20         1322  
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   97 use parent 'Exporter';
  20         34  
  20         88  
207              
208             our @EXPORT = qw(merge_report);
209              
210 20     20   1131 use Data::Dumper;
  20         39  
  20         63758  
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         3 my $bs_player;
228 3     64   12 $bs_player = firstval { $_->{_id} == $player->{_id} } @{$team->{roster}};
  64         68  
  3         12  
229 3 100 66     17 if (!$bs_player) {
    100          
230 1         5 $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}} = \$player;
231 1         2 push(@{$team->{roster}}, $player);
  1         3  
232 1         2 return $player;
233             }
234             elsif (!$bs_player->{number} || $bs_player->{number} != $player->{number}) {
235 1         2 $bs_player->{number} = $player->{number};
236 1         4 $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         3 $bs_player;
245             }
246              
247             sub find_player_by_name ($$$) {
248              
249 12     12 1 24 my $player = shift;
250 12         27 my $team = shift;
251 12         22 my $on_ice = shift;
252              
253 12         30 $player->{name} = uc $player->{name};
254 12         31 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     18 } @{$team->{roster}};
  259         1400  
  12         37  
265 12 100       52 return undef unless @found_players;
266             @found_players = grep {
267 1 50       3 ! $_->{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       5 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 2835 my $player = shift;
289 283         297 my $team = shift;
290 283   50     566 my $on_ice = shift || [];
291              
292 283 50       433 if (! ref $player) {
293 0 0       0 $player = $player =~ /^\d/ ?
294             { number => $player } : { name => $player };
295             }
296 283         300 my $bs_player;
297 283 100 66     605 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 278 100       613 } if $PLAYER_RESOLVE_CACHE->{$team->{name}}{$player->{number}};
  267         421  
305 278 100 100     442 return undef unless $bs_player || $player->{name};
306             }
307 282   100     418 $bs_player ||= find_player_by_name($player, $team, $on_ice);
308 282 100       397 return undef unless $bs_player;
309 271 50 33     893 $player->{number} = $bs_player->{number} if defined $bs_player->{number} && ! $bs_player->{broken};
310 271         443 $player->{_id} = $bs_player->{_id};
311 271         415 $player;
312             }
313              
314             sub refine_candidates ($@) {
315              
316 14     14 1 27 my $event = shift;
317 14         35 my @candidates = @_;
318              
319             grep {
320 14 50       28 if ($event->{type} eq 'PENL') {
  28 50       87  
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         38 my $s = 0;
329 28         34 for my $stopreason (@{$_->{stopreason}}) {
  28         61  
330 28 100 33     287 if (
      66        
331             $event->{stopreason} =~ /$stopreason/i
332             || $event->{stopreason} =~ /CHLG/i && $stopreason =~ /challenge/i
333             ) {
334 2         3 $s = 1;
335 2         3 last;
336             }
337             }
338 28         59 $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 4144 my $event = shift;
351 955         1122 my $bs_events = shift;
352 955         1148 my $type = shift;
353              
354 955 100       1614 return -1 if $event->{special};
355 954 100 100     2206 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     714891 || ($event->{ts} == $_->{ts}))
      0        
      33        
      66        
      100        
      100        
363 953         1165 } @{$bs_events};
  953         1806  
364 953 0 33     1980 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       2387 return $candidates[0] if @candidates == 1;
371 16 100       45 return -1 unless @candidates;
372 14         37 @candidates = refine_candidates($event, @candidates);
373 14 100       37 return $candidates[0] if @candidates;
374 13 50       38 return -1 unless @candidates;
375             }
376              
377             sub resolve_report_on_ice ($$) {
378              
379 1586     1586 1 3782 my $event = shift;
380 1586         2145 my $bs = shift;
381              
382 1586 50 66     4274 return if $event->{sources}{GS} && $event->{period} == 5 && $event->{stage} == $REGULAR;
      33        
383 1586         2161 for my $t (0,1) {
384 3172         3559 for my $on_ice (@{$event->{on_ice}[$t]}) {
  3172         6258  
385 18300 50       39454 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     35285 $on_ice,
392             );
393 18300 50       26389 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         19388 $on_ice = ${$new_on_ice}->{_id};
  18300         32402  
400             }
401             }
402             }
403              
404             sub resolve_report_roster ($$$) {
405              
406 26     26 1 62 my $roster = shift;
407 26         34 my $bs = shift;
408 26         35 my $t = shift;
409              
410 26         37 for my $player (@{$roster}) {
  26         59  
411 288 50       438 next if $player->{error};
412 288 100 100     1061 if (($player->{timeOnIce} || defined $player->{start} && $player->{start} != 2) && !($player->{_id} && $player->{_id} eq $EMPTY_NET_ID)) {
      33        
      66        
413 276         425 my $bs_player = find_player($player, $bs->{teams}[$t]);
414 276 50 66     472 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     482 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 2894 my $event = shift;
427 1590         1892 my $report = shift;
428              
429 1590 100       3588 if ($event->{team1}) {
430 1325 50       2352 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         2699 $event->{team1} = resolve_team($event->{team1});
435             }
436 1590 100       4472 if ($event->{team2}) {
437 715 50       1578 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         1420 $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 2902 my $event = shift;
466 1590         1850 my $bs = shift;
467              
468 1590         2716 for my $field (qw(player1 player2 assist1 assist2 servedby)) {
469 7950 100 100     17359 next if ! $event->{$field} || $event->{$field} =~ /^8\d{6}/;
470 2110 100       3501 my $team = $field eq 'player2' ? 'team2' : 'team1';
471 2110 100       2929 my $team2 = $field eq 'player2' ? 'team1' : 'team2';
472 2110 50 33     6294 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     5467 ) || $PLAYER_RESOLVE_CACHE->{$event->{$team2}}{$event->{$field}};
492 2110         2430 $event->{$field} = ${$matched_player}->{_id};
  2110         4537  
493             }
494             }
495             }
496              
497             sub resolve_report ($$) {
498              
499 12     12 1 11244 my $bs = shift;
500 12         24 my $rp = shift;
501              
502 12         29 for my $t (0,1) {
503 24         80 $rp->{teams}[$t]{name} = resolve_team($rp->{teams}[$t]{name});
504 24         92 resolve_report_roster($rp->{teams}[$t]{roster}, $bs, $t);
505             }
506 12 100       85 if ($rp->{events}) {
507 6         54 $rp->set_event_extra_data();
508 6         17 for my $event (@{$rp->{events}}) {
  6         39  
509 1274         2473 resolve_report_event_teams($event, $rp);
510 1274         2961 resolve_report_event_fields($event, $bs);
511 1274 100       2917 resolve_report_on_ice($event, $bs) if ($event->{on_ice});
512             }
513             }
514             }
515              
516             sub merge_me ($$;$$) {
517              
518 1149     1149 1 1553 my $bs_event = shift;
519 1149         1359 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     1922 } keys %{$rp_event}];
527 1149 100       2744 push(@{$fields}, 'stopreason') if $rp_event->{stopreason};
  135         336  
528 1149         1358 for (@{$fields}) {
  1149         1951  
529 5053         7393 when ('stopreason') {
530 135         174 $bs_event->{$_} = [ uniq (@{$bs_event->{stopreason}}, @{$rp_event->{stopreason}}) ];
  135         232  
  135         826  
531             }
532 4918         5726 when ('position') {
533             $bs_event->{$_} = $rp_event->{$_}
534 10 50 33     59 if (!$bs_event->{$_} || $bs_event->{$_} eq 'N/A');
535             }
536 4908         5751 when ('on_ice') {
537             $bs_event->{$_} = $rp_event->{$_}
538 933 50 0     3177 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         4160 default {
545 3975         8227 $bs_event->{$_} = $rp_event->{$_};
546             }
547             }
548 1149 100       4881 if (defined $bs_event->{position}) {
549 204         228 for my $field (keys %{$rp_event}) {
  204         560  
550 3494 50 66     5541 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 21 my $bs_team = shift;
563 14         20 my $rp_team = shift;
564              
565 14         19 for my $rp_player (@{$rp_team->{roster}}) {
  14         26  
566 208 50       305 next if $rp_player->{error};
567 208 100 100     475 next unless $rp_player->{timeOnIce} || defined $rp_player->{start};
568 204 50 66     518 next if $rp_player->{_id} && $rp_player->{_id} == $EMPTY_NET_ID;
569             merge_me(
570 204         447 ${$PLAYER_RESOLVE_CACHE->{$bs_team->{name}}{$rp_player->{number}}},
571             $rp_player, 0
572 204 50       304 ) if $rp_player->{number};
573             }
574             }
575              
576             sub merge_teams ($$) {
577              
578 7     7 1 2276 my $boxscore = shift;
579 7         13 my $report = shift;
580              
581 7         11 for my $t (0,1) {
582 14         26 my $bs_team = $boxscore->{teams}[$t];
583 14         20 my $rp_team = $report->{teams}[$t];
584 14 50       36 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     44 $bs_team->{coach} ||= $rp_team->{coach};
588 14         39 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 23 my $type = shift;
613 12         18 my $event = shift;
614 12         21 my $boxscore = shift;
615 12         22 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     289 || $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 18 my $boxscore = shift;
649 5         12 my $report = shift;
650              
651 5         13 my $type = $report->{type};
652 5         11 while (my $rp_event = shift @{$report->{events}}) {
  963         2262  
653 958 50 66     1842 next if $type eq 'GS' && $boxscore->{sources}{PL} && $boxscore->{season} >= 2007;
      33        
654 948         1908 my $e = find_event($rp_event, $boxscore->{events}, $type);
655 948 100 33     3004 if (! ref $e) {
    50          
656 12 50       35 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       1608 die "UNDEF e " . Dumper($rp_event) unless defined $e;
670 948 100       1661 next if $e == -1;
671 936         1871 $e->{sources}{$type} = 1;
672 936         1660 merge_me($e, $rp_event);
673             }
674             }
675              
676             sub merge_report ($$) {
677              
678 8     8 1 4827 my $boxscore = shift;
679 8         24 my $report = shift;
680              
681 8         23 my $type = $report->{type};
682              
683 8         18 $CURRENT = $type;
684 8         16 $BOXSCORE = $boxscore;
685 8         34 $PLAYER_RESOLVE_CACHE = $boxscore->{resolve_cache};
686 8         40 debug "Merging $type";
687 8         31 resolve_report($boxscore, $report);
688              
689 8         23 for ($type) {
690 8         46 when ([qw(RO ES GS PL)]) {
691 8         33 merge_me($boxscore, $report, \@MERGE_HEADER);
692 8         13 continue;
693             }
694 8         27 when ([qw(RO ES GS)]) {
695 6         22 merge_teams($boxscore, $report);
696 6         12 continue;
697             }
698 8         28 when ([qw(GS PL)]) {
699 4 50       7 @{$boxscore->{events}} ?
  4         27  
700             merge_events($boxscore, $report) : copy_events($boxscore, $report);
701 4         13 continue;
702             }
703             }
704 8         372 $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