#!/usr/bin/env perl
=begin -------------------------------------------------------------------------
1) function to email reports to:
a) location options:
* specific hospital department (eg Pinderfields/Haem)
* specific organisation department (eg Leeds/Haem)
* a hospital (all departments)
* an organisation (all departments)
b) request status options:
* new
* all
2) generates PDF of non-emailed & non-trial reports for manual printing
3) emails list of clinical trial reports
4) replaces crons/daily/email_reports.pl (LEEDS only) & crons/daily/print_run.pl
* ensure fastcgi started if run on test system (http://localhost for charts)
how to deal with reports where a departmental contact exists, and a
hospital/organisation contact (do they also get the dept report?), and also a
CWT contact (usually needs all 'new' reports)
* can manually run script using --re-run flag to dispatch any unsent reports
on same day: perl reports.pl --re-run >> ~/crons/cron.log 2>&1
--------------------------------------------------------------------------------
=cut
#===============================================================================
my @recipients = qw(hmds.secure raj); # for clinical trial list
#===============================================================================
BEGIN {
use lib '/home/raj/perl5/lib/perl5'; # *must* come before FindBin($Bin)
use FindBin qw($Bin); # warn $Bin; exit;
use lib ("$Bin/../../../lib", '/home/raj/perl-lib');
# override default db test:
$ENV{ROSEDB_DEVINIT} = "$Bin/../../../config/rosedb_devinit_prod.pl";
}
my($opt_d,$opt_t,$rerun);
use Getopt::Long;
GetOptions (
"days|d=s" => \$opt_d, # string
"testing|t" => \$opt_t, # flag
"re-run" => \$rerun, # flag
); # warn $opt_d; warn $opt_t; warn $rerun; exit;
# emails only 'service_email' addr in config, dumps history data to lims_test db:
my $JUST_TESTING = $opt_t || 0;
use IO::All;
use File::Slurp;
use Config::Auto;
use Modern::Perl;
use Data::Printer;
use SQL::Abstract::More;
use DateTime::Format::MySQL;
use LIMS::Local::Reports;
use LIMS::Local::ScriptHelpers;
my $cfg_file = "$Bin/../lib/settings.cfg";
my $settings = Config::Auto::parse($cfg_file); # p $settings;
# get tools from LIMS::Local::ScriptHelpers:
my $tools = LIMS::Local::ScriptHelpers->new();
$tools->test_only($JUST_TESTING);
my $sql_lib = $tools->sql_lib();
my $config = $tools->config();
my $today = $tools->time_now();
my $dbix = $tools->dbix;
# calculate dates --------------------------------------------------------------
# delta for how many days to subtract (default 1), adjust if $opt_d supplied:
my $delta = $opt_d || 1; # warn $delta; # days ago (1 for production)
# ref_date (normally yesterday):
my $ref_date = $tools->date_subtract( days => $delta ); # warn $ref_date;
# get last working day from ref_date:
my $lwd = LIMS::Local::Utils::last_working_date($ref_date); # warn $lwd;
#-------------------------------------------------------------------------------
# substitutes LIMS methods required by Role::RecordHandler:
my $lims = LIMS::Local::Reports->new({tools => $tools});
my $sqla = SQL::Abstract::More->new;
# localhost http address - for charts; is_print_request for tt formatting:
$lims->tt_params(
app_url => 'http://localhost/hilis4',
is_print_request => 1, # otherwise it loads guest view template by default
);
# common vars for email message:
my %mail = ( config => $config );
# load exemptions - never printed or emailed:
my $skip_reports = $tools->get_yaml_file('skip_email_reports') || {}; # hashref or 0
my $service_user = $tools->get_server_user_details();
# go:
#===============================================================================
# send reports by email; runs every day:
do_email_reports();
#===============================================================================
# generate PDF of any non-emailed, non-trial reports, for printing as PrintRun
# function; runs only on weekdays:
do_print_run_pdf() if $today->day_of_week() ~~ [1 .. 5]; # Mon - Fri
#===============================================================================
# generate list of trial cases for email; runs only on weekdays:
do_clinical_trials_list() if $today->day_of_week() ~~ [1 .. 5]; # Mon - Fri
#===============================================================================
#------------------------------------------------------------------------------
sub do_email_reports {
# get all requests authorised/modified yesterday as AoH [request_id, status,
# referral_source_id, department_code, parent_organisation_id, trial_name]
my $requests = get_requests($ref_date); # p $requests; # AoH
# get list of report email contact details as AoH:
my $contacts = get_contacts(); # p $contacts; # AoH
my @request_cols = qw( id status ref_src_id department_code parent_id );
my @contact_cols
= qw( scope contact_address parent_id department_id source_id status );
# for each contact address, assign any matching request ids from $requests
# involves looping through each request for each contact (but very fast):
for my $contact (@$contacts) {
my ($scope, $email, $parent_id, $dept_id, $location_id, $want_status)
= @{$contact}{@contact_cols};
my @request_ids = (); # list of request id's for this contact address
REQ:
for my $req (@$requests) {
my ($req_id, $req_status, $req_refsrc_id, $req_department_code,
$req_parent_id) = @{$req}{@request_cols};
# first deal with request status (requires new diagnoses or not):
if ( $want_status eq 'new' ) {
next REQ unless $req_status eq 'new';
}
# next deal with contacts for specific departments:
if ( $dept_id ) { # 823, 824, etc
next REQ unless $req_department_code == $dept_id;
}
# contact 'scope' is either hospital or organisation:
if ( $scope eq 'hospital' ) {
next REQ unless $req_refsrc_id == $location_id;
}
else { # scope = 'organisation'
next REQ unless $req_parent_id == $parent_id;
}
if ($rerun) { # manual command using --re-run flag
# has report already been sent today?
my $dispatched = $dbix->select('request_dispatch_log', 1,
{
'DATE(time)' => $today->ymd,
request_id => $req_id,
recipient => $email,
})->list;
next REQ if $dispatched;
}
# now have required combination of hospital/organisation + optional dept:
push @request_ids, $req_id;
}
# send email address & list of request_id's to function for creating pdf's:
generate_reports($email, \@request_ids);
}
}
#------------------------------------------------------------------------------
sub generate_reports { # creates pdf's for handing to dispatch_report()
my ($email, $request_ids) = @_; # contact address, [ request.ids ]
=begin #========================================================================
# for testing using known requests:
$email = 'hmds.lth@nhs.net';
$request_ids = [
# 307257 # NEQAS request - anonymised patient
# 308979 # PenENRICH trial - unknown referrer
# 309256 # Imatinib chart
# 308502 # Outreach
];
#===============================================================================
=cut
# get pdf's as in-memory object for each contact - may involve compiling some
# reports more than once but alternative is save to disk and clean up after
REQ:
for my $req_id (@$request_ids) {
my $pdf = $lims->format_report($req_id); # L::C::Roles::RecordHandler
# $pdf > io("$Bin/$req_id.pdf"); warn 'for test only'; next REQ;
$mail{attachment} = $pdf;
# retrieve request data object stashed in Recordhandler::_format_report():
my $data = $lims->stash->{request_data};
{ # add subject line:
my $referrer_department = $data->referrer_department;
my $subject = sprintf 'HMDS Report for %s / %s',
$referrer_department->hospital_department->display_name, # speciality
$referrer_department->referrer->name; # referrer
# doesn't work properly if unknown clinician - uses referrer_department.id 31116:
if ( $subject =~ /Unknown clinician/ ) { # "Unknown/Other / Unknown clinician"
$subject = 'HMDS Report [referrer unknown]'
}
$mail{subject} = $subject;
}
{ # add filename:
my $filename = sprintf '%02d_%05d.pdf',
$data->year - 2000,
$data->request_number;
$mail{filename} = $filename;
} # p \%mail;
# send report to recipient:
$mail{recipient} = $email; # p \%mail;
dispatch_report({ request_id => $req_id, message => \%mail });
}
}
#------------------------------------------------------------------------------
sub do_print_run_pdf {
my $pdf_title = ( $lwd->ymd eq $ref_date->ymd )
? sprintf ( 'Print run [%s]', $ref_date->dmy('.') )
: sprintf ( 'Print run [%s - %s]', $lwd->dmy('.'), $ref_date->dmy('.') ); # p $pdf_title;
my $pdf_name = 'printrun_' . $today->ymd . '.pdf'; # p $pdf_name;
my $pdf_path = $JUST_TESTING ? '/tmp' : '/backups/print_run'; # p $pdf_path;
my $pdf = PDF::API2->new(-file => join '/', $pdf_path, $pdf_name);
$pdf->info( Title => $pdf_title );
my $add_new_pdf = sub {
my $data = shift;
my $pdf_string = $lims->format_print_run_report($data);
my $api = PDF::API2->open_scalar($pdf_string);
$pdf->importpage($api, $_) for (1 .. $api->pages);
};
my $ref = get_request_ids('non-trial'); # href of request_ids & ccs
for my $request_id ( @{ $ref->{request_ids} } ) { # warn $request_id; # next;
my $req = $lims->get_single_request_data($request_id); # L::C::Roles::RecordHandler
&$add_new_pdf($req);
}
for my $request_id ( @{ $ref->{ccs} } ) {
my $req = $lims->get_single_request_data($request_id);
my $o = $req->{data};
$o->referrer_department->referrer->name('cc'); # replace referrer name
&$add_new_pdf($req);
}
$pdf->save; # to $pdf_path
}
#------------------------------------------------------------------------------
sub do_clinical_trials_list {
my $ref = get_request_ids('clinical-trial'); # href of request_ids & ccs (don't need)
my @rows;
for my $request_id ( @{ $ref->{request_ids} } ) { # warn $request_id; # next;
my $req = $lims->get_single_request_data($request_id); # L::C::Roles::RecordHandler
my $data = $req->{data}; # RDBO
push @rows, sprintf '%s :: %s/%s :: %s',
$data->request_trial->trial->trial_name,
$data->request_number,
$data->year - 2000,
$data->patient_case->referral_source->display_name;
}
if (@rows) {
my $subject = sprintf 'Clinical trial reports [%s]',
( $lwd->ymd eq $ref_date->ymd )
? sprintf $ref_date->dmy('.')
: join ' to ', $lwd->dmy('.'), $ref_date->dmy('.'); # p $subject;
my %mail = (
config => $config,
subject => $subject,
message => join "\n", @rows,
); # p %mail;
$tools->send_mail(\%mail, \@recipients);
}
}
#------------------------------------------------------------------------------
sub get_contacts {
my @cols = qw(
ec.display_name
ec.referral_source_id|source_id
rs.parent_organisation_id|parent_id
ec.scope
ec.department_id
ec.contact_address
ec.status
);
my @rels = qw(
email_contacts|ec ec.referral_source_id=rs.id referral_sources|rs
);
my @args = (
-columns => \@cols,
-from => [ -join => @rels ],
-where => { 'ec.type' => 'report', 'ec.is_active' => 'yes' },
); # p @args;
my ($sql, @bind) = $sqla->select(@args); # p $sql; # p \@bind;
# $dbix->dump_query($sql, @bind); exit;
my $ref = $dbix->query($sql, @bind)->hashes;
return $ref;
}
#------------------------------------------------------------------------------
sub get_request_ids { # used by do_print_run_pdf() & do_clinical_trials_list()
my $type = shift;
my %h = (
start_date => $lwd,
end_date => $ref_date,
type => $type,
);
my ($request_ids, $ccs) = $lims->model('PrintRun')->get_print_run_request_ids(\%h);
return { request_ids => $request_ids, ccs => $ccs };
}
#------------------------------------------------------------------------------
sub get_requests {
my $start_date = shift;
my $end_date = shift; # optional, absence implies single day, presence = date range
my @cols = qw(
r.id
rr.status
rs.id|ref_src_id
hd.id|department_code
rs.parent_organisation_id|parent_id
ct.trial_name
);
my @rels = (
'requests|r' => 'ris.request_id=r.id' ,
'request_initial_screen|ris' => 'ris.screen_id=s.id' ,
'screens|s' => 'r.patient_case_id=pc.id' ,
'patient_case|pc' => 'pc.referral_source_id=rs.id' ,
'referral_sources|rs' => 'r.referrer_department_id=rd.id' ,
'referrer_department|rd' => 'rd.hospital_department_code=hd.id' ,
'hospital_departments|hd' => 'rr.request_id=r.id' ,
'request_report_view|rr' => 'r.status_option_id=so.id' ,
'status_options|so' => '=>rt.request_id=r.id' ,
'request_trial|rt' => '=>rt.trial_id=ct.id' ,
'clinical_trials|ct'
);
my $date_restriction = $end_date # implies date range
? { -between => [ $start_date->ymd, $end_date->ymd ] }
: $start_date->ymd;
my %where = (
'DATE(rr.updated_at)' => $date_restriction,
'so.description' => { -in => [ qw(authorised complete) ] },
);
# skip specified screened_as/presentations:
if ( my $presentation = $skip_reports->{presentation} ) { # aref
$where{'s.description'} = { -not_in => $presentation };
}
# skip specified clinical trials:
if ( my $trials = $skip_reports->{clinical_trial} ) { # aref
$where{'ct.trial_name'} = # needs an undef or lose all non-ct entries
[ -or => { -not_in => $trials }, undef ];
}
my @args = (
-columns => \@cols,
-from => [ -join => @rels ],
-where => \%where,
);
my ($sql, @bind) = $sqla->select(@args); # p $sql; # p \@bind;
# $dbix->dump_query($sql, @bind); # exit;
my $ref = $dbix->query($sql, @bind)->hashes; # AoH
return $ref;
}
#------------------------------------------------------------------------------
sub dispatch_report {
my $args = shift;
my $request_id = $args->{request_id};
my $message = $args->{message};
# substitute recipient address here if $JUST_TESTING (L::L::Mail doesn't any more):
if ($JUST_TESTING) {
my $safe_address = $config->{service_email};
die "unsafe recipient address $safe_address"
unless $safe_address =~ /\@nhs.net\Z/;
$message->{recipient} = $safe_address;
}
my $result = LIMS::Model::Email->send_message($message); # Return::Value
my $centre = $ENV{CENTRE} || 'leeds';
if ( $result->type eq 'success' ) {
my $recipient = $message->{recipient};
my $filename = $message->{filename};
my $str = $result->string; # rs = 'message sent'; can't substitute direct on rs, so:
$str =~ s/message/$filename [$centre]/i; # str = eg '12_01234.pdf [uclh] sent'
printf "%s %s to %s\n", $tools->script_filename, $str, $recipient;
my %data = (
request_id => $request_id,
recipient => $recipient,
);
# log details in request_dispatch_log & request_history tables:
unless ($JUST_TESTING) {
my $rtn = $lims->model('Request')->update_request_dispatch_log(\%data);
warn "Error in $0: $rtn" if $rtn;
}
else { # manual input into lims_test db:
$dbix->insert('lims_test.request_dispatch_log', \%data);
$data{user_id} = $service_user->{id};
$data{action} = 'dispatched report to ' . $recipient;
delete $data{recipient};
$dbix->insert('lims_test.request_history', \%data);
}
}
else {
warn $result->string;
$tools->mail_admin({ script => $0, msg => $result->string });
}
}