RSS Git Download  Clone
Raw Blame History
package LIMS::Local::Billing;

use Moo;
use Data::Printer alias => 'ddp';
use MooX::HandlesVia;
use DateTime::Format::MySQL;
use Types::Standard qw(ArrayRef HashRef);

use LIMS::Local::Utils;

use feature 'say';

#===============================================================================
my $debug = $ENV{DEBUG_ON} || 0; # verbose output for testing

has settings => ( is => 'ro', isa => HashRef, required => 1 );

# keeps track of whether request has a sample type set to BMASEQ:
has bmaseq => (
    is => 'rw',
    handles_via => 'Hash',
    handles => {
        get_bmaseq => 'get',
        set_bmaseq => 'set',
        all_bmaseq => 'keys',
    },
    default => sub { {} },
);

# hash of molecular sequencing tests:
has mol_seq_tests => (
    is => 'lazy',
    isa => HashRef,
    builder => 1,
);

# list of request ids with BMA/BMAT sample (supplied by caller):
has bone_marrow_requests => (
    is => 'rw',
    handles_via => 'Hash',
);

# list of private patients:
has private_patients => (
    is => 'rw',
    handles_via => 'Hash',
);

# rows for billing data file:
has rows => (
    is => 'rw',
    handles => {
        add_row => 'push',
        get_all_rows => 'elements', # returns array NOT arrayref
    },
    default => sub { [] },
    handles_via => 'Array',
);

my $delimiter = '|';

sub set_initial_charge_code {
    my ($self, $vars) = @_;

    my $request_id = $vars->{id};

    # non-NHS sources unless in trial/study:
	if ( $vars->{organisation_code} =~ /^NT\d{3}|8EQ15/ ) {
		$vars->{category} = '04' unless $vars->{category} eq 'ST';
	}
    # private patient in NHS hospital:
	elsif ( $self->private_patients->{$request_id} ) {
		$vars->{category} = '02'; # p $request_id;
	}
}

