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 params_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;
my $dbix = $self->lims_dbix;
# set some internal params for calculations:
my $data = $args->{params_data}; # warn Dumper $data;
$self->params_data($data);
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_staging_sites');
my $staging_locations = $dbix->query($sql)->map;
SITE: while ( my ($description, $location) = each %$staging_locations ) {
# warn Dumper [$description, $location];
$involved_sites{$location}++ if $data->{staging}->{$description};
}
$self->involved_sites(\%involved_sites); # warn Dumper \%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 $dates = $dbix->query($sql, $patient_id)->hash; # warn Dumper $dates;
$self->inflate_mysql_dates_to_datetime($dates, [ qw(dob diagnosed) ]);
my $age = LIMS::Local::Utils::calculate_age(@{$dates}{qw(dob diagnosed)});
$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->params_data;
# all previous & current diagnostic categories:
my $categories_map = $self->diagnostic_categories_map; # warn Dumper $categories_map;
my @categories = qw( Hodgkin FL DLBCL MZL T-lymphoproliferative );
my $ct = $data->{lymphoid}->{ct} || 'N'; # avoid uninitialized error
# Ann Arbor calc needs to be one of above, plus had a CT scan:
return unless ( grep $categories_map->{$_}, @categories ) && $ct eq 'Y';
my $sites = $data->{staging}; # list of nodal & extranodal sites involved
my $i = $self->involved_sites; # numerical representation of site distribution
my $AA;
# marrow involvement, extensive disease or > 1 extranodal site:
if ( $sites->{marrow} || $sites->{extensive} || $i->{extranodal} > 1 ) {
$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 ($sites->{spleen});
$AA .= 'X' if ($sites->{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 $data->{lymphoid}->{$_} 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->params_data;
my $age = $self->patient_age;
my $AA = $self->ann_arbor;
my $i = $self->involved_sites; # numerical representation of site distribution
my $ecog = $data->{lymphoid}->{ecog};
my $ldh = $data->{lymphoid}->{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->params_data;
my $age = $self->patient_age;
my $AA = $self->ann_arbor;
my $lymph_data = $data->{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;
return unless $categories_map->{FL};
my $data = $self->params_data;
my $age = $self->patient_age;
my $AA = $self->ann_arbor;
my $i = $self->involved_sites; # numerical representation of site distribution
my $ldh = $data->{lymphoid}->{ldh}; # warn Dumper $ldh;
my $hb = $data->{lymphoid}->{hb}; # warn Dumper $hb;
my $ct = $data->{lymphoid}->{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->params_data;
my $sites = $data->{staging}; # list of nodal & extranodal sites involved
my $i = $self->involved_sites; # numerical representation of site distribution
my $binet;
my $plts = $data->{lymphoid}->{plts};
my $hb = $data->{lymphoid}->{hb};
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->{liver};
$binet = $sites >= 3 ? 'B' : 'A';
}
}
return $binet;
}
# 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;