예제 #1
0
def identify_most_severe_violation_type_and_subtype(
    violations: List[StateSupervisionViolation],
) -> Tuple[Optional[StateSupervisionViolationType], Optional[str]]:
    """Identifies the most severe violation type on the provided |violations|, and, if relevant, the subtype of that
    most severe violation type. Returns both as a tuple.
    """
    violation_subtypes: List[str] = []

    if not violations:
        return None, None

    state_code = get_single_state_code(violations)

    for violation in violations:
        violation_subtypes.extend(
            get_violation_type_subtype_strings_for_violation(violation))

    if not violation_subtypes:
        return None, None

    most_severe_subtype = most_severe_violation_subtype(
        state_code, violation_subtypes,
        DEFAULT_VIOLATION_SUBTYPE_SEVERITY_ORDER)

    most_severe_type = None

    if most_severe_subtype:
        most_severe_type = violation_type_from_subtype(state_code,
                                                       most_severe_subtype)

    return most_severe_type, most_severe_subtype
예제 #2
0
def find_program_events(
    program_assignments: List[StateProgramAssignment],
    assessments: List[StateAssessment],
    supervision_periods: List[StateSupervisionPeriod],
    supervision_period_to_agent_association: List[Dict[str, Any]],
) -> List[ProgramEvent]:
    """Finds instances of interaction with a program.

    Identifies instances of being referred to a program and actively participating in a program.

    Args:
        - program_assignments: All of the person's StateProgramAssignments
        - assessments: All of the person's recorded StateAssessments
        - supervision_periods: All of the person's supervision_periods
        - supervision_period_to_agent_associations: dictionary associating StateSupervisionPeriod ids to information
            about the corresponding StateAgent

    Returns:
        A list of ProgramEvents for the person.
    """
    # TODO(#2855): Bring in supervision and incarceration sentences to infer the supervision type on supervision
    #  periods that don't have a set supervision type
    program_events: List[ProgramEvent] = []

    if not program_assignments:
        return program_events

    supervision_period_to_agent_associations = list_of_dicts_to_dict_with_keys(
        supervision_period_to_agent_association,
        StateSupervisionPeriod.get_class_id_name(),
    )

    state_code = get_single_state_code(program_assignments)

    should_drop_federal_and_other_country = (
        filter_out_federal_and_other_country_supervision_periods(state_code)
    )

    supervision_periods = prepare_supervision_periods_for_calculations(
        supervision_periods,
        drop_federal_and_other_country_supervision_periods=should_drop_federal_and_other_country,
    )

    for program_assignment in program_assignments:
        program_referrals = find_program_referrals(
            program_assignment,
            assessments,
            supervision_periods,
            supervision_period_to_agent_associations,
        )

        program_events.extend(program_referrals)

        program_participation_events = find_program_participation_events(
            program_assignment, supervision_periods
        )

        program_events.extend(program_participation_events)

    return program_events
예제 #3
0
def find_incarceration_events(
        sentence_groups: List[StateSentenceGroup],
        incarceration_period_judicial_district_association: List[Dict[str, Any]],
        county_of_residence: Optional[str]) -> List[IncarcerationEvent]:
    """Finds instances of admission or release from incarceration.

    Transforms the person's StateIncarcerationPeriods, which are connected to their StateSentenceGroups, into
    IncarcerationAdmissionEvents, IncarcerationStayEvents, and IncarcerationReleaseEvents, representing admissions,
    stays in, and releases from incarceration in a state prison.

    Args:
        - sentence_groups: All of the person's StateSentenceGroups
        - incarceration_period_judicial_district_association: A list of dictionaries with information connecting
            StateIncarcerationPeriod ids to the judicial district responsible for the period of incarceration
        - county_of_residence: The person's most recent county of residence

    Returns:
        A list of IncarcerationEvents for the person.
    """
    incarceration_events: List[IncarcerationEvent] = []
    incarceration_sentences = []
    supervision_sentences = []
    for sentence_group in sentence_groups:
        incarceration_sentences.extend(sentence_group.incarceration_sentences)
        supervision_sentences.extend(sentence_group.supervision_sentences)
    incarceration_periods, _supervision_periods = get_unique_periods_from_sentence_groups_and_add_backedges(
        sentence_groups)

    if not incarceration_periods:
        return incarceration_events

    state_code = get_single_state_code(incarceration_periods)

    # Convert the list of dictionaries into one dictionary where the keys are the incarceration_period_id values
    incarceration_period_to_judicial_district = list_of_dicts_to_dict_with_keys(
        incarceration_period_judicial_district_association, key=StateIncarcerationPeriod.get_class_id_name())

    incarceration_events.extend(find_all_stay_events(
        state_code,
        incarceration_sentences,
        supervision_sentences,
        incarceration_periods,
        incarceration_period_to_judicial_district,
        county_of_residence))

    incarceration_events.extend(find_all_admission_release_events(
        state_code,
        incarceration_sentences,
        supervision_sentences,
        incarceration_periods,
        county_of_residence))

    return incarceration_events
