def test_range_fully_overlaps_other(self) -> None: range_1 = DateRange(datetime.date(2019, 2, 5), datetime.date(2019, 5, 2)) range_2 = DateRange(datetime.date(2019, 3, 1), datetime.date(2019, 3, 5)) time_range_diff = DateRangeDiff(range_1, range_2) self.assertEqual(range_2, time_range_diff.overlapping_range) self.assertEqual( [ DateRange(datetime.date(2019, 2, 5), datetime.date(2019, 3, 1)), DateRange(datetime.date(2019, 3, 5), datetime.date(2019, 5, 2)), ], time_range_diff.range_1_non_overlapping_parts, ) self.assertEqual([], time_range_diff.range_2_non_overlapping_parts) time_range_diff = DateRangeDiff(range_2, range_1) self.assertEqual(range_2, time_range_diff.overlapping_range) self.assertEqual([], time_range_diff.range_1_non_overlapping_parts) self.assertEqual( [ DateRange(datetime.date(2019, 2, 5), datetime.date(2019, 3, 1)), DateRange(datetime.date(2019, 3, 5), datetime.date(2019, 5, 2)), ], time_range_diff.range_2_non_overlapping_parts, )
def test_partially_overlapping_ranges(self): range_1 = DateRange(datetime.date(2019, 2, 5), datetime.date(2019, 5, 2)) range_2 = DateRange(datetime.date(2019, 3, 1), datetime.date(2019, 6, 5)) time_range_diff = DateRangeDiff(range_1, range_2) self.assertEqual( DateRange(datetime.date(2019, 3, 1), datetime.date(2019, 5, 2)), time_range_diff.overlapping_range) self.assertEqual( [DateRange(datetime.date(2019, 2, 5), datetime.date(2019, 3, 1))], time_range_diff.range_1_non_overlapping_parts) self.assertEqual( [DateRange(datetime.date(2019, 5, 2), datetime.date(2019, 6, 5))], time_range_diff.range_2_non_overlapping_parts) time_range_diff = DateRangeDiff(range_2, range_1) self.assertEqual( DateRange(datetime.date(2019, 3, 1), datetime.date(2019, 5, 2)), time_range_diff.overlapping_range) self.assertEqual( [DateRange(datetime.date(2019, 5, 2), datetime.date(2019, 6, 5))], time_range_diff.range_1_non_overlapping_parts) self.assertEqual( [DateRange(datetime.date(2019, 2, 5), datetime.date(2019, 3, 1))], time_range_diff.range_2_non_overlapping_parts)
def supervision_period_counts_towards_supervision_population_in_date_range_state_specific( date_range: DateRange, supervision_sentences: List[StateSupervisionSentence], incarceration_sentences: List[StateIncarcerationSentence], supervision_period: StateSupervisionPeriod, ) -> bool: """Returns False if there is state-specific information to indicate that the supervision period should not count towards any supervision metrics in the date range. Returns True if either there is a state-specific check that indicates that the supervision period should count or if there is no state-specific check to perform. """ if supervision_period.state_code == "US_MO": overlapping_range = DateRangeDiff( range_1=date_range, range_2=supervision_period.duration ).overlapping_range if not overlapping_range: return False return ( us_mo_get_most_recent_supervision_period_supervision_type_before_upper_bound_day( upper_bound_exclusive_date=overlapping_range.upper_bound_exclusive_date, lower_bound_inclusive_date=overlapping_range.lower_bound_inclusive_date, incarceration_sentences=incarceration_sentences, supervision_sentences=supervision_sentences, ) is not None ) return True
def ip_is_nested_in_previous_period( ip: StateIncarcerationPeriod, previous_ip: StateIncarcerationPeriod) -> bool: """Returns whether the StateIncarcerationPeriod |ip| is entirely nested within the |previous_ip|. Both periods must have set admission and release dates. A nested period is defined as an incarceration period that overlaps with the previous_ip and has no parts that are non-overlapping with the previous_ip. Single-day periods (admission_date = release_date) by definition do not have overlapping ranges with another period because the ranges are end date exclusive. If a single-day period falls within the admission and release of the previous_ip, then it is nested within that period. If a single-day period falls on the previous_ip.release_date, then it is not nested within that period. """ ip_range_diff = DateRangeDiff(ip.duration, previous_ip.duration) if not ip.admission_date or not ip.release_date: raise ValueError(f"ip cannot have unset dates: {ip}") if not previous_ip.admission_date or not previous_ip.release_date: raise ValueError(f"previous_ip cannot have unset dates: {previous_ip}") if ip.admission_date < previous_ip.admission_date: raise ValueError( "previous_ip should be sorted after ip. Error in _sort_ips_by_set_dates_and_statuses. " f"ip: {ip}, previous_ip: {previous_ip}") return (ip_range_diff.overlapping_range and not ip_range_diff.range_1_non_overlapping_parts) or ( ip.admission_date == ip.release_date and ip.release_date < previous_ip.release_date)
def test_exactly_overlapping_ranges(self): range_1 = DateRange.for_month(2019, 2) range_2 = DateRange.for_month(2019, 2) time_range_diff = DateRangeDiff(range_1, range_2) self.assertEqual(range_1, time_range_diff.overlapping_range) self.assertEqual([], time_range_diff.range_1_non_overlapping_parts) self.assertEqual([], time_range_diff.range_2_non_overlapping_parts)
def test_non_overlapping_ranges(self) -> None: range_1 = DateRange.for_month(2019, 2) range_2 = DateRange.for_month(2019, 3) time_range_diff = DateRangeDiff(range_1, range_2) self.assertEqual(None, time_range_diff.overlapping_range) self.assertEqual([range_1], time_range_diff.range_1_non_overlapping_parts) self.assertEqual([range_2], time_range_diff.range_2_non_overlapping_parts)
def _get_portions_of_range_not_covered_by_periods_subset( time_range_to_cover: DateRange, incarceration_periods_subset: List[StateIncarcerationPeriod] ) -> List[DateRange]: """Returns a list of date ranges within the provided |time_range_to_cover| which the provided set of incarceration periods does not fully overlap. """ remaining_ranges_to_cover = [time_range_to_cover] for incarceration_period in incarceration_periods_subset: new_remaining_ranges_to_cover = [] for time_range in remaining_ranges_to_cover: new_remaining_ranges_to_cover.extend( DateRangeDiff( range_1=incarceration_period.duration, range_2=time_range).range_2_non_overlapping_parts) remaining_ranges_to_cover = new_remaining_ranges_to_cover if not remaining_ranges_to_cover: break return remaining_ranges_to_cover
def should_include_in_release_cohort( status: StateIncarcerationPeriodStatus, release_date: Optional[date], release_reason: Optional[ReleaseReason], next_incarceration_period: Optional[StateIncarcerationPeriod]) -> bool: """Identifies whether a period of incarceration with the given features should be included in the release cohort.""" # If the person is still in custody, there is no release to include in a cohort. if status == StateIncarcerationPeriodStatus.IN_CUSTODY: return False if not release_date: # If the person is not in custody, there should be a release_date. # This should not happen after validation. Throw error. raise ValueError("release_date is not set where it should be.") if not release_reason: # If there is no recorded release reason, then we cannot classify this as a valid release for the cohort return False if next_incarceration_period: time_range_release = DateRange.for_day(release_date) if DateRangeDiff(time_range_release, next_incarceration_period.duration).overlapping_range: # If the release overlaps with the following incarceration period, this is not an actual release from # incarceration return False if next_incarceration_period.release_date and release_date == next_incarceration_period.release_date: # This release shares a release_date with the next incarceration period. Do not include this release. return False if release_reason in [ReleaseReason.DEATH, ReleaseReason.EXECUTION]: # If the person was released from this incarceration period because they died or were executed, do not include # them in the release cohort. return False if release_reason == ReleaseReason.ESCAPE: # If the person was released from this incarceration period because they escaped, do not include them in the # release cohort. return False if release_reason == ReleaseReason.RELEASED_FROM_TEMPORARY_CUSTODY: # If the person was released from a period of temporary custody, do not include them in the release_cohort. return False if release_reason == ReleaseReason.RELEASED_IN_ERROR: # If the person was released from this incarceration period due to an error, do not include them in the # release cohort. return False if release_reason == ReleaseReason.TRANSFER: # If the person was released from this incarceration period because they were transferred elsewhere, do not # include them in the release cohort. return False if release_reason == ReleaseReason.TRANSFERRED_OUT_OF_STATE: # Releases where the person has been transferred out of state don't really count as true releases. return False if release_reason == ReleaseReason.COURT_ORDER: # If the person was released from this incarceration period due to a court order, do not include them in the # release cohort. return False if release_reason in (ReleaseReason.EXTERNAL_UNKNOWN, ReleaseReason.INTERNAL_UNKNOWN): # We do not have enough information to determine whether this release qualifies for inclusion in the release # cohort. return False if release_reason in (ReleaseReason.COMMUTED, ReleaseReason.COMPASSIONATE, ReleaseReason.CONDITIONAL_RELEASE, ReleaseReason.PARDONED, ReleaseReason.RELEASED_FROM_ERRONEOUS_ADMISSION, ReleaseReason.SENTENCE_SERVED, ReleaseReason.VACATED): return True raise ValueError("Enum case not handled for " "StateIncarcerationPeriodReleaseReason of type:" f" {release_reason}.")