File Coverage

blib/lib/Sport/Analytics/NHL/Report/Player.pm
Criterion Covered Total %
statement 183 208 87.9
branch 32 54 59.2
condition 35 76 46.0
subroutine 25 26 96.1
pod 7 7 100.0
total 282 371 76.0


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::Report::Player;
2              
3 28     28   158664 use v5.10.1;
  28         172  
4 28     28   146 use strict;
  28         59  
  28         672  
5 28     28   121 use warnings FATAL => 'all';
  28         48  
  28         1105  
6 28     28   140 use experimental qw(smartmatch);
  28         53  
  28         208  
7              
8 28     28   9313 use Encode;
  28         130659  
  28         2412  
9 28     28   1773 use Storable qw(dclone);
  28         7993  
  28         1549  
10              
11 28     28   1244 use Date::Parse;
  28         13652  
  28         3131  
12 28     28   1206 use JSON;
  28         17885  
  28         235  
13 28     28   9336 use Try::Tiny;
  28         25779  
  28         1438  
14 28     28   6639 use Text::Unidecode;
  28         25106  
  28         1209  
15              
16 28     28   927 use Sport::Analytics::NHL::Util;
  28         66  
  28         1961  
17 28     28   928 use Sport::Analytics::NHL::Tools;
  28         62  
  28         5222  
18 28     28   181 use Sport::Analytics::NHL::LocalConfig;
  28         62  
  28         3447  
19 28     28   177 use Sport::Analytics::NHL::Config;
  28         53  
  28         4962  
20 28     28   1658 use Sport::Analytics::NHL::Errors;
  28         82  
  28         5133  
21              
22 28     28   176 use base qw(Sport::Analytics::NHL::Report Exporter);
  28         55  
  28         6782  
23              
24             =head1 NAME
25              
26             Sport::Analytics::NHL::Report::Player - Class for the Player JSON report
27              
28             =head1 SYNOPSYS
29              
30             Class for the Boxscore JSON report.
31              
32             use Sport::Analytics::NHL::Report::Player;
33             my $report = Sport::Analytics::NHL::Report::Player->new($json)
34             $report->process();
35              
36             =head1 METHODS
37              
38             =over 2
39              
40             =item C
41              
42             Create the Player object with the JSON.
43              
44             =item C
45              
46             Process the Player into the object compatible with further processing, etc.
47              
48             =item C
49              
50             Get the index of a career phase - regular or playoffs
51              
52             Argument: the phase substructure of th eobject
53             Returns: 0 for regular, 1 playoffs, dies otherwise
54              
55             =item C
56              
57             Normalize and standardize the bio parts of the NHL player report.
58              
59             Arguments: none, works on the object itself
60             Returns: void, the object is modified.
61              
62             =item C
63              
64             Normalize and standardize the career parts of the NHL player report.
65              
66             Arguments: none, works on the object itself
67             Returns: void, the object is modified.
68              
69             =item C
70              
71             Parse the bio parts of the NHL player report.
72              
73             Arguments: none, works on the object itself
74             Returns: void, the object is modified.
75              
76             =item C
77              
78             Parse the career parts of the NHL player report.
79              
80             Arguments: none, works on the object itself
81             Returns: void, the object is modified.
82              
83             =back
84              
85             =cut
86              
87             our @EXPORT = qw();
88              
89             our @PLAYER_FIELDS = qw(
90             number birthdate height city weight draftyear shoots
91             draftteam round pick name position team active rookie
92             );
93             our @COUNTRIES = (qw(
94             Canada Slovakia Slovenia Yugoslavia Germany Sweden Finland Japan
95             Switzerland Russia Ukraine Belarus Lithuania Romania Latvia
96             Haiti Kazakhstan Poland
97             ), "Czech Republic", "United States");
98              
99 28     28   189 use Data::Dumper;
  28         58  
  28         52743  
