def combination_referral_monthly_metrics( combo: Dict[str, Any], program_event: ProgramReferralEvent, all_referral_events: List[ProgramReferralEvent]) \ -> List[Tuple[Dict[str, Any], int]]: """Returns all unique referral metrics for the given event and combination. First, includes an event-based count for the month the event occurred with a metric period of 1 month. Then, if this event should be included in the person-based count for the month when the event occurred, adds those person- based metrics. Args: combo: A characteristic combination to convert into metrics program_event: The program event from which the combination was derived all_referral_events: All of this person's ProgramReferralEvents Returns: A list of key-value tuples representing specific metric combination dictionaries and the number 1 representing a positive contribution to that count metric. """ metrics = [] event_date = program_event.event_date event_year = event_date.year event_month = event_date.month # Add event-based combo for the 1-month period the month of the event event_based_same_month_combo = augmented_combo_for_calculations( combo, program_event.state_code, event_year, event_month, MetricMethodologyType.EVENT, 1) metrics.append((event_based_same_month_combo, 1)) # Create the person-based combo for the 1-month period of the month of the event person_based_same_month_combo = augmented_combo_for_calculations( combo, program_event.state_code, event_year, event_month, MetricMethodologyType.PERSON, 1 ) # Get all other referral events that happened the same month as this one all_referral_events_in_event_month = [ event for event in all_referral_events if event.event_date.year == event_date.year and event.event_date.month == event_date.month ] if include_referral_in_count( combo, program_event, last_day_of_month(event_date), all_referral_events_in_event_month): # Include this event in the person-based count metrics.append((person_based_same_month_combo, 1)) return metrics
def combination_incarceration_metric_period_metrics( combo: Dict[str, Any], incarceration_event: IncarcerationEvent, metric_period_end_date: date, periods_and_events: Dict[int, List[IncarcerationEvent]]) \ -> List[Tuple[Dict[str, Any], int]]: """Returns all unique incarceration metrics for the given event, combination, and relevant metric_period_months. Returns metrics for each of the metric period length that this event falls into if this event should be included in the person-based count for that metric period. Args: combo: A characteristic combination to convert into metrics incarceration_event: The IncarcerationEvent from which the combination was derived metric_period_end_date: The day the metric periods end periods_and_events: Dictionary mapping metric period month lengths to the IncarcerationEvents that fall in that period Returns: A list of key-value tuples representing specific metric combination dictionaries and the number 1 representing a positive contribution to that count metric. """ metrics = [] period_end_year = metric_period_end_date.year period_end_month = metric_period_end_date.month for period_length, events_in_period in periods_and_events.items(): if incarceration_event in events_in_period: # This event falls within this metric period person_based_period_combo = augmented_combo_for_calculations( combo, incarceration_event.state_code, period_end_year, period_end_month, MetricMethodologyType.PERSON, period_length) related_events_in_period: List[IncarcerationEvent] = [] if isinstance(incarceration_event, IncarcerationAdmissionEvent): related_events_in_period = [ event for event in events_in_period if isinstance(event, IncarcerationAdmissionEvent) ] elif isinstance(incarceration_event, IncarcerationReleaseEvent): related_events_in_period = [ event for event in events_in_period if isinstance(event, IncarcerationReleaseEvent) ] if related_events_in_period and include_event_in_count( incarceration_event, metric_period_end_date, related_events_in_period): # Include this event in the person-based count for this time period metrics.append((person_based_period_combo, 1)) return metrics
def combination_rate_metrics( combo: Dict[str, Any], event: ReleaseEvent, reincarcerations_by_follow_up_period: Dict[int, List[RecidivismReleaseEvent]], ) -> List[Tuple[Dict[str, Any], int]]: """Returns all recidivism rate metrics for the combo given the relevant follow-up periods. Args: combo: a characteristic combination to convert into metrics event: the release event from which the combination was derived reincarcerations_by_follow_up_period: dictionary where the keys are all relevant periods for measurement, and the values are lists of dictionaries representing the reincarceration admissions during that period Returns: A list of key-value tuples representing specific metric combination dictionaries and the recidivism value corresponding to that metric. """ metrics = [] for ( period, reincarceration_admissions, ) in reincarcerations_by_follow_up_period.items(): augmented_combo = augmented_combo_for_calculations( combo, event.state_code, metric_type=ReincarcerationRecidivismMetricType. REINCARCERATION_RATE, ) augmented_combo["follow_up_period"] = period # If they didn't recidivate at all or not yet for this period (or they didn't recidivate until 10 years had # passed), assign a value of 0. if (isinstance(event, NonRecidivismReleaseEvent) or not reincarceration_admissions): metrics.append((augmented_combo, 0)) # If they recidivated, each unique release of a given person within a follow-up period after the year of release # may be counted as an instance of recidivism for event-based measurement. elif isinstance(event, RecidivismReleaseEvent): for reincarceration in reincarceration_admissions: event_combo_copy = augmented_combo.copy() event_combo_copy["return_type"] = reincarceration.return_type event_combo_copy[ "from_supervision_type"] = reincarceration.from_supervision_type event_combo_copy[ "source_violation_type"] = reincarceration.source_violation_type metrics.append((event_combo_copy, 1)) return metrics
def combination_supervision_metric_period_metrics( combo: Dict[str, Any], supervision_time_bucket: SupervisionTimeBucket, metric_period_end_date: date, periods_and_buckets: Dict[int, List[SupervisionTimeBucket]], metric_type: SupervisionMetricType) \ -> List[Tuple[Dict[str, Any], int]]: """Returns all unique supervision metrics for the given time bucket and combination for each of the relevant metric_period_months. Returns metrics for each of the metric period lengths that this event falls into if this event should be included in the person-based count for that metric period length. Args: combo: A characteristic combination to convert into metrics supervision_time_bucket: The SupervisionTimeBucket from which the combination was derived metric_period_end_date: The day the metric periods end periods_and_buckets: Dictionary mapping metric period month lengths to the SupervisionTimeBuckets that fall in that period metric_type: The type of metric being tracked by this combo Returns: A list of key-value tuples representing specific metric combination dictionaries and the the metric value corresponding to that metric. """ metrics: List[Tuple[Dict[str, Any], int]] = [] period_end_year = metric_period_end_date.year period_end_month = metric_period_end_date.month for period_length, buckets_in_period in periods_and_buckets.items(): if supervision_time_bucket in buckets_in_period: # This event falls within this metric period person_based_period_combo = augmented_combo_for_calculations( combo, supervision_time_bucket.state_code, period_end_year, period_end_month, MetricMethodologyType.PERSON, period_length) relevant_buckets_in_period: List[SupervisionTimeBucket] = [] if metric_type == SupervisionMetricType.ASSESSMENT_CHANGE: # Get all other supervision time buckets for this period that should contribute to an assessment change # metric relevant_buckets_in_period = [ bucket for bucket in buckets_in_period if (isinstance(bucket, SupervisionTerminationBucket)) ] elif metric_type == SupervisionMetricType.POPULATION: # Get all other supervision time buckets for this period that should contribute to a population metric relevant_buckets_in_period = [ bucket for bucket in buckets_in_period if (isinstance(bucket, ( RevocationReturnSupervisionTimeBucket, NonRevocationReturnSupervisionTimeBucket))) ] elif metric_type in ( SupervisionMetricType.REVOCATION, SupervisionMetricType.REVOCATION_ANALYSIS, SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS): # Get all other revocation return time buckets for this period relevant_buckets_in_period = [ bucket for bucket in buckets_in_period if isinstance( bucket, RevocationReturnSupervisionTimeBucket) ] elif metric_type in ( SupervisionMetricType.SUCCESS, SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED): # Get all other projected completion buckets in this period relevant_buckets_in_period = [ bucket for bucket in buckets_in_period if (isinstance( bucket, ProjectedSupervisionCompletionBucket)) ] if relevant_buckets_in_period and include_supervision_in_count( combo, supervision_time_bucket, relevant_buckets_in_period, metric_type): person_combo_value = _person_combo_value( combo, supervision_time_bucket, relevant_buckets_in_period, metric_type) # Include this event in the person-based count metrics.append((person_based_period_combo, person_combo_value)) return metrics
def combination_supervision_monthly_metrics( combo: Dict[str, Any], supervision_time_bucket: SupervisionTimeBucket, all_supervision_time_buckets: List[SupervisionTimeBucket], metric_type: SupervisionMetricType ) -> List[Tuple[Dict[str, Any], int]]: """Returns all unique supervision metrics for the given time bucket and combination for the month of the bucket. First, includes an event-based count for the month the SupervisionTimeBucket represents. If this bucket of supervision should be included in the person-based count for the month when the supervision occurred, adds those person-based metrics. Args: combo: A characteristic combination to convert into metrics supervision_time_bucket: The SupervisionTimeBucket from which the combination was derived all_supervision_time_buckets: All of this person's SupervisionTimeBuckets metric_type: The type of metric being tracked by this combo Returns: A list of key-value tuples representing specific metric combination dictionaries and the the metric value corresponding to that metric. """ metrics: List[Tuple[Dict[str, Any], int]] = [] bucket_year = supervision_time_bucket.year bucket_month = supervision_time_bucket.month base_metric_period = 1 # Add event-based combo for the base metric period of the month and year of the bucket event_based_same_bucket_combo = augmented_combo_for_calculations( combo, supervision_time_bucket.state_code, bucket_year, bucket_month, MetricMethodologyType.EVENT, base_metric_period) event_combo_value = None if isinstance(supervision_time_bucket, ProjectedSupervisionCompletionBucket): if metric_type == SupervisionMetricType.SUCCESS: # Set 1 for successful completion, 0 for unsuccessful completion event_combo_value = 1 if supervision_time_bucket.successful_completion else 0 elif metric_type == SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED: if supervision_time_bucket.sentence_days_served is not None: # Only include this combo if there is a recorded number of days served. Set the value as the number of # days served. event_combo_value = supervision_time_bucket.sentence_days_served else: # If there's no recorded days served on this completion bucket, don't include it in any of the # successful sentence days served metrics. pass else: raise ValueError( f"Unsupported metric type {metric_type} for ProjectedSupervisionCompletionBucket." ) elif metric_type == SupervisionMetricType.ASSESSMENT_CHANGE and \ isinstance(supervision_time_bucket, SupervisionTerminationBucket): if supervision_time_bucket.assessment_score_change is not None: # Only include this combo if there is an assessment score change associated with this termination. Set the # value as the assessment score change event_combo_value = supervision_time_bucket.assessment_score_change else: # The only metric relying on the SupervisionTerminationBuckets is the # TerminatedSupervisionAssessmentScoreChangeMetric. So, if there's no recorded assessment score change on # this termination, don't include it in any of the metrics. pass else: # The default value for all combos is 1 event_combo_value = 1 if event_combo_value is None: # If the event_combo_value is not set, then exclude this bucket from all metrics return metrics # TODO(2913): Exclude combos with a supervision_type of DUAL from event-based counts metrics.append((event_based_same_bucket_combo, event_combo_value)) # Create the person-based combo for the base metric period of the month of the bucket person_based_same_bucket_combo = augmented_combo_for_calculations( combo, supervision_time_bucket.state_code, bucket_year, bucket_month, MetricMethodologyType.PERSON, base_metric_period) buckets_in_period: List[SupervisionTimeBucket] = [] if metric_type == SupervisionMetricType.POPULATION: # Get all other supervision time buckets for the same month as this one buckets_in_period = [ bucket for bucket in all_supervision_time_buckets if (isinstance(bucket, (RevocationReturnSupervisionTimeBucket, NonRevocationReturnSupervisionTimeBucket))) and bucket.year == bucket_year and bucket.month == bucket_month ] elif metric_type == SupervisionMetricType.REVOCATION: # Get all other revocation supervision buckets for the same month as this one buckets_in_period = [ bucket for bucket in all_supervision_time_buckets if isinstance(bucket, RevocationReturnSupervisionTimeBucket) and bucket.year == bucket_year and bucket.month == bucket_month ] elif metric_type in ( SupervisionMetricType.SUCCESS, SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED): # Get all other projected completion buckets for the same month as this one buckets_in_period = [ bucket for bucket in all_supervision_time_buckets if isinstance(bucket, ProjectedSupervisionCompletionBucket) and bucket.year == bucket_year and bucket.month == bucket_month ] elif metric_type == SupervisionMetricType.ASSESSMENT_CHANGE: # Get all other termination buckets for the same month as this one buckets_in_period = [ bucket for bucket in all_supervision_time_buckets if isinstance(bucket, SupervisionTerminationBucket) and bucket.year == bucket_year and bucket.month == bucket_month ] elif metric_type in ( SupervisionMetricType.REVOCATION_ANALYSIS, SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS): # Get all other revocation supervision buckets for the same month as this one buckets_in_period = [ bucket for bucket in all_supervision_time_buckets if isinstance(bucket, RevocationReturnSupervisionTimeBucket) and bucket.year == bucket_year and bucket.month == bucket_month ] if buckets_in_period and include_supervision_in_count( combo, supervision_time_bucket, buckets_in_period, metric_type): person_combo_value = _person_combo_value(combo, supervision_time_bucket, buckets_in_period, metric_type) # Include this event in the person-based count metrics.append((person_based_same_bucket_combo, person_combo_value)) return metrics
def combination_program_monthly_metrics( combo: Dict[str, Any], program_event: ProgramEvent, metric_type: ProgramMetricType, all_program_events: List[ProgramEvent], is_daily_metric: bool) -> List[Tuple[Dict[str, Any], int]]: """Returns all unique referral metrics for the given event and combination. First, includes an event-based count for the month the event occurred with a metric period of 1 month. Then, if this event should be included in the person-based count for the month when the event occurred, adds those person- based metrics. Args: combo: A characteristic combination to convert into metrics program_event: The program event from which the combination was derived metric_type: The type of metric being tracked by this combo all_program_events: All of this person's ProgramEvents is_daily_metric: If True, limits person-based counts to the date of the event. If False, limits person-based counts to the month of the event. Returns: A list of key-value tuples representing specific metric combination dictionaries and the number 1 representing a positive contribution to that count metric. """ metrics = [] event_date = program_event.event_date event_year = event_date.year event_month = event_date.month base_metric_period = 0 if is_daily_metric else 1 # Add event-based combo for the base metric period the month of the event event_based_same_month_combo = augmented_combo_for_calculations( combo, program_event.state_code, event_year, event_month, MetricMethodologyType.EVENT, base_metric_period) metrics.append((event_based_same_month_combo, 1)) # Create the person-based combo for the base metric period of the month of the event person_based_same_month_combo = augmented_combo_for_calculations( combo, program_event.state_code, event_year, event_month, MetricMethodologyType.PERSON, base_metric_period) events_in_period: List[ProgramEvent] = [] if metric_type == ProgramMetricType.PARTICIPATION: # Get all other participation events that happened on the same day as this one events_in_period = [ event for event in all_program_events if (isinstance(event, ProgramParticipationEvent)) and event.event_date == program_event.event_date ] elif metric_type == ProgramMetricType.REFERRAL: # Get all other referral events that happened in the same month as this one events_in_period = [ event for event in all_program_events if isinstance(event, ProgramReferralEvent) and event.event_date. year == event_year and event.event_date.month == event_month ] if include_event_in_count(combo, program_event, last_day_of_month(event_date), events_in_period): # Include this event in the person-based count metrics.append((person_based_same_month_combo, 1)) return metrics
def combination_incarceration_metrics( combo: Dict[str, Any], incarceration_event: IncarcerationEvent, all_incarceration_events: List[IncarcerationEvent], is_daily_metric: bool) \ -> List[Tuple[Dict[str, Any], int]]: """Returns all unique incarceration metrics for the given event and combination. First, includes an event-based count for the event. Then, if this is a daily metric, includes a count of the event if it should be included in the person-based count for the day when the event occurred. If this is not a daily metric, includes a count of the event if it should be included in the person-based count for the month of the event. Args: combo: A characteristic combination to convert into metrics incarceration_event: The IncarcerationEvent from which the combination was derived all_incarceration_events: All of this person's IncarcerationEvents is_daily_metric: If True, limits person-based counts to the date of the event. If False, limits person-based counts to the month of the event. Returns: A list of key-value tuples representing specific metric combination dictionaries and the number 1 representing a positive contribution to that count metric. """ metrics = [] event_date = incarceration_event.event_date event_year = event_date.year event_month = event_date.month metric_period_months = 0 if is_daily_metric else 1 # Add event-based combo for the 1-month period the month of the event event_based_same_month_combo = augmented_combo_for_calculations( combo, incarceration_event.state_code, event_year, event_month, MetricMethodologyType.EVENT, metric_period_months=metric_period_months) metrics.append((event_based_same_month_combo, 1)) # Create the person-based combo for the 1-month period of the month of the event person_based_same_month_combo = augmented_combo_for_calculations( combo, incarceration_event.state_code, event_year, event_month, MetricMethodologyType.PERSON, metric_period_months=metric_period_months) day_match_value = event_date.day if is_daily_metric else None # Get the events of the same type that happened in the same month events_in_period = matching_events_for_person_based_count( year=event_year, month=event_month, day=day_match_value, event_type=type(incarceration_event), all_incarceration_events=all_incarceration_events) if events_in_period and include_event_in_count( incarceration_event, last_day_of_month(event_date), events_in_period): # Include this event in the person-based count metrics.append((person_based_same_month_combo, 1)) return metrics
def map_supervision_combinations( person: StatePerson, supervision_time_buckets: List[SupervisionTimeBucket], metric_inclusions: Dict[SupervisionMetricType, bool], calculation_end_month: Optional[str], calculation_month_count: int, person_metadata: PersonMetadata, ) -> List[Tuple[Dict[str, Any], Any]]: """Transforms SupervisionTimeBuckets and a StatePerson into metric combinations. Takes in a StatePerson and all of her SupervisionTimeBuckets and returns an array of "supervision 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 time on supervision into many different supervision population metrics. Args: person: the StatePerson supervision_time_buckets: A list of SupervisionTimeBuckets for the given StatePerson. metric_inclusions: A dictionary where the keys are each SupervisionMetricType, 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. person_metadata: Contains information about the StatePerson that is necessary for the metrics. 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]] = [] supervision_time_buckets.sort(key=attrgetter("year", "month")) calculation_month_upper_bound = get_calculation_month_upper_bound_date( calculation_end_month) calculation_month_lower_bound = get_calculation_month_lower_bound_date( calculation_month_upper_bound, calculation_month_count) for supervision_time_bucket in supervision_time_buckets: event_date = supervision_time_bucket.event_date if (isinstance(supervision_time_bucket, NonRevocationReturnSupervisionTimeBucket) and supervision_time_bucket.case_compliance): event_date = supervision_time_bucket.case_compliance.date_of_evaluation event_year = event_date.year event_month = event_date.month if not include_in_output( event_year, event_month, calculation_month_upper_bound, calculation_month_lower_bound, ): continue applicable_metric_types = BUCKET_TO_METRIC_TYPES.get( type(supervision_time_bucket)) if not applicable_metric_types: raise ValueError( "No metric types mapped to supervision_time_bucket of type {}". format(type(supervision_time_bucket))) for metric_type in applicable_metric_types: if not metric_inclusions[metric_type]: continue metric_class = METRIC_TYPE_TO_CLASS.get(metric_type) if not metric_class: raise ValueError( "No metric class for metric type {}".format(metric_type)) if include_event_in_metric(supervision_time_bucket, metric_type): characteristic_combo = characteristics_dict_builder( pipeline="supervision", event=supervision_time_bucket, metric_class=metric_class, person=person, event_date=event_date, person_metadata=person_metadata, ) metric_combo = augmented_combo_for_calculations( characteristic_combo, supervision_time_bucket.state_code, metric_type, event_year, event_month, ) value = value_for_metric_combo(supervision_time_bucket, metric_type) metrics.append((metric_combo, value)) return metrics
def map_recidivism_combinations( person: StatePerson, release_events: Dict[int, List[ReleaseEvent]], metric_inclusions: Dict[ReincarcerationRecidivismMetricType, bool], person_metadata: PersonMetadata, ) -> List[Tuple[Dict[str, Any], Any]]: """Transforms ReleaseEvents and a StatePerson into metric combinations. Takes in a StatePerson and all of her ReleaseEvents and returns an array of "recidivism combinations". These are key-value pairs where the key represents a specific metric and the value represents whether or not recidivism occurred. This translates a particular recidivism event into many different recidivism metrics. Both count-based and rate-based metrics are generated. Each metric represents one of many possible combinations of characteristics being tracked for that event. For example, if an asian male is reincarcerated, there is a metric that corresponds to asian people, one to males, one to asian males, one to all people, and more depending on other dimensions in the data. If a release does not count towards recidivism, then the value is 0 for the rate-based metrics in either methodology. For both count and rate-based metrics, the value is 0 if the dimensions of the metric do not fully match the attributes of the person and their type of return to incarceration. For example, for a RecidivismReleaseEvent where the return_type is 'REVOCATION', there will be metrics produced where the return_type is 'NEW INCARCERATION_ADMISSION' and the value is 0. Args: person: the StatePerson release_events: A dictionary mapping release cohorts to a list of ReleaseEvents for the given StatePerson. metric_inclusions: A dictionary where the keys are each ReincarcerationRecidivismMetricType, and the values are boolean flags for whether or not to include that metric type in the calculations person_metadata: Contains information about the StatePerson that is necessary for the metrics. Returns: A list of key-value tuples representing specific metric combinations and the recidivism value corresponding to that metric. """ metrics = [] all_reincarcerations = reincarcerations(release_events) if metric_inclusions.get( ReincarcerationRecidivismMetricType.REINCARCERATION_RATE): for events in release_events.values(): for event in events: event_date = event.release_date characteristic_combo = characteristics_dict_builder( pipeline="recidivism", event=event, metric_class=ReincarcerationRecidivismRateMetric, person=person, event_date=event_date, person_metadata=person_metadata, ) reincarcerations_by_follow_up_period = reincarcerations_by_period( event_date, all_reincarcerations) metrics.extend( combination_rate_metrics( characteristic_combo, event, reincarcerations_by_follow_up_period, )) if metric_inclusions.get( ReincarcerationRecidivismMetricType.REINCARCERATION_COUNT): for reincarceration_event in all_reincarcerations.values(): event_date = reincarceration_event.reincarceration_date characteristic_combo = characteristics_dict_builder( pipeline="recidivism", event=reincarceration_event, metric_class=ReincarcerationRecidivismCountMetric, person=person, event_date=event_date, person_metadata=person_metadata, ) augmented_combo = augmented_combo_for_calculations( characteristic_combo, state_code=reincarceration_event.state_code, year=event_date.year, month=event_date.month, metric_type=ReincarcerationRecidivismMetricType. REINCARCERATION_COUNT, ) metrics.append((augmented_combo, 1)) return metrics