def test_completeEnumSet_admittedForRevocation(self):
        period = schema.StateIncarcerationPeriod()
        for admission_reason in StateIncarcerationPeriodAdmissionReason:
            period.admission_reason = admission_reason.value
            is_revocation_admission(
                StateIncarcerationPeriodAdmissionReason.
                parse_from_canonical_string(period.admission_reason))

        is_revocation_admission(
            StateIncarcerationPeriodAdmissionReason.
            parse_from_canonical_string(None))
Exemple #2
0
def get_return_type(
    reincarceration_admission_reason: AdmissionReason
) -> ReincarcerationReturnType:
    """Returns the return type for the reincarceration admission reason."""

    if reincarceration_admission_reason == AdmissionReason.ADMITTED_IN_ERROR:
        return ReincarcerationReturnType.NEW_ADMISSION
    if reincarceration_admission_reason == AdmissionReason.EXTERNAL_UNKNOWN:
        return ReincarcerationReturnType.NEW_ADMISSION
    if reincarceration_admission_reason == AdmissionReason.INTERNAL_UNKNOWN:
        return ReincarcerationReturnType.NEW_ADMISSION
    if reincarceration_admission_reason == AdmissionReason.NEW_ADMISSION:
        return ReincarcerationReturnType.NEW_ADMISSION
    if is_revocation_admission(reincarceration_admission_reason):
        return ReincarcerationReturnType.REVOCATION
    if reincarceration_admission_reason == AdmissionReason.TRANSFERRED_FROM_OUT_OF_STATE:
        return ReincarcerationReturnType.NEW_ADMISSION
    if reincarceration_admission_reason == AdmissionReason.TRANSFER:
        # This should be a rare case, but we are considering this a type of new admission recidivism because this person
        # became reincarcerated at some point after being released, and this is the most likely return type in most
        # cases in the absence of further information.
        return ReincarcerationReturnType.NEW_ADMISSION
    if reincarceration_admission_reason in (
            AdmissionReason.RETURN_FROM_ESCAPE,
            AdmissionReason.RETURN_FROM_ERRONEOUS_RELEASE,
            AdmissionReason.TEMPORARY_CUSTODY):
        # This should never happen. Should have been filtered by should_include_in_release_cohort function. Throw error.
        raise ValueError(
            f"should_include_in_release_cohort is not effectively filtering. Found unexpected "
            f"admission_reason of: {reincarceration_admission_reason}")

    raise ValueError(
        f"Enum case not handled for StateIncarcerationPeriodAdmissionReason of type:"
        f" {reincarceration_admission_reason}.")
def revoked_supervision_periods_if_revocation_occurred(
        incarceration_period: StateIncarcerationPeriod,
        supervision_periods: List[StateSupervisionPeriod],
        preceding_incarceration_period: Optional[StateIncarcerationPeriod]) -> \
        Tuple[bool, List[StateSupervisionPeriod]]:
    """If the incarceration period was a result of a supervision revocation, finds the supervision periods that were
    revoked.

    Returns False, [] if the incarceration period was not a result of a revocation. Returns True and the list of
    supervision periods that were revoked if the incarceration period was a result of a revocation. In some cases, it's
    possible for the admission to be a revocation even though we cannot identify the corresponding supervision periods
    that were revoked (e.g. the person was serving supervision out-of-state). In these instances, this function will
    return True and an empty list [].
    """
    revoked_periods: List[StateSupervisionPeriod] = []

    if incarceration_period.state_code.upper() == 'US_ID':
        admission_is_revocation, revoked_period = \
            us_id_revoked_supervision_period_if_revocation_occurred(
                incarceration_period, supervision_periods, preceding_incarceration_period
            )

        if revoked_period:
            revoked_periods = [revoked_period]
    else:
        admission_is_revocation = is_revocation_admission(
            incarceration_period.admission_reason)
        revoked_periods = get_relevant_supervision_periods_before_admission_date(
            incarceration_period.admission_date, supervision_periods)

    return admission_is_revocation, revoked_periods
