#!/usr/bin/perl =begin ------------------------------------------------------------------------- generates 3 files for results server and ppm: *.001 = new authorisations *.002 = diagnosis updates; new/revised results summary (post-authorisation) *.003 = previous reports for changed pid details includes: results: RR8 cases only, with NHS/GUM number ppm: RR8, RWY & RAE (not GUM cases) excludes: * screened as Chimerism samples (CHI[ABM]), Molecular miscellaneous & Community monitoring * null NHS Number unless FName = LGI run every day via cron - does not generate file if no reports so can run over weekend/public hols *** UPLOADED BY RESULTS SERVER AT 7AM - CRON NEEDS TO COMPLETE BEFOREHAND PPM FTP server connection problems handled by non-fatal error() so results feed can complete. =cut --------------------------------------------------------------------------- use strict; use warnings; my $JUST_TESTING = 0; # skips ftp, puts data files in /tmp with .PPM & .RESULTS suffixes ################################################################################ my $results_active = 1; # activate/deactivate results server feed my $ppm_active = 1; # activate/deactivate ppm server feed my $delta = 1; # days for date_sub calculations ################################################################################ use lib '/home/raj/perl5/lib/perl5'; use IO::All; use Config::Auto; use Data::Dumper; use DateTime::Format::MySQL; use Text::Wrap qw($columns fill); $columns = 68; use FindBin qw($Bin); # warn $Bin; use lib "$Bin/../../../lib"; use LIMS::Local::ScriptHelpers; my $cfg_file = "$Bin/../lib/settings.cfg"; my $settings = Config::Auto::parse($cfg_file); # warn Dumper $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 $dbix = $tools->dbix(); my $today = $tools->time_now(); # warn $today->dmy; # what date to get data from: my $ref_date = $tools->date_subtract( days => $delta )->ymd; # warn $ref_date; # global var to hold new cases request.ids for use in revisions ('NOT IN') my $AUTHORISED_IDS; # defined in set_authorised_ids() # options for 3 data files # rh = request_history; rdh = request_diagnosis_history; p = patients; # rrs = request_results_summaries my %opts = ( new_cases => { where => { "DATE_FORMAT(rh.`time`, '%Y-%m-%d')" => $ref_date, }, file_id => 1, }, revisions => { where => { 'r.id' => { -not_in => get_authorised_ids() }, # will be set in new_cases -or => [ "DATE_FORMAT(rdh.`time`, '%Y-%m-%d')" => $ref_date, "DATE_FORMAT(rrs.`time`, '%Y-%m-%d')" => $ref_date, ], }, file_id => 2, }, demographics => { where => { "DATE_FORMAT(rh.`time`, '%Y-%m-%d')" => { '<' => $ref_date }, "DATE_FORMAT(p.`updated_at`, '%Y-%m-%d')" => $ref_date, }, file_id => 3, }, ); my $file_header = sprintf $settings->{header}, $today->day, $today->month, $today->year, $today->hour, $today->min; my $file_footer = join "\n", @{ $settings->{footer} }; my $data_filename = sprintf 'hmds_%s%02d%02d', $today->year, $today->month, $today->day; my $query = $sql_lib->retr('results_and_ppm'); # go: DATAFILE: # for my $type ( sort by_fileId keys %opts ) { warn $type; # hash order now matters - revisions MUST come AFTER new_cases: for my $type ( qw/ new_cases revisions demographics / ) { # warn $type; my $vals = $opts{$type}; # query much faster passing list of request_ids to 'WHERE req.id IN (??)': my $request_ids = _get_request_ids($vals->{where}); # arrayref # set list of new cases request.ids for use in revisions ('NOT IN'): set_authorised_ids($request_ids) if $type eq 'new_cases'; next DATAFILE unless @$request_ids; # get data maps - much faster than tying vertical tables into main query: my $data_map = _get_data_map($request_ids); # warn Dumper $data_map; my %data = ( # reset: results => undef, # not strictly required, but to show data keys ppm => undef, ); my $result = $dbix->query($query, @$request_ids); ROW: while (my $vars = $result->hash) { # warn Dumper $vars; next; my $is_gum_request = ( # so ppm can skip GUM requests lc $vars->{first_name} eq 'lgi' && $vars->{clinical_details} eq 'HIV' ); # some data fields require manipulation for template: _process_data($vars, $data_map); # warn Dumper $vars; # results server requires only LTH cases, with nhs_number or GUM id: if ( $vars->{organisation_code} =~ /^RR8/ && ( $vars->{nhs_number} || $is_gum_request ) ) { my $dataset = $tools->process_template('cron/results.tt', $vars); push @{ $data{results} }, $dataset; # warn $dataset; } # ppm doesn't want GUM samples but does want null NHS numbers: unless ($is_gum_request) { my $dataset = $tools->process_template('cron/ppm.tt', $vars); push @{ $data{ppm} }, $dataset; # warn $dataset; } } # warn Dumper \%data; # PPM data: if ( $data{ppm} && $ppm_active ) { my $ppm_data = join "\n", @{ $data{ppm} }; my $file = sprintf '%s.%03d', $data_filename, $vals->{file_id}; my $file_path = '/tmp/' . $file; if ($JUST_TESTING) { $file_path .= '.PPM' } # add suffix for testing my $contents = join "\n", $file_header, $ppm_data, $file_footer; $contents > io($file_path); # warn $contents; # ftp ppm file: unless ($JUST_TESTING) { my %args = ( local_filename => $file_path, remote_filename => $file, ); _ftp_file(\%args); } } # results data: if ( $data{results} && $results_active ) { my $results_data = join "\n", @{ $data{results} }; # set tmp file if in test mode: my $path = $JUST_TESTING ? '/tmp' : $settings->{results_server}->{path_to_datafile}; my $file = sprintf '%s/%s.%03d', $path, $data_filename, $vals->{file_id}; if ($JUST_TESTING) { $file .= '.RESULTS' } # add suffix for testing my $contents = join "\n", $file_header, $results_data, $file_footer; $contents > io($file); # warn $contents; chmod 0664, $file; # so 'hilis' user can delete } } # archive results file: unless ($JUST_TESTING) { my $path_to_datafile = $settings->{results_server}->{path_to_datafile}; my $path_to_archive = $settings->{results_server}->{path_to_archive}; # make sure it exists otherwise get error trying to tar non-existant file if (grep -e "$path_to_datafile/$data_filename.00$_", (1..3) ) { my $tar_file = sprintf '%s/%s.%02d.%02d.tar', $path_to_archive, $today->year, $today->month, $today->day; chdir $path_to_datafile; # so we can use relative path for source directory system( sprintf 'tar -cf %s %s.*', $tar_file, $data_filename ); # using relative $data_file.* not full path system( sprintf "gzip $tar_file" ); # compress tar file } } # sub by_fileId { return $opts{$a}->{file_id} <=> $opts{$b}->{file_id} } # copy values into $AUTHORISED_IDS arrayref (rather than overwriting it): sub set_authorised_ids { @{$AUTHORISED_IDS} = @{ $_[0] } } sub get_authorised_ids { $AUTHORISED_IDS ||= [0] } # needs an arrayref with content # some $vars data fields require manipulation for template: sub _process_data { my ($vars, $data_map) = @_; my $request_id = $vars->{request_id}; my $specimen_code = $data_map->{specimen}->{$request_id}->{specimen_code}; $vars->{request_history} = $data_map->{history}->{$request_id}; $vars->{specimen_details} = $data_map->{specimen}->{$request_id}->{description}; $vars->{specimen_summary} = _specimen_summary($specimen_code); # re-arrange HIV's data: if ( $vars->{last_name} =~ /^\d+$/ && uc $vars->{first_name} eq 'LGI' ) { $vars->{unit_number} = 'GU' . $vars->{unit_number}; $vars->{first_name} = $vars->{last_name}; } { # remove 'Unknown clinician/practitioner': $vars->{referrer} = '' if $vars->{referrer_code} =~ /[CG]9999998/; } if ($vars->{referrer}) { # re-format referrer field to add comma (ppm requires, results doesn't care): my @ary = split ' ', $vars->{referrer}; my $inits = pop @ary; # remove 'initials' field $vars->{referrer} = join ', ', ( join ' ', @ary ), $inits; # warn $vars->{referrer}; } { # ensure no line > 68 chars, wrap text, replace blanks, etc: my $formatted_fields = $settings->{formatted_fields}; map $vars->{$_} = _format_text($vars->{$_}), @$formatted_fields; } { # generate composite result from lab_section results_summaries: my $result_summary_data = $data_map->{result_summary}->{$request_id}; # warn Dumper $result_summary_data; my @results = map { _format_text( $_ . ': ' . $result_summary_data->{$_} ); } keys %$result_summary_data; $vars->{results_summary} = ( join $settings->{new_line}, @results ) || $settings->{indent} . 'N/A'; } } # format text: indent, replace blanks with N/A, max 68 chars / line: sub _format_text { my $str = shift || ''; # needs to exist or uninitialized warnings below # put '07|' after of new-line markers: my $formatted_newline = $settings->{new_line}; $str =~ s/^(\s+|\t)//; # remove leading spaces or tabs $str =~ s/\|//g; # remove pipes if used!! $str ||= 'N/A'; # only applies to @formatted_fields, others already grepped my $wrapped_text = fill($settings->{indent}, $settings->{indent}, $str); # Text::Wrap function # new 07| in front of newly-wrapped lines: $wrapped_text =~ s/\n/$formatted_newline/g; # print $wrapped_text; return $wrapped_text; } sub _specimen_summary { local $_ = shift; # specimen string my @title; push @title, 'peripheral blood' if /PB|CMP/; # CMP excluded push @title, 'chimerism' if /CHI/; # currently excluding chimerism screen push @title, 'bone marrow aspirate' if /BMA$|BMA\,/; # BMA[^T] doesn't work push @title, 'bone marrow biopsy' if /BMAT|T[B|S][L|P]/; push @title, 'tissue biopsy' if /[DGLRX][BL|F|SL|U|A]/; push @title, 'effusion' if /EF/; push @title, 'CSF' if /CF/; push @title, 'slide' if /HS/; push @title, 'serum' if /SE/; push @title, 'urine' if /URI/; # my $title = @title > 1 ? join (' & ' => join (', ' => @title[ 0 .. @title - 2 ]), $title[-1]) : $title[0]; my $title = join '/', @title; # print Dumper ($title, length $title) if length $title > 40; $title ||= $_; # so var not empty if specimen doesn't match in regex's return ( length $title < 41 ) ? $title : 'multiple blood/marrow/tissue specimens'; } sub _get_request_ids { my $restriction = shift; my $excludes = $settings->{not_ins}; # arrayref my $org_codes = $settings->{org_codes}; # arrayref # add common restrictions: $restriction->{'rh.`action`'} = 'authorised'; # only allow auth'd requests $restriction->{'s.description' } = { 'NOT IN' => $excludes }; $restriction->{'po.parent_code'} = { IN => $org_codes }; my ($where, @bind) = $dbix->abstract->where($restriction); my $stmt = $sql_lib->retr('results_and_ppm_request_ids'); my $sql = $stmt . $where; # warn $sql; warn Dumper \@bind; my $request_ids = $dbix->query($sql, @bind)->flat; # warn Dumper $request_ids; return $request_ids; } sub _get_data_map { my $request_ids = shift; my $map = {}; { # request_specimens: my $sql = q!select `request_id`, group_concat(`description` separator '/') as 'description', group_concat(`sample_code`) as 'code' from `request_specimen` rs join `specimens` s on rs.`specimen_id` = s.`id` where rs.`request_id` in (??) group by `request_id`!; my $result = $dbix->query($sql, @$request_ids); while ( my $vars = $result->hash ) { # warn Dumper $vars; my $request_id = $vars->{request_id}; my %data = ( specimen_code => $vars->{code}, description => $vars->{description}, ); $map->{specimen}->{$request_id} = \%data; } # warn Dumper $map->{specimen}; } { # request_history: my $national_codes = $settings->{national_codes}; my $sql = q!select rh.`request_id`, rh.`action`, rh.`time`, u.`username`, u.`first_name`, u.`last_name` from `request_history` rh join `users` u on rh.`user_id` = u.`id` where rh.`action` in ('registered','reported','authorised') and rh.`request_id` in (??)!; my $result = $dbix->query($sql, @$request_ids); while ( my $vars = $result->hash ) { # warn Dumper $vars; my $request_id = $vars->{request_id}; my $user_id = $vars->{username}; # warn $username; my $action = $vars->{action}; my $name = join ' ', map ucfirst $_, $vars->{first_name}, $vars->{last_name}; my %data = ( datetime => DateTime::Format::MySQL->parse_datetime($vars->{time}), user_code => $national_codes->{$user_id}, user_name => $name, ); $map->{history}->{$request_id}->{$action} = \%data; } # warn Dumper $map->{history}; } { # result_summaries: my $sql = q!select rs.`request_id`, rs.`results_summary`, ls.`section_name` from `request_result_summaries` rs join `lab_sections` ls on rs.`lab_section_id` = ls.`id` where rs.`request_id` in (??)!; my $result = $dbix->query($sql, @$request_ids); while ( my $vars = $result->hash ) { # warn Dumper $vars; my $request_id = $vars->{request_id}; my $section = $vars->{section_name}; my $result = $vars->{results_summary}; $map->{result_summary}->{$request_id}->{$section} = $result; } # warn Dumper $map->{result_summary}; } return $map; } sub _ftp_file { my $args = shift; my $cfg = $settings->{ppm_server}; my %params = ( local_filename => $args->{local_filename}, remote_filename => $args->{remote_filename}, server_addr => $cfg->{remote_address}, username => $cfg->{username}, password => $cfg->{password}, ); # ftp file (returns str on failure, undef on success): my $rtn = $tools->ftp_file(\%params); if ($rtn) { # warn Dumper $rtn; $tools->mail_admin({ script => $0, msg => $rtn }); warn "$0 - $rtn\n"; # dump to logs but don't die!! } else { io($args->{local_filename})->unlink; } } __DATA__ # new_cases: SELECT DISTINCT(r.`id`) FROM requests r JOIN `patient_case` pc on r.`patient_case_id` = pc.`id` JOIN `patients` p on pc.`patient_id` = p.`id` JOIN `referral_sources` rs on pc.`referral_source_id` = rs.`id` JOIN `parent_organisations` po on rs.`parent_organisation_id` = po.`id` JOIN `request_history` rh on rh.`request_id` = r.`id` JOIN `request_initial_screen` ris on ris.`request_id` = r.`id` JOIN `screens` s on ris.`screen_id` = s.`id` LEFT JOIN `request_diagnosis_history` rdh on rdh.`request_id` = r.`id` LEFT JOIN `request_result_summaries` rrs on rrs.`request_id` = r.`id` WHERE DATE_FORMAT(rh.`time`, '%Y-%m-%d') = ? AND po.parent_code IN ( 'RR8','RAE','RWY' ) AND rh.`action` = 'authorised' AND s.description NOT IN ( 'Chimerism sample', 'Chimerism (CHIM)', 'Chimerism (CHIA/CHIB)', 'Molecular miscellaneous', 'Community monitoring' ); # revisions: SELECT DISTINCT(r.`id`) FROM requests r JOIN `patient_case` pc on r.`patient_case_id` = pc.`id` JOIN `patients` p on pc.`patient_id` = p.`id` JOIN `referral_sources` rs on pc.`referral_source_id` = rs.`id` JOIN `parent_organisations` po on rs.`parent_organisation_id` = po.`id` JOIN `request_history` rh on rh.`request_id` = r.`id` JOIN `request_initial_screen` ris on ris.`request_id` = r.`id` JOIN `screens` s on ris.`screen_id` = s.`id` LEFT JOIN `request_diagnosis_history` rdh on rdh.`request_id` = r.`id` LEFT JOIN `request_result_summaries` rrs on rrs.`request_id` = r.`id` WHERE ( DATE_FORMAT(rdh.`time`, '%Y-%m-%d') = ? OR DATE_FORMAT(rrs.`time`, '%Y-%m-%d') = ? ) AND po.parent_code IN ( 'RR8','RAE','RWY' ) /* AND 1=1 */ AND rh.`action` = 'authorised' AND s.description NOT IN ( 'Chimerism sample', 'Chimerism (CHIM)', 'Chimerism (CHIA/CHIB)', 'Molecular miscellaneous', 'Community monitoring' ); # demographic changes: SELECT DISTINCT(r.`id`) FROM requests r JOIN `patient_case` pc on r.`patient_case_id` = pc.`id` JOIN `patients` p on pc.`patient_id` = p.`id` JOIN `referral_sources` rs on pc.`referral_source_id` = rs.`id` JOIN `parent_organisations` po on rs.`parent_organisation_id` = po.`id` JOIN `request_history` rh on rh.`request_id` = r.`id` JOIN `request_initial_screen` ris on ris.`request_id` = r.`id` JOIN `screens` s on ris.`screen_id` = s.`id` LEFT JOIN `request_diagnosis_history` rdh on rdh.`request_id` = r.`id` LEFT JOIN `request_result_summaries` rrs on rrs.`request_id` = r.`id` WHERE DATE_FORMAT(p.`updated_at`, '%Y-%m-%d') = ? AND DATE_FORMAT(rh.`time`, '%Y-%m-%d') < ? AND po.parent_code IN ( 'RR8','RAE','RWY' ) /* AND 1=1 */ AND rh.`action` = 'authorised' AND s.description NOT IN ( 'Chimerism sample', 'Chimerism (CHIM)', 'Chimerism (CHIA/CHIB)', 'Molecular miscellaneous', 'Community monitoring' );