RSS Git Download  Clone
Raw Blame History
package LIMS::Model::HMRN::PrognosticIndicator;

use Moose::Role;
requires 'patient_id';

use Data::Dumper;

# all previous & current diagnostic categories on this patient:
has diagnostic_categories_map => (is => 'ro', isa => 'HashRef', lazy_build => 1);
# number of nodal & extranodal sites - set in prognostic_indicators():
has involved_sites => (is => 'rw', isa => 'HashRef', default => sub { {} });
# supplied by calling model class & set in prognostic_indicators()
has staging_data => (is => 'rw', isa => 'HashRef', default => sub { {} });

has patient_age => (is => 'rw', isa => 'Int'); # calculated in prognostic_indicators()
has ann_arbor   => (is => 'rw', isa => 'Str'); # calculated in ann_arbor()

sub prognostic_indicators {
    my $self = shift;
    my $args = shift; # warn Dumper $args;
    
    my $dbix = $self->lims_dbix;

	# set some internal params for calculations:
    my %h = (
        params  => $args->{params_data},
        imaging => $args->{initial_scan},
        option_maps => $args->{option_maps},
    ); # warn Dumper \%h;
	$self->staging_data(\%h);
	
    my $patient_id = $self->patient_id;
    
    # set some internal params for calculations:
	{ # number of extranodal sites + nodal sites from above & below diaphragm:
        my %involved_sites = ( # initialise all 3 regions:
			upper_nodal => 0,
			lower_nodal => 0,
			extranodal  => 0,                              
        );
        
        my $sql = $self->sql_lib->retr('hmrn_imaging_options'); # for prognostic_indicator.t
        my $sites_map = $dbix->query($sql)->map;
		
        while ( my ($id, $opt_type) = each %$sites_map ) { # warn Dumper [$id, $opt_type];
            $involved_sites{$opt_type}++ if $h{imaging}{site_opts}{$id};
        } # warn Dumper \%involved_sites;
        $self->involved_sites(\%involved_sites);
	}
	{ # need to calculate age again, as patient object not handed to model:
		my $sql = $self->sql_lib->retr('hmrn_calculate_patient_age');
		my $ref = $dbix->query($sql, $patient_id)->hash; # warn Dumper $ref;
        my @dates = qw(dob diagnosis);
        
		$self->inflate_mysql_dates_to_datetime($ref, \@dates);
        
		my $age = LIMS::Local::Utils::calculate_age(@{$ref}{@dates});
        $self->patient_age($age); # warn "AGE: $age";
	} 
    
    my %indices = ();
    
    # do this 1st as some other calculated indices depend on it:
    if ( my $ann_arbor = $self->_calculate_ann_arbor ) {
        $indices{ann_arbor} = $ann_arbor;
    }
    if ( my $binet = $self->_calculate_binet ) {
        $indices{binet} = $binet;
    }
    if ( my $flipi = $self->_calculate_flipi ) {
        $indices{flipi} = $flipi;
    }
    if ( my $hipi = $self->_calculate_hipi ) {
        $indices{hipi} = $hipi;
    }
    if ( my $ipi = $self->_calculate_ipi ) {
        $indices{ipi} = $ipi;
    }
    
    return \%indices;
}

