def test_find_most_recent_applicable_assessment_LSIR(self, mock_assessment_types):
        mock_assessment_types.return_value = [StateAssessmentType.LSIR]

        assessment_1 = StateAssessment.new_with_defaults(
            state_code='US_XX',
            assessment_type=StateAssessmentType.LSIR,
            assessment_date=date(2018, 4, 28),
            assessment_score=17
        )

        assessment_2 = StateAssessment.new_with_defaults(
            state_code='US_XX',
            assessment_type=StateAssessmentType.ORAS_COMMUNITY_SUPERVISION_SCREENING,
            assessment_date=date(2018, 4, 29),
            assessment_score=17
        )

        assessments = [assessment_1, assessment_2]

        most_recent_assessment = assessment_utils.find_most_recent_applicable_assessment_of_class_for_state(
            date(2018, 4, 30),
            assessments,
            StateAssessmentClass.RISK,
            'US_XX'
        )

        self.assertEqual(most_recent_assessment, assessment_1)
    def test_find_most_recent_applicable_assessment_US_MO(self):
        state_code = 'US_MO'

        lsir_assessment = StateAssessment.new_with_defaults(
            state_code=state_code,
            assessment_type=StateAssessmentType.LSIR,
            assessment_date=date(2018, 4, 28),
            assessment_score=17
        )

        oras_assessment = StateAssessment.new_with_defaults(
            state_code=state_code,
            assessment_date=date(2018, 4, 29),
            assessment_score=17
        )

        assessments = [lsir_assessment, oras_assessment]

        applicable_assessment_types = [StateAssessmentType.ORAS_COMMUNITY_SUPERVISION,
                                       StateAssessmentType.ORAS_COMMUNITY_SUPERVISION_SCREENING]

        for assessment_type in applicable_assessment_types:
            oras_assessment.assessment_type = assessment_type

            for pipeline in assessment_utils._ASSESSMENT_TYPES_TO_INCLUDE_FOR_CLASS:
                most_recent_assessment = assessment_utils.find_most_recent_applicable_assessment_of_class_for_state(
                    date(2018, 4, 30),
                    assessments,
                    pipeline,
                    state_code
                )

                self.assertEqual(most_recent_assessment, oras_assessment)
    def test_find_most_recent_applicable_assessment_LSIR_no_matches(self, mock_assessment_types):
        mock_assessment_types.return_value = [StateAssessmentType.LSIR]
        assessment = StateAssessment.new_with_defaults(
            state_code='US_XX',
            assessment_type=StateAssessmentType.ORAS_COMMUNITY_SUPERVISION_SCREENING,
            assessment_date=date(2018, 4, 29),
            assessment_score=17
        )

        most_recent_assessment = assessment_utils.find_most_recent_applicable_assessment_of_class_for_state(
            date(2018, 4, 30),
            [assessment],
            StateAssessmentClass.RISK,
            'US_XX')

        self.assertIsNone(most_recent_assessment)
    def test_same_dates(self) -> None:
        assessment_1 = StateAssessment.new_with_defaults(
            state_code="US_XX",
            assessment_type=StateAssessmentType.LSIR,
            assessment_date=date(2018, 4, 28),
            assessment_score=17,
            external_id="2",
        )

        assessment_2 = StateAssessment.new_with_defaults(
            state_code="US_XX",
            assessment_type=StateAssessmentType.LSIR,
            assessment_date=date(2018, 4, 28),
            assessment_score=21,
            external_id="10",
        )

        for pipeline in assessment_utils._ASSESSMENT_TYPES_TO_INCLUDE_FOR_CLASS:
            most_recent_assessment = assessment_utils.find_most_recent_applicable_assessment_of_class_for_state(
                date(2018, 4, 30), [assessment_1, assessment_2], pipeline,
                "US_XX")

            self.assertEqual(most_recent_assessment, assessment_2)
    def get_case_compliance_on_date(
        self, compliance_evaluation_date: date
    ) -> Optional[SupervisionCaseCompliance]:
        """
        Calculates several different compliance values for the supervision case represented by the supervision period,
        based on state specific compliance standards. Measures compliance values for the following types of supervision
        events:
            - Assessments
            - Face-to-Face Contacts

        For each event, we calculate two types of metrics when possible.
            - The total number of events that have occurred for this person this month (until the
              |compliance_evaluation_date|).
            - Whether or not the compliance standards have been met for this event type (this is set to None if we do
              not have clear, documented guidelines applicable to this case).

        Returns:
             A SupervisionCaseCompliance object containing information regarding the ways the case is or isn't in
             compliance with state standards on the given compliance_evaluation_date.
        """
        assessment_count = self._completed_assessments_in_compliance_month(
            compliance_evaluation_date
        )
        face_to_face_count = self._face_to_face_contacts_in_compliance_month(
            compliance_evaluation_date
        )

        most_recent_assessment_date = None
        num_days_assessment_overdue = None
        face_to_face_frequency_sufficient = None

        if self.guidelines_applicable_for_case:
            most_recent_assessment = (
                find_most_recent_applicable_assessment_of_class_for_state(
                    compliance_evaluation_date,
                    self.assessments,
                    assessment_class=StateAssessmentClass.RISK,
                    state_code=self.supervision_period.state_code,
                )
            )
            if most_recent_assessment is not None:
                most_recent_assessment_date = most_recent_assessment.assessment_date

            num_days_assessment_overdue = self._num_days_assessment_overdue(
                compliance_evaluation_date, most_recent_assessment
            )

            face_to_face_frequency_sufficient = (
                self._face_to_face_contact_frequency_is_sufficient(
                    compliance_evaluation_date
                )
            )

        return SupervisionCaseCompliance(
            date_of_evaluation=compliance_evaluation_date,
            assessment_count=assessment_count,
            most_recent_assessment_date=most_recent_assessment_date,
            num_days_assessment_overdue=num_days_assessment_overdue,
            face_to_face_count=face_to_face_count,
            most_recent_face_to_face_date=self._most_recent_face_to_face_contact(
                compliance_evaluation_date
            ),
            face_to_face_frequency_sufficient=face_to_face_frequency_sufficient,
        )