def drop_periods_not_under_state_custodial_authority(incarceration_periods: List[StateIncarcerationPeriod]) \
        -> List[StateIncarcerationPeriod]:
    """Returns a filtered subset of the provided |incarceration_periods| where all periods that are not under state
    custodial authority are filtered out.
    """
    # TODO(2912): Use `custodial_authority` to determine this insted, when that field exists on incarceration periods.
    state_code = get_single_state_code(incarceration_periods)
    if state_code == 'US_ND':
        filtered_incarceration_periods = drop_temporary_custody_periods(
            incarceration_periods)
    else:
        filtered_incarceration_periods = _drop_non_prison_periods(
            incarceration_periods)
    return filtered_incarceration_periods
예제 #5
0
def find_program_events(
    program_assignments: List[StateProgramAssignment],
    assessments: List[StateAssessment],
    supervision_periods: List[StateSupervisionPeriod],
    supervision_period_to_agent_associations: Dict[int, Dict[Any, Any]]
) -> List[ProgramEvent]:
    """Finds instances of interaction with a program.

    Identifies instances of being referred to a program and actively participating in a program.

    Args:
        - program_assignments: All of the person's StateProgramAssignments
        - assessments: All of the person's recorded StateAssessments
        - supervision_periods: All of the person's supervision_periods
        - supervision_period_to_agent_associations: dictionary associating StateSupervisionPeriod ids to information
            about the corresponding StateAgent

    Returns:
        A list of ProgramEvents for the person.
    """
    # TODO(#2855): Bring in supervision and incarceration sentences to infer the supervision type on supervision
    #  periods that don't have a set supervision type
    program_events: List[ProgramEvent] = []

    if not program_assignments:
        return program_events

    state_code = get_single_state_code(program_assignments)

    should_drop_non_state_custodial_authority_periods = \
        only_state_custodial_authority_in_supervision_population(state_code)

    supervision_periods = prepare_supervision_periods_for_calculations(
        supervision_periods,
        drop_non_state_custodial_authority_periods=
        should_drop_non_state_custodial_authority_periods)

    for program_assignment in program_assignments:
        program_referrals = find_program_referrals(
            program_assignment, assessments, supervision_periods,
            supervision_period_to_agent_associations)

        program_events.extend(program_referrals)

        program_participation_events = find_program_participation_events(
            program_assignment, supervision_periods)

        program_events.extend(program_participation_events)

    return program_events