# calculate Ann-Arbor if HL, FL, DLBCL, EMZL or T-cell (ie not CLL, SMZL, MCL or HCL):
sub _calculate_ann_arbor {
    my $self = shift;
    
    my $data = $self->staging_data; # warn Dumper $data;
    
    my $lymphoid_data = $data->{params}->{lymphoid}; # warn Dumper $lymphoid_data;

    # all previous & current diagnostic categories:
    my $categories_map = $self->diagnostic_categories_map; # warn Dumper $categories_map;
    
    # get hash of involved site names:
    my $site_names = $self->_get_involved_sites(); # warn Dumper $site_names;

	my $ct = $lymphoid_data->{ct} || 'N'; # avoid uninitialized error

    my @categories = qw( Hodgkin FL DLBCL MZL T-lymphoproliferative );
	
    # Ann Arbor calc needs to be one of above, plus had a CT scan:
    return unless ( grep $categories_map->{$_}, @categories ) && $ct eq 'Y';    

    my $i = $self->involved_sites; # numerical representation of site distribution

    my $AA;
    
	# marrow involvement, extensive disease or > 1 extranodal site:
	if ( $i->{extranodal} > 1 || grep $site_names->{$_}, qw(marrow extensive) ) {
	 	$AA = 'IV'; # number of nodal sites not relevant
	}
	# both upper & lower diaphragm involved:
	elsif ( $i->{upper_nodal} && $i->{lower_nodal} ) { $AA = 'III' }
	# either upper or lower diaphragm involved with > 1 group:
	elsif ( $i->{upper_nodal} > 1 || $i->{lower_nodal} > 1 ) { 
		$AA = sprintf 'II[%s]', $i->{upper_nodal} || $i->{lower_nodal};
	}
	# one group only, or no nodal involvement:
	elsif ( ($i->{upper_nodal} + $i->{lower_nodal}) <= 1 ) { $AA = 'I' }
	else { $AA = '[Unclassifiable - check data]' }  # presumably cocked-up the algorithm!!  
	
	$AA .= 'S' if $site_names->{spleen};
	$AA .= 'X' if $site_names->{bulky};
	# ? needs modifying to contiguous/proximal to nodal site:
	$AA .= 'E' if ($i->{extranodal} && $AA !~ /^IV/); # any extranodal & not already IV
	$AA .= 'B' if grep $lymphoid_data->{$_} eq 'Y', qw(sweats wt_loss fever);
    
	# some other calculated indices depend on this value:
    $self->ann_arbor($AA); # warn Dumper $AA;
	return $AA;
}

# calculate IPI if DLBCL or MCL + Ann-Arbor + age: 
sub _calculate_ipi {
    my $self = shift;
    
    # all previous & current diagnostic categories:
    my $categories_map = $self->diagnostic_categories_map;
    
    return unless grep $categories_map->{$_}, qw(DLBCL MCL); 
    
    my $data = $self->staging_data;
    my $age  = $self->patient_age; # warn $age;
    my $AA   = $self->ann_arbor; # warn $AA;
    
    my $i = $self->involved_sites; # numerical representation of site distribution

    my $lymphoid_data = $data->{params}->{lymphoid}; # warn Dumper $lymphoid_data;
    
	my $ecog = $lymphoid_data->{ecog}; # warn $ecog;
	my $ldh  = $lymphoid_data->{ldh}; # warn $ldh;

	my $ipi;

    if ( $ldh && (defined $ecog) && $age &&	$AA ) { # ECOG can be '0'
		my $score = 0 # add 1 for each (range 0 to 4):
			+ ( $AA =~ /^(III|IV)/ )
			+ ( $ecog > 1 )
			+ ( $ldh eq 'raised' )
			+ ( $age > 60 && $i->{extranodal} > 1 ); # only relevant > 60yr

		# works for both IPI & age-adjusted IPI, if age > 60 score cannot be 0,
		# so don't need to consider age 
		my @risk = qw(low low-intermediate high-intermediate high high); 
		
		$ipi = sprintf "%s [%s]", $risk[$score], $score; # warn Dumper $ipi;
	}
	return $ipi;
}

# calculate HIPI if HL: 
sub _calculate_hipi {
    my $self = shift;
    
    # all previous & current diagnostic categories:
    my $categories_map = $self->diagnostic_categories_map;
    
    return unless $categories_map->{Hodgkin};
    
    my $dbix = $self->lims_dbix;

    my $sql = 'select gender FROM patients WHERE id = ?';
	my $gender = $dbix->query($sql, $self->patient_id)->list || 'U';

    my $data = $self->staging_data;
    my $age  = $self->patient_age;
    my $AA   = $self->ann_arbor;
    
	my $lymph_data = $data->{params}->{lymphoid};

	my $albumin = $lymph_data->{albumin};
	my $lymphs  = $lymph_data->{lymphs};
	my $wbc     = $lymph_data->{wbc};
	my $hb      = $lymph_data->{hb};

	my $hipi;

	if ( $age && $AA && $gender # plus all 4 params defined:
			&& !grep { !defined $lymph_data->{$_} } qw/hb albumin wbc lymphs/ ) {
		my $score = 0 # add 1 for each (range 0 to 7):
			+ ( $gender eq 'M')
			+ ( $age >= 45 )
			+ ( $AA =~ /^IV/ )
			+ ( $wbc > 15 )
			+ ( $hb < 10.5 )
			+ ( $lymphs < 0.6 )
			+ ( $albumin < 40 ); # = 4g/dL 

		my $risk = $score <= 2 ? 'low-risk' : 'mod/high risk';
		$hipi = sprintf "%s [%s]", $risk, $score; 
	}
    return $hipi;
}

