File Coverage

blib/lib/Sport/Analytics/NHL/DB.pm
Criterion Covered Total %
statement 42 301 13.9
branch 0 78 0.0
condition 3 84 3.5
subroutine 14 35 40.0
pod 20 20 100.0
total 79 518 15.2


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::DB;
2              
3 66     66   4681 use strict;
  66         129  
  66         1892  
4 66     66   326 use warnings FATAL => 'all';
  66         135  
  66         2387  
5              
6 66     66   395 use Carp;
  66         129  
  66         3608  
7 66     66   4251 use POSIX;
  66         49999  
  66         443  
8              
9 66     66   125165 use List::MoreUtils qw(firstval);
  66         62170  
  66         475  
10 66     66   68472 use Tie::IxHash;
  66         162781  
  66         2013  
11 66     66   24421 use boolean;
  66         71694  
  66         289  
12              
13 66     66   4986 use Sport::Analytics::NHL::Config;
  66         153  
  66         12532  
14 66     66   1512 use Sport::Analytics::NHL::LocalConfig;
  66         127  
  66         7563  
15 66     66   14878 use Sport::Analytics::NHL::Util;
  66         187  
  66         4513  
16              
17 66     66   428 use Data::Dumper;
  66         135  
  66         3567  
18              
19 66   33 66   22164 use if ! $ENV{HOCKEYDB_NODB} && $MONGO_DB, 'MongoDB';
  66         524  
  66         861  
20 66   33 66   3288 use if ! $ENV{HOCKEYDB_NODB} && $MONGO_DB, 'MongoDB::OID';
  66         158  
  66         825  
21 66   33 66   2952 use if ! $ENV{HOCKEYDB_NODB} && $MONGO_DB, 'MongoDB::MongoClient';
  66         145  
  66         589  