예제 #6
0
    def _find_incarceration_events(
        self,
        sentence_groups: List[StateSentenceGroup],
        assessments: List[StateAssessment],
        violation_responses: List[StateSupervisionViolationResponse],
        incarceration_period_judicial_district_association: List[Dict[str, Any]],
        persons_to_recent_county_of_residence: List[Dict[str, Any]],
        supervision_period_to_agent_association: List[Dict[str, Any]],
    ) -> List[IncarcerationEvent]:
        """Finds instances of various events related to incarceration.
        Transforms the person's StateIncarcerationPeriods, which are connected to their StateSentenceGroups, into
        IncarcerationAdmissionEvents, IncarcerationStayEvents, and IncarcerationReleaseEvents, representing admissions,
        stays in, and releases from incarceration in a state prison.
        Args:
            - sentence_groups: All of the person's StateSentenceGroups
            - incarceration_period_judicial_district_association: A list of dictionaries with information connecting
                StateIncarcerationPeriod ids to the judicial district responsible for the period of incarceration
            - persons_to_recent_county_of_residence: Reference table rows containing the county that the incarcerated person
                lives in (prior to incarceration).
        Returns:
            A list of IncarcerationEvents for the person.
        """
        incarceration_events: List[IncarcerationEvent] = []
        incarceration_sentences: List[StateIncarcerationSentence] = []
        supervision_sentences: List[StateSupervisionSentence] = []
        for sentence_group in sentence_groups:
            incarceration_sentences.extend(sentence_group.incarceration_sentences)
            supervision_sentences.extend(sentence_group.supervision_sentences)
        (
            incarceration_periods,
            supervision_periods,
        ) = self._get_unique_periods_from_sentence_groups_and_add_backedges(
            sentence_groups
        )

        if not incarceration_periods:
            return incarceration_events

        county_of_residence: Optional[str] = extract_county_of_residence_from_rows(
            persons_to_recent_county_of_residence
        )

        state_code: str = get_single_state_code(incarceration_periods)

        # Convert the list of dictionaries into one dictionary where the keys are the
        # incarceration_period_id values
        incarceration_period_to_judicial_district: Dict[
            Any, Dict[str, Any]
        ] = list_of_dicts_to_dict_with_keys(
            incarceration_period_judicial_district_association,
            key=StateIncarcerationPeriod.get_class_id_name(),
        )

        supervision_period_to_agent_associations = list_of_dicts_to_dict_with_keys(
            supervision_period_to_agent_association,
            StateSupervisionPeriod.get_class_id_name(),
        )

        sorted_violation_responses = prepare_violation_responses_for_calculations(
            violation_responses=violation_responses,
            pre_processing_function=state_specific_violation_response_pre_processing_function(
                state_code=state_code
            ),
        )

        (
            ip_pre_processing_manager,
            sp_pre_processing_manager,
        ) = pre_processing_managers_for_calculations(
            state_code=state_code,
            incarceration_periods=incarceration_periods,
            supervision_periods=supervision_periods,
            violation_responses=sorted_violation_responses,
        )

        if not ip_pre_processing_manager or not sp_pre_processing_manager:
            raise ValueError(
                "Expected both pre-processed IPs and SPs for this pipeline."
            )

        incarceration_events.extend(
            self._find_all_stay_events(
                ip_pre_processing_manager=ip_pre_processing_manager,
                incarceration_period_to_judicial_district=incarceration_period_to_judicial_district,
                county_of_residence=county_of_residence,
            )
        )

        incarceration_events.extend(
            self._find_all_admission_release_events(
                incarceration_sentences=incarceration_sentences,
                supervision_sentences=supervision_sentences,
                ip_pre_processing_manager=ip_pre_processing_manager,
                sp_pre_processing_manager=sp_pre_processing_manager,
                assessments=assessments,
                sorted_violation_responses=sorted_violation_responses,
                supervision_period_to_agent_associations=supervision_period_to_agent_associations,
                county_of_residence=county_of_residence,
            )
        )

        return incarceration_events
