File Coverage

blib/lib/PHPLive/Report.pm
Criterion Covered Total %
statement 32 114 28.0
branch 6 18 33.3
condition n/a
subroutine 5 10 50.0
pod 2 2 100.0
total 45 144 31.2


line stmt bran cond sub pod time code
1             package PHPLive::Report;
2              
3             our $DATE = '2017-07-10'; # DATE
4             our $VERSION = '0.07'; # VERSION
5              
6 1     1   776 use 5.010;
  1         3  
7 1     1   6 use strict;
  1         2  
  1         22  
8 1     1   5 use warnings;
  1         2  
  1         29  
9 1     1   3765 use Log::ger;
  1         103  
  1         5  
10              
11             require Exporter;
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw(parse_phplive_transcript %reports %legends);
14              
15             our %SPEC;
16              
17             $SPEC{':package'} = {
18             v => 1.1,
19             summary => 'Generate reports for PHP Live!',
20             };
21              
22             our %reports = (
23             chat_report => 'Chat reports',
24             chat_report_by_dept => 'Chat report by department',
25             chat_report_by_op => 'Chat report by operator',
26             );
27              
28             our %legends = (
29             pct_rated => 'Percentage of chats that are rated by clients',
30             pct_has_transfers => 'Percentage of chats that involve a transfer of operators',
31             avg_chat_duration => 'Average chat duration, in minutes',
32             avg_rating => 'Average chat rating from clients (counted over chats that are rated only)',
33             num_chats => 'Number of chats',
34             avg_msg_lines => 'Average number of message lines in a single chat. Note that non-message lines are excluded',
35             avg_msg_words => 'Average number of words in a single chat. Note that the username prefix in message lines and non-message lines are excluded',
36             avg_msg_chars => 'Average number of characters in a single chat. Like in "avg_msg_words", the username prefix in message lines and non-message lines are excluded',
37              
38             avg_simul_chats => 'Average number of simultaneous chats held by the operator at a time',
39             );
40              
41              
42             $SPEC{parse_phplive_transcript} = {
43             v => 1.1,
44             description => <<'_',
45              
46             The `plain` column in `p_transcripts` table stores the actual chat transcript.
47             Entities characters like `<` and `>` are HTML-entities-escaped (becoming `&lt;`
48             and `&gt;`). Multiple lines are squished together into a single line. No
49             timestamp is recorded for each message.
50              
51             _
52             args => {
53             transcript => {schema=>'str*', req=>1, pos=>0},
54             },
55             args_as => 'array',
56             result_naked => 1,
57             };
58             sub parse_phplive_transcript {
59 1     1 1 107 my ($transcript) = @_;
60              
61 1         16 my @lines = split /^/m, $transcript;
62 1         3 my $num_transfers = 0;
63 1         4 my $num_msg_lines = 0;
64 1         4 my $num_msg_words = 0;
65 1         2 my $num_msg_chars = 0;
66              
67 1         3 my %operators;
68 1         4 for (@lines) {
69 30 100       108 if (/^(.+) has joined the chat\.$/) {
70 2         11 $operators{$1}++;
71 2         6 next;
72             }
73 28 100       132 if (/(.+?): (.+)/) {
74 25         58 $num_msg_lines++;
75 25         86 chomp(my $msg = $2);
76 25         337 $num_msg_words++ while $msg =~ /(\w+)/g;
77 25         89 $num_msg_chars += length($msg);
78 25         74 next;
79             }
80 3 100       13 if (/^Transferring chat to /) {
81 1         3 $num_transfers++;
82 1         3 next;
83             }
84             }
85              
86             return {
87 1         24 num_transfers => $num_transfers,
88             num_operators => scalar(keys %operators),
89             num_msg_lines => $num_msg_lines,
90             num_msg_words => $num_msg_words,
91             num_msg_chars => $num_msg_chars,
92             };
93             }
94              
95             sub _recap_transcripts {
96 0     0     my ($transcripts, $filter) = @_;
97              
98 0           my $n = 0;
99 0           my $has_transfers = 0;
100 0           my $total_msg_lines = 0;
101 0           my $total_msg_words = 0;
102 0           my $total_msg_chars = 0;
103 0           for my $k (keys %$transcripts) {
104 0           my $t = $transcripts->{$k};
105 0 0         next unless $filter->($t);
106 0           $n++;
107 0 0         $has_transfers++ if $t->{num_transfers};
108 0           $total_msg_lines += $t->{num_msg_lines};
109 0           $total_msg_words += $t->{num_msg_words};
110 0           $total_msg_chars += $t->{num_msg_chars};
111             }
112             return {
113             #num_transcripts => $n,
114 0 0         pct_has_transfers => $n == 0 ? 0 : sprintf("%.2f", $has_transfers/$n*100.0),
    0          
    0          
    0          
115             avg_msg_lines => $n == 0 ? 0 : sprintf("%.f", $total_msg_lines/$n),
116             avg_msg_words => $n == 0 ? 0 : sprintf("%.f", $total_msg_words/$n),
117             avg_msg_chars => $n == 0 ? 0 : sprintf("%.f", $total_msg_chars/$n),
118             };
119             }
120              
121             $SPEC{gen_phplive_reports} = {
122             v => 1.1,
123             summary => 'Generate reports for PHP Live!',
124             args => {
125             dbh => {schema=>'obj*', req=>1},
126             year => {schema=>'int*', req=>1},
127             month => {schema=>['int*', between=>[1,12]], req=>1},
128             },
129             result_naked=>1,
130             };
131             sub gen_phplive_reports {
132 0     0 1   require DateTime;
133              
134 0           my %args = @_;
135              
136 0           my $res;
137              
138 0           my $dbh = $args{dbh};
139 0           my $year = $args{year}+0;
140 0           my $month = $args{month}+0;
141              
142 0           my $dt = DateTime->new(year=>$year, month=>$month, day=>1);
143 0           my $ts_start_of_month = $dt->epoch;
144 0           $dt->add(months => 1)->subtract(seconds => 1);
145 0           my $ts_end_of_month = $dt->epoch;
146              
147 0           my $sql;
148             my $sth;
149              
150 0           log_debug("Parsing all transcripts ...");
151 0           $sql = <<_;
152             SELECT
153             ces,
154             opID,
155             deptID,
156             plain transcript
157             FROM p_transcripts
158             WHERE created BETWEEN $ts_start_of_month AND $ts_end_of_month
159             _
160 0           $sth = $dbh->prepare($sql);
161 0           $sth->execute;
162 0           my %transcripts; # key = ces (table PK)
163 0           while (my $row = $sth->fetchrow_hashref) {
164 0           my $res = parse_phplive_transcript($row->{transcript});
165             # insert this so we can recapitulate on a per-department/per-operator
166             # basis
167 0           $res->{opID} = $row->{opID};
168 0           $res->{deptID} = $row->{deptID};
169 0           $transcripts{$row->{ces}} = $res;
170             }
171              
172 0           log_debug("Preparing chat reports ...");
173 0           my $sql_cr = <<_;
174             COUNT(*) num_chats,
175             ROUND(AVG(t.ended-t.created)/60, 1) avg_chat_duration,
176             IF(COUNT(*)=0,0,ROUND(SUM(IF(t.rating>0,1,0))/COUNT(*)*100.0,2)) pct_rated,
177             IF(SUM(IF(t.rating>0,1,0))=0,0,ROUND(SUM(t.rating)/SUM(IF(t.rating>0,1,0)),2)) avg_rating
178             _
179 0           $sql = <<_;
180             SELECT
181             $sql_cr
182             FROM p_transcripts t
183             WHERE created BETWEEN $ts_start_of_month AND $ts_end_of_month
184             _
185 0           $sth = $dbh->prepare($sql);
186 0           $sth->execute;
187             {
188 0           my @rows;
  0            
189 0           while (my $row = $sth->fetchrow_hashref) {
190 0           push @rows, $row;
191 0     0     my $tres = _recap_transcripts(\%transcripts, sub{1});
  0            
192 0           $row->{$_} = $tres->{$_} for keys %$tres;
193             }
194 0           $res->{chat_report} = \@rows;
195             }
196              
197 0           log_debug("Preparing per-department chat reports ...");
198 0           $sql = <<_;
199             SELECT
200             t.deptID deptID,
201             (SELECT name FROM p_departments WHERE deptID=t.deptID) deptName,
202             $sql_cr
203             FROM p_transcripts t
204             WHERE created BETWEEN $ts_start_of_month AND $ts_end_of_month
205             GROUP BY t.deptID
206             _
207 0           $sth = $dbh->prepare($sql);
208 0           $sth->execute;
209             {
210 0           my @rows;
  0            
211 0           while (my $row = $sth->fetchrow_hashref) {
212 0           push @rows, $row;
213             my $tres = _recap_transcripts(
214 0     0     \%transcripts, sub{shift->{deptID} == $row->{deptID}});
  0            
215 0           $row->{$_} = $tres->{$_} for keys %$tres;
216             # so they are the first/leftmost columns
217 0           $row->{'00deptID'} = $row->{deptID};
218 0           delete $row->{deptID};
219 0           $row->{'00deptName'} = $row->{deptName};
220 0           delete $row->{deptName};
221             }
222 0           $res->{chat_report_by_dept} = \@rows;
223             }
224              
225 0           log_debug("Preparing per-operator chat reports ...");
226 0           $sql = <<_;
227             SELECT
228             t.opID opID,
229             (SELECT name FROM p_operators WHERE opID=t.opID) opName,
230             $sql_cr
231             FROM p_transcripts t
232             WHERE created BETWEEN $ts_start_of_month AND $ts_end_of_month
233             GROUP BY t.opID
234             _
235 0           $sth = $dbh->prepare($sql);
236 0           $sth->execute;
237             {
238 0           my @rows;
  0            
239 0           while (my $row = $sth->fetchrow_hashref) {
240 0           push @rows, $row;
241             my $tres = _recap_transcripts(
242 0     0     \%transcripts, sub{shift->{opID} == $row->{opID}});
  0            
243 0           $row->{$_} = $tres->{$_} for keys %$tres;
244             # so they are the first/leftmost columns
245 0           $row->{'00opID'} = $row->{opID};
246 0           delete $row->{opID};
247 0           $row->{'00opName'} = $row->{opName};
248 0           delete $row->{opName};
249             }
250 0           $res->{chat_report_by_op} = \@rows;
251             }
252              
253 0           $res;
254             }
255              
256             1;
257             # ABSTRACT: Generate reports for PHP Live!
258              
259             __END__
260              
261             =pod
262              
263             =encoding UTF-8
264              
265             =head1 NAME
266              
267             PHPLive::Report - Generate reports for PHP Live!
268              
269             =head1 VERSION
270              
271             This document describes version 0.07 of PHPLive::Report (from Perl distribution PHPLive-Report), released on 2017-07-10.
272              
273             =head1 SYNOPSIS
274              
275             Use the included L<gen-phplive-reports> to generate HTML report files.
276              
277             =head1 DESCRIPTION
278              
279             PHP Live! is a web-based live chat/live support application,
280             L<http://www.phplivesupport.com/>. As of this writing, version 4.4.7, the
281             reports it generates are quite limited. This module produces additional reports
282             for your PHP Live! installation.
283              
284             =head1 FUNCTIONS
285              
286              
287             =head2 gen_phplive_reports
288              
289             Usage:
290              
291             gen_phplive_reports(%args) -> any
292              
293             Generate reports for PHP Live!.
294              
295             This function is not exported.
296              
297             Arguments ('*' denotes required arguments):
298              
299             =over 4
300              
301             =item * B<dbh>* => I<obj>
302              
303             =item * B<month>* => I<int>
304              
305             =item * B<year>* => I<int>
306              
307             =back
308              
309             Return value: (any)
310              
311              
312             =head2 parse_phplive_transcript
313              
314             Usage:
315              
316             parse_phplive_transcript($transcript) -> any
317              
318             The C<plain> column in C<p_transcripts> table stores the actual chat transcript.
319             Entities characters like C<< E<lt> >> and C<< E<gt> >> are HTML-entities-escaped (becoming C<&lt;>
320             and C<&gt;>). Multiple lines are squished together into a single line. No
321             timestamp is recorded for each message.
322              
323             This function is not exported by default, but exportable.
324              
325             Arguments ('*' denotes required arguments):
326              
327             =over 4
328              
329             =item * B<$transcript>* => I<str>
330              
331             =back
332              
333             Return value: (any)
334              
335             =head1 HOMEPAGE
336              
337             Please visit the project's homepage at L<https://metacpan.org/release/PHPLive-Report>.
338              
339             =head1 SOURCE
340              
341             Source repository is at L<https://github.com/perlancar/perl-PHPLive-Report>.
342              
343             =head1 BUGS
344              
345             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=PHPLive-Report>
346              
347             When submitting a bug or request, please include a test-file or a
348             patch to an existing test-file that illustrates the bug or desired
349             feature.
350              
351             =head1 AUTHOR
352              
353             perlancar <perlancar@cpan.org>
354              
355             =head1 COPYRIGHT AND LICENSE
356              
357             This software is copyright (c) 2017, 2015, 2014 by perlancar@cpan.org.
358              
359             This is free software; you can redistribute it and/or modify it under
360             the same terms as the Perl 5 programming language system itself.
361              
362             =cut