def apply_to(self, dialog_state: DialogState) -> None:
     dialog_state.drill_instance_id = None
     dialog_state.current_prompt_state = None
     dialog_state.current_drill = None
     dialog_state.user_profile.validated = True
     dialog_state.user_profile.is_demo = self.code_validation_payload.is_demo
     dialog_state.user_profile.account_info = self.code_validation_payload.account_info
Exemple #2
0
 def fetch_dialog_state(self, phone_number: str) -> DialogState:
     if phone_number in self.repo:
         state = DialogState(**json.loads(self.repo[phone_number]))
         return state
     else:
         return DialogState(
             phone_number=phone_number,
             seq="0",
             user_profile=UserProfile(validated=False, language=self.lang),
         )
Exemple #3
0
    def _check_response(
        self, dialog_state: DialogState, base_args: Dict[str, Any]
    ) -> Optional[List[stopcovid.dialog.models.events.DialogEvent]]:
        prompt = dialog_state.get_prompt()
        if prompt is None:
            return
        events = []
        if prompt.should_advance_with_answer(
                self.content_lower, dialog_state.user_profile.language):
            events.append(
                CompletedPrompt(
                    prompt=prompt,
                    drill_instance_id=dialog_state.
                    drill_instance_id,  # type: ignore
                    response=self.content,
                    **base_args,
                ))
            should_advance = True
        else:
            should_advance = dialog_state.current_prompt_state.failures >= prompt.max_failures
            events.append(
                FailedPrompt(
                    prompt=prompt,
                    response=self.content,
                    drill_instance_id=dialog_state.
                    drill_instance_id,  # type: ignore
                    abandoned=should_advance,
                    **base_args,
                ))

        if should_advance:
            next_prompt = dialog_state.get_next_prompt()
            if next_prompt is not None:
                events.append(
                    AdvancedToNextPrompt(
                        prompt=next_prompt,
                        drill_instance_id=dialog_state.
                        drill_instance_id,  # type: ignore
                        **base_args,
                    ))
                if dialog_state.is_next_prompt_last():
                    # assume the last prompt doesn't wait for an answer
                    events.append(
                        DrillCompleted(
                            drill_instance_id=dialog_state.
                            drill_instance_id,  # type: ignore
                            **base_args,
                        ))
        return events
Exemple #4
0
 def test_user_revalidated(self):
     profile = UserProfile(validated=True, is_demo=True)
     dialog_state = DialogState(
         "123456789",
         "0",
         user_profile=profile,
         current_drill=DRILL,
         drill_instance_id=uuid.uuid4(),
         current_prompt_state=PromptState(slug=DRILL.prompts[0].slug,
                                          start_time=NOW),
     )
     event = UserValidated(
         phone_number="123456789",
         user_profile=profile,
         code_validation_payload=CodeValidationPayload(
             valid=True, is_demo=False, account_info={"foo": "bar"}),
     )
     event.apply_to(dialog_state)
     self.assertTrue(dialog_state.user_profile.validated)
     self.assertFalse(dialog_state.user_profile.is_demo)
     self.assertIsNone(dialog_state.current_drill)
     self.assertIsNone(dialog_state.current_prompt_state)
     self.assertIsNone(dialog_state.drill_instance_id)
     self.assertEqual({"foo": "bar"},
                      dialog_state.user_profile.account_info)
 def apply_to(self, dialog_state: DialogState) -> None:
     if self.abandoned:
         dialog_state.current_prompt_state = None
     else:
         assert dialog_state.current_prompt_state
         dialog_state.current_prompt_state.last_response_time = self.created_time
         dialog_state.current_prompt_state.failures += 1
Exemple #6
0
 def test_failed_and_not_abandoned(self):
     profile = UserProfile(validated=True)
     event = FailedPrompt(
         phone_number="123456789",
         user_profile=profile,
         prompt=DRILL.prompts[2],
         drill_instance_id=uuid.uuid4(),
         response="b",
         abandoned=False,
     )
     dialog_state = DialogState(
         "123456789",
         seq="0",
         user_profile=profile,
         current_drill=DRILL,
         drill_instance_id=event.drill_instance_id,
         current_prompt_state=PromptState(DRILL.prompts[2].slug,
                                          start_time=NOW),
     )
     event.apply_to(dialog_state)
     self.assertEqual(
         PromptState(
             slug=DRILL.prompts[2].slug,
             start_time=NOW,
             last_response_time=event.created_time,
             failures=1,
         ),
         dialog_state.current_prompt_state,
     )
