File Coverage

blib/lib/Conductrics/Agent.pm
Criterion Covered Total %
statement 36 109 33.0
branch 0 20 0.0
condition n/a
subroutine 12 18 66.6
pod 3 5 60.0
total 51 152 33.5


line stmt bran cond sub pod time code
1             package Conductrics::Agent;
2              
3 1     1   443 use strict;
  1         1  
  1         22  
4 1     1   2 use warnings;
  1         1  
  1         17  
5 1     1   371 use namespace::autoclean;
  1         12235  
  1         3  
6 1     1   525 use Moose;
  1         272980  
  1         5  
7 1     1   4823 use MooseX::Types::Moose qw( Str );
  1         32122  
  1         7  
8 1     1   3370 use MooseX::Types::URI qw(Uri);
  1         92595  
  1         5  
9 1     1   1089 use URI;
  1         1  
  1         16  
10 1     1   4 use URI::QueryParam;
  1         1  
  1         12  
11 1     1   507 use JSON::MaybeXS;
  1         843  
  1         44  
12 1     1   743 use Time::HiRes;
  1         1057  
  1         4  
13 1     1   637 use LWP::UserAgent;
  1         24776  
  1         24  
14 1     1   498 use Data::Dumper;
  1         4508  
  1         681  
15              
16             our $VERSION = '0.004';
17             $VERSION = eval $VERSION;
18              
19             sub build_uri {
20 0     0 0   my($self)=@_;
21 0           return URI->new($self->baseUrl);
22             }
23              
24             has 'apiKey' => (is=>'ro', isa=>Str, required=>1);
25             has 'ownerCode' => (is=>'ro', isa=>Str, required=>1);
26             has 'baseUrl' => (is=>'ro', isa=>Str, required=>1);
27             has 'baseUri' => (is=>'ro', isa=>Uri, lazy=>1, builder=>'build_uri');
28             has 'sessionId' => (is=>'rw', isa=>Str);
29             has 'name' => (is=>'rw', isa=>Str, required=>1);
30              
31             my $ua = LWP::UserAgent->new();
32             $ua->agent('Perl Conductrics::Agent');
33             $ua->timeout(2);
34             $ua->env_proxy;
35              
36             sub _request {
37 0     0     my ($self, $uri, @params) = @_;
38 0           my ($seconds, $microseconds) = Time::HiRes::gettimeofday;
39 0           my %parameters = (nocache=>"$seconds$microseconds", apikey=>$self->apiKey, session=>$self->sessionId, @params);
40 0           for my $k (keys %parameters) {
41 0           $uri->query_param_append($k, $parameters{$k});
42             }
43              
44 0           my $response = $ua->get($uri);
45 0 0         if ($response->is_success) {
46 0 0         if ($response->code != 200) {
47 0           warn "Content: ", $response->decoded_content; # or whatever
48 0           warn "Code: ", $response->code;
49 0           warn "Err:", $response->message;
50 0           warn "Something get wrong on response";
51 0           warn Dumper($response);
52             }
53              
54 0           JSON::MaybeXS::decode_json($response->decoded_content);
55             } else {
56 0           warn "Content: ", $response->decoded_content; # or whatever
57 0           warn "Code: ", $response->code;
58 0           warn "Err:", $response->message;
59 0           die $response->status_line;
60             }
61             }
62              
63             sub decide {
64 0     0 1   my ($self, $session, @choices) = @_;
65 0           my $uri = $self->baseUri->clone;
66 0           $self->sessionId($session);
67 0           my ($answer, $kind);
68 0 0         if ('ARRAY' eq ref $choices[0]) {
    0          
69             # Multi decisions request simple ([ qw/ red black green / ], [ qw/ verdana arial /] )
70 0           $uri->path_segments($self->ownerCode, $self->name, "decisions", map { join(',', @{$_}) } @choices );
  0            
  0            
71             } elsif ('HASH' eq ref $choices[0]) {
72             # Multi decision request with names ( {colour=>[qw/red black green/] }, { font=>[qw/ verdana arial /] })
73 0           $uri->path_segments($self->ownerCode, $self->name, "decisions", map { my ($k) = keys %{$_}; "$k:" . join (",", map {$_} @{$_->{$k}} ) } @choices );
  0            
  0            
  0            
  0            
  0            
74             } else {
75             # single decision
76 0           $kind = 'single';
77 0           $uri->path_segments($self->ownerCode, $self->name, "decision", join(',', @choices));
78             }
79             # handle multidecision answer
80 0           eval {
81 0           $answer = $self->_request(
82             $uri,
83             );
84             };
85 0 0         if ($@) {
86 0           die('Not able to get decision');
87             }
88 0 0         return $answer->{decision} if ( 'single' eq $kind );
89 0           return $answer;
90             }
91              
92              
93             sub get_decisions {
94 0     0 0   my ($self, $session, $point) = @_;
95 0           my $uri = $self->baseUri->clone;
96 0           $self->sessionId($session);
97 0           $uri->path_segments($self->ownerCode, $self->name, "decisions" );
98             # handle multidecision answer
99 0           my $answer;
100 0           eval {
101 0           $answer = $self->_request(
102             $uri,
103             point=>$point,
104             );
105             };
106 0 0         if ($@) {
107 0           die('Not able to get decision');
108             }
109 0           return $answer;
110             }
111              
112              
113              
114             sub reward {
115 0     0 1   my ($self, $session, $goalCode, $value) = @_;
116 0 0         $value = 1 unless (defined $value);
117 0           my $uri = $self->baseUri->clone;
118 0           $uri->path_segments($self->ownerCode, $self->name, 'goal', $goalCode);
119 0           $self->sessionId($session);
120 0           my $answer;
121 0           eval {
122 0           $answer = $self->_request(
123             $uri,
124             reward=>$value,
125             );
126             };
127 0 0         if ($@) {
128 0           die("Not able to set reward");
129             }
130 0           return $answer;
131             }
132              
133             sub expire {
134 0     0 1   my ($self, $session) = @_;
135 0           my $uri = $self->baseUri->clone;
136 0           $uri->path_segments($self->ownerCode, $self->name, "expire");
137 0           $self->sessionId($session);
138 0           my $answer;
139 0           eval {
140 0           $answer = $self->_request($uri);
141             };
142 0 0         if ($@) {
143 0           die("Not able to expire");
144             }
145 0           return $answer;
146             }
147              
148             1;
149              
150             =encoding utf-8
151              
152             =head1 NAME
153              
154             Conductrics Agent
155              
156             =head1 DESCRIPTION
157              
158             First I've got php agent API from conductrics github (https://github.com/conductrics/conductrics-php)
159             and I've rewritten it in Modern Perl, then I've improved it.
160              
161             I've substituted rand() calls with less cpu expensive Time::Hires to unvalidate cache.
162              
163             I'll use this module for a new Catalyst model.
164              
165              
166             =head1 SYNOPSIS
167              
168             use Conductrics::Agent;
169              
170             my $agent = Conductrics::Agent->new(
171             name=>'', # your conductrics agent
172             apiKey=>'', # place your apikey here
173             ownerCode=>'', # place your ownerCode here
174             baseUrl=>'http://api.conductrics.com',
175             );
176              
177             #
178             # $agent will ask for a decision the conductrics server about which colour
179             #
180             my $choice = $agent->decide($userSessionid, qw/red jellow green blue/);
181             print "$choice\n";
182              
183              
184             =head1 METHODS
185              
186              
187             =head2 decide()
188              
189             Whenever in your code you want to act using decision evaluated by Conductrics you just call decide in
190             a proper form, simple, multiple with names or nameless.
191              
192             =head2 decide($sessionId, @choices)
193              
194             Conductrics will compute the decision and this returns which $choice.
195              
196             =head2 decide($sessionId, {decisionN1=>[ qw/option1 option2 option3/ ]}, {decisionN2=>[ qw/anotherOpt oneMoreOpt / ]})
197              
198             =head2 decide($sessionId, [ qw/option1 option2 option3/ ], [ qw/anotherOpt oneMoreOpt / ] )
199              
200             decisionN1 is only a placeholder for name you have choose for this decision point as well as decisionN2 is another name you like.
201              
202             Here is how to use Multi-Faceted Decisions, with or without name: you are asking at the server more than one
203             Here some words from the conductrics help:
204             "Whenver you ask us for a decision, we'll pick one option from each list, and send them back to you in one answer."
205             "We're basically doing multivariate testing ("MVT") for you, tracking the success of combinations of options rather than each option individually."
206              
207              
208             =head2 get_decision($session, $pointCode)
209              
210             If you have defined more decision points for your agent, you can get decisions from Conductrics
211             using 'point code'.
212             While decide() needs more information, with this call you have already provided those information to the server
213             during agent's definition.
214              
215             To define agents see Conductrics::API::Client.
216              
217              
218             =head2 reward($sessionId, $goalCode, [$value])
219              
220             Conductrics will collect the numeric value about the goalCode. This is the way it learn whick decisions are winning.
221              
222             =head2 expire($sessionId)
223              
224             You are notifing that this session has been closed, for example on user logout action.
225              
226             http://www.conductrics.com/ for more info about their analysis service.
227              
228              
229             =head1 TESTS
230              
231              
232             =head2 Execute full test suite
233              
234             First you have to create two test agents following these description:
235              
236             {
237             "code": "test-agent",
238             "owner": "$your_ownerCode",
239              
240             "goals": [
241             {"code": "goal-1"}
242             ],
243              
244             "points": [
245             {
246             "code": "point-1",
247             "decisions": [
248             {
249             "code": "colori",
250             "choices": [
251             {"code": "rosso"},
252             {"code": "giallo"}
253             ]
254             }
255             ]
256             }
257             ]
258             }
259              
260             and
261              
262             {
263             "code": "mvt-agent",
264             "owner": "$your_ownerCode",
265              
266             "goals": [
267             {"code": "goal-2"}
268             ],
269              
270             "points": [
271             {
272             "code": "point-2",
273             "decisions": [
274             {
275             "code": "colour",
276             "choices": [
277             {"code": "red"},
278             {"code": "black"},
279             {"code": "green"}
280             ]
281             },
282             {
283             "code": "font",
284             "choices": [
285             {"code": "Helvetica"},
286             {"code": "Times"}
287             ]
288             }
289             ]
290             }
291             ]
292             }
293              
294              
295             You have to set some env to execute t/02-real_test.t
296             You will find your data into Account/Keys and Users page.
297              
298             Required env for execute full test's suite:
299              
300             Conductrics_apikey
301             Conductrics_ownerCode
302             Conductrics_agent_name=test-agent
303             Conductrics_mvt_agent_name=mvt-agent
304              
305             Test's sources are good examples about how to use this API, so "Use The Source Luke".
306              
307              
308             =head1 MORE INFO
309              
310             Conductrics has many help pages available from the console, so signup and read it.
311              
312             http://conductrics.com/
313              
314             There are also Report API, Management API and Targetting Rule API.
315              
316             =head2 ToDo
317              
318             I wuold like to return promises for handling non blocking request to conductrics server.
319              
320              
321             =head1 AUTHORS
322              
323             Ferruccio Zamuner - nonsolosoft@diff.org
324              
325             =head1 COPYRIGHT
326              
327             This library is free software. You can redistribute it and/or modify
328             it under the same terms as Perl itself.
329              
330              
331             =cut
332              
333