def test_record_many_answers(self): state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertIsNone(state_answers) submitted_answer_list = [ stats_domain.SubmittedAnswer('answer a', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 10.0), stats_domain.SubmittedAnswer('answer ccc', 'TextInput', 1, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 3.0), stats_domain.SubmittedAnswer('answer bbbbb', 'TextInput', 1, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 7.5), ] stats_services.record_answers(self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', submitted_answer_list) # The order of the answers returned depends on the size of the answers. state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(state_answers.exploration_id, 'exp_id0') self.assertEqual(state_answers.exploration_version, 1) self.assertEqual(state_answers.state_name, feconf.DEFAULT_INIT_STATE_NAME) self.assertEqual(state_answers.interaction_id, 'TextInput') self.assertEqual(state_answers.get_submitted_answer_dict_list(), [{ 'answer': 'answer a', 'time_spent_in_sec': 10.0, 'answer_group_index': 0, 'rule_spec_index': 1, 'classification_categorization': 'explicit', 'session_id': 'session_id_v', 'interaction_id': 'TextInput', 'params': {} }, { 'answer': 'answer ccc', 'time_spent_in_sec': 3.0, 'answer_group_index': 1, 'rule_spec_index': 1, 'classification_categorization': 'explicit', 'session_id': 'session_id_v', 'interaction_id': 'TextInput', 'params': {} }, { 'answer': 'answer bbbbb', 'time_spent_in_sec': 7.5, 'answer_group_index': 1, 'rule_spec_index': 0, 'classification_categorization': 'explicit', 'session_id': 'session_id_v', 'interaction_id': 'TextInput', 'params': {} }])
def test_record_and_retrieve_single_answer_with_preexisting_entry(self): stats_services.record_answer( self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', stats_domain.SubmittedAnswer('first answer', 'TextInput', 0, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'a_session_id_val', 1.0)) state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(state_answers.get_submitted_answer_dict_list(), [{ 'answer': 'first answer', 'time_spent_in_sec': 1.0, 'answer_group_index': 0, 'rule_spec_index': 0, 'classification_categorization': 'explicit', 'session_id': 'a_session_id_val', 'interaction_id': 'TextInput', 'params': {} }]) stats_services.record_answer( self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', stats_domain.SubmittedAnswer('some text', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'a_session_id_val', 10.0)) state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(state_answers.exploration_id, 'exp_id0') self.assertEqual(state_answers.exploration_version, 1) self.assertEqual(state_answers.state_name, feconf.DEFAULT_INIT_STATE_NAME) self.assertEqual(state_answers.interaction_id, 'TextInput') self.assertEqual(state_answers.get_submitted_answer_dict_list(), [{ 'answer': 'first answer', 'time_spent_in_sec': 1.0, 'answer_group_index': 0, 'rule_spec_index': 0, 'classification_categorization': 'explicit', 'session_id': 'a_session_id_val', 'interaction_id': 'TextInput', 'params': {} }, { 'answer': 'some text', 'time_spent_in_sec': 10.0, 'answer_group_index': 0, 'rule_spec_index': 1, 'classification_categorization': 'explicit', 'session_id': 'a_session_id_val', 'interaction_id': 'TextInput', 'params': {} }])
def test_record_answers_exceeding_one_shard(self): # Use a smaller max answer list size so less answers are needed to # exceed a shard. with self.swap(stats_models.StateAnswersModel, '_MAX_ANSWER_LIST_BYTE_SIZE', 100000): state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertIsNone(state_answers) submitted_answer_list = [ stats_domain.SubmittedAnswer( 'answer a', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 10.0), stats_domain.SubmittedAnswer( 'answer ccc', 'TextInput', 1, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 3.0), stats_domain.SubmittedAnswer( 'answer bbbbb', 'TextInput', 1, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 7.5), ] stats_services.record_answers(self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', submitted_answer_list * 200) # Verify more than 1 shard was stored. The index shard (shard_id 0) # is not included in the shard count. model = stats_models.StateAnswersModel.get( '%s:%s:%s:%s' % (self.exploration.id, str(self.exploration.version), self.exploration.init_state_name, '0')) self.assertEqual(model.shard_count, 1) # The order of the answers returned depends on the size of the # answers. state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(state_answers.exploration_id, 'exp_id0') self.assertEqual(state_answers.exploration_version, 1) self.assertEqual(state_answers.state_name, feconf.DEFAULT_INIT_STATE_NAME) self.assertEqual(state_answers.interaction_id, 'TextInput') self.assertEqual( len(state_answers.get_submitted_answer_dict_list()), 600)
def test_can_be_converted_to_full_dict(self): submitted_answer = stats_domain.SubmittedAnswer( 'Text', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'sess', 10.5, rule_spec_str='rule spec str', answer_str='answer str') self.assertEqual( submitted_answer.to_dict(), { 'answer': 'Text', 'interaction_id': 'TextInput', 'answer_group_index': 0, 'rule_spec_index': 1, 'classification_categorization': exp_domain.EXPLICIT_CLASSIFICATION, 'params': {}, 'session_id': 'sess', 'time_spent_in_sec': 10.5, 'rule_spec_str': 'rule spec str', 'answer_str': 'answer str' })
def setUp(self): super(SubmittedAnswerValidationTests, self).setUp() self.submitted_answer = stats_domain.SubmittedAnswer( 'Text', 'TextInput', 0, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id', 0.) # The canonical object should have no validation problems. self.submitted_answer.validate()
def test_all_answers_returned_if_main_shard_has_few_answers(self): submitted_answer_list = [ stats_domain.SubmittedAnswer('answer a', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 10.0), stats_domain.SubmittedAnswer('answer bbbbb', 'TextInput', 1, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 7.5), ] # Record 2 answers. stats_services.record_answers(self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', submitted_answer_list) sample_answers = stats_services.get_sample_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(sample_answers, ['answer a', 'answer bbbbb'])
def test_that_state_answers_sharded_models_accumulate_stats(self): with self.swap(stats_models.StateAnswersModel, '_MAX_ANSWER_LIST_BYTE_SIZE', 100000): submitted_answer_list = [ stats_domain.SubmittedAnswer( 'answer a', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 10.0), stats_domain.SubmittedAnswer( 'answer ccc', 'TextInput', 1, 1, exp_domain.DEFAULT_OUTCOME_CLASSIFICATION, {}, 'session_id_v', 3.0), stats_domain.SubmittedAnswer( 'answer bbbbb', 'TextInput', 1, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 7.5), ] stats_services.record_answers(self.exp_id, self.exploration.version, 'Home', 'TextInput', submitted_answer_list * 200) job_id = stats_jobs_one_off.GenerateV1StatisticsJob.create_new() stats_jobs_one_off.GenerateV1StatisticsJob.enqueue(job_id) self.assertEqual( self.count_jobs_in_taskqueue( taskqueue_services.QUEUE_NAME_ONE_OFF_JOBS), 1) self.process_and_flush_pending_tasks() exploration_stats = stats_services.get_exploration_stats_by_id( self.exp_id, self.exploration.version) # There are an additional two answers from the setup function. self.assertEqual( exploration_stats.state_stats_mapping['Home']. total_answers_count_v1, 602) # There is an additional answer from the setup function. self.assertEqual( exploration_stats.state_stats_mapping['Home']. useful_feedback_count_v1, 401)
def _record_answer( self, answer_str, exploration, state_name, classification=exp_domain.DEFAULT_OUTCOME_CLASSIFICATION): stats_services.record_answer( exploration.id, exploration.version, state_name, 'TextInput', stats_domain.SubmittedAnswer(answer_str, 'TextInput', 0, 0, classification, {}, 'session', self.DEFAULT_TIME_SPENT))
def test_at_most_100_answers_returned_even_if_there_are_lots(self): submitted_answer_list = [ stats_domain.SubmittedAnswer('answer a', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 10.0), stats_domain.SubmittedAnswer('answer ccc', 'TextInput', 1, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 3.0), stats_domain.SubmittedAnswer('answer bbbbb', 'TextInput', 1, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 7.5), ] # Record 600 answers. stats_services.record_answers(self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', submitted_answer_list * 200) sample_answers = stats_services.get_sample_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(len(sample_answers), 100)
def setUp(self): """Complete the signup process for self.ADMIN_EMAIL.""" super(DataExtractionQueryHandlerTests, self).setUp() self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME) self.signup(self.EDITOR_EMAIL, self.EDITOR_USERNAME) self.editor_id = self.get_user_id_from_email(self.EDITOR_EMAIL) self.exploration = self.save_new_valid_exploration( self.EXP_ID, self.editor_id, end_state_name='End') stats_services.record_answer( self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', stats_domain.SubmittedAnswer('first answer', 'TextInput', 0, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'a_session_id_val', 1.0)) stats_services.record_answer( self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', stats_domain.SubmittedAnswer('second answer', 'TextInput', 0, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'a_session_id_val', 1.0))
def _handle_event(cls, exploration_id, exploration_version, state_name, interaction_id, answer_group_index, rule_spec_index, classification_categorization, session_id, time_spent_in_secs, params, normalized_answer): """Records an event when an answer triggers a rule. The answer recorded here is a Python-representation of the actual answer submitted by the user. """ # TODO(sll): Escape these args? stats_services.record_answer( exploration_id, exploration_version, state_name, interaction_id, stats_domain.SubmittedAnswer(normalized_answer, interaction_id, answer_group_index, rule_spec_index, classification_categorization, params, session_id, time_spent_in_secs))
def test_dict_may_not_include_rule_spec_str_or_answer_str(self): submitted_answer = stats_domain.SubmittedAnswer( 'Text', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'sess', 10.5) self.assertEqual( submitted_answer.to_dict(), { 'answer': 'Text', 'interaction_id': 'TextInput', 'answer_group_index': 0, 'rule_spec_index': 1, 'classification_categorization': exp_domain.EXPLICIT_CLASSIFICATION, 'params': {}, 'session_id': 'sess', 'time_spent_in_sec': 10.5 })
def test_can_be_converted_to_from_full_dict(self): submitted_answer = stats_domain.SubmittedAnswer( 'Text', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'sess', 10.5, rule_spec_str='rule spec str', answer_str='answer str') submitted_answer_dict = submitted_answer.to_dict() cloned_submitted_answer = stats_domain.SubmittedAnswer.from_dict( submitted_answer_dict) self.assertEqual(cloned_submitted_answer.to_dict(), submitted_answer_dict)
def _handle_event(cls, exploration_id, exploration_version, state_name, interaction_id, answer_group_index, rule_spec_index, classification_categorization, session_id, time_spent_in_secs, params, normalized_answer): """Records an event when an answer triggers a rule. The answer recorded here is a Python-representation of the actual answer submitted by the user. """ # TODO(sll): Escape these args? stats_services.record_answer( exploration_id, exploration_version, state_name, interaction_id, stats_domain.SubmittedAnswer(normalized_answer, interaction_id, answer_group_index, rule_spec_index, classification_categorization, params, session_id, time_spent_in_secs)) feedback_is_useful = (classification_categorization != (exp_domain.DEFAULT_OUTCOME_CLASSIFICATION)) stats_models.AnswerSubmittedEventLogEntryModel.create( exploration_id, exploration_version, state_name, session_id, time_spent_in_secs, feedback_is_useful)
def test_record_answer_without_retrieving_it_first(self): stats_services.record_answer( self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', stats_domain.SubmittedAnswer('first answer', 'TextInput', 0, 0, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'a_session_id_val', 1.0)) state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertEqual(state_answers.get_submitted_answer_dict_list(), [{ 'answer': 'first answer', 'time_spent_in_sec': 1.0, 'answer_group_index': 0, 'rule_spec_index': 0, 'classification_categorization': 'explicit', 'session_id': 'a_session_id_val', 'interaction_id': 'TextInput', 'params': {} }])
def test_only_sample_answers_in_main_shard_returned(self): # Use a smaller max answer list size so fewer answers are needed to # exceed a shard. with self.swap(stats_models.StateAnswersModel, '_MAX_ANSWER_LIST_BYTE_SIZE', 15000): state_answers = stats_services.get_state_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertIsNone(state_answers) submitted_answer_list = [ stats_domain.SubmittedAnswer( 'answer ccc', 'TextInput', 1, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'session_id_v', 3.0), ] stats_services.record_answers(self.EXP_ID, self.exploration.version, self.exploration.init_state_name, 'TextInput', submitted_answer_list * 100) # Verify more than 1 shard was stored. The index shard (shard_id 0) # is not included in the shard count. Since a total of 100 answers were # submitted, there must therefore be fewer than 100 answers in the # index shard. model = stats_models.StateAnswersModel.get( '%s:%s:%s:%s' % (self.exploration.id, str(self.exploration.version), self.exploration.init_state_name, '0')) self.assertEqual(model.shard_count, 1) # Verify that the list of sample answers returned contains fewer than # 100 answers, although a total of 100 answers were submitted. sample_answers = stats_services.get_sample_answers( self.EXP_ID, self.exploration.version, self.exploration.init_state_name) self.assertLess(len(sample_answers), 100)
def test_can_retrieve_properly_constructed_submitted_answer_dict_list( self): state_answers = stats_domain.StateAnswers( 'exp_id', 1, 'initial_state', 'TextInput', [ stats_domain.SubmittedAnswer( 'Text', 'TextInput', 0, 1, exp_domain.EXPLICIT_CLASSIFICATION, {}, 'sess', 10.5, rule_spec_str='rule spec str1', answer_str='answer str1'), stats_domain.SubmittedAnswer( 'Other text', 'TextInput', 1, 0, exp_domain.DEFAULT_OUTCOME_CLASSIFICATION, {}, 'sess', 7.5, rule_spec_str='rule spec str2', answer_str='answer str2') ]) submitted_answer_dict_list = ( state_answers.get_submitted_answer_dict_list()) self.assertEqual(submitted_answer_dict_list, [{ 'answer': 'Text', 'interaction_id': 'TextInput', 'answer_group_index': 0, 'rule_spec_index': 1, 'classification_categorization': exp_domain.EXPLICIT_CLASSIFICATION, 'params': {}, 'session_id': 'sess', 'time_spent_in_sec': 10.5, 'rule_spec_str': 'rule spec str1', 'answer_str': 'answer str1' }, { 'answer': 'Other text', 'interaction_id': 'TextInput', 'answer_group_index': 1, 'rule_spec_index': 0, 'classification_categorization': (exp_domain.DEFAULT_OUTCOME_CLASSIFICATION), 'params': {}, 'session_id': 'sess', 'time_spent_in_sec': 7.5, 'rule_spec_str': 'rule spec str2', 'answer_str': 'answer str2' }])