Esempio n. 1
0
    def test_non_admin(
        self,
        mock_get_access_permissions: MagicMock,
        mock_officer_for_hashed_email: MagicMock,
        mock_officer_for_email: MagicMock,
        mock_can_impersonate: MagicMock,
    ) -> None:
        def test_officer_for_hashed_email(_: Session,
                                          _hashed_email: str) -> ETLOfficer:
            raise OfficerDoesNotExistError

        def test_officer_for_email(_: Session, email: str) -> ETLOfficer:
            return generate_fake_officer(officer_id="test", email=email)

        def test_impersonate(_email: str, _other_email: str) -> bool:
            return False

        def test_get_access_permissions(_email: str) -> AccessPermissions:
            return AccessPermissions(
                can_access_case_triage=True,
                can_access_leadership_dashboard=False,
                impersonatable_state_codes=set(),
            )

        mock_can_impersonate.side_effect = test_impersonate
        mock_officer_for_email.side_effect = test_officer_for_email
        mock_officer_for_hashed_email.side_effect = test_officer_for_hashed_email
        mock_get_access_permissions.side_effect = test_get_access_permissions
        with self.test_app.test_request_context():
            # Test that if there's no given email, a non-admin falls through as if
            # impersonation didn't happen at all.
            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                sess["user_info"] = {
                    "email": "*****@*****.**",
                }
            g.user_context = UserContext("*****@*****.**",
                                         self.auth_store)
            response = self.test_client.get("/api/bootstrap")
            self.assertEqual(response.status_code, HTTPStatus.OK)

            # Test that if there's a given email, a non-admin also falls through as if
            # impersonation didn't happen at all and that key is no longer in the session.
            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                sess["user_info"] = {
                    "email": "*****@*****.**",
                }
            g.user_context = UserContext("*****@*****.**",
                                         self.auth_store)
            response = self.test_client.get(
                f"/api/bootstrap?{IMPERSONATED_EMAIL_KEY}={parse.quote(hash_email('*****@*****.**'))}"
            )
            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                self.assertTrue(IMPERSONATED_EMAIL_KEY not in session)
            self.assertEqual(response.status_code, HTTPStatus.OK)
            self.assertEqual(g.user_context.current_user.email_address,
                             "*****@*****.**")
Esempio n. 2
0
    def test_clients_for_officer(self) -> None:
        officer_1 = generate_fake_officer("id_1")
        officer_2 = generate_fake_officer("id_2")
        officer_3 = generate_fake_officer("id_3")
        auth_store = AuthorizationStore()
        user_context_1 = UserContext(
            email=officer_1.email_address,
            authorization_store=auth_store,
            current_user=officer_1,
        )
        user_context_2 = UserContext(
            email=officer_2.email_address,
            authorization_store=auth_store,
            current_user=officer_2,
        )
        user_context_3 = UserContext(
            email=officer_3.email_address,
            authorization_store=auth_store,
            current_user=officer_3,
        )
        client_1 = generate_fake_client("client_1",
                                        supervising_officer_id="id_1")
        client_2 = generate_fake_client("client_2",
                                        supervising_officer_id="id_1")
        client_3 = generate_fake_client("client_3",
                                        supervising_officer_id="id_2")
        with SessionFactory.using_database(self.database_key) as session:
            session.expire_on_commit = False
            session.add(officer_1)
            session.add(officer_2)
            session.add(officer_3)
            session.add(client_1)
            session.add(client_2)
            session.add(client_3)

        with SessionFactory.using_database(self.database_key,
                                           autocommit=False) as read_session:
            self.assertEqual(
                len(
                    CaseTriageQuerier.clients_for_officer(
                        read_session, user_context_1)),
                2,
            )
            self.assertEqual(
                len(
                    CaseTriageQuerier.clients_for_officer(
                        read_session, user_context_2)),
                1,
            )
            self.assertEqual(
                len(
                    CaseTriageQuerier.clients_for_officer(
                        read_session, user_context_3)),
                0,
            )