示例#6
0
def us_id_case_compliance_on_date(supervision_period: StateSupervisionPeriod,
                                  case_type: StateSupervisionCaseType,
                                  start_of_supervision: date,
                                  compliance_evaluation_date: date,
                                  assessments: List[StateAssessment],
                                  supervision_contacts: List[StateSupervisionContact]) -> \
        Optional[SupervisionCaseCompliance]:
    """
    Calculates several different compliance values for the supervision case represented by the supervision period, based
    on US_ID compliance standards. Measures compliance values for the following types of supervision events:
        - Assessments
        - Face-to-Face Contacts
    We currently measure compliance for `GENERAL` and `SEX_OFFENSE` case types. Below are the expected requirements:
        - For `GENERAL` cases, there are two level systems:
            1. Deprecated system mapping (`StateSupervisionLevel`: raw string) and expected frequencies:
                - Initial compliance standards (same for all levels):
                    - LSI-R Assessment: within 45 days (if no assessment exists, or if one is past due)
                    - Face to face: within 3 days of start of supervision
                - `MINIMUM`:`LEVEL 1`
                    - Face to face contacts: none necessary
                    - LSI-R Assessment: none
                - `MEDIUM`:`LEVEL 2`
                    - Face to face contacts: 1x every 180 days
                    - LSI-R Assessment: 1x every 365 days
                - `MEDIUM`:`LEVEL 2`
                    - Face to face contacts: 1x every 180 days
                    - LSI-R Assessment: 1x every 365 days
                - `HIGH`: `LEVEL 3`
                    - Face to face contacts: 1x every 30 days
                    - LSI-R Assessment: 1x every 365 days
                - `MAXIMUM`: `LEVEL 4`
                    - Face to face contacts: 2x every 30 days
                    - LSI-R Assessment: 1x every 365 days
            2. New system mapping (`StateSupervisionLevel`: raw string) and expected frequencies:
                - Initial compliance standards (same for all levels):
                    - LSI-R Assessment: within 45 days (if no assessment exists, or if one is past due)
                    - Face to face: within 3 days of start of supervision
                 - `MINIMUM`:`MINIMUM`
                    - Face to face contacts: 1x every 180 days
                    - LSI-R Assessment: none
                - `MEDIUM`:`MODERATE`
                    - Face to face contacts: 2x every 90 days
                    - LSI-R Assessment: 1x every 365 days
                - `HIGH`: `HIGH`
                    - Face to face contacts: 2x every 30 days
                    - LSI-R Assessment: 1x every 365 days
        - For `SEX_OFFENSE` cases, there is one level system with the following mapping and expected frequencies:
            - Initial compliance standards (same for all levels):
                - LSI-R Assessment: within 45 days if on probation, or within 90 days if on parole
                - Face to face: within 3 days of start of supervision
            - `MINIMUM`:`SO LEVEL 1`/`SO LOW`
                - Face to face contacts: 1x every 90 days
                - LSI-R Assessment: every 365 days if LSI-R > 16
            - `MEDIUM`:`SO LEVEL 2`/`SO MODERATE`
                - Face to face contacts: 1x every 30 days
                - LSI-R Assessment: every 365 days if LSI-R > 16
            - `HIGH`: `SO LEVEL 3`/`SO HIGH`
                - Face to face contacts: 2x every 30 days
                - LSI-R Assessment: every 365 days if LSI-R > 16
    For each event, we calculate two types of metrics when possible.
        - The total number of events that have occurred for this person this month (until the
          |compliance_evaluation_date|).
        - Whether or not the compliance standards have been met for this event type (this is set to None if we do not
          have clear, documented guidelines applicable to this case).

    Args:
        supervision_period: The supervision_period representing the supervision case
        case_type: The "most severe" case type for the given supervision period
        start_of_supervision: The date the person started serving this supervision
        compliance_evaluation_date: The date that the compliance of the given case is being evaluated
        assessments: The risk assessments completed on this person
        supervision_contacts: The instances of contact between supervision officers and the person on supervision

    Returns:
         A SupervisionCaseCompliance object containing information regarding the ways the case is or isn't in compliance
         with state standards on the given compliance_evaluation_date.
    """
    assessment_count = _assessments_in_compliance_month(compliance_evaluation_date, assessments)
    face_to_face_count = _face_to_face_contacts_in_compliance_month(compliance_evaluation_date, supervision_contacts)

    assessment_is_up_to_date = None
    face_to_face_frequency_sufficient = None

    if _guidelines_applicable_for_case(supervision_period, case_type):
        most_recent_assessment = find_most_recent_applicable_assessment_of_class_for_state(
            compliance_evaluation_date,
            assessments,
            assessment_class=StateAssessmentClass.RISK,
            state_code=supervision_period.state_code
        )

        assessment_is_up_to_date = _assessment_is_up_to_date(case_type,
                                                             supervision_period,
                                                             start_of_supervision,
                                                             compliance_evaluation_date,
                                                             most_recent_assessment)

        face_to_face_frequency_sufficient = _face_to_face_contact_frequency_is_sufficient(case_type,
                                                                                          supervision_period,
                                                                                          start_of_supervision,
                                                                                          compliance_evaluation_date,
                                                                                          supervision_contacts)

    return SupervisionCaseCompliance(
        date_of_evaluation=compliance_evaluation_date,
        assessment_count=assessment_count,
        assessment_up_to_date=assessment_is_up_to_date,
        face_to_face_count=face_to_face_count,
        face_to_face_frequency_sufficient=face_to_face_frequency_sufficient
    )
