package LIMS::Controller::Roles::PatientDemographics; use Moose::Role; use Data::Dumper; use Sys::SigAction qw( timeout_call ); # for PDS service use lib '/home/raj/perl-lib'; # PDS classes # PDS::* classes generated using SOAP::WSDL function: # wsdl2perl.pl -b lib -p PDS:: file:RenalWebService.wsdl.xml use PDS::Interfaces::PatientInterface::PatientInterfaceSoap; # SOAP::WSDL classes sub get_pds_data { my ($self, $patient) = @_; # hashref (with DoB = DT) my $messages = $self->messages('demographics'); my $config = $self->cfg('settings'); my $pds = PDS::Interfaces::PatientInterface::PatientInterfaceSoap->new(); $pds->set_proxy($config->{pds_proxy})if $ENV{DEVEL_SERVER}; # dev server can't find 'regint' =begin # alternate method - doesn't work use SOAP::WSDL::Client; my $urn = 'urn:Leeds-Renal.service/' . 'LTH.Renal.BusinessService.RenalWebService.GetPatientDetails'; my %method = ( soap_action => $urn, operation => 'GetPatientDetails', style => 'document', use => 'literal', ); my @parts = %h; warn Dumper \@parts; my $soap = SOAP::WSDL::Client->new({ proxy => $config->{pds_proxy} }); my $result2 = $soap->call( \%method, [] ); warn Dumper $result2; =cut my $result; { # get PDS result inside a Sys::SigAction::timeout_call - normal alarm/eval/die doesn't work with $pds my $ref = $self->_format_patient_details($patient); # warn Dumper $ref; my $sub = sub { $result = $pds->GetPatientDetails($ref); }; # Sys::SigAction::timeout_call( $duration, sub ): if ( timeout_call( 10, $sub ) ) { my %args = ( ErrorCode => $messages->{pds_timeout} ); return \%args; } } # warn Dumper $result; # PDS::Elements::GetPatientDetailsResponse object =begin # normal alarm/eval/die doesn't work eval { # in case we're in off-line testing mode local $SIG{ALRM} = sub { die $messages->{pds_timeout} . "\n" }; # NB \n required # create data structure from $patient to hand to PDS: my $args = $self->_process_patient_details($patient); # warn Dumper $args; alarm 10; # while (1) {} # this triggers timeout, PDS offline doesn't - why ?? does $pds use alarm ?? $result = $pds->GetPatientDetails($args); # warn Dumper $result; alarm 0; # warn Dumper $result; # PDS::Elements::GetPatientDetailsResponse object }; return $@ if $@; # return $result if not $result; # see docs for reasons for stupid syntax =cut # parse PDS return: my $data = $self->_process_pds_response($result); # warn Dumper $data; # check if PDS details match supplied patient details (adds 'demographics_verified' flag): if ( ref $data eq 'HASH' && $data->{ErrorCode} eq 'SMSP-0000' ) { # if PDS returned success my %args = ( pds => $data, patient => $patient ); $self->_check_demographic_details(\%args); } return $data; } #------------------------------------------------------------------------------- # checks PDS return fields against original patient fields and adds demographics_verified flag: sub _check_demographic_details { my ($self, $args) = @_; # warn Dumper $args; my $patient = $args->{patient}; # original patient data (dob = DT) my $pds = $args->{pds}; # PDS return my @fields = qw(last_name first_name gender nhs_number dob); my $i = 0; # starts 0 $i += ( lc $pds->{$_} eq lc $patient->{$_} ) for @fields; # dob will fail, so: # check patient dob DT object against PDS string: $i += ( $pds->{dob} eq $patient->{dob}->ymd ); # warn $i; # add 'demographics_verified' flag - true if $matches same number as fields: $pds->{demographics_verified} = ( $i == @fields ); } #------------------------------------------------------------------------------- # create a data structure to hand to the PDS: sub _format_patient_details { my ($self, $patient) = @_; # warn Dumper $patient; # hashref (with dob = DT) my %h = ( # mandatory fields: Surname => $patient->{last_name}, Gender => 0, # default, maybe updated below DOB => '19000101', # default, maybe updated below ); # reformat $H{DOB} as yyyymmdd if supplied (mandatory field so should be): if ( my $dob = $patient->{dob} ) { # DT object $h{DOB} = $dob->ymd(''); # string, no spaces } # update $h{Gender} with numerical index: if ( my $gender = $patient->{gender} ) { my %o = ( M => 1, F => 2 ); # 'U' will retain default 0 $h{Gender} = $o{$gender} if $o{$gender}; # update default unknown 0 if M/F } # add optional fields: $h{NHSNumber} = $patient->{nhs_number} if $patient->{nhs_number}; $h{Forename} = $patient->{first_name} if $patient->{first_name}; $h{Postcode} = $patient->{post_code} if $patient->{post_code}; # add user ID for audit: $h{VitalDataUserID} = 'HMDS-' . $self->user_profile->{id}; # warn Dumper \%h; return \%h; } #------------------------------------------------------------------------------- sub _process_pds_response { my ($self, $pds) = @_; # PDS::Elements::GetPatientDetailsResponse object my $messages = $self->messages('demographics'); return $messages->{pds_no_xml} unless $pds; =begin # using as_hash_ref() on $pds object directly now my @args = ( ForceArray => 0, # force nested elements to be represented as arrays KeyAttr => '', # don't need it, but required by :strict SuppressEmpty => undef, # default empty is {}, displayed as HASHREF by tt ); my $ref = XMLin($pds, @args); warn Dumper $ref; return 1; my $patient = $ref->{GetPatientDetailsResult}; # warn Dumper $patient; # hashref =cut # can call serialize() on $pds to get xml, or as_hash_ref(): my $ref = $pds->as_hash_ref; # warn Dumper $ref; my $result = $ref->{GetPatientDetailsResult}; # warn Dumper $patient; # hashref =begin # don't need to return early as tt tests for ErrorCode anwyay { # return 'error code' unless SMSP-0000 (success) my $pds_return_codes = $self->get_yaml_file('pds_return_codes'); # warn Dumper $pds_return_codes; my $code = $result->{ErrorCode}; my $outcome = $pds_return_codes->{$code}; return $result unless lc $outcome eq 'success'; # single hash value = ErrorCode } =cut # reformat DoB (yyyymmdd) as yyyy-mm-dd: if ( $result->{DoB} ) { $result->{DoB} =~ s/(\d{4})(\d{2})(\d{2})/$1-$2-$3/; } # warn Dumper $patient; # decode PDS Gender: if ( my $gender = $result->{Gender} ) { my %o = ( 0 => 'U', 1 => 'M', 2 => 'F' ); $result->{Gender} = $o{$gender}; } my %data = ( prefix => $result->{Prefix}, first_name => $result->{GivenName1}, last_name => $result->{FamilyName}, nhs_number => $result->{NHSNumber}, post_code => $result->{HomePostCode}, gender => $result->{Gender}, dob => $result->{DoB}, ); # only include middle name if > 1 char: $data{middle_name} = $result->{GivenName2} if $result->{GivenName2} && length $result->{GivenName2} > 1; # create $data{home_address} attr from HomeAddr1 .. HomeAddr5 components: if ( grep $result->{'HomeAddr'.$_}, 1 .. 5 ) { my $str = join ', ', map $result->{'HomeAddr'.$_}, grep $result->{'HomeAddr'.$_}, (1 .. 5); # empty field is {}, or undef if using SuppressEmpty $data{home_address} = $str; } # include PDS error-code for tt; key = same as PDS return: $data{ErrorCode} = $result->{ErrorCode}; return \%data; } 1;