예제 #7
0
    def _find_program_events(
        self,
        program_assignments: List[StateProgramAssignment],
        assessments: List[StateAssessment],
        supervision_periods: List[StateSupervisionPeriod],
        supervision_period_to_agent_association: List[Dict[str, Any]],
    ) -> List[ProgramEvent]:
        """Finds instances of interaction with a program.

        Identifies instances of being referred to a program and actively participating in a program.

        Args:
            - program_assignments: All of the person's StateProgramAssignments
            - assessments: All of the person's recorded StateAssessments
            - supervision_periods: All of the person's supervision_periods
            - supervision_period_to_agent_associations: dictionary associating StateSupervisionPeriod ids to information
                about the corresponding StateAgent

        Returns:
            A list of ProgramEvents for the person.
        """
        # TODO(#2855): Bring in supervision and incarceration sentences to infer the supervision type on supervision
        #  periods that don't have a set supervision type
        program_events: List[ProgramEvent] = []

        if not program_assignments:
            return program_events

        state_code = get_single_state_code(program_assignments)

        supervision_period_to_agent_associations = list_of_dicts_to_dict_with_keys(
            supervision_period_to_agent_association,
            StateSupervisionPeriod.get_class_id_name(),
        )

        (
            _,
            sp_pre_processing_manager,
        ) = pre_processing_managers_for_calculations(
            state_code=state_code,
            # SP pre-processing doesn't rely on StateIncarcerationPeriod entities,
            # and this pipeline doesn't require StateIncarcerationPeriods
            incarceration_periods=None,
            supervision_periods=supervision_periods,
            # Note: This pipeline cannot be run for any state that relies on
            # StateSupervisionViolationResponse entities in IP pre-processing
            violation_responses=None,
        )

        if not sp_pre_processing_manager:
            raise ValueError("Expected pre-processed SPs for this pipeline.")

        supervision_periods_for_calculations = (
            sp_pre_processing_manager.
            pre_processed_supervision_period_index_for_calculations(
            ).supervision_periods)

        for program_assignment in program_assignments:
            program_referrals = self._find_program_referrals(
                program_assignment,
                assessments,
                supervision_periods_for_calculations,
                supervision_period_to_agent_associations,
            )

            program_events.extend(program_referrals)

            program_participation_events = self._find_program_participation_events(
                program_assignment, supervision_periods_for_calculations)

            program_events.extend(program_participation_events)

        return program_events
예제 #8
0
def find_release_events_by_cohort_year(
        incarceration_periods: List[StateIncarcerationPeriod],
        county_of_residence: Optional[str]) -> Dict[int, List[ReleaseEvent]]:
    """Finds instances of release and determines if they resulted in recidivism.

    Transforms each StateIncarcerationPeriod from which the person has been released into a mapping from its release
    cohort to the details of the event. The release cohort is an integer for the year, e.g. 2006. The event details
    are a ReleaseEvent object, which can represent events of both recidivism and non-recidivism. That is, each
    StateIncarcerationPeriod is transformed into a recidivism event unless it is the most recent period of
    incarceration and they are still incarcerated, or it is connected to a subsequent StateIncarcerationPeriod by a
    transfer.

    Example output for someone who went to prison in 2006, was released in 2008, went back in 2010, was released in
    2012, and never returned:
    {
      2008: [RecidivismReleaseEvent(original_admission_date="2006-04-05", ...)],
      2012: [NonRecidivismReleaseEvent(original_admission_date="2010-09-17", ...)]
    }

    Args:
        incarceration_periods: list of StateIncarcerationPeriods for a person
        county_of_residence: the county that the incarcerated person lives in (prior to incarceration).

    Returns:
        A dictionary mapping release cohorts to a list of ReleaseEvents for the given person in that cohort.
    """
    release_events: Dict[int, List[ReleaseEvent]] = defaultdict(list)

    if not incarceration_periods:
        return release_events

    state_code = get_single_state_code(incarceration_periods)

    incarceration_periods = prepare_incarceration_periods_for_recidivism_calculations(
        state_code, incarceration_periods)

    for index, incarceration_period in enumerate(incarceration_periods):
        state_code = incarceration_period.state_code
        admission_date = incarceration_period.admission_date
        status = incarceration_period.status
        release_date = incarceration_period.release_date
        release_reason = incarceration_period.release_reason
        release_facility = incarceration_period.facility

        event = None
        next_incarceration_period = (incarceration_periods[index + 1]
                                     if index <= len(incarceration_periods) - 2
                                     else None)

        if not should_include_in_release_cohort(status, release_date,
                                                release_reason,
                                                next_incarceration_period):
            # If this release should not be included in a release cohort, then we do not need to produce any events
            # for this period of incarceration
            continue

        if not release_date or not release_reason:
            raise ValueError(
                "Incarceration_period must have valid release_date and release_reason to be included in"
                " the release_cohort. Error in should_include_in_release_cohort."
            )

        # Admission data and status have been validated already
        if admission_date and status:
            if index == len(incarceration_periods) - 1:
                event = for_incarceration_period_no_return(
                    state_code, admission_date, release_date, release_facility,
                    county_of_residence)
            else:
                reincarceration_period = find_valid_reincarceration_period(
                    incarceration_periods, index, release_date)

                if not reincarceration_period:
                    # We were unable to identify a reincarceration for this period
                    event = for_incarceration_period_no_return(
                        state_code, admission_date, release_date,
                        release_facility, county_of_residence)
                else:
                    reincarceration_date = reincarceration_period.admission_date
                    reincarceration_facility = reincarceration_period.facility
                    reincarceration_admission_reason = reincarceration_period.admission_reason

                    if is_revocation_admission(
                            reincarceration_admission_reason):
                        source_supervision_violation_response = \
                            reincarceration_period.source_supervision_violation_response
                    else:
                        source_supervision_violation_response = None

                    # These fields have been validated already
                    if not reincarceration_date or not reincarceration_admission_reason:
                        raise ValueError(
                            "Incarceration period pre-processing should have set admission_dates and"
                            "admission_reasons on all periods.")

                    event = for_intermediate_incarceration_period(
                        state_code=state_code,
                        admission_date=admission_date,
                        release_date=release_date,
                        release_facility=release_facility,
                        county_of_residence=county_of_residence,
                        reincarceration_date=reincarceration_date,
                        reincarceration_facility=reincarceration_facility,
                        reincarceration_admission_reason=
                        reincarceration_admission_reason,
                        source_supervision_violation_response=
                        source_supervision_violation_response)

        if event:
            if release_date:
                release_cohort = release_date.year
                release_events[release_cohort].append(event)

    return release_events