sub apply_local_rules {
    my ($self, $vars) = @_; # p $vars; exit; # hashref

	my $request_id = $vars->{id};
    my $lab_tests  = $vars->{request_lab_tests};
    my $settings   = $self->settings;

    # override ST code and charge as normal sample:
    my $chargable_trials = $settings->{chargable_trials}; # p $chargable_trials;
    # get map of higher-charge molecular sequencing tests:
    my $mol_seq_tests = $self->mol_seq_tests; # p $mol_seq_tests;

    my $has_bone_marrow = $self->bone_marrow_requests->{$request_id};

	if (
        $vars->{first_name} =~ /\A(lgi|pin)\Z/i ||
        $vars->{last_name}  =~ /\Ahtg\d+/i
    ) {
		$vars->{first_name} = uc $vars->{first_name};
	}

	# $vars->{unit_number} = '' if $vars->{unit_number} eq 'UNKNOWN';

	# downstream system can't handle d.o.b eg 1/1/1000, replace with NULL:
	if ( my $dob = $vars->{dob} ) { # ddp $dob;
		my $date = DateTime::Format::MySQL->parse_date($dob);
		if ( $date->year <= 1900 ) { # p $dob;
			$vars->{dob} = $vars->{age} = undef; # p @{$vars}{'dob','age'};
		}
		else { # to save rpt D::F::MySQL in parse_data():
			$vars->{dob} = $date->dmy('/'); # convert to activity data format
		}
	}

    # avoid uninitialized value error:
	map $vars->{$_} ||= '', qw(trial_name presentation);

    my $presentation = $vars->{presentation};

    # haematology needs to be clinical not laboratory:
    $vars->{hospital_department_code} =~ s/823/303/;

    # Castleford & Normanton DH now RGX18, unless Dermatology which has its own code:
    if ( $vars->{organisation_code} eq 'RXF07' ) {
        if ( # examine both speciality & specimen:
            $vars->{hospital_department_code} == 330 ||
            $vars->{sample_description} =~ /^skin/i
        ) {
            $vars->{organisation_code} = '5N301D';
        }
        else {
            $vars->{organisation_code} = 'RGX18';
        }
    }
	# Birmingham Royal Orthopaedic Hospital - handles Leeds cases so don't charge:
    elsif ( $vars->{organisation_code} eq 'RRJ05' ) {
		$vars->{category} = 'ST';
	}

    { # branch => parent practices map:
		my $org_code = $vars->{organisation_code};
		if ( my $parent = $settings->{location_codes_map}->{$org_code} ) {
			$vars->{organisation_code} = $parent;
		}
	}

    # maybe change sample type based on presentation or test type - PB only:
	if ( $vars->{sample_code} =~ /^PB/ ) {
		my $set_code_map = $settings->{set_codes_map};

        # force specimen code to PBSEQ if at least one molecular sequencing test:
        if ( grep $mol_seq_tests->{$_}, @$lab_tests ) { # warn '=' x 30;
            $vars->{sample_code} = 'PBSEQ'; # p $request_id;
        }
		elsif ( my $set_code = $set_code_map->{$presentation} ) { # eg PB => PBCML:
			# $vars->{sample_code} .= $set_code if $vars->{sample_code} eq 'PB';
			$vars->{sample_code} =~ s/^PB$/${set_code}/; # p $vars->{sample_code};
		}
	}
    # override 01 category for research PB samples from LTH source:
    if ( $vars->{sample_code} eq 'RXPB' && $vars->{organisation_code} =~ /^RR8/) {
        $vars->{category} = 'ST';
    }

    # presentation / screened as:
#===============================================================================
	# Outreach:
    if ( $presentation =~ /Outreach/ ) { # billed directly now
        $vars->{category} = 'ST';
    }
    # NCG PNH (PB & BMAT):
  # TODO: change to /^PNH NCG/ after April 2019 run:
    elsif ( $presentation =~ /^(?:NCG )?PNH(?: NCG)?$/ ) { # billed directly now
        $vars->{category} = 'ST';
    }
#===============================================================================
    # elsif ( $presentation eq 'HTS myeloid only' ) { # until April 2017:
    #    $vars->{sample_code} = 'BMASEQ' unless
    #        grep $vars->{parent_organisation_id} eq $_, @local_network_locations;
    # }

    # HTS myeloid test (from Apr/17) needs to generate a new row for all referral
    # sources if HTS myeloid requested *in addition to* any other test(s) except
    # hts_quantification, dna_extraction & molecular srsf2 (which is also part of
    # HTS section but never requested individually), unless screened as "HTS
    # myeloid only" in which case sample set to BMASEQ & any other rows set to ST:
    if ( grep $_ eq 'hts_myeloid', @$lab_tests ) {
        # ddp $self->all_bmaseq;
          output( $vars->{request_number} . ': ' . $vars->{sample_code} );
        # create list of other requested tests (exclude some irrelevant ones):
        my @non_hts_myeloid_tests = grep {
            $_ !~ /^(srsf2|hts_myeloid|dna_extraction|hts_quantification)/
        } @$lab_tests;

        # make a new row if any other tests, not screened as 'HTS myeloid only'
        # and not already done for this request (ie has multiple samples, none
        # yet set to BMASEQ):
        if (
                ( scalar @non_hts_myeloid_tests >= 1  ) # any other tests
            and ( $presentation ne 'HTS myeloid only' ) # not screened as
            and ( not $self->get_bmaseq($request_id)  ) # not already done
           )
        {
              output( 'no BMASEQ, have other tests, not screened as HTS myeloid '
                . 'only, creating new BMASEQ' );
            $self->set_bmaseq($request_id => 1); # p $vars;
            # clone data and change sample type to BMASEQ:
            my $ref = LIMS::Local::Utils::clone($vars);
            $ref->{sample_code} = 'BMASEQ'; # p $ref;
          # !!! this action will skip all further local rules on this new sample:
            my $data = $self->parse_data($ref);
            $self->add_row($data);
        }
        # change existing sample code to BMASEQ if we don't already have one:
        elsif ( not $self->get_bmaseq($request_id) ) {
              output( "no BMASEQ, changing $vars->{sample_code} to BMASEQ" );
            $vars->{sample_code} = 'BMASEQ';
            $self->set_bmaseq($request_id => 1);
        }
        # if screened as 'HTS myeloid only' & no other tests performed and
        # already has a BMASEQ, set any other(s) to ST:
        elsif ( $presentation eq 'HTS myeloid only'
            and scalar @non_hts_myeloid_tests == 0 )
        {
              output( 'already have BMASEQ, screened as HTS myeloid only and no '
                . 'additional tests, setting charge code ST' );
            $vars->{category} = 'ST';
        }
        # ignore it, existing sample type retained:
        else {
            output( "screened as $presentation, has previous BMASEQ, retaining "
                . 'sample type');
        }
    } # p @rows;
      output( "final sample code $vars->{sample_code}; charge code "
        . $vars->{category} );
      output( '=' x 30 ); # exit if $vars->{request_number} > 169;
#===============================================================================
    # EQA samples:
    if ( $vars->{last_name} eq 'ceqas' && length $vars->{first_name} == 2 ) {
        $vars->{category} = 'ST';
    }

	# set category to ST for PB if accompanied by BMA[T] (skip if already set to ST):
	if ( $vars->{sample_code} eq 'PB' && $vars->{category} ne 'ST' ) {
		$vars->{category} = 'ST' if $has_bone_marrow;
	}

    # trial sample ST code overriden and billed as normal:
    if ( grep $vars->{trial_name} eq $_, @$chargable_trials ) {
        $vars->{category} = '01'; # override ST code
    }

    # FLAIR trial:
    if ( $presentation =~ /FLAIR/ || $vars->{trial_name} =~ /CLL FLAIR/ ) {
        my @flair_followups = ( # FLMRD
            'FLAIR follow-up (I/IR/I+V/unstated)',
            'FLAIR MRD confirmation/EoT',
            'FLAIR response BM',
        );
		# set default charge to ST (all samples):
		$vars->{category} = 'ST';
        my $sample = $vars->{sample_code};

        # baseline requests:
        if ( $presentation eq 'FLAIR baseline' ) {
            # require BSL sample code & category 01 if BMA(T) or PB without a BMA(T):
			do { $vars->{sample_code} = 'FLBSL'; $vars->{category} = '01' }
				if $sample =~ /^BMA/
					|| ( $sample =~ /^PB/ && ! $has_bone_marrow );
        }
        # disease progression:
        elsif ( $presentation eq 'FLAIR disease progression' ) {
            if ( $sample =~ /^BMA/ ) { # should never get BM for this screen
                $vars->{sample_code} = 'FLMRD'; $vars->{category} = '01';
            }
            # PB only (ie without a BMA/T - don't want FLMRD as well as FLRES:
            elsif ( $sample =~ /^PB/ && ! $has_bone_marrow ) {
                $vars->{sample_code} = 'FLMRD'; $vars->{category} = '01'
            }
        }
        # other follow-ups:
        elsif ( grep $presentation eq $_, @flair_followups ) {
            # BMA/T:
            if ( $sample =~ /^BMA/ ) { # should never get BM for these screens
                $vars->{sample_code} = 'FLRES'; $vars->{category} = '01';
            }
            # PB only (ie without a BMA/T - don't want FLMRD as well as FLRES:
            elsif ( $sample =~ /^PB/ && ! $has_bone_marrow ) {
                $vars->{sample_code} = 'FLMRD'; $vars->{category} = '01'
            }
        }
        # warn Dumper [$request_id, $vars->{sample_code}, $vars->{category} ];
    }
}

