コード例 #1
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")
        client_1 = generate_fake_client("client_1", "id_1")
        client_2 = generate_fake_client("client_2", "id_1")
        client_3 = generate_fake_client("client_3", "id_2")
        session = SessionFactory.for_schema_base(CaseTriageBase)
        session.add(officer_1)
        session.add(officer_2)
        session.add(officer_3)
        session.add(client_1)
        session.add(client_2)
        session.add(client_3)
        session.commit()

        read_session = SessionFactory.for_schema_base(CaseTriageBase)
        self.assertEqual(
            len(CaseTriageQuerier.clients_for_officer(read_session,
                                                      officer_1)),
            2,
        )
        self.assertEqual(
            len(CaseTriageQuerier.clients_for_officer(read_session,
                                                      officer_2)),
            1,
        )
        self.assertEqual(
            len(CaseTriageQuerier.clients_for_officer(read_session,
                                                      officer_3)),
            0,
        )
コード例 #2
0
ファイル: querier_test.py プロジェクト: Recidiviz/pulse-data
    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,
            )
コード例 #3
0
    def test_etl_client_with_id_and_state_code(self) -> None:
        client_1 = generate_fake_client("client_1")
        session = SessionFactory.for_schema_base(CaseTriageBase)
        session.add(client_1)
        session.commit()

        read_session = SessionFactory.for_schema_base(CaseTriageBase)
        with self.assertRaises(PersonDoesNotExistError):
            CaseTriageQuerier.etl_client_with_id_and_state_code(
                read_session, "nonexistent", "US_XX")

        # Should not raise an error
        CaseTriageQuerier.etl_client_with_id_and_state_code(
            read_session, "client_1", "US_XX")
コード例 #4
0
    def test_fetch_officer_id_happy_path(self) -> None:
        officer_1 = generate_fake_officer("id_1", "*****@*****.**")
        officer_2 = generate_fake_officer("id_2", "*****@*****.**")
        session = SessionFactory.for_schema_base(CaseTriageBase)
        session.add(officer_1)
        session.add(officer_2)
        session.commit()

        read_session = SessionFactory.for_schema_base(CaseTriageBase)
        first_fetch = CaseTriageQuerier.officer_for_email(
            read_session, "*****@*****.**")
        self.assertEqual(first_fetch.external_id, "id_1")
        second_fetch = CaseTriageQuerier.officer_for_email(
            read_session, "*****@*****.**")
        self.assertEqual(second_fetch.external_id, "id_2")
コード例 #5
0
ファイル: querier_test.py プロジェクト: Recidiviz/pulse-data
    def test_fetch_officer_id_happy_path(self) -> None:
        officer_1 = generate_fake_officer("id_1", "*****@*****.**")
        officer_2 = generate_fake_officer("id_2", "*****@*****.**")
        with SessionFactory.using_database(self.database_key) as session:
            session.add(officer_1)
            session.add(officer_2)

        with SessionFactory.using_database(self.database_key,
                                           autocommit=False) as read_session:
            first_fetch = CaseTriageQuerier.officer_for_email(
                read_session, "*****@*****.**")
            self.assertEqual(first_fetch.external_id, "id_1")
            second_fetch = CaseTriageQuerier.officer_for_email(
                read_session, "*****@*****.**")
            self.assertEqual(second_fetch.external_id, "id_2")
コード例 #6
0
def _retrieve_data_for_top_opportunities(state_code: StateCode) -> List[Recipient]:
    """Fetches list of recipients from the Case Triage backend where we store information
    about which opportunities are active via the OpportunityPresenter."""
    recipients = []
    for officer_email in _top_opps_email_recipient_addresses():
        mismatches = _get_mismatch_data_for_officer(officer_email)
        if mismatches is not None:
            with SessionFactory.using_database(
                SQLAlchemyDatabaseKey.for_schema(SchemaType.CASE_TRIAGE),
                autocommit=False,
            ) as session:
                officer = CaseTriageQuerier.officer_for_email(session, officer_email)
                recipients.append(
                    Recipient.from_report_json(
                        {
                            utils.KEY_EMAIL_ADDRESS: officer_email,
                            utils.KEY_STATE_CODE: state_code.value,
                            utils.KEY_DISTRICT: None,
                            OFFICER_GIVEN_NAME: officer.given_names,
                            "mismatches": mismatches,
                        }
                    )
                )

    return recipients
コード例 #7
0
ファイル: querier_test.py プロジェクト: Recidiviz/pulse-data
    def test_fetch_officer_by_hash(self) -> None:
        officer_1 = generate_fake_officer("id_1", "*****@*****.**")
        officer_2 = generate_fake_officer("id_2", "*****@*****.**")
        with SessionFactory.using_database(self.database_key) as session:
            session.add(officer_1)
            session.add(officer_2)

        with SessionFactory.using_database(self.database_key,
                                           autocommit=False) as read_session:
            fetch_by_email_address = CaseTriageQuerier.officer_for_hashed_email(
                read_session, hash_email("*****@*****.**"))
            self.assertEqual(fetch_by_email_address.external_id, "id_1")
            fetch_by_hashed_email = CaseTriageQuerier.officer_for_hashed_email(
                read_session,
                hash_email("*****@*****.**"),
            )
            self.assertEqual(fetch_by_hashed_email.external_id, "id_2")