예제 #9
0
    def _find_release_events_by_cohort_year(
        self,
        incarceration_periods: List[StateIncarcerationPeriod],
        supervision_periods: List[StateSupervisionPeriod],
        persons_to_recent_county_of_residence: List[Dict[str, Any]],
    ) -> Dict[int, List[ReleaseEvent]]:
        """Finds instances of release and determines if they resulted in recidivism.

        Transforms each StateIncarcerationPeriod from which the person has been released into a mapping from its release
        cohort to the details of the event. The release cohort is an integer for the year, e.g. 2006. The event details
        are a ReleaseEvent object, which can represent events of both recidivism and non-recidivism. That is, each
        StateIncarcerationPeriod is transformed into a recidivism event unless it is the most recent period of
        incarceration and they are still incarcerated, or it is connected to a subsequent StateIncarcerationPeriod by a
        transfer.

        Example output for someone who went to prison in 2006, was released in 2008, went back in 2010, was released in
        2012, and never returned:
        {
        2008: [RecidivismReleaseEvent(original_admission_date="2006-04-05", ...)],
        2012: [NonRecidivismReleaseEvent(original_admission_date="2010-09-17", ...)]
        }

        Args:
            incarceration_periods: list of StateIncarcerationPeriods for a person
            persons_to_recent_county_of_residence: Reference table rows containing the county that the incarcerated person
                lives in (prior to incarceration).

        Returns:
            A dictionary mapping release cohorts to a list of ReleaseEvents for the given person in that cohort.
        """
        release_events: Dict[int, List[ReleaseEvent]] = defaultdict(list)

        if not incarceration_periods:
            return release_events

        county_of_residence = extract_county_of_residence_from_rows(
            persons_to_recent_county_of_residence)

        state_code = get_single_state_code(incarceration_periods)

        (
            ip_pre_processing_manager,
            _,
        ) = pre_processing_managers_for_calculations(
            state_code=state_code,
            incarceration_periods=incarceration_periods,
            supervision_periods=supervision_periods,
            # Note: This pipeline cannot be run for any state that relies on
            # StateSupervisionViolationResponse entities in IP pre-processing
            violation_responses=None,
        )

        if not ip_pre_processing_manager:
            raise ValueError("Expected pre-processed SPs for this pipeline.")

        incarceration_periods = ip_pre_processing_manager.pre_processed_incarceration_period_index_for_calculations(
            collapse_transfers=True,
            overwrite_facility_information_in_transfers=True,
        ).incarceration_periods

        for index, incarceration_period in enumerate(incarceration_periods):
            state_code = incarceration_period.state_code
            admission_date = incarceration_period.admission_date
            status = incarceration_period.status
            release_date = incarceration_period.release_date
            release_reason = incarceration_period.release_reason
            release_facility = incarceration_period.facility
            purpose_for_incarceration = (
                incarceration_period.specialized_purpose_for_incarceration)

            event = None
            next_incarceration_period = (
                incarceration_periods[index + 1]
                if index <= len(incarceration_periods) - 2 else None)

            if not self._should_include_in_release_cohort(
                    status,
                    release_date,
                    release_reason,
                    purpose_for_incarceration,
                    next_incarceration_period,
            ):
                # If this release should not be included in a release cohort, then we do not need to produce any events
                # for this period of incarceration
                continue

            if not release_date or not release_reason:
                raise ValueError(
                    "Incarceration_period must have valid release_date and release_reason to be included in"
                    " the release_cohort. Error in should_include_in_release_cohort."
                )

            # Admission data and status have been validated already
            if admission_date and status:
                if index == len(incarceration_periods) - 1:
                    event = self._for_incarceration_period_no_return(
                        state_code,
                        admission_date,
                        release_date,
                        release_facility,
                        county_of_residence,
                    )
                else:
                    reincarceration_period = self._find_valid_reincarceration_period(
                        incarceration_periods, index, release_date)

                    if not reincarceration_period:
                        # We were unable to identify a reincarceration for this period
                        event = self._for_incarceration_period_no_return(
                            state_code,
                            admission_date,
                            release_date,
                            release_facility,
                            county_of_residence,
                        )
                    else:
                        reincarceration_date = reincarceration_period.admission_date
                        reincarceration_facility = reincarceration_period.facility
                        reincarceration_admission_reason = (
                            reincarceration_period.admission_reason)

                        # These fields have been validated already
                        if (not reincarceration_date
                                or not reincarceration_admission_reason):
                            raise ValueError(
                                "Incarceration period pre-processing should have set admission_dates and"
                                "admission_reasons on all periods.")

                        event = self._for_intermediate_incarceration_period(
                            state_code=state_code,
                            admission_date=admission_date,
                            release_date=release_date,
                            release_facility=release_facility,
                            county_of_residence=county_of_residence,
                            reincarceration_date=reincarceration_date,
                            reincarceration_facility=reincarceration_facility,
                        )

            if event:
                if release_date:
                    release_cohort = release_date.year
                    release_events[release_cohort].append(event)

        return release_events