Esempio n. 3
0
 def set_receiving_ssi_or_disability_income(
     session: Session,
     user_context: UserContext,
     client: ETLClient,
     mark_receiving: bool,
 ) -> None:
     insert_statement = (insert(ClientInfo).values(
         person_external_id=user_context.person_id(client),
         state_code=user_context.client_state_code(client),
         receiving_ssi_or_disability_income=mark_receiving,
     ).on_conflict_do_update(
         constraint="unique_person",
         set_={"receiving_ssi_or_disability_income": mark_receiving},
     ))
     session.execute(insert_statement)
     session.commit()
Esempio n. 4
0
 def set_preferred_name(
     session: Session,
     user_context: UserContext,
     client: ETLClient,
     name: Optional[str],
 ) -> None:
     insert_statement = (insert(ClientInfo).values(
         person_external_id=user_context.person_id(client),
         state_code=user_context.client_state_code(client),
         preferred_name=name,
     ).on_conflict_do_update(
         constraint="unique_person",
         set_={"preferred_name": name},
     ))
     session.execute(insert_statement)
     session.commit()
Esempio n. 5
0
 def set_preferred_contact_method(
     session: Session,
     user_context: UserContext,
     client: ETLClient,
     contact_method: PreferredContactMethod,
 ) -> None:
     insert_statement = (insert(ClientInfo).values(
         person_external_id=user_context.person_id(client),
         state_code=user_context.client_state_code(client),
         preferred_contact_method=contact_method.value,
     ).on_conflict_do_update(
         constraint="unique_person",
         set_={"preferred_contact_method": contact_method.value},
     ))
     session.execute(insert_statement)
     session.commit()
Esempio n. 6
0
    def create_note(session: Session, user_context: UserContext,
                    client: ETLClient, text: str) -> OfficerNote:
        """Creates a new officer note in postgres for a given client."""
        officer_id = user_context.officer_id
        client_id = user_context.person_id(client)
        state_code = user_context.client_state_code(client)
        insert_statement = insert(OfficerNote).values(
            state_code=state_code,
            officer_external_id=officer_id,
            person_external_id=client_id,
            text=text,
        )
        result = session.execute(insert_statement)
        session.commit()

        return session.query(OfficerNote).get(result.inserted_primary_key)
Esempio n. 7
0
 def test_unimpersonating_without_impersonating(self) -> None:
     auth_store = AuthorizationStore()
     auth_store.case_triage_admin_users = ["*****@*****.**"]
     with self.test_app.test_request_context():
         with self.test_client.session_transaction(
         ) as sess:  # type: ignore
             sess["user_info"] = {"email": "*****@*****.**"}
         g.user_context = UserContext.base_context_for_email(
             "*****@*****.**", auth_store)
         # Perform initial impersonation
         response = self.test_client.delete("/api/impersonation")
         self.assertEqual(response.status_code, HTTPStatus.OK)
Esempio n. 8
0
    def update_case_for_person(
        session: Session,
        user_context: UserContext,
        client: ETLClient,
        action: CaseUpdateActionType,
        comment: Optional[str] = None,
    ) -> CaseUpdate:
        """This method updates the case_updates table with the newly provided actions.

        Because the underlying table does not have foreign key constraints, independent
        validation must be provided before calling this method.
        """
        action_ts = user_context.now()
        last_version = serialize_client_case_version(action, client).to_json()
        officer_id = user_context.officer_id
        person_external_id = user_context.person_id(client)
        insert_statement = (insert(CaseUpdate).values(
            person_external_id=person_external_id,
            officer_external_id=officer_id,
            state_code=client.state_code,
            action_type=action.value,
            action_ts=action_ts,
            last_version=last_version,
            comment=comment,
        ).on_conflict_do_update(
            constraint="unique_person_officer_action_triple",
            set_={
                "last_version": last_version,
                "action_ts": action_ts,
                "comment": comment,
            },
        ))
        session.execute(insert_statement)
        session.commit()

        return (session.query(CaseUpdate).filter(
            CaseUpdate.person_external_id == person_external_id,
            CaseUpdate.officer_external_id == officer_id,
            CaseUpdate.action_type == action.value,
        ).one())