Exemple #7
0
 def test_user_validated(self):
     profile = UserProfile(validated=False)
     dialog_state = DialogState(phone_number="123456789",
                                seq="0",
                                user_profile=profile)
     event = UserValidated(
         phone_number="123456789",
         user_profile=profile,
         code_validation_payload=CodeValidationPayload(
             valid=True,
             is_demo=False,
             account_info={
                 "employer_id": 1,
                 "unit_id": 1,
                 "employer_name": "employer_name",
                 "unit_name": "unit_name",
             },
         ),
     )
     event.apply_to(dialog_state)
     self.assertTrue(dialog_state.user_profile.validated)
     self.assertEqual(
         {
             "employer_id": 1,
             "unit_id": 1,
             "employer_name": "employer_name",
             "unit_name": "unit_name",
         },
         dialog_state.user_profile.account_info,
     )
Exemple #8
0
 def test_user_validation_failed(self):
     profile = UserProfile(validated=False)
     dialog_state = DialogState("123456789", "0", user_profile=profile)
     event = UserValidationFailed(phone_number="123456789",
                                  user_profile=profile)
     event.apply_to(dialog_state)
     self.assertFalse(dialog_state.user_profile.validated)
 def apply_to(self, dialog_state: DialogState) -> None:
     # TODO: revisit this (pretty unfortunate) updating logic. Maybe we should always just
     # overwrite the dialog engine user profile data from scadmin, rather than having two
     # sources of truth?
     account_info = dialog_state.user_profile.account_info
     if "account_info" in self.user_profile_data:
         account_info = (account_info.copy(
             update=self.user_profile_data["account_info"])
                         if account_info else AccountInfo(
                             **self.user_profile_data["account_info"]))
     dialog_state.user_profile = dialog_state.user_profile.copy(
         update=self.user_profile_data)
     dialog_state.user_profile.account_info = account_info
     if self.purge_drill_state:
         dialog_state.current_drill = None
         dialog_state.drill_instance_id = None
         dialog_state.current_prompt_state = None
Exemple #10
0
 def apply_to(self, dialog_state: DialogState) -> None:
     dialog_state.current_prompt_state = None
     if self.prompt.response_user_profile_key:
         setattr(
             dialog_state.user_profile,
             self.prompt.response_user_profile_key,
             self.response,
         )
Exemple #11
0
 def _next_drill_requested(
         self, dialog_state: DialogState,
         base_args: Dict[str, Any]) -> Optional[List[DialogEvent]]:
     prompt = dialog_state.get_prompt()
     if prompt is None:
         if self.content_lower in ["more", "mas", "más"]:
             return [NextDrillRequested(**base_args)]
     return None
Exemple #12
0
 def _advance_to_next_drill(
     self, dialog_state: DialogState, base_args: Dict[str, Any]
 ) -> Optional[List[stopcovid.dialog.models.events.DialogEvent]]:
     prompt = dialog_state.get_prompt()
     if prompt is None:
         if self.content_lower == "more":
             return [NextDrillRequested(**base_args)]
         return []