22              
23             =head1 NAME
24              
25             Sport::Analytics::NHL::DB - Interface to MongoDB to store NHL reports.
26              
27             =head1 SYNOPSYS
28              
29             Interface to MongoDB in order to store the semi-structured NHL reports into it. Provides the database handle and most of the bulky database operations. Does not subclass MongoDB - the handle is stored in the class's object.
30              
31             use Sport::Analytics::NHL::DB;
32             my $db = Sport::Analytics::NHL::DB->new();
33             my $team_id = $db->resolve_team_db('San Jose Sharks'); # $team_id becomes 'SJS'.
34              
35             =head1 METHODS
36              
37             =over 2
38              
39             =item C
40              
41             Constructor. Sets the database connection. Controlled by global variables:
42             * $MONGO_HOST - host of the mongodb server (def. 127.0.0.1)
43             * $MONGO_PORT - port of the mongodb server (def. 27017)
44             * $MONGO_DB - name of the mongo database (def 'hockey')
45             Also, the database can be specified via $ENV{HOCKEYDB_DBNAME}
46              
47             The database handle is stored in the dbh field of the object which is a blessed hashref.
48              
49             =item C
50              
51             Resolves a team by a given possible identifier to a normalized 3-letter identifier. The target identifiers are the keys to the %TEAMS hash in Sport::Analytics::NHL::Config.pm (q.v.)
52             Argument: the team identifier/name (e.g. 'Rangers')
53             Returns: the system identifier (e.g. NYR)
54              
55             =item C
56              
57             Inserts the collected schedule (see Sport::Analytics::NHL::Scraper), initializing the indexes for the schedule collection if necessary.
58             Collections: schedule
59             Arguments: the list of scheduled games with their defined fields
60             Returns: the number of the inserted games
61              
62             =item C
63              
64             Gets the list of ids of games already in the database
65             Collections: games
66             Arguments: optional - hashref containing the start_season and stop_season of the query
67             Returns: the arrayref of the ids of the existing games
68              
69             =item C
70              
71             Actually puts the fully prepared boxscore, with set references to other collections, into the database.
72              
73             Argument: the boxscore
74             Returns: the inserted id
75              
76             =item C
77              
78             Adds the coaches of the teams from the boxscore to the database and provides a reference to the added coach in the boxscore.
79              
80             Argument: the boxscore
81             Returns: void, the coaches names are replaced with OIDs in the boxscore.
82              
83             =item C
84              
85             Adds a player from the boxscore to the database, and sets his team, injury, start and captaincy statuses and histories.
86              
87             Arguments:
88             * the player hashref as parsed by Sport::Analytics::NHL::Report::Player
89             * the game boxscore
90             * the player's team name
91             * [optional] overwrite force flag
92              
93             Returns: void
94              
95             =item C
96              
97             Initializes a new entry for a coach in the database.
98              
99             Arguments:
100             * the coaches database collection
101             * the boxscore
102             * the team of the coach from the boxscore
103              
104             Returns: the OID of the coach
105              
106             =item C
107              
108             Initializes a new entry for a player in the database.
109              
110             Arguments:
111             * the players database collection
112             * the player parsed by Sport::Analytics::Report::Player (q.v.)
113              
114             Returns: the id of the inserted player
115              
116             =item C
117              
118             Creates a new event in the database, referencing all relevant fields by their own database catalogs. The event is inserted twice: first, with only least indexing information into the general 'events' collection; second, with the particular information in the event type's collection.
119              
120             Argument: the event from the boxscore
121             Returns: the inserted event's id.
122              
123             =item C
124              
125             Creates a new location (stadium/arena) entry in the database by its name and capacity.
126              
127             Argument: the location information from the boxscore
128             Returns: the location entry as inserted.
129              
130             =item C
131              
132             Ensures the correct extra indices for the event's type collection.
133              
134             Arguments:
135             * the event
136             * the event's collection
137              
138             Returns: void
139              
140             =item C
141              
142             Wraps around the new MongoDB collection index creation routine, replacing its own ensure_index() method.
143              
144             Arguments:
145             * the collection
146             * the index mapping as expected by create_one or create_many
147             * [optional] - whether to reapply the index on non-empty collection
148              
149             Returns: the status of the index creation
150              
151             =item C
152              
153             Creates if necessary a catalog of NHL event subtypes (e.g. zones, penalties, stop reasons) by the name of the event subtype as normalized by the vocabulary in Sport::Analytics::NHL::Config (q.v.), and fetches the corresponding entry.
154              
155             Arguments:
156             * the catalog's name to operate upon
157             * the name of the catalog item
158              
159             =item C
160              
161             A wrapper over $self->{dbh}->get_collection();
162              
163             =item C
164              
165              
166              
167             =item C
168              
169             Sets the injury history of the player in the database. Either the current status is extended, or if the status changed, a new chapter is added.
170              
171             Arguments:
172             * player's db entry
173             * the boxscore
174             * the injury status
175              
176             Returns: void
177              
178             =item C
179              
180             Sets the status (captain, assistant captain) history of the player in the database. Either the current status (with the same team) is extended, or if the status changed, a new chapter is added.
181              
182             Arguments:
183             * player's db entry
184             * player's boxscore entry
185             * the boxscore
186             * the player's team name
187              
188             Returns: void
189              
190             =item C
191              
192             Sets the team history of the player in the database. Either the current team is extended, or if the team changed, a new chapter is added.
193              
194             Arguments:
195             * player's db entry
196             * the boxscore
197             * the team
198              
199             Returns: void
200              
201             This function is similar to the two above and all of them may be merged into one.
202              
203             =item C
204              
205             During removal of game data, wipes a game from player's history.
206              
207             Arguments:
208             * player's db entry
209             * game's db entry
210              
211             Returns: void
212              
213             =back
214              
215             =cut
216              
217             our $DEFAULT_DB = 'hockey';
218             our $DEFAULT_HOST = '127.0.0.1';
219             our $DEFAULT_PORT = 27017;
220              
221             our @PLAYER_HISTORIES = qw(teams statuses starts games injury_history);
222             our %EVENT_CATALOGS = (
223             shot_type => 'shot_types',
224             zone => 'zones',
225             miss => 'misses',
226             penalty => 'penalties',
227             strength => 'strengths',
228             );
229              
230             sub new ($;$) {
231              
232 0     0 1   my $class = shift;
233 0   0       my $database = shift || $ENV{HOCKEYDB_DBNAME} || $MONGO_DB || $DEFAULT_DB;
234              
235 0           my $self = {};
236 0   0       my $host = $MONGO_HOST || $DEFAULT_HOST;
237 0   0       my $port = $MONGO_PORT || $DEFAULT_PORT;
238 0           $self->{client} = MongoDB::MongoClient->new(
239             host => sprintf(
240             "mongodb://%s:%d", $host, $port
241             )
242             );
243 0   0       my $db = $database || $DEFAULT_DB;
244 0           $self->{dbh} = $self->{client}->get_database($db);
245 0           $ENV{HOCKEYDB_DBNAME} = $db;
246 0           verbose "Using Mongo database $db";
247 0           $self->{dbname} = $db;
248 0           bless $self, $class;
249 0           $self;
250             }
251              
252 0     0 1   sub get_collection ($$) { shift->{dbh}->get_collection(shift) }
253              
254             sub ensure_index ($$;$) {
255              
256 0     0 1   my $collection = shift;
257 0           my $index_map = shift;
258 0   0       my $reapply = shift || 0;
259              
260 0 0 0       return if ! $reapply && $collection->count();
261              
262 0           my $indices = $collection->indexes();
263 0 0         my $method = ref $index_map eq 'ARRAY'
264             ? 'create_many'
265             : 'create_one';
266             my @index_map = ref $index_map eq 'ARRAY'
267 0 0         ? @{$index_map}
  0            
268             : ($index_map);
269 0           $indices->$method(@index_map);
270             }
271              
272             sub resolve_team_db ($$) {
273              
274 0     0 1   my $self = shift;
275 0           my $team = shift;
276              
277 0           $team = uc $team;
278 0           my $teams_c = $self->get_collection('teams');
279              
280 0           my $team_db = $teams_c->find_one({
281             '$or' => [
282             { _id => $team },
283             { short => $team },
284             { long => $team },
285             { full => $team },
286             ],
287             });
288 0 0         $team_db ? $team_db->{_id} : undef;
289             }
290              
291             sub insert_schedule ($@) {
292              
293 0     0 1   my $self = shift;
294 0           my @games = @_;
295              
296 0 0         return 0 unless @games;
297 0           my $schedule_c = $self->get_collection('schedule');
298 0           ensure_index($schedule_c, [
299             { keys => [ game_id => 1 ], options => {unique => 1} },
300             { keys => [ date => 1 ], },
301             { keys => [ season => 1, stage => 1, season_id => 1 ] },
302             ], 1);
303             @games = grep {
304 0 0 0       if ($_->{stage} == $REGULAR || $_->{stage} == $PLAYOFF) {
305 0           $_->{game_id} += 0;
306 0           $_->{ts} += 0;
307             }
308 0           else { 0 }
309 0 0 0       } map(ref $_ && ref $_ eq 'ARRAY' ? @{$_} : $_, @games);
  0            
310 0           $schedule_c->delete_many({_id => { '$in' => [ map {$_->{_id}} @games ] } });
  0            
311 0           $schedule_c->insert_many([@games]);
312 0           debug "Inserted " . scalar(@games) . " games for season $games[0]->{season}";
313 0           scalar @games;
314             }
315              
316             sub get_existing_game_ids ($;$) {
317              
318 0     0 1   my $self = shift;
319 0   0       my $opts = shift || {
320             stop_season => $CURRENT_SEASON, start_season => $CURRENT_SEASON
321             };
322              
323             my @games = $self->get_collection('games')->find({
324             season => {
325             '$gte' => $opts->{start_season}+0,
326 0           '$lte' => $opts->{stop_season} +0
327             },
328             }, {_id => 1})->all();
329              
330 0           [ map($_->{_id}+0,@games) ];
331             }
332              
333             sub create_location ($$) {
334              
335 0     0 1   my $self = shift;
336 0           my $location = shift;
337              
338 0           my $locations_c = $self->get_collection('locations');
339 0           ensure_index($locations_c, [
340             { keys => [ name => 1 ], options => { unique => 1} }
341             ]);
342 0           $location->{name} = normalize_string($location->{name});
343 0           my $location_db = $locations_c->find_one({name => $location->{name}});
344 0 0         if ($location_db) {
345 0 0         if ($location_db->{capacity} < $location->{capacity}) {
346             $locations_c->update_one(
347             { name => $location->{name} },
348             { '$set' => { capacity => $location->{capacity} }}
349 0           );
350             }
351             }
352             else {
353 0           $locations_c->insert_one($location);
354             }
355 0           $locations_c->find_one({name => $location->{name}});
356             }
357              
358             sub add_new_player ($$) {
359              
360 0     0 1   my $players_c = shift;
361 0           my $player = shift;
362              
363 0           for my $h (@PLAYER_HISTORIES) {
364 0   0       $player->{$h} ||= [];
365             }
366 0   0       $player->{games} ||= [];
367 0   0       $player->{starts} ||= [];
368 0           $players_c->insert_one($player);
369 0           $players_c->find_one({_id => $player->{_id}});
370             }
371              
372             sub set_player_statuses ($$$$) {
373              
374 0     0 1   my $player_db = shift;
375 0           my $player_game = shift;
376 0           my $game = shift;
377 0           my $team = shift;
378              
379 0   0       $player_db->{statuses} ||= [];
380 0 0 0       if (
      0        
381 0           ! @{$player_db->{statuses}}
382             || $player_db->{statuses}[-1]{status} ne $player_game->{status}
383             || $player_db->{statuses}[-1]{team} ne $team
384             ) {
385             push(
386 0           @{$player_db->{statuses}}, {
387             start => $game->{start_ts}, end => $game->{start_ts},
388             team => $team, status => $player_game->{status},
389             },
390 0           );
391             }
392             else {
393             $player_db->{statuses}[-1]{end} = $game->{start_ts}
394 0 0         if $player_db->{statuses}[-1]{end} < $game->{start_ts};
395             $player_db->{statuses}[-1]{start} = $game->{start_ts}
396 0 0         if $player_db->{statuses}[-1]{start} > $game->{start_ts};
397             }
398             }
399              
400             sub set_player_teams ($$$) {
401              
402 0     0 1   my $player_db = shift;
403 0           my $game = shift;
404 0           my $team = shift;
405              
406 0   0       $player_db->{teams} ||= [];
407 0 0 0       if (! @{$player_db->{teams}} || $player_db->{teams}[-1]{team} ne $team) {
  0            
408             push(
409 0           @{$player_db->{teams}}, {
410             start => $game->{start_ts}, end => $game->{start_ts},
411 0           team => $team,
412             },
413             );
414             }
415             else {
416             $player_db->{teams}[-1]{end} = $game->{start_ts}
417 0 0         if $player_db->{teams}[-1]{end} < $game->{start_ts};
418             $player_db->{teams}[-1]{start} = $game->{start_ts}
419 0 0         if $player_db->{teams}[-1]{start} > $game->{start_ts};
420             }
421 0           $player_db->{team} = $team;
422             }
423              
424             sub set_injury_history ($$$) {
425              
426 0     0 1   my $player_db = shift;
427 0           my $game = shift;
428 0           my $injury_status = shift;
429              
430 0   0       $player_db->{injury_history} ||= [];
431 0 0 0       if (! @{$player_db->{injury_history}}
  0            
432             || $player_db->{injury_history}[-1]{status} ne $injury_status) {
433             push(
434 0           @{$player_db->{injury_history}}, {
435             start => $game->{start_ts}, end => $game->{start_ts},
436 0           status => $injury_status,
437             },
438             );
439             }
440             else {
441             $player_db->{injury_history}[-1]{end} = $game->{start_ts}
442 0 0         if $player_db->{injury_history}[-1]{end} < $game->{start_ts};
443             $player_db->{injury_history}[-1]{start} = $game->{start_ts}
444 0 0         if $player_db->{injury_history}[-1]{start} > $game->{start_ts};
445             }
446 0           $player_db->{injury_status} = $injury_status;
447 0           $player_db;
448             }
449              
450             sub wipe_game_from_player_history ($$) {
451              
452 0     0 1   my $player_db = shift;
453 0           my $game = shift;
454              
455 0           debug "Cleaning game $game->{_id} from the records of $player_db->{_id}";
456             $player_db->{games} = [ grep {
457 0           $game->{_id} != $_
458 0           } @{$player_db->{games}} ];
  0            
459             $player_db->{starts} = [ grep {
460 0           $game->{_id} != $_
461 0           } @{$player_db->{starts}} ];
  0            
462             }
463              
464             sub add_game_player ($$$$;$) {
465              
466 0     0 1   my $self = shift;
467 0           my $player = shift;
468 0           my $game = shift;
469 0           my $team_name = shift;
470 0   0       my $force = shift || 0;
471              
472 0           my $players_c = $self->get_collection('players');
473 0           ensure_index($players_c, {name => 1});
474             my $player_db = $players_c->find_one({_id => $player->{_id}})
475 0   0       || add_new_player($players_c, $player);
476 0 0   0     if (firstval { $game->{_id} == $_ } @{$player_db->{games}} > -1) {
  0            
  0            
477 0 0         if ($force) {
478 0           wipe_game_from_player_history($player_db, $game);
479             }
480             else {
481 0           verbose "Player $player->{_id} already has $game->{_id} in his history, skipping";
482 0           return $player_db;
483             }
484             }
485 0           for my $h (@PLAYER_HISTORIES) {
486 0   0       $player_db->{$h} ||= [];
487             }
488 0 0         my $team = $game->{teams}[$game->{teams}[0]{name} eq $team_name ? 0 : 1];
489             my $player_game = (grep {
490             $player->{_id} == $_->{_id}
491 0           } @{$team->{roster}})[0];
  0            
  0            
492 0           push(@{$player_db->{games}}, $game->{_id} + 0);
  0            
493 0           push(@{$player_db->{starts}}, $game->{_id} + 0)
494 0 0 0       if defined $player_game->{start} && $player_game->{start} == 1;
495 0   0       $player_game->{status} ||= 'X';
496 0   0       $player_game->{start} ||= 2;
497 0           set_player_statuses($player_db, $player_game, $game, $team->{name});
498 0           set_player_teams($player_db, $game, $team->{name});
499 0           set_injury_history($player_db, $game, 'OK');
500             $players_c->replace_one(
501             { _id => delete $player_db->{_id} },
502 0           $player_db,
503             )
504             }
505              
506             sub add_new_coach ($$$) {
507              
508 0     0 1   my $coaches_c = shift;
509 0           my $game = shift;
510 0           my $team = shift;
511              
512             $coaches_c->insert_one({
513             name => $team->{coach},
514             teams => [{
515             start => $game->{start_ts},
516             end => $game->{start_ts},
517             team => $team->{name},
518             }],
519             games => [ $game->{_id} ],
520             team => $team->{name},
521             start => $game->{start_ts},
522             end => $game->{start_ts},
523 0           });
524 0           $coaches_c->find_one({name => $team->{coach}});
525             }
526              
527             sub add_game_coaches ($$) {
528              
529 0     0 1   my $self = shift;
530 0           my $game = shift;
531              
532 0           my $coaches_c = $self->{dbh}->get_collection('coaches');
533              
534 0           ensure_index($coaches_c, {name => 1});
535 0           for my $t (0,1) {
536 0           my $team = $game->{teams}[$t];
537 0 0         next if ref $team->{coach};
538             my $coach_db = $coaches_c->find_one({name => $team->{coach}})
539 0   0       || add_new_coach($coaches_c, $game, $team);
540 0           debug "Setting coach from $team->{coach} to $coach_db->{_id}";
541 0           $team->{coach} = $coach_db->{_id};
542 0 0         next if $coach_db->{name} eq 'UNKNOWN COACH';
543              
544 0 0         next if grep { $game->{_id} == $_ } @{$coach_db->{games}};
  0            
  0            
545 0           $coach_db->{end} = $game->{start_ts};
546 0           push(@{$coach_db->{games}}, $game->{_id});
  0            
547 0 0         if ($coach_db->{team} eq $team->{name}) {
548 0           $coach_db->{teams}[-1]{end} = $game->{start_ts};
549             }
550             else {
551 0           push(@{$coach_db->{teams}}, {
552             start => $game->{start_ts},
553             end => $game->{start_ts},
554             team => $team->{name},
555 0           });
556             $coach_db->{start} = $game->{start_ts}
557 0 0         if $coach_db->{start} > $game->{start_ts};
558             $coach_db->{end} = $game->{start_ts}
559 0 0         if $coach_db->{end} < $game->{start_ts};
560 0           $coach_db->{team} = $team->{name};
561             }
562             $coaches_c->replace_one(
563             { _id => $coach_db->{_id} },
564 0           $coach_db
565             );
566             }
567             }
568              
569             sub get_catalog_entry ($$$) {
570              
571 0     0 1   my $self = shift;
572 0           my $catalog = shift;
573 0           my $name = shift;
574              
575 0 0         return $name if ref $name;
576 0           my $catalog_c = $self->get_collection($catalog);
577 0           ensure_index($catalog_c, [
578             { keys => [ name => 1 ], options => { unique => 1 } },
579             ]);
580 0           my $entry = $catalog_c->find_one({ name => $name });
581 0 0         if (! $entry) {
582 0           debug "DB: $self->{dbname} inserting $name into catalog $catalog";
583 0           $catalog_c->insert_one({ name => $name });
584 0           $entry = $catalog_c->find_one({ name => $name });
585             }
586 0           $entry;
587             }
588              
589             sub ensure_event_indices ($$$) {
590              
591 0     0 1   my $self = shift;
592 0           my $event = shift;
593 0           my $event_c = shift;
594              
595 0 0         if ($event->{type} eq 'STOP') {
596             $event->{stopreasons} = [ map (
597             $self->get_catalog_entry('stopreasons', $_)->{_id},
598 0           @{$event->{stopreason}},
  0            
599             )];
600 0           delete $event->{stopreason};
601             }
602 0           my $keys = {
603             keys => [ game_id => 1 ],
604             };
605             $keys->{options} = { unique => 1 }
606             if $event->{type} eq 'GEND'
607             || $event->{type} eq 'PSTR'
608 0 0 0       || $event->{type} eq 'PEND';
      0        
609 0           push(@{$keys->{keys}}, period => 1)
610             if $event->{type} eq 'PEND'
611 0 0 0       || $event->{type} eq 'PSTR';
612 0           ensure_index($event_c, [ $keys ]);
613 0 0         if (exists $event->{coordinates}{x}) {
614 0           $event->{coordinates}{x} += 0;
615 0           $event->{coordinates}{y} += 0;
616             }
617 0           $event->{_id} += 0;
618             }
619              
620             sub create_event ($$) {
621              
622 0     0 1   my $self = shift;
623 0           my $event = shift;
624              
625 0           my $event_c = $self->get_collection($event->{type});
626 0           my $event_db = $event_c->find_one({_id => $event->{_id}+0});
627 0 0         return $event->{_id} if $event_db;
628 0           for my $field (qw(shot_type miss penalty strength zone)) {
629             $event->{$field} = $self->get_catalog_entry(
630             $EVENT_CATALOGS{$field}, $event->{$field}
631 0 0         )->{_id} if exists $event->{$field};
632             }
633 0           $self->ensure_event_indices($event, $event_c);
634 0           my $events_c = $self->get_collection('events');
635 0           ensure_index($events_c, [
636             { keys => [ event_id => 1 ], options => {unique => 1} },
637             { keys => [ game_id => 1 ], },
638             ]);
639             $events_c->insert_one({
640             type => $event->{type},
641             event_id => $event->{_id} + 0,
642 0           game_id => $event->{game_id} + 0,
643             });
644 0           $event_c->insert_one($event);
645 0           $event->{_id};
646             }
647              
648             sub remove_game ($$) {
649              
650 0     0 1   my $self = shift;
651 0           my $game = shift;
652              
653 0           my $events_c = $self->get_collection('events');
654 0           my $events_i = $events_c->find({game_id => $game->{_id} + 0});
655 0           my %collections = ();
656 0           debug "Cleaning events";
657 0           while (my $_event = $events_i->next()) {
658 0 0         if (! $collections{$_event->{type}}) {
659 0           $collections{$_event->{type}} = 1;
660 0           my $event_c = $self->get_collection($_event->{type});
661 0           $event_c->delete_many({game_id => $game->{_id} + 0});
662             }
663             }
664 0           $events_c->delete_many({game_id => $game->{_id} + 0});
665 0           my $coaches_c = $self->get_collection('coaches');
666 0           my $players_c = $self->get_collection('players');
667 0           for my $t (0,1) {
668 0           my $team = $game->{teams}[$t];
669 0           my $coach = $coaches_c->find_one({_id => $team->{coach}});
670 0 0         if ($coach->{name} ne 'UNKNOWN COACH') {
671 0           debug "Cleaning coach";
672             $coach->{games} =
673 0           [ grep { $_ != $game->{_id} } @{$coach->{games}} ];
  0            
  0            
674             $coaches_c->update_one({
675             _id => $coach->{_id},
676             }, {
677             '$set' => { games => $coach->{games} }
678 0           });
679             }
680 0           for my $player (@{$team->{roster}}) {
  0            
681 0           debug "Cleaning player";
682             $player->{games} =
683 0           [ grep { $_ != $game->{_id} } @{$player->{games}} ];
  0            
  0            
684             $player->{starts} =
685 0           [ grep { $_ != $game->{_id} } @{$player->{starts}} ];
  0            
  0            
686             $players_c->update_one({
687             _id => $player->{_id},
688             }, {
689             '$set' => {
690             starts => $player->{starts},
691             games => $player->{games}
692             }
693 0           });
694             }
695             }
696 0           my $games_c = $self->get_collection('games');
697 0           $games_c->delete_one({_id => $game->{_id}+0 });
698             }
699              
700             sub add_game ($$) {
701              
702 0     0 1   my $self = shift;
703 0           my $game = shift;
704              
705 0           my $games_c = $self->get_collection('games');
706 0           for my $t (0,1) {
707 0           for my $player (@{$game->{teams}[$t]{roster}}) {
  0            
708 0           for my $stat (keys %{$player}) {
  0            
709 0 0         delete $player->{$stat} if ref $player->{$stat};
710             }
711             }
712             }
713 0           $games_c->insert_one($game);
714 0           $game->{_id};
715             }
716              
717             1;
718              
719             =head1 AUTHOR
720              
721             More Hockey Stats, C<< >>
722              
723             =head1 BUGS
724              
725             Please report any bugs or feature requests to C, or through
726             the web interface at L. I will be notified, and then you'll
727             automatically be notified of progress on your bug as I make changes.
728              
729             =head1 SUPPORT
730              
731             You can find documentation for this module with the perldoc command.
732              
733             perldoc Sport::Analytics::NHL::DB
734              
735              
736             You can also look for information at:
737              
738             =over 4
739              
740             =item * RT: CPAN's request tracker (report bugs here)
741              
742             L
743              
744             =item * AnnoCPAN: Annotated CPAN documentation
745              
746             L
747              
748             =item * CPAN Ratings
749              
750             L
751              
752             =item * Search CPAN
753              
754             L
755              
756             =back
757              
758              
759             =head1 ACKNOWLEDGEMENTS
760              
761              
762             =head1 LICENSE AND COPYRIGHT
763              
764             Copyright 2018 More Hockey Stats.
765              
766             This program is released under the following license: gnu
767              
768             =cut