package History; use Moose; with qw( Role::User Role::RebuildTables ); use namespace::clean -except => 'meta'; use Data::Dumper; has $_ => (is => 'ro', isa => 'HashRef', required => 1) foreach qw( db sql ); has log_file => ( is => 'ro', required => 1 ); has request => ( is => 'ro', isa => 'HashRef', default => sub { {} } ); has $_ => ( is => 'ro', isa => 'Object', lazy_build => 1 ) foreach qw( missing_screener_info missing_reporter_info missing_registration_info ); has actions_list => ( is => 'ro', isa => 'Str', lazy_build => 1 ); has actions_map => ( is => 'ro', isa => 'HashRef[Str]',lazy_build => 1 ); has error_code_map => ( is => 'ro', isa => 'HashRef[HashRef]',lazy_build => 1 ); has $_ => ( is => 'ro', isa => 'ArrayRef[Str]', lazy_build => 1, auto_deref => 1 ) foreach qw( request_actions modified_request_fields lab_test_actions ); __PACKAGE__->meta->make_immutable; my @tables = qw( request_history request_error_code request_lab_test_history ); $|++; sub convert { my $self = shift; my $dbix3 = $self->db->{dbix3}; my $dbix4 = $self->db->{dbix4}; my $dbh4 = $self->db->{dbh4}; my $fh = $self->log_file; #=begin $self->rebuild_tables(\@tables); $dbh4->do( q!DROP TABLE IF EXISTS `temp`! ); $dbh4->do( q!CREATE TABLE `temp` LIKE `request_history`! ); my $sql = q!select ID,Date,Time,UserID,Action,HMDS from History!; # where ID > 172000 my $history = $dbix3->query($sql); HISTORY: while ( my $vals = $history->hash ) { # warn Dumper $vals; $vals->{id} % 10000 || print $vals->{id}, "\n"; # eg Ki67 -> selection, remove trailing 'on': $vals->{action} = $self->_do_action_field_substitutions($vals->{action}); my $lab_no = $vals->{hmds}; # warn $lab_no; my ($hmds, $yr) = $lab_no =~ /H(\d+)\/(\d{2})/; # create lab_no -> request.id map if not already exists: $self->request->{$lab_no} ||= $dbix4->query( q!SELECT id FROM requests WHERE request_number = ? AND year = ?!, $hmds, 2000 + $yr )->list; # warn $self->request->{$lab_no}; unless ($self->request->{$lab_no}) { print $fh "no request.id for lab_no $lab_no\n"; next HISTORY; } # do this 1st so 'entered foo' list matches before lab_tests: if ( grep $vals->{action} =~ /^$_/, $self->request_actions ) { # match at beginning $self->_do_request_actions($vals); } # do this next so we remove 'recorded error codes' so can do other 'recorded foo': elsif ( $vals->{action} =~ /^recorded error code \w/ ) { $self->_do_error_code_actions($vals); } elsif ( grep $vals->{action} =~ /^$_/, $self->lab_test_actions ) { # match at beginning $self->_do_lab_test_actions($vals); } elsif ( $vals->{action} =~ /^(modified)/ ) { $self->_do_modification_actions($vals); } elsif ( $vals->{action} =~ /diagnosis of DLBCL or Burkitt lymphoma/ ) { $vals->{action} = 'dispatched DLBCL/Burkitt lymphoma email alert'; $self->_do_request_actions($vals); } else { print $vals->{action}, "\n"; } } # change status to incomplete if outstanding tests or final_diag required: $self->_revert_status_on_outstanding_tests; $self->_revert_status_if_final_diagnosis_required; # get missing registration, screener, reporter info: $self->_do_missing_registration_info; $self->_do_missing_screener_info; $self->_do_missing_reporter_info; warn 'dumping temp table into request_history'; # transfer data from temp to request_history in date/time order: my $data = $dbix4->query( q!select request_id, action, user_id, time from `temp` order by `time`,`request_id`,`id`! ); # don't want id while ( my $vals = $data->hash ) { # warn $vals->{request_id}; $dbix4->insert('request_history', $vals); } $dbh4->do( q!DROP TABLE `temp`! ); #=cut # update requests table 'created_at' col with registration date/time: $self->_do_request_created_at; # update request_report table 'created_at' col with report date/time: $self->_do_report_created_at; print $fh Dumper $self->no_username; } # change status to incomplete if outstanding tests (before timestamp updated): sub _revert_status_on_outstanding_tests { my $self = shift; my $dbh = $self->db->{dbh4}; my $sql = q! update requests r set r.status_option_id = 4 where r.id in ( select s.request_id from request_lab_test_status s where s.status_option_id <> 2 ) and r.status_option_id = 5!; $dbh->do($sql); } # use hilis3 final_diagnosis.pl cron queries to find requests which need final diagnosis: sub _revert_status_if_final_diagnosis_required { my $self = shift; my $dbix3 = $self->db->{dbix3}; my $dbix4 = $self->db->{dbix4}; my $sql = q! SELECT `HMDS`, DATE_FORMAT(`Date`, '%Y') as 'year', `InitialDiag`, `Specimen` FROM `Report` LEFT JOIN `Main` on `DBID` = `Rpt_ID` LEFT JOIN `MolGen` on `Mol_ID` = `DBID` WHERE YEAR(`Date`) >= 2010 AND `AuthorisedBy` IS NOT NULL AND `FinalDiag` IS NULL AND (`GenResult` IS NOT NULL OR `FishResult` IS NOT NULL OR `MolResult` IS NOT NULL) AND `InitialDiag` NOT IN ('Molecular miscellaneous', 'Chimerism sample', 'PNH', 'Rheumatoid arthritis')!; my @excluded_if_peripheral_blood = ( 'CML follow-up (post-BMT)', 'CML imatinib PB follow-up', 'CML interferon follow-up', 'CML STI follow-up' ); my $data = $dbix3->query($sql); ROW: while ( my $vals = $data->hash ) { # warn $vals->{request_id}; # skip 'escapees' from HILIS3: next ROW if grep $vals->{hmds} == $_, (2646, 2656, 5217); my $specimen = $vals->{specimen}; my $screen = $vals->{initialdiag}; next ROW if ( grep $screen eq $_, @excluded_if_peripheral_blood ) && $specimen eq 'PB'; # skip CML follow-up PB's # get lab_tests: my $sql = q! select t3.test_name, t4.description from requests t1 join request_lab_test_status t2 on t2.request_id = t1.id join lab_tests t3 on t2.lab_test_id = t3.id join lab_test_status_options t4 on t2.status_option_id = t4.id where t1.request_number = ? and t1.year = ?!; my $lt = $dbix4->query( $sql, @{$vals}{ qw(hmds year) } )->hashes; # warn Dumper $tests; next; my @status = map $_->{description}, @$lt; my @tests = map $_->{test_name}, @$lt; # next if any incomplete tests: next ROW if grep $_ ne 'complete', @status; if ( $screen eq 'CMPD pres & follow-up' && $specimen eq 'PB' ) { # check to see if JAK2 is only lab_test: next ROW if (join '', @tests) eq 'jak2'; } # warn Dumper [ @{$vals}{qw(hmds initialdiag specimen)}, @tests ]; # update status if already set to complete: $dbix4->update( 'requests', { status_option_id => 4 }, { request_number => $vals->{hmds}, year => $vals->{year}, status_option_id => 5 } ); } } # update requests table 'created_at' col with registration date/time: sub _do_request_created_at { my $self = shift; my $dbix = $self->db->{dbix4}; my $dbh = $self->db->{dbh4}; my $sql = q!select request_id, time from request_history where action = 'registered'!; my $request_time_map = $dbix->query( $sql )->map; # to avoid updated_at col updating to now(): $dbh->do( q!ALTER TABLE `requests` CHANGE COLUMN `updated_at` `updated_at` timestamp;! ); map { # warn $_ unless $_ % 1000; my $created_at = $request_time_map->{$_}; $dbix->update('requests', { created_at => $created_at }, { id => $_ } ); } sort keys %$request_time_map; $dbh->do( q!ALTER TABLE `requests` CHANGE COLUMN `updated_at` `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP! ); } # update request_report table 'created_at' col with report date/time: sub _do_report_created_at { my $self = shift; my $dbix = $self->db->{dbix4}; my $dbh = $self->db->{dbh4}; my $sql = q!select request_id, time from request_history where action = 'reported'!; my $report_time_map = $dbix->query( $sql )->map; # to avoid updated_at col updating to now(): $dbh->do( q!ALTER TABLE `request_report` CHANGE COLUMN `updated_at` `updated_at` timestamp;! ); map { # warn $_ unless $_ % 1000; my $created_at = $report_time_map->{$_}; $dbix->update('request_report', { created_at => $created_at }, { request_id => $_ } ); } sort keys %$report_time_map; $dbh->do( q!ALTER TABLE `request_report` CHANGE COLUMN `updated_at` `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP! ); } sub _do_lab_test_actions { my $self = shift; my $vals = shift; my $dbh = $self->db->{dbix4}; # get common fields: user_id, request_id & time: my $data = $self->_get_history_table_data($vals); $data->{action} = $vals->{action}; $dbh->insert('request_lab_test_history', $data); } sub _do_error_code_actions { my $self = shift; my $vals = shift; my $dbh = $self->db->{dbix4}; my $data = $self->_get_history_table_data($vals); my $error_code_map = $self->error_code_map; my ($error_code) = $vals->{action} =~ /error code (\w)/; { # request_history my %data = %$data; $data{action} = 'recorded error code ' . $error_code; $dbh->insert('temp', \%data); if ($vals->{action} =~ /pre-IR1/) { $data{action} = 'completed LIC'; $dbh->insert('temp', \%data); } } my $error_code_id = $error_code_map->{$error_code}->{id} || die "Cannot find error code id for $error_code"; my $error_code_is_unique = $error_code_map->{$error_code}->{unique} || die "Cannot find error code 'is_unique' for $error_code"; { # request_error_code: $data->{error_code_id} = $error_code_id; if ($error_code_is_unique eq 'yes') { my $sql = q!select 1 from request_error_code where request_id = ? and error_code_id = ?!; return if $dbh->query( $sql, @{$data}{ qw(request_id error_code_id) } )->list; } # ok, request_error_code doesn't exist or code not unique: $dbh->insert('request_error_code', $data); } } # can be modifications to request, patient or lab_test fields: sub _do_modification_actions { my $self = shift; my $vals = shift; my $dbh = $self->db->{dbix4}; # get error_code data: if ( $vals->{action} =~ /error code \w/ ) { # record error_code: $self->_do_error_code_actions($vals); # strip error code (it's already logged in request_history file): $vals->{action} =~ s/\s::\serror code \w//; # warn $vals->{action}; } # get common fields: user_id, request_id & time: my $data = $self->_get_history_table_data($vals); $data->{action} = $vals->{action}; my $request_fields = join '|', $self->modified_request_fields; # using auto_deref here # patient fields: if ( $vals->{action} =~ /Name|DoB|NHSNo|PatNo|Sex|PID/ ) { # TODO: skip for now my $fh = $self->log_file; print $fh $vals->{action}, "\n"; } # request fields: elsif ( $vals->{action} =~ /$request_fields/ ) { $dbh->insert('temp', $data); } # assume lab_test fields: else { $dbh->insert('request_lab_test_history', $data); } } sub _do_request_actions { my $self = shift; my $vals = shift; my $dbh = $self->db->{dbix4}; # get common fields: user_id, request_id & time: my $data = $self->_get_history_table_data($vals); my $actions_list = $self->actions_list; my $actions_map = $self->actions_map; # screened / reported / authorised: my (@actions) = $vals->{action} =~ /$actions_list/g; # if action in actions_list (eg screened, reported & authorised): if (@actions) { foreach my $action(@actions) { # substitute 'action' if in %actions_map: $data->{action} = $actions_map->{$action} || $action; $dbh->insert('temp', $data); } } # else just insert action: else { $vals->{action} =~ s/pre-IR1/LIC/; $vals->{action} =~ s/\Ae-mailed\Z//; # empty e-mailed lines $vals->{action} =~ s/\A(e-mailed)/$1 diagnosis status alert to/; $data->{action} = $vals->{action} || return; # skip empty lines $dbh->insert('temp', $data); } # update request_lab_test_status if status not complete: if ($vals->{action} eq 'screened') { # should only be singlicate if outstanding tests my $sql = q!select 1 from request_lab_test_status where request_id = ? and status_option_id <> 2!; if ( $dbh->query($sql, $data->{request_id})->list ) { my $request_id = $data->{request_id}; my $user_id = $data->{user_id}; $dbh->update( 'request_lab_test_status', { user_id => $user_id }, { request_id => $request_id, status_option_id => { '!=', 2 } }, ); } } # update request_diagnosis_history table: if ($vals->{action} eq 'entered ReviseDiag and FinalDiag') { # only 5 of these foreach ( qw/error update/ ) { $dbh->update('request_diagnosis_history', { time => $data->{time} }, # set { # where: request_id => $data->{request_id}, user_id => $data->{user_id}, reason => $_, } ); } } elsif ($vals->{action} =~ /(Revise|Final)Diag/) { my $reason = $1 eq 'Revise' ? 'error' : 'update'; $dbh->update('request_diagnosis_history', { time => $data->{time} }, # set { # where: request_id => $data->{request_id}, user_id => $data->{user_id}, reason => $reason, } ); } } sub _get_history_table_data { my $self = shift; my $vals = shift; # warn Dumper $vals; my $username = lc $vals->{userid}; # warn $username if $lab_no eq 'H1608/02'; # get user.id from History.UserID: my $user_id = $self->_get_user_id($username); # warn $user_id if $lab_no eq 'H1608/02'; my $lab_no = $vals->{hmds}; # warn $lab_no; # get request.id from %request map: my $request_id = $self->request->{$lab_no}; my %data = ( user_id => $user_id, request_id => $request_id, time => join ' ', $vals->{date}, $vals->{time}, ); return \%data; } # look for missing registration info: sub _do_missing_registration_info { my $self = shift; my $missing_registration_info = $self->missing_registration_info; my $dbix3 = $self->db->{dbix3}; my $dbix4 = $self->db->{dbix4}; while ( my $vals = $missing_registration_info->hash ) { my $date = $dbix3->query( q!select Date from Main where HMDS = ? and year(Date) = ?!, $vals->{request_number}, $vals->{year})->list; my %data = ( action => 'registered', request_id => $vals->{id}, user_id => $self->username_userid_map->{unknown}, time => join ' ', $date, '00:00:00', ); $dbix4->insert('temp', \%data); } } # look for missing screener info: sub _do_missing_screener_info { my $self = shift; my $missing_screener_info = $self->missing_screener_info; my $dbix3 = $self->db->{dbix3}; my $dbix4 = $self->db->{dbix4}; RECORD: while ( my $vals = $missing_screener_info->hash ) { my ($user,$date) = $dbix3->query( q!select Screener, ScreenDate from Main,Report where HMDS = ? and year(Date) = ? and DBID = Rpt_ID!, $vals->{request_number}, $vals->{year})->list; # $user = signature - need user.id from $self->_get_user_id(), which needs UserID: $user || next RECORD; # presume not screened; my $username = $self->_get_username($user) || die "No username for $vals->{request_number}/$vals->{year}"; if ( my $user_id = $self->_get_user_id($username) ) { my %data = ( action => 'screened', request_id => $vals->{id}, user_id => $user_id, time => join ' ', $date, '00:00:00', ); $dbix4->insert('temp', \%data); } } } # look for missing reporter info: sub _do_missing_reporter_info { my $self = shift; my $missing_reporter_info = $self->missing_reporter_info; my $dbix3 = $self->db->{dbix3}; my $dbix4 = $self->db->{dbix4}; RECORD: while ( my $vals = $missing_reporter_info->hash ) { my ($user,$date) = $dbix3->query( q!select ReportBy, ReportDate from Main,Report where HMDS = ? and year(Date) = ? and DBID = Rpt_ID!, $vals->{request_number}, $vals->{year})->list; # $user = signature - need user.id from $self->_get_user_id(), which needs UserID: $user || next RECORD; # presume not screened; my $username = $self->_get_username($user) || die "No username for $vals->{request_number}/$vals->{year}"; if ( my $user_id = $self->_get_user_id($username) ) { my %data = ( action => 'reported', request_id => $vals->{id}, user_id => $user_id, time => join ' ', $date, '00:00:00', ); $dbix4->insert('temp', \%data); } } } sub _build_error_code_map { my $self = shift; my $dbh = $self->db->{dbix4}; # needs to be uppercase: my $sql = q!select `id`, upper(`code`) as 'code', `is_unique` from error_codes!; my $codes = $dbh->query($sql); my %map; while ( my $vals = $codes->hash ) { # warn Dumper $vals; my $code = $vals->{code}; my %data = ( id => $vals->{id}, unique => $vals->{is_unique}, ); $map{$code} = \%data; } return \%map; # add discontinued codes: } sub _build_actions_map { my $self = shift; my %map = ( added => 'registered', 'deleted record' => 'deleted', 'unlocked record' => 'unlocked', ); return \%map; } sub _build_missing_registration_info { my $self = shift; my $dbh = $self->db->{dbix4}; return $dbh->query( q!select id, request_number, year from requests where id not in ( select request_id from temp where action = 'registered' )! ); } sub _build_missing_screener_info { my $self = shift; my $dbh = $self->db->{dbix4}; return $dbh->query( q!select id, request_number, year from requests where id not in ( select request_id from temp where action = 'screened' )! ); } sub _build_missing_reporter_info { my $self = shift; my $dbh = $self->db->{dbix4}; return $dbh->query( q!select id, request_number, year from requests where id not in ( select request_id from temp where action = 'reported' )! ); } sub _build_actions_list { my $self = shift; my $actions_list = join '|', qw(registered added screened reported authorised deleted unlocked); return $actions_list; } sub _build_modified_request_fields { my $self = shift; my @fields = qw( NewDiagnosis TrialNo Urgent SpecQuality Private Store Study Research ReviseDiag Specimen Source BlockRef ClinDetails Clinician Comment Consent DoI FinalDiag GrossDesc Hospital InitDiag ); return \@fields; } sub _build_request_actions { my $self = shift; my @request_actions = ( 'added', 'screened', 'reported', 'authorised', 'registered', 'unlocked', # always 'unlocked record' in table 'reported and authorised', 'screened, reported and authorised', 'deleted', # always 'deleted%' in table 'emailed', # always 'emailed report' or 'emailed report%' in table 'e-mailed', # always 'emailed report' or 'emailed report%' in table 'recorded telephone', 'completed pre-IR1', 'dispatched', # report, CMP pack, sample alert, etc ); push @request_actions, map { 'entered ' . $_ } qw( Comment ClinDetails FinalDiag GrossDesc ReviseDiag ); return \@request_actions; } sub _build_lab_test_actions { my $self = shift; my @lab_test_actions = ( 'updated', 'checked', 're-checked', 'requested', 'input', 'recorded', # will match all not matched in 'recorded error code' & @_build_request_actions::request_actions 'signed out', 'H & E', # always 'H & E signed out', 'returned blocks', 'referred blocks', 'block updated', 'removed FISH', 'analysed flow data', 'entered', # will match all which didn't match in @_build_request_actions::request_actions ); return \@lab_test_actions; } sub _do_action_field_substitutions { my $self = shift; my $action = shift; $action =~ s/Ki67/selection/; # remove trailing 'on' & 'records:': $action =~ s/\s(on|records\:)\Z//; return $action; } 1;