Exemple #13
0
    def test_save_and_fetch(self):
        dialog_state = self.repo.fetch_dialog_state(self.phone_number)
        self.assertEqual(self.phone_number, dialog_state.phone_number)

        event1 = CompletedPrompt(
            phone_number=self.phone_number,
            user_profile=UserProfile(validated=True),
            prompt=Prompt(
                slug="one",
                messages=[
                    PromptMessage(text="one"),
                    PromptMessage(text="two")
                ],
            ),
            response="hi",
            drill_instance_id=uuid.uuid4(),
        )
        event2 = AdvancedToNextPrompt(
            phone_number=self.phone_number,
            user_profile=UserProfile(validated=True),
            prompt=Prompt(
                slug="two",
                messages=[
                    PromptMessage(text="three"),
                    PromptMessage(text="four")
                ],
            ),
            drill_instance_id=event1.drill_instance_id,
        )
        dialog_state = DialogState(
            phone_number=self.phone_number,
            seq="0",
            user_profile=UserProfile(validated=True, language="de"),
            drill_instance_id=event1.drill_instance_id,
        )
        batch = DialogEventBatch(phone_number=self.phone_number,
                                 events=[event1, event2],
                                 seq="216")

        self.repo.persist_dialog_state(batch, dialog_state)
        dialog_state2 = self.repo.fetch_dialog_state(self.phone_number)
        self.assertEqual(dialog_state.phone_number, dialog_state2.phone_number)
        self.assertEqual(dialog_state.user_profile.validated,
                         dialog_state2.user_profile.validated)
        self.assertEqual(dialog_state.user_profile.language,
                         dialog_state2.user_profile.language)

        batch_retrieved = self.repo.fetch_dialog_event_batch(
            self.phone_number, batch.batch_id)

        event1_retrieved = batch_retrieved.events[0]
        self.assertEqual(event1.response,
                         event1_retrieved.response)  # type: ignore

        event2_retrieved = batch_retrieved.events[1]
        self.assertEqual(event2.prompt.slug,
                         event2_retrieved.prompt.slug)  # type: ignore
Exemple #14
0
    def test_next_drill_requested(self):
        profile = UserProfile(validated=True, opted_out=True)
        event = NextDrillRequested("123456789",
                                   user_profile=profile,
                                   drill_instance_id=uuid.uuid4())
        dialog_state = DialogState("123456789", seq="0", user_profile=profile)

        self.assertTrue(profile.opted_out)
        event.apply_to(dialog_state)
        self.assertFalse(profile.opted_out)
Exemple #15
0
    def test_opted_out_no_drill(self):
        profile = UserProfile(validated=True)
        event = OptedOut("123456789",
                         user_profile=profile,
                         drill_instance_id=None)
        dialog_state = DialogState("123456789", seq="0", user_profile=profile)

        self.assertFalse(profile.opted_out)
        event.apply_to(dialog_state)
        self.assertTrue(profile.opted_out)
        self.assertIsNone(dialog_state.drill_instance_id)
        self.assertIsNone(dialog_state.current_prompt_state)
        self.assertIsNone(dialog_state.current_drill)
Exemple #16
0
 def test_user_validated(self):
     profile = UserProfile(validated=False)
     dialog_state = DialogState("123456789", "0", user_profile=profile)
     event = UserValidated(
         phone_number="123456789",
         user_profile=profile,
         code_validation_payload=CodeValidationPayload(
             valid=True, is_demo=False, account_info={"foo": "bar"}),
     )
     event.apply_to(dialog_state)
     self.assertTrue(dialog_state.user_profile.validated)
     self.assertEqual({"foo": "bar"},
                      dialog_state.user_profile.account_info)
Exemple #17
0
 def setUp(self) -> None:
     logging.disable(logging.CRITICAL)
     self.phone_number = "123456789"
     self.dialog_state = DialogState(phone_number=self.phone_number,
                                     seq="0")
     self.drill = DRILL
     self.repo = MagicMock()
     self.repo.fetch_dialog_state = MagicMock(
         return_value=self.dialog_state)
     self.repo.persist_dialog_state = MagicMock()
     self.next_seq = 1
     self.now = datetime.datetime.now(datetime.timezone.utc)
     self.localization_patcher = patch("stopcovid.drills.drills.localize",
                                       return_value="translated")
     self.localization_patcher.start()
Exemple #18
0
 def test_drill_completed(self):
     profile = UserProfile(validated=False)
     event = DrillCompleted("123456789",
                            user_profile=profile,
                            drill_instance_id=uuid.uuid4())
     dialog_state = DialogState(
         "123456789",
         seq="0",
         user_profile=profile,
         current_drill=DRILL,
         drill_instance_id=event.drill_instance_id,
         current_prompt_state=PromptState(DRILL.prompts[-1].slug,
                                          start_time=NOW),
     )
     event.apply_to(dialog_state)
     self.assertIsNone(dialog_state.drill_instance_id)
     self.assertIsNone(dialog_state.current_prompt_state)
     self.assertIsNone(dialog_state.current_drill)
