Async release

This commit is contained in:
Carlos Mesquita
2024-07-23 08:40:35 +01:00
parent a4caecdb4f
commit 3cf9fa5cba
116 changed files with 5609 additions and 30630 deletions

View File

View File

@@ -0,0 +1,17 @@
from .level import ILevelController
from .listening import IListeningController
from .reading import IReadingController
from .writing import IWritingController
from .speaking import ISpeakingController
from .grade import IGradeController
from .training import ITrainingController
__all__ = [
"IListeningController",
"IReadingController",
"IWritingController",
"ISpeakingController",
"ILevelController",
"IGradeController",
"ITrainingController"
]

View File

@@ -0,0 +1,26 @@
from abc import ABC, abstractmethod
from typing import Dict
class IGradeController(ABC):
@abstractmethod
async def grade_writing_task(self, task: int, data):
pass
@abstractmethod
async def grade_speaking_task(self, task: int, data: Dict):
pass
@abstractmethod
async def grading_summary(self, data: Dict):
pass
@abstractmethod
async def _grade_speaking_task_1_2(self, task: int, question: str, answer_firebase_path: str):
pass
@abstractmethod
async def _grade_speaking_task3(self, answers: Dict):
pass

View File

@@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
class ILevelController(ABC):
@abstractmethod
async def get_level_exam(self):
pass
@abstractmethod
async def get_level_utas(self):
pass

View File

@@ -0,0 +1,13 @@
from abc import ABC, abstractmethod
from typing import List
class IListeningController(ABC):
@abstractmethod
async def get_listening_question(self, section_id: int, topic: str, exercises: List[str], difficulty: str):
pass
@abstractmethod
async def save_listening(self, data):
pass

View File

@@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from typing import List
class IReadingController(ABC):
@abstractmethod
async def get_reading_passage(self, passage: int, topic: str, exercises: List[str], difficulty: str):
pass

View File

@@ -0,0 +1,21 @@
from abc import ABC, abstractmethod
from fastapi import BackgroundTasks
class ISpeakingController(ABC):
@abstractmethod
async def get_speaking_task(self, task: int, topic: str, difficulty: str):
pass
@abstractmethod
async def save_speaking(self, data, background_tasks: BackgroundTasks):
pass
@abstractmethod
async def generate_speaking_video(self, data):
pass
@abstractmethod
async def generate_interactive_video(self, data):
pass

View File

@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
class ITrainingController(ABC):
@abstractmethod
async def fetch_tips(self, data):
pass

View File

@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
class IWritingController(ABC):
@abstractmethod
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
pass

View 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"
]

View 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']
)
)

View 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)

View 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)

View 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)

View 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)

View 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)

View 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)