ENCOA-311

This commit is contained in:
Carlos-Mesquita
2025-01-13 01:13:28 +00:00
parent 8550b520e1
commit b32e38156c
27 changed files with 126 additions and 62 deletions

View File

@@ -1,4 +1,5 @@
import random
from typing import List
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, Path, Query, UploadFile
@@ -31,7 +32,7 @@ async def upload(
@inject
async def generate_listening_dialog(
section: int = Path(..., ge=1, le=4),
difficulty: str = Query(default=None),
difficulty: List[str] = Query(default=None),
topic: str = Query(default=None),
listening_controller: IListeningController = Depends(Provide[controller])
):

View File

@@ -59,7 +59,7 @@ async def get_speaking_task(
topic: Optional[str] = Query(None),
first_topic: Optional[str] = Query(None),
second_topic: Optional[str] = Query(None),
difficulty: Optional[str] = None,
difficulty: List[str] = Query(default=None),
speaking_controller: ISpeakingController = Depends(Provide[controller])
):
if not second_topic:
@@ -67,8 +67,7 @@ async def get_speaking_task(
else:
topic_or_first_topic = first_topic if first_topic else random.choice(EducationalContent.MTI_TOPICS)
if not difficulty:
difficulty = random.choice(random.choice(EducationalContent.DIFFICULTIES))
difficulty = [random.choice(EducationalContent.DIFFICULTIES)] if not difficulty else difficulty
second_topic = second_topic if second_topic else random.choice(EducationalContent.MTI_TOPICS)
return await speaking_controller.get_speaking_part(task, topic_or_first_topic, second_topic, difficulty)

View File

@@ -20,10 +20,10 @@ writing_router = APIRouter()
async def generate_writing_academic(
task: int = Path(..., ge=1, le=2),
file: UploadFile = File(...),
difficulty: Optional[List[str]] = None,
difficulty: List[str] = Query(default=None),
writing_controller: IWritingController = Depends(Provide[controller])
):
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
difficulty = [random.choice(EducationalContent.DIFFICULTIES)] if not difficulty else difficulty
return await writing_controller.get_writing_task_academic_question(task, file, difficulty)
@@ -34,10 +34,10 @@ async def generate_writing_academic(
@inject
async def generate_writing(
task: int = Path(..., ge=1, le=2),
difficulty: Optional[str] = None,
difficulty: List[str] = Query(default=None),
topic: str = Query(default=None),
writing_controller: IWritingController = Depends(Provide[controller])
):
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
difficulty = [random.choice(EducationalContent.DIFFICULTIES)] if not difficulty else difficulty
topic = random.choice(EducationalContent.MTI_TOPICS) if not topic else topic
return await writing_controller.get_writing_task_general_question(task, topic, difficulty)

View File

@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from typing import List
from fastapi import UploadFile
@@ -10,7 +11,7 @@ class IListeningController(ABC):
pass
@abstractmethod
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: List[str]):
pass
@abstractmethod

View File

@@ -1,10 +1,11 @@
from abc import ABC, abstractmethod
from typing import List
class ISpeakingController(ABC):
@abstractmethod
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: str):
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: List[str]):
pass
@abstractmethod

View File

@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from typing import List
from fastapi.datastructures import UploadFile
@@ -6,9 +7,9 @@ from fastapi.datastructures import UploadFile
class IWritingController(ABC):
@abstractmethod
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
pass
@abstractmethod
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: List[str]):
pass

View File

@@ -1,4 +1,5 @@
import io
from typing import List
from fastapi import UploadFile
from fastapi.responses import StreamingResponse, Response
@@ -20,7 +21,7 @@ class ListeningController(IListeningController):
else:
return res
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: List[str]):
return await self._service.generate_listening_dialog(section_id, topic, difficulty)
async def get_listening_question(self, dto: ListeningExercisesDTO):

View File

