def test_sort_periods_by_set_dates_and_statuses_ips_empty_release_dates( self): state_code = "US_XX" ip_1 = StateIncarcerationPeriod.new_with_defaults( incarceration_period_id=1111, incarceration_type=StateIncarcerationType.STATE_PRISON, status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, state_code=state_code, admission_date=date(2008, 11, 20), admission_reason=StateIncarcerationPeriodAdmissionReason. NEW_ADMISSION, release_date=date(2010, 12, 4), release_reason=StateIncarcerationPeriodReleaseReason. SENTENCE_SERVED, ) ip_2 = StateIncarcerationPeriod.new_with_defaults( incarceration_period_id=2222, incarceration_type=StateIncarcerationType.STATE_PRISON, status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, state_code=state_code, admission_date=date(2011, 3, 4), admission_reason=StateIncarcerationPeriodAdmissionReason. NEW_ADMISSION, release_date=date(2014, 4, 14), release_reason=StateIncarcerationPeriodReleaseReason. SENTENCE_SERVED, ) ip_3 = StateIncarcerationPeriod.new_with_defaults( incarceration_period_id=3333, incarceration_type=StateIncarcerationType.STATE_PRISON, status=StateIncarcerationPeriodStatus.IN_CUSTODY, state_code=state_code, admission_date=date(2012, 2, 4), admission_reason=StateIncarcerationPeriodAdmissionReason. NEW_ADMISSION, ) incarceration_periods = [ ip_1, ip_3, ip_2, ] for ip_order_combo in permutations(incarceration_periods): ips_for_test = [attr.evolve(ip) for ip in ip_order_combo] sort_periods_by_set_dates_and_statuses( ips_for_test, incarceration_period_utils._is_active_period, incarceration_period_utils._is_transfer_start, incarceration_period_utils._is_transfer_end, ) self.assertEqual( [ip_1, ip_2, ip_3], ips_for_test, )
def test_sort_periods_by_set_dates_and_statuses_ips_two_same_day_zero_day_periods_release( self, ): state_code = "US_XX" ip_1 = StateIncarcerationPeriod.new_with_defaults( external_id="X", incarceration_period_id=1111, incarceration_type=StateIncarcerationType.STATE_PRISON, status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, state_code=state_code, admission_date=date(2008, 11, 20), admission_reason=StateIncarcerationPeriodAdmissionReason. ADMITTED_FROM_SUPERVISION, release_date=date(2009, 1, 1), release_reason=StateIncarcerationPeriodReleaseReason.TRANSFER, ) ip_2 = StateIncarcerationPeriod.new_with_defaults( external_id="Z", incarceration_period_id=2222, incarceration_type=StateIncarcerationType.STATE_PRISON, status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, state_code=state_code, admission_date=date(2009, 1, 1), admission_reason=StateIncarcerationPeriodAdmissionReason.TRANSFER, release_date=date(2009, 1, 1), release_reason=StateIncarcerationPeriodReleaseReason.TRANSFER, ) ip_3 = StateIncarcerationPeriod.new_with_defaults( external_id="Y", incarceration_period_id=3333, incarceration_type=StateIncarcerationType.STATE_PRISON, status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, state_code=state_code, admission_date=date(2009, 1, 1), admission_reason=StateIncarcerationPeriodAdmissionReason.TRANSFER, release_date=date(2009, 1, 1), release_reason=StateIncarcerationPeriodReleaseReason. CONDITIONAL_RELEASE, ) incarceration_periods = [ip_1, ip_2, ip_3] for ip_order_combo in permutations(incarceration_periods): ips_for_test = [attr.evolve(ip) for ip in ip_order_combo] sort_periods_by_set_dates_and_statuses( ips_for_test, incarceration_period_utils._is_active_period, incarceration_period_utils._is_transfer_start, incarceration_period_utils._is_transfer_end, ) self.assertEqual( [ip_1, ip_2, ip_3], ips_for_test, )
def standard_date_sort_for_incarceration_periods( incarceration_periods: List[StateIncarcerationPeriod], ) -> List[StateIncarcerationPeriod]: """Sorts incarceration periods chronologically by dates and statuses.""" sort_periods_by_set_dates_and_statuses(incarceration_periods, _is_active_period, _is_transfer_start, _is_transfer_end) return incarceration_periods
def test_sort_periods_by_set_dates_and_statuses_sps(self): supervision_period_1 = StateSupervisionPeriod.new_with_defaults( state_code="US_XX", start_date=date(2000, 1, 1), termination_date=date(2010, 8, 30), status=StateSupervisionPeriodStatus.PRESENT_WITHOUT_INFO, ) supervision_period_2 = StateSupervisionPeriod.new_with_defaults( state_code="US_XX", start_date=date(2006, 3, 1), termination_date=date(2010, 9, 1), status=StateSupervisionPeriodStatus.PRESENT_WITHOUT_INFO, ) supervision_period_3 = StateSupervisionPeriod.new_with_defaults( state_code="US_XX", start_date=date(2012, 12, 1), termination_date=date(2018, 2, 4), status=StateSupervisionPeriodStatus.PRESENT_WITHOUT_INFO, ) periods = [ supervision_period_1, supervision_period_2, supervision_period_3 ] for sp_order_combo in permutations(periods): sps_for_test = [attr.evolve(sp) for sp in sp_order_combo] sort_periods_by_set_dates_and_statuses( sps_for_test, supervision_period_utils._is_active_period, supervision_period_utils._is_transfer_start, supervision_period_utils._is_transfer_end, ) self.assertEqual( [ supervision_period_1, supervision_period_2, supervision_period_3 ], sps_for_test, )
def _infer_missing_dates_and_statuses( supervision_periods: List[StateSupervisionPeriod], ) -> List[StateSupervisionPeriod]: """First, sorts the supervision_periods in chronological order of the start and termination dates. Then, for any periods missing dates and statuses, infers this information given the other supervision periods. """ sort_periods_by_set_dates_and_statuses(supervision_periods, _is_active_period) updated_periods: List[StateSupervisionPeriod] = [] for sp in supervision_periods: if sp.termination_date is None: if sp.status != StateSupervisionPeriodStatus.UNDER_SUPERVISION: # If the person is not under supervision on this period, set the termination date to the start date. sp.termination_date = sp.start_date sp.termination_reason = TerminationReason.INTERNAL_UNKNOWN elif sp.termination_reason or sp.termination_reason_raw_text: # There is no termination date on this period, but the set termination_reason indicates that the person # is no longer in custody. Set the termination date to the start date. sp.termination_date = sp.start_date sp.status = StateSupervisionPeriodStatus.TERMINATED logging.warning( "No termination_date for supervision period (%d) with nonnull termination_reason (%s) " "or termination_reason_raw_text (%s)", sp.supervision_period_id, sp.termination_reason, sp.termination_reason_raw_text, ) elif sp.termination_date > date.today(): # This is an erroneous termination_date in the future. For the purpose of calculations, clear the # termination_date and the termination_reason. sp.termination_date = None sp.termination_reason = None sp.status = StateSupervisionPeriodStatus.UNDER_SUPERVISION if sp.start_date is None: logging.info("Dropping supervision period without start_date: [%s]", sp) continue if sp.start_date > date.today(): logging.info( "Dropping supervision period with start_date in the future: [%s]", sp ) continue if sp.admission_reason is None: # We have no idea what this admission reason was. Set as INTERNAL_UNKNOWN. sp.admission_reason = AdmissionReason.INTERNAL_UNKNOWN if sp.termination_date is not None and sp.termination_reason is None: # We have no idea what this termination reason was. Set as INTERNAL_UNKNOWN. sp.termination_reason = TerminationReason.INTERNAL_UNKNOWN if sp.start_date and sp.termination_date: if sp.termination_date < sp.start_date: logging.info( "Dropping supervision period with termination before admission: [%s]", sp, ) continue updated_periods.append(sp) return updated_periods
def _infer_missing_dates_and_statuses( incarceration_periods: List[StateIncarcerationPeriod], ) -> List[StateIncarcerationPeriod]: """First, sorts the incarceration_periods in chronological order of the admission and release dates. Then, for any periods missing dates and statuses, infers this information given the other incarceration periods. """ sort_periods_by_set_dates_and_statuses(incarceration_periods, _is_active_period) updated_periods: List[StateIncarcerationPeriod] = [] for index, ip in enumerate(incarceration_periods): previous_ip = incarceration_periods[index - 1] if index > 0 else None next_ip = (incarceration_periods[index + 1] if index < len(incarceration_periods) - 1 else None) if ip.release_date is None: if next_ip: # This is not the last incarceration period in the list. Set the release date to the next admission or # release date. ip.release_date = (next_ip.admission_date if next_ip.admission_date else next_ip.release_date) if ip.release_reason is None: if next_ip.admission_reason == AdmissionReason.TRANSFER: # If they were transferred into the next period, infer that this release was a transfer ip.release_reason = ReleaseReason.TRANSFER ip.status = StateIncarcerationPeriodStatus.NOT_IN_CUSTODY else: # This is the last incarceration period in the list. if ip.status != StateIncarcerationPeriodStatus.IN_CUSTODY: # If the person is no longer in custody on this period, set the release date to the admission date. ip.release_date = ip.admission_date ip.release_reason = ReleaseReason.INTERNAL_UNKNOWN elif ip.release_reason or ip.release_reason_raw_text: # There is no release date on this period, but the set release_reason indicates that the person # is no longer in custody. Set the release date to the admission date. ip.release_date = ip.admission_date ip.status = StateIncarcerationPeriodStatus.NOT_IN_CUSTODY logging.warning( "No release_date for incarceration period (%d) with nonnull release_reason (%s) or " "release_reason_raw_text (%s)", ip.incarceration_period_id, ip.release_reason, ip.release_reason_raw_text, ) elif ip.release_date > date.today(): # This is an erroneous release_date in the future. For the purpose of calculations, clear the release_date # and the release_reason. ip.release_date = None ip.release_reason = None ip.status = StateIncarcerationPeriodStatus.IN_CUSTODY if ip.admission_date is None: if previous_ip: # If the admission date is not set, and this is not the first incarceration period, then set the # admission_date to be the same as the release_date or admission_date of the preceding period ip.admission_date = (previous_ip.release_date if previous_ip.release_date else previous_ip.admission_date) if ip.admission_reason is None: if previous_ip.release_reason == ReleaseReason.TRANSFER: # If they were transferred out of the previous period, infer that this admission was a transfer ip.admission_reason = AdmissionReason.TRANSFER else: # If the admission date is not set, and this is the first incarceration period, then set the # admission_date to be the same as the release_date ip.admission_date = ip.release_date ip.admission_reason = AdmissionReason.INTERNAL_UNKNOWN elif ip.admission_date > date.today(): logging.info( "Dropping incarceration period with admission_date in the future: [%s]", ip, ) continue if ip.admission_reason is None: # We have no idea what this admission reason was. Set as INTERNAL_UNKNOWN. ip.admission_reason = AdmissionReason.INTERNAL_UNKNOWN if ip.release_date is not None and ip.release_reason is None: # We have no idea what this release reason was. Set as INTERNAL_UNKNOWN. ip.release_reason = ReleaseReason.INTERNAL_UNKNOWN if ip.admission_date and ip.release_date: if ip.release_date < ip.admission_date: logging.info( "Dropping incarceration period with release before admission: [%s]", ip, ) continue if updated_periods: most_recent_valid_period = updated_periods[-1] if _ip_is_nested_in_previous_period(ip, most_recent_valid_period): # This period is entirely nested within the period before it. Do not include in the list of periods. logging.info( "Dropping incarceration period [%s] that is nested in period [%s]", ip, most_recent_valid_period, ) continue updated_periods.append(ip) return updated_periods