Esempio n. 9
0
    def test_etl_client_for_officer(self) -> None:
        officer_1 = generate_fake_officer("officer_1")
        officer_2 = generate_fake_officer("officer_2")
        auth_store = AuthorizationStore()
        user_context_1 = UserContext(
            email=officer_1.email_address,
            authorization_store=auth_store,
            current_user=officer_1,
        )
        user_context_2 = UserContext(
            email=officer_2.email_address,
            authorization_store=auth_store,
            current_user=officer_2,
        )
        client_1 = generate_fake_client(
            "client_1", supervising_officer_id=officer_1.external_id)
        with SessionFactory.using_database(self.database_key) as session:
            session.expire_on_commit = False
            session.add(officer_1)
            session.add(officer_2)
            session.add(client_1)

        with SessionFactory.using_database(self.database_key,
                                           autocommit=False) as read_session:
            # Client does not exist at all
            with self.assertRaises(PersonDoesNotExistError):
                CaseTriageQuerier.etl_client_for_officer(
                    read_session, user_context_1, "nonexistent")

            # Client does not exist for the officer
            with self.assertRaises(PersonDoesNotExistError):
                CaseTriageQuerier.etl_client_for_officer(
                    read_session, user_context_2, "client_1")

            # Should not raise an error
            CaseTriageQuerier.etl_client_for_officer(read_session,
                                                     user_context_1,
                                                     "client_1")
Esempio n. 10
0
    def fetch_opportunity(
        session: Session,
        user_context: UserContext,
        client: ETLClient,
        opportunity_type: OpportunityType,
    ) -> Opportunity:
        """Fetches a given opportunity for an officer and client."""
        computed_opps = ComputedOpportunity.build_all_for_client(client)
        if opportunity_type in computed_opps:
            return computed_opps[opportunity_type]

        if user_context.should_see_demo:
            opportunities = get_fixture_opportunities()
            for opp in opportunities:
                if (opp.person_external_id == client.person_external_id
                        and opp.opportunity_type == opportunity_type.value):
                    return opp
        elif user_context.current_user and client:
            try:
                return (session.query(ETLOpportunity).filter(
                    ETLOpportunity.state_code ==
                    user_context.client_state_code(client),
                    ETLOpportunity.supervising_officer_external_id ==
                    user_context.officer_id,
                    ETLOpportunity.person_external_id ==
                    user_context.person_id(client),
                    ETLOpportunity.opportunity_type == opportunity_type.value,
                ).one())
            except sqlalchemy.orm.exc.NoResultFound as e:
                raise OpportunityDoesNotExistError(
                    f"Could not find opportunity for officer: {user_context.officer_id}, "
                    f"person: {user_context.person_id(client)}, opportunity_type: {opportunity_type}"
                ) from e

        raise OpportunityDoesNotExistError(
            f"No opportunity exists with type {opportunity_type} and "
            f"for person {user_context.person_id(client)}")
Esempio n. 11
0
    def setUp(self) -> None:
        self.database_key = SQLAlchemyDatabaseKey.for_schema(
            SchemaType.CASE_TRIAGE)
        local_postgres_helpers.use_on_disk_postgresql_database(
            self.database_key)

        self.mock_officer = generate_fake_officer("id_1")
        self.mock_client = generate_fake_client(
            "person_id_1",
            last_assessment_date=date(2021, 2, 1),
        )
        self.mock_context = UserContext(
            email=self.mock_officer.email_address,
            authorization_store=AuthorizationStore(),
            current_user=self.mock_officer,
        )