100              
101             sub new ($$$) {
102              
103 6     6 1 15 my $class = shift;
104 6         9 my $json = shift;
105              
106 6         48 my $code = JSON->new();
107 6         29 $code->utf8(1);
108 6         7 my $self;
109 6 50       17 return undef unless $json;
110 6     6   282 try { $self = {json => $code->decode(decode "UTF-8", $json)} }
111 6     0   53 catch { $self = {json => $code->decode($json)} };
  0         0  
112             # $self->{json} = $self->{json}{people}[0];
113 6 50       5320 return undef unless $self->{json}{id};
114 6         16 bless $self, $class;
115 6         431 $self;
116             }
117              
118             sub parse_bio ($) {
119              
120 3     3 1 46 my $self = shift;
121 3         26 my $bio = $self->{json};
122              
123             my $player = {
124             _id => $bio->{id},
125             name => $bio->{fullName},
126             position => $bio->{primaryPosition}{code},
127             number => $bio->{primaryNumber},
128             shoots => $bio->{shootsCatches} || 'R',
129             birthdate => $bio->{birthDate},
130             city => $bio->{birthCity},
131             state => $bio->{birthStateProvince} || 'NA',
132             country => $bio->{birthCountry},
133             team => $bio->{currentTeam}{name},
134 3   50     42 };
      100        
135 3         8 for my $key (
136             qw(active rookie height weight pick round draftteam draftyear undrafted),
137 3         12 keys %{$player},
138             ) {
139 57   100     175 $self->{$key} = $player->{$key} || $bio->{$key};
140             }
141 3 50       13 if ($MISSING_PLAYER_INFO{$bio->{id}}) {
142 0         0 for my $k (keys %{$MISSING_PLAYER_INFO{$bio->{id}}}) {
  0         0  
143 0   0     0 $self->{$k} ||= $MISSING_PLAYER_INFO{$bio->{id}}->{$k};
144             }
145             }
146 3         11 $self->{name} = normalize_string($player->{name});
147             }
148              
149             sub get_career_phase_index ($) {
150              
151 6     6 1 8 my $phase = shift;
152              
153 6         9 my $index;
154              
155 6 100       17 if ($phase->{type}{displayName} eq 'yearByYear') {
    50          
156 3         5 $index = 0;
157             }
158             elsif ($phase->{type}{displayName} eq 'yearByYearPlayoffs') {
159 3         3 $index = 1;
160             }
161             else {
162 0         0 die "Strange phase $phase->{type}{displayName}";
163             }
164 6         8 $index;
165             }
166              
167             sub parse_career ($) {
168              
169 3     3 1 4 my $self = shift;
170              
171 3         7 my $j_career = $self->{json}{stats};
172 3         5 my $position = $self->{position};
173              
174 3         4 my $career = [];
175 3         5 my $c;
176 3         4 for my $phase (@{$j_career}) {
  3         6  
177 6         15 my $c = get_career_phase_index($phase);
178 6         12 $career->[$c] = [];
179 6         7 for my $season (@{$phase->{splits}}) {
  6         14  
180 160 50       223 next unless $season->{season};
181 160         199 my $start = substr($season->{season}, 0, 4);
182 160         169 my $end = substr($season->{season}, 4, 4);
183 160         166 my $career_year = {};
184 160         254 $career_year->{season} = "$start-$end";
185             $career_year->{team} =
186 160   33     334 $season->{team}{abbreviation} || $season->{team}{name} || $season->{team}{id};
187 160         209 $career_year->{league} = $season->{league}{name};
188 160         182 $career_year->{gp} = $season->{stat}{games};
189 160         187 $career_year->{pim} = $season->{stat}{pim};
190 160         199 $career_year->{toi} = $season->{stat}{timeOnIce};
191 160 50       217 if ($position eq 'G') {
192 0         0 $career_year->{w} = $season->{stat}{wins};
193 0         0 $career_year->{l} = $season->{stat}{losses};
194 0         0 $career_year->{t} = $season->{stat}{ties};
195 0         0 $career_year->{ot} = $season->{stat}{ot};
196 0         0 $career_year->{so} = $season->{stat}{shutouts};
197 0         0 $career_year->{ga} = $season->{stat}{goalsAgainst};
198 0         0 $career_year->{sa} = $season->{stat}{saves};
199 0   0     0 $career_year->{'sv%'} = $season->{stat}{savePercentage} || 0;
200 0         0 $career_year->{gaa} = $season->{stat}{goalAgainstAverage};
201 0   0     0 $career_year->{min} = sprintf("%.0f", get_seconds($season->{stat}{timeOnIce} || '0:00')/60);
202 0         0 $career_year->{gs} = $season->{stat}{gamesStarted};
203             }
204             else {
205 160         186 $career_year->{g} = $season->{stat}{goals};
206 160         226 $career_year->{a} = $season->{stat}{assists};
207 160         191 $career_year->{pts} = $season->{stat}{points};
208 160         175 $career_year->{'+/-'} = $season->{stat}{plusMinus};
209 160         186 $career_year->{ppg} = $season->{stat}{powerPlayGoals};
210 160         186 $career_year->{shg} = $season->{stat}{shortHandedGoals};
211 160         177 $career_year->{s} = $season->{stat}{shots};
212 160         189 $career_year->{'s%'} = $season->{stat}{shotPct};
213 160         177 $career_year->{gwg} = $season->{stat}{gameWinningGoals};
214 160         260 $career_year->{shifts} = $season->{stat}{shifts};
215 160         184 $career_year->{'fo%'} = $season->{stat}{faceOffPct};
216 160         187 $career_year->{otg} = $season->{stat}{overTimeGoals};
217             }
218 160         157 push(@{$career->[$c]}, $career_year);
  160         258  
219             }
220             }
221 3         9 $self->{career} = $career;
222             }
223              
224             sub normalize_bio ($) {
225              
226 3     3 1 5 my $self = shift;
227              
228 3         9 for my $field (@PLAYER_FIELDS) {
229 45 100       109 if (defined $self->{$field}) {
230 40         71 $self->{$field} =~ s/^\s//;
231 40         75 $self->{$field} =~ s/\s$//;
232 40         76 for ($field) {
233 40         63 when ('name') { $self->{$field} = uc $self->{$field} }
  3         9  
234 37         41 when ('number') { $self->{$field} =~ s/\D//g; }
  3         9  
235 34         38 when ('weight') { $self->{$field} =~ s/\D//g; }
  3         10  
236 31         38 when ('shoots') {
237 3         8 $self->{$field} = substr($self->{$field}, 0, 1)
238             }
239 28         30 when ('position') {
240 3         10 $self->{$field} = substr($self->{$field}, 0, 1)
241             }
242 25         27 when ('birthdate') {
243 3         20 $self->{$field} = str2time($self->{$field});
244             }
245 22         38 when (['draftteam', 'team']) {
246 3         11 $self->{$field} = resolve_team($self->{$field});
247             }
248 19         28 when ('height') {
249 3         13 $self->{$field} =~ /(\d)\'\s*(\d+)\"/;
250 3 50 33     24 $self->{$field} = $1 * 12 + $2 if defined $1 && defined $2;
251             }
252 16         20 when ('draftyear') {
253 2 50       7 if ($self->{draftyear} =~ /^\s*(\S\S\S)\s+.*?(\d{4})/) {
254 0         0 $self->{draftteam} = $1;
255 0         0 $self->{draftyear} = $2;
256             }
257             }
258 14         19 when ('draftposition') {
259 0 0       0 if($self->{draftposition} =~ /(\d+)\D+(\d+)/) {
260 0         0 $self->{round} = $1;
261 0         0 $self->{pick} = $2;
262             }
263             }
264             }
265             $self->{$field} += 0
266 40 100 66     888 if defined $self->{$field} && $self->{$field} =~ /^\-?\d*\.?\d+$/;
267             }
268             else {
269 5         8 delete $self->{$field};
270             }
271             }
272             }
273              
274             sub normalize_career ($) {
275              
276 3     3 1 5 my $self = shift;
277              
278 3         6 my $career = $self->{career};
279              
280 3         4 for my $stage (@{$career}) {
  3         4  
281 6         10 for my $season (@{$stage}) {
  6         12  
282 160         182 my @fields = keys %{$season};
  160         621  
283 160         221 for my $field (@fields) {
284 2892 100       4045 unless (defined $season->{$field}) {
285 746         830 delete $season->{$field};
286 746         819 next;
287             }
288 2146         3244 $season->{$field} =~ s/^\s+//;
289 2146         2668 $season->{$field} =~ s/\s+$//;
290 2146         2350 $season->{$field} =~ s/\,//g;
291 2146 50       2897 $season->{$field} = '' if $season->{$field} eq '-';
292 2146 50       3203 $season->{lc $field} = delete $season->{$field} if $field =~ /[A-Z]/;
293             }
294             # NHL data error fix
295 160 50 33     256 $season->{ga} = 117 if $season->{ga} && $season->{ga} == 871;
296 160 50 33     230 $season->{ga} = 94 if $season->{ga} && $season->{ga} == 1465;
297 160 50 33     235 $season->{ga} = 13 if $season->{ga} && $season->{ga} == 1380;
298 160 50 66     418 $season->{gp} = 9 if $season->{gp} && $season->{gp} == 119;
299 160 50 66     357 $season->{gp} = 47 if $season->{gp} && $season->{gp} == 487;
300 160 50 33     252 $season->{so} = 1 if $season->{so} && $season->{so} == 149;
301 160 50       461 if ($season->{season} =~ /(\d+)\-(\d+)/) {
302 160         362 $season->{start} = $1;
303 160         265 $season->{end} = $2;
304 160   50     222 $season->{team} ||= 'Unknown-UNKHL';
305 160   50     207 $season->{league} ||= 'UNKHL';
306 160 100 66     405 $season->{league} = $LEAGUE_NAME if $season->{league} eq 'National Hockey League' && $season->{start} >= 1942;
307 160 100 66     981 if ($season->{team} =~ /(.*?)\-(\S+)\s*$/ ||
      66        
      33        
      33        
      33        
      33        
308             $season->{team} =~ /(.*?)\-(\S+\s*Ten)$/ ||
309             $season->{team} =~ /(.*?)\-(\S+\s*Midget)$/ ||
310             $season->{team} =~ /(.*?)\-(\S+)\s*Italy\s*$/ ||
311             $season->{team} =~ /(.*?)\s+(U18-20 Elit)/ ||
312             $season->{team} =~ /(.*?)\-(\S+\s*Jr.)/ ||
313             $season->{team} =~ /(.*?)\-(\S+\s*Sr.)$/) {
314 1         2 $season->{team} = $1;
315 1         3 $season->{league} = $2;
316             }
317             else {
318 159   50     222 $season->{league} ||= 'NHL';
319             }
320 160 100       285 if ($season->{league} eq 'NHL') {
321 101   66     162 $stage->[-1]{career_start} ||= $season->{start};
322             $stage->[-1]{career_end} = $season->{end}
323 101 50 100     357 if $season->{end} > ($season->{career_end} || 0);
324             }
325             }
326             else {
327 0 0 0     0 if ($season->{team} =~ /total/i && $season->{gp}) {
328 0         0 $season->{season} = 'total';
329 0         0 $season->{league} = 'NHL';
330             }
331             else {
332 0         0 $season->{league} = 'bogus';
333             }
334             }
335             }
336             }
337 3         7 $career;
338             }
339              
340             sub process ($) {
341              
342 3     3 1 44 my $self = shift;
343              
344 3         15 $self->parse_bio();
345 3         13 $self->normalize_bio();
346 3         32 $self->parse_career();
347 3         10 $self->normalize_career();
348              
349 3         522 delete $self->{json};
350             }
351              
352             1;
353              
354             =head1 AUTHOR
355              
356             More Hockey Stats, C<< >>
357              
358             =head1 BUGS
359              
360             Please report any bugs or feature requests to C, or through
361             the web interface at L. I will be notified, and then you'll
362             automatically be notified of progress on your bug as I make changes.
363              
364              
365             =head1 SUPPORT
366              
367             You can find documentation for this module with the perldoc command.
368              
369             perldoc Sport::Analytics::NHL::Report::Player
370              
371             You can also look for information at:
372              
373             =over 4
374              
375             =item * RT: CPAN's request tracker (report bugs here)
376              
377             L
378              
379             =item * AnnoCPAN: Annotated CPAN documentation
380              
381             L
382              
383             =item * CPAN Ratings
384              
385             L
386              
387             =item * Search CPAN
388              
389             L
390              
391             =back