# calculate FLIPI if FL or diffuse FL: 
sub _calculate_flipi {
    my $self = shift;    
    
    # all previous & current diagnostic categories:
    my $categories_map = $self->diagnostic_categories_map; # warn Dumper $categories_map;
    
    return unless $categories_map->{FL};
    
    my $data = $self->staging_data; # warn Dumper $data;
    my $age  = $self->patient_age; # warn $age;
    my $AA   = $self->ann_arbor; # warn $AA;

    my $i = $self->involved_sites; # numerical representation of site distribution

	my $lymph_data = $data->{params}->{lymphoid};

	my $ldh = $lymph_data->{ldh}; # warn Dumper $ldh;
	my $hb  = $lymph_data->{hb};  # warn Dumper $hb;
	my $ct  = $lymph_data->{ct} || 'N'; # avoid uninitialized error

	my $flipi;

	if ( $age && $AA && $hb && $ldh && $ct eq 'Y' ) {
		my @risk = qw(good good intermediate poor poor poor); # for scores 0 - 5 

		my $score = 0 # add 1 for each (range 0 to 5):
			+ ( $age > 60 )
			+ ( $hb < 12 )
			+ ( $ldh eq 'raised' )
			+ ( $AA =~ /^(III|IV)/ )
			+ ( $i->{upper_nodal} + $i->{lower_nodal} > 4 );

		$flipi = sprintf "%s [%s]", $risk[$score], $score; # warn Dumper $flipi;
	}
    return $flipi;
}

# BINET for CLL:
sub _calculate_binet {
    my $self = shift;
    
    # all previous & current diagnostic categories:
    my $categories_map = $self->diagnostic_categories_map;
    
    return unless $categories_map->{CLL};
    
    my $data = $self->staging_data; # warn Dumper $data;

    # get hash of involved site names:
    my $sites_names = $self->_get_involved_sites();
    
    my $i = $self->involved_sites; # numerical representation of site distribution

	my $lymph_data = $data->{params}->{lymphoid};
    
	my $plts = $lymph_data->{plts};
	my $hb   = $lymph_data->{hb};

	my $binet;

	if ( $hb && $plts ) {
		if ($hb < 10 || $plts < 100) { $binet = 'C' }
		else { # 1 for each of neck, axillae, groin, spleen and liver (assumes CLL does not get CT scan)
			no warnings 'uninitialized'; # adding undef vals causes error
			my $sites = 0 # add numeric data:
				+ $i->{upper_nodal}
				+ $i->{lower_nodal}
				+ $sites_names->{liver};

			$binet = $sites >= 3 ? 'B' : 'A';
		}
	} 
    return $binet;
}

sub _get_involved_sites {
    my $self = shift;
    
    my $data = $self->staging_data; # warn Dumper $data;

    # hashref of involved nodal & extranodal opt. ids:
    my $site_opts = $data->{imaging}->{site_opts}; # warn Dumper $site_opts;
    # map of imaging_options ( id => { description & option_type } ):
    my $option_map = $data->{option_maps}->{imaging_options}->{map}; # warn Dumper $option_map;
    
    # get hash of involved site names:
    my %sites = map +($option_map->{$_}->{description} => 1), keys %$site_opts; # warn Dumper \%sites;
    return \%sites;
}

# get all previous & current diagnostic categories on this patient:
sub _build_diagnostic_categories_map {
    my $self = shift; # warn $self->patient_id;
    
    my $dbix = $self->lims_dbix;
    
	my $sql = $self->sql_lib->retr('hmrn_patient_diagnostic_categories');

	my $categories = $dbix->query($sql, $self->patient_id)->flat; # warn Dumper $categories;
    my %map = map { $_ => 1 } @$categories; # warn Dumper \%map;
    return \%map;    
}

1;