Brushed up the backend, added writing task 1 academic prompt gen and grading ENCOA-274
This commit is contained in:
12
ielts_be/controllers/impl/__init__.py
Normal file
12
ielts_be/controllers/impl/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from .training import TrainingController
|
||||
from .grade import GradeController
|
||||
from .user import UserController
|
||||
from .exam import *
|
||||
|
||||
__all__ = [
|
||||
"TrainingController",
|
||||
"GradeController",
|
||||
"UserController"
|
||||
]
|
||||
|
||||
__all__.extend(exam.__all__)
|
||||
13
ielts_be/controllers/impl/exam/__init__.py
Normal file
13
ielts_be/controllers/impl/exam/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from .level import LevelController
|
||||
from .listening import ListeningController
|
||||
from .reading import ReadingController
|
||||
from .speaking import SpeakingController
|
||||
from .writing import WritingController
|
||||
|
||||
__all__ = [
|
||||
"LevelController",
|
||||
"ListeningController",
|
||||
"ReadingController",
|
||||
"SpeakingController",
|
||||
"WritingController",
|
||||
]
|
||||
26
ielts_be/controllers/impl/exam/level.py
Normal file
26
ielts_be/controllers/impl/exam/level.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from fastapi import UploadFile
|
||||
from typing import Dict, Optional
|
||||
|
||||
from ielts_be.controllers import ILevelController
|
||||
from ielts_be.services import ILevelService
|
||||
|
||||
|
||||
class LevelController(ILevelController):
|
||||
|
||||
def __init__(self, level_service: ILevelService):
|
||||
self._service = level_service
|
||||
|
||||
async def generate_exercises(self, dto):
|
||||
return await self._service.generate_exercises(dto)
|
||||
|
||||
async def get_level_exam(self):
|
||||
return await self._service.get_level_exam()
|
||||
|
||||
async def get_level_utas(self):
|
||||
return await self._service.get_level_utas()
|
||||
|
||||
async def upload_level(self, exercises: UploadFile, solutions: Optional[UploadFile] = None):
|
||||
return await self._service.upload_level(exercises, solutions)
|
||||
|
||||
async def get_custom_level(self, data: Dict):
|
||||
return await self._service.get_custom_level(data)
|
||||
39
ielts_be/controllers/impl/exam/listening.py
Normal file
39
ielts_be/controllers/impl/exam/listening.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import io
|
||||
|
||||
from fastapi import UploadFile
|
||||
from starlette.responses import StreamingResponse, Response
|
||||
|
||||
from ielts_be.controllers import IListeningController
|
||||
from ielts_be.services import IListeningService
|
||||
from ielts_be.dtos.listening import GenerateListeningExercises, Dialog
|
||||
|
||||
|
||||
class ListeningController(IListeningController):
|
||||
|
||||
def __init__(self, listening_service: IListeningService):
|
||||
self._service = listening_service
|
||||
|
||||
async def import_exam(self, exercises: UploadFile, solutions: UploadFile = None):
|
||||
res = await self._service.import_exam(exercises, solutions)
|
||||
if not res:
|
||||
return Response(status_code=500)
|
||||
else:
|
||||
return res
|
||||
|
||||
async def generate_listening_dialog(self, section_id: int, topic: str, difficulty: str):
|
||||
return await self._service.generate_listening_dialog(section_id, topic, difficulty)
|
||||
|
||||
async def get_listening_question(self, dto: GenerateListeningExercises):
|
||||
return await self._service.get_listening_question(dto)
|
||||
|
||||
async def generate_mp3(self, dto: Dialog):
|
||||
mp3 = await self._service.generate_mp3(dto)
|
||||
|
||||
return StreamingResponse(
|
||||
content=io.BytesIO(mp3),
|
||||
media_type="audio/mpeg",
|
||||
headers={
|
||||
"Content-Type": "audio/mpeg",
|
||||
"Content-Disposition": "attachment;filename=speech.mp3"
|
||||
}
|
||||
)
|
||||
28
ielts_be/controllers/impl/exam/reading.py
Normal file
28
ielts_be/controllers/impl/exam/reading.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import UploadFile, Response
|
||||
|
||||
from ielts_be.controllers import IReadingController
|
||||
from ielts_be.services import IReadingService
|
||||
from ielts_be.dtos.reading import ReadingDTO
|
||||
|
||||
|
||||
class ReadingController(IReadingController):
|
||||
|
||||
def __init__(self, reading_service: IReadingService):
|
||||
self._service = reading_service
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
async def import_exam(self, exercises: UploadFile, solutions: UploadFile = None):
|
||||
res = await self._service.import_exam(exercises, solutions)
|
||||
if not res:
|
||||
return Response(status_code=500)
|
||||
else:
|
||||
return res
|
||||
|
||||
async def generate_reading_passage(self, passage: int, topic: Optional[str], word_count: Optional[int]):
|
||||
return await self._service.generate_reading_passage(passage, topic, word_count)
|
||||
|
||||
async def generate_reading_exercises(self, dto: ReadingDTO):
|
||||
return await self._service.generate_reading_exercises(dto)
|
||||
24
ielts_be/controllers/impl/exam/speaking.py
Normal file
24
ielts_be/controllers/impl/exam/speaking.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
|
||||
from ielts_be.controllers import ISpeakingController
|
||||
from ielts_be.services import ISpeakingService, IVideoGeneratorService
|
||||
|
||||
|
||||
class SpeakingController(ISpeakingController):
|
||||
|
||||
def __init__(self, speaking_service: ISpeakingService, vid_gen: IVideoGeneratorService):
|
||||
self._service = speaking_service
|
||||
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):
|
||||
return await self._service.get_speaking_part(task, topic, second_topic, difficulty)
|
||||
|
||||
async def get_avatars(self):
|
||||
return await self._vid_gen.get_avatars()
|
||||
|
||||
async def generate_video(self, text: str, avatar: str):
|
||||
return await self._vid_gen.create_video(text, avatar)
|
||||
|
||||
async def poll_video(self, vid_id: str):
|
||||
return await self._vid_gen.poll_status(vid_id)
|
||||
19
ielts_be/controllers/impl/exam/writing.py
Normal file
19
ielts_be/controllers/impl/exam/writing.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from fastapi import UploadFile, HTTPException
|
||||
|
||||
from ielts_be.controllers import IWritingController
|
||||
from ielts_be.services 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):
|
||||
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):
|
||||
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)
|
||||
116
ielts_be/controllers/impl/grade.py
Normal file
116
ielts_be/controllers/impl/grade.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import BackgroundTasks, Response, HTTPException
|
||||
from fastapi.datastructures import FormData
|
||||
|
||||
from ielts_be.controllers import IGradeController
|
||||
from ielts_be.services import IGradeService, IEvaluationService
|
||||
from ielts_be.dtos.evaluation import EvaluationType
|
||||
from ielts_be.dtos.speaking import GradeSpeakingItem
|
||||
from ielts_be.dtos.writing import WritingGradeTaskDTO
|
||||
|
||||
class GradeController(IGradeController):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
grade_service: IGradeService,
|
||||
evaluation_service: IEvaluationService,
|
||||
):
|
||||
self._service = grade_service
|
||||
self._evaluation_service = evaluation_service
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
async def grade_writing_task(
|
||||
self,
|
||||
task: int, dto: WritingGradeTaskDTO, background_tasks: BackgroundTasks
|
||||
):
|
||||
if task == 1 and dto.type == "academic" and dto.attachment is None:
|
||||
raise HTTPException(status_code=400, detail="Academic writing task requires a picture!")
|
||||
|
||||
await self._evaluation_service.create_evaluation(
|
||||
dto.userId, dto.sessionId, dto.exerciseId, EvaluationType.WRITING, task
|
||||
)
|
||||
|
||||
await self._evaluation_service.begin_evaluation(
|
||||
dto.userId, dto.sessionId, task, dto.exerciseId, EvaluationType.WRITING, dto, background_tasks
|
||||
)
|
||||
|
||||
return Response(status_code=200)
|
||||
|
||||
async def grade_speaking_task(self, task: int, form: FormData, background_tasks: BackgroundTasks):
|
||||
answers: Dict[int, Dict] = {}
|
||||
user_id = form.get("userId")
|
||||
session_id = form.get("sessionId")
|
||||
exercise_id = form.get("exerciseId")
|
||||
|
||||
if not session_id or not exercise_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Fields sessionId and exerciseId are required!"
|
||||
)
|
||||
|
||||
for key, value in form.items():
|
||||
if '_' not in key:
|
||||
continue
|
||||
|
||||
field_name, index = key.rsplit('_', 1)
|
||||
index = int(index)
|
||||
|
||||
if index not in answers:
|
||||
answers[index] = {}
|
||||
|
||||
if field_name == 'question':
|
||||
answers[index]['question'] = value
|
||||
elif field_name == 'audio':
|
||||
answers[index]['answer'] = value
|
||||
|
||||
for i, answer in answers.items():
|
||||
if 'question' not in answer or 'answer' not in answer:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Incomplete data for answer {i}. Both question and audio required."
|
||||
)
|
||||
|
||||
items = [
|
||||
GradeSpeakingItem(
|
||||
question=answers[i]['question'],
|
||||
answer=answers[i]['answer']
|
||||
)
|
||||
for i in sorted(answers.keys())
|
||||
]
|
||||
|
||||
ex_type = EvaluationType.SPEAKING if task == 2 else EvaluationType.SPEAKING_INTERACTIVE
|
||||
|
||||
await self._evaluation_service.create_evaluation(
|
||||
user_id, session_id, exercise_id, ex_type, task
|
||||
)
|
||||
|
||||
await self._evaluation_service.begin_evaluation(
|
||||
user_id, session_id, task, exercise_id, ex_type, items, background_tasks
|
||||
)
|
||||
|
||||
return Response(status_code=200)
|
||||
|
||||
async def grade_short_answers(self, data: Dict):
|
||||
return await self._service.grade_short_answers(data)
|
||||
|
||||
async def grading_summary(self, data: Dict):
|
||||
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)
|
||||
|
||||
@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']
|
||||
)
|
||||
)
|
||||
|
||||
17
ielts_be/controllers/impl/training.py
Normal file
17
ielts_be/controllers/impl/training.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import Dict
|
||||
|
||||
from ielts_be.controllers import ITrainingController
|
||||
from ielts_be.services import ITrainingService
|
||||
from ielts_be.dtos.training import FetchTipsDTO
|
||||
|
||||
|
||||
class TrainingController(ITrainingController):
|
||||
|
||||
def __init__(self, training_service: ITrainingService):
|
||||
self._service = training_service
|
||||
|
||||
async def fetch_tips(self, data: FetchTipsDTO):
|
||||
return await self._service.fetch_tips(data.context, data.question, data.answer, data.correct_answer)
|
||||
|
||||
async def get_training_content(self, data: Dict):
|
||||
return await self._service.get_training_content(data)
|
||||
12
ielts_be/controllers/impl/user.py
Normal file
12
ielts_be/controllers/impl/user.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from ielts_be.controllers import IUserController
|
||||
from ielts_be.services import IUserService
|
||||
from ielts_be.dtos.user_batch import BatchUsersDTO
|
||||
|
||||
|
||||
class UserController(IUserController):
|
||||
|
||||
def __init__(self, user_service: IUserService):
|
||||
self._service = user_service
|
||||
|
||||
async def batch_import(self, batch: BatchUsersDTO):
|
||||
return await self._service.batch_users(batch)
|
||||
Reference in New Issue
Block a user