Esempio n. 12
0
    def clients_for_officer(session: Session,
                            user_context: UserContext) -> List[CasePresenter]:
        """Outputs the list of clients for a given officer in CasePresenter form."""
        if user_context.should_see_demo:
            # Organize CaseUpdates
            case_updates = (session.query(CaseUpdate).filter(
                CaseUpdate.officer_external_id ==
                user_context.officer_id).all())
            client_ids_to_case_updates = defaultdict(list)
            for case_update in case_updates:
                client_ids_to_case_updates[
                    case_update.person_external_id].append(case_update)

            clients = get_fixture_clients()
            for client in clients:
                client.person_external_id = user_context.person_id(client)

            # Organize ClientInfo structs
            client_infos = (session.query(ClientInfo).filter(
                ClientInfo.person_external_id.in_(
                    (client.person_external_id for client in clients))).all())
            client_ids_to_client_info = {}
            for client_info in client_infos:
                client_ids_to_client_info[
                    client_info.person_external_id] = client_info

            # Organize OfficerNotes
            notes = session.query(OfficerNote).filter(
                OfficerNote.person_external_id.in_(
                    (client.person_external_id for client in clients)))
            client_ids_to_notes = defaultdict(list)
            for note in notes:
                client_ids_to_notes[note.person_external_id].append(note)

            for client in clients:
                if client_info := client_ids_to_client_info.get(
                        client.person_external_id):
                    client.client_info = client_info
                client.notes = client_ids_to_notes[client.person_external_id]

            return [
                CasePresenter(
                    client,
                    client_ids_to_case_updates[client.person_external_id])
                for client in clients
            ]
Esempio n. 13
0
    def test_happy_path(
        self,
        mock_get_access_permissions: MagicMock,
        mock_officer_for_hashed_email: MagicMock,
    ) -> None:
        def test_officer_for_hashed_email(_: Session,
                                          _hashed_email: str) -> ETLOfficer:
            return generate_fake_officer(
                officer_id="test",
                email="*****@*****.**",
                state_code="US_XX",
            )

        def test_get_access_permissions(_email: str) -> AccessPermissions:
            return AccessPermissions(
                can_access_case_triage=True,
                can_access_leadership_dashboard=False,
                impersonatable_state_codes={"US_XX"},
            )

        mock_officer_for_hashed_email.side_effect = test_officer_for_hashed_email
        mock_get_access_permissions.side_effect = test_get_access_permissions
        with self.test_app.test_request_context():
            self.auth_store.case_triage_admin_users = [
                "*****@*****.**"
            ]
            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                sess["user_info"] = {
                    "email": "*****@*****.**",
                }
            g.user_context = UserContext("*****@*****.**",
                                         self.auth_store)
            response = self.test_client.get(
                f"/api/bootstrap?{IMPERSONATED_EMAIL_KEY}={parse.quote(hash_email('*****@*****.**'))}"
            )
            self.assertEqual(response.status_code, HTTPStatus.OK)

            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                self.assertEqual(
                    sess[IMPERSONATED_EMAIL_KEY],
                    hash_email("*****@*****.**"),
                )
            self.assertEqual(g.user_context.current_user.email_address,
                             "*****@*****.**")
Esempio n. 14
0
    def test_no_exception_if_both_impersonated_officer_and_self_not_found(
        self,
        mock_officer_for_hashed_email: MagicMock,
        mock_officer_for_email: MagicMock,
    ) -> None:
        def mock_officer_for_hash(_session: Session,
                                  _hashed_email: str) -> ETLOfficer:
            raise OfficerDoesNotExistError

        def mock_officer(_session: Session, _email: str) -> ETLOfficer:
            raise OfficerDoesNotExistError

        mock_officer_for_email.side_effect = mock_officer
        mock_officer_for_hashed_email.side_effect = mock_officer_for_hash
        auth_store = AuthorizationStore()
        with self.test_app.test_request_context():
            auth_store.case_triage_admin_users = [
                "*****@*****.**"
            ]
            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                sess["user_info"] = {
                    "email": "*****@*****.**"
                }
            g.user_context = UserContext.base_context_for_email(
                "*****@*****.**", auth_store)
            # Perform initial impersonation
            response = self.test_client.get(
                f"/api/bootstrap?{IMPERSONATED_EMAIL_KEY}={parse.quote(hash_email('*****@*****.**'))}"
            )
            # Assert we called officer_for_email twice, once for the non-existent impersonated user, once for non-existent self
            self.assertEqual(mock_officer_for_email.call_count, 1)
            self.assertEqual(mock_officer_for_hashed_email.call_count, 1)
            self.assertIn(
                hash_email("*****@*****.**"),
                mock_officer_for_hashed_email.call_args_list[0].args,
            )
            self.assertIn(
                "*****@*****.**",
                mock_officer_for_email.call_args_list[0].args,
            )
            self.assertIsNone(g.user_context.current_user)
            with self.test_client.session_transaction(
            ) as sess:  # type: ignore
                self.assertTrue(IMPERSONATED_EMAIL_KEY not in session)
            self.assertEqual(response.status_code, HTTPStatus.OK)