@@ -1,4 +1,5 @@
import logging
from typing import List
from ielts_be.controllers import ISpeakingController
from ielts_be.services import ISpeakingService, IVideoGeneratorService
@@ -11,7 +12,7 @@ class SpeakingController(ISpeakingController):
self._vid_gen = vid_gen
self._logger = logging.getLogger(__name__)
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: str):
async def get_speaking_part(self, task: int, topic: str, second_topic: str, difficulty: List[str]):
return await self._service.get_speaking_part(task, topic, second_topic, difficulty)
async def get_avatars(self):

View File

@@ -1,3 +1,5 @@
from typing import List
from fastapi import UploadFile, HTTPException
from ielts_be.controllers import IWritingController
@@ -9,10 +11,10 @@ class WritingController(IWritingController):
def __init__(self, writing_service: IWritingService):
self._service = writing_service
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
return await self._service.get_writing_task_general_question(task, topic, difficulty)
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: List[str]):
if attachment.content_type not in ['image/jpeg', 'image/png']:
raise HTTPException(status_code=400, detail="Invalid file type. Only JPEG and PNG allowed.")
return await self._service.get_writing_task_academic_question(task, attachment, difficulty)

View File

@@ -12,7 +12,8 @@ class LevelExercises(BaseModel):
sa_qty: Optional[int] = None
mc_qty: Optional[int] = None
topic: Optional[str] = None
difficulty: Optional[str] = None
class LevelExercisesDTO(BaseModel):
exercises: List[LevelExercises]
difficulty: Optional[str] = None
difficulty: Optional[List[str]] = None

View File

@@ -17,11 +17,12 @@ class SaveListeningDTO(BaseModel):
class ListeningExercises(BaseModel):
type: ListeningExerciseType
quantity: int
difficulty: Optional[str] = None
class ListeningExercisesDTO(BaseModel):
text: str
exercises: List[ListeningExercises]
difficulty: Optional[str]
difficulty: Optional[List[str]] = None
class InstructionsDTO(BaseModel):
text: str

View File

@@ -10,8 +10,9 @@ class ReadingExercise(BaseModel):
quantity: int
num_random_words: Optional[int] = Field(1)
max_words: Optional[int] = Field(3)
difficulty: Optional[str] = None
class ReadingDTO(BaseModel):
text: str = Field(...)
exercises: List[ReadingExercise] = Field(...)
difficulty: Optional[str] = None
difficulty: Optional[List[str]] = None

View File

@@ -9,7 +9,7 @@ from fastapi import UploadFile
class IListeningService(ABC):
@abstractmethod
async def generate_listening_dialog( self, section_id: int, topic: str, difficulty: str):
async def generate_listening_dialog( self, section_id: int, topic: str, difficulty: List[str]):
pass
@abstractmethod

View File

@@ -6,7 +6,7 @@ class ISpeakingService(ABC):
@abstractmethod
async def get_speaking_part(
self, part: int, topic: str, second_topic: str, difficulty: str
self, part: int, topic: str, second_topic: str, difficulty: List[str]
) -> Dict:
pass

View File

@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Optional
from typing import Optional, List
from fastapi import UploadFile
@@ -7,11 +7,11 @@ from fastapi import UploadFile
class IWritingService(ABC):
@abstractmethod
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
pass
@abstractmethod
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: List[str]):
pass
@abstractmethod

View File

