File Coverage

blib/lib/Algorithm/Classifier/IsolationForest/App/Command/proto.pm
Criterion Covered Total %
statement 55 65 84.6
branch 26 38 68.4
condition 2 9 22.2
subroutine 8 10 80.0
pod 4 5 80.0
total 95 127 74.8


line stmt bran cond sub pod time code
1             package Algorithm::Classifier::IsolationForest::App::Command::proto;
2              
3 81     81   51418 use strict;
  81         193  
  81         2592  
4 81     81   305 use warnings;
  81         133  
  81         4192  
5 81     81   980 use Algorithm::Classifier::IsolationForest ();
  81         621  
  81         2467  
6 81     81   505 use Algorithm::Classifier::IsolationForest::App -command;
  81         202  
  81         728  
7 81     81   24374 use File::Slurp qw(read_file write_file);
  81         145  
  81         60488  
8              
9             sub opt_spec {
10             return (
11             [
12 5     5 1 126237 'from-model=s',
13             'Extract a prototype from this saved model JSON (batch or online).',
14             { 'completion' => 'files' }
15             ],
16             [
17             'check=s',
18             'Validate this prototype file and print a summary of it; exits non-zero when invalid.',
19             { 'completion' => 'files' }
20             ],
21             [
22             'o=s',
23             'Output the prototype to this file instead of printing (--from-model only).',
24             { 'completion' => 'files' }
25             ],
26             [ 'w', 'If the file specified via -o exists, over write it.' ],
27             );
28             } ## end sub opt_spec
29              
30 0     0 1 0 sub abstract { 'Extract a prototype from a saved model, or validate a prototype file' }
31              
32             sub description {
33 0     0 1 0 'Works with model prototypes: small JSON documents holding the variable
34             schema (feature names, per-feature descriptions, munger specs, missing
35             policy), a user-owned schema_version and schema_description, and
36             optionally the tuning knobs. `iforest fit --prototype` and
37             `iforest stream --prototype` create models from one; see PROTOTYPES in
38             the Algorithm::Classifier::IsolationForest POD for the file format.
39              
40             --from-model extracts a prototype from a saved model, closing the loop:
41             pull the schema and knobs out of a good model, edit the metadata, and
42             create fresh models from it. A model with no recorded schema_version /
43             schema_description gets placeholder values to edit in.
44              
45             --check validates a prototype file and prints a summary of what it
46             describes, exiting non-zero when the file is not a valid prototype.
47              
48             Exactly one of --from-model or --check must be given.
49             ';
50             } ## end sub description
51              
52             sub validate {
53 5     5 0 13 my ( $self, $opt, $args ) = @_;
54              
55 5 100       23 my $from = defined $opt->{'from_model'} ? 1 : 0;
56 5 100       14 my $check = defined $opt->{'check'} ? 1 : 0;
57 5 100       17 if ( $from + $check != 1 ) {
58 2         11 $self->usage_error('exactly one of --from-model or --check must be specified');
59             }
60              
61 3 100       14 my ( $switch, $file ) = $from ? ( '--from-model', $opt->{'from_model'} ) : ( '--check', $opt->{'check'} );
62 3 50       101 if ( !-f $file ) {
    50          
63 0         0 $self->usage_error( $switch . ', "' . $file . '", is not a file or does not exist' );
64             } elsif ( !-r $file ) {
65 0         0 $self->usage_error( $switch . ', "' . $file . '", is not readable' );
66             }
67              
68 3 100       15 if ( defined $opt->{'o'} ) {
69 1 50       2 if ($check) {
70 0         0 $self->usage_error('-o may only be used with --from-model');
71             }
72 1 50 33     66 if ( -e $opt->{'o'} && !$opt->{'w'} ) {
73 0         0 $self->usage_error( '-o, "' . $opt->{'o'} . '", already exists and -w is not specified' );
74             }
75             }
76              
77 3         12 return 1;
78             } ## end sub validate
79              
80             sub execute {
81 3     3 1 18 my ( $self, $opt, $args ) = @_;
82              
83 3 100       10 if ( defined $opt->{'from_model'} ) {
84 1         10 my $model = Algorithm::Classifier::IsolationForest->load( $opt->{'from_model'} );
85 1         5 my $proto_json = $model->to_prototype;
86 1 50       582 if ( defined $opt->{'o'} ) {
87 1         9 write_file( $opt->{'o'}, { 'atomic' => 1 }, $proto_json . "\n" );
88             } else {
89 0         0 print $proto_json. "\n";
90             }
91 1         1202 return 1;
92             } ## end if ( defined $opt->{'from_model'} )
93              
94             # --check: structural validation plus a human summary of the file.
95 2         16 my $raw = read_file( $opt->{'check'} );
96 2         237 my $proto = eval { Algorithm::Classifier::IsolationForest->validate_prototype($raw) };
  2         20  
97 2 100       158 die( '--check, "' . $opt->{'check'} . '", is not a valid prototype: ' . $@ ) if $@;
98              
99 1         2 my $schema = $proto->{schema};
100 1         2 my $tags = $schema->{feature_names};
101 1         7 printf " %-20s %s\n", 'file', $opt->{'check'};
102 1         3 printf " %-20s %s\n", 'class', $proto->{class};
103 1         3 printf " %-20s %s\n", 'schema_version', $proto->{schema_version};
104 1         3 printf " %-20s %s\n", 'schema_description', $proto->{schema_description};
105 1 50       5 printf " %-20s %s\n", 'missing', ( defined $schema->{missing} ? $schema->{missing} : '(unset)' );
106 1         5 printf " %-20s %s\n", 'feature_names', join( ', ', @$tags );
107 1 50       3 my $fd = ref $schema->{feature_descriptions} eq 'HASH' ? $schema->{feature_descriptions} : {};
108              
109 1         5 for my $i ( 0 .. $#$tags ) {
110             printf " [%d] %s%s\n", $i, $tags->[$i],
111 3 100       12 ( defined $fd->{ $tags->[$i] } ? ' -- ' . $fd->{ $tags->[$i] } : '' );
112             }
113 1         3 my $mungers = $schema->{mungers};
114 1 50 33     4 if ( ref $mungers eq 'HASH' && %$mungers ) {
115 0         0 printf " %-20s %s\n", 'mungers', scalar( keys %$mungers ) . ' configured';
116 0         0 for my $k ( sort keys %$mungers ) {
117             printf " %-18s %s\n", $k,
118 0 0 0     0 ( ref $mungers->{$k} eq 'HASH' && defined $mungers->{$k}{munger} ? $mungers->{$k}{munger} : '(?)' );
119             }
120             }
121 1 50       4 my $params = ref $proto->{params} eq 'HASH' ? $proto->{params} : {};
122 1         3 for my $k ( sort keys %$params ) {
123 4 50       11 printf " %-20s %s\n", $k, ( defined $params->{$k} ? $params->{$k} : '(unset)' );
124             }
125              
126 1         117 return 1;
127             } ## end sub execute
128              
129             return 1;