def _original_admission_reasons_by_period_id(
        self,
    ) -> Dict[int, Tuple[StateIncarcerationPeriodAdmissionReason,
                         Optional[str]]]:
        """Determines the original admission reason of each period of incarceration. Returns a dictionary mapping
        incarceration_period_id values to the original admission_reason and corresponding admission_reason_raw_text that
        started the period of being incarcerated. People are often transferred between facilities during their time
        incarcerated, so this in practice is the most recent non-transfer admission reason for the given incarceration
        period."""
        original_admission_reasons_by_period_id: Dict[int, Tuple[
            StateIncarcerationPeriodAdmissionReason, Optional[str]]] = {}

        most_recent_official_admission_reason: Optional[
            StateIncarcerationPeriodAdmissionReason] = None
        most_recent_official_admission_reason_raw_text: Optional[str] = None

        for index, incarceration_period in enumerate(
                self.incarceration_periods):
            incarceration_period_id = incarceration_period.incarceration_period_id

            if not incarceration_period_id:
                raise ValueError(
                    "Unexpected incarceration period without a incarceration_period_id."
                )

            if not incarceration_period.admission_reason:
                raise ValueError(
                    "Incarceration period pre-processing is not setting missing admission_reasons correctly."
                )

            if index == 0 or is_official_admission(
                    incarceration_period.admission_reason):
                # These indicate that incarceration is "officially" starting
                most_recent_official_admission_reason = (
                    incarceration_period.admission_reason)
                most_recent_official_admission_reason_raw_text = (
                    incarceration_period.admission_reason_raw_text)

            if not most_recent_official_admission_reason:
                original_admission_reasons_by_period_id[
                    incarceration_period_id] = (
                        incarceration_period.admission_reason,
                        incarceration_period.admission_reason_raw_text,
                    )
            else:
                original_admission_reasons_by_period_id[
                    incarceration_period_id] = (
                        most_recent_official_admission_reason,
                        most_recent_official_admission_reason_raw_text,
                    )

            if is_official_release(incarceration_period.release_reason):
                # If the release from this period of incarceration indicates an official end to the period of
                # incarceration, then subsequent periods should not share the most recent admission reason.
                most_recent_official_admission_reason = None
                most_recent_official_admission_reason_raw_text = None

        return original_admission_reasons_by_period_id
Exemple #2
0
    def test_is_official_admission_all_enums(self) -> None:
        for admission_reason in StateIncarcerationPeriodAdmissionReason:
            if (
                admission_reason
                == StateIncarcerationPeriodAdmissionReason.ADMITTED_FROM_SUPERVISION
            ):
                with self.assertRaises(ValueError):
                    _ = state_incarceration_period.is_official_admission(
                        admission_reason
                    )

                # Assert no error when we allow ingest-only values
                _ = state_incarceration_period.is_official_admission(
                    admission_reason, allow_ingest_only_enum_values=True
                )
            else:
                # Assert no error
                _ = state_incarceration_period.is_official_admission(admission_reason)
def collapse_incarceration_period_transfers(
        sorted_incarceration_periods: List[StateIncarcerationPeriod],
        overwrite_facility_information_in_transfers: bool = True,
        collapse_transfers_with_different_pfi: bool = True) -> List[StateIncarcerationPeriod]:
    """Collapses any incarceration periods that are connected by transfers.

    Loops through all of the StateIncarcerationPeriods and combines adjacent
    periods that are connected by a transfer. Only connects two periods if the
    release reason of the first is `TRANSFER` and the admission reason for the
    second is also `TRANSFER`.

    Args:
        sorted_incarceration_periods: list of StateIncarcerationPeriods for a StatePerson, sorted by ascending
            admission_date
        overwrite_facility_information_in_transfers: Whether or not to overwrite facility information when
            collapsing transfers.
        collapse_transfers_with_different_pfi: Whether or not to collapse two periods connected by a transfer
            if their overwrite_facility_information_in_transfers values are different
    Returns:
        A list of collapsed StateIncarcerationPeriods.
    """

    new_incarceration_periods: List[StateIncarcerationPeriod] = []
    open_transfer = False

    # TODO(#1782): Check to see if back to back incarceration periods are related
    #  to the same StateIncarcerationSentence or SentenceGroup to be sure we
    #  aren't counting stacked sentences or related periods as recidivism.
    for incarceration_period in sorted_incarceration_periods:
        if open_transfer:
            admission_reason = incarceration_period.admission_reason

            # Do not collapse any period with an official admission reason
            if not is_official_admission(admission_reason) and admission_reason == AdmissionReason.TRANSFER:
                # If there is an open transfer period and they were
                # transferred into this incarceration period, then combine this
                # period with the open transfer period.
                start_period = new_incarceration_periods.pop(-1)

                if (not collapse_transfers_with_different_pfi
                        and start_period.specialized_purpose_for_incarceration !=
                        incarceration_period.specialized_purpose_for_incarceration):
                    # If periods with different specialized_purpose_for_incarceration values should not be collapsed,
                    # and this period has a different specialized_purpose_for_incarceration value than the one before
                    # it, add the two period separately
                    new_incarceration_periods.append(start_period)
                    new_incarceration_periods.append(incarceration_period)
                else:
                    combined_period = combine_incarceration_periods(
                        start_period,
                        incarceration_period,
                        overwrite_facility_information=overwrite_facility_information_in_transfers)
                    new_incarceration_periods.append(combined_period)
            else:
                # They weren't transferred here. Add this as a new
                # incarceration period.
                # TODO(#1790): Analyze how often a transfer out is followed by an
                #  admission type that isn't a transfer to ensure we aren't
                #  making bad assumptions with this transfer logic.
                new_incarceration_periods.append(incarceration_period)
        else:
            # TODO(#1790): Analyze how often an incarceration period that starts
            #  with a transfer in is not preceded by a transfer out of a
            #  different facility.
            new_incarceration_periods.append(incarceration_period)

        # If this incarceration period ended in a transfer, then flag
        # that there's an open transfer period.
        open_transfer = (incarceration_period.release_reason == ReleaseReason.TRANSFER)

    return new_incarceration_periods