def _classify_buckets_by_relevant_metric_periods( supervision_time_buckets: List[SupervisionTimeBucket], metric_period_end_date: date ) -> Dict[int, List[SupervisionTimeBucket]]: """Returns a dictionary mapping metric period month values to the corresponding relevant SupervisionTimeBuckets.""" periods_and_buckets: Dict[int, List[SupervisionTimeBucket]] = defaultdict() # Organize the month buckets by the relevant metric periods for supervision_time_bucket in supervision_time_buckets: bucket_start_date = date(supervision_time_bucket.year, supervision_time_bucket.month, 1) relevant_periods = relevant_metric_periods( bucket_start_date, metric_period_end_date.year, metric_period_end_date.month) if relevant_periods: for period in relevant_periods: period_events = periods_and_buckets.get(period) if period_events: period_events.append(supervision_time_bucket) else: periods_and_buckets[period] = [supervision_time_bucket] return periods_and_buckets
def map_recidivism_count_combinations( characteristic_combos: List[Dict[str, Any]], event: ReleaseEvent, all_reincarcerations: Dict[date, Dict[str, Any]], metric_period_end_date: date) -> \ List[Tuple[Dict[str, Any], Any]]: """Maps the given event and characteristic combinations to a variety of metrics that track count-based recidivism. If the event is a RecidivismReleaseEvent, then a count of reincarceration occurred. This produces metrics for both the year and the month in which the person was reincarcerated. Args: characteristic_combos: A list of dictionaries containing all unique combinations of characteristics. event: the recidivism event from which the combination was derived all_reincarcerations: dictionary where the keys are all dates of reincarceration for the person's ReleaseEvents, and the values are a dictionary containing return type and from supervision type information metric_period_end_date: The day the metric periods end Returns: A list of key-value tuples representing specific metric combinations and the recidivism value corresponding to that metric. """ metrics = [] if isinstance(event, RecidivismReleaseEvent): reincarceration_date = event.reincarceration_date relevant_periods = relevant_metric_periods( reincarceration_date, metric_period_end_date.year, metric_period_end_date.month) for combo in characteristic_combos: combo['metric_type'] = ReincarcerationRecidivismMetricType.COUNT # Bucket for the month of the incarceration combo['year'] = reincarceration_date.year combo['month'] = reincarceration_date.month combo['metric_period_months'] = 1 end_of_event_month = last_day_of_month(reincarceration_date) metrics.extend( combination_count_metrics(combo, event, all_reincarcerations, end_of_event_month)) # Bucket for each of the relevant metric period month lengths for relevant_period in relevant_periods: combo['year'] = metric_period_end_date.year combo['month'] = metric_period_end_date.month combo['metric_period_months'] = relevant_period metrics.extend( combination_count_metrics(combo, event, all_reincarcerations, metric_period_end_date)) return metrics
def test_relevant_metric_periods_all_periods(self): """Tests the relevant_metric_periods function when all metric periods are relevant.""" event_date = date(2020, 1, 3) end_year = 2020 end_month = 1 relevant_periods = calculator_utils.relevant_metric_periods( event_date, end_year, end_month) expected_periods = [36, 12, 6, 3] self.assertEqual(expected_periods, relevant_periods)
def test_relevant_metric_periods_first_of_month(self): """Tests the relevant_metric_periods function when the event is on the first day of the month of the 36 month period start.""" event_date = date(2005, 2, 1) end_year = 2008 end_month = 1 relevant_periods = calculator_utils.relevant_metric_periods( event_date, end_year, end_month) expected_periods = [36] self.assertEqual(expected_periods, relevant_periods)
def test_relevant_metric_periods_end_of_month(self): """Tests the relevant_metric_periods function when the event is on the last day of the month of the end of the metric period.""" event_date = date(2008, 1, 31) end_year = 2008 end_month = 1 relevant_periods = calculator_utils.relevant_metric_periods( event_date, end_year, end_month) expected_periods = [36, 12, 6, 3] self.assertEqual(expected_periods, relevant_periods)
def test_relevant_metric_periods_none_relevant(self): """Tests the relevant_metric_periods function when no metric periods are relevant.""" event_date = date(2001, 2, 23) end_year = 2008 end_month = 1 relevant_periods = calculator_utils.relevant_metric_periods( event_date, end_year, end_month) expected_periods = [] self.assertEqual(expected_periods, relevant_periods)
def test_relevant_metric_periods_only_36(self): """Tests the relevant_metric_periods function when only the 36 month period is relevant.""" event_date = date(2006, 3, 3) end_year = 2008 end_month = 1 relevant_periods = calculator_utils.relevant_metric_periods( event_date, end_year, end_month) expected_periods = [36] self.assertEqual(expected_periods, relevant_periods)
def test_relevant_metric_periods_all_after_6(self): """Tests the relevant_metric_periods function when all metric periods are relevant except the 1, 3, and 6 month periods.""" event_date = date(2007, 2, 3) end_year = 2008 end_month = 1 relevant_periods = calculator_utils.relevant_metric_periods( event_date, end_year, end_month) expected_periods = [36, 12] self.assertEqual(expected_periods, relevant_periods)
def map_program_combinations( person: StatePerson, program_events: List[ProgramEvent], metric_inclusions: Dict[ProgramMetricType, bool], calculation_end_month: Optional[str], calculation_month_count: int) -> List[Tuple[Dict[str, Any], Any]]: """Transforms ProgramEvents and a StatePerson into metric combinations. Takes in a StatePerson and all of her ProgramEvents and returns an array of "program combinations". These are key-value pairs where the key represents a specific metric and the value represents whether or not the person should be counted as a positive instance of that metric. This translates a particular interaction with a program into many different program metrics. Args: person: the StatePerson program_events: A list of ProgramEvents for the given StatePerson. metric_inclusions: A dictionary where the keys are each ProgramMetricType, and the values are boolean flags for whether or not to include that metric type in the calculations calculation_end_month: The year and month in YYYY-MM format of the last month for which metrics should be calculated. If unset, ends with the current month. calculation_month_count: The number of months (including the month of the calculation_end_month) to limit the monthly calculation output to. If set to -1, does not limit the calculations. Returns: A list of key-value tuples representing specific metric combinations and the value corresponding to that metric. """ metrics: List[Tuple[Dict[str, Any], Any]] = [] periods_and_events: Dict[int, List[ProgramEvent]] = defaultdict() calculation_month_upper_bound = get_calculation_month_upper_bound_date( calculation_end_month) # If the calculations include the current month, then we will calculate person-based metrics for each metric # period in METRIC_PERIOD_MONTHS ending with the current month include_metric_period_output = calculation_month_upper_bound == get_calculation_month_upper_bound_date( date.today().strftime('%Y-%m')) if include_metric_period_output: # Organize the events by the relevant metric periods for program_event in program_events: relevant_periods = relevant_metric_periods( program_event.event_date, calculation_month_upper_bound.year, calculation_month_upper_bound.month) if relevant_periods: for period in relevant_periods: period_events = periods_and_events.get(period) if period_events: period_events.append(program_event) else: periods_and_events[period] = [program_event] calculation_month_lower_bound = get_calculation_month_lower_bound_date( calculation_month_upper_bound, calculation_month_count) for program_event in program_events: if isinstance(program_event, ProgramReferralEvent) and metric_inclusions.get( ProgramMetricType.REFERRAL): characteristic_combo = characteristics_dict(person, program_event) program_referral_metrics_event_based = map_metric_combinations( characteristic_combo, program_event, calculation_month_upper_bound, calculation_month_lower_bound, program_events, periods_and_events, ProgramMetricType.REFERRAL, include_metric_period_output) metrics.extend(program_referral_metrics_event_based) elif (isinstance(program_event, ProgramParticipationEvent) and metric_inclusions.get(ProgramMetricType.PARTICIPATION)): characteristic_combo = characteristics_dict(person, program_event) program_participation_metrics_event_based = map_metric_combinations( characteristic_combo, program_event, calculation_month_upper_bound, calculation_month_lower_bound, program_events, periods_and_events, ProgramMetricType.PARTICIPATION, # The ProgramParticipationMetric is explicitly a daily metric include_metric_period_output=False) metrics.extend(program_participation_metrics_event_based) return metrics
def map_incarceration_combinations( person: StatePerson, incarceration_events: List[IncarcerationEvent], metric_inclusions: Dict[IncarcerationMetricType, bool], calculation_end_month: Optional[str], calculation_month_count: int) -> List[Tuple[Dict[str, Any], Any]]: """Transforms IncarcerationEvents and a StatePerson into metric combinations. Takes in a StatePerson and all of their IncarcerationEvent and returns an array of "incarceration combinations". These are key-value pairs where the key represents a specific metric and the value represents whether or not the person should be counted as a positive instance of that metric. This translates a particular incarceration event, e.g. admission or release, into many different incarceration metrics. Args: person: the StatePerson incarceration_events: A list of IncarcerationEvents for the given StatePerson. metric_inclusions: A dictionary where the keys are each IncarcerationMetricType, and the values are boolean flags for whether or not to include that metric type in the calculations calculation_end_month: The year and month in YYYY-MM format of the last month for which metrics should be calculated. If unset, ends with the current month. calculation_month_count: The number of months (including the month of the calculation_month_upper_bound) to limit the monthly calculation output to. If set to -1, does not limit the calculations. Returns: A list of key-value tuples representing specific metric combinations and the value corresponding to that metric. """ metrics: List[Tuple[Dict[str, Any], Any]] = [] periods_and_events: Dict[int, List[IncarcerationEvent]] = defaultdict() calculation_month_upper_bound = get_calculation_month_upper_bound_date( calculation_end_month) # If the calculations include the current month, then we will calculate person-based metrics for each metric # period in METRIC_PERIOD_MONTHS ending with the current month include_metric_period_output = calculation_month_upper_bound == get_calculation_month_upper_bound_date( date.today().strftime('%Y-%m')) if include_metric_period_output: # Organize the events by the relevant metric periods for incarceration_event in incarceration_events: relevant_periods = relevant_metric_periods( incarceration_event.event_date, calculation_month_upper_bound.year, calculation_month_upper_bound.month) if relevant_periods: for period in relevant_periods: period_events = periods_and_events.get(period) if period_events: period_events.append(incarceration_event) else: periods_and_events[period] = [incarceration_event] calculation_month_lower_bound = get_calculation_month_lower_bound_date( calculation_month_upper_bound, calculation_month_count) for incarceration_event in incarceration_events: metric_type = METRIC_TYPES.get(type(incarceration_event)) if not metric_type: raise ValueError( 'No metric type mapped to incarceration event of type {}'. format(type(incarceration_event))) if metric_inclusions.get(metric_type): characteristic_combo = characteristics_dict( person, incarceration_event) metrics.extend( map_metric_combinations(characteristic_combo, incarceration_event, calculation_month_upper_bound, calculation_month_lower_bound, incarceration_events, periods_and_events, metric_type, include_metric_period_output)) return metrics
def map_incarceration_combinations(person: StatePerson, incarceration_events: List[IncarcerationEvent], inclusions: Dict[str, bool], calculation_month_limit: int) -> List[Tuple[Dict[str, Any], Any]]: """Transforms IncarcerationEvents and a StatePerson into metric combinations. Takes in a StatePerson and all of their IncarcerationEvent and returns an array of "incarceration combinations". These are key-value pairs where the key represents a specific metric and the value represents whether or not the person should be counted as a positive instance of that metric. This translates a particular incarceration event, e.g. admission or release, into many different incarceration metrics. Each metric represents one of many possible combinations of characteristics being tracked for that event. For example, if a White male is admitted to prison, there is a metric that corresponds to White people, one to males, one to White males, one to all people, and more depending on other dimensions in the data. Args: person: the StatePerson incarceration_events: A list of IncarcerationEvents for the given StatePerson. inclusions: A dictionary containing the following keys that correspond to characteristic dimensions: - age_bucket - ethnicity - gender - race Where the values are boolean flags indicating whether to include the dimension in the calculations. calculation_month_limit: The number of months (including this one) to limit the monthly calculation output to. If set to -1, does not limit the calculations. Returns: A list of key-value tuples representing specific metric combinations and the value corresponding to that metric. """ metrics: List[Tuple[Dict[str, Any], Any]] = [] periods_and_events: Dict[int, List[IncarcerationEvent]] = defaultdict() # We will calculate person-based metrics for each metric period in METRIC_PERIOD_MONTHS ending with the current # month metric_period_end_date = last_day_of_month(date.today()) calculation_month_lower_bound = get_calculation_month_lower_bound_date( metric_period_end_date, calculation_month_limit) # Organize the events by the relevant metric periods for incarceration_event in incarceration_events: relevant_periods = relevant_metric_periods( incarceration_event.event_date, metric_period_end_date.year, metric_period_end_date.month) if relevant_periods: for period in relevant_periods: period_events = periods_and_events.get(period) if period_events: period_events.append(incarceration_event) else: periods_and_events[period] = [incarceration_event] for incarceration_event in incarceration_events: metric_type = METRIC_TYPES.get(type(incarceration_event)) if not metric_type: raise ValueError( 'No metric type mapped to incarceration event of type {}'.format(type(incarceration_event))) characteristic_combos = characteristic_combinations(person, incarceration_event, inclusions, metric_type) metrics.extend(map_metric_combinations( characteristic_combos, incarceration_event, metric_period_end_date, calculation_month_lower_bound, incarceration_events, periods_and_events, metric_type )) return metrics