def incarceration_period_is_from_revocation(
        incarceration_period: StateIncarcerationPeriod,
        preceding_incarceration_period: Optional[StateIncarcerationPeriod]) \
        -> bool:
    """Determines if the sequence of incarceration periods represents a revocation."""
    if incarceration_period.state_code == 'US_ID':
        return us_id_is_revocation_admission(incarceration_period, preceding_incarceration_period)
    return is_revocation_admission(incarceration_period.admission_reason)
def include_event_in_count(incarceration_event: IncarcerationEvent,
                           metric_period_end_date: date,
                           all_events_in_period:
                           Sequence[IncarcerationEvent]) -> bool:
    """Determines whether the given incarceration_event should be included in a person-based count for this given
    calculation_month_upper_bound.

    For the release counts, the last instance of a release before the end of the period is included. For the counts of
    stay events, the last instance of a stay in the period is included.

    For the admission counts, if any of the admissions have an admission_reason indicating a supervision revocation,
    then this incarceration_event is only included if it is the last instance of a revocation admission before the end
    of that period. If none of the admissions in the period are revocation admissions, then this event is included only
    if it is the last instance of an admission before the end of the period.
    """
    events_rest_of_period = incarceration_events_in_period(
        incarceration_event.event_date,
        metric_period_end_date,
        all_events_in_period)

    events_rest_of_period.sort(key=lambda b: b.event_date)

    if isinstance(incarceration_event, IncarcerationReleaseEvent):
        if len(events_rest_of_period) == 1:
            # If this is the last instance of a release before the end of the period, then include it in the
            # person-based count.
            return True
    elif isinstance(incarceration_event, IncarcerationAdmissionEvent):
        revocation_events_in_period = [
            event for event in all_events_in_period
            if isinstance(event, IncarcerationAdmissionEvent) and is_revocation_admission(event.admission_reason)
        ]

        revocation_events_in_period.sort(key=lambda b: b.event_date)

        if (not revocation_events_in_period or
                len(revocation_events_in_period) == len(all_events_in_period)) \
                and len(events_rest_of_period) == 1:
            # If all of the admission events this period are either all revocation or all non-revocation, and this is
            # the last instance of an admission before the end of the period, then include it in the person-based count.
            return True

        if revocation_events_in_period and incarceration_event == \
                revocation_events_in_period[-1]:
            # This person has both revocation and non-revocation admissions during this period. If this is the last
            # revocation admission event, then include it in the person-based count.
            return True
    elif isinstance(incarceration_event, IncarcerationStayEvent):
        # If this is the last recorded event for this month, include it
        if id(incarceration_event) == id(events_rest_of_period[-1]):
            return True

    return False
Exemple #6
0
def has_revocation_admission_in_month(
    date_in_month: date, incarceration_periods_by_admission_month: Dict[
        int, Dict[int, List[StateIncarcerationPeriod]]]
) -> bool:

    if date_in_month.year not in incarceration_periods_by_admission_month or \
            date_in_month.month not in incarceration_periods_by_admission_month[date_in_month.year]:
        return False

    # An admission to prison happened during this month
    incarceration_periods = incarceration_periods_by_admission_month[
        date_in_month.year][date_in_month.month]
    for incarceration_period in incarceration_periods:
        if is_revocation_admission(incarceration_period.admission_reason):
            return True
    return False