sub parse_data {
    my ($self, $vars) = @_; # hashref

    my $line = join $delimiter, (
		'HMDS',
        'H' . $vars->{request_number} . '/' . ( $vars->{year} - 2000 ),
		uc $vars->{last_name},
		( join ' ', map ucfirst $vars->{$_}, grep $vars->{$_}, qw/first_name middle_name/ ),
		$vars->{unit_number}, # have to handle default unknown
		$vars->{dob} || '',   # already converted to dmy('/') in apply_local_rules()
		$vars->{age} || '',
		$vars->{gender} || 'U',
		$vars->{organisation_code},
		$vars->{referrer_code},
		$vars->{organisation_code} =~ /\A(RR8|RAE)/ ? $vars->{hospital_department_code} : '',
		'HM' . $vars->{sample_code},
		'HM' . $vars->{sample_code},
		$vars->{category},
		$vars->{on_call},
		DateTime::Format::MySQL->parse_datetime($vars->{created_at})->dmy('/'),
        DateTime::Format::MySQL->parse_datetime($vars->{created_at})->strftime('%d/%m/%Y:%H:%M'),
        $vars->{trial_number} || '', # external ref field
		$vars->{nhs_number}	  || '',
	);
    return $line;
}

sub output {
    return unless $debug; # ENV{DEBUG_ON} true
    say $_[0];
}

sub _build_mol_seq_tests { # only returning map of molecular_sequencing_tests now:
    my $self = shift;
	my $test_names = $self->settings->{molecular_sequencing_tests}; # p $test_names;
    my %map = map { $_ => 1 } @$test_names; # p %map;
	return \%map;
}

1;