Skip to content

Commit

Permalink
[US_IX][Workflows] Add criteria to exclude clients serving their firs…
Browse files Browse the repository at this point in the history
…t year for a dui if risk level is low (Recidiviz/recidiviz-data#29320)

## Description of the change

According to [IDOC
policy](https://drive.google.com/file/d/1YbNrkaTwNX3tfdeDoH2f09Y7_8LOlMa7/view?usp=sharing),
clients who are serving for a DUI in their first year on supervision
should be supervised at MODERATE if their risk level is LOW. In this PR,
I add that as a criteria for the `supervision_level_downgrade`
opportunity.

Tested in `shuff_m`, 95/399 clients are no longer eligible.

## Type of change

> All pull requests must have at least one of the following labels
applied (otherwise the PR will fail):

| Label | Description |
|-----------------------------
|-----------------------------------------------------------------------------------------------------------
|
| Type: Bug | non-breaking change that fixes an issue |
| Type: Feature | non-breaking change that adds functionality |
| Type: Breaking Change | fix or feature that would cause existing
functionality to not work as expected |
| Type: Non-breaking refactor | change addresses some tech debt item or
prepares for a later change, but does not change functionality |
| Type: Configuration Change | adjusts configuration to achieve some end
related to functionality, development, performance, or security |
| Type: Dependency Upgrade | upgrades a project dependency - these
changes are not included in release notes |

## Related issues

Closes Recidiviz/recidiviz-data#29044

## Checklists

### Development

**This box MUST be checked by the submitter prior to merging**:
- [x] **Double- and triple-checked that there is no Personally
Identifiable Information (PII) being mistakenly added in this pull
request**

These boxes should be checked by the submitter prior to merging:
- [ ] Tests have been written to cover the code changed/added as part of
this pull request

### Code review

These boxes should be checked by reviewers prior to merging:

- [ ] This pull request has a descriptive title and information useful
to a reviewer
- [ ] Potential security implications or infrastructural changes have
been considered, if relevant

GitOrigin-RevId: 729b0a142e0ff3596f815f680dab5ed53c6fd38d
  • Loading branch information
samanthahuff authored and Helper Bot committed May 10, 2024
1 parent e390f09 commit 6b5d8fa
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 3 deletions.
@@ -0,0 +1,156 @@
# Recidiviz - a data platform for criminal justice reform
# Copyright (C) 2023 Recidiviz, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ============================================================================
"""Defines a criteria span view that shows spans of time during which someone has served one year
on supervision for a DUI offense and their LSI-R score is LOW. Before the year is up, policy states
that their LSI-R score should be overridden to MODERATE.
"""
from recidiviz.calculator.query.bq_utils import nonnull_end_date_clause
from recidiviz.calculator.query.sessions_query_fragments import (
create_sub_sessions_with_attributes,
)
from recidiviz.calculator.query.state.dataset_config import SESSIONS_DATASET
from recidiviz.common.constants.states import StateCode
from recidiviz.task_eligibility.task_criteria_big_query_view_builder import (
StateSpecificTaskCriteriaBigQueryViewBuilder,
)
from recidiviz.task_eligibility.utils.critical_date_query_fragments import (
critical_date_has_passed_spans_cte,
)
from recidiviz.utils.environment import GCP_PROJECT_STAGING
from recidiviz.utils.metadata import local_project_id_override

_CRITERIA_NAME = "US_IX_SERVED_AT_LEAST_ONE_YEAR_FOR_DUI_IF_LSIR_LEVEL_LOW"

_DESCRIPTION = """Defines a criteria span view that shows spans of time during which someone has served one year
on supervision for a DUI offense and their LSI-R score is LOW. Before the year is up, policy states
that their LSI-R score should be overridden to MODERATE.
"""
_QUERY_TEMPLATE = f"""
WITH supervision_starts AS (
SELECT
state_code,
person_id,
start_date AS supervision_start_date,
FROM `{{project_id}}.{{sessions_dataset}}.compartment_level_1_super_sessions_materialized`
WHERE state_code = "US_IX"
AND compartment_level_1 = "SUPERVISION"
),
sentences AS (
/* This CTE looks at imposed sentences (as opposed to sentence spans) to identify start dates of DUI sentences.
This way, we don't consider the start date of every unique group of sentences, to be the start of time served for a DUI,
only the date imposed of each DUI sentence.
*/
SELECT DISTINCT
state_code,
person_id,
date_imposed AS start_date,
completion_date AS end_date,
statute,
FROM `{{project_id}}.{{sessions_dataset}}.sentences_preprocessed_materialized` sent
WHERE state_code = "US_IX"
AND (lower(description) LIKE '%driving under the influence%' OR lower(description) LIKE '%dui%')
#TODO(#24145, #28350) revert once normalization fix is in
AND EXTRACT(YEAR FROM date_imposed) < 9999
),
supervision_starts_with_sentences AS (
SELECT
s.*,
supervision_start_date,
--take the greatest of the supervision start date and the sentence start date for the date the DUI started
--to be served. This accounts for incarceration sentences for DUIs, where we want to consider the supervision
--start date for this sentence, and not the incarceration start date
GREATEST(s.start_date, supervision_start_date) AS sentence_served_start_date
FROM sentences s
INNER JOIN supervision_starts p
ON p.state_code = s.state_code
AND p.person_id = s.person_id
AND p.supervision_start_date < {nonnull_end_date_clause('s.end_date')}
QUALIFY ROW_NUMBER() OVER (
PARTITION BY s.state_code, s.person_id, s.start_date ORDER BY supervision_start_date DESC
-- Pick the most recent supervision start date
) = 1
),
critical_date_spans AS (
SELECT
state_code,
person_id,
start_date AS start_datetime,
end_date AS end_datetime,
DATE_ADD(sentence_served_start_date, INTERVAL 1 YEAR) AS critical_date
FROM supervision_starts_with_sentences
),
{critical_date_has_passed_spans_cte()},
lsir_dui_spans AS (
/* This CTE unions sessions of time for which someone is serving a DUI with sessions of time someone has a LOW
risk score to ultimately identify spans of time where someone has not yet served one year for a DUI and their risk
score is LOW */
SELECT *
EXCEPT(critical_date, critical_date_has_passed),
critical_date_has_passed,
--risk level set to FALSE so we can identify all spans where ANY risk level is TRUE
FALSE AS risk_level_low
FROM critical_date_has_passed_spans
UNION ALL
SELECT
state_code,
person_id,
assessment_date AS start_date,
score_end_date_exclusive AS end_date,
--critical_date_has_passed is set to TRUE so we can identify all spans where ANY risk level is FALSE
TRUE AS critical_date_has_passed,
TRUE AS risk_level_low
FROM `{{project_id}}.{{sessions_dataset}}.assessment_score_sessions_materialized`
WHERE state_code = "US_IX"
AND assessment_level = 'LOW'
),
{create_sub_sessions_with_attributes('lsir_dui_spans')}
SELECT
state_code,
person_id,
start_date,
end_date,
/* False spans should be:
1. when any of the sub sessions have a TRUE value for risk_level_low (LOGICAL_OR) AND
2. when any value for critical_date_has_passed is FALSE
(Using LOGICAL_AND should return TRUE only when ALL values are TRUE, so will return FALSE if any of the values
are FALSE. Therefore NOT LOGICAL_AND() should return TRUE when any value is FALSE)
Taking the NOT of LOGICAL_OR(risk_level) and NOT LOGICAL_AND(critical_date_has_passed) then returns FALSE
spans whenever risk levels are low, and the critical date has not passed.
*/
NOT(LOGICAL_OR(risk_level_low) AND NOT LOGICAL_AND(critical_date_has_passed)) AS meets_criteria,
TO_JSON(STRUCT(
start_date AS eligible_date
)) AS reason,
FROM sub_sessions_with_attributes
GROUP BY 1,2,3,4
"""
VIEW_BUILDER: StateSpecificTaskCriteriaBigQueryViewBuilder = (
StateSpecificTaskCriteriaBigQueryViewBuilder(
criteria_name=_CRITERIA_NAME,
description=_DESCRIPTION,
state_code=StateCode.US_IX,
criteria_spans_query_template=_QUERY_TEMPLATE,
meets_criteria_default=True,
sessions_dataset=SESSIONS_DATASET,
)
)

if __name__ == "__main__":
with local_project_id_override(GCP_PROJECT_STAGING):
VIEW_BUILDER.build_and_print()
Expand Up @@ -21,15 +21,16 @@
from recidiviz.task_eligibility.candidate_populations.general import (
active_supervision_population_08_2020_present,
)
from recidiviz.task_eligibility.completion_events.general import (
supervision_level_downgrade,
)
from recidiviz.task_eligibility.criteria.general import (
supervision_level_is_not_internal_unknown,
supervision_level_is_not_interstate_compact,
supervision_level_is_not_unassigned,
)
from recidiviz.task_eligibility.completion_events.general import (
supervision_level_downgrade,
)
from recidiviz.task_eligibility.criteria.state_specific.us_ix import (
served_at_least_one_year_for_dui_if_lsir_level_low,
supervision_level_higher_than_assessment_level,
)
from recidiviz.task_eligibility.single_task_eligiblity_spans_view_builder import (
Expand All @@ -52,6 +53,7 @@
supervision_level_is_not_internal_unknown.VIEW_BUILDER,
supervision_level_is_not_interstate_compact.VIEW_BUILDER,
supervision_level_is_not_unassigned.VIEW_BUILDER,
served_at_least_one_year_for_dui_if_lsir_level_low.VIEW_BUILDER,
],
completion_event_builder=supervision_level_downgrade.VIEW_BUILDER,
)
Expand Down

0 comments on commit 6b5d8fa

Please sign in to comment.