Exemple #7
0
def associate_revocation_svrs_with_ips(
        merged_persons: List[schema.StatePerson]):
    """
    For each person in the provided |merged_persons|, attempts to associate
    StateSupervisionViolationResponses that result in revocation with their
    corresponding StateIncarcerationPeriod.
    """
    for person in merged_persons:
        svrs = get_all_entities_of_cls(
            [person], schema.StateSupervisionViolationResponse)
        ips = get_all_entities_of_cls([person],
                                      schema.StateIncarcerationPeriod)

        revocation_svrs: List[schema.StateSupervisionViolationResponse] = []
        for svr in svrs:
            svr = cast(schema.StateSupervisionViolationResponse, svr)
            if revoked_to_prison(svr) and svr.response_date:
                revocation_svrs.append(svr)
        revocation_ips: List[schema.StateIncarcerationPeriod] = []
        for ip in ips:
            ip = cast(schema.StateIncarcerationPeriod, ip)
            admission_reason = StateIncarcerationPeriodAdmissionReason.parse_from_canonical_string(
                ip.admission_reason)
            if isinstance(admission_reason,
                          StateIncarcerationPeriodAdmissionReason):
                if is_revocation_admission(
                        admission_reason) and ip.admission_date:
                    revocation_ips.append(ip)

        if not revocation_svrs or not revocation_ips:
            continue

        sorted_svrs = sorted(revocation_svrs, key=lambda x: x.response_date)

        seen: Set[int] = set()
        for svr in sorted_svrs:
            closest_ip = _get_closest_matching_incarceration_period(
                svr, revocation_ips)
            if closest_ip and id(closest_ip) not in seen:
                seen.add(id(closest_ip))
                closest_ip.source_supervision_violation_response = svr
