Files
Talal Sharabi f5b627256f EnCoach Odoo 19 custom modules
Full backend implementation with custom Odoo modules:
- encoach_api: Core API, user management, JWT auth
- encoach_exam: Exam generation (reading, writing, listening, speaking)
- encoach_evaluate: AI-powered evaluation (writing, speaking)
- encoach_training: Training tips and walkthrough
- encoach_storage: File storage management
- encoach_payment: Stripe, PayPal, Paymob integration
- encoach_mail: Email notifications

Made-with: Cursor
2026-03-14 16:46:46 +04:00

353 lines
14 KiB
Python

import json
import logging
import random
from odoo.addons.encoach_ai.models.constants import (
CEFR_LEVELS,
GPT_MODELS,
TEMPERATURE,
TOPICS,
)
from odoo.addons.encoach_ai.services.openai_service import EncoachOpenAIService
_logger = logging.getLogger(__name__)
PASSAGE_DIFFICULTY = {
1: "fairly easy and consist of multiple paragraphs",
2: "fairly hard and consist of multiple paragraphs",
3: (
"very hard, present different points or theories, "
"cite different sources, and consist of multiple paragraphs"
),
}
LISTENING_SECTION_CONFIG = {
1: {
"type": "conversation",
"format": '{"conversation": [{"name": "name", "gender": "gender", "text": "text"}]}',
"prompt": (
"Compose an authentic conversation between two individuals "
'on the topic of "{topic}". Please include random names and genders. '
"Include misleading discourse (dates, colors, etc.) and spelling of names. "
"Make sure that the generated conversation does not contain "
"forbidden subjects in muslim countries."
),
},
2: {
"type": "monologue",
"format": '{"monologue": "monologue"}',
"prompt": (
"Generate a comprehensive monologue set in the social context "
'of "{topic}". Make sure that the generated monologue does not '
"contain forbidden subjects in muslim countries."
),
},
3: {
"type": "conversation",
"format": '{"conversation": [{"name": "name", "gender": "gender", "text": "text"}]}',
"prompt": (
"Compose an authentic and elaborate conversation between "
'up to four individuals on the topic of "{topic}". '
"Please include random names and genders. Make sure that the "
"generated conversation does not contain forbidden subjects "
"in muslim countries."
),
},
4: {
"type": "monologue",
"format": '{"monologue": "monologue"}',
"prompt": (
"Generate a comprehensive and complex monologue on the academic "
'subject of "{topic}". Make sure that the generated monologue '
"does not contain forbidden subjects in muslim countries."
),
},
}
class EncoachGenerationService:
def __init__(self, env):
self.env = env
self.ai = EncoachOpenAIService(env)
# ------------------------------------------------------------------
# Reading
# ------------------------------------------------------------------
def generate_reading_passage(self, passage_num, topic=None, word_count=500):
"""Generate an IELTS reading passage (passage 1, 2, or 3)."""
topic = topic or random.choice(TOPICS)
difficulty = PASSAGE_DIFFICULTY.get(passage_num, PASSAGE_DIFFICULTY[1])
system_msg = (
"You are a helpful assistant designed to output JSON on this format: "
'{"title": "title of the text", "text": "generated text"}'
)
user_msg = (
f"Generate an extensive text for IELTS Reading Passage {passage_num}, "
f'of at least {word_count} words, on the topic of "{topic}". '
"The passage should offer a substantial amount of information "
f"relevant to the chosen subject matter. It should be {difficulty}. "
"Make sure that the generated text does not contain forbidden "
"subjects in muslim countries."
)
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
fields_to_check=["title", "text"],
)
def generate_reading_exercises(self, text, exercises_config, difficulty):
"""Generate exercises for a reading passage.
exercises_config: list of dicts with 'type' and 'quantity' keys.
difficulty: CEFR level string.
"""
system_msg = (
"You are a helpful assistant designed to output JSON. "
"Generate reading comprehension exercises based on the given text."
)
config_str = json.dumps(exercises_config)
user_msg = (
f"Based on the following text, generate exercises according to this "
f"configuration: {config_str}. Target CEFR level: {difficulty}.\n\n"
f'Text: "{text}"'
)
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
)
# ------------------------------------------------------------------
# Listening
# ------------------------------------------------------------------
def generate_listening_dialog(self, section, topic=None, difficulty=None):
"""Generate a listening dialog/monologue for the given section (1-4)."""
topic = topic or random.choice(TOPICS)
config = LISTENING_SECTION_CONFIG.get(section, LISTENING_SECTION_CONFIG[1])
system_msg = (
"You are a helpful assistant designed to output JSON on this format: "
+ config["format"]
)
user_msg = config["prompt"].format(topic=topic)
if difficulty:
user_msg += f" Target CEFR level: {difficulty}."
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
)
def generate_listening_exercises(self, text, exercises_config, difficulty):
"""Generate exercises for a listening transcript."""
system_msg = (
"You are a helpful assistant designed to output JSON. "
"Generate listening comprehension exercises based on the given transcript."
)
config_str = json.dumps(exercises_config)
user_msg = (
f"Based on the following transcript, generate exercises according to "
f"this configuration: {config_str}. Target CEFR level: {difficulty}.\n\n"
f'Transcript: "{text}"'
)
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
)
# ------------------------------------------------------------------
# Writing
# ------------------------------------------------------------------
def generate_writing_task(
self, task, topic=None, difficulty=None, task_type="general"
):
"""Generate a writing task prompt.
task: 1 or 2
task_type: 'general' or 'academic'
"""
topic = topic or random.choice(TOPICS)
difficulty = difficulty or random.choice(CEFR_LEVELS)
system_msg = (
"You are a helpful assistant designed to output JSON on this format: "
'{"question": "the generated writing task prompt"}'
)
if task == 1 and task_type == "general":
user_msg = (
"Craft a prompt for an IELTS Writing Task 1 General Training "
"exercise that instructs the student to compose a letter based "
f'on the topic of "{topic}" of {difficulty} CEFR level difficulty. '
'The prompt should end with "In the letter you should" followed '
"by 3 bullet points. Make sure it does not contain forbidden "
"subjects in muslim countries."
)
elif task == 2:
user_msg = (
f"Craft a comprehensive question of {difficulty} CEFR level "
"difficulty like the ones for IELTS Writing Task 2 General "
"Training that directs the candidate to delve into an in-depth "
"analysis of contrasting perspectives on the topic of "
f'"{topic}". The question should lead to an answer with either '
'"theories", "complicated information" or be "very descriptive" '
"on the topic."
)
else:
user_msg = (
"Analyze the uploaded image and create a detailed IELTS Writing "
"Task 1 Academic prompt. Describe the visual type, context, and "
f"create a prompt at {difficulty} CEFR level."
)
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
fields_to_check=["question"],
)
# ------------------------------------------------------------------
# Speaking
# ------------------------------------------------------------------
def generate_speaking_task(
self,
part,
topic=None,
first_topic=None,
second_topic=None,
difficulty=None,
):
"""Generate a speaking task for the given part (1, 2, or 3)."""
system_msg = (
"You are a helpful assistant designed to output JSON. "
"Generate IELTS speaking task questions."
)
if part == 1:
first_topic = first_topic or random.choice(TOPICS)
second_topic = second_topic or random.choice(TOPICS)
user_msg = (
"Craft 5 simple and single questions of easy difficulty for "
"IELTS Speaking Part 1 that encourages candidates to delve "
"deeply into personal experiences, preferences, or insights "
f'on the topic of "{first_topic}" and the topic of '
f'"{second_topic}". The questions should lead to the usage '
"of 4 verb tenses (present perfect, present, past and future). "
"Make sure that the generated question does not contain "
"forbidden subjects in muslim countries."
)
elif part == 2:
topic = topic or random.choice(TOPICS)
user_msg = (
"Create a question of medium difficulty for IELTS Speaking "
"Part 2 that encourages candidates to narrate a personal "
f'experience or story related to the topic of "{topic}". '
"Include 3 prompts that guide the candidate. The prompts "
"must not be questions. Also include a suffix like the ones "
'in the IELTS exams that start with "And explain why". '
"Make sure that the generated question does not contain "
"forbidden subjects in muslim countries."
)
else:
topic = topic or random.choice(TOPICS)
user_msg = (
"Formulate a set of 5 single questions of hard difficulty "
"for IELTS Speaking Part 3 that encourage candidates to "
"engage in a meaningful discussion on the topic of "
f'"{topic}". They must be 1 single question each and not '
"be double-barreled questions. Make sure that the generated "
"question does not contain forbidden subjects in muslim countries."
)
if difficulty:
user_msg += f" Target CEFR level: {difficulty}."
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
)
# ------------------------------------------------------------------
# Level exercises
# ------------------------------------------------------------------
def generate_level_exercises(self, exercises_config, difficulty):
"""Generate level-exam exercises (multiple choice, fill-blanks, etc.).
exercises_config: list of dicts with 'type', 'quantity', and optional
'topic', 'size' keys.
difficulty: CEFR level string.
"""
system_msg = (
"You are a helpful assistant designed to output JSON. "
"Generate English level exam exercises."
)
prompts = []
for cfg in exercises_config:
ex_type = cfg.get("type", "multiple_choice")
quantity = cfg.get("quantity", 5)
topic = cfg.get("topic") or random.choice(TOPICS)
size = cfg.get("size", 200)
if ex_type == "multiple_choice":
prompts.append(
f"Generate {quantity} multiple choice questions of 4 options "
f"for an english level exam of {difficulty} CEFR level. "
"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."
)
elif ex_type == "fill_blanks":
prompts.append(
f"Generate a text of at least {size} words about the topic "
f"{topic}. From the generated text choose exactly {quantity} "
"words (cannot be sequential words), replace each with "
"{id}, and generate a JSON object containing: the modified "
"text, solutions array, words array with four options per blank."
)
user_msg = "\n\n".join(prompts)
return self.ai.prediction(
model=GPT_MODELS["generation"],
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=TEMPERATURE["generation"],
)