def test_age_at_date_later_month(): birthdate = date(1989, 6, 17) check_date = date(2014, 7, 11) person = StatePerson.new_with_defaults(state_code="US_XX", birthdate=birthdate) assert calculator_utils.age_at_date(person, check_date) == 25
def test_age_at_date_birthdate_unknown(): assert (calculator_utils.age_at_date( StatePerson.new_with_defaults(state_code="US_XX"), datetime.today()) is None)
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, incarceration_event: IncarcerationEvent, inclusions: Dict[str, bool], metric_type: IncarcerationMetricType) -> \ List[Dict[str, Any]]: """Calculates all incarceration metric combinations. Returns the list of all combinations of the metric characteristics, of all sizes, given the StatePerson and IncarcerationEvent. 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 number of keys in the given inclusions dictionary that are set to True + the dimensions for the given type of event. Args: person: the StatePerson we are picking characteristics from incarceration_event: the IncarcerationEvent 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 IncarcerationMetricType that 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] = {} # Add characteristics that will be used to generate dictionaries with unique combinations. if isinstance(incarceration_event, IncarcerationAdmissionEvent): if incarceration_event.admission_reason: characteristics['admission_reason'] = incarceration_event.admission_reason if incarceration_event.supervision_type_at_admission: characteristics['supervision_type_at_admission'] = incarceration_event.supervision_type_at_admission if incarceration_event.specialized_purpose_for_incarceration: characteristics['specialized_purpose_for_incarceration'] = \ incarceration_event.specialized_purpose_for_incarceration if isinstance(incarceration_event, IncarcerationReleaseEvent): if incarceration_event.release_reason: characteristics['release_reason'] = incarceration_event.release_reason if isinstance(incarceration_event, IncarcerationStayEvent): if incarceration_event.admission_reason: characteristics['admission_reason'] = incarceration_event.admission_reason if incarceration_event.supervision_type_at_admission: characteristics['supervision_type_at_admission'] = incarceration_event.supervision_type_at_admission # Always include facility as a dimension if incarceration_event.facility: characteristics['facility'] = incarceration_event.facility # Always include county_of_residence as a dimension if incarceration_event.county_of_residence: characteristics['county_of_residence'] = incarceration_event.county_of_residence if inclusions.get('age_bucket'): start_of_bucket = first_day_of_month(incarceration_event.event_date) 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) characteristics_with_person_details = add_person_level_characteristics( person, incarceration_event, characteristics) if metric_type == IncarcerationMetricType.ADMISSION: characteristics_with_person_details['admission_date'] = incarceration_event.event_date all_combinations.append(characteristics_with_person_details) return all_combinations
def test_age_at_date_same_month_later_date(): birthdate = date(1989, 6, 17) check_date = date(2014, 6, 18) person = StatePerson.new_with_defaults(birthdate=birthdate) assert calculator_utils.age_at_date(person, check_date) == 25
def test_age_at_date_earlier_month(): birthdate = date(1989, 6, 17) check_date = date(2014, 4, 15) person = StatePerson.new_with_defaults(birthdate=birthdate) assert calculator_utils.age_at_date(person, check_date) == 24
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