@@ -13,6 +13,7 @@ from ielts_be.services import (
ILevelService, ILLMService, IReadingService,
IWritingService, IListeningService, ISpeakingService
)
from ielts_be.utils import pick_difficulty
from .exercises import MultipleChoice, BlankSpace, PassageUtas, FillBlanks
from .full_exams import CustomLevelModule, LevelUtas
from .upload import UploadLevelModule
@@ -50,19 +51,22 @@ class LevelService(ILevelService):
async def upload_level(self, upload: UploadFile, solutions: Optional[UploadFile] = None) -> Dict:
return await self._upload_module.generate_level_from_file(upload, solutions)
async def _generate_exercise(self, req_exercise, start_id):
async def _generate_exercise(self, req_exercise, start_id, difficulties):
difficulty = pick_difficulty(req_exercise.difficulty, difficulties)
if req_exercise.type == "mcBlank":
questions = await self._mc.gen_multiple_choice("blank_space", req_exercise.quantity, start_id)
questions = await self._mc.gen_multiple_choice("blank_space", req_exercise.quantity, difficulty, start_id)
questions["variant"] = "mcBlank"
questions["type"] = "multipleChoice"
questions["prompt"] = "Choose the correct word or group of words that completes the sentences."
questions["difficulty"] = difficulty
return questions
elif req_exercise.type == "mcUnderline":
questions = await self._mc.gen_multiple_choice("underline", req_exercise.quantity, start_id)
questions = await self._mc.gen_multiple_choice("underline", req_exercise.quantity, difficulty, start_id)
questions["variant"] = "mcUnderline"
questions["type"] = "multipleChoice"
questions["prompt"] = "Choose the underlined word or group of words that is not correct."
questions["difficulty"] = difficulty
return questions
elif req_exercise.type == "passageUtas":
@@ -70,21 +74,24 @@ class LevelService(ILevelService):
exercise = await self._passage_utas.gen_reading_passage_utas(
start_id,
req_exercise.quantity,
difficulty,
topic,
req_exercise.text_size
)
exercise["prompt"] = "Read the text and answer the questions below."
exercise["difficulty"] = difficulty
return exercise
elif req_exercise.type == "fillBlanksMC":
exercise = await self._fill_blanks.gen_fill_blanks(
start_id,
req_exercise.quantity,
difficulty,
req_exercise.text_size,
req_exercise.topic
)
exercise["prompt"] = "Read the text below and choose the correct word for each space."
exercise["difficulty"] = difficulty
return exercise
async def generate_exercises(self, dto: LevelExercisesDTO):
@@ -95,7 +102,7 @@ class LevelService(ILevelService):
current_id += req_exercise.quantity
tasks = [
self._generate_exercise(req_exercise, start_id)
self._generate_exercise(req_exercise, start_id, dto.difficulty)
for req_exercise, start_id in zip(dto.exercises, start_ids)
]
questions = await gather(*tasks)
@@ -105,10 +112,12 @@ class LevelService(ILevelService):
# Just here to support other modules that I don't know if they are supposed to still be used
async def gen_multiple_choice(self, mc_variant: str, quantity: int, start_id: int = 1):
return await self._mc.gen_multiple_choice(mc_variant, quantity, start_id)
difficulty = random.choice(EducationalContent.DIFFICULTIES)
return await self._mc.gen_multiple_choice(mc_variant, quantity, difficulty, start_id)
async def gen_reading_passage_utas(self, start_id, mc_quantity: int, topic=Optional[str]): # sa_quantity: int,
return await self._passage_utas.gen_reading_passage_utas(start_id, mc_quantity, topic)
difficulty = random.choice(EducationalContent.DIFFICULTIES)
return await self._passage_utas.gen_reading_passage_utas(start_id, mc_quantity, difficulty, topic)
async def gen_blank_space_text_utas(self, quantity: int, start_id: int, size: int, topic: str):
return await self._blank_space.gen_blank_space_text_utas(quantity, start_id, size, topic)

View File

@@ -11,12 +11,10 @@ class FillBlanks:
async def gen_fill_blanks(
self, start_id: int, quantity: int, size: int = 300, topic=None
self, start_id: int, quantity: int, difficulty: str, size: int = 300, topic=None
):
if not topic:
topic = random.choice(EducationalContent.MTI_TOPICS)
print(quantity)
print(start_id)
messages = [
{
"role": "system",
@@ -34,8 +32,15 @@ class FillBlanks:
'JSON object containing: the modified text, a solutions array with each word\'s correct '
'letter (A-D), and a words array containing each id with four options where one is '
'the original word (matching the solution) and three are plausible but incorrect '
'alternatives that maintain grammatical consistency. '
'You cannot use repeated words!' #TODO: Solve this after
f'alternatives that maintain grammatical consistency and {difficulty} CEFR level complexity. '
'You cannot use repeated words!'
# TODO: Solve this after -> forgot about this TODO just saw now in
# 1/11/25 what I meant by this is gpt still sometimes returns repeated
# words even with explicit instructions to not do so, this is a general problem
# for all exercises, for more robust validation use self._llm.pydantic_prediction
# or implement a method that calls a mapper, catches validation error messages
# from that mapper and asks gpt to retry if creating a pydantic model for each
# operation proves to be unsustainable
)
}
]

