From 9df48895173cf5a84a3ed45f406c4f102ddf2973 Mon Sep 17 00:00:00 2001 From: Cristiano Ferreira Date: Mon, 2 Sep 2024 15:28:41 +0100 Subject: [PATCH] New custom level tests. --- app.py | 96 ++++++++++++- helper/constants.py | 16 +++ helper/exercises.py | 331 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 430 insertions(+), 13 deletions(-) diff --git a/app.py b/app.py index a28fd30..fcf2c2d 100644 --- a/app.py +++ b/app.py @@ -1248,17 +1248,17 @@ def get_level_utas(): all_mc_questions = [] # PART 1 - mc_exercises1 = gen_multiple_choice_blank_space_utas(15, 1, all_mc_questions) + mc_exercises1 = gen_multiple_choice_blank_space_utas(15, 1, None, all_mc_questions) print(json.dumps(mc_exercises1, indent=4)) all_mc_questions.append(mc_exercises1) # PART 2 - mc_exercises2 = gen_multiple_choice_blank_space_utas(15, 16, all_mc_questions) + mc_exercises2 = gen_multiple_choice_blank_space_utas(15, 16, None, all_mc_questions) print(json.dumps(mc_exercises2, indent=4)) all_mc_questions.append(mc_exercises2) # PART 3 - mc_exercises3 = gen_multiple_choice_blank_space_utas(15, 31, all_mc_questions) + mc_exercises3 = gen_multiple_choice_blank_space_utas(15, 31, None, all_mc_questions) print(json.dumps(mc_exercises3, indent=4)) all_mc_questions.append(mc_exercises3) @@ -1323,8 +1323,15 @@ class CustomLevelExerciseTypes(Enum): LISTENING_2 = "listening_2" LISTENING_3 = "listening_3" LISTENING_4 = "listening_4" + TRANSFORMATION = "transformation" + GAP_FILLING = "gap_filling" + MATCHING = "matching" + CLOZE = "cloze" + TRUE_FALSE = "true_false" + ERROR_CORRECTION = "error_correction" +# https://www.teachingenglish.org.uk/professional-development/teachers/assessing-learning/articles/test-question-types @app.route('/custom_level', methods=['GET']) @jwt_required() def get_custom_level(): @@ -1340,8 +1347,8 @@ def get_custom_level(): exercise_difficulty = request.args.get('exercise_' + str(i) + '_difficulty', random.choice(['easy', 'medium', 'hard'])) exercise_qty = int(request.args.get('exercise_' + str(i) + '_qty', -1)) - exercise_topic = request.args.get('exercise_' + str(i) + '_topic', random.choice(topics)) - exercise_topic_2 = request.args.get('exercise_' + str(i) + '_topic_2', random.choice(topics)) + exercise_topic = request.args.get('exercise_' + str(i) + '_topic', None) + exercise_topic_2 = request.args.get('exercise_' + str(i) + '_topic_2', None) exercise_text_size = int(request.args.get('exercise_' + str(i) + '_text_size', 700)) exercise_sa_qty = int(request.args.get('exercise_' + str(i) + '_sa_qty', -1)) exercise_mc_qty = int(request.args.get('exercise_' + str(i) + '_mc_qty', -1)) @@ -1383,7 +1390,7 @@ def get_custom_level(): qty = exercise_qty response["exercises"]["exercise_" + str(i)]["questions"].extend( - gen_multiple_choice_blank_space_utas(qty, exercise_id, + gen_multiple_choice_blank_space_utas(qty, exercise_id, exercise_topic, response["exercises"]["exercise_" + str(i)]["questions"])[ "questions"]) exercise_id = exercise_id + qty @@ -1412,28 +1419,42 @@ def get_custom_level(): response["exercises"]["exercise_" + str(i)]["type"] = "blankSpaceText" exercise_id = exercise_id + exercise_qty elif exercise_type == CustomLevelExerciseTypes.READING_PASSAGE_UTAS.value: + if exercise_topic is None: + exercise_topic = random.choice(topics) response["exercises"]["exercise_" + str(i)] = gen_reading_passage_utas(exercise_id, exercise_sa_qty, exercise_mc_qty, exercise_topic) response["exercises"]["exercise_" + str(i)]["type"] = "readingExercises" exercise_id = exercise_id + exercise_qty elif exercise_type == CustomLevelExerciseTypes.WRITING_LETTER.value: + if exercise_topic is None: + exercise_topic = random.choice(topics) response["exercises"]["exercise_" + str(i)] = gen_writing_task_1(exercise_topic, exercise_difficulty) response["exercises"]["exercise_" + str(i)]["type"] = "writing" exercise_id = exercise_id + 1 elif exercise_type == CustomLevelExerciseTypes.WRITING_2.value: + if exercise_topic is None: + exercise_topic = random.choice(topics) response["exercises"]["exercise_" + str(i)] = gen_writing_task_2(exercise_topic, exercise_difficulty) response["exercises"]["exercise_" + str(i)]["type"] = "writing" exercise_id = exercise_id + 1 elif exercise_type == CustomLevelExerciseTypes.SPEAKING_1.value: + if exercise_topic is None: + exercise_topic = random.choice(topics) + if exercise_topic_2 is None: + exercise_topic_2 = random.choice(topics) response["exercises"]["exercise_" + str(i)] = ( gen_speaking_part_1(exercise_topic, exercise_topic_2, exercise_difficulty)) response["exercises"]["exercise_" + str(i)]["type"] = "interactiveSpeaking" exercise_id = exercise_id + 1 elif exercise_type == CustomLevelExerciseTypes.SPEAKING_2.value: + if exercise_topic is None: + exercise_topic = random.choice(topics) response["exercises"]["exercise_" + str(i)] = gen_speaking_part_2(exercise_topic, exercise_difficulty) response["exercises"]["exercise_" + str(i)]["type"] = "speaking" exercise_id = exercise_id + 1 elif exercise_type == CustomLevelExerciseTypes.SPEAKING_3.value: + if exercise_topic is None: + exercise_topic = random.choice(topics) response["exercises"]["exercise_" + str(i)] = gen_speaking_part_3(exercise_topic, exercise_difficulty) response["exercises"]["exercise_" + str(i)]["type"] = "interactiveSpeaking" exercise_id = exercise_id + 1 @@ -1458,6 +1479,9 @@ def get_custom_level(): exercise_qty_q.put(exercise_paragraphmatch_qty) total_qty = total_qty + exercise_paragraphmatch_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_reading_passage_1(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) response["exercises"]["exercise_" + str(i)]["type"] = "reading" @@ -1484,6 +1508,9 @@ def get_custom_level(): exercise_qty_q.put(exercise_paragraphmatch_qty) total_qty = total_qty + exercise_paragraphmatch_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_reading_passage_2(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) response["exercises"]["exercise_" + str(i)]["type"] = "reading" @@ -1514,6 +1541,9 @@ def get_custom_level(): exercise_qty_q.put(exercise_ideamatch_qty) total_qty = total_qty + exercise_ideamatch_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_reading_passage_3(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) response["exercises"]["exercise_" + str(i)]["type"] = "reading" @@ -1540,6 +1570,9 @@ def get_custom_level(): exercise_qty_q.put(exercise_writeblanksform_qty) total_qty = total_qty + exercise_writeblanksform_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_listening_section_1(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) @@ -1559,6 +1592,9 @@ def get_custom_level(): exercise_qty_q.put(exercise_writeblanksquestions_qty) total_qty = total_qty + exercise_writeblanksquestions_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_listening_section_2(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) @@ -1578,6 +1614,9 @@ def get_custom_level(): exercise_qty_q.put(exercise_writeblanksquestions_qty) total_qty = total_qty + exercise_writeblanksquestions_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_listening_section_3(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) @@ -1605,12 +1644,57 @@ def get_custom_level(): exercise_qty_q.put(exercise_writeblanksform_qty) total_qty = total_qty + exercise_writeblanksform_qty + if exercise_topic is None: + exercise_topic = random.choice(topics) + response["exercises"]["exercise_" + str(i)] = gen_listening_section_4(exercise_topic, exercise_difficulty, exercises, exercise_qty_q, exercise_id) response["exercises"]["exercise_" + str(i)]["type"] = "listening" exercise_id = exercise_id + total_qty + elif exercise_type == CustomLevelExerciseTypes.TRANSFORMATION.value: + response["exercises"]["exercise_" + str(i)] = gen_transformation_exercise(exercise_qty, + exercise_id, + exercise_difficulty, + exercise_topic) + response["exercises"]["exercise_" + str(i)]["type"] = "transformation" + exercise_id = exercise_id + exercise_qty + elif exercise_type == CustomLevelExerciseTypes.GAP_FILLING.value: + response["exercises"]["exercise_" + str(i)] = gen_gap_filling_exercise(exercise_qty, + exercise_id, + exercise_difficulty, + exercise_topic) + response["exercises"]["exercise_" + str(i)]["type"] = "gapFilling" + exercise_id = exercise_id + exercise_qty + elif exercise_type == CustomLevelExerciseTypes.MATCHING.value: + response["exercises"]["exercise_" + str(i)] = gen_grammar_matching_exercise(exercise_qty, + exercise_id, + exercise_difficulty, + exercise_topic) + response["exercises"]["exercise_" + str(i)]["type"] = "matchSentences" + exercise_id = exercise_id + exercise_qty + elif exercise_type == CustomLevelExerciseTypes.CLOZE.value: + response["exercises"]["exercise_" + str(i)] = gen_cloze_exercise(exercise_qty, + exercise_id, + exercise_difficulty, + exercise_topic) + response["exercises"]["exercise_" + str(i)]["type"] = "writeBlanks" + exercise_id = exercise_id + exercise_qty + elif exercise_type == CustomLevelExerciseTypes.TRUE_FALSE.value: + response["exercises"]["exercise_" + str(i)] = gen_true_false_exercise(exercise_qty, + exercise_id, + exercise_difficulty, + exercise_topic) + response["exercises"]["exercise_" + str(i)]["type"] = "trueFalse" + exercise_id = exercise_id + exercise_qty + elif exercise_type == CustomLevelExerciseTypes.ERROR_CORRECTION.value: + response["exercises"]["exercise_" + str(i)] = gen_error_correction_exercise(exercise_qty, + exercise_id, + exercise_difficulty, + exercise_topic) + response["exercises"]["exercise_" + str(i)]["type"] = "questionAnswer" + exercise_id = exercise_id + exercise_qty return response diff --git a/helper/constants.py b/helper/constants.py index fdd45e4..af5c174 100644 --- a/helper/constants.py +++ b/helper/constants.py @@ -659,3 +659,19 @@ academic_subjects = [ "Ecology", "International Business" ] + +grammar_types = [ + "parts of speech", + "parts of speech - Nouns", + "parts of speech - Pronouns", + "parts of speech - Verbs", + "parts of speech - Adverbs", + "parts of speech - Adjectives", + "parts of speech - Conjunctions", + "parts of speech - Prepositions", + "parts of speech - Interjections", + "sentence structure", + "types of sentences", + "tenses", + "active voice and passive voice" +] diff --git a/helper/exercises.py b/helper/exercises.py index b3f22c5..62cc96d 100644 --- a/helper/exercises.py +++ b/helper/exercises.py @@ -1443,18 +1443,29 @@ def parse_conversation(conversation_data): return "\n".join(readable_text) -def gen_multiple_choice_blank_space_utas(quantity: int, start_id: int, all_exams=None): +def gen_multiple_choice_blank_space_utas(quantity: int, start_id: int, topic=None, all_exams=None): gen_multiple_choice_for_text = "Generate " + str( - quantity) + " multiple choice blank space questions of 4 options for an english level exam, some easy questions, some intermediate " \ - "questions and some advanced questions. Ensure that the questions cover a range of topics such as " \ - "verb tense, subject-verb agreement, pronoun usage, sentence structure, and punctuation. Make sure " \ - "every question only has 1 correct answer." + quantity) + (" multiple choice blank space questions of 4 options for an english level exam, some easy " + "questions, some intermediate questions and some advanced questions. Make sure every question " + "only has 1 correct answer.") + + if topic is None: + gen_multiple_choice_for_text = gen_multiple_choice_for_text + ("Ensure that the questions cover a range of " + "topics such as verb tense, subject-verb " + "agreement, pronoun usage, sentence structure, " + "and punctuation.") + else: + gen_multiple_choice_for_text = gen_multiple_choice_for_text + ("Ensure that the questions are fill the blanks " + "and cover the grammar " + "topic of '" + topic + "' and the prompts " + "are varied.") messages = [ { "role": "system", "content": ( - 'You are a helpful assistant designed to output JSON on this format: {"questions": [{"id": "9", "options": ' + 'You are a helpful assistant designed to output JSON on this format: ' + '{"questions": [{"id": "9", "options": ' '[{"id": "A", "text": ' '"And"}, {"id": "B", "text": "Cat"}, {"id": "C", "text": ' '"Happy"}, {"id": "D", "text": "Jump"}], ' @@ -1473,7 +1484,7 @@ def gen_multiple_choice_blank_space_utas(quantity: int, start_id: int, all_exams GEN_QUESTION_TEMPERATURE) if len(question["questions"]) != quantity: - return gen_multiple_choice_blank_space_utas(quantity, start_id) + return gen_multiple_choice_blank_space_utas(quantity, start_id, topic, all_exams) else: if all_exams is not None: seen_keys = set() @@ -2089,3 +2100,309 @@ def gen_listening_section_4(topic, difficulty, req_exercises, number_of_exercise "text": monologue, "difficulty": difficulty } + + +def gen_transformation_exercise(quantity, start_id, difficulty, topic=None): + json_format = { + "exercises": [ + { + "id": 1, + "first": "first sentence", + "second": "second sentence", + "solutions": ["first_missing_word", "second_missing_word"] + } + ] + } + + messages = [ + { + "role": "system", + "content": 'You are a helpful assistant designed to output JSON on this format: ' + str(json_format) + }, + { + "role": "user", + "content": ("Create " + str(quantity) + " transformation exercises of " + difficulty + " where the student " + "has to complete the " + "second sentences' 2 blank spaces so that it has the same meaning " + "as the first. Each blank space must correspond to a single word.") + }, + { + "role": "user", + "content": 'The id starts at ' + str(start_id) + '.' + } + ] + + if topic is not None: + messages.append({ + "role": "user", + "content": 'Focus the exercises on the grammar subject of ' + topic + '.' + }) + + token_count = count_total_tokens(messages) + response = make_openai_call(GPT_4_O, messages, token_count, GEN_FIELDS, GEN_QUESTION_TEMPERATURE) + response["prompt"] = "Complete the second sentence so that it has the same meaning as the first." + response["difficulty"] = difficulty + response["topic"] = topic + return response + + +def gen_gap_filling_exercise(quantity, start_id, difficulty, topic=None): + json_format = { + "exercises": [ + { + "id": 1, + "question": "sentence with a blank space to fill", + "solutions": ["option 1", "option 2"] + } + ] + } + + messages = [ + { + "role": "system", + "content": 'You are a helpful assistant designed to output JSON on this format: ' + str(json_format) + }, + { + "role": "user", + "content": ("Create " + str(quantity) + " gap filling exercises of " + difficulty + " where the student " + "has to complete the " + "sentence's blank space (signaled as {{id}}) so that it makes sense. " + "The blank space must correspond to a single word.") + }, + { + "role": "user", + "content": 'The id starts at ' + str(start_id) + '.' + } + ] + + if topic is not None: + messages.append({ + "role": "user", + "content": 'Focus the exercises on the grammar subject of ' + topic + '.' + }) + + token_count = count_total_tokens(messages) + response = make_openai_call(GPT_4_O, messages, token_count, GEN_FIELDS, GEN_QUESTION_TEMPERATURE) + response["prompt"] = "Complete the sentence." + response["difficulty"] = difficulty + response["topic"] = topic + return response + + +def gen_grammar_matching_exercise(quantity, start_id, difficulty, topic=None): + json_format = { + "matching_pairs": [ + { + "left": "word/sentence on left", + "right": "word/sentence on right", + } + ] + } + + messages = [ + { + "role": "system", + "content": 'You are a helpful assistant designed to output JSON on this format: ' + str(json_format) + }, + { + "role": "user", + "content": ("Create " + str(quantity) + " grammar related matching exercises " + "of " + difficulty + " where the student has to match the " + "words/sentences on the left with " + "words/sentences on the right.") + } + ] + + if topic is not None: + messages.append({ + "role": "user", + "content": 'Focus the exercises on the grammar subject of ' + topic + '.' + }) + + token_count = count_total_tokens(messages) + response = make_openai_call(GPT_4_O, messages, token_count, GEN_FIELDS, GEN_QUESTION_TEMPERATURE) + + return { + "allowRepetition": False, + "options": build_options_grammar_matching(response["matching_pairs"]), + "prompt": "Match the words/sentences on the left with the ones on the right.", + "sentences": build_sentences_grammar_matching(response["matching_pairs"], start_id), + "type": "matchSentences", + "difficulty": difficulty, + "topic": topic + } + + +def gen_cloze_exercise(quantity, start_id, difficulty, topic=None): + json_format = { + "text": "the text {{1}} blank spaces {{2}} it", + "solutions": [ + { + "id": 1, + "solution": [ + "with" + ] + }, + { + "id": 2, + "word": [ + "on" + ] + } + ] + } + + messages = [ + { + "role": "system", + "content": 'You are a helpful assistant designed to output JSON on this format: ' + str(json_format) + }, + { + "role": "user", + "content": ("Generate a text for a cloze exercise with " + str(quantity) + " blank spaces to fill of " + difficulty + " where the student " + "has to complete the " + "blank spaces (signaled as {{id}}) on the text so that it makes sense. " + "Each blank space must correspond to a single word.") + }, + { + "role": "user", + "content": 'The id starts at ' + str(start_id) + '.' + } + ] + + if topic is not None: + messages.append({ + "role": "user", + "content": 'Focus the exercises on the grammar subject of ' + topic + '.' + }) + + token_count = count_total_tokens(messages) + response = make_openai_call(GPT_4_O, messages, token_count, GEN_FIELDS, GEN_QUESTION_TEMPERATURE) + response["prompt"] = "Complete the text by adding a word to each gap." + response["difficulty"] = difficulty + response["topic"] = topic + response["maxWords"] = 1 + return response + + +def gen_true_false_exercise(quantity: int, start_id, difficulty, topic=None): + json_format = { + "questions": [ + { + "id": 1, + "prompt": "statement_1", + "solution": "true/false" + }, + { + "id": 2, + "prompt": "statement_2", + "solution": "true/false" + } + ] + } + + messages = [ + { + "role": "system", + "content": 'You are a helpful assistant designed to output JSON on this format: ' + str(json_format) + }, + { + "role": "user", + "content": ( + 'Generate ' + str( + quantity) + ' ' + difficulty + ' difficulty grammar related statements for a true or false exercise.') + + }, + { + "role": "user", + "content": 'The id starts at ' + str(start_id) + '.' + } + ] + + if topic is not None: + messages.append({ + "role": "user", + "content": 'Focus the exercises on the grammar subject of ' + topic + '.' + }) + + token_count = count_total_tokens(messages) + response = make_openai_call(GPT_4_O, messages, token_count, GEN_FIELDS, GEN_QUESTION_TEMPERATURE) + response["prompt"] = "Decide if the statements are true or false." + response["difficulty"] = difficulty + response["topic"] = topic + return response + + +def gen_error_correction_exercise(quantity: int, start_id, difficulty, topic=None): + json_format = { + "questions": [ + { + "id": 1, + "prompt": "sentence with errors", + "solution": "corrected sentence" + }, + { + "id": 2, + "prompt": "sentence with errors", + "solution": "corrected sentence" + } + ] + } + + messages = [ + { + "role": "system", + "content": 'You are a helpful assistant designed to output JSON on this format: ' + str(json_format) + }, + { + "role": "user", + "content": ( + 'Generate ' + str( + quantity) + ' ' + difficulty + ' difficulty grammatically incorrect sentences for an exercise where ' + 'the user has to fix the sentence.') + + }, + { + "role": "user", + "content": 'The id starts at ' + str(start_id) + '.' + } + ] + + if topic is not None: + messages.append({ + "role": "user", + "content": 'Focus the exercises on the grammar subject of ' + topic + '.' + }) + + token_count = count_total_tokens(messages) + response = make_openai_call(GPT_4_O, messages, token_count, GEN_FIELDS, GEN_QUESTION_TEMPERATURE) + response["prompt"] = "Find the mistakes in the sentence and correct them." + response["difficulty"] = difficulty + response["topic"] = topic + return response + + +def build_options_grammar_matching(pairs): + options = [] + letters = iter(string.ascii_uppercase) + for pair in pairs: + options.append({ + "id": next(letters), + "sentence": pair["left"] + }) + return options + + +def build_sentences_grammar_matching(pairs, start_id): + sentences = [] + letters = iter(string.ascii_uppercase) + for pair in pairs: + sentences.append({ + "solution": next(letters), + "sentence": pair["right"] + }) + + random.shuffle(sentences) + for i, sentence in enumerate(sentences, start=start_id): + sentence["id"] = i + return sentences