Exemple #19
0
 def test_completed_and_not_stored(self):
     profile = UserProfile(validated=True)
     event = CompletedPrompt(
         "123456789",
         user_profile=profile,
         prompt=DRILL.prompts[0],
         drill_instance_id=uuid.uuid4(),
         response="go",
     )
     dialog_state = DialogState(
         "123456789",
         "0",
         user_profile=profile,
         current_drill=DRILL,
         current_prompt_state=PromptState(DRILL.prompts[0].slug, NOW),
     )
     event.apply_to(dialog_state)
     self.assertEqual(profile, dialog_state.user_profile)
     self.assertIsNone(dialog_state.current_prompt_state)
Exemple #20
0
 def test_drill_started(self):
     profile = UserProfile(validated=True)
     event = DrillStarted(
         phone_number="123456789",
         user_profile=profile,
         drill=DRILL,
         first_prompt=DRILL.prompts[0],
     )
     dialog_state = DialogState(phone_number="123456789",
                                seq="0",
                                user_profile=profile)
     event.apply_to(dialog_state)
     self.assertEqual(DRILL, dialog_state.current_drill)
     self.assertEqual(
         PromptState(slug=DRILL.prompts[0].slug,
                     start_time=event.created_time),
         dialog_state.current_prompt_state,
     )
     self.assertEqual(event.drill_instance_id,
                      dialog_state.drill_instance_id)
Exemple #21
0
    def test_user_updated(self):
        phone_number = "123456789"
        profile = UserProfile(validated=True)
        event = UserUpdated(
            phone_number=phone_number,
            user_profile=profile,
            user_profile_data={"name": "Cat Stevens"},
            purge_drill_state=True,
        )
        dialog_state = DialogState(
            phone_number=phone_number,
            seq="0",
            user_profile=profile,
            drill_instance_id=uuid.uuid4(),
            current_drill=DRILL,
        )

        event.apply_to(dialog_state)
        self.assertIsNone(dialog_state.current_drill)
        self.assertIsNone(dialog_state.drill_instance_id)
Exemple #22
0
    def test_opted_out_during_drill(self):
        profile = UserProfile(validated=True)
        event = OptedOut("123456789",
                         user_profile=profile,
                         drill_instance_id=uuid.uuid4())
        dialog_state = DialogState(
            "123456789",
            seq="0",
            user_profile=profile,
            current_drill=DRILL,
            drill_instance_id=event.drill_instance_id,
            current_prompt_state=PromptState(DRILL.prompts[-1].slug,
                                             start_time=NOW),
        )

        self.assertFalse(profile.opted_out)
        event.apply_to(dialog_state)
        self.assertTrue(profile.opted_out)
        self.assertIsNone(dialog_state.drill_instance_id)
        self.assertIsNone(dialog_state.current_prompt_state)
        self.assertIsNone(dialog_state.current_drill)
Exemple #23
0
 def test_completed_and_stored(self):
     profile = UserProfile(validated=True)
     event = CompletedPrompt(
         phone_number="123456789",
         user_profile=profile,
         prompt=DRILL.prompts[1],
         drill_instance_id=uuid.uuid4(),
         response="7",
     )
     dialog_state = DialogState(
         phone_number="123456789",
         seq="0",
         user_profile=profile,
         current_drill=DRILL,
         current_prompt_state=PromptState(slug=DRILL.prompts[0].slug,
                                          start_time=NOW),
     )
     event.apply_to(dialog_state)
     self.assertEqual(UserProfile(validated=True, self_rating_1="7"),
                      dialog_state.user_profile)
     self.assertIsNone(dialog_state.current_prompt_state)
Exemple #24
0
 def test_advanced_to_next_prompt(self):
     profile = UserProfile(validated=True)
     event = AdvancedToNextPrompt(
         phone_number="123456789",
         user_profile=profile,
         prompt=DRILL.prompts[1],
         drill_instance_id=uuid.uuid4(),
     )
     dialog_state = DialogState(
         "123456789",
         seq="0",
         user_profile=profile,
         current_drill=DRILL,
         drill_instance_id=event.drill_instance_id,
         current_prompt_state=PromptState(DRILL.prompts[0].slug,
                                          start_time=NOW),
     )
     event.apply_to(dialog_state)
     self.assertEqual(
         PromptState(DRILL.prompts[1].slug, start_time=event.created_time),
         dialog_state.current_prompt_state,
     )