View File

@@ -10,16 +10,16 @@ class MultipleChoice:
self._mc_variants = mc_variants
async def gen_multiple_choice(
self, mc_variant: str, quantity: int, start_id: int = 1
self, mc_variant: str, quantity: int, difficulty: str, start_id: int = 1
):
mc_template = self._mc_variants[mc_variant]
blank_mod = " blank space " if mc_variant == "blank_space" else " "
gen_multiple_choice_for_text: str = (
'Generate {quantity} multiple choice{blank}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.'
'Generate {quantity} multiple choice{blank}questions of 4 options for an english level exam of {difficulty} '
'CEFR level, 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.'
)
messages = [
@@ -31,7 +31,7 @@ class MultipleChoice:
},
{
"role": "user",
"content": gen_multiple_choice_for_text.format(quantity=str(quantity), blank=blank_mod)
"content": gen_multiple_choice_for_text.format(quantity=str(quantity), blank=blank_mod, difficulty=difficulty)
}
]

View File

@@ -13,11 +13,11 @@ class PassageUtas:
self._mc_variants = mc_variants
async def gen_reading_passage_utas(
self, start_id, mc_quantity: int, topic: Optional[str], word_size: Optional[int] # sa_quantity: int,
self, start_id, mc_quantity: int, difficulty: str, topic: Optional[str] = None, word_size: Optional[int] = None# sa_quantity: int,
):
passage = await self._reading_service.generate_reading_passage(1, topic, word_size)
mc_exercises = await self._gen_text_multiple_choice_utas(passage["text"], start_id, mc_quantity)
mc_exercises = await self._gen_text_multiple_choice_utas(passage["text"], start_id, mc_quantity, difficulty)
mc_exercises["type"] = "multipleChoice"
"""
exercises: {
@@ -61,7 +61,7 @@ class PassageUtas:
return question["questions"]
async def _gen_text_multiple_choice_utas(self, text: str, start_id: int, mc_quantity: int):
async def _gen_text_multiple_choice_utas(self, text: str, start_id: int, mc_quantity: int, difficulty: str):
json_template = self._mc_variants["text_mc_utas"]
messages = [
@@ -71,7 +71,9 @@ class PassageUtas:
},
{
"role": "user",
"content": f'Generate {mc_quantity} multiple choice questions of 4 options for this text:\n{text}'
"content": (
f'Generate {mc_quantity} multiple choice questions of 4 options, {difficulty} CEFR '
f'level difficulty, for this text:\n{text}')
},
{
"role": "user",

View File

@@ -1,6 +1,8 @@
import json
import random
import uuid
from ielts_be.configs.constants import EducationalContent
from ielts_be.services import ILLMService

View File

@@ -1,7 +1,7 @@
import asyncio
from logging import getLogger
import random
from typing import Dict, Any, Union
from typing import Dict, Any, Union, List
from starlette.datastructures import UploadFile
@@ -20,6 +20,7 @@ from .write_blank_forms import WriteBlankForms
from .write_blanks import WriteBlanks
from .write_blank_notes import WriteBlankNotes
from ..shared import TrueFalse, MultipleChoice
from ielts_be.utils import pick_difficulty
class ListeningService(IListeningService):
@@ -94,7 +95,8 @@ class ListeningService(IListeningService):
return await self._import.import_from_file(exercises, solutions)
async def generate_listening_dialog(self, section: int, topic: str, difficulty: str):
async def generate_listening_dialog(self, section: int, topic: str, difficulty: List[str]):
# TODO: difficulties to difficulty
return await self._sections[f'section_{section}']["generate_dialogue"](section, topic)
async def transcribe_dialog(self, audio: UploadFile):
@@ -142,7 +144,7 @@ class ListeningService(IListeningService):
"dialog or monologue",
dto.text,
start_id,
dto.difficulty
pick_difficulty(req_exercise.difficulty, dto.difficulty)
)
)
start_id += req_exercise.quantity
@@ -157,6 +159,7 @@ class ListeningService(IListeningService):
question = await self._multiple_choice.gen_multiple_choice(
text, req_exercise.quantity, start_id, difficulty, n_options
)
question["difficulty"] = difficulty
self._logger.info(f"Added multiple choice: {question}")
return question
@@ -165,6 +168,7 @@ class ListeningService(IListeningService):
dialog_type, text, req_exercise.quantity, start_id, difficulty
)
question["variant"] = "questions"
question["difficulty"] = difficulty
self._logger.info(f"Added write blanks questions: {question}")
return question
@@ -173,6 +177,7 @@ class ListeningService(IListeningService):
dialog_type, text, req_exercise.quantity, start_id, difficulty
)
question["variant"] = "fill"
question["difficulty"] = difficulty
self._logger.info(f"Added write blanks notes: {question}")
return question
@@ -181,12 +186,14 @@ class ListeningService(IListeningService):
dialog_type, text, req_exercise.quantity, start_id, difficulty
)
question["variant"] = "form"
question["difficulty"] = difficulty
self._logger.info(f"Added write blanks form: {question}")
return question
elif req_exercise.type == "trueFalse":
question = await self._true_false.gen_true_false_not_given_exercise(
text, req_exercise.quantity, start_id, difficulty, "listening"
)
question["difficulty"] = difficulty
self._logger.info(f"Added trueFalse: {question}")
return question

View File

@@ -7,6 +7,7 @@ from ielts_be.configs.constants import GPTModels, FieldsAndExercises, Temperatur
from ielts_be.dtos.reading import ReadingDTO
from ielts_be.helpers import ExercisesHelper
from ielts_be.services import IReadingService, ILLMService
from ielts_be.utils import pick_difficulty
from .fill_blanks import FillBlanks
from .idea_match import IdeaMatch
from .paragraph_match import ParagraphMatch
@@ -84,6 +85,7 @@ class ReadingService(IReadingService):
question = await self._fill_blanks.gen_summary_fill_blanks_exercise(
text, req_exercise.quantity, start_id, difficulty, req_exercise.num_random_words
)
question["difficulty"] = difficulty
self._logger.info(f"Added fill blanks: {question}")
return question
@@ -91,6 +93,7 @@ class ReadingService(IReadingService):
question = await self._true_false.gen_true_false_not_given_exercise(
text, req_exercise.quantity, start_id, difficulty, "reading"
)
question["difficulty"] = difficulty
self._logger.info(f"Added trueFalse: {question}")
return question
@@ -100,6 +103,7 @@ class ReadingService(IReadingService):
)
if ExercisesHelper.answer_word_limit_ok(question):
question["difficulty"] = difficulty
self._logger.info(f"Added write blanks: {question}")
return question
else:
@@ -110,6 +114,7 @@ class ReadingService(IReadingService):
question = await self._paragraph_match.gen_paragraph_match_exercise(
text, req_exercise.quantity, start_id
)
question["difficulty"] = difficulty
self._logger.info(f"Added paragraph match: {question}")
return question
@@ -118,12 +123,14 @@ class ReadingService(IReadingService):
text, req_exercise.quantity, start_id
)
question["variant"] = "ideaMatch"
question["difficulty"] = difficulty
self._logger.info(f"Added idea match: {question}")
return question
elif req_exercise.type == "multipleChoice":
question = await self._multiple_choice.gen_multiple_choice(
text, req_exercise.quantity, start_id, difficulty, 4
)
question["difficulty"] = difficulty
self._logger.info(f"Added multiple choice: {question}")
return question
@@ -137,7 +144,7 @@ class ReadingService(IReadingService):
req_exercise,
dto.text,
start_id,
dto.difficulty
pick_difficulty(req_exercise.difficulty, dto.difficulty)
)
)
start_id += req_exercise.quantity

View File

@@ -1,5 +1,6 @@
import logging
import re
import random
from typing import Dict, List
@@ -99,8 +100,9 @@ class SpeakingService(ISpeakingService):
}
async def get_speaking_part(
self, part: int, topic: str, second_topic: str, difficulty: str
self, part: int, topic: str, second_topic: str, difficulty: List[str]
) -> Dict:
diff = difficulty[0] if len(difficulty) == 1 else random.choice(difficulty)
task_values = self._tasks[f'task_{part}']['get']
if part == 1:
@@ -157,7 +159,7 @@ class SpeakingService(ISpeakingService):
]
response["type"] = part
response["difficulty"] = difficulty
response["difficulty"] = diff
if part in {2, 3}:
response["topic"] = topic

View File

@@ -1,3 +1,4 @@
import random
from typing import List, Dict, Optional
from fastapi import UploadFile
@@ -16,7 +17,8 @@ class WritingService(IWritingService):
self._llm = llm
self._grade = GradeWriting(llm, file_storage, ai_detector)
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: List[str]):
diff = difficulty[0] if len(difficulty) == 1 else random.choice(difficulty)
messages = [
{
"role": "system",
@@ -24,7 +26,7 @@ class WritingService(IWritingService):
'You are a helpful assistant designed to output JSON on this format: {"prompt": "prompt content"}'
)
},
*get_writing_args_general(task, topic, difficulty)
*get_writing_args_general(task, topic, diff)
]
llm_model = GPTModels.GPT_3_5_TURBO if task == 1 else GPTModels.GPT_4_O
@@ -40,11 +42,12 @@ class WritingService(IWritingService):
return {
"question": self._add_newline_before_hyphen(question) if task == 1 else question,
"difficulty": difficulty,
"topic": topic
"topic": topic,
"difficulty": diff
}
async def get_writing_task_academic_question(self, task: int, file: UploadFile, difficulty: str):
async def get_writing_task_academic_question(self, task: int, file: UploadFile, difficulty: List[str]):
diff = difficulty[0] if len(difficulty) == 1 else random.choice(difficulty)
messages = [
{
"role": "system",
@@ -52,7 +55,7 @@ class WritingService(IWritingService):
'You are a helpful assistant designed to output JSON on this format: {"prompt": "prompt content"}'
)
},
*(await get_writing_args_academic(task, file))
*(await get_writing_args_academic(task, file, diff))
]
response = await self._llm.prediction(
@@ -66,7 +69,7 @@ class WritingService(IWritingService):
return {
"question": self._add_newline_before_hyphen(question) if task == 1 else question,
"difficulty": difficulty,
"difficulty": diff,
}
async def grade_writing_task(self, task: int, question: str, answer: str, attachment: Optional[str] = None):

View File

@@ -4,7 +4,7 @@ from typing import List, Dict
from fastapi.datastructures import UploadFile
async def get_writing_args_academic(task: int, attachment: UploadFile) -> List[Dict]:
async def get_writing_args_academic(task: int, attachment: UploadFile, difficulty: str) -> List[Dict]:
writing_args = {
"1": {
"prompt": (
@@ -16,7 +16,8 @@ async def get_writing_args_academic(task: int, attachment: UploadFile) -> List[D
'The generated prompt must:\n'
'1. Clearly describe the type of visual representation in the image\n'
'2. Provide a concise context for the data shown\n'
'3. End with the standard IELTS Task 1 Academic instruction:\n'
f'3. Be adequate for {difficulty} CEFR level users\n'
'4. End with the standard IELTS Task 1 Academic instruction:\n'
'"Summarise the information by selecting and reporting the main features, and make comparisons where relevant."'
)
},

View File

@@ -1,7 +1,9 @@
from .handle_exception import handle_exception
from .logger import suppress_loggers
from .pick_difficulty import pick_difficulty
__all__ = [
"handle_exception",
"suppress_loggers"
"suppress_loggers",
"pick_difficulty"
]

View File

@@ -0,0 +1,14 @@
import random
from typing import Optional, List
from ielts_be.configs.constants import EducationalContent
def pick_difficulty(difficulty: Optional[str], difficulties: Optional[List[str]]) -> str:
if difficulty:
return difficulty
if difficulties:
return random.choice(difficulties)
return random.choice(EducationalContent.DIFFICULTIES)