def test_portion_overlapping_with_month(self): self.assertEqual( None, self.negative_day_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( None, self.zero_day_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( self.one_day_range, self.one_day_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( self.single_month_range, self.single_month_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( TimeRange(lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 3, 1)), self.multi_month_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( TimeRange(lower_bound_inclusive_date=datetime.date(2019, 3, 1), upper_bound_exclusive_date=datetime.date(2019, 4, 1)), self.multi_month_range.portion_overlapping_with_month(2019, 3)) self.assertEqual( TimeRange(lower_bound_inclusive_date=datetime.date(2019, 4, 1), upper_bound_exclusive_date=datetime.date(2019, 4, 10)), self.multi_month_range.portion_overlapping_with_month(2019, 4))
def is_fully_incarcerated_for_range(self, range_to_cover: TimeRange) -> bool: """Returns True if this person is incarcerated for the full duration of the time range.""" months_range_overlaps = range_to_cover.get_months_range_overlaps_at_all( ) if not months_range_overlaps: return False months_without_complete_incarceration = [] for year, month in months_range_overlaps: was_incarcerated_all_month = ( year, month) in self.months_fully_incarcerated if not was_incarcerated_all_month: months_without_complete_incarceration.append((year, month)) for year, month in months_without_complete_incarceration: overlapping_periods = self.month_to_overlapping_incarceration_periods[ year][month] range_portion_overlapping_month = range_to_cover.portion_overlapping_with_month( year, month) if not range_portion_overlapping_month: raise ValueError( f'Expecting only months that overlap with the range, month ({year}, {month}) does not.' ) remaining_ranges_to_cover = self._get_portions_of_range_not_covered_by_periods_subset( range_portion_overlapping_month, overlapping_periods) if remaining_ranges_to_cover: return False return True
def test_exactly_overlapping_ranges(self): range_1 = TimeRange.for_month(2019, 2) range_2 = TimeRange.for_month(2019, 2) time_range_diff = TimeRangeDiff(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_partially_overlapping_ranges(self): range_1 = TimeRange(datetime.date(2019, 2, 5), datetime.date(2019, 5, 2)) range_2 = TimeRange(datetime.date(2019, 3, 1), datetime.date(2019, 6, 5)) time_range_diff = TimeRangeDiff(range_1, range_2) self.assertEqual( TimeRange(datetime.date(2019, 3, 1), datetime.date(2019, 5, 2)), time_range_diff.overlapping_range) self.assertEqual( [TimeRange(datetime.date(2019, 2, 5), datetime.date(2019, 3, 1))], time_range_diff.range_1_non_overlapping_parts) self.assertEqual( [TimeRange(datetime.date(2019, 5, 2), datetime.date(2019, 6, 5))], time_range_diff.range_2_non_overlapping_parts) time_range_diff = TimeRangeDiff(range_2, range_1) self.assertEqual( TimeRange(datetime.date(2019, 3, 1), datetime.date(2019, 5, 2)), time_range_diff.overlapping_range) self.assertEqual( [TimeRange(datetime.date(2019, 5, 2), datetime.date(2019, 6, 5))], time_range_diff.range_1_non_overlapping_parts) self.assertEqual( [TimeRange(datetime.date(2019, 2, 5), datetime.date(2019, 3, 1))], time_range_diff.range_2_non_overlapping_parts)
def _get_portions_of_range_not_covered_by_periods_subset( time_range_to_cover: TimeRange, incarceration_periods_subset: List[StateIncarcerationPeriod] ) -> List[TimeRange]: """Returns a list of time 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: ip_time_range = TimeRange.for_incarceration_period( incarceration_period) new_remaining_ranges_to_cover = [] for time_range in remaining_ranges_to_cover: new_remaining_ranges_to_cover.extend( TimeRangeDiff( range_1=ip_time_range, 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 supervision_period_counts_towards_supervision_population_in_date_range_state_specific( date_range: TimeRange, 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 the supervision population in a 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': sp_range = TimeRange.for_supervision_period(supervision_period) overlapping_range = TimeRangeDiff(range_1=date_range, range_2=sp_range).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 run_is_fully_incarcerated_for_range_check( self, incarceration_periods: List[StateIncarcerationPeriod], range_start_num_days_from_periods_start: int, range_end_num_days_from_periods_end: int, is_fully_incarcerated: bool): period_range_start = incarceration_periods[0].admission_date if not period_range_start: raise ValueError("Expected admission date") period_range_end = incarceration_periods[-1].release_date period_range_end = period_range_end if period_range_end else ( date.today() + timedelta(days=1)) lower_bound_inclusive = period_range_start + timedelta( days=range_start_num_days_from_periods_start) upper_bound_exclusive = period_range_end + timedelta( days=range_end_num_days_from_periods_end) index = IncarcerationPeriodIndex(incarceration_periods) time_range = TimeRange( lower_bound_inclusive_date=lower_bound_inclusive, upper_bound_exclusive_date=upper_bound_exclusive) if is_fully_incarcerated: self.assertTrue(index.is_fully_incarcerated_for_range(time_range)) else: self.assertFalse(index.is_fully_incarcerated_for_range(time_range))
def setUp(self) -> None: self.negative_day_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 2, 2)) self.zero_day_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 2, 3)) self.one_day_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 2, 4)) self.single_month_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 1), upper_bound_exclusive_date=datetime.date(2019, 3, 1)) self.multi_month_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 4, 10))
def _months_fully_incarcerated(self) -> Set[Tuple[int, int]]: """For each StateIncarcerationPeriod, identifies months where the person was incarcerated for every day during that month. Returns a set of months in the format (year, month) for which the person spent the entire month in a prison. """ months_fully_incarcerated: Set[Tuple[int, int]] = set() for incarceration_period in self.incarceration_periods: ip_range = TimeRange.for_incarceration_period(incarceration_period) months_overlaps_at_all = ip_range.get_months_range_overlaps_at_all( ) for year, month in months_overlaps_at_all: overlapping_periods = self.month_to_overlapping_incarceration_periods[ year][month] remaining_ranges_to_cover = self._get_portions_of_range_not_covered_by_periods_subset( TimeRange.for_month(year, month), overlapping_periods) if not remaining_ranges_to_cover: months_fully_incarcerated.add((year, month)) return months_fully_incarcerated
def test_no_periods(self): index = IncarcerationPeriodIndex([]) self.assertFalse( index.is_fully_incarcerated_for_range( TimeRange(lower_bound_inclusive_date=date(2019, 1, 2), upper_bound_exclusive_date=date(2020, 2, 1)))) self.assertFalse( index.is_fully_incarcerated_for_range( TimeRange(lower_bound_inclusive_date=date(2019, 1, 1), upper_bound_exclusive_date=date(2019, 2, 1)))) self.assertFalse( index.is_fully_incarcerated_for_range( TimeRange(lower_bound_inclusive_date=date(2019, 1, 1), upper_bound_exclusive_date=date(2019, 1, 2)))) self.assertFalse( index.is_fully_incarcerated_for_range( TimeRange(lower_bound_inclusive_date=date(2019, 1, 1), upper_bound_exclusive_date=date(2019, 1, 1))))
def _month_to_overlapping_incarceration_periods( self) -> Dict[int, Dict[int, List[StateIncarcerationPeriod]]]: month_to_overlapping_incarceration_periods: Dict[int, Dict[int, List[StateIncarcerationPeriod]]] = \ defaultdict(lambda: defaultdict(list)) for incarceration_period in self.incarceration_periods: ip_time_range = TimeRange.for_incarceration_period( incarceration_period) for year, month in ip_time_range.get_months_range_overlaps_at_all( ): month_to_overlapping_incarceration_periods[year][month].append( incarceration_period) return month_to_overlapping_incarceration_periods
class TestTimeRange(unittest.TestCase): """Tests for TimeRange""" def setUp(self) -> None: self.negative_day_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 2, 2)) self.zero_day_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 2, 3)) self.one_day_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 2, 4)) self.single_month_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 1), upper_bound_exclusive_date=datetime.date(2019, 3, 1)) self.multi_month_range = TimeRange( lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 4, 10)) def test_get_months_range_overlaps_at_all(self): self.assertEqual( [], self.negative_day_range.get_months_range_overlaps_at_all()) self.assertEqual( [], self.zero_day_range.get_months_range_overlaps_at_all()) self.assertEqual([(2019, 2)], self.one_day_range.get_months_range_overlaps_at_all()) self.assertEqual( [(2019, 2)], self.single_month_range.get_months_range_overlaps_at_all()) self.assertEqual( [(2019, 2), (2019, 3), (2019, 4)], self.multi_month_range.get_months_range_overlaps_at_all()) def test_portion_overlapping_with_month(self): self.assertEqual( None, self.negative_day_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( None, self.zero_day_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( self.one_day_range, self.one_day_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( self.single_month_range, self.single_month_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( TimeRange(lower_bound_inclusive_date=datetime.date(2019, 2, 3), upper_bound_exclusive_date=datetime.date(2019, 3, 1)), self.multi_month_range.portion_overlapping_with_month(2019, 2)) self.assertEqual( TimeRange(lower_bound_inclusive_date=datetime.date(2019, 3, 1), upper_bound_exclusive_date=datetime.date(2019, 4, 1)), self.multi_month_range.portion_overlapping_with_month(2019, 3)) self.assertEqual( TimeRange(lower_bound_inclusive_date=datetime.date(2019, 4, 1), upper_bound_exclusive_date=datetime.date(2019, 4, 10)), self.multi_month_range.portion_overlapping_with_month(2019, 4))