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))
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
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
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
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
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
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