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_supervision_combinations( person: StatePerson, supervision_time_buckets: List[SupervisionTimeBucket], metric_inclusions: Dict[SupervisionMetricType, bool], calculation_end_month: Optional[str], calculation_month_count: int) -> 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. 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_buckets: Dict[int, List[SupervisionTimeBucket]] = defaultdict() supervision_time_buckets.sort(key=attrgetter('year', 'month')) 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: periods_and_buckets = _classify_buckets_by_relevant_metric_periods( supervision_time_buckets, calculation_month_upper_bound) 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: if isinstance(supervision_time_bucket, ProjectedSupervisionCompletionBucket): if metric_inclusions.get(SupervisionMetricType.SUCCESS): characteristic_combo_success = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.SUCCESS) supervision_success_metrics = map_metric_combinations( characteristic_combo_success, supervision_time_bucket, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.SUCCESS, include_metric_period_output) metrics.extend(supervision_success_metrics) if metric_inclusions.get(SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED) \ and supervision_time_bucket.successful_completion \ and not supervision_time_bucket.incarcerated_during_sentence: # Only include successful sentences where the person was not incarcerated during the sentence in this # metric characteristic_combo_successful_sentence_length = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED) successful_sentence_length_metrics = map_metric_combinations( characteristic_combo_successful_sentence_length, supervision_time_bucket, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED, include_metric_period_output) metrics.extend(successful_sentence_length_metrics) elif isinstance(supervision_time_bucket, SupervisionTerminationBucket): if metric_inclusions.get(SupervisionMetricType.ASSESSMENT_CHANGE): characteristic_combo_assessment = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.ASSESSMENT_CHANGE) assessment_change_metrics = map_metric_combinations( characteristic_combo_assessment, supervision_time_bucket, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.ASSESSMENT_CHANGE, include_metric_period_output) metrics.extend(assessment_change_metrics) else: if metric_inclusions.get(SupervisionMetricType.POPULATION): characteristic_combo_population = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.POPULATION) population_metrics = map_metric_combinations( characteristic_combo_population, supervision_time_bucket, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.POPULATION, include_metric_period_output) metrics.extend(population_metrics) if metric_inclusions.get(SupervisionMetricType.REVOCATION): characteristic_combo_revocation = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.REVOCATION) if isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): revocation_metrics = map_metric_combinations( characteristic_combo_revocation, supervision_time_bucket, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.REVOCATION, include_metric_period_output) metrics.extend(revocation_metrics) if metric_inclusions.get(SupervisionMetricType.REVOCATION_ANALYSIS) and \ isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): characteristic_combo_revocation_analysis = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.REVOCATION_ANALYSIS) revocation_analysis_metrics = map_metric_combinations( characteristic_combo_revocation_analysis, supervision_time_bucket, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.REVOCATION_ANALYSIS, include_metric_period_output) metrics.extend(revocation_analysis_metrics) if (metric_inclusions.get( SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS) and isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket) and supervision_time_bucket.violation_type_frequency_counter): characteristic_combo_revocation_violation_type_analysis = characteristics_dict( person, supervision_time_bucket, SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS) revocation_violation_type_analysis_metrics = get_revocation_violation_type_analysis_metrics( supervision_time_bucket, characteristic_combo_revocation_violation_type_analysis, calculation_month_upper_bound, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, include_metric_period_output) metrics.extend(revocation_violation_type_analysis_metrics) 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_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_supervision_combinations( person: StatePerson, supervision_time_buckets: List[SupervisionTimeBucket], inclusions: Dict[str, bool], calculation_month_limit: int) -> 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. Each metric represents one of many possible combinations of characteristics being tracked for that event. For example, if a White male is on supervision, 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 supervision_time_buckets: A list of SupervisionTimeBuckets 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]] = [] # 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) supervision_time_buckets.sort(key=attrgetter('year', 'month')) periods_and_buckets = _classify_buckets_by_relevant_metric_periods( supervision_time_buckets, metric_period_end_date) for supervision_time_bucket in supervision_time_buckets: if isinstance(supervision_time_bucket, ProjectedSupervisionCompletionBucket): if inclusions.get(SupervisionMetricType.SUCCESS.value): characteristic_combos_success = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.SUCCESS) supervision_success_metrics = map_metric_combinations( characteristic_combos_success, supervision_time_bucket, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.SUCCESS) metrics.extend(supervision_success_metrics) if inclusions.get(SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED.value) \ and supervision_time_bucket.successful_completion \ and not supervision_time_bucket.incarcerated_during_sentence: # Only include successful sentences where the person was not incarcerated during the sentence in this # metric characteristic_combos_successful_sentence_length = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED) successful_sentence_length_metrics = map_metric_combinations( characteristic_combos_successful_sentence_length, supervision_time_bucket, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.SUCCESSFUL_SENTENCE_DAYS_SERVED) metrics.extend(successful_sentence_length_metrics) elif isinstance(supervision_time_bucket, SupervisionTerminationBucket): if inclusions.get(SupervisionMetricType.ASSESSMENT_CHANGE.value): characteristic_combos_assessment = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.ASSESSMENT_CHANGE) assessment_change_metrics = map_metric_combinations( characteristic_combos_assessment, supervision_time_bucket, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.ASSESSMENT_CHANGE) metrics.extend(assessment_change_metrics) else: if inclusions.get(SupervisionMetricType.POPULATION.value): characteristic_combos_population = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.POPULATION) population_metrics = map_metric_combinations( characteristic_combos_population, supervision_time_bucket, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.POPULATION) metrics.extend(population_metrics) if inclusions.get(SupervisionMetricType.REVOCATION.value): characteristic_combos_revocation = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.REVOCATION) if isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): revocation_metrics = map_metric_combinations( characteristic_combos_revocation, supervision_time_bucket, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.REVOCATION) metrics.extend(revocation_metrics) if inclusions.get(SupervisionMetricType.REVOCATION_ANALYSIS.value) and \ isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): characteristic_combos_revocation_analysis = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.REVOCATION_ANALYSIS) revocation_analysis_metrics = map_metric_combinations( characteristic_combos_revocation_analysis, supervision_time_bucket, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets, SupervisionMetricType.REVOCATION_ANALYSIS) metrics.extend(revocation_analysis_metrics) if (inclusions.get(SupervisionMetricType. REVOCATION_VIOLATION_TYPE_ANALYSIS.value) and isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket) and supervision_time_bucket.violation_type_frequency_counter): characteristic_combos_revocation_violation_type_analysis = characteristic_combinations( person, supervision_time_bucket, inclusions, SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS) revocation_violation_type_analysis_metrics = get_revocation_violation_type_analysis_metrics( supervision_time_bucket, characteristic_combos_revocation_violation_type_analysis, metric_period_end_date, calculation_month_lower_bound, supervision_time_buckets, periods_and_buckets) metrics.extend(revocation_violation_type_analysis_metrics) 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
def produce_metrics( self, person: StatePerson, identifier_events: List[SupervisionEvent], metric_inclusions: Dict[SupervisionMetricType, bool], person_metadata: PersonMetadata, pipeline_type: PipelineType, pipeline_job_id: str, calculation_end_month: Optional[str] = None, calculation_month_count: int = -1, ) -> List[SupervisionMetric]: """Transforms SupervisionEvents and a StatePerson into SuperviisonMetrics. Takes in a StatePerson and all of their SupervisionEvents and returns an array of SupervisionMetrics. Args: person: the StatePerson identifier_events: A list of SupervisionEvents 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. pipeline_job_id: The job_id of the pipeline that is currently running. Returns: A list of SupervisionMetrics. """ metrics: List[SupervisionMetric] = [] identifier_events.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 event in identifier_events: event_date = event.event_date if (isinstance( event, SupervisionPopulationEvent, ) and event.case_compliance): event_date = event.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 = self.event_to_metric_types.get( type(event)) if not applicable_metric_types: raise ValueError( "No metric types mapped to event of type {}".format( type(event))) for metric_type in applicable_metric_types: if not metric_inclusions[metric_type]: continue metric_class = self.metric_type_to_class.get(metric_type) if not metric_class: raise ValueError( "No metric class for metric type {}".format( metric_type)) if self.include_event_in_metric(event, metric_type): additional_attributes: Dict[str, Any] = {} if (isinstance( event, ProjectedSupervisionCompletionEvent, ) and metric_type == SupervisionMetricType. SUPERVISION_SUCCESSFUL_SENTENCE_DAYS_SERVED): additional_attributes[ "days_served"] = event.sentence_days_served metric = build_metric( pipeline=pipeline_type.value.lower(), event=event, metric_class=metric_class, person=person, event_date=event_date, person_metadata=person_metadata, pipeline_job_id=pipeline_job_id, additional_attributes=additional_attributes, ) if not isinstance(metric, SupervisionMetric): raise ValueError( f"Unexpected metric type {type(metric)}. " "All metrics should be SupervisionMetric.") metrics.append(metric) return metrics