예제 #10
0
def find_supervision_time_buckets(
        supervision_sentences: List[StateSupervisionSentence],
        incarceration_sentences: List[StateIncarcerationSentence],
        supervision_periods: List[StateSupervisionPeriod],
        incarceration_periods: List[StateIncarcerationPeriod],
        assessments: List[StateAssessment],
        violation_responses: List[StateSupervisionViolationResponse],
        ssvr_agent_associations: Dict[int, Dict[Any, Any]],
        supervision_period_to_agent_associations: Dict[int, Dict[Any, Any]],
        state_code_filter: Optional[str] = 'ALL'
) -> List[SupervisionTimeBucket]:
    """Finds buckets of time that a person was on supervision and determines if they resulted in revocation return.

    Transforms each StateSupervisionPeriod into months where the person spent any number of days on
    supervision. Excludes any time buckets in which the person was incarcerated for the entire time by looking through
    all of the person's StateIncarcerationPeriods. For each month that someone was on supervision, a
    SupervisionTimeBucket object is created. If the person was admitted to prison in that time for a revocation that
    matches the type of supervision the SupervisionTimeBucket describes, then that object is a
    RevocationSupervisionTimeBucket. If no applicable revocation occurs, then the object is of type
    NonRevocationSupervisionTimeBucket.

    If someone is serving both probation and parole simultaneously, there will be one SupervisionTimeBucket for the
    probation type and one for the parole type. If someone has multiple overlapping supervision periods,
    there will be one SupervisionTimeBucket for each month on each supervision period.

    If a revocation return to prison occurs in a time bucket where there is no recorded supervision period, we count
    the individual as having been on supervision that time so that they can be included in the
    revocation return count and rate metrics for that bucket. In these cases, we add supplemental
    RevocationReturnSupervisionTimeBuckets to the list of SupervisionTimeBuckets.

    Args:
        - supervision_sentences: list of StateSupervisionSentences for a person
        - supervision_periods: list of StateSupervisionPeriods for a person
        - incarceration_periods: list of StateIncarcerationPeriods for a person
        - assessments: list of StateAssessments for a person
        - violations: list of StateSupervisionViolations for a person
        - violation_responses: list of StateSupervisionViolationResponses for a person
        - ssvr_agent_associations: dictionary associating StateSupervisionViolationResponse ids to information about the
            corresponding StateAgent on the response
        - supervision_period_to_agent_associations: dictionary associating StateSupervisionPeriod ids to information
            about the corresponding StateAgent
        - state_code_filter: the state_code to limit the output to. If this is 'ALL' or is omitted, then all states
        will be included in the result.

    Returns:
        A list of SupervisionTimeBuckets for the person.
    """
    if not supervision_periods and not incarceration_periods:
        return []

    # We assume here that that a person will only have supervision or incarceration periods from a single state - this
    #  will break in the future when we start entity matching across multiple states.
    all_periods: Sequence[ExternalIdEntity] = [
        *supervision_periods, *incarceration_periods
    ]
    state_code = get_single_state_code(all_periods)

    if state_code_filter not in ('ALL', state_code):
        return []

    supervision_time_buckets: List[SupervisionTimeBucket] = []

    incarceration_periods = prepare_incarceration_periods_for_calculations(
        incarceration_periods,
        collapse_temporary_custody_periods_with_revocation=True)

    incarceration_periods.sort(key=lambda b: b.admission_date)

    incarceration_periods_by_admission_month = index_incarceration_periods_by_admission_month(
        incarceration_periods)

    indexed_supervision_periods = _index_supervision_periods_by_termination_month(
        supervision_periods)

    months_fully_incarcerated = _identify_months_fully_incarcerated(
        incarceration_periods)

    projected_supervision_completion_buckets = classify_supervision_success(
        supervision_sentences, supervision_period_to_agent_associations)

    supervision_time_buckets.extend(projected_supervision_completion_buckets)

    for supervision_period in supervision_periods:
        # Don't process placeholder supervision periods
        if not is_placeholder(supervision_period):
            supervision_time_buckets = supervision_time_buckets + find_time_buckets_for_supervision_period(
                supervision_sentences, incarceration_sentences,
                supervision_period, incarceration_periods_by_admission_month,
                months_fully_incarcerated, assessments, violation_responses,
                supervision_period_to_agent_associations)

            supervision_termination_bucket = find_supervision_termination_bucket(
                supervision_sentences, incarceration_sentences,
                supervision_period, indexed_supervision_periods, assessments,
                supervision_period_to_agent_associations)

            if supervision_termination_bucket:
                supervision_time_buckets.append(supervision_termination_bucket)

    supervision_time_buckets = find_revocation_return_buckets(
        supervision_sentences, incarceration_sentences, supervision_periods,
        incarceration_periods, supervision_time_buckets, assessments,
        violation_responses, ssvr_agent_associations,
        supervision_period_to_agent_associations)

    expanded_dual_buckets = _expand_dual_supervision_buckets(
        supervision_time_buckets)

    supervision_time_buckets.extend(expanded_dual_buckets)

    return supervision_time_buckets