Пример #1
0
    def test_get_calculation_month_upper_bound_date_bad_year(self):
        value = "0001-31"

        with pytest.raises(ValueError) as e:
            _ = calculator_utils.get_calculation_month_upper_bound_date(value)

        assert "Invalid value for calculation_end_month" in str(e.value)
Пример #2
0
    def test_get_calculation_month_upper_bound_date(self):
        value = "2009-01"

        calculation_month_upper_bound = (
            calculator_utils.get_calculation_month_upper_bound_date(value))

        self.assertEqual(date(2009, 1, 31), calculation_month_upper_bound)
Пример #3
0
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
Пример #4
0
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
Пример #5
0
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
Пример #6
0
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
Пример #7
0
    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