Ejemplo n.º 1
0
def supervision_period_supervision_type_mapper(
    label: str,
) -> Optional[StateSupervisionPeriodSupervisionType]:
    """Parses status information from the 'offstat' table into potential supervision types. Ranking of priority
    is taken from Idaho itself (the 'statstrt' table in the us_id_raw_data dataset).
    """
    statuses = sorted_list_from_str(label, delimiter=" ")
    if "PR" in statuses and "PB" in statuses:  # Parole and Probation
        return StateSupervisionPeriodSupervisionType.DUAL
    if "PR" in statuses:  # Parole
        return StateSupervisionPeriodSupervisionType.PAROLE
    if "PB" in statuses:  # Probation
        return StateSupervisionPeriodSupervisionType.PROBATION
    if "PS" in statuses:  # Pre sentence investigation
        return StateSupervisionPeriodSupervisionType.INVESTIGATION
    if (
        "PA" in statuses
    ):  # Pardon applicant     TODO(#3506): Get more info from ID. Filter these people out entirely?
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if (
        "PF" in statuses
    ):  # Firearm applicant    TODO(#3506): Get more info from ID. Filter these people out entirely?
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if "CR" in statuses:  # Rider (no longer used).
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if "BW" in statuses:  # Bench Warrant
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if "CP" in statuses:  # Court Probation
        return StateSupervisionPeriodSupervisionType.INFORMAL_PROBATION
    if "TM" in statuses:  # Termer
        # Note: This in general shouldn't be showing up since Termer is an
        # incarceration type and not a supervision type. We have this as
        # a fallback here to handle erroneous instances that we've seen.
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    return None
Ejemplo n.º 2
0
def supervision_period_supervision_type_mapper(
        label: str) -> Optional[StateSupervisionPeriodSupervisionType]:
    """Parses status information from the 'offstat' table into potential supervision types. Ranking of priority
    is taken from Idaho itself (the 'statstrt' table in the us_id_raw_data dataset).
    """
    statuses = sorted_list_from_str(label, delimiter=' ')
    if 'PR' in statuses and 'PB' in statuses:  # Parole and Probation
        return StateSupervisionPeriodSupervisionType.DUAL
    if 'PR' in statuses:  # Parole
        return StateSupervisionPeriodSupervisionType.PAROLE
    if 'PB' in statuses:  # Probation
        return StateSupervisionPeriodSupervisionType.PROBATION
    if 'PS' in statuses:  # Pre sentence investigation
        return StateSupervisionPeriodSupervisionType.INVESTIGATION
    if 'PA' in statuses:  # Pardon applicant     TODO(#3506): Get more info from ID. Filter these people out entirely?
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if 'PF' in statuses:  # Firearm applicant    TODO(#3506): Get more info from ID. Filter these people out entirely?
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if 'CR' in statuses:  # Rider (no longer used).
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if 'BW' in statuses:  # Bench Warrant
        return StateSupervisionPeriodSupervisionType.INTERNAL_UNKNOWN
    if 'CP' in statuses:  # Court Probation
        return StateSupervisionPeriodSupervisionType.INFORMAL_PROBATION
    return None
Ejemplo n.º 3
0
def purpose_for_incarceration_mapper(
    label: str,
) -> Optional[StateSpecializedPurposeForIncarceration]:
    """Parses status information from the 'offstat' table into potential purposes for incarceration. Ranking of priority
    is taken from Idaho itself (the 'statstrt' table in the us_id_raw_data dataset).
    """
    statuses = sorted_list_from_str(label, delimiter=" ")
    if "TM" in statuses:  # Termer
        return StateSpecializedPurposeForIncarceration.GENERAL
    if "RJ" in statuses:  # Rider
        return StateSpecializedPurposeForIncarceration.TREATMENT_IN_PRISON
    if (
        "NO" in statuses
    ):  # Non Idaho commitment TODO(#3518): Consider adding as specialized purpose.
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if "PV" in statuses:  # Parole board hold
        return StateSpecializedPurposeForIncarceration.PAROLE_BOARD_HOLD
    if (
        "IP" in statuses
    ):  # Institutional Probation   TODO(#3518): Understand what this is.
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if (
        "CV" in statuses
    ):  # Civil commitment      TODO(#3518): Consider adding a specialized purpose for this
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if "CH" in statuses:  # Courtesy Hold         TODO(#3518): Understand what this is
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if (
        "PB" in statuses
    ):  # Probation -- happens VERY infrequently (occurs as a data error from ID)
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    return None
Ejemplo n.º 4
0
def supervision_period_admission_reason_mapper(
    label: str,
) -> Optional[StateSupervisionPeriodAdmissionReason]:
    """Maps |label|, a space delimited list of statuses from TAK026, to the most relevant
    SupervisionPeriodAdmissionReason, when possible.

    If the status list is empty, we assume that this period ended because the person transferred between POs or offices.
    """
    if not label:
        raise ValueError(
            "Unexpected empty/null status list - empty values should not be passed to this mapper"
        )

    if label == "TRANSFER WITHIN STATE":
        return StateSupervisionPeriodAdmissionReason.TRANSFER_WITHIN_STATE

    # TODO(#2865): Update enum normalization so that we separate by commas instead of spaces
    statuses = sorted_list_from_str(label, " ")

    def status_rank(status: str) -> int:
        """In the case that there are multiple statuses on the same day, we pick the status that is most likely to
        give us accurate info about the reason this supervision period was started. In the case of supervision
        period admissions, we pick statuses that have the pattern X5I* (e.g. '15I1000'), since those statuses are
        field (5) IN (I) statuses. In the absence if one of those statuses, we get our info from other statuses.
        """
        if status not in INVESTIGATION_START_STATUSES:
            if re.match(TAK026_STATUS_SUPERVISION_PERIOD_START_REGEX, status):
                return 0

            return 1

        # Since we filter out all portions of supervision periods that happen before an initial investigation is over,
        # if we find a period that starts with one of these statuses, it means a new investigation happened to open up
        # on the same day as a person transferred POs. We generally want to ignore this case and just treat it as a
        # transfer unless there are other statuses that give us more info.
        return 2

    sorted_statuses = sorted(
        statuses, key=lambda status: _status_rank_str(status, status_rank)
    )

    for sp_admission_reason_str in sorted_statuses:
        if (
            sp_admission_reason_str
            in STR_TO_SUPERVISION_PERIOD_ADMISSION_REASON_MAPPINGS
        ):
            return STR_TO_SUPERVISION_PERIOD_ADMISSION_REASON_MAPPINGS[
                sp_admission_reason_str
            ]

    return StateSupervisionPeriodAdmissionReason.INTERNAL_UNKNOWN
