aboutsummaryrefslogtreecommitdiffstats
path: root/perllib
diff options
context:
space:
mode:
Diffstat (limited to 'perllib')
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm29
-rw-r--r--perllib/FixMyStreet/Integrations/ExorRDI.pm96
-rwxr-xr-xperllib/FixMyStreet/Script/UpdateAllReports.pm163
3 files changed, 238 insertions, 50 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index ed851f71f..024a23872 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -54,6 +54,15 @@ sub index : Path : Args(0) {
$c->detach( 'redirect_body' );
}
+ if (my $body = $c->get_param('body')) {
+ $body = $c->model('DB::Body')->find( { id => $body } );
+ if ($body) {
+ $body = $c->cobrand->short_name($body);
+ $c->res->redirect("/reports/$body");
+ $c->detach;
+ }
+ }
+
# Fetch all bodies
my @bodies = $c->model('DB::Body')->search({
deleted => 0,
@@ -67,15 +76,21 @@ sub index : Path : Args(0) {
$c->stash->{bodies} = \@bodies;
$c->stash->{any_empty_bodies} = any { $_->get_column('area_count') == 0 } @bodies;
- eval {
+ my $dashboard = eval {
+ my $data = File::Slurp::read_file(
+ FixMyStreet->path_to( '../data/all-reports-dashboard.json' )->stringify
+ );
+ $c->stash(decode_json($data));
+ return 1;
+ };
+ my $table = eval {
my $data = File::Slurp::read_file(
FixMyStreet->path_to( '../data/all-reports.json' )->stringify
);
- my $j = decode_json($data);
- $c->stash->{fixed} = $j->{fixed};
- $c->stash->{open} = $j->{open};
+ $c->stash(decode_json($data));
+ return 1;
};
- if ($@) {
+ if (!$dashboard && !$table) {
my $message = _("There was a problem showing the All Reports page. Please try again later.");
if ($c->config->{STAGING_SITE}) {
$message .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>'
@@ -88,7 +103,7 @@ sub index : Path : Args(0) {
$c->response->header('Cache-Control' => 'max-age=3600');
}
-=head2 index
+=head2 body
Show the summary page for a particular body.
@@ -99,7 +114,7 @@ sub body : Path : Args(1) {
$c->detach( 'ward', [ $body ] );
}
-=head2 index
+=head2 ward
Show the summary page for a particular ward.
diff --git a/perllib/FixMyStreet/Integrations/ExorRDI.pm b/perllib/FixMyStreet/Integrations/ExorRDI.pm
index b0e581f84..dab307fff 100644
--- a/perllib/FixMyStreet/Integrations/ExorRDI.pm
+++ b/perllib/FixMyStreet/Integrations/ExorRDI.pm
@@ -5,12 +5,21 @@ with 'Throwable';
has message => (is => 'ro');
+package FixMyStreet::Integrations::ExorRDI::CSV;
+
+use parent 'Text::CSV';
+
+sub add_row {
+ my ($self, $data, @data) = @_;
+ $self->combine(@data);
+ push @$data, $self->string;
+}
+
package FixMyStreet::Integrations::ExorRDI;
use DateTime;
use Moo;
use Scalar::Util 'blessed';
-use Text::CSV;
use FixMyStreet::DB;
use namespace::clean;
@@ -23,7 +32,8 @@ has user => (
is => 'ro',
coerce => sub {
return $_[0] if blessed($_[0]) && $_[0]->isa('FixMyStreet::DB::Result::User');
- FixMyStreet::DB->resultset('User')->find( { id => $_[0] } );
+ FixMyStreet::DB->resultset('User')->find( { id => $_[0] } )
+ if $_[0];
},
);
@@ -67,40 +77,21 @@ sub construct {
}
}
- my $csv = Text::CSV->new({ binary => 1, eol => "" });
+ my $csv = FixMyStreet::Integrations::ExorRDI::CSV->new({ binary => 1, eol => "" });
my $p_count = 0;
my $link_id = $cobrand->exor_rdi_link_id;
# RDI first line is always the same
- $csv->combine("1", "1.8", "1.0.0.0", "ENHN", "");
- my @body = ($csv->string);
+ my $body = [];
+ $csv->add_row($body, "1", "1.8", "1.0.0.0", "ENHN", "");
my $i = 0;
foreach my $inspector_id (keys %$inspectors) {
my $inspections = $inspectors->{$inspector_id};
my $initials = $inspector_initials->{$inspector_id};
- $csv->combine(
- "G", # start of an area/sequence
- $link_id, # area/link id, fixed value for our purposes
- "","", # must be empty
- $initials || "XX", # inspector initials
- $self->start_date->strftime("%y%m%d"), # date of inspection yymmdd
- "0700", # time of inspection hhmm, set to static value for now
- "D", # inspection variant, should always be D
- "INS", # inspection type, always INS
- "N", # Area of the county - north (N) or south (S)
- "", "", "", "" # empty fields
- );
- push @body, $csv->string;
-
- $csv->combine(
- "H", # initial inspection type
- "MC" # minor carriageway (changes depending on activity code)
- );
- push @body, $csv->string;
-
+ my %body_by_activity_code;
foreach my $report (@$inspections) {
my ($eastings, $northings) = $report->local_coords;
@@ -119,8 +110,9 @@ sub construct {
my $traffic_information = $report->get_extra_metadata('traffic_information') ?
'TM ' . $report->get_extra_metadata('traffic_information')
: 'TM none';
+ $body_by_activity_code{$activity_code} ||= [];
- $csv->combine(
+ $csv->add_row($body_by_activity_code{$activity_code},
"I", # beginning of defect record
$activity_code, # activity code - minor carriageway, also FC (footway)
"", # empty field, can also be A (seen on MC) or B (seen on FC)
@@ -131,12 +123,11 @@ sub construct {
$traffic_information,
$description, # defect description
);
- push @body, $csv->string;
my $defect_type = $report->defect_type ?
$report->defect_type->get_extra_metadata('defect_code')
: 'SFP2';
- $csv->combine(
+ $csv->add_row($body_by_activity_code{$activity_code},
"J", # georeferencing record
$defect_type, # defect type - SFP2: sweep and fill <1m2, POT2 also seen
$report->response_priority ?
@@ -147,31 +138,51 @@ sub construct {
$northings, # northings
"","","","","" # empty fields
);
- push @body, $csv->string;
- $csv->combine(
+ $csv->add_row($body_by_activity_code{$activity_code},
"M", # bill of quantities record
"resolve", # permanent repair
"","", # empty fields
- "/CMC", # /C + activity code
+ "/C$activity_code", # /C + activity code
"", "" # empty fields
);
- push @body, $csv->string;
}
- # end this group of defects with a P record
- $csv->combine(
- "P", # end of area/sequence
- 0, # always 0
- 999999, # charging code, always 999999 in OCC
- );
- push @body, $csv->string;
- $p_count++;
+ foreach my $activity_code (sort keys %body_by_activity_code) {
+ $csv->add_row($body,
+ "G", # start of an area/sequence
+ $link_id, # area/link id, fixed value for our purposes
+ "","", # must be empty
+ $initials || "XX", # inspector initials
+ $self->start_date->strftime("%y%m%d"), # date of inspection yymmdd
+ "0700", # time of inspection hhmm, set to static value for now
+ "D", # inspection variant, should always be D
+ "INS", # inspection type, always INS
+ "N", # Area of the county - north (N) or south (S)
+ "", "", "", "" # empty fields
+ );
+
+ $csv->add_row($body,
+ "H", # initial inspection type
+ $activity_code # e.g. MC = minor carriageway
+ );
+
+ # List of I/J/M entries from above
+ push @$body, @{$body_by_activity_code{$activity_code}};
+
+ # end this group of defects with a P record
+ $csv->add_row($body,
+ "P", # end of area/sequence
+ 0, # always 0
+ 999999, # charging code, always 999999 in OCC
+ );
+ $p_count++;
+ }
}
# end the RDI file with an X record
my $record_count = $i;
- $csv->combine(
+ $csv->add_row($body,
"X", # end of inspection record
$p_count,
$p_count,
@@ -183,11 +194,10 @@ sub construct {
$p_count,
0, 0, 0 # error counts, always zero
);
- push @body, $csv->string;
# The RDI format is very weird CSV - each line must be wrapped in
# double quotes.
- return join "", map { "\"$_\"\r\n" } @body;
+ return join "", map { "\"$_\"\r\n" } @$body;
}
has filename => (
diff --git a/perllib/FixMyStreet/Script/UpdateAllReports.pm b/perllib/FixMyStreet/Script/UpdateAllReports.pm
index 7b5c34290..107143aa2 100755
--- a/perllib/FixMyStreet/Script/UpdateAllReports.pm
+++ b/perllib/FixMyStreet/Script/UpdateAllReports.pm
@@ -10,6 +10,7 @@ use File::Path ();
use File::Slurp;
use JSON::MaybeXS;
use List::MoreUtils qw(zip);
+use List::Util qw(sum);
my $fourweeks = 4*7*24*60*60;
@@ -77,4 +78,166 @@ sub generate {
File::Slurp::write_file( FixMyStreet->path_to( '../data/all-reports.json' )->stringify, \$body );
}
+sub generate_dashboard {
+ my %data;
+
+ my $now = DateTime->now;
+ my $min_confirmed = FixMyStreet::DB->resultset('Problem')->search({
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
+ }, {
+ select => [ { min => 'confirmed' } ],
+ as => [ 'confirmed' ],
+ })->first->confirmed;
+
+ my ($group_by, @problem_periods);
+ if (DateTime::Duration->compare($now - $min_confirmed, DateTime::Duration->new(months => 1)) < 0) {
+ $group_by = 'day';
+ while ($min_confirmed < $now) {
+ push @problem_periods, $min_confirmed->day;
+ $min_confirmed->add(days => 1);
+ }
+ } elsif (DateTime::Duration->compare($now - $min_confirmed, DateTime::Duration->new(years => 1)) < 0) {
+ $group_by = 'month';
+ while ($min_confirmed < $now) {
+ push @problem_periods, $min_confirmed->month_abbr;
+ $min_confirmed->add(months => 1);
+ }
+ } else {
+ $group_by = 'year';
+ @problem_periods = ($min_confirmed->year..$now->year);
+ }
+
+ my %problems_reported_by_period = stuff_by_day_or_year(
+ $group_by, 'Problem',
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
+ );
+ my %problems_fixed_by_period = stuff_by_day_or_year(
+ $group_by, 'Problem',
+ state => [ FixMyStreet::DB::Result::Problem->fixed_states() ],
+ );
+
+ my (@problems_reported_by_period, @problems_fixed_by_period);
+ foreach (@problem_periods) {
+ push @problems_reported_by_period, ($problems_reported_by_period[-1]||0) + ($problems_reported_by_period{$_}||0);
+ push @problems_fixed_by_period, ($problems_fixed_by_period[-1]||0) + ($problems_fixed_by_period{$_}||0);
+ }
+ $data{problem_periods} = \@problem_periods;
+ $data{problems_reported_by_period} = \@problems_reported_by_period;
+ $data{problems_fixed_by_period} = \@problems_fixed_by_period;
+
+ my %last_seven_days = (
+ problems => [],
+ updated => [],
+ fixed => [],
+ );
+ $data{last_seven_days} = \%last_seven_days;
+
+ %problems_reported_by_period = stuff_by_day_or_year('day',
+ 'Problem',
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
+ confirmed => { '>=', \"current_timestamp-'8 days'::interval" },
+ );
+ %problems_fixed_by_period = stuff_by_day_or_year('day',
+ 'Comment',
+ confirmed => { '>=', \"current_timestamp-'8 days'::interval" },
+ problem_state => [ FixMyStreet::DB::Result::Problem->fixed_states() ],
+ );
+ my %problems_updated_by_period = stuff_by_day_or_year('day',
+ 'Comment',
+ confirmed => { '>=', \"current_timestamp-'8 days'::interval" },
+ );
+
+ my $date = DateTime->today->subtract(days => 7);
+ while ($date < DateTime->today) {
+ push @{$last_seven_days{problems}}, $problems_reported_by_period{$date->day} || 0;
+ push @{$last_seven_days{fixed}}, $problems_fixed_by_period{$date->day} || 0;
+ push @{$last_seven_days{updated}}, $problems_updated_by_period{$date->day} || 0;
+ $date->add(days => 1);
+ }
+ $last_seven_days{problems_total} = sum @{$last_seven_days{problems}};
+ $last_seven_days{fixed_total} = sum @{$last_seven_days{fixed}};
+ $last_seven_days{updated_total} = sum @{$last_seven_days{updated}};
+
+ my(@top_five_bodies);
+ $data{top_five_bodies} = \@top_five_bodies;
+
+ my $bodies = FixMyStreet::DB->resultset('Body')->search;
+ my $substmt = "select min(id) from comment where me.problem_id=comment.problem_id and (problem_state in ('fixed', 'fixed - council', 'fixed - user') or mark_fixed)";
+ while (my $body = $bodies->next) {
+ my $subquery = FixMyStreet::DB->resultset('Comment')->to_body($body)->search({
+ -or => [
+ problem_state => [ FixMyStreet::DB::Result::Problem->fixed_states() ],
+ mark_fixed => 1,
+ ],
+ 'me.id' => \"= ($substmt)",
+ 'me.state' => 'confirmed',
+ }, {
+ select => [
+ { extract => "epoch from me.confirmed-problem.confirmed", -as => 'time' },
+ ],
+ as => [ qw/time/ ],
+ rows => 100,
+ order_by => { -desc => 'me.confirmed' },
+ join => 'problem'
+ })->as_subselect_rs;
+ my $avg = $subquery->search({
+ }, {
+ select => [ { avg => "time" } ],
+ as => [ qw/avg/ ],
+ })->first->get_column('avg');
+ push @top_five_bodies, { name => $body->name, days => int($avg / 60 / 60 / 24 + 0.5) }
+ if defined $avg;
+ }
+ @top_five_bodies = sort { $a->{days} <=> $b->{days} } @top_five_bodies;
+ $data{average} = @top_five_bodies
+ ? int((sum map { $_->{days} } @top_five_bodies) / @top_five_bodies + 0.5) : undef;
+
+ @top_five_bodies = @top_five_bodies[0..4] if @top_five_bodies > 5;
+
+
+ my $last_seven_days = FixMyStreet::DB->resultset("Problem")->search({
+ confirmed => { '>=', \"current_timestamp-'7 days'::interval" },
+ })->count;
+ my @top_five_categories = FixMyStreet::DB->resultset("Problem")->search({
+ confirmed => { '>=', \"current_timestamp-'7 days'::interval" },
+ category => { '!=', 'Other' },
+ }, {
+ select => [ 'category', { count => 'id' } ],
+ as => [ 'category', 'count' ],
+ group_by => 'category',
+ rows => 5,
+ order_by => { -desc => 'count' },
+ });
+ $data{top_five_categories} = [ map {
+ { category => $_->category, count => $_->get_column('count') }
+ } @top_five_categories ];
+ foreach (@top_five_categories) {
+ $last_seven_days -= $_->get_column('count');
+ }
+ $data{other_categories} = $last_seven_days;
+
+ my $body = encode_json( \%data );
+ File::Path::mkpath( FixMyStreet->path_to( '../data/' )->stringify );
+ File::Slurp::write_file( FixMyStreet->path_to( '../data/all-reports-dashboard.json' )->stringify, \$body );
+}
+
+sub stuff_by_day_or_year {
+ my $period = shift;
+ my $table = shift;
+ my %params = @_;
+ my $results = FixMyStreet::DB->resultset($table)->search({
+ %params
+ }, {
+ select => [ { extract => \"$period from confirmed", -as => $period }, { count => 'id' } ],
+ as => [ $period, 'count' ],
+ group_by => [ $period ],
+ });
+ my %out;
+ while (my $row = $results->next) {
+ my $p = $row->get_column($period);
+ $out{$p} = $row->get_column('count');
+ }
+ return %out;
+}
+
1;