Esempio n. 15
0
    def test_opportunities_for_officer(self) -> None:
        officer = generate_fake_officer("officer_1")
        user_context = UserContext(
            current_user=officer,
            authorization_store=AuthorizationStore(),
            email=officer.email_address,
        )
        client = generate_fake_client(
            "client_1", supervising_officer_id=officer.external_id)
        etl_opp = generate_fake_etl_opportunity(
            officer_id=officer.external_id,
            person_external_id=client.person_external_id)
        etl_reminder = generate_fake_reminder(etl_opp)
        with SessionFactory.using_database(self.database_key) as session:
            session.expire_on_commit = False
            session.add(officer)
            session.add(client)
            session.add(etl_opp)
            session.add(etl_reminder)

        with SessionFactory.using_database(self.database_key) as read_session:
            # expect a non-etl opportunity that we want to mark as deferred
            reminder = generate_fake_reminder(
                opportunity=CaseTriageQuerier.opportunities_for_officer(
                    read_session, user_context)[1].opportunity)

        with SessionFactory.using_database(self.database_key) as session:
            session.add(reminder)

        with SessionFactory.using_database(self.database_key) as read_session:
            queried_opps = CaseTriageQuerier.opportunities_for_officer(
                read_session, user_context)

            self.assertEqual(len(queried_opps), 2)

            employment_opp = queried_opps[1]

            self.assertEqual(
                employment_opp.opportunity.opportunity_type,
                OpportunityType.EMPLOYMENT.value,
            )
            self.assertTrue(employment_opp.is_deferred())
Esempio n. 16
0
def on_successful_authorization(payload: Dict[str, str], token: str) -> None:
    """
    Memoize the user's info (email_address, picture, etc) into our session
    """

    # Populate the session with user information; This could have changed since the last request
    if session.get("jwt_sub", None) != payload["sub"]:
        session["jwt_sub"] = payload["sub"]
        session["user_info"] = get_userinfo(authorization_config.domain, token)
        # Also pop the impersonated email key if it exists, since the request could've been an impersonation request prior.
        if IMPERSONATED_EMAIL_KEY in session:
            session.pop(IMPERSONATED_EMAIL_KEY)

    email = session["user_info"]["email"].lower()
    g.user_context = UserContext(email, authorization_store)

    if (not g.user_context.access_permissions.can_access_case_triage and not g.
            user_context.access_permissions.can_access_leadership_dashboard):
        raise CaseTriageAuthorizationError(
            code="no_case_triage_access",
            description="You are not authorized to access this application",
        )