Ejemplo n.º 5
0
def incarceration_period_admission_reason_mapper(
    status_list_str: str,
) -> StateIncarcerationPeriodAdmissionReason:
    """Converts a string with a list of TAK026 MO status codes into a valid incarceration period admission reason."""
    start_statuses = sorted_list_from_str(status_list_str, " ")

    ranked_status_map: Dict[int, List[str]] = {}

    # First rank all statuses individually
    for status_str in start_statuses:
        status_rank = rank_incarceration_period_admission_reason_status_str(status_str)
        if status_rank is None:
            # If None, this is not an status code for determining the admission status
            continue
        if status_rank not in ranked_status_map:
            ranked_status_map[status_rank] = []
        ranked_status_map[status_rank].append(status_str)

    if not ranked_status_map:
        # None of the statuses can meaningfully tell us what the admission reason is (rare)
        return StateIncarcerationPeriodAdmissionReason.INTERNAL_UNKNOWN

    # Find the highest order status(es) and use those to determine the admission reason
    highest_rank = sorted(list(ranked_status_map.keys()))[0]
    statuses_at_rank = ranked_status_map[highest_rank]

    potential_admission_reasons: Set[StateIncarcerationPeriodAdmissionReason] = set()
    for status_str in statuses_at_rank:
        if status_str not in STR_TO_INCARCERATION_PERIOD_ADMISSION_REASON_MAPPINGS:
            raise ValueError(
                f"No mapping for incarceration admission status {status_str}"
            )
        potential_admission_reasons.add(
            STR_TO_INCARCERATION_PERIOD_ADMISSION_REASON_MAPPINGS[status_str]
        )

    if potential_admission_reasons == {
        StateIncarcerationPeriodAdmissionReason.PROBATION_REVOCATION,
        StateIncarcerationPeriodAdmissionReason.PAROLE_REVOCATION,
    }:
        return StateIncarcerationPeriodAdmissionReason.DUAL_REVOCATION

    if len(potential_admission_reasons) > 1:
        raise EnumParsingError(
            StateIncarcerationPeriodAdmissionReason,
            f"Found status codes with conflicting information: [{statuses_at_rank}], which evaluate to "
            f"[{potential_admission_reasons}]",
        )

    return one(potential_admission_reasons)
Ejemplo n.º 6
0
def supervision_period_termination_reason_mapper(
    label: str,
) -> Optional[StateSupervisionPeriodTerminationReason]:
    """Maps |label|, a space delimited list of statuses from TAK026, to the most relevant
    SupervisionPeriodTerminationReason, when possible.

    If the status list is empty, we assume that this period ended because the person transferred between POs or offices.
    """
    if not label:
        raise ValueError(
            "Unexpected empty/null status list - empty values should not be passed to this mapper"
        )

    if label == "TRANSFER WITHIN STATE":
        return StateSupervisionPeriodTerminationReason.TRANSFER_WITHIN_STATE

    # TODO(#2865): Update enum normalization so that we separate by commas instead of spaces
    statuses = sorted_list_from_str(label, " ")

    def status_rank(status: str) -> int:
        """In the case that there are multiple statuses on the same day, we pick the status that is most likely to
        give us accurate info about the reason this supervision period was terminated. In the case of supervision
        period terminations, we pick statuses first that have the pattern 99O* (e.g. '99O9020'), since those
        statuses always end a whole offender cycle, then statuses with pattern 95O* (sentence termination), then
        finally X5O*, since those statuses are field (5) OUT (O) statuses. In the absence if one of those statuses,
        we get our info from other statuses.
        """
        if re.match(TAK026_STATUS_CYCLE_TERMINATION_REGEX, status):
            return 0
        if re.match(TAK026_STATUS_SUPERVISION_SENTENCE_COMPLETION_REGEX, status):
            return 1
        if re.match(TAK026_STATUS_SUPERVISION_PERIOD_TERMINATION_REGEX, status):
            return 2
        return 3

    sorted_statuses = sorted(
        statuses, key=lambda status: _status_rank_str(status, status_rank)
    )

    for sp_termination_reason_str in sorted_statuses:
        if (
            sp_termination_reason_str
            in STR_TO_SUPERVISION_PERIOD_TERMINATION_REASON_MAPPINGS
        ):
            return STR_TO_SUPERVISION_PERIOD_TERMINATION_REASON_MAPPINGS[
                sp_termination_reason_str
            ]

    return StateSupervisionPeriodTerminationReason.INTERNAL_UNKNOWN
