Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
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
Ejemplo n.º 10
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
Ejemplo n.º 11
0
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