Esempio n. 17
0
    def defer_opportunity(
        session: Session,
        user_context: UserContext,
        client: ETLClient,
        opportunity_type: OpportunityType,
        deferral_type: OpportunityDeferralType,
        defer_until: datetime,
        reminder_requested: bool,
    ) -> OpportunityDeferral:
        """Implements base opportunity deferral and commits back to database."""
        opportunity = CaseTriageQuerier.fetch_opportunity(
            session, user_context, client, opportunity_type)
        officer_id = user_context.officer_id
        person_external_id = user_context.person_id(client)
        insert_statement = (insert(OpportunityDeferral).values(
            person_external_id=person_external_id,
            supervising_officer_external_id=officer_id,
            state_code=opportunity.state_code,
            opportunity_type=opportunity.opportunity_type,
            deferral_type=deferral_type.value,
            deferred_until=defer_until,
            reminder_was_requested=reminder_requested,
            opportunity_metadata=opportunity.opportunity_metadata,
        ).on_conflict_do_update(
            constraint="unique_person_officer_opportunity_triple",
            set_={
                "deferral_type": deferral_type.value,
                "deferred_until": defer_until,
                "reminder_was_requested": reminder_requested,
                "opportunity_metadata": opportunity.opportunity_metadata,
            },
        ))
        result = session.execute(insert_statement)
        session.commit()

        return session.query(OpportunityDeferral).get(
            result.inserted_primary_key)
Esempio n. 18
0
    def opportunities_for_officer(
            session: Session,
            user_context: UserContext) -> List[OpportunityPresenter]:
        """Fetches all opportunities for an officer."""
        if user_context.should_see_demo:
            opportunity_deferrals = (session.query(OpportunityDeferral).filter(
                OpportunityDeferral.supervising_officer_external_id ==
                user_context.officer_id).all())

            # Map from person -> opportunity type -> optional deferral
            opportunity_to_deferral: Dict[str, Dict[
                str, Optional[OpportunityDeferral]]] = defaultdict(dict)
            for deferral in opportunity_deferrals:
                opportunity_to_deferral[deferral.person_external_id][
                    deferral.opportunity_type] = deferral

            etl_opportunities = get_fixture_opportunities()
            for opportunity in etl_opportunities:
                opportunity.person_external_id = user_context.opportunity_id(
                    opportunity)

            computed_opps_by_client = [
                ComputedOpportunity.build_all_for_client(
                    c.etl_client).values()
                for c in CaseTriageQuerier.clients_for_officer(
                    session, user_context)
            ]

            opportunities: List[Opportunity] = [
                *etl_opportunities,
                # this flattens the list of lists
                *chain.from_iterable(computed_opps_by_client),
            ]

            return [
                OpportunityPresenter(
                    opportunity,
                    opportunity_to_deferral[
                        opportunity.person_external_id].get(
                            opportunity.opportunity_type),
                ) for opportunity in opportunities
            ]
        if user_context.current_user:
            etl_opportunity_info = (session.query(
                ETLOpportunity, OpportunityDeferral
            ).outerjoin(
                OpportunityDeferral,
                (ETLOpportunity.person_external_id
                 == OpportunityDeferral.person_external_id)
                & (ETLOpportunity.state_code == OpportunityDeferral.state_code)
                & (ETLOpportunity.supervising_officer_external_id
                   == OpportunityDeferral.supervising_officer_external_id)
                & (ETLOpportunity.opportunity_type
                   == OpportunityDeferral.opportunity_type),
            ).filter(
                ETLOpportunity.supervising_officer_external_id ==
                user_context.officer_id,
                ETLOpportunity.state_code == user_context.officer_state_code,
            ).all())
            # we'll fill this with opportunities computed on the fly based on client conditions
            computed_opportunity_info: List[Tuple[
                ComputedOpportunity, Optional[OpportunityDeferral]]] = []

            # one query to fetch all clients and their associated opportunity deferrals
            client_opportunities: List[Tuple[
                ETLClient, Optional[OpportunityDeferral]]] = (session.query(
                    ETLClient, OpportunityDeferral).filter(
                        ETLClient.state_code ==
                        user_context.officer_state_code,
                        ETLClient.supervising_officer_external_id ==
                        user_context.officer_id,
                    ).outerjoin(
                        OpportunityDeferral,
                        OpportunityDeferral.person_external_id ==
                        ETLClient.person_external_id,
                    ).order_by(ETLClient.person_external_id).all())
            # deferrals are not grouped in DB result because there isn't a proper relationship
            # to clients; now we group them and iterate over clients to find opportunities
            for client, client_rows in groupby(client_opportunities,
                                               lambda row: row[0]):
                deferrals = [
                    row[1] for row in client_rows if row[1] is not None
                ]

                computed_opps = ComputedOpportunity.build_all_for_client(
                    client).values()

                for opp in computed_opps:
                    deferral = next(
                        (d for d in deferrals
                         if d.opportunity_type == opp.opportunity_type),
                        None,
                    )
                    computed_opportunity_info.append((opp, deferral))

            return [
                *[
                    OpportunityPresenter(*info) for info in
                    [*etl_opportunity_info, *computed_opportunity_info]
                ],
            ]

        raise NoCaseloadException()