Ejemplo n.º 7
0
    def _hydrate_violation_report_fields(_file_tag: str, row: Dict[str, str],
                                         extracted_objects: List[IngestObject],
                                         _cache: IngestObjectCache):
        """Adds fields/children to the SupervisionViolationResponses as necessary. This assumes all
        SupervisionViolationResponses are of violation reports.
        """
        recommendations = set(
            sorted_list_from_str(
                row.get('parolee_placement_recommendation', '')) +
            sorted_list_from_str(
                row.get('probationer_placement_recommendation', '')))

        for obj in extracted_objects:
            if isinstance(obj, StateSupervisionViolationResponse):
                obj.response_type = StateSupervisionViolationResponseType.VIOLATION_REPORT.value

                for recommendation in recommendations:
                    if recommendation in VIOLATION_REPORT_NO_RECOMMENDATION_VALUES:
                        continue
                    recommendation_to_create = StateSupervisionViolationResponseDecisionEntry(
                        decision=recommendation)
                    create_if_not_exists(
                        recommendation_to_create, obj,
                        'state_supervision_violation_response_decisions')
Ejemplo n.º 8
0
    def _hydrate_violation_types(_file_tag: str, row: Dict[str, str],
                                 extracted_objects: List[IngestObject],
                                 _cache: IngestObjectCache):
        """Adds ViolationTypeEntries onto the already generated SupervisionViolations."""
        violation_types = sorted_list_from_str(row.get('violation_types', ''))

        if not violation_types:
            return

        for obj in extracted_objects:
            if isinstance(obj, StateSupervisionViolation):
                for violation_type in violation_types:
                    violation_type_to_create = StateSupervisionViolationTypeEntry(
                        violation_type=violation_type)
                    create_if_not_exists(violation_type_to_create, obj,
                                         'state_supervision_violation_types')
Ejemplo n.º 9
0
    def _set_violation_violent_sex_offense(
            _file_tag: str, row: Dict[str, str],
            extracted_objects: List[IngestObject], _cache: IngestObjectCache):
        """Sets the fields `is_violent` and `is_sex_offense` onto StateSupervisionViolations based on fields passed in
        through the |row|.
        """
        new_crime_types = sorted_list_from_str(row.get('new_crime_types', ''))

        if not all(ct in ALL_NEW_CRIME_TYPES for ct in new_crime_types):
            raise ValueError(f'Unexpected new crime type: {new_crime_types}')

        violent = any([ct in VIOLENT_CRIME_TYPES for ct in new_crime_types])
        sex_offense = any([ct in SEX_CRIME_TYPES for ct in new_crime_types])

        for obj in extracted_objects:
            if isinstance(obj, StateSupervisionViolation):
                obj.is_violent = str(violent)
                obj.is_sex_offense = str(sex_offense)
Ejemplo n.º 10
0
def purpose_for_incarceration_mapper(
        label: str) -> Optional[StateSpecializedPurposeForIncarceration]:
    """Parses status information from the 'offstat' table into potential purposes for incarceration. Ranking of priority
    is taken from Idaho itself (the 'statstrt' table in the us_id_raw_data dataset).
    """
    statuses = sorted_list_from_str(label, delimiter=' ')
    if 'TM' in statuses:  # Termer
        return StateSpecializedPurposeForIncarceration.GENERAL
    if 'RJ' in statuses:  # Rider
        return StateSpecializedPurposeForIncarceration.TREATMENT_IN_PRISON
    if 'NO' in statuses:  # Non Idaho commitment TODO(3518): Consider adding as specialized purpose.
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if 'PV' in statuses:  # Parole board hold
        return StateSpecializedPurposeForIncarceration.PAROLE_BOARD_HOLD
    if 'IP' in statuses:  # Institutional Probation   TODO(3518): Understand what this is.
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if 'CV' in statuses:  # Civil commitment      TODO(3518): Consider adding a specialized purpose for this
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    if 'CH' in statuses:  # Courtesy Hold         TODO(3518): Understand what this is
        return StateSpecializedPurposeForIncarceration.INTERNAL_UNKNOWN
    return None