Exemple #25
0
 def setUp(self) -> None:
     logging.disable(logging.CRITICAL)
     self.drill_instance_id = uuid.UUID("11111111-1111-1111-1111-111111111111")
     self.phone_number = "123456789"
     self.dialog_state = DialogState(
         phone_number=self.phone_number,
         seq="0",
         drill_instance_id=self.drill_instance_id,
     )
     self.drill = Drill(
         name="test-drill",
         slug="test-drill",
         prompts=[
             Prompt(slug="ignore-response-1", messages=[PromptMessage(text="{{msg1}}")]),
             Prompt(
                 slug="store-response",
                 messages=[PromptMessage(text="{{msg1}}")],
                 response_user_profile_key="self_rating_1",
             ),
             Prompt(
                 slug="graded-response-1",
                 messages=[PromptMessage(text="{{msg1}}")],
                 correct_response="{{response1}}",
             ),
             Prompt(
                 slug="graded-response-2",
                 messages=[PromptMessage(text="{{msg1}}")],
                 correct_response="{{response1}}",
             ),
             Prompt(slug="ignore-response-2", messages=[PromptMessage(text="{{msg1}}")]),
         ],
     )
     self.repo = MagicMock()
     self.repo.fetch_dialog_state = MagicMock(return_value=self.dialog_state)
     self.repo.persist_dialog_state = MagicMock()
     self.next_seq = 1
     self.now = datetime.now(UTC)
Exemple #26
0
    def _check_response(
            self, dialog_state: DialogState,
            base_args: Dict[str, Any]) -> Optional[List[DialogEvent]]:
        prompt = dialog_state.get_prompt()
        if prompt is None:
            return None
        events: List[DialogEvent] = []
        if prompt.should_advance_with_answer(self.content_lower):
            user_profile_updates = None
            if prompt.response_user_profile_key:
                user_profile_updates = {
                    prompt.response_user_profile_key: self.content
                }
            events.append(
                CompletedPrompt(
                    prompt=prompt,
                    drill_instance_id=dialog_state.drill_instance_id,
                    response=self.content,
                    user_profile_updates=user_profile_updates,
                    **base_args,
                ))
            should_advance = True
        else:
            assert dialog_state.current_prompt_state
            should_advance = dialog_state.current_prompt_state.failures >= (
                prompt.max_failures or 1)
            events.append(
                FailedPrompt(
                    prompt=prompt,
                    response=self.content or None,
                    drill_instance_id=dialog_state.drill_instance_id,
                    abandoned=should_advance,
                    **base_args,
                ))

        if should_advance:
            assert dialog_state.current_drill
            next_prompt = dialog_state.get_next_prompt()
            if next_prompt is not None:
                events.append(
                    AdvancedToNextPrompt(
                        prompt=next_prompt,
                        drill_instance_id=dialog_state.drill_instance_id,
                        **base_args,
                    ))
                if dialog_state.is_next_prompt_last():
                    # assume the last prompt doesn't wait for an answer
                    events.append(
                        DrillCompleted(
                            drill_instance_id=dialog_state.drill_instance_id,
                            **base_args,
                        ))
            elif len(dialog_state.current_drill.prompts) == 1:
                events.append(
                    DrillCompleted(
                        drill_instance_id=dialog_state.drill_instance_id,
                        last_prompt_response=self.content or None,
                        **base_args,
                    ))

        return events
Exemple #27
0
 def apply_to(self, dialog_state: DialogState) -> None:
     dialog_state.current_prompt_state = PromptState(
         slug=self.prompt.slug, start_time=self.created_time)
Exemple #28
0
 def apply_to(self, dialog_state: DialogState) -> None:
     dialog_state.current_drill = None
     dialog_state.drill_instance_id = None
     dialog_state.current_prompt_state = None
Exemple #29
0
 def apply_to(self, dialog_state: DialogState) -> None:
     dialog_state.current_drill = None
     dialog_state.drill_instance_id = None
     dialog_state.current_prompt_state = None
     dialog_state.user_profile.opted_out = False
Exemple #30
0
 def apply_to(self, dialog_state: DialogState) -> None:
     dialog_state.current_drill = self.drill
     dialog_state.drill_instance_id = self.drill_instance_id
     dialog_state.current_prompt_state = PromptState(
         slug=self.first_prompt.slug, start_time=self.created_time)