class StateSupervisionCaseComplianceManager:
    """Interface for state-specific supervision case compliance calculations."""
    def __init__(
        self,
        person: StatePerson,
        supervision_period: StateSupervisionPeriod,
        case_type: StateSupervisionCaseType,
        start_of_supervision: date,
        assessments: List[StateAssessment],
        supervision_contacts: List[StateSupervisionContact],
    ):
        self.person = person
        self.supervision_period = supervision_period
        self.case_type = case_type
        self.start_of_supervision = start_of_supervision
        self.assessments = assessments
        self.supervision_contacts = supervision_contacts

    def get_case_compliance_on_date(
        self, compliance_evaluation_date: date
    ) -> Optional[SupervisionCaseCompliance]:
        """
        Calculates several different compliance values for the supervision case represented by the supervision period,
        based on state specific compliance standards. Measures compliance values for the following types of supervision
        events:
            - Assessments
            - Face-to-Face Contacts

        For each event, we calculate two types of metrics when possible.
            - The total number of events that have occurred for this person this month (until the
              |compliance_evaluation_date|).
            - Whether or not the compliance standards have been met for this event type (this is set to None if we do
              not have clear, documented guidelines applicable to this case).

        Returns:
             A SupervisionCaseCompliance object containing information regarding the ways the case is or isn't in
             compliance with state standards on the given compliance_evaluation_date.
        """
        assessment_count = self._completed_assessments_on_date(
            compliance_evaluation_date)
        face_to_face_count = self._face_to_face_contacts_on_date(
            compliance_evaluation_date)
        home_visit_count = self._home_visits_on_date(
            compliance_evaluation_date)

        most_recent_assessment = (
            find_most_recent_applicable_assessment_of_class_for_state(
                compliance_evaluation_date,
                self.assessments,
                assessment_class=StateAssessmentClass.RISK,
                state_code=self.supervision_period.state_code,
            ))
        most_recent_assessment_date = (most_recent_assessment.assessment_date
                                       if most_recent_assessment is not None
                                       else None)

        next_recommended_assessment_date = None
        face_to_face_frequency_sufficient = None
        home_visit_frequency_sufficient = None

        if self._guidelines_applicable_for_case(compliance_evaluation_date):
            next_recommended_assessment_date = self._next_recommended_assessment_date(
                most_recent_assessment)

            face_to_face_frequency_sufficient = (
                self._face_to_face_contact_frequency_is_sufficient(
                    compliance_evaluation_date))

            home_visit_frequency_sufficient = self._home_visit_frequency_is_sufficient(
                compliance_evaluation_date)

        return SupervisionCaseCompliance(
            date_of_evaluation=compliance_evaluation_date,
            assessment_count=assessment_count,
            most_recent_assessment_date=most_recent_assessment_date,
            next_recommended_assessment_date=next_recommended_assessment_date,
            face_to_face_count=face_to_face_count,
            most_recent_face_to_face_date=self.
            _most_recent_face_to_face_contact(compliance_evaluation_date),
            face_to_face_frequency_sufficient=face_to_face_frequency_sufficient,
            most_recent_home_visit_date=self._most_recent_home_visit_contact(
                compliance_evaluation_date),
            home_visit_count=home_visit_count,
            home_visit_frequency_sufficient=home_visit_frequency_sufficient,
            recommended_supervision_downgrade_level=self.
            _get_recommended_supervision_downgrade_level(
                compliance_evaluation_date),
        )

    def _next_recommended_assessment_date(
            self, most_recent_assessment: Optional[StateAssessment]
    ) -> Optional[date]:
        """Outputs what the next assessment date is for the person under supervision."""
        if (not most_recent_assessment
                or not most_recent_assessment.assessment_date
                or not most_recent_assessment.assessment_score):
            # No assessment has been filed, so the next recommended assessment date
            # is the initial assessment date.
            return self.start_of_supervision + timedelta(
                days=self._get_initial_assessment_number_of_days())

        return self._next_recommended_reassessment(
            most_recent_assessment.assessment_date,
            most_recent_assessment.assessment_score,
        )

    def _most_recent_face_to_face_contact(
            self, compliance_evaluation_date: date) -> Optional[date]:
        """Gets the most recent face to face contact date. If there is not any, it returns None."""
        applicable_contacts = self._get_applicable_face_to_face_contacts_between_dates(
            self.start_of_supervision, compliance_evaluation_date)
        contact_dates = [
            contact.contact_date for contact in applicable_contacts
            if contact.contact_date is not None
        ]
        if not contact_dates:
            return None

        return max(contact_dates)

    def _face_to_face_contacts_on_date(
            self, compliance_evaluation_date: date) -> int:
        """Returns the number of face-to-face contacts that were completed on compliance_evaluation_date."""
        applicable_contacts = self._get_applicable_face_to_face_contacts_between_dates(
            lower_bound_inclusive=compliance_evaluation_date,
            upper_bound_inclusive=compliance_evaluation_date,
        )

        return len(applicable_contacts)

    def _get_applicable_face_to_face_contacts_between_dates(
            self, lower_bound_inclusive: date,
            upper_bound_inclusive: date) -> List[StateSupervisionContact]:
        """Returns the completed contacts that can be counted as face-to-face contacts and occurred between the
        lower_bound_inclusive date and the upper_bound_inclusive date.
        """
        return [
            contact for contact in self.supervision_contacts
            # These are the types of contacts that can satisfy the face-to-face contact requirement
            if contact.contact_type in (
                StateSupervisionContactType.FACE_TO_FACE,
                StateSupervisionContactType.TELEPHONE,
                StateSupervisionContactType.VIRTUAL,
            )
            # Contact must be marked as completed
            and contact.status == StateSupervisionContactStatus.COMPLETED and
            contact.contact_date is not None and lower_bound_inclusive <=
            contact.contact_date <= upper_bound_inclusive
        ]

    def _completed_assessments_on_date(
            self, compliance_evaluation_date: date) -> int:
        """Returns the number of assessments that were completed on the compliance evaluation date."""
        assessments_on_compliance_date = [
            assessment for assessment in self.assessments
            if assessment.assessment_date is not None
            and assessment.assessment_score is not None
            and assessment.assessment_date == compliance_evaluation_date
        ]

        return len(assessments_on_compliance_date)

    def _face_to_face_contact_frequency_is_in_compliance(
            self, compliance_evaluation_date: date, required_contacts: int,
            period_days: int) -> Optional[bool]:
        """Returns whether the face-to-face contacts within the period are compliant with respect to the state
        standards for the level of supervision of the case."""
        # Get applicable contacts that occurred between the start of supervision and the
        # compliance_evaluation_date (inclusive)
        applicable_contacts = self._get_applicable_face_to_face_contacts_between_dates(
            self.start_of_supervision, compliance_evaluation_date)

        if not applicable_contacts:
            # This person has been on supervision for longer than the allowed number of days without an initial contact.
            # The face-to-face contact standard is not in compliance.
            return False

        days_since_start = (compliance_evaluation_date -
                            self.start_of_supervision).days

        if days_since_start < period_days:
            # If they've had a contact since the start of their supervision, and they have been on supervision for less
            # than the number of days in which they would need another contact, then the case is in compliance
            return True

        contacts_within_period = [
            contact for contact in applicable_contacts
            if contact.contact_date is not None and
            (compliance_evaluation_date -
             contact.contact_date).days < period_days
        ]

        return len(contacts_within_period) >= required_contacts

    def _home_visits_on_date(self, compliance_evaluation_date: date) -> int:
        """Returns the number of face-to-face contacts that were completed on compliance_evaluation_date."""
        applicable_visits = self._get_applicable_home_visits_between_dates(
            lower_bound_inclusive=compliance_evaluation_date,
            upper_bound_inclusive=compliance_evaluation_date,
        )
        return len(applicable_visits)

    def _get_applicable_home_visits_between_dates(
            self, lower_bound_inclusive: date,
            upper_bound_inclusive: date) -> List[StateSupervisionContact]:
        """Returns the completed contacts that can be counted as home visits and occurred between the
        lower_bound_inclusive date and the upper_bound_inclusive date.
        """
        return [
            contact for contact in self.supervision_contacts
            # These are the types of contacts that can satisfy the home visit requirement
            if contact.location == StateSupervisionContactLocation.RESIDENCE
            # Contact must be marked as completed
            and contact.status == StateSupervisionContactStatus.COMPLETED and
            contact.contact_date is not None and lower_bound_inclusive <=
            contact.contact_date <= upper_bound_inclusive
        ]

    def _most_recent_home_visit_contact(
            self, compliance_evaluation_date: date) -> Optional[date]:
        """Gets the most recent home visit contact date. If there is not any, it returns None."""
        applicable_contacts = self._get_applicable_home_visits_between_dates(
            self.start_of_supervision, compliance_evaluation_date)
        contact_dates = [
            contact.contact_date for contact in applicable_contacts
            if contact.contact_date is not None
        ]
        if not contact_dates:
            return None

        return max(contact_dates)

    def _get_recommended_supervision_downgrade_level(
            self, evaluation_date: date) -> Optional[StateSupervisionLevel]:
        """Determines whether the person under evaluation was eligible for a downgrade
        on this date."""
        policy = self._get_supervision_level_policy(evaluation_date)
        if not policy:
            return None

        if (not (current_level := self.supervision_period.supervision_level)
                or not current_level.is_comparable()):
            return None

        most_recent_assessment = (
            find_most_recent_applicable_assessment_of_class_for_state(
                evaluation_date,
                self.assessments,
                assessment_class=StateAssessmentClass.RISK,
                state_code=self.supervision_period.state_code,
            ))
        if (most_recent_assessment is None or
            (last_score := most_recent_assessment.assessment_score) is None):
            return None