コード例 #8
0
ファイル: querier_test.py プロジェクト: Recidiviz/pulse-data
    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())
コード例 #9
0
ファイル: api_routes.py プロジェクト: Recidiviz/pulse-data
    def fetch_user_info() -> None:
        """This method both fetches the current user and (by virtue of the decorator) enforces authorization
        for all API routes.

        If the user is an admin (i.e. an approved Recidiviz employee), and the `impersonated_email` param is
        set, then they can make requests as if they were the impersonated user.
        """
        if not hasattr(g, "user_context"):
            # We expect the authorization decorator to have populated the user context.
            # However, in the case that it doesn't successfully happen, this is to check
            # for that.
            raise CaseTriageSecretForbiddenException()

        impersonated_email: Optional[str] = None
        if request.url_rule and request.url_rule.rule == "/api/bootstrap":
            impersonated_email = request.args.get(IMPERSONATED_EMAIL_KEY)

            if impersonated_email:
                session[IMPERSONATED_EMAIL_KEY] = impersonated_email

        if IMPERSONATED_EMAIL_KEY in session:
            try:
                impersonated_officer = CaseTriageQuerier.officer_for_hashed_email(
                    current_session, session[IMPERSONATED_EMAIL_KEY])
                if g.user_context.can_impersonate(impersonated_officer):
                    g.user_context.current_user = impersonated_officer
                else:
                    session.pop(IMPERSONATED_EMAIL_KEY)
            except OfficerDoesNotExistError:
                logging.warning("Cannot find officer for hashed email %s",
                                impersonated_email)
                session.pop(IMPERSONATED_EMAIL_KEY)

        if not g.user_context.current_user:
            try:
                g.user_context.current_user = CaseTriageQuerier.officer_for_email(
                    current_session, g.user_context.email)
            except OfficerDoesNotExistError:
                pass
コード例 #10
0
ファイル: server.py プロジェクト: jazzPouls/pulse-data
def fetch_user_info() -> None:
    """This method both fetches the current user and (by virtue of the decorator) enforces authorization
    for all API routes.

    If the user is an admin (i.e. an approved Recidiviz employee), and the `impersonated_email` param is
    set, then they can make requests as if they were the impersonated user.
    """
    try:
        if (IMPERSONATED_EMAIL_KEY in session and session["user_info"]["email"]
                in authorization_store.admin_users):
            g.current_user = CaseTriageQuerier.officer_for_email(
                current_session, session[IMPERSONATED_EMAIL_KEY])

        if not getattr(g, "current_user", None):
            g.current_user = CaseTriageQuerier.officer_for_email(
                current_session, session["user_info"]["email"])
    except NoResultFound as e:
        if not session.get(SESSION_ADMIN_KEY):
            raise CaseTriageAuthorizationError(
                code="unauthorized",
                description="You are not authorized to access this application",
            ) from e
コード例 #11
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)
コード例 #12
0
ファイル: querier_test.py プロジェクト: Recidiviz/pulse-data
    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")
コード例 #13
0
ファイル: querier_test.py プロジェクト: Recidiviz/pulse-data
 def test_nonexistent_officer(self) -> None:
     with SessionFactory.using_database(self.database_key,
                                        autocommit=False) as session:
         with self.assertRaises(OfficerDoesNotExistError):
             CaseTriageQuerier.officer_for_email(session,
                                                 "*****@*****.**")
コード例 #14
0
ファイル: api_routes.py プロジェクト: Recidiviz/pulse-data
 def _get_opportunities() -> str:
     opportunity_presenters = CaseTriageQuerier.opportunities_for_officer(
         current_session, g.user_context)
     return jsonify(
         [opportunity.to_json() for opportunity in opportunity_presenters])
コード例 #15
0
ファイル: api_routes.py プロジェクト: Recidiviz/pulse-data
def load_client(person_external_id: str) -> ETLClient:
    try:
        return CaseTriageQuerier.etl_client_for_officer(
            current_session, g.user_context, person_external_id)
    except PersonDoesNotExistError as e:
        raise CaseTriagePersonNotOnCaseloadException from e
コード例 #16
0
ファイル: api_routes.py プロジェクト: Recidiviz/pulse-data
 def _get_clients() -> str:
     clients = CaseTriageQuerier.clients_for_officer(
         current_session, g.user_context)
     return jsonify([client.to_json() for client in clients])
コード例 #17
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
コード例 #18
0
    def test_nonexistent_officer(self) -> None:
        session = SessionFactory.for_schema_base(CaseTriageBase)

        with self.assertRaises(sqlalchemy.orm.exc.NoResultFound):
            CaseTriageQuerier.officer_for_email(session,
                                                "*****@*****.**")