def add_person_level_characteristics(person, incarceration_event, characteristics): """Given |characteristics|, adds new characteristics that do not matter for aggregation and are only important for person-level metrics. These characteristics therefore are NOT used to generate the dictionaries with unique combinations, but are simply add-ons to those dictionaries. """ characteristics_with_person_details = characteristics_with_person_id_fields( characteristics, incarceration_event.state_code, person, 'incarceration') if isinstance(incarceration_event, IncarcerationStayEvent): if incarceration_event.most_serious_offense_statute: characteristics_with_person_details['most_serious_offense_ncic_code'] = \ incarceration_event.most_serious_offense_ncic_code if incarceration_event.most_serious_offense_statute: characteristics_with_person_details['most_serious_offense_statute'] = \ incarceration_event.most_serious_offense_statute if incarceration_event.admission_reason_raw_text: characteristics_with_person_details['admission_reason_raw_text'] = \ incarceration_event.admission_reason_raw_text if isinstance(incarceration_event, IncarcerationAdmissionEvent): if incarceration_event.admission_reason_raw_text: characteristics_with_person_details['admission_reason_raw_text'] = \ incarceration_event.admission_reason_raw_text return characteristics_with_person_details
def characteristics_dict(person: StatePerson, program_event: ProgramEvent) -> Dict[str, Any]: """Builds a dictionary that describes the characteristics of the person and event. Args: person: the StatePerson we are picking characteristics from program_event: the ProgramEvent we are picking characteristics from Returns: A dictionary populated with all relevant characteristics. """ characteristics: Dict[str, Any] = {} event_date = program_event.event_date if isinstance(program_event, ProgramReferralEvent): if program_event.supervision_type: characteristics['supervision_type'] = program_event.supervision_type if program_event.assessment_score and program_event.assessment_type: assessment_bucket = assessment_score_bucket( assessment_score=program_event.assessment_score, assessment_level=None, assessment_type=program_event.assessment_type) if assessment_bucket and include_assessment_in_metric( 'program', program_event.state_code, program_event.assessment_type): characteristics['assessment_score_bucket'] = assessment_bucket characteristics['assessment_type'] = program_event.assessment_type if program_event.participation_status: characteristics['participation_status'] = program_event.participation_status if program_event.supervising_officer_external_id: characteristics['supervising_officer_external_id'] = program_event.supervising_officer_external_id if program_event.supervising_district_external_id: characteristics['supervising_district_external_id'] = program_event.supervising_district_external_id elif isinstance(program_event, ProgramParticipationEvent): characteristics['date_of_participation'] = event_date if program_event.supervision_type: characteristics['supervision_type'] = program_event.supervision_type if program_event.program_location_id: characteristics['program_location_id'] = program_event.program_location_id if program_event.program_id: characteristics['program_id'] = program_event.program_id characteristics = add_demographic_characteristics(characteristics, person, event_date) characteristics_with_person_details = characteristics_with_person_id_fields( characteristics, program_event.state_code, person, 'program') return characteristics_with_person_details
def characteristics_dict( person: StatePerson, event: ReleaseEvent, metric_type: ReincarcerationRecidivismMetricType) -> Dict[str, Any]: """Builds a dictionary that describes the characteristics of the person and the release event. Release cohort, follow-up period, and methodology are not included in the output here. They are added into augmented versions of these combinations later. Args: person: the StatePerson we are picking characteristics from event: the ReleaseEvent we are picking characteristics from metric_type: The ReincarcerationRecidivismMetricType provided determines which fields should be added to the characteristics dictionary Returns: A dictionary populated with all relevant characteristics. """ characteristics: Dict[str, Any] = {} if event.county_of_residence: characteristics['county_of_residence'] = event.county_of_residence if event.release_facility is not None: characteristics['release_facility'] = event.release_facility event_stay_length = stay_length_from_event(event) event_stay_length_bucket = stay_length_bucket(event_stay_length) characteristics['stay_length_bucket'] = event_stay_length_bucket characteristics = add_demographic_characteristics( characteristics, person, event.original_admission_date) if isinstance( event, RecidivismReleaseEvent ) and metric_type == ReincarcerationRecidivismMetricType.COUNT: time_at_liberty = days_at_liberty(event) characteristics['days_at_liberty'] = time_at_liberty characteristics = characteristics_with_person_id_fields( characteristics, event.state_code, person, 'recidivism') return characteristics
def characteristics_dict(person: StatePerson, supervision_time_bucket: SupervisionTimeBucket, metric_type: SupervisionMetricType) -> Dict[str, Any]: """Builds a dictionary that describes the characteristics of the person and supervision_time_bucket. Args: person: the StatePerson we are picking characteristics from supervision_time_bucket: the SupervisionTimeBucket we are picking characteristics from metric_type: The SupervisionMetricType provided determines which fields should be added to the characteristics dictionary Returns: A dictionary populated with all relevant characteristics. """ characteristics: Dict[str, Any] = {} include_revocation_dimensions = _include_revocation_dimensions_for_metric( metric_type) include_assessment_dimensions = _include_assessment_dimensions_for_metric( metric_type) include_demographic_dimensions = _include_demographic_dimensions_for_metric( metric_type) include_person_level_dimensions = _include_person_level_dimensions_for_metric( metric_type) if (metric_type == SupervisionMetricType.POPULATION and isinstance(supervision_time_bucket, (RevocationReturnSupervisionTimeBucket, NonRevocationReturnSupervisionTimeBucket))): if supervision_time_bucket.most_severe_violation_type: characteristics[ 'most_severe_violation_type'] = supervision_time_bucket.most_severe_violation_type if supervision_time_bucket.most_severe_violation_type_subtype: characteristics['most_severe_violation_type_subtype'] = \ supervision_time_bucket.most_severe_violation_type_subtype if supervision_time_bucket.response_count is not None: characteristics[ 'response_count'] = supervision_time_bucket.response_count if include_revocation_dimensions and \ isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): if supervision_time_bucket.revocation_type: characteristics[ 'revocation_type'] = supervision_time_bucket.revocation_type if supervision_time_bucket.source_violation_type: characteristics[ 'source_violation_type'] = supervision_time_bucket.source_violation_type if metric_type in [ SupervisionMetricType.REVOCATION_ANALYSIS, SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS ]: if supervision_time_bucket.most_severe_violation_type: characteristics[ 'most_severe_violation_type'] = supervision_time_bucket.most_severe_violation_type if supervision_time_bucket.most_severe_violation_type_subtype: characteristics['most_severe_violation_type_subtype'] = \ supervision_time_bucket.most_severe_violation_type_subtype if metric_type in [SupervisionMetricType.REVOCATION_ANALYSIS]: if supervision_time_bucket.most_severe_response_decision: characteristics['most_severe_response_decision'] = \ supervision_time_bucket.most_severe_response_decision if supervision_time_bucket.response_count is not None: characteristics[ 'response_count'] = supervision_time_bucket.response_count if isinstance(supervision_time_bucket, SupervisionTerminationBucket): if supervision_time_bucket.termination_reason: characteristics[ 'termination_reason'] = supervision_time_bucket.termination_reason if supervision_time_bucket.supervision_type: characteristics[ 'supervision_type'] = supervision_time_bucket.supervision_type if supervision_time_bucket.case_type: characteristics['case_type'] = supervision_time_bucket.case_type if not include_revocation_dimensions and supervision_time_bucket.supervision_level: characteristics[ 'supervision_level'] = supervision_time_bucket.supervision_level if include_assessment_dimensions: # TODO(2853): Figure out more robust solution for not assessed people. Here we don't set assessment_type when # someone is not assessed. This only works as desired because BQ doesn't rely on assessment_type at all. characteristics['assessment_score_bucket'] = 'NOT_ASSESSED' if supervision_time_bucket.assessment_score and supervision_time_bucket.assessment_type: assessment_bucket = assessment_score_bucket( supervision_time_bucket.assessment_score, supervision_time_bucket.assessment_level, supervision_time_bucket.assessment_type) if assessment_bucket and include_assessment_in_metric( 'supervision', supervision_time_bucket.state_code, supervision_time_bucket.assessment_type): characteristics['assessment_score_bucket'] = assessment_bucket characteristics[ 'assessment_type'] = supervision_time_bucket.assessment_type if supervision_time_bucket.supervising_officer_external_id: characteristics[ 'supervising_officer_external_id'] = supervision_time_bucket.supervising_officer_external_id if supervision_time_bucket.supervising_district_external_id: characteristics[ 'supervising_district_external_id'] = supervision_time_bucket.supervising_district_external_id if isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): event_date = supervision_time_bucket.revocation_admission_date elif isinstance(supervision_time_bucket, SupervisionTerminationBucket): event_date = supervision_time_bucket.termination_date else: year = supervision_time_bucket.year month = supervision_time_bucket.month event_date = date(year, month, 1) if include_demographic_dimensions: characteristics = add_demographic_characteristics( characteristics, person, event_date) if include_person_level_dimensions: characteristics = characteristics_with_person_id_fields( characteristics, person, 'supervision') if not include_revocation_dimensions and supervision_time_bucket.supervision_level_raw_text: characteristics['supervision_level_raw_text'] = \ supervision_time_bucket.supervision_level_raw_text if metric_type == SupervisionMetricType.POPULATION: if isinstance(supervision_time_bucket, (RevocationReturnSupervisionTimeBucket, NonRevocationReturnSupervisionTimeBucket)): characteristics['is_on_supervision_last_day_of_month'] = \ supervision_time_bucket.is_on_supervision_last_day_of_month if metric_type == SupervisionMetricType.REVOCATION_ANALYSIS: if isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket) \ and supervision_time_bucket.violation_history_description: characteristics['violation_history_description'] = \ supervision_time_bucket.violation_history_description if include_revocation_dimensions and isinstance( supervision_time_bucket, RevocationReturnSupervisionTimeBucket): characteristics['revocation_admission_date'] = \ supervision_time_bucket.revocation_admission_date if metric_type == SupervisionMetricType.ASSESSMENT_CHANGE: if isinstance(supervision_time_bucket, SupervisionTerminationBucket) \ and supervision_time_bucket.termination_date: characteristics[ 'termination_date'] = supervision_time_bucket.termination_date return characteristics
def characteristic_combinations(person: StatePerson, supervision_time_bucket: SupervisionTimeBucket, inclusions: Dict[str, bool], metric_type: SupervisionMetricType) -> \ List[Dict[str, Any]]: """Calculates all supervision metric combinations. Returns the list of all combinations of the metric characteristics, of all sizes, given the StatePerson and SupervisionTimeBucket. That is, this returns a list of dictionaries where each dictionary is a combination of 0 to n unique elements of characteristics applicable to the given person and supervision_time_bucket. Args: person: the StatePerson we are picking characteristics from supervision_time_bucket: the SupervisionTimeBucket we are picking characteristics from inclusions: A dictionary containing the following keys that correspond to characteristic dimensions: - age_bucket - ethnicity - gender - race Where the values are boolean flags indicating whether to include the dimension in the calculations. metric_type: The SupervisionMetricType provided determines which fields should be added to the characteristics dictionary Returns: A list of dictionaries containing all unique combinations of characteristics. """ characteristics: Dict[str, Any] = {} include_revocation_dimensions = _include_revocation_dimensions_for_metric( metric_type) include_assessment_dimensions = _include_assessment_dimensions_for_metric( metric_type) include_person_level_dimensions = _include_person_level_dimensions_for_metric( metric_type) if (metric_type == SupervisionMetricType.POPULATION and isinstance(supervision_time_bucket, (RevocationReturnSupervisionTimeBucket, NonRevocationReturnSupervisionTimeBucket))): if supervision_time_bucket.most_severe_violation_type: characteristics[ 'most_severe_violation_type'] = supervision_time_bucket.most_severe_violation_type if supervision_time_bucket.most_severe_violation_type_subtype: characteristics['most_severe_violation_type_subtype'] = \ supervision_time_bucket.most_severe_violation_type_subtype if supervision_time_bucket.response_count is not None: characteristics[ 'response_count'] = supervision_time_bucket.response_count if include_revocation_dimensions and \ isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket): if supervision_time_bucket.revocation_type: characteristics[ 'revocation_type'] = supervision_time_bucket.revocation_type if supervision_time_bucket.source_violation_type: characteristics[ 'source_violation_type'] = supervision_time_bucket.source_violation_type if metric_type in [ SupervisionMetricType.REVOCATION_ANALYSIS, SupervisionMetricType.REVOCATION_VIOLATION_TYPE_ANALYSIS ]: if supervision_time_bucket.most_severe_violation_type: characteristics[ 'most_severe_violation_type'] = supervision_time_bucket.most_severe_violation_type if supervision_time_bucket.most_severe_violation_type_subtype: characteristics['most_severe_violation_type_subtype'] = \ supervision_time_bucket.most_severe_violation_type_subtype if metric_type in [SupervisionMetricType.REVOCATION_ANALYSIS]: if supervision_time_bucket.most_severe_response_decision: characteristics['most_severe_response_decision'] = \ supervision_time_bucket.most_severe_response_decision if supervision_time_bucket.response_count is not None: characteristics[ 'response_count'] = supervision_time_bucket.response_count if isinstance(supervision_time_bucket, SupervisionTerminationBucket): if supervision_time_bucket.termination_reason: characteristics[ 'termination_reason'] = supervision_time_bucket.termination_reason if supervision_time_bucket.supervision_type: characteristics[ 'supervision_type'] = supervision_time_bucket.supervision_type if supervision_time_bucket.case_type: characteristics['case_type'] = supervision_time_bucket.case_type if not include_revocation_dimensions and supervision_time_bucket.supervision_level: characteristics[ 'supervision_level'] = supervision_time_bucket.supervision_level if include_assessment_dimensions: # TODO(2853): Figure out more robust solution for not assessed people. Here we don't set assessment_type when # someone is not assessed. This only works as desired because BQ doesn't rely on assessment_type at all. characteristics['assessment_score_bucket'] = 'NOT_ASSESSED' if supervision_time_bucket.assessment_score and supervision_time_bucket.assessment_type: assessment_bucket = assessment_score_bucket( supervision_time_bucket.assessment_score, supervision_time_bucket.assessment_level, supervision_time_bucket.assessment_type) if assessment_bucket and include_assessment_in_metric( 'supervision', supervision_time_bucket.state_code, supervision_time_bucket.assessment_type): characteristics['assessment_score_bucket'] = assessment_bucket characteristics[ 'assessment_type'] = supervision_time_bucket.assessment_type if supervision_time_bucket.supervising_officer_external_id: characteristics[ 'supervising_officer_external_id'] = supervision_time_bucket.supervising_officer_external_id if supervision_time_bucket.supervising_district_external_id: characteristics[ 'supervising_district_external_id'] = supervision_time_bucket.supervising_district_external_id if inclusions.get('age_bucket'): year = supervision_time_bucket.year month = supervision_time_bucket.month start_of_bucket = date(year, month, 1) entry_age = age_at_date(person, start_of_bucket) entry_age_bucket = age_bucket(entry_age) if entry_age_bucket is not None: characteristics['age_bucket'] = entry_age_bucket if inclusions.get('gender'): if person.gender is not None: characteristics['gender'] = person.gender if person.races or person.ethnicities: if inclusions.get('race'): races = person.races else: races = [] if inclusions.get('ethnicity'): ethnicities = person.ethnicities else: ethnicities = [] all_combinations = for_characteristics_races_ethnicities( races, ethnicities, characteristics) else: all_combinations = for_characteristics(characteristics) if include_person_level_dimensions: characteristics_with_person_details = characteristics_with_person_id_fields( characteristics, person, 'supervision') if not include_revocation_dimensions and supervision_time_bucket.supervision_level_raw_text: characteristics_with_person_details['supervision_level_raw_text'] = \ supervision_time_bucket.supervision_level_raw_text if metric_type == SupervisionMetricType.POPULATION: if isinstance(supervision_time_bucket, (RevocationReturnSupervisionTimeBucket, NonRevocationReturnSupervisionTimeBucket)): characteristics_with_person_details['is_on_supervision_last_day_of_month'] = \ supervision_time_bucket.is_on_supervision_last_day_of_month if metric_type == SupervisionMetricType.REVOCATION_ANALYSIS: # Only include violation history descriptions on person-level metrics if isinstance(supervision_time_bucket, RevocationReturnSupervisionTimeBucket) \ and supervision_time_bucket.violation_history_description: characteristics_with_person_details['violation_history_description'] = \ supervision_time_bucket.violation_history_description all_combinations.append(characteristics_with_person_details) return all_combinations
def characteristic_combinations(person: StatePerson, event: ReleaseEvent, inclusions: Dict[str, bool]) -> \ List[Dict[str, Any]]: """Calculates all recidivism metric combinations. Returns the list of all combinations of the metric characteristics, of all sizes, given the StatePerson and ReleaseEvent. That is, this returns a list of dictionaries where each dictionary is a combination of 0 to n unique elements of characteristics, where n is the size of the given array. For each event, we need to calculate metrics across combinations of: Release Cohort; Follow-up Period (up to 10 years); MetricMethodologyType (Event-based, Person-based); Demographics (age, race, ethnicity, gender); Location (facility, region); Facility Stay Breakdown (stay length); Return Descriptors (return type, from supervision type) TODO: Add support for offense and sentencing type (Issues 33 and 32) Release cohort, follow-up period, and methodology are not included in the output here. They are added into augmented versions of these combinations later. The output for a black female age 24 and an incarceration that began in January 2008 and ended in February 2009 is equal to the output of: for_characteristics({'age': '<25', 'race': Race.BLACK, 'gender': Gender.FEMALE, 'stay_length_bucket': '12-24'}) Our schema allows for a StatePerson to have more than one race. The output for a female age 24 who is both white and black and an incarceration that began in January 2008 and ended in February 2009 is equal to the union of the outputs for: for_characteristics({'age': '<25', 'race': Race.BLACK, 'gender': Gender.FEMALE, 'stay_length_bucket': '12-24'}) and for_characteristics({'age': '<25', 'race': Race.WHITE, 'gender': Gender.FEMALE, 'stay_length_bucket': '12-24'}) where there are no duplicate metrics in the output. Args: person: the StatePerson we are picking characteristics from event: the ReleaseEvent we are picking characteristics from inclusions: A dictionary containing the following keys that correspond to characteristic dimensions: - age_bucket - ethnicity - gender - race - release_facility - stay_length_bucket Where the values are boolean flags indicating whether to include the dimension in the calculations. Returns: A list of dictionaries containing all unique combinations of characteristics. """ characteristics: Dict[str, Any] = {} if event.county_of_residence: characteristics['county_of_residence'] = event.county_of_residence if inclusions.get('age_bucket'): entry_age = age_at_date(person, event.original_admission_date) entry_age_bucket = age_bucket(entry_age) if entry_age_bucket is not None: characteristics['age_bucket'] = entry_age_bucket if inclusions.get('gender'): if person.gender is not None: characteristics['gender'] = person.gender if inclusions.get('release_facility'): if event.release_facility is not None: characteristics['release_facility'] = event.release_facility if inclusions.get('stay_length_bucket'): event_stay_length = stay_length_from_event(event) event_stay_length_bucket = stay_length_bucket(event_stay_length) characteristics['stay_length_bucket'] = event_stay_length_bucket if person.races or person.ethnicities: if inclusions.get('race'): races = person.races else: races = [] if inclusions.get('ethnicity'): ethnicities = person.ethnicities else: ethnicities = [] all_combinations = for_characteristics_races_ethnicities( races, ethnicities, characteristics) else: all_combinations = for_characteristics(characteristics) characteristics_with_person_details = characteristics_with_person_id_fields( characteristics, person, 'recidivism') all_combinations.append(characteristics_with_person_details) return all_combinations