def test_for_characteristics():
    characteristics = {'race': 'black', 'gender': 'female', 'age': '<25'}
    combinations = calculator_utils.for_characteristics(characteristics)

    assert combinations == [{}, {
        'race': 'black'
    }, {
        'gender': 'female'
    }, {
        'age': '<25'
    }, {
        'race': 'black',
        'gender': 'female'
    }, {
        'age': '<25',
        'race': 'black'
    }, {
        'age': '<25',
        'gender': 'female'
    }, {
        'age': '<25',
        'race': 'black',
        'gender': 'female'
    }]
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_for_characteristics_one_characteristic():
    characteristics = {'gender': 'male'}
    combinations = calculator_utils.for_characteristics(characteristics)

    assert combinations == [{}, {'gender': 'male'}]
Beispiel #5
0
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