line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package WWW::Analytics::MultiTouch; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
83312
|
use warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
109
|
|
4
|
2
|
|
|
2
|
|
13
|
use strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
71
|
|
5
|
2
|
|
|
2
|
|
1942
|
use Net::Google::Analytics; |
|
2
|
|
|
|
|
276150
|
|
|
2
|
|
|
|
|
69
|
|
6
|
2
|
|
|
2
|
|
2401
|
use Net::Google::Analytics::OAuth2; |
|
2
|
|
|
|
|
2010
|
|
|
2
|
|
|
|
|
63
|
|
7
|
2
|
|
|
2
|
|
3556
|
use DateTime; |
|
2
|
|
|
|
|
505540
|
|
|
2
|
|
|
|
|
98
|
|
8
|
2
|
|
|
2
|
|
2532
|
use Data::Dumper; |
|
2
|
|
|
|
|
19241
|
|
|
2
|
|
|
|
|
232
|
|
9
|
2
|
|
|
2
|
|
22
|
use Params::Validate qw(:all); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
675
|
|
10
|
2
|
|
|
2
|
|
20
|
use List::Util qw/sum max/; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
215
|
|
11
|
2
|
|
|
2
|
|
12
|
use List::MoreUtils qw/part/; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
174
|
|
12
|
2
|
|
|
2
|
|
2580
|
use Config::General qw/ParseConfig SaveConfig/; |
|
2
|
|
|
|
|
72282
|
|
|
2
|
|
|
|
|
229
|
|
13
|
2
|
|
|
2
|
|
2217
|
use Hash::Merge qw/merge/; |
|
2
|
|
|
|
|
6203
|
|
|
2
|
|
|
|
|
136
|
|
14
|
2
|
|
|
2
|
|
1767
|
use Path::Class qw/file/; |
|
2
|
|
|
|
|
101779
|
|
|
2
|
|
|
|
|
162
|
|
15
|
|
|
|
|
|
|
|
16
|
2
|
|
|
2
|
|
1744
|
use WWW::Analytics::MultiTouch::Tabular; |
|
2
|
|
|
|
|
10
|
|
|
2
|
|
|
|
|
20225
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
our $VERSION = '0.36'; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
my $client_id = "452786331228.apps.googleusercontent.com"; |
21
|
|
|
|
|
|
|
my $client_secret = "ZNSff9Rzw0WS0I4M-F_8NUL7"; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
my $default_header_colour = { bold => 1, |
24
|
|
|
|
|
|
|
color => 'white', |
25
|
|
|
|
|
|
|
bg_color => 'gray', |
26
|
|
|
|
|
|
|
right => 'white', |
27
|
|
|
|
|
|
|
}; |
28
|
|
|
|
|
|
|
my $default_column_formats = [ |
29
|
|
|
|
|
|
|
{ bg_color => '#D0D0D0', }, |
30
|
|
|
|
|
|
|
{ bg_color => '#E8E8E8', }, |
31
|
|
|
|
|
|
|
]; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $default_title_format = { bold => 1 }; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
my $default_row__heading = { bold => 1 }; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my %formatting_params = ( |
38
|
|
|
|
|
|
|
title_format => { type => HASHREF, |
39
|
|
|
|
|
|
|
default => $default_title_format, |
40
|
|
|
|
|
|
|
}, |
41
|
|
|
|
|
|
|
column_heading_format => { type => HASHREF, |
42
|
|
|
|
|
|
|
default => $default_header_colour, |
43
|
|
|
|
|
|
|
}, |
44
|
|
|
|
|
|
|
column_formats => { type => ARRAYREF, |
45
|
|
|
|
|
|
|
default => $default_column_formats, |
46
|
|
|
|
|
|
|
}, |
47
|
|
|
|
|
|
|
row_heading_format => { type => HASHREF, |
48
|
|
|
|
|
|
|
default => $default_row__heading, |
49
|
|
|
|
|
|
|
}, |
50
|
|
|
|
|
|
|
heading_map => { type => HASHREF, |
51
|
|
|
|
|
|
|
default => {}, |
52
|
|
|
|
|
|
|
}, |
53
|
|
|
|
|
|
|
header_layout => 0, |
54
|
|
|
|
|
|
|
footer_layout => 0, |
55
|
|
|
|
|
|
|
strict_integer_values => 0, |
56
|
|
|
|
|
|
|
); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub new { |
59
|
5
|
|
|
5
|
1
|
123895
|
my $class = shift; |
60
|
|
|
|
|
|
|
|
61
|
5
|
|
|
|
|
366
|
my %params = validate(@_, { |
62
|
|
|
|
|
|
|
auth_token => 0, |
63
|
|
|
|
|
|
|
refresh_token => 0, |
64
|
|
|
|
|
|
|
auth_file => 0, |
65
|
|
|
|
|
|
|
id => 1, |
66
|
|
|
|
|
|
|
event_category => { default => 'multitouch' }, |
67
|
|
|
|
|
|
|
fieldsep => { default => '!' }, |
68
|
|
|
|
|
|
|
recsep => { default => '*' }, |
69
|
|
|
|
|
|
|
patsep => { default => '-' }, |
70
|
|
|
|
|
|
|
debug => { default => 0 }, |
71
|
|
|
|
|
|
|
bugfix1 => { default => 0 }, |
72
|
|
|
|
|
|
|
channel_map => { type => HASHREF, |
73
|
|
|
|
|
|
|
default => {} }, |
74
|
|
|
|
|
|
|
date_format => { default => '%d %b %Y' }, |
75
|
|
|
|
|
|
|
time_format => { default => '%Y-%m-%d %H:%M:%S' }, |
76
|
|
|
|
|
|
|
ga_timezone => { default => 'UTC' }, |
77
|
|
|
|
|
|
|
report_timezone => { default => 'UTC' }, |
78
|
|
|
|
|
|
|
revenue_scale => { default => 1 }, |
79
|
|
|
|
|
|
|
}); |
80
|
5
|
|
33
|
|
|
113
|
my $self = bless \%params, ref $class || $class; |
81
|
|
|
|
|
|
|
|
82
|
5
|
|
|
|
|
24
|
return $self; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub get_data { |
86
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
87
|
0
|
|
|
|
|
0
|
my %params = validate(@_, { start_date => 0, |
88
|
|
|
|
|
|
|
end_date => 0, |
89
|
|
|
|
|
|
|
}); |
90
|
|
|
|
|
|
|
|
91
|
0
|
0
|
|
|
|
0
|
unless (exists $self->{analytics}) { |
92
|
0
|
|
|
|
|
0
|
$self->{oauth} = Net::Google::Analytics::OAuth2->new( |
93
|
|
|
|
|
|
|
client_id => $client_id, |
94
|
|
|
|
|
|
|
client_secret => $client_secret, |
95
|
|
|
|
|
|
|
); |
96
|
0
|
0
|
|
|
|
0
|
if (! $self->{refresh_token}) { |
97
|
0
|
|
|
|
|
0
|
$self->authorise; |
98
|
|
|
|
|
|
|
} |
99
|
0
|
|
|
|
|
0
|
$self->{analytics} = Net::Google::Analytics->new(); |
100
|
|
|
|
|
|
|
} |
101
|
0
|
|
|
|
|
0
|
my $token = $self->{oauth}->refresh_access_token($self->{refresh_token}); |
102
|
0
|
|
|
|
|
0
|
$self->{analytics}->token($token); |
103
|
|
|
|
|
|
|
|
104
|
0
|
|
|
|
|
0
|
my $req = $self->{analytics}->new_request(); |
105
|
0
|
|
|
|
|
0
|
$req->ids("ga:" . $self->{id}); |
106
|
0
|
|
|
|
|
0
|
$req->dimensions('ga:eventCategory,ga:eventAction,ga:eventLabel'); |
107
|
0
|
|
|
|
|
0
|
$req->metrics('ga:totalEvents'); |
108
|
0
|
|
|
|
|
0
|
$req->sort('ga:eventAction'); |
109
|
0
|
|
|
|
|
0
|
$req->filters('ga:eventCategory==' . $self->{event_category}); |
110
|
|
|
|
|
|
|
|
111
|
0
|
|
|
|
|
0
|
my $start_date = _to_date_time($params{start_date}, $self->{report_timezone}); |
112
|
0
|
|
|
|
|
0
|
my $end_date = _to_date_time($params{end_date}, $self->{report_timezone})->add(days => 1); |
113
|
0
|
|
|
|
|
0
|
my $date = $start_date->clone; |
114
|
0
|
|
|
|
|
0
|
my %data; |
115
|
0
|
|
|
|
|
0
|
while (DateTime->compare($date, $end_date) <= 0) { |
116
|
0
|
|
|
|
|
0
|
my $ymd = $date->ymd('-'); |
117
|
0
|
|
|
|
|
0
|
my $ga_ymd = $date->clone->set_time_zone($self->{ga_timezone})->ymd('-'); |
118
|
0
|
|
|
|
|
0
|
$self->_debug("Processing $ga_ymd\n"); |
119
|
0
|
|
|
|
|
0
|
$req->start_date($ga_ymd); |
120
|
0
|
|
|
|
|
0
|
$req->end_date($ga_ymd); |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
0
|
my $res = $self->retrieve_paged($req); |
123
|
0
|
|
|
|
|
0
|
for my $entry (@{$res->rows}) { |
|
0
|
|
|
|
|
0
|
|
124
|
0
|
|
|
|
|
0
|
my @events = $self->split_events($entry->get('event_label')); |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Keep event if within reporting time range (not GA time range) |
127
|
0
|
|
|
|
|
0
|
my $t = DateTime->from_epoch(epoch => $events[0][3])->set_time_zone($self->{report_timezone}); |
128
|
0
|
0
|
0
|
|
|
0
|
if ($start_date <= $t && $t < $end_date) { |
129
|
0
|
|
|
|
|
0
|
my ($key, $events) = $self->condition_entry($entry->get('event_action'), \@events); |
130
|
0
|
0
|
|
|
|
0
|
$data{$key} = [ $ymd, @$events ] if $key; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
} |
133
|
0
|
|
|
|
|
0
|
$date->add(days => 1); |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
0
|
|
|
|
|
0
|
$self->{current_data} = { start_date => $start_date, |
137
|
|
|
|
|
|
|
end_date => $end_date->subtract(days => 1), |
138
|
|
|
|
|
|
|
transactions => \%data, |
139
|
|
|
|
|
|
|
}; |
140
|
0
|
|
|
0
|
|
0
|
$self->_debug(sub { Dumper($self->{current_data}) }); |
|
0
|
|
|
|
|
0
|
|
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub retrieve_paged { |
144
|
0
|
|
|
0
|
0
|
0
|
my ($self, $req) = @_; |
145
|
|
|
|
|
|
|
|
146
|
0
|
|
|
|
|
0
|
my $start_index = $req->start_index; |
147
|
0
|
0
|
|
|
|
0
|
$start_index = 1 if !defined($start_index); |
148
|
0
|
|
|
|
|
0
|
my $remaining_items = $req->max_results; |
149
|
0
|
|
|
|
|
0
|
my $max_items_per_page = 10_000; |
150
|
0
|
|
|
|
|
0
|
my $res; |
151
|
|
|
|
|
|
|
|
152
|
0
|
|
0
|
|
|
0
|
while (!defined($remaining_items) || $remaining_items > 0) { |
153
|
0
|
0
|
0
|
|
|
0
|
my $max_results = |
154
|
|
|
|
|
|
|
defined($remaining_items) && |
155
|
|
|
|
|
|
|
$remaining_items < $max_items_per_page ? |
156
|
|
|
|
|
|
|
$remaining_items : $max_items_per_page; |
157
|
|
|
|
|
|
|
|
158
|
0
|
|
|
|
|
0
|
my $page = $self->{analytics}->retrieve($req, $start_index, $max_results); |
159
|
|
|
|
|
|
|
# $self->_debug("Page data: " . Dumper($page)); |
160
|
0
|
0
|
|
|
|
0
|
if (! $page->is_success) { |
161
|
0
|
|
|
|
|
0
|
die "There was a problem fetching analytics data. Authorisation errors such as 'Forbidden' can occur if the Analytics ID you have specified is not accessible via the authorised Google account.\n Error reported was: " . $page->message; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
0
|
0
|
|
|
|
0
|
if (!defined($res)) { |
165
|
0
|
|
|
|
|
0
|
$res = $page; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
else { |
168
|
0
|
|
|
|
|
0
|
push(@{ $res->rows }, @{ $page->rows }); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
0
|
|
|
|
|
0
|
my $items_per_page = $page->items_per_page; |
172
|
0
|
0
|
0
|
|
|
0
|
last if $page->total_results == 0 || $items_per_page < $max_results; |
173
|
|
|
|
|
|
|
|
174
|
0
|
0
|
|
|
|
0
|
$remaining_items -= $items_per_page if defined($remaining_items); |
175
|
0
|
|
|
|
|
0
|
$start_index += $items_per_page; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
0
|
|
|
|
|
0
|
$res->items_per_page(scalar(@{ $res->rows })); |
|
0
|
|
|
|
|
0
|
|
179
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
0
|
return $res; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub set_data { |
185
|
1
|
|
|
1
|
1
|
1390
|
my $self = shift; |
186
|
1
|
|
|
|
|
27
|
my %params = validate(@_, { start_date => 0, |
187
|
|
|
|
|
|
|
end_date => 0, |
188
|
|
|
|
|
|
|
transactions => { required => 1, |
189
|
|
|
|
|
|
|
type => HASHREF, |
190
|
|
|
|
|
|
|
}, |
191
|
|
|
|
|
|
|
}); |
192
|
|
|
|
|
|
|
|
193
|
1
|
|
|
|
|
22
|
my $start_date = _to_date_time($params{start_date}, $self->{report_timezone}); |
194
|
1
|
|
|
|
|
211
|
my $end_date = _to_date_time($params{end_date}, $self->{report_timezone}); |
195
|
|
|
|
|
|
|
|
196
|
1
|
|
|
|
|
162
|
$self->{current_data} = { start_date => $start_date, |
197
|
|
|
|
|
|
|
end_date => $end_date, |
198
|
|
|
|
|
|
|
transactions => $params{transactions}, |
199
|
|
|
|
|
|
|
}; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub condition_entry { |
203
|
0
|
|
|
0
|
1
|
0
|
my ($self, $key, $touches) = @_; |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
0
|
return ($key, $touches); |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub _to_date_time { |
210
|
2
|
|
|
2
|
|
4
|
my $date = shift; |
211
|
2
|
|
50
|
|
|
7
|
my $tz = shift || 'UTC'; |
212
|
|
|
|
|
|
|
|
213
|
2
|
50
|
|
|
|
10
|
if ($date) { |
214
|
2
|
|
|
|
|
75
|
my ($y, $m, $d) = ( $date =~ m/^(\d{4})-?(\d{2})-?(\d{2})/ ); |
215
|
2
|
50
|
|
|
|
38
|
die "Invalid date format: $date\n" if ! defined $d; |
216
|
2
|
|
|
|
|
152
|
return DateTime->new(year => $y, month => $m, day => $d, time_zone => $tz); |
217
|
|
|
|
|
|
|
} |
218
|
0
|
|
|
|
|
0
|
return DateTime->now->set_time_zone($tz)->truncate(to => 'day'); |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# Splits event label into array of [ source, medium, subcat, time ] |
222
|
|
|
|
|
|
|
# or for orders, [ __ORD, TID, revenue, time ] |
223
|
|
|
|
|
|
|
sub split_events { |
224
|
25
|
|
|
25
|
1
|
119
|
my ($self, $events) = @_; |
225
|
|
|
|
|
|
|
|
226
|
25
|
50
|
|
|
|
55
|
return unless $events; |
227
|
25
|
|
|
|
|
53
|
my $rs = $self->{recsep}; |
228
|
25
|
|
|
|
|
35
|
my $fs = $self->{fieldsep}; |
229
|
25
|
|
|
|
|
115
|
my @events = split(/\Q$rs\E/, $events); |
230
|
25
|
|
|
|
|
44
|
my @rec = map { [ split(/\Q$fs\E/, $_) ] } @events; |
|
75
|
|
|
|
|
342
|
|
231
|
|
|
|
|
|
|
|
232
|
25
|
50
|
|
|
|
74
|
if ($self->{bugfix1}) { |
233
|
0
|
|
|
|
|
0
|
for (@rec) { |
234
|
0
|
0
|
0
|
|
|
0
|
if ($_->[0] eq 'organic' && $_->[1] ne 'organic') { |
235
|
0
|
|
|
|
|
0
|
my $tmp = $_->[1]; $_->[1] = $_->[0]; $_->[0] = $tmp; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
} |
239
|
25
|
|
|
|
|
115
|
return @rec; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
sub summarise { |
243
|
5
|
|
|
5
|
1
|
6227
|
my $self = shift; |
244
|
|
|
|
|
|
|
|
245
|
5
|
|
|
|
|
133
|
my %params = validate(@_, { window_length => { default => 45 }, |
246
|
|
|
|
|
|
|
single_order_model => 0, |
247
|
|
|
|
|
|
|
channel_pattern => { default => join($self->{patsep}, qw/source med subcat/) }, |
248
|
|
|
|
|
|
|
channel => 0, |
249
|
|
|
|
|
|
|
adjustments => { type => HASHREF, default => {} }, |
250
|
|
|
|
|
|
|
}); |
251
|
5
|
|
|
|
|
49
|
my $patsubst = $self->_compile_channel_pattern($params{channel_pattern}); |
252
|
5
|
|
|
|
|
15
|
my $dt = $params{window_length} * 24 * 3600; |
253
|
|
|
|
|
|
|
|
254
|
5
|
|
|
|
|
9
|
my %distr_touches; |
255
|
|
|
|
|
|
|
my %even_touches; |
256
|
0
|
|
|
|
|
0
|
my %all_touches; |
257
|
0
|
|
|
|
|
0
|
my @trans; |
258
|
0
|
|
|
|
|
0
|
my @touchlist; |
259
|
0
|
|
|
|
|
0
|
my %transdist; |
260
|
0
|
|
|
|
|
0
|
my %transdistoverall; |
261
|
0
|
|
|
|
|
0
|
my %firstlast; |
262
|
0
|
|
|
|
|
0
|
my %overlap; |
263
|
5
|
|
|
|
|
10
|
my %first_touch_channels = map { $_ => 1 } grep { $params{channel}{$_}{requires_first_touch} } keys %{$params{channel}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
5
|
|
|
|
|
20
|
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# Each event has category 'multitouch', action TIDtid, label ORDER*TOUCH*TOUCH... |
266
|
|
|
|
|
|
|
# Each order is of format __ORD!tid!rev!time |
267
|
|
|
|
|
|
|
# Each touch is of format source!medium!subcat!time |
268
|
5
|
|
|
|
|
11
|
for my $tid (keys %{$self->{current_data}->{transactions}}) { |
|
5
|
|
|
|
|
34
|
|
269
|
25
|
|
|
|
|
53
|
my $rec = $self->{current_data}->{transactions}->{$tid}; |
270
|
25
|
|
|
|
|
35
|
my $order = $rec->[1]; |
271
|
25
|
50
|
33
|
|
|
256
|
if (! ($order->[0] eq '__ORD' |
|
|
|
33
|
|
|
|
|
272
|
|
|
|
|
|
|
&& 'TID' . $order->[1] eq $tid |
273
|
|
|
|
|
|
|
&& $order->[3] =~ m/^\d+$/)) { |
274
|
0
|
|
|
|
|
0
|
$self->_debug("Bad record for TID $tid: no __ORD. " . Dumper($rec)); |
275
|
0
|
|
|
|
|
0
|
next; |
276
|
|
|
|
|
|
|
} |
277
|
25
|
|
|
|
|
69
|
my $rev = $self->_currency_conversion($order->[2]); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Set window start based on browser timestamps |
280
|
25
|
|
|
|
|
51
|
my $window_start = $order->[3] - $dt; |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# work out adjustment factors, if any |
283
|
25
|
|
100
|
|
|
109
|
my $trans_adj = $params{adjustments}{$rec->[0]}{transactions} || 1; |
284
|
25
|
|
66
|
|
|
99
|
my $rev_adj = $params{adjustments}{$rec->[0]}{revenue} || $trans_adj; |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# Iterate through list of touches and summarise in %touches and @touchlist |
287
|
25
|
|
|
|
|
30
|
my %touches; |
288
|
25
|
|
|
|
|
41
|
push(@touchlist, []); |
289
|
25
|
|
|
|
|
29
|
my $first_touch = 1; |
290
|
25
|
|
|
|
|
24
|
my %seen_first_touch; |
291
|
25
|
|
|
|
|
70
|
for my $entry (@$rec[2 .. @$rec - 1]) { |
292
|
49
|
50
|
|
|
|
113
|
if (@$entry != 4) { |
293
|
0
|
|
|
|
|
0
|
$self->_debug("Bad record for TID $tid: invalid entry. " . Dumper($entry)); |
294
|
0
|
|
|
|
|
0
|
next; |
295
|
|
|
|
|
|
|
} |
296
|
49
|
100
|
|
|
|
127
|
last if $entry->[3] < $window_start; |
297
|
46
|
100
|
|
|
|
117
|
if ($entry->[0] =~ m/__ORD/) { |
298
|
5
|
100
|
|
|
|
13
|
last if $params{single_order_model}; |
299
|
4
|
|
|
|
|
6
|
unshift(@{$touchlist[-1]}, [ "ORDER($entry->[1])", $entry->[-1] ]); |
|
4
|
|
|
|
|
16
|
|
300
|
4
|
|
|
|
|
8
|
next; |
301
|
|
|
|
|
|
|
} |
302
|
41
|
100
|
|
|
|
86
|
if (my $channel = $self->_map_channel(join($self->{patsep}, map { $entry->[$_] || '(none)' } @$patsubst ))) { |
|
123
|
50
|
|
|
|
408
|
|
303
|
41
|
100
|
|
|
|
78
|
if ($first_touch) { |
304
|
25
|
|
|
|
|
31
|
$first_touch = 0; |
305
|
25
|
|
|
|
|
46
|
$seen_first_touch{$channel}++; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
41
|
50
|
33
|
|
|
106
|
unless ($first_touch_channels{$channel} && !$seen_first_touch{$channel}) { |
309
|
41
|
|
|
|
|
96
|
$touches{$channel}{count} += $trans_adj; |
310
|
41
|
|
|
|
|
67
|
$touches{$channel}{transactions} = $trans_adj; |
311
|
41
|
|
|
|
|
70
|
$touches{$channel}{revenue} = $rev * $rev_adj; |
312
|
41
|
|
|
|
|
40
|
unshift(@{$touchlist[-1]}, [ $channel, $entry->[-1] ]); |
|
41
|
|
|
|
|
186
|
|
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# Summarise first/last touch attribution using @touchlist |
318
|
25
|
50
|
|
|
|
31
|
if (@{$touchlist[-1]} > 0) { |
|
25
|
|
|
|
|
61
|
|
319
|
25
|
|
|
|
|
28
|
my $start = 0; |
320
|
25
|
|
|
|
|
24
|
my $end = @{$touchlist[-1]} - 1; |
|
25
|
|
|
|
|
40
|
|
321
|
25
|
|
|
|
|
38
|
my $firstchannel = $touchlist[-1][$start][0]; |
322
|
25
|
|
|
|
|
79
|
my $lastchannel = $touchlist[-1][$end][0]; |
323
|
|
|
|
|
|
|
|
324
|
25
|
|
66
|
|
|
119
|
while (defined($firstchannel) && $firstchannel =~ m/^ORDER\(/) { |
325
|
1
|
|
|
|
|
2
|
$start++; |
326
|
1
|
50
|
|
|
|
4
|
if ($start > $end) { |
327
|
0
|
|
|
|
|
0
|
$firstchannel = undef; |
328
|
0
|
|
|
|
|
0
|
last; |
329
|
|
|
|
|
|
|
} |
330
|
1
|
|
|
|
|
6
|
$firstchannel = $touchlist[-1][$start][0]; |
331
|
|
|
|
|
|
|
} |
332
|
25
|
|
33
|
|
|
315
|
while (defined($lastchannel) && $lastchannel =~ m/^ORDER\(/) { |
333
|
0
|
|
|
|
|
0
|
$end--; |
334
|
0
|
0
|
|
|
|
0
|
if ($end <= $start) { |
335
|
0
|
|
|
|
|
0
|
$lastchannel = $firstchannel; |
336
|
0
|
|
|
|
|
0
|
last; |
337
|
|
|
|
|
|
|
} |
338
|
0
|
|
|
|
|
0
|
$lastchannel = $touchlist[-1][$end][0]; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
25
|
50
|
|
|
|
50
|
if (defined $firstchannel) { |
342
|
25
|
|
|
|
|
64
|
$firstlast{'first'}{$firstchannel}{count} += $trans_adj; |
343
|
25
|
|
|
|
|
40
|
$firstlast{'first'}{$firstchannel}{transactions} += $trans_adj; |
344
|
25
|
|
|
|
|
43
|
$firstlast{'first'}{$firstchannel}{revenue} += $rev * $rev_adj; |
345
|
25
|
|
|
|
|
45
|
$firstlast{'last'}{$lastchannel}{count} += $trans_adj; |
346
|
25
|
|
|
|
|
36
|
$firstlast{'last'}{$lastchannel}{transactions} += $trans_adj; |
347
|
25
|
|
|
|
|
44
|
$firstlast{'last'}{$lastchannel}{revenue} += $rev * $rev_adj; |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
# for hybrid, only count once if first/last channel are the same touch |
351
|
25
|
100
|
|
|
|
46
|
if ($end == $start) { |
352
|
9
|
|
|
|
|
24
|
$lastchannel = undef; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
# Attribute to first or last channel or both for hybrid |
355
|
25
|
|
|
|
|
27
|
my @channels; |
356
|
25
|
50
|
|
|
|
52
|
push(@channels, $firstchannel) if defined $firstchannel; |
357
|
25
|
100
|
|
|
|
50
|
push(@channels, $lastchannel) if defined $lastchannel; |
358
|
25
|
50
|
|
|
|
50
|
if (@channels) { |
359
|
25
|
|
|
|
|
43
|
my $scale = 1/@channels; |
360
|
25
|
|
|
|
|
35
|
for my $channel (@channels) { |
361
|
41
|
|
|
|
|
82
|
$firstlast{hybrid}{$channel}{count} += $trans_adj; |
362
|
41
|
|
|
|
|
71
|
$firstlast{hybrid}{$channel}{transactions} += $scale * $trans_adj; |
363
|
41
|
|
|
|
|
118
|
$firstlast{hybrid}{$channel}{revenue} += $scale * $rev * $rev_adj; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
# Finish off touchlist by attaching order details as prefix and last touch |
368
|
25
|
|
|
|
|
95
|
push(@{$touchlist[-1]}, [ "ORDER($order->[1])", $order->[3] ]); |
|
25
|
|
|
|
|
82
|
|
369
|
25
|
|
|
|
|
31
|
unshift(@{$touchlist[-1]}, $order->[1], $order->[2], $order->[3]); # order details prefix |
|
25
|
|
|
|
|
78
|
|
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
# Summarise according to various attribution methods using %touches |
372
|
25
|
50
|
|
|
|
55
|
if (scalar keys %touches > 0) { |
373
|
25
|
|
|
|
|
41
|
for my $sum (qw/count transactions revenue/) { |
374
|
75
|
|
|
|
|
323
|
$all_touches{$_}{$sum} += $touches{$_}{$sum} for keys %touches; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
# normalise |
377
|
25
|
|
|
|
|
28
|
my %touches_norm; |
378
|
25
|
|
|
|
|
73
|
my $touches_total = sum(map { $touches{$_}{count} } keys %touches); |
|
37
|
|
|
|
|
117
|
|
379
|
25
|
|
|
|
|
42
|
for my $sum (qw/transactions revenue/) { |
380
|
50
|
|
50
|
|
|
740
|
$touches_norm{$_}{$sum} = $touches{$_}{$sum} * $touches{$_}{count} / ($touches_total || 1) for keys %touches; |
381
|
|
|
|
|
|
|
} |
382
|
25
|
|
|
|
|
49
|
for (keys %touches) { |
383
|
37
|
|
|
|
|
58
|
my $c = $touches{$_}{count}; |
384
|
37
|
|
|
|
|
52
|
$touches_norm{$_}{count} += $c; |
385
|
37
|
|
|
|
|
90
|
$distr_touches{$_}{count} += $c; |
386
|
37
|
|
|
|
|
75
|
$even_touches{$_}{count} += $c; |
387
|
|
|
|
|
|
|
} |
388
|
25
|
|
|
|
|
45
|
my $scale = 1 / (scalar keys %touches); |
389
|
25
|
|
|
|
|
34
|
for my $sum (qw/transactions revenue/) { |
390
|
50
|
|
|
|
|
198
|
$distr_touches{$_}{$sum} += $touches_norm{$_}{$sum} for keys %touches; |
391
|
50
|
|
|
|
|
220
|
$even_touches{$_}{$sum} += $touches{$_}{$sum} * $scale for keys %touches; |
392
|
|
|
|
|
|
|
} |
393
|
25
|
|
|
|
|
144
|
push(@trans, { tid => $order->[1], |
394
|
|
|
|
|
|
|
timestamp => $order->[3], |
395
|
|
|
|
|
|
|
date => $rec->[0], |
396
|
|
|
|
|
|
|
rev => $order->[2], |
397
|
|
|
|
|
|
|
touches => \%touches_norm }); |
398
|
|
|
|
|
|
|
# distribution of touches by number of conversions |
399
|
25
|
|
|
|
|
127
|
$transdist{$touches{$_}{count}}{$_} += $trans_adj for keys %touches; |
400
|
25
|
|
|
|
|
50
|
$transdistoverall{sum(map { $touches{$_}{count}} keys %touches)} += $trans_adj; |
|
37
|
|
|
|
|
205
|
|
401
|
|
|
|
|
|
|
|
402
|
25
|
|
|
|
|
88
|
my $key = join('+', sort keys %touches); |
403
|
25
|
|
|
|
|
76
|
$overlap{joint}{$key}{transactions} += $trans_adj; |
404
|
25
|
|
|
|
|
50
|
$overlap{joint}{$key}{revenue} += $rev * $rev_adj; |
405
|
25
|
|
|
|
|
36
|
$overlap{joint}{$key}{touches} += $touches_total; |
406
|
25
|
|
|
|
|
29
|
$key = scalar keys %touches; |
407
|
25
|
|
|
|
|
52
|
$overlap{count}{$key}{transactions} += $trans_adj; |
408
|
25
|
|
|
|
|
34
|
$overlap{count}{$key}{revenue} += $rev * $rev_adj; |
409
|
25
|
|
|
|
|
131
|
$overlap{count}{$key}{touches} += $touches_total; |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
} |
413
|
5
|
|
|
|
|
30
|
@touchlist = sort { $a->[0] cmp $b->[0] } @touchlist; |
|
38
|
|
|
|
|
52
|
|
414
|
5
|
|
|
|
|
87
|
$self->{summary} = { |
415
|
|
|
|
|
|
|
all_touches => \%all_touches, |
416
|
|
|
|
|
|
|
distr_touches => \%distr_touches, |
417
|
|
|
|
|
|
|
even_touches => \%even_touches, |
418
|
|
|
|
|
|
|
trans => \@trans, |
419
|
|
|
|
|
|
|
touchlist => \@touchlist, |
420
|
|
|
|
|
|
|
transdist => \%transdist, |
421
|
|
|
|
|
|
|
transdistoverall => \%transdistoverall, |
422
|
|
|
|
|
|
|
firstlast => \%firstlast, |
423
|
|
|
|
|
|
|
overlap => \%overlap, |
424
|
|
|
|
|
|
|
window_length => $params{window_length}, |
425
|
|
|
|
|
|
|
}; |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
sub _map_channel { |
430
|
41
|
|
|
41
|
|
54
|
my ($self, $channel) = @_; |
431
|
41
|
100
|
|
|
|
154
|
return $channel if defined $self->{no_mappings}; |
432
|
|
|
|
|
|
|
|
433
|
5
|
50
|
|
|
|
22
|
if (! exists $self->{compiled_mappings}) { |
434
|
5
|
50
|
|
|
|
9
|
if (scalar keys %{$self->{channel_map}} == 0) { |
|
5
|
|
|
|
|
22
|
|
435
|
5
|
|
|
|
|
13
|
$self->{no_mappings} = 1; |
436
|
5
|
|
|
|
|
105
|
return $channel; |
437
|
|
|
|
|
|
|
} |
438
|
0
|
|
|
|
|
0
|
$self->{compiled_mappings} = []; |
439
|
0
|
|
|
|
|
0
|
for my $key (keys %{$self->{channel_map}}) { |
|
0
|
|
|
|
|
0
|
|
440
|
0
|
|
|
|
|
0
|
eval { |
441
|
0
|
0
|
|
|
|
0
|
if ($key =~ m{ ^/(.*)/$ }x) { |
442
|
0
|
|
|
|
|
0
|
my $re = $1; |
443
|
0
|
|
|
|
|
0
|
push(@{$self->{compiled_mappings}}, [ qr/$re/, $self->{channel_map}{$key} ]); |
|
0
|
|
|
|
|
0
|
|
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
}; |
446
|
0
|
0
|
|
|
|
0
|
if ($@) { |
447
|
0
|
|
|
|
|
0
|
warn "Failed to compile channel mapping for $key: $@\n"; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
0
|
0
|
|
|
|
0
|
if (exists $self->{channel_map}{$channel}) { |
453
|
0
|
|
|
|
|
0
|
return $self->{channel_map}{$channel}; |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
0
|
|
|
|
|
0
|
for my $match (@{$self->{compiled_mappings}}) { |
|
0
|
|
|
|
|
0
|
|
457
|
0
|
0
|
|
|
|
0
|
return $match->[1] if $channel =~ $match->[0]; |
458
|
|
|
|
|
|
|
} |
459
|
0
|
|
|
|
|
0
|
return $channel; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
sub _map_header { |
463
|
206
|
|
|
206
|
|
264
|
my ($header, $header_map) = @_; |
464
|
|
|
|
|
|
|
|
465
|
206
|
50
|
|
|
|
792
|
return exists $header_map->{$header} ? $header_map->{$header} : $header; |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
sub report { |
469
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
470
|
0
|
|
|
|
|
0
|
my %params = validate(@_, { all_touches_report => { default => 1 }, |
471
|
|
|
|
|
|
|
even_touches_report => { default => 1 }, |
472
|
|
|
|
|
|
|
distributed_touches_report => { default => 1 }, |
473
|
|
|
|
|
|
|
first_touch_report => { default => 1 }, |
474
|
|
|
|
|
|
|
last_touch_report => { default => 1 }, |
475
|
|
|
|
|
|
|
fifty_fifty_report => { default => 1 }, |
476
|
|
|
|
|
|
|
transactions_report => { default => 1 }, |
477
|
|
|
|
|
|
|
touchlist_report => { default => 1 }, |
478
|
|
|
|
|
|
|
transaction_distribution_report => { default => 1 }, |
479
|
|
|
|
|
|
|
channel_overlap_report => { default => 1 }, |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
all_touches => 0, |
482
|
|
|
|
|
|
|
even_touches => 0, |
483
|
|
|
|
|
|
|
distributed_touches => 0, |
484
|
|
|
|
|
|
|
first_touch => 0, |
485
|
|
|
|
|
|
|
last_touch => 0, |
486
|
|
|
|
|
|
|
fifty_fifty => 0, |
487
|
|
|
|
|
|
|
transactions => 0, |
488
|
|
|
|
|
|
|
touchlist => 0, |
489
|
|
|
|
|
|
|
transaction_distribution => 0, |
490
|
|
|
|
|
|
|
channel_overlap => 0, |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
report_order => { type => ARRAYREF, |
493
|
|
|
|
|
|
|
default => [ qw/all_touches even_touches distributed_touches first_touch last_touch fifty_fifty transactions touchlist transaction_distribution channel_overlap/ ], |
494
|
|
|
|
|
|
|
}, |
495
|
|
|
|
|
|
|
filename => 1, |
496
|
|
|
|
|
|
|
'format' => 0, |
497
|
|
|
|
|
|
|
title => 0, |
498
|
|
|
|
|
|
|
column_heading_format => 0, |
499
|
|
|
|
|
|
|
column_formats => 0, |
500
|
|
|
|
|
|
|
header_layout => 0, |
501
|
|
|
|
|
|
|
footer_layout => 0, |
502
|
|
|
|
|
|
|
strict_integer_values => 0, |
503
|
|
|
|
|
|
|
heading_map => 0, |
504
|
|
|
|
|
|
|
report_writer => { type => CODEREF, |
505
|
|
|
|
|
|
|
optional => 1, |
506
|
|
|
|
|
|
|
}, |
507
|
|
|
|
|
|
|
}); |
508
|
0
|
0
|
|
|
|
0
|
if ($params{filename} =~ m/\.(xls|txt|csv)$/i) { |
|
|
0
|
|
|
|
|
|
509
|
0
|
|
|
|
|
0
|
$params{'format'} = lc($1); |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
elsif (! defined $params{format}) { |
512
|
0
|
|
|
|
|
0
|
$params{'format'} = 'csv'; |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
0
|
|
|
|
|
0
|
my @reports; |
516
|
0
|
|
|
|
|
0
|
my @report_options = (qw/title sheetname/, keys %formatting_params); |
517
|
|
|
|
|
|
|
|
518
|
0
|
|
|
|
|
0
|
for my $report (@{$params{report_order}}) { |
|
0
|
|
|
|
|
0
|
|
519
|
0
|
|
|
|
|
0
|
my $method = $report . '_report'; |
520
|
0
|
0
|
|
|
|
0
|
if (! $self->can($method)) { |
521
|
0
|
|
|
|
|
0
|
warn "Report type $report is not valid\n"; |
522
|
0
|
|
|
|
|
0
|
next; |
523
|
|
|
|
|
|
|
} |
524
|
0
|
0
|
|
|
|
0
|
next unless $params{$method}; |
525
|
0
|
|
|
|
|
0
|
$self->_debug("Generating report '$report'\n"); |
526
|
0
|
|
|
|
|
0
|
push(@reports, |
527
|
|
|
|
|
|
|
$self->$method(_opts_subset(_merge_params(\%params, $params{$report}), |
528
|
|
|
|
|
|
|
@report_options))) |
529
|
|
|
|
|
|
|
} |
530
|
0
|
0
|
|
|
|
0
|
if ($params{report_writer}) { |
531
|
0
|
|
|
|
|
0
|
$params{report_writer}->(\@reports, \%params); |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
else { |
534
|
0
|
|
|
|
|
0
|
my $output = WWW::Analytics::MultiTouch::Tabular->new(_opts_subset(\%params, |
535
|
|
|
|
|
|
|
qw/format filename/)); |
536
|
0
|
|
|
|
|
0
|
$output->print(\@reports); |
537
|
0
|
|
|
|
|
0
|
$output->close(); |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
sub _merge_params { |
542
|
0
|
|
|
0
|
|
0
|
my $h1 = shift; |
543
|
0
|
|
|
|
|
0
|
my $h2 = shift; |
544
|
0
|
0
|
|
|
|
0
|
return $h1 unless ref($h2) eq 'HASH'; |
545
|
|
|
|
|
|
|
|
546
|
0
|
|
|
|
|
0
|
Hash::Merge::set_behavior('RIGHT_PRECEDENT'); |
547
|
0
|
|
|
|
|
0
|
return merge($h1, $h2); |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
sub all_touches_report { |
551
|
4
|
|
|
4
|
1
|
30
|
my $self = shift; |
552
|
4
|
|
|
|
|
155
|
my %params = validate(@_, { title => { default => 'All Touches' }, |
553
|
|
|
|
|
|
|
sheetname => { default => 'All Touches' }, |
554
|
|
|
|
|
|
|
%formatting_params, |
555
|
|
|
|
|
|
|
}); |
556
|
4
|
|
|
|
|
36
|
$params{total_100} = 0; |
557
|
4
|
|
|
|
|
10
|
$params{total_header} = 'ACTUAL TOTALS'; |
558
|
|
|
|
|
|
|
|
559
|
4
|
|
|
|
|
21
|
return $self->_touches_report(\%params, |
560
|
|
|
|
|
|
|
$self->{summary}{all_touches}, |
561
|
|
|
|
|
|
|
$self->{summary}{distr_touches}); |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
sub even_touches_report { |
565
|
2
|
|
|
2
|
1
|
45914
|
my $self = shift; |
566
|
2
|
|
|
|
|
118
|
my %params = validate(@_, { title => { default => 'Even Touches' }, |
567
|
|
|
|
|
|
|
sheetname => { default => 'Even Touches' }, |
568
|
|
|
|
|
|
|
%formatting_params, |
569
|
|
|
|
|
|
|
}); |
570
|
|
|
|
|
|
|
|
571
|
2
|
|
|
|
|
20
|
$params{total_100} = 1; |
572
|
2
|
|
|
|
|
6
|
$params{total_header} = 'TOTAL'; |
573
|
|
|
|
|
|
|
|
574
|
2
|
|
|
|
|
17
|
return $self->_touches_report(\%params, |
575
|
|
|
|
|
|
|
$self->{summary}{even_touches}, |
576
|
|
|
|
|
|
|
$self->{summary}{even_touches}); |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
sub distributed_touches_report { |
580
|
3
|
|
|
3
|
1
|
49235
|
my $self = shift; |
581
|
3
|
|
|
|
|
148
|
my %params = validate(@_, { title => { default => 'Distributed Touches' }, |
582
|
|
|
|
|
|
|
sheetname => { default => 'Distributed Touches' }, |
583
|
|
|
|
|
|
|
%formatting_params, |
584
|
|
|
|
|
|
|
}); |
585
|
|
|
|
|
|
|
|
586
|
3
|
|
|
|
|
29
|
$params{total_100} = 1; |
587
|
3
|
|
|
|
|
10
|
$params{total_header} = 'TOTAL'; |
588
|
|
|
|
|
|
|
|
589
|
3
|
|
|
|
|
72
|
return $self->_touches_report(\%params, |
590
|
|
|
|
|
|
|
$self->{summary}{distr_touches}, |
591
|
|
|
|
|
|
|
$self->{summary}{distr_touches}); |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
sub first_touch_report { |
596
|
1
|
|
|
1
|
1
|
14971
|
my $self = shift; |
597
|
1
|
|
|
|
|
56
|
my %params = validate(@_, { title => { default => 'First Touch' }, |
598
|
|
|
|
|
|
|
sheetname => { default => 'First Touch' }, |
599
|
|
|
|
|
|
|
%formatting_params, |
600
|
|
|
|
|
|
|
}); |
601
|
1
|
|
|
|
|
12
|
$params{total_100} = 1; |
602
|
1
|
|
|
|
|
4
|
$params{total_header} = 'TOTAL'; |
603
|
|
|
|
|
|
|
|
604
|
1
|
|
|
|
|
16
|
return $self->_touches_report(\%params, |
605
|
|
|
|
|
|
|
$self->{summary}{firstlast}{'first'}, |
606
|
|
|
|
|
|
|
$self->{summary}{firstlast}{'first'}); |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
} |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
sub last_touch_report { |
611
|
1
|
|
|
1
|
1
|
16085
|
my $self = shift; |
612
|
1
|
|
|
|
|
61
|
my %params = validate(@_, { title => { default => 'Last Touch' }, |
613
|
|
|
|
|
|
|
sheetname => { default => 'Last Touch' }, |
614
|
|
|
|
|
|
|
%formatting_params, |
615
|
|
|
|
|
|
|
}); |
616
|
1
|
|
|
|
|
13
|
$params{total_100} = 1; |
617
|
1
|
|
|
|
|
4
|
$params{total_header} = 'TOTAL'; |
618
|
|
|
|
|
|
|
|
619
|
1
|
|
|
|
|
11
|
return $self->_touches_report(\%params, |
620
|
|
|
|
|
|
|
$self->{summary}{firstlast}{'last'}, |
621
|
|
|
|
|
|
|
$self->{summary}{firstlast}{'last'}); |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
} |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
sub fifty_fifty_report { |
626
|
2
|
|
|
2
|
1
|
15567
|
my $self = shift; |
627
|
2
|
|
|
|
|
93
|
my %params = validate(@_, { title => { default => '50/50 First-Last Touch' }, |
628
|
|
|
|
|
|
|
sheetname => { default => 'Fifty Fifty' }, |
629
|
|
|
|
|
|
|
%formatting_params, |
630
|
|
|
|
|
|
|
}); |
631
|
2
|
|
|
|
|
20
|
$params{total_100} = 1; |
632
|
2
|
|
|
|
|
6
|
$params{total_header} = 'TOTAL'; |
633
|
|
|
|
|
|
|
|
634
|
2
|
|
|
|
|
14
|
return $self->_touches_report(\%params, |
635
|
|
|
|
|
|
|
$self->{summary}{firstlast}{hybrid}, |
636
|
|
|
|
|
|
|
$self->{summary}{distr_touches}); |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
sub _touches_report { |
641
|
13
|
|
|
13
|
|
37
|
my ($self, $params, $summary, $summary_for_total) = @_; |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
# Total based on distributed touches to get actual totals |
644
|
13
|
|
|
|
|
21
|
my @totals; |
645
|
|
|
|
|
|
|
my %totals; |
646
|
13
|
|
|
141
|
|
72
|
my $formatter = sub { shift }; |
|
141
|
|
|
|
|
265
|
|
647
|
13
|
100
|
|
12
|
|
60
|
$formatter = sub { sprintf("%d", shift) } if $params->{strict_integer_values}; |
|
12
|
|
|
|
|
29
|
|
648
|
|
|
|
|
|
|
|
649
|
13
|
|
|
|
|
35
|
for my $col (qw/count transactions revenue/) { |
650
|
39
|
|
|
|
|
58
|
push(@totals, $formatter->(sum map { $summary_for_total->{$_}{$col} } keys %{$summary_for_total})); |
|
114
|
|
|
|
|
330
|
|
|
39
|
|
|
|
|
86
|
|
651
|
39
|
|
|
|
|
110
|
$totals{$col} = $totals[-1]; |
652
|
|
|
|
|
|
|
} |
653
|
13
|
100
|
|
|
|
48
|
if ($params->{total_100}) { |
654
|
9
|
|
|
|
|
24
|
push(@totals, 100, 100); # % transactions, % revenue |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
else { |
657
|
4
|
|
|
|
|
10
|
push(@totals, '', ''); # % transactions, % revenue |
658
|
|
|
|
|
|
|
} |
659
|
13
|
|
|
|
|
25
|
my @data; |
660
|
13
|
|
|
|
|
24
|
for my $channel (sort keys %{$summary}) { |
|
13
|
|
|
|
|
64
|
|
661
|
38
|
|
|
|
|
153
|
my $i = 0; |
662
|
114
|
|
|
|
|
636
|
push(@data, [ [ $channel, $params->{row_heading_format} ], |
663
|
114
|
|
|
|
|
251
|
(map { [ $formatter->($summary->{$channel}{$_}), $params->{column_formats}->[$i++ % @{$params->{column_formats}}] ] } qw/count transactions revenue/), |
|
76
|
|
|
|
|
486
|
|
664
|
38
|
|
50
|
|
|
107
|
(map { [ sprintf("%.2f", $summary->{$channel}{$_} / ($totals{$_} || 1) * 100), $params->{column_formats}->[$i++ % @{$params->{column_formats}}] ] } qw/transactions revenue/), |
|
76
|
|
|
|
|
605
|
|
665
|
|
|
|
|
|
|
]); |
666
|
|
|
|
|
|
|
} |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# sort by revenue, transactions descending |
669
|
13
|
50
|
|
|
|
64
|
@data = sort { $b->[3][0] <=> $a->[3][0] || $b->[2][0] <=> $a->[2][0] } @data; |
|
37
|
|
|
|
|
122
|
|
670
|
|
|
|
|
|
|
|
671
|
13
|
|
|
|
|
75
|
push(@data, [ map { [ $_, $params->{column_heading_format} ] } |
|
78
|
|
|
|
|
360
|
|
672
|
|
|
|
|
|
|
_map_header($params->{total_header}, $params->{heading_map}), @totals ]); |
673
|
|
|
|
|
|
|
|
674
|
13
|
|
|
|
|
27
|
my $i = 0; |
675
|
13
|
|
|
|
|
43
|
my @heading = ('Channel', 'Touches', 'Transactions', 'Revenue', '% Transactions', '% Revenue'); |
676
|
78
|
|
|
|
|
142
|
my %report = ( title => [ $params->{title}, $params->{title_format} ], |
677
|
|
|
|
|
|
|
sheetname => $params->{sheetname}, |
678
|
26
|
|
|
|
|
443
|
headings => [ map { [ _map_header($_, $params->{heading_map}), $params->{column_heading_format} ] } @heading ], |
679
|
|
|
|
|
|
|
data => \@data, |
680
|
13
|
|
|
|
|
53
|
chart => [ map { { type => 'pie', |
681
|
|
|
|
|
|
|
title => { name => $heading[$_], |
682
|
|
|
|
|
|
|
name_formula => [-1, $_], |
683
|
|
|
|
|
|
|
}, |
684
|
|
|
|
|
|
|
abs_row => 20 * $i++, |
685
|
|
|
|
|
|
|
abs_col => 7, |
686
|
|
|
|
|
|
|
x_scale => 1, |
687
|
|
|
|
|
|
|
y_scale => 1, |
688
|
|
|
|
|
|
|
series => [ |
689
|
|
|
|
|
|
|
{ categories => [ 0, scalar @data - 2, 0, 0 ], |
690
|
|
|
|
|
|
|
values => [ 0, (scalar @data - 2), $_, $_ ], |
691
|
|
|
|
|
|
|
name_formula => [$_, 0], |
692
|
|
|
|
|
|
|
name => $data[$_][0][0], |
693
|
|
|
|
|
|
|
} ], |
694
|
|
|
|
|
|
|
} } (2, 3) ], |
695
|
|
|
|
|
|
|
start_date => $self->_format_date($self->{current_data}{start_date}), |
696
|
|
|
|
|
|
|
end_date => $self->_format_date($self->{current_data}{end_date}), |
697
|
|
|
|
|
|
|
generation_date => $self->_format_date(), |
698
|
|
|
|
|
|
|
window_length => $self->{summary}->{window_length}, |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
); |
701
|
|
|
|
|
|
|
|
702
|
13
|
|
|
|
|
855
|
_add_layout(\%report, $params); |
703
|
13
|
|
|
|
|
111
|
return \%report; |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
sub transactions_report { |
708
|
3
|
|
|
3
|
1
|
41805
|
my $self = shift; |
709
|
3
|
|
|
|
|
154
|
my %params = validate(@_, { title => { default => 'Transactions' }, |
710
|
|
|
|
|
|
|
sheetname => { default => 'Transactions' }, |
711
|
|
|
|
|
|
|
%formatting_params, |
712
|
|
|
|
|
|
|
}); |
713
|
|
|
|
|
|
|
|
714
|
3
|
|
|
|
|
27
|
my @summary = sort { $a->{tid} cmp $b->{tid} } @{$self->{summary}{trans}}; |
|
21
|
|
|
|
|
50
|
|
|
3
|
|
|
|
|
28
|
|
715
|
3
|
|
|
|
|
7
|
my %channels; |
716
|
3
|
|
|
|
|
10
|
for my $rec (@summary) { |
717
|
15
|
|
|
|
|
18
|
$channels{$_}++ for keys %{$rec->{touches}}; |
|
15
|
|
|
|
|
89
|
|
718
|
|
|
|
|
|
|
} |
719
|
3
|
50
|
|
|
|
13
|
my @channels = sort { $channels{$b} <=> $channels{$a} || $b cmp $a } keys %channels; |
|
8
|
|
|
|
|
32
|
|
720
|
3
|
|
|
|
|
10
|
my @data = ( [ map { [ _map_header($_, $params{heading_map}), $params{column_heading_format} ] } ('', '', map { qw/Touches Transactions Revenue/ } @channels )] ); |
|
33
|
|
|
|
|
68
|
|
|
9
|
|
|
|
|
20
|
|
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
|
723
|
3
|
|
|
|
|
12
|
for my $rec (@summary) { |
724
|
15
|
|
|
|
|
28
|
my $i = 0; |
725
|
45
|
|
|
|
|
128
|
push(@data, [ [ $rec->{tid}, $params{row_heading_format} ], |
726
|
|
|
|
|
|
|
$rec->{'date'}, |
727
|
15
|
|
|
|
|
53
|
map { my $cf = $params{column_formats}->[$i++ % @{$params{column_formats}}]; |
|
45
|
|
|
|
|
67
|
|
728
|
45
|
|
100
|
|
|
540
|
( [ $rec->{touches}{$_}{count} || '', $cf ], |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
729
|
|
|
|
|
|
|
[ $rec->{touches}{$_}{transactions} || '', $cf ], |
730
|
|
|
|
|
|
|
[ $rec->{touches}{$_}{revenue} || '', $cf ] ) |
731
|
|
|
|
|
|
|
} @channels ]); |
732
|
|
|
|
|
|
|
} |
733
|
|
|
|
|
|
|
|
734
|
33
|
|
|
|
|
56
|
my %report = ( title => [ $params{title}, $params{title_format} ], |
735
|
|
|
|
|
|
|
sheetname => $params{sheetname}, |
736
|
3
|
|
|
|
|
17
|
headings => [ map { [ _map_header($_, $params{heading_map}), $params{column_heading_format} ] } ('Transaction ID', 'Date', map { (' ', $_, ' ') } @channels) ], |
|
9
|
|
|
|
|
21
|
|
737
|
|
|
|
|
|
|
data => \@data, |
738
|
|
|
|
|
|
|
start_date => $self->_format_date($self->{current_data}{start_date}), |
739
|
|
|
|
|
|
|
end_date => $self->_format_date($self->{current_data}{end_date}), |
740
|
|
|
|
|
|
|
generation_date => $self->_format_date(), |
741
|
|
|
|
|
|
|
window_length => $self->{summary}->{window_length}, |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
); |
744
|
|
|
|
|
|
|
|
745
|
3
|
|
|
|
|
280
|
_add_layout(\%report, \%params); |
746
|
3
|
|
|
|
|
21
|
return \%report; |
747
|
|
|
|
|
|
|
} |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
sub touchlist_report { |
751
|
3
|
|
|
3
|
1
|
102562
|
my $self = shift; |
752
|
|
|
|
|
|
|
|
753
|
3
|
|
|
|
|
165
|
my %params = validate(@_, { title => { default => 'Touch List' }, |
754
|
|
|
|
|
|
|
sheetname => { default => 'Touch List' }, |
755
|
|
|
|
|
|
|
%formatting_params, |
756
|
|
|
|
|
|
|
}); |
757
|
|
|
|
|
|
|
|
758
|
3
|
|
|
|
|
29
|
my @data; |
759
|
3
|
|
|
|
|
11
|
for my $touchlist (sort { $a->[0] cmp $b->[0] } @{$self->{summary}{touchlist}}) { |
|
24
|
|
|
|
|
83
|
|
|
3
|
|
|
|
|
29
|
|
760
|
15
|
|
|
|
|
3979
|
my $i = 0; |
761
|
40
|
|
|
|
|
97
|
push(@data, [ |
762
|
|
|
|
|
|
|
[ $touchlist->[0], $params{row_heading_format} ], #tid |
763
|
|
|
|
|
|
|
$self->_format_time($touchlist->[2]), # date |
764
|
|
|
|
|
|
|
$touchlist->[1], # revenue |
765
|
15
|
|
|
|
|
100
|
map { my $cf = $params{column_formats}->[$i++ % @{$params{column_formats}}]; |
|
40
|
|
|
|
|
12818
|
|
766
|
40
|
|
|
|
|
177
|
( [ $_->[0], $cf ], [ $self->_format_time($_->[1]), $cf ] ) } @$touchlist[3 .. @$touchlist - 1] |
767
|
|
|
|
|
|
|
]); |
768
|
|
|
|
|
|
|
} |
769
|
3
|
|
50
|
|
|
914
|
my $maxcols = max(map { 2 * ( @$_ - 3 ) } @{$self->{summary}{touchlist}}) || 0; |
770
|
|
|
|
|
|
|
|
771
|
32
|
|
|
|
|
56
|
my %report = ( title => [ $params{title}, $params{title_format} ], |
772
|
|
|
|
|
|
|
sheetname => $params{sheetname}, |
773
|
3
|
|
|
|
|
21
|
headings => [ map { [ _map_header($_, $params{heading_map}), $params{column_heading_format} ] } ('Transaction ID', 'Date', 'Revenue', 'Touches', ('') x $maxcols) ], |
774
|
|
|
|
|
|
|
data => \@data, |
775
|
|
|
|
|
|
|
start_date => $self->_format_date($self->{current_data}{start_date}), |
776
|
|
|
|
|
|
|
end_date => $self->_format_date($self->{current_data}{end_date}), |
777
|
|
|
|
|
|
|
generation_date => $self->_format_date(), |
778
|
|
|
|
|
|
|
window_length => $self->{summary}->{window_length}, |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
); |
781
|
|
|
|
|
|
|
|
782
|
3
|
|
|
|
|
273
|
_add_layout(\%report, \%params); |
783
|
3
|
|
|
|
|
21
|
return \%report; |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
sub transaction_distribution_report { |
787
|
1
|
|
|
1
|
1
|
17773
|
my $self = shift; |
788
|
|
|
|
|
|
|
|
789
|
1
|
|
|
|
|
56
|
my %params = validate(@_, { title => { default => 'Transaction Distribution' }, |
790
|
|
|
|
|
|
|
sheetname => { default => 'Transaction Distribution' }, |
791
|
|
|
|
|
|
|
%formatting_params, |
792
|
|
|
|
|
|
|
}); |
793
|
|
|
|
|
|
|
|
794
|
1
|
|
|
|
|
12
|
my $transdist = $self->{summary}{transdist}; |
795
|
1
|
|
|
|
|
4
|
my $transdistoverall = $self->{summary}{transdistoverall}; |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
# find 95th percentile so last column contains remaining 5% |
798
|
1
|
|
|
|
|
2
|
my @total; |
799
|
|
|
|
|
|
|
my %channels; |
800
|
1
|
|
|
|
|
5
|
my %bins = map { $_ => 1 } keys %$transdist; |
|
2
|
|
|
|
|
7
|
|
801
|
1
|
|
|
|
|
8
|
$bins{$_}++ for keys %$transdistoverall; |
802
|
|
|
|
|
|
|
|
803
|
1
|
|
|
|
|
8
|
for my $count (sort { $a <=> $b } keys %bins) { |
|
1
|
|
|
|
|
5
|
|
804
|
2
|
100
|
50
|
|
|
15
|
push(@total, [ $count, ($transdistoverall->{$count} || 0) + (@total > 0 ? $total[-1][1] : 0) ]); |
805
|
2
|
|
|
|
|
3
|
$channels{$_}++ for keys %{$transdist->{$count}}; |
|
2
|
|
|
|
|
15
|
|
806
|
|
|
|
|
|
|
} |
807
|
1
|
|
|
|
|
3
|
my $main = \@total; |
808
|
1
|
|
|
|
|
3
|
my $rest; |
809
|
1
|
50
|
|
|
|
5
|
if (@total > 10) { |
810
|
0
|
|
|
|
|
0
|
my $threshold = 0.95 * $total[-1][1]; |
811
|
0
|
|
|
|
|
0
|
my $i = 0; |
812
|
0
|
0
|
|
0
|
|
0
|
($main, $rest) = part { $total[$i++][1] <= $threshold ? 0 : 1 } @total; |
|
0
|
|
|
|
|
0
|
|
813
|
|
|
|
|
|
|
} |
814
|
1
|
|
|
|
|
3
|
my @headings = ("No. of Touches", map { $_->[0] } @$main); |
|
2
|
|
|
|
|
6
|
|
815
|
|
|
|
|
|
|
# create last roll-up heading |
816
|
1
|
50
|
|
|
|
18
|
push(@headings, ">" . $main->[-1][0]) if $rest; |
817
|
|
|
|
|
|
|
|
818
|
1
|
|
|
|
|
2
|
my @data; |
819
|
1
|
|
|
|
|
6
|
for my $channel ((sort keys %channels), 'OVERALL') { |
820
|
4
|
|
|
|
|
5
|
my $i = 0; |
821
|
8
|
|
|
|
|
149
|
push(@data, [ [ $channel, $params{row_heading_format} ], |
822
|
|
|
|
|
|
|
map { |
823
|
8
|
|
|
|
|
13
|
my $cf = $params{column_formats}->[$i++ % @{$params{column_formats}}]; |
|
8
|
|
|
|
|
15
|
|
824
|
8
|
|
100
|
|
|
51
|
[ ($channel eq 'OVERALL' ? $transdistoverall->{$_} : $transdist->{$_}{$channel}) || 0, $cf ] |
825
|
4
|
|
|
|
|
13
|
} map { $_->[0] } @$main ]); |
826
|
4
|
50
|
|
|
|
15
|
if ($rest) { |
827
|
|
|
|
|
|
|
# append a bin containing the sum of remaining values |
828
|
0
|
0
|
|
|
|
0
|
push(@{$data[-1]}, sum(map { ($channel eq 'OVERALL' ? $transdistoverall->{$_->[0]} : $transdist->{$_->[0]}{$channel}) || 0 } @$rest)); |
|
0
|
0
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
} |
831
|
|
|
|
|
|
|
|
832
|
3
|
|
|
|
|
11
|
my %report = ( title => [ $params{title}, $params{title_format} ], |
833
|
|
|
|
|
|
|
sheetname => $params{sheetname}, |
834
|
4
|
|
|
|
|
10
|
headings => [ map { [ _map_header($_, $params{heading_map}), $params{column_heading_format} ] } @headings ], |
835
|
|
|
|
|
|
|
data => \@data, |
836
|
|
|
|
|
|
|
chart => [ { type => 'column', |
837
|
|
|
|
|
|
|
x_scale => 1.5, |
838
|
|
|
|
|
|
|
y_scale => 1.5, |
839
|
|
|
|
|
|
|
series => [ map { |
840
|
1
|
|
|
|
|
7
|
{ categories => [ -1, -1, 1, scalar @{$data[0]} ], |
|
4
|
|
|
|
|
35
|
|
841
|
4
|
|
|
|
|
6
|
values => [ $_, $_, 1, scalar @{$data[0]} ], |
842
|
|
|
|
|
|
|
name_formula => [$_, 0], |
843
|
|
|
|
|
|
|
name => $data[$_][0], |
844
|
|
|
|
|
|
|
} } (0 .. @data - 1) ] |
845
|
|
|
|
|
|
|
} ], |
846
|
|
|
|
|
|
|
start_date => $self->_format_date($self->{current_data}{start_date}), |
847
|
|
|
|
|
|
|
end_date => $self->_format_date($self->{current_data}{end_date}), |
848
|
|
|
|
|
|
|
generation_date => $self->_format_date(), |
849
|
|
|
|
|
|
|
window_length => $self->{summary}->{window_length}, |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
); |
852
|
|
|
|
|
|
|
|
853
|
1
|
|
|
|
|
69
|
_add_layout(\%report, \%params); |
854
|
1
|
|
|
|
|
10
|
return \%report; |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
} |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
sub channel_overlap_report { |
859
|
1
|
|
|
1
|
1
|
16998
|
my $self = shift; |
860
|
|
|
|
|
|
|
|
861
|
1
|
|
|
|
|
55
|
my %params = validate(@_, { title => { default => 'Channel Overlap' }, |
862
|
|
|
|
|
|
|
sheetname => { default => 'Channel Overlap' }, |
863
|
|
|
|
|
|
|
%formatting_params, |
864
|
|
|
|
|
|
|
}); |
865
|
|
|
|
|
|
|
|
866
|
1
|
|
|
|
|
14
|
my @data; |
867
|
|
|
|
|
|
|
my @offsets; # row offsets into data array for each report |
868
|
|
|
|
|
|
|
|
869
|
1
|
|
|
|
|
5
|
push(@offsets, scalar @data); |
870
|
|
|
|
|
|
|
$self->_overlap_report(\%params, "Channel Count", |
871
|
1
|
|
|
1
|
|
13
|
\@data, sub { $a->[0][0] <=> $b->[0][0] }, $self->{summary}{overlap}{count}); |
|
1
|
|
|
|
|
8
|
|
872
|
1
|
|
|
|
|
6
|
push(@data, [ ' ' ]); |
873
|
1
|
|
|
|
|
2
|
push(@offsets, scalar @data); |
874
|
|
|
|
|
|
|
$self->_overlap_report(\%params, "Channel Combination", |
875
|
1
|
|
|
4
|
|
9
|
\@data, sub { $b->[6][0] <=> $a->[6][0] }, $self->{summary}{overlap}{joint}); |
|
4
|
|
|
|
|
16
|
|
876
|
|
|
|
|
|
|
|
877
|
1
|
|
|
|
|
4
|
push(@offsets, scalar @data); |
878
|
|
|
|
|
|
|
|
879
|
1
|
|
|
|
|
3
|
my $i = 0; |
880
|
1
|
|
|
|
|
35
|
my %report = ( title => [ $params{title}, $params{title_format} ], |
881
|
|
|
|
|
|
|
sheetname => $params{sheetname}, |
882
|
|
|
|
|
|
|
data => \@data, |
883
|
1
|
|
|
|
|
6
|
chart => [ map { { type => 'pie', |
884
|
|
|
|
|
|
|
title => { name => $data[$offsets[$_]][0][0], |
885
|
|
|
|
|
|
|
name_formula => [$offsets[$_], 0], |
886
|
|
|
|
|
|
|
}, |
887
|
|
|
|
|
|
|
abs_row => 20 * $i++, |
888
|
|
|
|
|
|
|
abs_col => 8, |
889
|
|
|
|
|
|
|
x_scale => 1, |
890
|
|
|
|
|
|
|
y_scale => 1, |
891
|
|
|
|
|
|
|
series => [ |
892
|
|
|
|
|
|
|
{ categories => [ $offsets[$_] + 1, $offsets[$_ + 1] - 1, |
893
|
|
|
|
|
|
|
0, 0 ], |
894
|
|
|
|
|
|
|
values => [ $offsets[$_] + 1, $offsets[$_ + 1] - 1, 1, 1 ], |
895
|
|
|
|
|
|
|
name_formula => [$offsets[$_] + 1, 0], |
896
|
|
|
|
|
|
|
name => $data[$offsets[$_]][0][0], |
897
|
|
|
|
|
|
|
} ], |
898
|
|
|
|
|
|
|
} } (0 .. 0) ], # just doing first pie, second is too busy |
899
|
|
|
|
|
|
|
start_date => $self->_format_date($self->{current_data}{start_date}), |
900
|
|
|
|
|
|
|
end_date => $self->_format_date($self->{current_data}{end_date}), |
901
|
|
|
|
|
|
|
generation_date => $self->_format_date(), |
902
|
|
|
|
|
|
|
window_length => $self->{summary}->{window_length}, |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
); |
905
|
|
|
|
|
|
|
|
906
|
1
|
|
|
|
|
64
|
_add_layout(\%report, \%params); |
907
|
1
|
|
|
|
|
6
|
return \%report; |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
sub _overlap_report { |
911
|
2
|
|
|
2
|
|
6
|
my ($self, $params, $heading1, $result, $comparator, $src) = @_; |
912
|
|
|
|
|
|
|
|
913
|
2
|
|
|
|
|
7
|
push(@$result, [ map { [ _map_header($_, $params->{heading_map}), $params->{column_heading_format} ] } |
|
14
|
|
|
|
|
30
|
|
914
|
|
|
|
|
|
|
($heading1, 'Touches', 'Transactions', 'Revenue', '% Transactions', '% Revenue', 'Efficiency' ) ]); |
915
|
|
|
|
|
|
|
|
916
|
2
|
|
|
|
|
6
|
my %totals; |
917
|
2
|
|
|
|
|
4
|
for my $sum (qw/transactions revenue/) { |
918
|
4
|
|
|
|
|
37
|
$totals{$sum} += $src->{$_}{$sum} for keys %$src; |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
|
921
|
2
|
|
|
|
|
5
|
my @data; |
922
|
2
|
|
|
|
|
6
|
for my $row (keys %$src) { |
923
|
6
|
|
|
|
|
8
|
my $i = 0; |
924
|
18
|
|
|
|
|
52
|
push(@data, [ [ $row, $params->{row_heading_format} ], |
925
|
18
|
|
|
|
|
38
|
(map { [ $src->{$row}{$_}, $params->{column_formats}->[$i++ % @{$params->{column_formats}}] ] } qw/touches transactions revenue/), |
|
12
|
|
|
|
|
91
|
|
926
|
6
|
|
50
|
|
|
17
|
(map { [ sprintf("%.2f", $src->{$row}{$_} / ($totals{$_} || 1) * 100), $params->{column_formats}->[$i++ % @{$params->{column_formats}}] ] } qw/transactions revenue/), |
|
12
|
|
50
|
|
|
92
|
|
927
|
|
|
|
|
|
|
[ sprintf("%.2f", $src->{$row}{transactions} / ($src->{$row}{touches} || 1)) ] |
928
|
|
|
|
|
|
|
]); |
929
|
|
|
|
|
|
|
} |
930
|
2
|
|
|
|
|
13
|
push(@$result, sort $comparator @data); |
931
|
|
|
|
|
|
|
} |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
sub _add_layout { |
936
|
21
|
|
|
21
|
|
44
|
my $report = shift; |
937
|
21
|
|
|
|
|
44
|
my $params = shift; |
938
|
|
|
|
|
|
|
|
939
|
21
|
|
|
|
|
61
|
for (qw/header_layout footer_layout strict_integer_values/) { |
940
|
63
|
100
|
|
|
|
197
|
$report->{$_} = $params->{$_} if defined $params->{$_}; |
941
|
|
|
|
|
|
|
} |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
sub _compile_channel_pattern { |
945
|
5
|
|
|
5
|
|
12
|
my ($self, $pat) = @_; |
946
|
|
|
|
|
|
|
|
947
|
5
|
|
|
|
|
36
|
my @parts = split($self->{patsep}, $pat); |
948
|
5
|
|
|
|
|
14
|
my @idx; |
949
|
5
|
|
|
|
|
16
|
for (@parts) { |
950
|
15
|
100
|
|
|
|
48
|
m/source/ && do { push(@idx, 0); next }; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
11
|
|
951
|
10
|
100
|
|
|
|
24
|
m/med/ && do { push(@idx, 1); next }; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
6
|
|
952
|
5
|
50
|
|
|
|
41
|
m/sub|cat/ && do { push(@idx, 2); next }; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
12
|
|
953
|
0
|
|
|
|
|
0
|
warn "Invalid channel pattern component: $_\n"; |
954
|
|
|
|
|
|
|
} |
955
|
5
|
50
|
|
|
|
14
|
if (! @idx) { |
956
|
0
|
|
|
|
|
0
|
@idx = (0, 1, 2); |
957
|
|
|
|
|
|
|
} |
958
|
5
|
|
|
|
|
16
|
return \@idx; |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
sub _currency_conversion { |
962
|
25
|
|
|
25
|
|
45
|
my ($self, $dv) = @_; |
963
|
25
|
50
|
|
|
|
137
|
return $self->{revenue_scale} * $dv if $dv =~ m/^[0-9.]+$/; |
964
|
|
|
|
|
|
|
|
965
|
0
|
|
|
|
|
0
|
die "Currency conversion not implemented: rev = $dv\n"; |
966
|
|
|
|
|
|
|
} |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
sub _debug { |
969
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
970
|
0
|
0
|
|
|
|
0
|
my @args = map { ref($_) eq 'CODE' ? $_->() : $_ } @_; |
|
0
|
|
|
|
|
0
|
|
971
|
0
|
0
|
|
|
|
0
|
print STDERR @args if $self->{debug}; |
972
|
|
|
|
|
|
|
} |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
sub process { |
975
|
0
|
|
|
0
|
1
|
0
|
my $class = shift; |
976
|
0
|
|
|
|
|
0
|
my $opts = shift; |
977
|
|
|
|
|
|
|
|
978
|
0
|
|
|
|
|
0
|
my $mt = $class->new(_opts_subset($opts, qw/id event_category fieldsep recsep patsep debug bugfix1 channel_map date_format time_format ga_timezone report_timezone revenue_scale auth_file refresh_token auth_token/)); |
979
|
|
|
|
|
|
|
|
980
|
0
|
|
|
|
|
0
|
$mt->get_data(_opts_subset($opts, qw/start_date end_date/)); |
981
|
0
|
|
|
|
|
0
|
$mt->summarise(_opts_subset($opts, qw/window_length single_order_model channel_pattern channel adjustments/)); |
982
|
0
|
|
|
|
|
0
|
$mt->report(_opts_subset($opts, qw/ |
983
|
|
|
|
|
|
|
all_touches_report even_touches_report distributed_touches_report first_touch_report last_touch_report fifty_fifty_report |
984
|
|
|
|
|
|
|
transactions_report touchlist_report transaction_distribution_report channel_overlap_report |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
all_touches even_touches distributed_touches first_touch last_touch fifty_fifty |
987
|
|
|
|
|
|
|
transactions touchlist transaction_distribution channel_overlap |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
report_order filename format column_heading_format column_formats header_layout footer_layout strict_integer_values heading_map/)); |
990
|
|
|
|
|
|
|
} |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
sub _opts_subset { |
993
|
0
|
|
|
0
|
|
0
|
my ($opts, @fields) = @_; |
994
|
|
|
|
|
|
|
|
995
|
0
|
|
|
|
|
0
|
my %result; |
996
|
0
|
|
|
|
|
0
|
for (@fields) { |
997
|
0
|
0
|
|
|
|
0
|
$result{$_} = $opts->{$_} if exists $opts->{$_}; |
998
|
|
|
|
|
|
|
} |
999
|
|
|
|
|
|
|
|
1000
|
0
|
|
|
|
|
0
|
return %result; |
1001
|
|
|
|
|
|
|
} |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
sub _format_date { |
1004
|
63
|
|
|
63
|
|
2682
|
my $self = shift; |
1005
|
63
|
|
66
|
|
|
369
|
my $date = shift || DateTime->now->set_time_zone($self->{report_timezone})->truncate(to => 'day'); |
1006
|
|
|
|
|
|
|
|
1007
|
63
|
|
|
|
|
15142
|
return $date->strftime( $self->{date_format} ); |
1008
|
|
|
|
|
|
|
} |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
sub _format_time { |
1011
|
55
|
|
|
55
|
|
75
|
my $self = shift; |
1012
|
55
|
|
|
|
|
137
|
my $t = shift; |
1013
|
|
|
|
|
|
|
|
1014
|
55
|
50
|
|
|
|
277
|
return DateTime->from_epoch(epoch => $t) |
1015
|
|
|
|
|
|
|
->set_time_zone($self->{report_timezone}) |
1016
|
|
|
|
|
|
|
->strftime( $self->{time_format} ) |
1017
|
|
|
|
|
|
|
if defined $t; |
1018
|
0
|
|
|
|
|
|
return 'UNKNOWN'; |
1019
|
|
|
|
|
|
|
} |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
sub authorise { |
1022
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
1023
|
|
|
|
|
|
|
|
1024
|
0
|
|
|
|
|
|
my $url = $self->{oauth}->authorize_url; |
1025
|
|
|
|
|
|
|
|
1026
|
0
|
|
|
|
|
|
print(<<"EOF"); |
1027
|
|
|
|
|
|
|
Multitouch Analytics requires access to data from your Google Analytics account. |
1028
|
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
Please visit the following URL, grant access to this application, and enter |
1030
|
|
|
|
|
|
|
the code you will be shown: |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
$url |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
EOF |
1035
|
|
|
|
|
|
|
|
1036
|
0
|
|
|
|
|
|
print("Enter code: "); |
1037
|
0
|
|
|
|
|
|
my $code = ; |
1038
|
0
|
|
|
|
|
|
chomp($code); |
1039
|
|
|
|
|
|
|
|
1040
|
0
|
|
|
|
|
|
my $res = $self->{oauth}->get_access_token($code); |
1041
|
|
|
|
|
|
|
|
1042
|
0
|
|
0
|
|
|
|
SaveConfig($self->{auth_file} || _default_auth_file(), |
1043
|
|
|
|
|
|
|
{ access_token => $res->{access_token}, |
1044
|
|
|
|
|
|
|
refresh_token => $res->{refresh_token}}); |
1045
|
0
|
|
|
|
|
|
$self->{refresh_token} = $res->{refresh_token}; |
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
sub parse_config { |
1049
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
1050
|
0
|
|
|
|
|
|
my $opts = shift; |
1051
|
0
|
|
|
|
|
|
my $conf_file = shift; |
1052
|
|
|
|
|
|
|
|
1053
|
0
|
|
|
|
|
|
Hash::Merge::set_behavior('RIGHT_PRECEDENT'); |
1054
|
|
|
|
|
|
|
|
1055
|
0
|
0
|
|
|
|
|
if ($conf_file) { |
1056
|
0
|
0
|
0
|
|
|
|
die "Config file $conf_file does not exist or is not readable" unless -f $conf_file && -r $conf_file; |
1057
|
|
|
|
|
|
|
|
1058
|
0
|
|
|
|
|
|
my %file_opts = ParseConfig(-ConfigFile => $conf_file, |
1059
|
|
|
|
|
|
|
-AutoTrue => 1, |
1060
|
|
|
|
|
|
|
-SplitPolicy => 'equalsign', |
1061
|
|
|
|
|
|
|
-UTF8 => 1, |
1062
|
|
|
|
|
|
|
-InterPolateVars => 1, |
1063
|
|
|
|
|
|
|
-InterPolateEnv => 1, |
1064
|
|
|
|
|
|
|
-IncludeRelative => 1, |
1065
|
|
|
|
|
|
|
-DefaultConfig => { |
1066
|
|
|
|
|
|
|
cwd => file($conf_file)->dir->absolute->stringify, |
1067
|
|
|
|
|
|
|
}, |
1068
|
|
|
|
|
|
|
); |
1069
|
0
|
|
|
|
|
|
_fix_array_keys(\%file_opts, 'column_formats'); |
1070
|
|
|
|
|
|
|
|
1071
|
0
|
|
|
|
|
|
$opts = merge($opts, \%file_opts); |
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
|
1074
|
0
|
|
0
|
|
|
|
$opts->{auth_file} ||= _default_auth_file($conf_file); |
1075
|
0
|
0
|
|
|
|
|
if (-f $opts->{auth_file}) { |
1076
|
0
|
|
|
|
|
|
my %auth_opts = ParseConfig(-ConfigFile => $opts->{auth_file}); |
1077
|
0
|
|
|
|
|
|
$opts = merge($opts, \%auth_opts); |
1078
|
0
|
0
|
|
|
|
|
if ($opts->{debug}) { |
1079
|
0
|
|
|
|
|
|
print "Opened auth_file $opts->{auth_file}\n"; |
1080
|
0
|
0
|
|
|
|
|
open my $fh, '<', $opts->{auth_file} or die "Failed to open $opts->{auth_file}: $!"; |
1081
|
0
|
|
|
|
|
|
local $/ = undef; |
1082
|
0
|
|
|
|
|
|
my $s = <$fh>; |
1083
|
0
|
|
|
|
|
|
close($fh); |
1084
|
0
|
|
|
|
|
|
print "Contents:\n$s\n"; |
1085
|
0
|
|
|
|
|
|
print "Parsed content:\n" . Dumper(\%auth_opts); |
1086
|
|
|
|
|
|
|
} |
1087
|
|
|
|
|
|
|
} |
1088
|
|
|
|
|
|
|
|
1089
|
0
|
|
|
|
|
|
return $opts; |
1090
|
|
|
|
|
|
|
} |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
sub _default_auth_file { |
1093
|
0
|
|
0
|
0
|
|
|
my $conf_file = shift || 'multitouchanalytics'; |
1094
|
|
|
|
|
|
|
|
1095
|
0
|
|
|
|
|
|
my (@parts) = grep { $_ } split(/\./, $conf_file); |
|
0
|
|
|
|
|
|
|
1096
|
0
|
0
|
|
|
|
|
pop(@parts) if @parts > 1; |
1097
|
0
|
|
|
|
|
|
push(@parts, 'auth'); |
1098
|
0
|
|
|
|
|
|
return '.' . join('.', @parts); |
1099
|
|
|
|
|
|
|
} |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
sub _fix_array_keys { |
1102
|
0
|
|
|
0
|
|
|
my $hash = shift; |
1103
|
0
|
|
|
|
|
|
my $key = shift; |
1104
|
0
|
0
|
0
|
|
|
|
if (exists($hash->{$key}) && ref($hash->{$key}) ne 'ARRAY') { |
1105
|
0
|
|
|
|
|
|
$hash->{$key} = [ $hash->{$key} ]; |
1106
|
|
|
|
|
|
|
} |
1107
|
0
|
|
|
|
|
|
for my $v (values %$hash) { |
1108
|
0
|
0
|
|
|
|
|
if (ref($v) eq 'HASH') { |
1109
|
0
|
|
|
|
|
|
_fix_array_keys($v, $key); |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=head1 NAME |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
WWW::Analytics::MultiTouch - Multi-touch web analytics, using Google Analytics |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
=head1 SYNOPSIS |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
use WWW::Analytics::MultiTouch; |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
# Simple, all-in-one approach |
1123
|
|
|
|
|
|
|
WWW::Analytics::MultiTouch->process(id => $analytics_id, |
1124
|
|
|
|
|
|
|
start_date => '2010-01-01', |
1125
|
|
|
|
|
|
|
end_date => '2010-02-01', |
1126
|
|
|
|
|
|
|
filename => 'report.xls'); |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
# Or step by step |
1129
|
|
|
|
|
|
|
my $mt = WWW::Analytics::MultiTouch->new(id => $analytics_id); |
1130
|
|
|
|
|
|
|
$mt->get_data(start_date => '2010-01-01', |
1131
|
|
|
|
|
|
|
end_date => '2010-02-01'); |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
$mt->summarise(window_length => 45); |
1134
|
|
|
|
|
|
|
$mt->report(filename => 'report-45day.xls'); |
1135
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
$mt->summarise(window_length => 30); |
1137
|
|
|
|
|
|
|
$mt->report(filename => 'report-30day.xls'); |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
=head1 DESCRIPTION |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
This module provides reporting for multi-touch web analytics, as described at |
1142
|
|
|
|
|
|
|
L. |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
Unlike typical last-session attribution web analytics, multi-touch gives insight |
1145
|
|
|
|
|
|
|
into all of the various marketing channels to which a visitor is exposed before |
1146
|
|
|
|
|
|
|
finally making the decision to buy. |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
Multi-touch analytics uses a javascript library to send information from a |
1149
|
|
|
|
|
|
|
web user's browser to Google Analytics for raw data collection; this module uses |
1150
|
|
|
|
|
|
|
the Google Analytics API to collate the data and then summarises it in a |
1151
|
|
|
|
|
|
|
spreadsheet, showing (for example): |
1152
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
=over 4 |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
=item * Summary of marketing channels and number of transactions to which each channel |
1156
|
|
|
|
|
|
|
had some contribution (sum of transactions > total transactions) |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
=item * Summary of channels and fair attribution of transactions (sum of |
1159
|
|
|
|
|
|
|
transactions = total transactions) |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
=item * First touch, last touch, fifty-fifty first/last touch, and even attribution of transactions. |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
=item * Overlap analysis |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
=item * Transaction/touch distribution |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
=item * List of each transaction and the contributing channels |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
=back |
1170
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
=head1 GOOGLE ACCOUNT AUTHORISATION |
1172
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
In order to give permission for the multitouch reporting to access your data, you must follow the authorisation process. On first use, a URL will be displayed. You must click on this URL or cut and paste it into a browser, log in as the Google user that has access to the Google Analytics profile that you wish to analyse, grant permission, and paste the resulting authorisation code into the console. After this, the authorisation tokens will be stored and there should be no need to repeat the process. |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
In case you need to change user or profile or re-authenticate, see the information on the L option. |
1176
|
|
|
|
|
|
|
|
1177
|
|
|
|
|
|
|
=head1 BASIC USAGE |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
=head2 process |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
WWW::Analytics::MultiTouch->process(%options) |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
The process() function integrates all of the steps required to generate a report |
1184
|
|
|
|
|
|
|
into one, i.e. it creates a WWW::Analytics::MultiTouch object, fetches data from |
1185
|
|
|
|
|
|
|
the Google Analytics API, summarises the data and generates a report. |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
Options available are all of the options for L, L, L |
1188
|
|
|
|
|
|
|
and L. Minimum options are id, and typically start_date, |
1189
|
|
|
|
|
|
|
end_date and filename. |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
Typically the most time consuming part of the process is fetching the data from |
1192
|
|
|
|
|
|
|
Google. The process() function is suitable if only one set of parameters is to |
1193
|
|
|
|
|
|
|
be used for the reports; to generate multiple reports using, for example, |
1194
|
|
|
|
|
|
|
different attribution windows, it is more efficient to use the full API to fetch |
1195
|
|
|
|
|
|
|
the data once and then run all the needed reports. |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
=head1 METHODS |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=head2 new |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
my $mt = WWW::Analytics::MultiTouch->new(%options) |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
Creates a new WWW::Analytics::MultiTouch object. |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
Options are: |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
=over 4 |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
=item * id |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
This is the Google Analytics reporting ID. This parameter is mandatory. This is NOT the ID that you use in the javascript code! You can find the reporting id in the URL when you log into the Google Analytics console; it is the number following the letter 'p' in the URL, e.g. |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
https://www.google.com/analytics/web/#dashboard/default/a111111w222222p123456/ |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
In this example, the ID is 123456. |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
=item * auth_file |
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
This is the file in which authentication keys received from Google are kept for subsequent use. The default filename is derived from the configuration file (look for a file in the same directory as the configuration file ending in '.auth'). You may specify an alternative filename if you wish. |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
The auth_file will be created on initial usage when authorisation keys are received from Google. If you need to change the Google username, or re-authorise the software for any other reason, delete the auth_file or specify an auth_file of a different name that does not exist. Then the initial authorisation process will be repeated and a new auth_file will be created. |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
=item * event_category |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
The name of the event category used in Google Analytics to store multi-touch |
1226
|
|
|
|
|
|
|
data. Defaults to 'multitouch' and only needs to be changed if the equivalent |
1227
|
|
|
|
|
|
|
variable in the associated javascript library has been customised. |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
=item * fieldsep, recsep |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
Field and record separators for stored multi-touch data. These default to '!' |
1232
|
|
|
|
|
|
|
and '*' respectively and only need to be changed if the equivalent variables in |
1233
|
|
|
|
|
|
|
the associated javascript library has been customised. |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
=item * patsep |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
The pattern separator for turning source, medium and subcategory information |
1238
|
|
|
|
|
|
|
into a "channel" identifier. See the C option under |
1239
|
|
|
|
|
|
|
L for more information. Defaults to '-'. |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=item * channel_map |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
This is a hashref of channel name (after applying C) that maps |
1244
|
|
|
|
|
|
|
the extracted name to a more friendly name. For example, if channel_pattern is |
1245
|
|
|
|
|
|
|
'med-subcat', then direct traffic appears as '(none)-(none), organic traffic as |
1246
|
|
|
|
|
|
|
organic-(none), etc. An appropriate channel_map might be: |
1247
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
channel_map => { |
1249
|
|
|
|
|
|
|
'(none)-(none)' => 'Direct', |
1250
|
|
|
|
|
|
|
'organic-(none)' => 'Organic' |
1251
|
|
|
|
|
|
|
} |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
=item * date_format, time_format |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
The format to be used for printing dates and times, respectively, using strftime |
1257
|
|
|
|
|
|
|
patterns. See L for details. Defaults are '%d %b |
1258
|
|
|
|
|
|
|
%Y' (e.g. 1 Jan 2010) and '%Y-%m-%d %H:%M:%S' (e.g. 2010-01-01 01:00:00). |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
=item * ga_timezone, report_timezone |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
Timezone used by Google Analytics, and timezone to be used in the reports, |
1263
|
|
|
|
|
|
|
respectively. May be specified either as an Olson DB time zone name |
1264
|
|
|
|
|
|
|
("America/Chicago", "UTC") or an offset string ("+0600"). Default is UTC for both. |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
=item * revenue_scale |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
Scaling factor for revenue amounts. Useful if, for example, you wish to display |
1269
|
|
|
|
|
|
|
revenue in thousands of dollars instead of dollars. |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
=item * debug |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
Enable debug output. |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
=back |
1276
|
|
|
|
|
|
|
|
1277
|
|
|
|
|
|
|
=head2 get_data |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
$mt->get_data(%options) |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
Get data via the Google Analytics API. |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
Options are: |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
=over 4 |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
=item * start_date, end_date |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
Start and end dates respectively. The total interval includes both start and |
1290
|
|
|
|
|
|
|
end dates. Date format is YYYY-MM-DD or YYYYMMDD. (These dates are with |
1291
|
|
|
|
|
|
|
respect to the report timezone). |
1292
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
=back |
1294
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
=head2 summarise |
1296
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
$mt->summarise(%options) |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
Summarise data. |
1300
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
Options are: |
1302
|
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
=over 4 |
1304
|
|
|
|
|
|
|
|
1305
|
|
|
|
|
|
|
=item * window_length |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
The analysis window length, in days. Only touches this many days prior to any |
1308
|
|
|
|
|
|
|
given order will be included in the analysis. |
1309
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
=item * single_order_model |
1311
|
|
|
|
|
|
|
|
1312
|
|
|
|
|
|
|
If set, any touch is counted only once, toward the next order only; subsequent |
1313
|
|
|
|
|
|
|
repeat orders do not include touches prior to the initial order. |
1314
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
=item * channel_pattern |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
Each "channel" is derived from the Google source (source), Google medium (med) |
1318
|
|
|
|
|
|
|
and a subcategory (subcat) field that can be set in the javascript calls, joined |
1319
|
|
|
|
|
|
|
using the pattern separator patsep (defined in L, default '-'). |
1320
|
|
|
|
|
|
|
|
1321
|
|
|
|
|
|
|
For example, the source might be 'yahoo' or 'google' and the medium 'organic' or |
1322
|
|
|
|
|
|
|
'cpc'. To see a report on channels yahoo-organic, google-organic, google-cpc |
1323
|
|
|
|
|
|
|
etc, the channel pattern would be 'source-med'. To see the report just at the |
1324
|
|
|
|
|
|
|
search engine level, channel pattern would be 'source', and to see the report |
1325
|
|
|
|
|
|
|
just at the medium level, the channel pattern would be 'med'. |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
Arbitrary ordering is permissible, e.g. med-subcat-source. |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
The default channel pattern is 'source-med-subcat'. |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
=item * channel |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
A hashref containing channel-specific options. This is a mapping from channel |
1334
|
|
|
|
|
|
|
name (the friendly name, given in the L) to option hash. |
1335
|
|
|
|
|
|
|
|
1336
|
|
|
|
|
|
|
Currently the only option is 'requires_first_touch' (boolean). If set, a |
1337
|
|
|
|
|
|
|
transaction will only be attributed to the channel if it received the first |
1338
|
|
|
|
|
|
|
touch in the analysis window. This is mainly used to correct for |
1339
|
|
|
|
|
|
|
over-attribution to the direct channel. Example: |
1340
|
|
|
|
|
|
|
|
1341
|
|
|
|
|
|
|
{ |
1342
|
|
|
|
|
|
|
Direct => { requires_first_touch => 1 } |
1343
|
|
|
|
|
|
|
} |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
=item * adjustments |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
A hashref containing transactions and revenue corrections that may be |
1348
|
|
|
|
|
|
|
applied for a given day. This allows, for example, compensation for |
1349
|
|
|
|
|
|
|
lost data for short periods of time. A typical form of the adjustments hash is: |
1350
|
|
|
|
|
|
|
|
1351
|
|
|
|
|
|
|
{ |
1352
|
|
|
|
|
|
|
2010-09-01 => { transactions => 1.4, revenue => 1.3 } |
1353
|
|
|
|
|
|
|
} |
1354
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
which would apply correction factors of 1.4 and 1.3 for transaction counts |
1356
|
|
|
|
|
|
|
and revenue respectively for any transactions occurring on the date |
1357
|
|
|
|
|
|
|
2010-09-01. |
1358
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
=back |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
=head2 report |
1362
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
$mt->report(%options) |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
Generate reports. |
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
=head3 Report Type Options |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
=over 4 |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
=item * all_touches_report |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
If set, the generated report includes the all-touches report; enabled by |
1374
|
|
|
|
|
|
|
default. The all-touches report shows, for each channel, the total number of |
1375
|
|
|
|
|
|
|
transactions and the total revenue amount in which that channel played a role. |
1376
|
|
|
|
|
|
|
Since multiple channels may have contributed to each transaction, the total of |
1377
|
|
|
|
|
|
|
all transactions across all channels will exceed the actual number of |
1378
|
|
|
|
|
|
|
transactions. |
1379
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
=item * even_touches_report |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
If set, the generated report includes the even-touches report; enabled by |
1383
|
|
|
|
|
|
|
default. The even-touches report shows, for each channel, a number of |
1384
|
|
|
|
|
|
|
transactions and revenue amount evenly distributed between the participating |
1385
|
|
|
|
|
|
|
channels. For example, if Channel A has 3 touches and Channel B 2 touches, half |
1386
|
|
|
|
|
|
|
of the revenue/transactions will be allocated to Channel A and half to Channel |
1387
|
|
|
|
|
|
|
B. Since each individual transaction is evenly distributed across the |
1388
|
|
|
|
|
|
|
contributing channels, the total of all transactions (revenue) across all |
1389
|
|
|
|
|
|
|
channels will equal the actual number of transactions (revenue). |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
=item * distributed_touches_report |
1392
|
|
|
|
|
|
|
|
1393
|
|
|
|
|
|
|
If set, the generated report includes the distributed-touches report; enabled by |
1394
|
|
|
|
|
|
|
default. The distributed-touches report shows, for each channel, a number of |
1395
|
|
|
|
|
|
|
transactions and revenue amount in proportion to the number of touches for that |
1396
|
|
|
|
|
|
|
channel. Since each individual transaction is distributed across the |
1397
|
|
|
|
|
|
|
contributing channels, the total of all transactions (revenue) across all |
1398
|
|
|
|
|
|
|
channels will equal the actual number of transactions (revenue). |
1399
|
|
|
|
|
|
|
|
1400
|
|
|
|
|
|
|
=item * first_touch_report |
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
If set, the generated report includes the first-touch report; enabled by |
1403
|
|
|
|
|
|
|
default. The first-touch report allocates transactions and revenue to the |
1404
|
|
|
|
|
|
|
channel that received the first touch within the analysis window. |
1405
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
=item * last_touch_report |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
If set, the generated report includes the last-touch report; enabled by |
1409
|
|
|
|
|
|
|
default. The last-touch report allocates transactions and revenue to the |
1410
|
|
|
|
|
|
|
channel that received the last touch prior to the transaction. |
1411
|
|
|
|
|
|
|
|
1412
|
|
|
|
|
|
|
=item * fifty_fifty_report |
1413
|
|
|
|
|
|
|
|
1414
|
|
|
|
|
|
|
If set, the generated report includes the fifty-fifty report; enabled by |
1415
|
|
|
|
|
|
|
default. The fifty-fifty report allocates transactions and revenue equally |
1416
|
|
|
|
|
|
|
between first touch and last touch contributors. |
1417
|
|
|
|
|
|
|
|
1418
|
|
|
|
|
|
|
=item * transactions_report |
1419
|
|
|
|
|
|
|
|
1420
|
|
|
|
|
|
|
If set, the generated report includes the transactions report; enabled by default. |
1421
|
|
|
|
|
|
|
The transactions report lists each transaction and the channels that contributed |
1422
|
|
|
|
|
|
|
to it. |
1423
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
=item * touchlist_report |
1425
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
If set, the generated report includes the touchlist report; enabled by default. |
1427
|
|
|
|
|
|
|
The touchlist report lists the touches for each transaction in chronological |
1428
|
|
|
|
|
|
|
order. Note that this can be a very large amount of data compared to other reports. |
1429
|
|
|
|
|
|
|
|
1430
|
|
|
|
|
|
|
=item * transaction_distribution_report |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
If set, the generated report includes the transaction distribution report; |
1433
|
|
|
|
|
|
|
enabled by default. The transaction distribution report shows the number of |
1434
|
|
|
|
|
|
|
transactions that had one touch, two touches, etc, both by channel and as a |
1435
|
|
|
|
|
|
|
total. |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
=item * channel_overlap_report |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
If set, the generated report includes the channel overlap report; enabled by |
1440
|
|
|
|
|
|
|
default. The channel overlap report shows the number of transactions that were |
1441
|
|
|
|
|
|
|
touched by 1 channel, 2 channels, etc, and the number of transactions by channel |
1442
|
|
|
|
|
|
|
combination. |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
=back |
1445
|
|
|
|
|
|
|
|
1446
|
|
|
|
|
|
|
=head3 Report Output Options |
1447
|
|
|
|
|
|
|
|
1448
|
|
|
|
|
|
|
=over 4 |
1449
|
|
|
|
|
|
|
|
1450
|
|
|
|
|
|
|
=item * filename |
1451
|
|
|
|
|
|
|
|
1452
|
|
|
|
|
|
|
Name of file in which to save reports. If not specified, output is sent to STDOUT. The filename extension, if given, is used to determine the file format, which can be xls, csv or txt. |
1453
|
|
|
|
|
|
|
|
1454
|
|
|
|
|
|
|
=item * format |
1455
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
May be set to xls, csv or txt to specify Excel, CSV and Text format output |
1457
|
|
|
|
|
|
|
respectively. The filename extension takes precedence over this parameter. |
1458
|
|
|
|
|
|
|
|
1459
|
|
|
|
|
|
|
=back |
1460
|
|
|
|
|
|
|
|
1461
|
|
|
|
|
|
|
=head3 Report Formatting Options |
1462
|
|
|
|
|
|
|
|
1463
|
|
|
|
|
|
|
=over 4 |
1464
|
|
|
|
|
|
|
|
1465
|
|
|
|
|
|
|
=item * title |
1466
|
|
|
|
|
|
|
|
1467
|
|
|
|
|
|
|
Title to insert into reports. |
1468
|
|
|
|
|
|
|
|
1469
|
|
|
|
|
|
|
=item * column_heading_format |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
The cell format (see L) to use for column headings. |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
=item * column_formats |
1474
|
|
|
|
|
|
|
|
1475
|
|
|
|
|
|
|
An array of one or more cell formats (see L
|
1476
|
|
|
|
|
|
|
FORMATS>) to use in a round-robin manner across the columns of the data. |
1477
|
|
|
|
|
|
|
|
1478
|
|
|
|
|
|
|
=item * header_layout, footer_layout |
1479
|
|
|
|
|
|
|
|
1480
|
|
|
|
|
|
|
Page headers and footers. See L for details. |
1481
|
|
|
|
|
|
|
|
1482
|
|
|
|
|
|
|
=item * strict_integer_values |
1483
|
|
|
|
|
|
|
|
1484
|
|
|
|
|
|
|
If set, transactions and revenue will be reported in integer formats. |
1485
|
|
|
|
|
|
|
Where a reasonable number of transactions are being counted, the |
1486
|
|
|
|
|
|
|
fractional part of the transaction count in a distributed transactions |
1487
|
|
|
|
|
|
|
report is rarely of consequence, and for some the concept of a |
1488
|
|
|
|
|
|
|
fractional transaction attribution can be a distraction from the key |
1489
|
|
|
|
|
|
|
messages of these reports, so this option helps to keep it simple. |
1490
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
=item * heading_map |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
A mapping from default report headings to custom report headings. For example, |
1494
|
|
|
|
|
|
|
|
1495
|
|
|
|
|
|
|
heading_map => { |
1496
|
|
|
|
|
|
|
Transactions => 'Distributed Transactions', |
1497
|
|
|
|
|
|
|
'Revenue' => 'Distributed Revenue (US$)' |
1498
|
|
|
|
|
|
|
} |
1499
|
|
|
|
|
|
|
|
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
=back |
1502
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
For every type of report, (all_touches_report, first_touch_report, |
1504
|
|
|
|
|
|
|
transactions_report, etc), report-specific formatting options can be given in a |
1505
|
|
|
|
|
|
|
hashref with corresponding name, e.g. 'all_touches', 'first_touch', |
1506
|
|
|
|
|
|
|
'transactions'. For example, |
1507
|
|
|
|
|
|
|
|
1508
|
|
|
|
|
|
|
column_heading_format => { |
1509
|
|
|
|
|
|
|
bold => 1, |
1510
|
|
|
|
|
|
|
color => 'white', |
1511
|
|
|
|
|
|
|
bg_color => 'gray', |
1512
|
|
|
|
|
|
|
right => 'white', |
1513
|
|
|
|
|
|
|
}, |
1514
|
|
|
|
|
|
|
column_format => [ |
1515
|
|
|
|
|
|
|
{ bg_color => '#D0D0D0', }, |
1516
|
|
|
|
|
|
|
{ bg_color => '#E8E8E8', }, |
1517
|
|
|
|
|
|
|
], |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
all_touches => { |
1520
|
|
|
|
|
|
|
column_heading_format => { |
1521
|
|
|
|
|
|
|
color => 'blue' |
1522
|
|
|
|
|
|
|
} |
1523
|
|
|
|
|
|
|
} |
1524
|
|
|
|
|
|
|
|
1525
|
|
|
|
|
|
|
|
1526
|
|
|
|
|
|
|
The report-specific options are merged with the top level options and then used. |
1527
|
|
|
|
|
|
|
|
1528
|
|
|
|
|
|
|
=head2 all_touches_report |
1529
|
|
|
|
|
|
|
|
1530
|
|
|
|
|
|
|
=head2 even_touches_report |
1531
|
|
|
|
|
|
|
|
1532
|
|
|
|
|
|
|
=head2 distributed_touches_report |
1533
|
|
|
|
|
|
|
|
1534
|
|
|
|
|
|
|
=head2 first_touch_report |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
=head2 last_touch_report |
1537
|
|
|
|
|
|
|
|
1538
|
|
|
|
|
|
|
=head2 fifty_fifty_report |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
=head2 touchlist_report |
1541
|
|
|
|
|
|
|
|
1542
|
|
|
|
|
|
|
=head2 transaction_distribution_report |
1543
|
|
|
|
|
|
|
|
1544
|
|
|
|
|
|
|
=head2 channel_overlap_report |
1545
|
|
|
|
|
|
|
|
1546
|
|
|
|
|
|
|
These implement the individual reports, taking options similar to those described under L above. |
1547
|
|
|
|
|
|
|
|
1548
|
|
|
|
|
|
|
=head1 DEVELOPER METHODS |
1549
|
|
|
|
|
|
|
|
1550
|
|
|
|
|
|
|
As well as the user API methods list above, there are also a number of methods |
1551
|
|
|
|
|
|
|
that have been exposed as part of the API for developer purposes, e.g. for |
1552
|
|
|
|
|
|
|
developing subclasses to override specific functionality, or for integrating |
1553
|
|
|
|
|
|
|
into systems other than Google Analytics. |
1554
|
|
|
|
|
|
|
|
1555
|
|
|
|
|
|
|
=head2 set_data |
1556
|
|
|
|
|
|
|
|
1557
|
|
|
|
|
|
|
$mt->set_data(start_date => 20100101, |
1558
|
|
|
|
|
|
|
end_date => 20100130, |
1559
|
|
|
|
|
|
|
transactions => \%transactions, |
1560
|
|
|
|
|
|
|
); |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
Instead of invoking get_data to retrieve data from Google Analytics, it is also |
1563
|
|
|
|
|
|
|
possible to set data directly - e.g. data collected through another mechanism, |
1564
|
|
|
|
|
|
|
or data from Google Analytics that has been saved to file. set_data allows you |
1565
|
|
|
|
|
|
|
to directly specify the data for subsequent analysis. |
1566
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
Parameters 'start_date' and 'end_date' are used for reporting and should have the form YYYYMMDD. |
1568
|
|
|
|
|
|
|
|
1569
|
|
|
|
|
|
|
Parameter 'transactions' is a hash of transaction ID to [date (YMD), list of touches] (see |
1570
|
|
|
|
|
|
|
L for description). |
1571
|
|
|
|
|
|
|
|
1572
|
|
|
|
|
|
|
=head2 split_events |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
@events = $mt->split_events($cookie_value) |
1575
|
|
|
|
|
|
|
|
1576
|
|
|
|
|
|
|
Splits event label (i.e. 'multitouch' cookie value) into a list comprising order and touch arrayrefs. A touch has format |
1577
|
|
|
|
|
|
|
[ source, medium, subcat, time ] |
1578
|
|
|
|
|
|
|
and an order has format |
1579
|
|
|
|
|
|
|
[ '__ORD', transactionID, revenue, time ]. |
1580
|
|
|
|
|
|
|
|
1581
|
|
|
|
|
|
|
=head2 condition_entry |
1582
|
|
|
|
|
|
|
|
1583
|
|
|
|
|
|
|
($conditioned_key, $conditioned_touches) = $self->condition_entry($key, \@touches) |
1584
|
|
|
|
|
|
|
|
1585
|
|
|
|
|
|
|
condition_entry is called by L for each data entry retrieved from |
1586
|
|
|
|
|
|
|
Google Analytics. It is possibly useful for subclasses to override, in case any |
1587
|
|
|
|
|
|
|
special data conditioning is required. $key is the event label (transaction ID) |
1588
|
|
|
|
|
|
|
and @touches is the list of touches, each touch being an array as described |
1589
|
|
|
|
|
|
|
under L. Conditioning might include removal of duplicates, or |
1590
|
|
|
|
|
|
|
normalisation of transaction IDs. |
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
=head2 parse_config |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
$opts = WWW::Analytics::MultiTouch->parse_config($opts, $config_file) |
1595
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
Parses $config_file and merges options with $opts. |
1597
|
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
|
1599
|
|
|
|
|
|
|
=head1 RELATED INFORMATION |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
See L for further details. |
1602
|
|
|
|
|
|
|
|
1603
|
|
|
|
|
|
|
=head1 AUTHOR |
1604
|
|
|
|
|
|
|
|
1605
|
|
|
|
|
|
|
Jon Schutz, C<< >> |
1606
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
=head1 BUGS |
1608
|
|
|
|
|
|
|
|
1609
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
1610
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
1611
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
1612
|
|
|
|
|
|
|
|
1613
|
|
|
|
|
|
|
|
1614
|
|
|
|
|
|
|
=head1 SUPPORT |
1615
|
|
|
|
|
|
|
|
1616
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1617
|
|
|
|
|
|
|
|
1618
|
|
|
|
|
|
|
perldoc WWW::Analytics::MultiTouch |
1619
|
|
|
|
|
|
|
|
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
You can also look for information at: |
1622
|
|
|
|
|
|
|
|
1623
|
|
|
|
|
|
|
=over 4 |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
L |
1628
|
|
|
|
|
|
|
|
1629
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1630
|
|
|
|
|
|
|
|
1631
|
|
|
|
|
|
|
L |
1632
|
|
|
|
|
|
|
|
1633
|
|
|
|
|
|
|
=item * CPAN Ratings |
1634
|
|
|
|
|
|
|
|
1635
|
|
|
|
|
|
|
L |
1636
|
|
|
|
|
|
|
|
1637
|
|
|
|
|
|
|
=item * Search CPAN |
1638
|
|
|
|
|
|
|
|
1639
|
|
|
|
|
|
|
L |
1640
|
|
|
|
|
|
|
|
1641
|
|
|
|
|
|
|
=back |
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
|
1644
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
1645
|
|
|
|
|
|
|
|
1646
|
|
|
|
|
|
|
Copyright 2010 YourAmigo Ltd. |
1647
|
|
|
|
|
|
|
|
1648
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
1649
|
|
|
|
|
|
|
of this software and associated documentation files (the "Software"), to deal |
1650
|
|
|
|
|
|
|
in the Software without restriction, including without limitation the rights |
1651
|
|
|
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
1652
|
|
|
|
|
|
|
copies of the Software, and to permit persons to whom the Software is |
1653
|
|
|
|
|
|
|
furnished to do so, subject to the following conditions: |
1654
|
|
|
|
|
|
|
|
1655
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in |
1656
|
|
|
|
|
|
|
all copies or substantial portions of the Software. |
1657
|
|
|
|
|
|
|
|
1658
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
1659
|
|
|
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
1660
|
|
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
1661
|
|
|
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
1662
|
|
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
1663
|
|
|
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
1664
|
|
|
|
|
|
|
THE SOFTWARE. |
1665
|
|
|
|
|
|
|
|
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
=cut |
1668
|
|
|
|
|
|
|
|
1669
|
|
|
|
|
|
|
1; # End of WWW::Analytics::MultiTouch |