Exemple #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

    incarceration_periods = prepare_incarceration_periods_for_recidivism_calculations(
        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

        # Admission data and status have been validated already
        if admission_date and status:
            if index == len(incarceration_periods) - 1:
                event = for_last_incarceration_period(
                    state_code, admission_date, status, release_date,
                    release_reason, release_facility, county_of_residence)
            else:
                reincarceration_period = incarceration_periods[index + 1]
                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 release_date and release_reason and reincarceration_date and reincarceration_admission_reason:

                    event = for_intermediate_incarceration_period(
                        state_code=state_code,
                        admission_date=admission_date,
                        release_date=release_date,
                        release_reason=release_reason,
                        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
Exemple #9
0
def find_revocation_return_buckets(
        supervision_sentences: List[StateSupervisionSentence],
        incarceration_sentences: List[StateIncarcerationSentence],
        supervision_periods: List[StateSupervisionPeriod],
        incarceration_periods: List[StateIncarcerationPeriod],
        supervision_time_buckets: List[SupervisionTimeBucket],
        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]]) -> \
        List[SupervisionTimeBucket]:
    """Looks at all incarceration periods to see if they were revocation returns. For each revocation admission, adds
    one RevocationReturnSupervisionTimeBuckets for each overlapping supervision period. If there are no overlapping
    supervision periods, looks for a recently terminated period. If there are no overlapping or recently terminated
    supervision periods, adds a RevocationReturnSupervisionTimeBuckets with as much information about the time on
    supervision as possible.
    """
    revocation_return_buckets: List[SupervisionTimeBucket] = []

    for incarceration_period in incarceration_periods:
        if not incarceration_period.admission_date:
            raise ValueError(
                f"Admission date for null for {incarceration_period}")

        if not is_revocation_admission(incarceration_period.admission_reason):
            continue

        admission_date = incarceration_period.admission_date
        admission_year = admission_date.year
        admission_month = admission_date.month
        end_of_month = last_day_of_month(admission_date)

        assessment_score, assessment_level, assessment_type = find_most_recent_assessment(
            end_of_month, assessments)

        relevant_pre_incarceration_supervision_periods = \
            _get_relevant_supervision_periods_before_admission_date(admission_date, supervision_periods)

        if relevant_pre_incarceration_supervision_periods:
            # Add a RevocationReturnSupervisionTimeBucket for each overlapping supervision period
            for supervision_period in relevant_pre_incarceration_supervision_periods:
                revocation_details = _get_revocation_details(
                    incarceration_period, supervision_period,
                    ssvr_agent_associations,
                    supervision_period_to_agent_associations)

                supervision_type_at_admission = get_pre_incarceration_supervision_type(
                    incarceration_sentences, supervision_sentences,
                    incarceration_period, [supervision_period])

                case_type = _identify_most_severe_case_type(supervision_period)
                supervision_level = supervision_period.supervision_level
                supervision_level_raw_text = supervision_period.supervision_level_raw_text

                # Get details about the violation and response history leading up to the revocation
                violation_history = get_violation_and_response_history(
                    admission_date, violation_responses)

                revocation_month_bucket = RevocationReturnSupervisionTimeBucket(
                    state_code=incarceration_period.state_code,
                    year=admission_year,
                    month=admission_month,
                    supervision_type=supervision_type_at_admission,
                    case_type=case_type,
                    assessment_score=assessment_score,
                    assessment_level=assessment_level,
                    assessment_type=assessment_type,
                    revocation_type=revocation_details.revocation_type,
                    source_violation_type=revocation_details.
                    source_violation_type,
                    most_severe_violation_type=violation_history.
                    most_severe_violation_type,
                    most_severe_violation_type_subtype=violation_history.
                    most_severe_violation_type_subtype,
                    most_severe_response_decision=violation_history.
                    most_severe_response_decision,
                    response_count=violation_history.response_count,
                    violation_history_description=violation_history.
                    violation_history_description,
                    violation_type_frequency_counter=violation_history.
                    violation_type_frequency_counter,
                    supervising_officer_external_id=revocation_details.
                    supervising_officer_external_id,
                    supervising_district_external_id=revocation_details.
                    supervising_district_external_id,
                    supervision_level=supervision_level,
                    supervision_level_raw_text=supervision_level_raw_text)

                revocation_return_buckets.append(revocation_month_bucket)
        else:
            # There are no overlapping or proximal supervision periods. Add one
            # RevocationReturnSupervisionTimeBucket with as many details as possible about this revocation
            revocation_details = _get_revocation_details(
                incarceration_period, None, ssvr_agent_associations, None)

            supervision_type_at_admission = get_pre_incarceration_supervision_type(
                incarceration_sentences, supervision_sentences,
                incarceration_period, [])

            # TODO(2853): Don't default to GENERAL once we figure out how to handle unset fields
            case_type = StateSupervisionCaseType.GENERAL

            end_of_month = last_day_of_month(admission_date)

            assessment_score, assessment_level, assessment_type = find_most_recent_assessment(
                end_of_month, assessments)

            # Get details about the violation and response history leading up to the revocation
            violation_history = get_violation_and_response_history(
                admission_date, violation_responses)

            if supervision_type_at_admission is not None:
                revocation_month_bucket = RevocationReturnSupervisionTimeBucket(
                    state_code=incarceration_period.state_code,
                    year=admission_year,
                    month=admission_month,
                    supervision_type=supervision_type_at_admission,
                    case_type=case_type,
                    assessment_score=assessment_score,
                    assessment_level=assessment_level,
                    assessment_type=assessment_type,
                    revocation_type=revocation_details.revocation_type,
                    source_violation_type=revocation_details.
                    source_violation_type,
                    most_severe_violation_type=violation_history.
                    most_severe_violation_type,
                    most_severe_violation_type_subtype=violation_history.
                    most_severe_violation_type_subtype,
                    most_severe_response_decision=violation_history.
                    most_severe_response_decision,
                    response_count=violation_history.response_count,
                    violation_history_description=violation_history.
                    violation_history_description,
                    violation_type_frequency_counter=violation_history.
                    violation_type_frequency_counter,
                    supervising_officer_external_id=revocation_details.
                    supervising_officer_external_id,
                    supervising_district_external_id=revocation_details.
                    supervising_district_external_id)

                revocation_return_buckets.append(revocation_month_bucket)

    if revocation_return_buckets:
        supervision_time_buckets.extend(revocation_return_buckets)

    return supervision_time_buckets
Exemple #10
0
def find_release_events_by_cohort_year(
    incarceration_periods: List[StateIncarcerationPeriod],
    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

    state_code = get_single_state_code(incarceration_periods)

    county_of_residence = extract_county_of_residence_from_rows(
        persons_to_recent_county_of_residence
    )

    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