Esempio n. 19
0
def _get_mismatch_data_for_officer(
    officer_email: str,
) -> List[Dict[str, str]]:
    """Fetches the list of supervision mismatches on an officer's caseload for display
    in our email templates."""
    with SessionFactory.using_database(
        SQLAlchemyDatabaseKey.for_schema(SchemaType.CASE_TRIAGE), autocommit=False
    ) as session:
        try:
            officer = CaseTriageQuerier.officer_for_email(session, officer_email)
        except OfficerDoesNotExistError:
            return []

        try:
            policy_requirements = policy_requirements_for_state(
                StateCode(officer.state_code)
            )
        except Exception:
            # If for some reason we can't fetch the policy requirements, we should not show mismatches.
            return []

        user_context = UserContext(
            email=officer_email,
            authorization_store=AuthorizationStore(),  # empty store won't actually be leveraged
            current_user=officer,
        )
        opportunities = [
            opp.opportunity
            for opp in CaseTriageQuerier.opportunities_for_officer(
                session, user_context
            )
            if not opp.is_deferred()
            and opp.opportunity.opportunity_type
            == OpportunityType.OVERDUE_DOWNGRADE.value
        ]
        mismatches: List[Dict[str, str]] = []
        for opp in opportunities:
            client = CaseTriageQuerier.etl_client_for_officer(
                session, user_context, opp.person_external_id
            )

            client_name = json.loads(client.full_name)
            # TODO(#7957): We shouldn't be converting to title-case because there
            # are many names whose preferred casing is not that. Once we figure out
            # how to access the original name casing, we should use that wherever possible.
            given_names = client_name.get("given_names", "").title()
            surname = client_name.get("surname", "").title()
            full_name = " ".join([given_names, surname]).strip()
            mismatches.append(
                {
                    "name": full_name,
                    "person_external_id": client.person_external_id,
                    "last_score": opp.opportunity_metadata["assessmentScore"],
                    "last_assessment_date": opp.opportunity_metadata[
                        "latestAssessmentDate"
                    ],
                    "current_supervision_level": policy_requirements.get_supervision_level_name(
                        StateSupervisionLevel(client.supervision_level)
                    ),
                    "recommended_level": policy_requirements.get_supervision_level_name(
                        StateSupervisionLevel(
                            opp.opportunity_metadata["recommendedSupervisionLevel"]
                        )
                    ),
                }
            )

        mismatches.sort(key=lambda x: x["last_assessment_date"], reverse=True)
        if len(mismatches) > MAX_SUPERVISION_MISMATCHES_TO_SHOW:
            cutoff_date = date.today() - timedelta(
                days=IDEAL_SUPERVISION_MISMATCH_AGE_IN_DAYS
            )

            cutoff_index = len(mismatches) - MAX_SUPERVISION_MISMATCHES_TO_SHOW
            for i in range(cutoff_index):
                if (
                    dateutil.parser.parse(mismatches[i]["last_assessment_date"]).date()
                    <= cutoff_date
                ):
                    cutoff_index = i
                    break

            return mismatches[
                cutoff_index : cutoff_index + MAX_SUPERVISION_MISMATCHES_TO_SHOW
            ]

        return mismatches
Esempio n. 20
0
 def is_on_caseload(client: ETLClient, user_context: UserContext) -> bool:
     return user_context.should_see_demo or (
         user_context.current_user is not None
         and client.state_code == user_context.client_state_code(client) and
         client.supervising_officer_external_id == user_context.officer_id)