Async release
This commit is contained in:
17
app/controllers/impl/__init__.py
Normal file
17
app/controllers/impl/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from .level import LevelController
|
||||
from .listening import ListeningController
|
||||
from .reading import ReadingController
|
||||
from .speaking import SpeakingController
|
||||
from .writing import WritingController
|
||||
from .training import TrainingController
|
||||
from .grade import GradeController
|
||||
|
||||
__all__ = [
|
||||
"LevelController",
|
||||
"ListeningController",
|
||||
"ReadingController",
|
||||
"SpeakingController",
|
||||
"WritingController",
|
||||
"TrainingController",
|
||||
"GradeController"
|
||||
]
|
||||
86
app/controllers/impl/grade.py
Normal file
86
app/controllers/impl/grade.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.configs.constants import FilePaths
|
||||
from app.controllers.abc import IGradeController
|
||||
from app.dtos.speaking import SpeakingGradeTask1And2DTO, SpeakingGradeTask3DTO
|
||||
from app.dtos.writing import WritingGradeTaskDTO
|
||||
from app.helpers import IOHelper
|
||||
from app.services.abc import ISpeakingService, IWritingService, IGradeService
|
||||
|
||||
|
||||
class GradeController(IGradeController):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
grade_service: IGradeService,
|
||||
speaking_service: ISpeakingService,
|
||||
writing_service: IWritingService
|
||||
):
|
||||
self._service = grade_service
|
||||
self._speaking_service = speaking_service
|
||||
self._writing_service = writing_service
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
async def grade_writing_task(self, task: int, data: WritingGradeTaskDTO):
|
||||
try:
|
||||
return await self._writing_service.grade_writing_task(task, data.question, data.answer)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def grade_speaking_task(self, task: int, data: Dict):
|
||||
try:
|
||||
if task in {1, 2}:
|
||||
body = SpeakingGradeTask1And2DTO(**data)
|
||||
return await self._grade_speaking_task_1_2(task, body.question, body.answer)
|
||||
else:
|
||||
body = SpeakingGradeTask3DTO(**data)
|
||||
return await self._grade_speaking_task3(body.answers)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(status_code=422, detail=e.errors())
|
||||
|
||||
async def grading_summary(self, data: Dict):
|
||||
try:
|
||||
section_keys = ['reading', 'listening', 'writing', 'speaking', 'level']
|
||||
extracted_sections = self._extract_existing_sections_from_body(data, section_keys)
|
||||
return await self._service.calculate_grading_summary(extracted_sections)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def _grade_speaking_task_1_2(self, task: int, question: str, answer_firebase_path: str):
|
||||
sound_file_name = FilePaths.AUDIO_FILES_PATH + str(uuid.uuid4())
|
||||
try:
|
||||
IOHelper.delete_files_older_than_one_day(FilePaths.AUDIO_FILES_PATH)
|
||||
return await self._speaking_service.grade_speaking_task_1_and_2(
|
||||
task, question, answer_firebase_path, sound_file_name
|
||||
)
|
||||
except Exception as e:
|
||||
os.remove(sound_file_name)
|
||||
return str(e), 400
|
||||
|
||||
async def _grade_speaking_task3(self, answers: Dict):
|
||||
try:
|
||||
IOHelper.delete_files_older_than_one_day(FilePaths.AUDIO_FILES_PATH)
|
||||
return await self._speaking_service.grade_speaking_task_3(answers)
|
||||
except Exception as e:
|
||||
return str(e), 400
|
||||
|
||||
@staticmethod
|
||||
def _extract_existing_sections_from_body(my_dict, keys_to_extract):
|
||||
if 'sections' in my_dict and isinstance(my_dict['sections'], list) and len(my_dict['sections']) > 0:
|
||||
return list(
|
||||
filter(
|
||||
lambda item:
|
||||
'code' in item and
|
||||
item['code'] in keys_to_extract and
|
||||
'grade' in item and
|
||||
'name' in item,
|
||||
my_dict['sections']
|
||||
)
|
||||
)
|
||||
|
||||
20
app/controllers/impl/level.py
Normal file
20
app/controllers/impl/level.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from app.controllers.abc import ILevelController
|
||||
from app.services.abc import ILevelService
|
||||
|
||||
|
||||
class LevelController(ILevelController):
|
||||
|
||||
def __init__(self, level_service: ILevelService):
|
||||
self._service = level_service
|
||||
|
||||
async def get_level_exam(self):
|
||||
try:
|
||||
return await self._service.get_level_exam()
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def get_level_utas(self):
|
||||
try:
|
||||
return await self._service.get_level_utas()
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
97
app/controllers/impl/listening.py
Normal file
97
app/controllers/impl/listening.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import random
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from app.controllers.abc import IListeningController
|
||||
from app.dtos import SaveListeningDTO
|
||||
from app.services.abc import IListeningService
|
||||
from app.helpers import IOHelper, ExercisesHelper
|
||||
from app.configs.constants import (
|
||||
FilePaths, EducationalContent, FieldsAndExercises
|
||||
)
|
||||
|
||||
|
||||
class ListeningController(IListeningController):
|
||||
|
||||
def __init__(self, listening_service: IListeningService):
|
||||
self._service = listening_service
|
||||
self._logger = logging.getLogger(__name__)
|
||||
self._sections = {
|
||||
"section_1": {
|
||||
"topic": EducationalContent.TWO_PEOPLE_SCENARIOS,
|
||||
"exercise_sample_size": 1,
|
||||
"total_exercises": FieldsAndExercises.TOTAL_LISTENING_SECTION_1_EXERCISES,
|
||||
"type": "conversation",
|
||||
"start_id": 1
|
||||
},
|
||||
"section_2": {
|
||||
"topic": EducationalContent.SOCIAL_MONOLOGUE_CONTEXTS,
|
||||
"exercise_sample_size": 2,
|
||||
"total_exercises": FieldsAndExercises.TOTAL_LISTENING_SECTION_2_EXERCISES,
|
||||
"type": "monologue",
|
||||
"start_id": 11
|
||||
},
|
||||
"section_3": {
|
||||
"topic": EducationalContent.FOUR_PEOPLE_SCENARIOS,
|
||||
"exercise_sample_size": 1,
|
||||
"total_exercises": FieldsAndExercises.TOTAL_LISTENING_SECTION_3_EXERCISES,
|
||||
"type": "conversation",
|
||||
"start_id": 21
|
||||
},
|
||||
"section_4": {
|
||||
"topic": EducationalContent.ACADEMIC_SUBJECTS,
|
||||
"exercise_sample_size": 2,
|
||||
"total_exercises": FieldsAndExercises.TOTAL_LISTENING_SECTION_4_EXERCISES,
|
||||
"type": "monologue",
|
||||
"start_id": 31
|
||||
}
|
||||
}
|
||||
|
||||
async def get_listening_question(self, section_id: int, topic: str, req_exercises: List[str], difficulty: str):
|
||||
try:
|
||||
IOHelper.delete_files_older_than_one_day(FilePaths.AUDIO_FILES_PATH)
|
||||
section = self._sections[f"section_{str(section_id)}"]
|
||||
if not topic:
|
||||
topic = random.choice(section["topic"])
|
||||
|
||||
if len(req_exercises) == 0:
|
||||
req_exercises = random.sample(FieldsAndExercises.LISTENING_EXERCISE_TYPES, section["exercise_sample_size"])
|
||||
|
||||
number_of_exercises_q = ExercisesHelper.divide_number_into_parts(section["total_exercises"], len(req_exercises))
|
||||
|
||||
dialog = await self._service.generate_listening_question(section_id, topic)
|
||||
|
||||
if section_id in {1, 3}:
|
||||
dialog = self.parse_conversation(dialog)
|
||||
|
||||
self._logger.info(f'Generated {section["type"]}: {str(dialog)}')
|
||||
|
||||
exercises = await self._service.generate_listening_exercises(
|
||||
section_id, str(dialog), req_exercises, number_of_exercises_q, section["start_id"], difficulty
|
||||
)
|
||||
|
||||
return {
|
||||
"exercises": exercises,
|
||||
"text": dialog,
|
||||
"difficulty": difficulty
|
||||
}
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def save_listening(self, data: SaveListeningDTO):
|
||||
try:
|
||||
return await self._service.save_listening(data.parts, data.minTimer, data.difficulty)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
@staticmethod
|
||||
def parse_conversation(conversation_data):
|
||||
conversation_list = conversation_data.get('conversation', [])
|
||||
readable_text = []
|
||||
|
||||
for message in conversation_list:
|
||||
name = message.get('name', 'Unknown')
|
||||
text = message.get('text', '')
|
||||
readable_text.append(f"{name}: {text}")
|
||||
|
||||
return "\n".join(readable_text)
|
||||
43
app/controllers/impl/reading.py
Normal file
43
app/controllers/impl/reading.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import random
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from app.controllers.abc import IReadingController
|
||||
from app.services.abc import IReadingService
|
||||
from app.configs.constants import FieldsAndExercises
|
||||
from app.helpers import ExercisesHelper
|
||||
|
||||
|
||||
class ReadingController(IReadingController):
|
||||
|
||||
def __init__(self, reading_service: IReadingService):
|
||||
self._service = reading_service
|
||||
self._logger = logging.getLogger(__name__)
|
||||
self._passages = {
|
||||
"passage_1": {
|
||||
"total_exercises": FieldsAndExercises.TOTAL_READING_PASSAGE_1_EXERCISES
|
||||
},
|
||||
"passage_2": {
|
||||
"total_exercises": FieldsAndExercises.TOTAL_READING_PASSAGE_2_EXERCISES
|
||||
},
|
||||
"passage_3": {
|
||||
"total_exercises": FieldsAndExercises.TOTAL_READING_PASSAGE_3_EXERCISES
|
||||
}
|
||||
}
|
||||
|
||||
async def get_reading_passage(self, passage_id: int, topic: str, req_exercises: List[str], difficulty: str):
|
||||
try:
|
||||
passage = self._passages[f'passage_{str(passage_id)}']
|
||||
|
||||
if len(req_exercises) == 0:
|
||||
req_exercises = random.sample(FieldsAndExercises.READING_EXERCISE_TYPES, 2)
|
||||
|
||||
number_of_exercises_q = ExercisesHelper.divide_number_into_parts(
|
||||
passage["total_exercises"], len(req_exercises)
|
||||
)
|
||||
|
||||
return await self._service.gen_reading_passage(
|
||||
passage_id, topic, req_exercises, number_of_exercises_q, difficulty
|
||||
)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
63
app/controllers/impl/speaking.py
Normal file
63
app/controllers/impl/speaking.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
from app.controllers.abc import ISpeakingController
|
||||
from app.dtos import (
|
||||
SaveSpeakingDTO, SpeakingGenerateVideoDTO,
|
||||
SpeakingGenerateInteractiveVideoDTO
|
||||
)
|
||||
from app.services.abc import ISpeakingService
|
||||
from app.configs.constants import ExamVariant, MinTimers
|
||||
from app.configs.question_templates import getSpeakingTemplate
|
||||
|
||||
|
||||
class SpeakingController(ISpeakingController):
|
||||
|
||||
def __init__(self, speaking_service: ISpeakingService):
|
||||
self._service = speaking_service
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
async def get_speaking_task(self, task: int, topic: str, difficulty: str):
|
||||
try:
|
||||
return await self._service.get_speaking_task(task, topic, difficulty)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def save_speaking(self, data: SaveSpeakingDTO, background_tasks: BackgroundTasks):
|
||||
try:
|
||||
exercises = data.exercises
|
||||
min_timer = data.minTimer
|
||||
|
||||
template = getSpeakingTemplate()
|
||||
template["minTimer"] = min_timer
|
||||
|
||||
if min_timer < MinTimers.SPEAKING_MIN_TIMER_DEFAULT:
|
||||
template["variant"] = ExamVariant.PARTIAL.value
|
||||
else:
|
||||
template["variant"] = ExamVariant.FULL.value
|
||||
|
||||
req_id = str(uuid.uuid4())
|
||||
self._logger.info(f'Received request to save speaking with id: {req_id}')
|
||||
|
||||
background_tasks.add_task(self._service.create_videos_and_save_to_db, exercises, template, req_id)
|
||||
|
||||
self._logger.info('Started background task to save speaking.')
|
||||
|
||||
# Return response without waiting for create_videos_and_save_to_db to finish
|
||||
return {**template, "id": req_id}
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def generate_speaking_video(self, data: SpeakingGenerateVideoDTO):
|
||||
try:
|
||||
return await self._service.generate_speaking_video(data.question, data.topic, data.avatar, data.prompts)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def generate_interactive_video(self, data: SpeakingGenerateInteractiveVideoDTO):
|
||||
try:
|
||||
return await self._service.generate_interactive_video(data.questions, data.topic, data.avatar)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
15
app/controllers/impl/training.py
Normal file
15
app/controllers/impl/training.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from app.controllers.abc import ITrainingController
|
||||
from app.dtos import TipsDTO
|
||||
from app.services.abc import ITrainingService
|
||||
|
||||
|
||||
class TrainingController(ITrainingController):
|
||||
|
||||
def __init__(self, training_service: ITrainingService):
|
||||
self._service = training_service
|
||||
|
||||
async def fetch_tips(self, data: TipsDTO):
|
||||
try:
|
||||
return await self._service.fetch_tips(data.context, data.question, data.answer, data.correct_answer)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
14
app/controllers/impl/writing.py
Normal file
14
app/controllers/impl/writing.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from app.controllers.abc import IWritingController
|
||||
from app.services.abc import IWritingService
|
||||
|
||||
|
||||
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):
|
||||
try:
|
||||
return await self._service.get_writing_task_general_question(task, topic, difficulty)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
Reference in New Issue
Block a user