def test_pre_processing_managers_for_calculations_no_ips(self): state_code = "US_XX" supervision_period = StateSupervisionPeriod.new_with_defaults( supervision_period_id=111, external_id="sp1", status=StateSupervisionPeriodStatus.TERMINATED, state_code="US_XX", start_date=datetime.date(2017, 3, 5), admission_reason=StateSupervisionPeriodAdmissionReason. COURT_SENTENCE, termination_date=datetime.date(2017, 5, 9), termination_reason=StateSupervisionPeriodTerminationReason. REVOCATION, ) ( ip_pre_processing_manager, sp_pre_processing_manager, ) = pre_processing_managers_for_calculations( state_code=state_code, incarceration_periods=None, supervision_periods=[supervision_period], violation_responses=None, ) self.assertIsNone(ip_pre_processing_manager) self.assertEqual( [supervision_period], sp_pre_processing_manager. pre_processed_supervision_period_index_for_calculations( ).supervision_periods, )
def test_pre_processing_managers_for_calculations_no_violation_responses_state_requires( self, ): self.mock_pre_processing_delegate.return_value = ( TestIncarcerationPreProcessingDelegate()) state_code = "US_XX" incarceration_period = StateIncarcerationPeriod.new_with_defaults( incarceration_period_id=111, external_id="ip1", status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, incarceration_type=StateIncarcerationType.STATE_PRISON, state_code="US_XX", admission_date=datetime.date(2017, 4, 11), admission_reason=StateIncarcerationPeriodAdmissionReason. NEW_ADMISSION, release_date=datetime.date(2020, 5, 17), release_reason=StateIncarcerationPeriodReleaseReason. SENTENCE_SERVED, ) with self.assertRaises(ValueError): # Assert an error is raised if the violation_responses arg is None for a # state that relies on violation responses for IP pre-processing ( _, _, ) = pre_processing_managers_for_calculations( state_code=state_code, incarceration_periods=[incarceration_period], supervision_periods=[], violation_responses=None, )
def test_pre_processing_managers_for_calculations(self): state_code = "US_XX" supervision_period = StateSupervisionPeriod.new_with_defaults( supervision_period_id=111, external_id="sp1", status=StateSupervisionPeriodStatus.TERMINATED, state_code="US_XX", start_date=datetime.date(2017, 3, 5), admission_reason=StateSupervisionPeriodAdmissionReason. COURT_SENTENCE, termination_date=datetime.date(2017, 5, 9), termination_reason=StateSupervisionPeriodTerminationReason. REVOCATION, ) incarceration_period = StateIncarcerationPeriod.new_with_defaults( incarceration_period_id=111, external_id="ip1", status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY, incarceration_type=StateIncarcerationType.STATE_PRISON, state_code="US_XX", admission_date=datetime.date(2017, 4, 11), admission_reason=StateIncarcerationPeriodAdmissionReason. NEW_ADMISSION, release_date=datetime.date(2020, 5, 17), release_reason=StateIncarcerationPeriodReleaseReason. SENTENCE_SERVED, specialized_purpose_for_incarceration= StateSpecializedPurposeForIncarceration.GENERAL, ) ( ip_pre_processing_manager, sp_pre_processing_manager, ) = pre_processing_managers_for_calculations( state_code=state_code, incarceration_periods=[incarceration_period], supervision_periods=[supervision_period], violation_responses=None, ) self.assertEqual( [incarceration_period], ip_pre_processing_manager. pre_processed_incarceration_period_index_for_calculations( collapse_transfers=False, overwrite_facility_information_in_transfers=False, ).incarceration_periods, ) self.assertEqual( [supervision_period], sp_pre_processing_manager. pre_processed_supervision_period_index_for_calculations( ).supervision_periods, )
def test_pre_processing_managers_for_calculations_empty_lists(self): state_code = "US_XX" ( ip_pre_processing_manager, sp_pre_processing_manager, ) = pre_processing_managers_for_calculations( state_code=state_code, incarceration_periods=[], supervision_periods=[], violation_responses=[], ) self.assertEqual([], ip_pre_processing_manager._incarceration_periods) self.assertEqual([], sp_pre_processing_manager._supervision_periods)
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
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
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