diff --git a/.gitignore b/.gitignore deleted file mode 100644 index dc5805fb..00000000 --- a/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -__pycache__/ -*.pyc -*.pyo -.env -*.egg-info/ -.vscode/ -.idea/ -*.swp -*.swo diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..d805796e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM odoo:19.0 + +USER root + +COPY requirements.txt /tmp/requirements.txt +RUN pip3 install --no-cache-dir -r /tmp/requirements.txt + +COPY custom_addons /opt/odoo/custom_addons +COPY odoo.conf /etc/odoo/odoo.conf + +USER odoo + +EXPOSE 8069 8072 diff --git a/custom_addons/clarity_backend_theme_bits/__pycache__/__init__.cpython-312.pyc b/custom_addons/clarity_backend_theme_bits/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..119e0fa0 Binary files /dev/null and b/custom_addons/clarity_backend_theme_bits/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/clarity_backend_theme_bits/controller/__pycache__/__init__.cpython-312.pyc b/custom_addons/clarity_backend_theme_bits/controller/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..43579608 Binary files /dev/null and b/custom_addons/clarity_backend_theme_bits/controller/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/clarity_backend_theme_bits/controller/__pycache__/main.cpython-312.pyc b/custom_addons/clarity_backend_theme_bits/controller/__pycache__/main.cpython-312.pyc new file mode 100644 index 00000000..72b57c84 Binary files /dev/null and b/custom_addons/clarity_backend_theme_bits/controller/__pycache__/main.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_adaptive/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..36d80de1 Binary files /dev/null and b/custom_addons/encoach_adaptive/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_adaptive/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..427f9727 Binary files /dev/null and b/custom_addons/encoach_adaptive/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/controllers/__pycache__/adaptive.cpython-312.pyc b/custom_addons/encoach_adaptive/controllers/__pycache__/adaptive.cpython-312.pyc new file mode 100644 index 00000000..c635214f Binary files /dev/null and b/custom_addons/encoach_adaptive/controllers/__pycache__/adaptive.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/controllers/adaptive.py b/custom_addons/encoach_adaptive/controllers/adaptive.py index 18bbc6dc..4cb0b32b 100644 --- a/custom_addons/encoach_adaptive/controllers/adaptive.py +++ b/custom_addons/encoach_adaptive/controllers/adaptive.py @@ -27,14 +27,33 @@ class EncoachAdaptiveController(http.Controller): from odoo.fields import Datetime as DT today_start = DT.now().replace(hour=0, minute=0, second=0, microsecond=0) - total_students = len(Path.search([]).mapped('student_id')) - active_courses = len(Path.search([]).mapped('course_id').filtered(lambda c: c)) + all_paths = Path.search([]) + total_students = len(all_paths.mapped('student_id')) + active_courses = len(all_paths.mapped('course_id').filtered(lambda c: c)) signals_today = Event.search_count([ ('event_type', '=', 'signal'), ('created_at', '>=', today_start), ]) + avg_progress = 0.0 + if all_paths: + progress_values = [] + for p in all_paths: + try: + module_queue = json.loads(p.module_queue or '[]') + except (json.JSONDecodeError, TypeError): + module_queue = [] + total_modules = len(module_queue) if module_queue else 1 + completed = sum( + 1 for m in module_queue + if isinstance(m, dict) and m.get('done') + ) + progress_values.append( + round(completed / total_modules * 100, 1) if total_modules else 0.0 + ) + avg_progress = round(sum(progress_values) / len(progress_values), 1) if progress_values else 0.0 + recent_decisions = [] decisions = Event.search( [('event_type', '=', 'decision')], @@ -52,7 +71,7 @@ class EncoachAdaptiveController(http.Controller): return _json_response({ 'total_students': total_students, 'active_courses': active_courses, - 'avg_progress': 0.0, + 'avg_progress': avg_progress, 'signals_today': signals_today, 'recent_decisions': recent_decisions, }) diff --git a/custom_addons/encoach_adaptive/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..09915cee Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/adaptive_event.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/adaptive_event.cpython-312.pyc new file mode 100644 index 00000000..3e181ac6 Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/adaptive_event.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/adaptive_path.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/adaptive_path.cpython-312.pyc new file mode 100644 index 00000000..ac5b845b Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/adaptive_path.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/adaptive_settings.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/adaptive_settings.cpython-312.pyc new file mode 100644 index 00000000..8c4b34db Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/adaptive_settings.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/content_cache.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/content_cache.cpython-312.pyc new file mode 100644 index 00000000..d6a4ef21 Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/content_cache.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/diagnostic_session.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/diagnostic_session.cpython-312.pyc new file mode 100644 index 00000000..1f2426d0 Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/diagnostic_session.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/learning_plan.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/learning_plan.cpython-312.pyc new file mode 100644 index 00000000..f57484c2 Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/learning_plan.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/learning_plan_item.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/learning_plan_item.cpython-312.pyc new file mode 100644 index 00000000..b59fe392 Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/learning_plan_item.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/models/__pycache__/proficiency.cpython-312.pyc b/custom_addons/encoach_adaptive/models/__pycache__/proficiency.cpython-312.pyc new file mode 100644 index 00000000..c19304c9 Binary files /dev/null and b/custom_addons/encoach_adaptive/models/__pycache__/proficiency.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_adaptive/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..ce456d38 Binary files /dev/null and b/custom_addons/encoach_adaptive/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/services/__pycache__/adaptive_engine.cpython-312.pyc b/custom_addons/encoach_adaptive/services/__pycache__/adaptive_engine.cpython-312.pyc new file mode 100644 index 00000000..4b4c0fc6 Binary files /dev/null and b/custom_addons/encoach_adaptive/services/__pycache__/adaptive_engine.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/services/__pycache__/alert_service.cpython-312.pyc b/custom_addons/encoach_adaptive/services/__pycache__/alert_service.cpython-312.pyc new file mode 100644 index 00000000..698c75ae Binary files /dev/null and b/custom_addons/encoach_adaptive/services/__pycache__/alert_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_adaptive/services/__pycache__/style_matcher.cpython-312.pyc b/custom_addons/encoach_adaptive/services/__pycache__/style_matcher.cpython-312.pyc new file mode 100644 index 00000000..7e93ce73 Binary files /dev/null and b/custom_addons/encoach_adaptive/services/__pycache__/style_matcher.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/__manifest__.py b/custom_addons/encoach_ai/__manifest__.py index 2e1b91e8..1535e00d 100644 --- a/custom_addons/encoach_ai/__manifest__.py +++ b/custom_addons/encoach_ai/__manifest__.py @@ -16,6 +16,9 @@ """, "author": "EnCoach", "depends": ["base", "encoach_core"], + "external_dependencies": { + "python": ["openai", "boto3"], + }, "data": [ "security/ir.model.access.csv", "views/ai_settings_views.xml", diff --git a/custom_addons/encoach_ai/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..d3678e69 Binary files /dev/null and b/custom_addons/encoach_ai/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..6f97ccef Binary files /dev/null and b/custom_addons/encoach_ai/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/controllers/__pycache__/ai_controller.cpython-312.pyc b/custom_addons/encoach_ai/controllers/__pycache__/ai_controller.cpython-312.pyc new file mode 100644 index 00000000..c8a0af46 Binary files /dev/null and b/custom_addons/encoach_ai/controllers/__pycache__/ai_controller.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/controllers/__pycache__/coach_controller.cpython-312.pyc b/custom_addons/encoach_ai/controllers/__pycache__/coach_controller.cpython-312.pyc new file mode 100644 index 00000000..a70bd2c0 Binary files /dev/null and b/custom_addons/encoach_ai/controllers/__pycache__/coach_controller.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/controllers/__pycache__/media_controller.cpython-312.pyc b/custom_addons/encoach_ai/controllers/__pycache__/media_controller.cpython-312.pyc new file mode 100644 index 00000000..1b7a5d79 Binary files /dev/null and b/custom_addons/encoach_ai/controllers/__pycache__/media_controller.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/controllers/media_controller.py b/custom_addons/encoach_ai/controllers/media_controller.py index df152ac9..e470cb23 100644 --- a/custom_addons/encoach_ai/controllers/media_controller.py +++ b/custom_addons/encoach_ai/controllers/media_controller.py @@ -125,54 +125,6 @@ class MediaController(http.Controller): except Exception as e: return _json_response({"video_id": video_id, "status": "error", "error": str(e)}) - # ── POST /api/placement/speaking-upload — transcribe speaking audio ── - @http.route("/api/placement/speaking-upload", type="http", auth="user", methods=["POST"], csrf=False) - def speaking_upload(self, **kw): - try: - audio_file = request.httprequest.files.get("audio") - if not audio_file: - return _json_response({"error": "No audio file"}, 400) - audio_data = audio_file.read() - from odoo.addons.encoach_ai.services.whisper_service import WhisperService - whisper = WhisperService(request.env) - transcript = whisper.transcribe(audio_data, use_api=True) - - from odoo.addons.encoach_ai.services.openai_service import OpenAIService - ai = OpenAIService(request.env) - grade = ai.grade_speaking("IELTS Speaking Band Descriptors", transcript["text"]) - - return _json_response({ - "transcript": transcript["text"], - "scores": grade.get("scores", {}), - "overall_band": grade.get("overall_band", 0), - "feedback": grade.get("feedback", ""), - "status": "completed", - }) - except Exception as e: - _logger.exception("Speaking upload failed") - return _json_response({"status": "error", "error": str(e)}, 500) - - # ── GET /api/placement/speaking-status — poll speaking evaluation ── - @http.route("/api/placement/speaking-status", type="http", auth="user", methods=["GET"], csrf=False) - def speaking_status(self, **kw): - try: - AiLog = request.env.get("encoach.ai.log") - if AiLog: - log = AiLog.sudo().search([ - ("action", "=", "grade_speaking"), - ("create_uid", "=", request.env.uid), - ], limit=1, order="create_date desc") - if log: - return _json_response({ - "status": log.status or "completed", - "log_id": log.id, - "latency_ms": log.latency_ms, - "created_at": log.create_date.isoformat() if log.create_date else "", - }) - return _json_response({"status": "completed"}) - except Exception: - return _json_response({"status": "completed"}) - # ── POST /api/courses/ai-generate — AiCreationAssistant.tsx ── @http.route("/api/courses/ai-generate", type="http", auth="user", methods=["POST"], csrf=False) def ai_generate_course(self, **kw): diff --git a/custom_addons/encoach_ai/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4664ced0 Binary files /dev/null and b/custom_addons/encoach_ai/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/models/__pycache__/ai_log.cpython-312.pyc b/custom_addons/encoach_ai/models/__pycache__/ai_log.cpython-312.pyc new file mode 100644 index 00000000..5b634c65 Binary files /dev/null and b/custom_addons/encoach_ai/models/__pycache__/ai_log.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/models/__pycache__/ai_settings.cpython-312.pyc b/custom_addons/encoach_ai/models/__pycache__/ai_settings.cpython-312.pyc new file mode 100644 index 00000000..409a5e14 Binary files /dev/null and b/custom_addons/encoach_ai/models/__pycache__/ai_settings.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..463c1c4c Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/coach_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/coach_service.cpython-312.pyc new file mode 100644 index 00000000..9013520f Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/coach_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/elai_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/elai_service.cpython-312.pyc new file mode 100644 index 00000000..5630477d Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/elai_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/elevenlabs_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/elevenlabs_service.cpython-312.pyc new file mode 100644 index 00000000..a6b05bd8 Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/elevenlabs_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/gptzero_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/gptzero_service.cpython-312.pyc new file mode 100644 index 00000000..9de34fae Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/gptzero_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/openai_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/openai_service.cpython-312.pyc new file mode 100644 index 00000000..d060a965 Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/openai_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/polly_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/polly_service.cpython-312.pyc new file mode 100644 index 00000000..27f98d1f Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/polly_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai/services/__pycache__/whisper_service.cpython-312.pyc b/custom_addons/encoach_ai/services/__pycache__/whisper_service.cpython-312.pyc new file mode 100644 index 00000000..31a17369 Binary files /dev/null and b/custom_addons/encoach_ai/services/__pycache__/whisper_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai_course/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..6ba89960 Binary files /dev/null and b/custom_addons/encoach_ai_course/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai_course/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..065e24cf Binary files /dev/null and b/custom_addons/encoach_ai_course/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/controllers/__pycache__/ai_course.cpython-312.pyc b/custom_addons/encoach_ai_course/controllers/__pycache__/ai_course.cpython-312.pyc new file mode 100644 index 00000000..0a384740 Binary files /dev/null and b/custom_addons/encoach_ai_course/controllers/__pycache__/ai_course.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai_course/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..eab880a6 Binary files /dev/null and b/custom_addons/encoach_ai_course/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/models/__pycache__/ai_generation_log.cpython-312.pyc b/custom_addons/encoach_ai_course/models/__pycache__/ai_generation_log.cpython-312.pyc new file mode 100644 index 00000000..0cc580c4 Binary files /dev/null and b/custom_addons/encoach_ai_course/models/__pycache__/ai_generation_log.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/models/__pycache__/ai_ielts_generation_log.cpython-312.pyc b/custom_addons/encoach_ai_course/models/__pycache__/ai_ielts_generation_log.cpython-312.pyc new file mode 100644 index 00000000..f1ff610e Binary files /dev/null and b/custom_addons/encoach_ai_course/models/__pycache__/ai_ielts_generation_log.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ai_course/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..86fa689f Binary files /dev/null and b/custom_addons/encoach_ai_course/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/services/__pycache__/english_pipeline.cpython-312.pyc b/custom_addons/encoach_ai_course/services/__pycache__/english_pipeline.cpython-312.pyc new file mode 100644 index 00000000..bfe76af3 Binary files /dev/null and b/custom_addons/encoach_ai_course/services/__pycache__/english_pipeline.cpython-312.pyc differ diff --git a/custom_addons/encoach_ai_course/services/__pycache__/ielts_pipeline.cpython-312.pyc b/custom_addons/encoach_ai_course/services/__pycache__/ielts_pipeline.cpython-312.pyc new file mode 100644 index 00000000..35874d0d Binary files /dev/null and b/custom_addons/encoach_ai_course/services/__pycache__/ielts_pipeline.cpython-312.pyc differ diff --git a/custom_addons/encoach_api/__manifest__.py b/custom_addons/encoach_api/__manifest__.py index 574513ef..e9a6fd0d 100644 --- a/custom_addons/encoach_api/__manifest__.py +++ b/custom_addons/encoach_api/__manifest__.py @@ -5,6 +5,9 @@ 'summary': 'Base controller utilities (JWT auth, response helpers) for EnCoach REST API', 'author': 'EnCoach', 'depends': ['base', 'encoach_core'], + 'external_dependencies': { + 'python': ['PyJWT'], + }, 'data': [], 'installable': True, 'license': 'LGPL-3', diff --git a/custom_addons/encoach_api/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_api/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..a15491bd Binary files /dev/null and b/custom_addons/encoach_api/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_api/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_api/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..6056dc00 Binary files /dev/null and b/custom_addons/encoach_api/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_api/controllers/__pycache__/auth.cpython-312.pyc b/custom_addons/encoach_api/controllers/__pycache__/auth.cpython-312.pyc new file mode 100644 index 00000000..9c047dc6 Binary files /dev/null and b/custom_addons/encoach_api/controllers/__pycache__/auth.cpython-312.pyc differ diff --git a/custom_addons/encoach_api/controllers/__pycache__/base.cpython-312.pyc b/custom_addons/encoach_api/controllers/__pycache__/base.cpython-312.pyc new file mode 100644 index 00000000..b84fd07e Binary files /dev/null and b/custom_addons/encoach_api/controllers/__pycache__/base.cpython-312.pyc differ diff --git a/custom_addons/encoach_branding/__manifest__.py b/custom_addons/encoach_branding/__manifest__.py index b7f3c0c8..5e70d443 100644 --- a/custom_addons/encoach_branding/__manifest__.py +++ b/custom_addons/encoach_branding/__manifest__.py @@ -4,7 +4,7 @@ 'category': 'Education', 'summary': 'Whitelabeling and custom branding per entity', 'author': 'EnCoach', - 'depends': ['encoach_core'], + 'depends': ['encoach_core', 'encoach_api'], 'data': [ 'security/ir.model.access.csv', 'views/branding_views.xml', diff --git a/custom_addons/encoach_branding/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_branding/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..970fb799 Binary files /dev/null and b/custom_addons/encoach_branding/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_branding/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_branding/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..10c5264a Binary files /dev/null and b/custom_addons/encoach_branding/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_branding/controllers/__pycache__/branding.cpython-312.pyc b/custom_addons/encoach_branding/controllers/__pycache__/branding.cpython-312.pyc new file mode 100644 index 00000000..2d40721c Binary files /dev/null and b/custom_addons/encoach_branding/controllers/__pycache__/branding.cpython-312.pyc differ diff --git a/custom_addons/encoach_branding/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_branding/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..67e50448 Binary files /dev/null and b/custom_addons/encoach_branding/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_branding/models/__pycache__/branding.cpython-312.pyc b/custom_addons/encoach_branding/models/__pycache__/branding.cpython-312.pyc new file mode 100644 index 00000000..83af282a Binary files /dev/null and b/custom_addons/encoach_branding/models/__pycache__/branding.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..35acb5d4 Binary files /dev/null and b/custom_addons/encoach_core/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/migrations/19.0.1.1/pre-migrate.py b/custom_addons/encoach_core/migrations/19.0.1.1/pre-migrate.py new file mode 100644 index 00000000..17156344 --- /dev/null +++ b/custom_addons/encoach_core/migrations/19.0.1.1/pre-migrate.py @@ -0,0 +1,13 @@ +"""Placeholder migration script for EnCoach Core v19.0.1.1. + +Add actual migration logic here when schema changes are introduced. +""" +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + _logger.info('EnCoach Core: pre-migrate from %s', version) diff --git a/custom_addons/encoach_core/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..9fce35a0 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/code.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/code.cpython-312.pyc new file mode 100644 index 00000000..00b10015 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/code.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/encoach_entity.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/encoach_entity.cpython-312.pyc new file mode 100644 index 00000000..a246b098 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/encoach_entity.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/encoach_invite_code.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/encoach_invite_code.cpython-312.pyc new file mode 100644 index 00000000..c91ca5bd Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/encoach_invite_code.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/encoach_permission.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/encoach_permission.cpython-312.pyc new file mode 100644 index 00000000..0a70503b Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/encoach_permission.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/encoach_role.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/encoach_role.cpython-312.pyc new file mode 100644 index 00000000..4a5ffe66 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/encoach_role.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/encoach_user.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/encoach_user.cpython-312.pyc new file mode 100644 index 00000000..9f0e13e6 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/encoach_user.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/entity_level_mapping.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/entity_level_mapping.cpython-312.pyc new file mode 100644 index 00000000..171e3861 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/entity_level_mapping.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/invite.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/invite.cpython-312.pyc new file mode 100644 index 00000000..99216aaa Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/invite.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/permission.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/permission.cpython-312.pyc new file mode 100644 index 00000000..04faf1e6 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/permission.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/role.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/role.cpython-312.pyc new file mode 100644 index 00000000..0b8d51fd Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/role.cpython-312.pyc differ diff --git a/custom_addons/encoach_core/models/__pycache__/user_entity_rel.cpython-312.pyc b/custom_addons/encoach_core/models/__pycache__/user_entity_rel.cpython-312.pyc new file mode 100644 index 00000000..38f20051 Binary files /dev/null and b/custom_addons/encoach_core/models/__pycache__/user_entity_rel.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_course_gen/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..072db1d1 Binary files /dev/null and b/custom_addons/encoach_course_gen/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_course_gen/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..8aac14f9 Binary files /dev/null and b/custom_addons/encoach_course_gen/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/controllers/__pycache__/course.cpython-312.pyc b/custom_addons/encoach_course_gen/controllers/__pycache__/course.cpython-312.pyc new file mode 100644 index 00000000..d2baf7ba Binary files /dev/null and b/custom_addons/encoach_course_gen/controllers/__pycache__/course.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_course_gen/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..14d439a0 Binary files /dev/null and b/custom_addons/encoach_course_gen/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/models/__pycache__/course_extension.cpython-312.pyc b/custom_addons/encoach_course_gen/models/__pycache__/course_extension.cpython-312.pyc new file mode 100644 index 00000000..286ebcd9 Binary files /dev/null and b/custom_addons/encoach_course_gen/models/__pycache__/course_extension.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/models/__pycache__/course_module.cpython-312.pyc b/custom_addons/encoach_course_gen/models/__pycache__/course_module.cpython-312.pyc new file mode 100644 index 00000000..9af5bd4d Binary files /dev/null and b/custom_addons/encoach_course_gen/models/__pycache__/course_module.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/models/__pycache__/course_section.cpython-312.pyc b/custom_addons/encoach_course_gen/models/__pycache__/course_section.cpython-312.pyc new file mode 100644 index 00000000..a2356227 Binary files /dev/null and b/custom_addons/encoach_course_gen/models/__pycache__/course_section.cpython-312.pyc differ diff --git a/custom_addons/encoach_course_gen/models/__pycache__/gap_profile.cpython-312.pyc b/custom_addons/encoach_course_gen/models/__pycache__/gap_profile.cpython-312.pyc new file mode 100644 index 00000000..ab9b300f Binary files /dev/null and b/custom_addons/encoach_course_gen/models/__pycache__/gap_profile.cpython-312.pyc differ diff --git a/custom_addons/encoach_entity_onboard/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_entity_onboard/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..93fc161d Binary files /dev/null and b/custom_addons/encoach_entity_onboard/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_entity_onboard/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_entity_onboard/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..04e4a7ce Binary files /dev/null and b/custom_addons/encoach_entity_onboard/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_entity_onboard/controllers/__pycache__/entity_onboard.cpython-312.pyc b/custom_addons/encoach_entity_onboard/controllers/__pycache__/entity_onboard.cpython-312.pyc new file mode 100644 index 00000000..b21cd663 Binary files /dev/null and b/custom_addons/encoach_entity_onboard/controllers/__pycache__/entity_onboard.cpython-312.pyc differ diff --git a/custom_addons/encoach_entity_onboard/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_entity_onboard/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..57df094b Binary files /dev/null and b/custom_addons/encoach_entity_onboard/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_entity_onboard/services/__pycache__/credential_service.cpython-312.pyc b/custom_addons/encoach_entity_onboard/services/__pycache__/credential_service.cpython-312.pyc new file mode 100644 index 00000000..3c3bba29 Binary files /dev/null and b/custom_addons/encoach_entity_onboard/services/__pycache__/credential_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_entity_onboard/services/__pycache__/csv_parser.cpython-312.pyc b/custom_addons/encoach_entity_onboard/services/__pycache__/csv_parser.cpython-312.pyc new file mode 100644 index 00000000..5424bd94 Binary files /dev/null and b/custom_addons/encoach_entity_onboard/services/__pycache__/csv_parser.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_session/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_exam_session/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..c321f8e4 Binary files /dev/null and b/custom_addons/encoach_exam_session/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_session/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_exam_session/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..13aeab1c Binary files /dev/null and b/custom_addons/encoach_exam_session/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_session/controllers/__pycache__/exam_session.cpython-312.pyc b/custom_addons/encoach_exam_session/controllers/__pycache__/exam_session.cpython-312.pyc new file mode 100644 index 00000000..35b2ab00 Binary files /dev/null and b/custom_addons/encoach_exam_session/controllers/__pycache__/exam_session.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_exam_template/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..0f5d194e Binary files /dev/null and b/custom_addons/encoach_exam_template/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..b9b76159 Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/custom_exam.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/custom_exam.cpython-312.pyc new file mode 100644 index 00000000..670cba11 Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/custom_exam.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/exam_structures.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/exam_structures.cpython-312.pyc new file mode 100644 index 00000000..92b4cae4 Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/exam_structures.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/ielts_exam.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/ielts_exam.cpython-312.pyc new file mode 100644 index 00000000..7bed2f22 Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/ielts_exam.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/templates.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/templates.cpython-312.pyc new file mode 100644 index 00000000..f38f2f7b Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/templates.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4e68ed99 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/audio_file.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/audio_file.cpython-312.pyc new file mode 100644 index 00000000..bdcb34f8 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/audio_file.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/exam_assignment.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/exam_assignment.cpython-312.pyc new file mode 100644 index 00000000..510137f6 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/exam_assignment.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/exam_custom.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/exam_custom.cpython-312.pyc new file mode 100644 index 00000000..9be64d32 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/exam_custom.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/exam_custom_section.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/exam_custom_section.cpython-312.pyc new file mode 100644 index 00000000..e9815b20 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/exam_custom_section.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/exam_structure.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/exam_structure.cpython-312.pyc new file mode 100644 index 00000000..95c8ef16 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/exam_structure.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/exam_template.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/exam_template.cpython-312.pyc new file mode 100644 index 00000000..f92e04cc Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/exam_template.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/passage.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/passage.cpython-312.pyc new file mode 100644 index 00000000..fcbdbe77 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/passage.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/question.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/question.cpython-312.pyc new file mode 100644 index 00000000..0ad38fa4 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/question.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/rubric.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/rubric.cpython-312.pyc new file mode 100644 index 00000000..2a448d7c Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/rubric.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/speaking_card.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/speaking_card.cpython-312.pyc new file mode 100644 index 00000000..419a6048 Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/speaking_card.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/models/__pycache__/writing_prompt.cpython-312.pyc b/custom_addons/encoach_exam_template/models/__pycache__/writing_prompt.cpython-312.pyc new file mode 100644 index 00000000..ee4a61bf Binary files /dev/null and b/custom_addons/encoach_exam_template/models/__pycache__/writing_prompt.cpython-312.pyc differ diff --git a/custom_addons/encoach_ielts_validation/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ielts_validation/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..0febde4f Binary files /dev/null and b/custom_addons/encoach_ielts_validation/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ielts_validation/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_ielts_validation/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..3fa3406c Binary files /dev/null and b/custom_addons/encoach_ielts_validation/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_ielts_validation/services/__pycache__/validator.cpython-312.pyc b/custom_addons/encoach_ielts_validation/services/__pycache__/validator.cpython-312.pyc new file mode 100644 index 00000000..6b20b900 Binary files /dev/null and b/custom_addons/encoach_ielts_validation/services/__pycache__/validator.cpython-312.pyc differ diff --git a/custom_addons/encoach_it/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_it/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e9fe2573 Binary files /dev/null and b/custom_addons/encoach_it/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_it/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_it/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4b02f48f Binary files /dev/null and b/custom_addons/encoach_it/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_it/services/__pycache__/code_scorer.cpython-312.pyc b/custom_addons/encoach_it/services/__pycache__/code_scorer.cpython-312.pyc new file mode 100644 index 00000000..cebb3021 Binary files /dev/null and b/custom_addons/encoach_it/services/__pycache__/code_scorer.cpython-312.pyc differ diff --git a/custom_addons/encoach_math/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_math/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..a26aaae5 Binary files /dev/null and b/custom_addons/encoach_math/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_math/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_math/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e7a27c91 Binary files /dev/null and b/custom_addons/encoach_math/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_math/services/__pycache__/math_scorer.cpython-312.pyc b/custom_addons/encoach_math/services/__pycache__/math_scorer.cpython-312.pyc new file mode 100644 index 00000000..8e8c7c01 Binary files /dev/null and b/custom_addons/encoach_math/services/__pycache__/math_scorer.cpython-312.pyc differ diff --git a/custom_addons/encoach_pdf_report/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_pdf_report/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..03ecc329 Binary files /dev/null and b/custom_addons/encoach_pdf_report/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_pdf_report/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_pdf_report/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..1af9b63e Binary files /dev/null and b/custom_addons/encoach_pdf_report/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_pdf_report/controllers/__pycache__/reports.cpython-312.pyc b/custom_addons/encoach_pdf_report/controllers/__pycache__/reports.cpython-312.pyc new file mode 100644 index 00000000..5bd2aadc Binary files /dev/null and b/custom_addons/encoach_pdf_report/controllers/__pycache__/reports.cpython-312.pyc differ diff --git a/custom_addons/encoach_pdf_report/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_pdf_report/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..488f7800 Binary files /dev/null and b/custom_addons/encoach_pdf_report/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_pdf_report/services/__pycache__/pdf_generator.cpython-312.pyc b/custom_addons/encoach_pdf_report/services/__pycache__/pdf_generator.cpython-312.pyc new file mode 100644 index 00000000..429b0ddf Binary files /dev/null and b/custom_addons/encoach_pdf_report/services/__pycache__/pdf_generator.cpython-312.pyc differ diff --git a/custom_addons/encoach_pdf_report/services/pdf_generator.py b/custom_addons/encoach_pdf_report/services/pdf_generator.py index 4915fc78..349536e9 100644 --- a/custom_addons/encoach_pdf_report/services/pdf_generator.py +++ b/custom_addons/encoach_pdf_report/services/pdf_generator.py @@ -71,9 +71,9 @@ class PdfGenerator: score_header = ['Skill', 'Band Score'] score_rows = [score_header] for score in scores: - skill_name = getattr(score, 'skill_name', '') or getattr(score, 'name', 'N/A') - band = getattr(score, 'band_score', '') or getattr(score, 'score', 'N/A') - score_rows.append([str(skill_name), str(band)]) + skill_name = score.skill or 'N/A' + band = score.band_score if score.band_score else 'N/A' + score_rows.append([str(skill_name).capitalize(), str(band)]) if len(score_rows) > 1: score_table = Table(score_rows, colWidths=[8 * cm, 8 * cm]) diff --git a/custom_addons/encoach_placement/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_placement/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e88b4f69 Binary files /dev/null and b/custom_addons/encoach_placement/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_placement/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..a0388c7f Binary files /dev/null and b/custom_addons/encoach_placement/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/controllers/__pycache__/placement.cpython-312.pyc b/custom_addons/encoach_placement/controllers/__pycache__/placement.cpython-312.pyc new file mode 100644 index 00000000..3c8163a3 Binary files /dev/null and b/custom_addons/encoach_placement/controllers/__pycache__/placement.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/controllers/placement.py b/custom_addons/encoach_placement/controllers/placement.py index 1d182a1f..b80c4af2 100644 --- a/custom_addons/encoach_placement/controllers/placement.py +++ b/custom_addons/encoach_placement/controllers/placement.py @@ -303,18 +303,49 @@ class EncoachPlacementController(http.Controller): @jwt_required def speaking_status(self, **kw): try: + session_id = kw.get('session_id') upload_id = kw.get('upload_id') - if not upload_id: - return _error_response('upload_id is required', 400) - attachment = request.env['ir.attachment'].sudo().browse(int(upload_id)) - if not attachment.exists(): - return _error_response('Upload not found', 404) + if session_id: + session = request.env['encoach.cat.session'].sudo().browse(int(session_id)) + if not session.exists(): + return _error_response('Session not found', 404) - return _json_response({ - 'status': 'completed', - 'transcription': None, - }) + try: + from odoo.addons.encoach_ai.services.whisper_service import WhisperService + whisper = WhisperService(request.env) + + attachments = request.env['ir.attachment'].sudo().search([ + ('res_model', '=', 'encoach.cat.session'), + ('create_uid', '=', request.env.uid), + ], limit=1, order='create_date desc') + + if attachments and attachments.datas: + audio_data = base64.b64decode(attachments.datas) + transcript = whisper.transcribe(audio_data, use_api=True) + return _json_response({ + 'status': 'scored', + 'transcription': transcript.get('text', ''), + }) + except Exception as ai_err: + _logger.warning('Whisper transcription not available: %s', ai_err) + + return _json_response({ + 'status': 'processing', + 'transcription': None, + }) + + if upload_id: + attachment = request.env['ir.attachment'].sudo().browse(int(upload_id)) + if not attachment.exists(): + return _error_response('Upload not found', 404) + + return _json_response({ + 'status': 'processing', + 'transcription': None, + }) + + return _error_response('session_id or upload_id is required', 400) except Exception as e: _logger.exception('speaking status failed') diff --git a/custom_addons/encoach_placement/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_placement/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e8480f97 Binary files /dev/null and b/custom_addons/encoach_placement/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/models/__pycache__/cat_session.cpython-312.pyc b/custom_addons/encoach_placement/models/__pycache__/cat_session.cpython-312.pyc new file mode 100644 index 00000000..1e63fea9 Binary files /dev/null and b/custom_addons/encoach_placement/models/__pycache__/cat_session.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/models/__pycache__/student_ability.cpython-312.pyc b/custom_addons/encoach_placement/models/__pycache__/student_ability.cpython-312.pyc new file mode 100644 index 00000000..196d2006 Binary files /dev/null and b/custom_addons/encoach_placement/models/__pycache__/student_ability.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_placement/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e2066ed5 Binary files /dev/null and b/custom_addons/encoach_placement/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/services/__pycache__/cat_engine.cpython-312.pyc b/custom_addons/encoach_placement/services/__pycache__/cat_engine.cpython-312.pyc new file mode 100644 index 00000000..fd7e71da Binary files /dev/null and b/custom_addons/encoach_placement/services/__pycache__/cat_engine.cpython-312.pyc differ diff --git a/custom_addons/encoach_placement/services/__pycache__/cefr_mapper.cpython-312.pyc b/custom_addons/encoach_placement/services/__pycache__/cefr_mapper.cpython-312.pyc new file mode 100644 index 00000000..b117887d Binary files /dev/null and b/custom_addons/encoach_placement/services/__pycache__/cefr_mapper.cpython-312.pyc differ diff --git a/custom_addons/encoach_portal/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_portal/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..52fae4f3 Binary files /dev/null and b/custom_addons/encoach_portal/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_portal/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_portal/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..83f40bca Binary files /dev/null and b/custom_addons/encoach_portal/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_portal/controllers/__pycache__/portal.cpython-312.pyc b/custom_addons/encoach_portal/controllers/__pycache__/portal.cpython-312.pyc new file mode 100644 index 00000000..192ddf8d Binary files /dev/null and b/custom_addons/encoach_portal/controllers/__pycache__/portal.cpython-312.pyc differ diff --git a/custom_addons/encoach_quality_gate/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_quality_gate/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..6e506758 Binary files /dev/null and b/custom_addons/encoach_quality_gate/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_quality_gate/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_quality_gate/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..a2478ca3 Binary files /dev/null and b/custom_addons/encoach_quality_gate/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_quality_gate/models/__pycache__/ielts_standards_check.cpython-312.pyc b/custom_addons/encoach_quality_gate/models/__pycache__/ielts_standards_check.cpython-312.pyc new file mode 100644 index 00000000..5f395429 Binary files /dev/null and b/custom_addons/encoach_quality_gate/models/__pycache__/ielts_standards_check.cpython-312.pyc differ diff --git a/custom_addons/encoach_quality_gate/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_quality_gate/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e9f77ca4 Binary files /dev/null and b/custom_addons/encoach_quality_gate/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_quality_gate/services/__pycache__/content_gate.cpython-312.pyc b/custom_addons/encoach_quality_gate/services/__pycache__/content_gate.cpython-312.pyc new file mode 100644 index 00000000..de18c28a Binary files /dev/null and b/custom_addons/encoach_quality_gate/services/__pycache__/content_gate.cpython-312.pyc differ diff --git a/custom_addons/encoach_quality_gate/services/__pycache__/quality_checker.cpython-312.pyc b/custom_addons/encoach_quality_gate/services/__pycache__/quality_checker.cpython-312.pyc new file mode 100644 index 00000000..86a5458a Binary files /dev/null and b/custom_addons/encoach_quality_gate/services/__pycache__/quality_checker.cpython-312.pyc differ diff --git a/custom_addons/encoach_resources/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_resources/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..78503b81 Binary files /dev/null and b/custom_addons/encoach_resources/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_resources/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_resources/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..a3c53fba Binary files /dev/null and b/custom_addons/encoach_resources/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_resources/controllers/__pycache__/resources_rest.cpython-312.pyc b/custom_addons/encoach_resources/controllers/__pycache__/resources_rest.cpython-312.pyc new file mode 100644 index 00000000..4cdbbb91 Binary files /dev/null and b/custom_addons/encoach_resources/controllers/__pycache__/resources_rest.cpython-312.pyc differ diff --git a/custom_addons/encoach_resources/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_resources/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..c0793c6c Binary files /dev/null and b/custom_addons/encoach_resources/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_resources/models/__pycache__/resource.cpython-312.pyc b/custom_addons/encoach_resources/models/__pycache__/resource.cpython-312.pyc new file mode 100644 index 00000000..5e491e17 Binary files /dev/null and b/custom_addons/encoach_resources/models/__pycache__/resource.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_scoring/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..44b96489 Binary files /dev/null and b/custom_addons/encoach_scoring/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_scoring/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..d0e4b583 Binary files /dev/null and b/custom_addons/encoach_scoring/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/controllers/__pycache__/exam_session.cpython-312.pyc b/custom_addons/encoach_scoring/controllers/__pycache__/exam_session.cpython-312.pyc new file mode 100644 index 00000000..cc14c149 Binary files /dev/null and b/custom_addons/encoach_scoring/controllers/__pycache__/exam_session.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/controllers/__pycache__/grading.cpython-312.pyc b/custom_addons/encoach_scoring/controllers/__pycache__/grading.cpython-312.pyc new file mode 100644 index 00000000..9e4f1a1e Binary files /dev/null and b/custom_addons/encoach_scoring/controllers/__pycache__/grading.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/controllers/__pycache__/score_release.cpython-312.pyc b/custom_addons/encoach_scoring/controllers/__pycache__/score_release.cpython-312.pyc new file mode 100644 index 00000000..edfd648b Binary files /dev/null and b/custom_addons/encoach_scoring/controllers/__pycache__/score_release.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/controllers/exam_session.py b/custom_addons/encoach_scoring/controllers/exam_session.py index 87eec2de..7466c941 100644 --- a/custom_addons/encoach_scoring/controllers/exam_session.py +++ b/custom_addons/encoach_scoring/controllers/exam_session.py @@ -369,6 +369,41 @@ class EncoachExamSessionController(http.Controller): _logger.exception('submit failed') return _error_response(str(e), 500) + # ------------------------------------------------------------------ + # GET /api/exam//status + # ------------------------------------------------------------------ + @http.route('/api/exam//status', type='http', auth='none', + methods=['GET'], csrf=False) + @jwt_required + def get_status(self, exam_id, **kw): + try: + uid = request.env.user.id + Attempt = request.env['encoach.student.attempt'].sudo() + attempt = Attempt.search([ + ('student_id', '=', uid), + ('exam_id', '=', int(exam_id)), + ], limit=1, order='id desc') + + if not attempt: + return _json_response({ + 'status': 'not_started', + 'scores_available': False, + }) + + scores_available = attempt.status in ('released', 'scored') + + return _json_response({ + 'status': attempt.status, + 'scores_available': scores_available, + 'attempt_id': attempt.id, + 'completed_at': attempt.completed_at, + 'released_at': attempt.released_at, + }) + + except Exception as e: + _logger.exception('get_status failed') + return _error_response(str(e), 500) + # ------------------------------------------------------------------ # GET /api/exam//results # ------------------------------------------------------------------ diff --git a/custom_addons/encoach_scoring/controllers/grading.py b/custom_addons/encoach_scoring/controllers/grading.py index a37f4ca4..108dd147 100644 --- a/custom_addons/encoach_scoring/controllers/grading.py +++ b/custom_addons/encoach_scoring/controllers/grading.py @@ -24,9 +24,10 @@ def _band_to_cefr(band): def _recompute_bands(attempt): - """Recompute skill and overall bands after grading updates.""" + """Recompute skill and overall bands after grading updates, incorporating rubric sub-scores.""" Answer = request.env['encoach.student.answer'].sudo() Score = request.env['encoach.score'].sudo() + Feedback = request.env['encoach.feedback'].sudo() skill_totals = {} skill_max = {} @@ -36,6 +37,24 @@ def _recompute_bands(attempt): skill = q.skill or 'general' skill_totals.setdefault(skill, 0.0) skill_max.setdefault(skill, 0.0) + + fb = Feedback.search([ + ('attempt_id', '=', attempt.id), + ('question_id', '=', q.id), + ], limit=1, order='id desc') + + if fb and fb.rubric_scores: + try: + rubric_data = json.loads(fb.rubric_scores) + if isinstance(rubric_data, dict) and rubric_data: + rubric_avg = sum(float(v) for v in rubric_data.values()) / len(rubric_data) + rubric_score = (rubric_avg / 9.0) * (q.marks or 1.0) + skill_totals[skill] += rubric_score + skill_max[skill] += q.marks or 1.0 + continue + except (json.JSONDecodeError, TypeError, ValueError): + pass + skill_totals[skill] += ans.score or 0.0 skill_max[skill] += q.marks or 1.0 diff --git a/custom_addons/encoach_scoring/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_scoring/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..cedb307c Binary files /dev/null and b/custom_addons/encoach_scoring/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/models/__pycache__/feedback.cpython-312.pyc b/custom_addons/encoach_scoring/models/__pycache__/feedback.cpython-312.pyc new file mode 100644 index 00000000..c9684a91 Binary files /dev/null and b/custom_addons/encoach_scoring/models/__pycache__/feedback.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/models/__pycache__/score.cpython-312.pyc b/custom_addons/encoach_scoring/models/__pycache__/score.cpython-312.pyc new file mode 100644 index 00000000..82867230 Binary files /dev/null and b/custom_addons/encoach_scoring/models/__pycache__/score.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/models/__pycache__/student_answer.cpython-312.pyc b/custom_addons/encoach_scoring/models/__pycache__/student_answer.cpython-312.pyc new file mode 100644 index 00000000..2ce33a1f Binary files /dev/null and b/custom_addons/encoach_scoring/models/__pycache__/student_answer.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/models/__pycache__/student_attempt.cpython-312.pyc b/custom_addons/encoach_scoring/models/__pycache__/student_attempt.cpython-312.pyc new file mode 100644 index 00000000..b5c4c3e9 Binary files /dev/null and b/custom_addons/encoach_scoring/models/__pycache__/student_attempt.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/models/__pycache__/student_progress.cpython-312.pyc b/custom_addons/encoach_scoring/models/__pycache__/student_progress.cpython-312.pyc new file mode 100644 index 00000000..a8e01667 Binary files /dev/null and b/custom_addons/encoach_scoring/models/__pycache__/student_progress.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_scoring/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4ba9cfa1 Binary files /dev/null and b/custom_addons/encoach_scoring/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_scoring/services/__pycache__/speaking_evaluator.cpython-312.pyc b/custom_addons/encoach_scoring/services/__pycache__/speaking_evaluator.cpython-312.pyc new file mode 100644 index 00000000..7fb72403 Binary files /dev/null and b/custom_addons/encoach_scoring/services/__pycache__/speaking_evaluator.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_signup/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..cbc7b805 Binary files /dev/null and b/custom_addons/encoach_signup/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_signup/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..966c95f2 Binary files /dev/null and b/custom_addons/encoach_signup/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/controllers/__pycache__/auth.cpython-312.pyc b/custom_addons/encoach_signup/controllers/__pycache__/auth.cpython-312.pyc new file mode 100644 index 00000000..cf1e7cae Binary files /dev/null and b/custom_addons/encoach_signup/controllers/__pycache__/auth.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/controllers/auth.py b/custom_addons/encoach_signup/controllers/auth.py index 917cf0a4..b3538f7d 100644 --- a/custom_addons/encoach_signup/controllers/auth.py +++ b/custom_addons/encoach_signup/controllers/auth.py @@ -101,6 +101,24 @@ class EncoachSignupController(http.Controller): _logger.info('Registration OTP for %s: %s', email, otp_code) + try: + template = request.env.ref('encoach_signup.email_template_otp', raise_if_not_found=False) + if template: + template.sudo().with_context(otp_code=otp_code).send_mail(user.id, force_send=True) + else: + mail_values = { + 'subject': 'EnCoach - Verify Your Email', + 'email_from': request.env['ir.config_parameter'].sudo().get_param( + 'mail.catchall.email', 'noreply@encoach.com'), + 'email_to': email, + 'body_html': f'

Welcome to EnCoach!

' + f'

Your verification code is: {otp_code}

' + f'

This code expires in 15 minutes.

', + } + request.env['mail.mail'].sudo().create(mail_values).send() + except Exception as mail_err: + _logger.warning('OTP email failed for %s: %s', email, mail_err) + return _json_response({ 'message': 'Registration successful. Please verify your email.', 'user_id': user.id, @@ -205,12 +223,116 @@ class EncoachSignupController(http.Controller): _logger.info('Resend OTP for %s: %s', email, otp_code) + try: + user = request.env['res.users'].sudo().search( + [('login', '=', email)], limit=1) + if user: + mail_values = { + 'subject': 'EnCoach - Your New Verification Code', + 'email_from': request.env['ir.config_parameter'].sudo().get_param( + 'mail.catchall.email', 'noreply@encoach.com'), + 'email_to': email, + 'body_html': f'

Your new verification code is: {otp_code}

' + f'

This code expires in 15 minutes.

', + } + request.env['mail.mail'].sudo().create(mail_values).send() + except Exception as mail_err: + _logger.warning('Resend OTP email failed for %s: %s', email, mail_err) + return _json_response({'message': 'OTP resent'}) except Exception as e: _logger.exception('resend_otp failed') return _error_response(str(e), 500) + # ------------------------------------------------------------------ + # POST /api/reset/sendVerification + # ------------------------------------------------------------------ + @http.route('/api/reset/sendVerification', type='http', auth='public', + methods=['POST'], csrf=False) + def reset_send_verification(self, **kw): + try: + body = _get_json_body() + email = (body.get('email') or '').strip().lower() + if not email: + return _error_response('email is required', 400) + + user = request.env['res.users'].sudo().search( + [('login', '=', email)], limit=1) + if not user: + return _json_response({'message': 'If the email exists, a reset code has been sent.'}) + + otp_code = ''.join(random.choices(string.digits, k=6)) + otp_hash = hashlib.sha256(otp_code.encode()).hexdigest() + expires_at = fields.Datetime.now() + timedelta(minutes=15) + + request.env['encoach.otp'].sudo().create({ + 'email': email, + 'otp_hash': otp_hash, + 'expires_at': expires_at, + }) + + try: + template = request.env.ref('encoach_signup.email_template_password_reset', raise_if_not_found=False) + if template: + template.sudo().with_context(otp_code=otp_code).send_mail(user.id, force_send=True) + else: + mail_values = { + 'subject': 'EnCoach Password Reset Code', + 'email_from': request.env['ir.config_parameter'].sudo().get_param( + 'mail.catchall.email', 'noreply@encoach.com'), + 'email_to': email, + 'body_html': f'

Your password reset code is: {otp_code}

' + f'

This code expires in 15 minutes.

', + } + request.env['mail.mail'].sudo().create(mail_values).send() + except Exception as mail_err: + _logger.warning('Password reset email failed for %s: %s', email, mail_err) + + _logger.info('Password reset OTP for %s: %s', email, otp_code) + return _json_response({'message': 'If the email exists, a reset code has been sent.'}) + + except Exception as e: + _logger.exception('reset_send_verification failed') + return _error_response(str(e), 500) + + # ------------------------------------------------------------------ + # POST /api/auth/invite/set-password + # ------------------------------------------------------------------ + @http.route('/api/auth/invite/set-password', type='http', auth='public', + methods=['POST'], csrf=False) + def invite_set_password(self, **kw): + try: + body = _get_json_body() + token = body.get('token', '').strip() + password = body.get('password', '') + + if not token or not password: + return _error_response('token and password are required', 400) + + Invite = request.env['encoach.invite'].sudo() + invite = Invite.search([('token', '=', token), ('used', '=', False)], limit=1) + + if not invite: + return _error_response('Invalid or expired invitation token', 400) + + user = invite.user_id + if not user: + return _error_response('No user associated with this invitation', 400) + + user.sudo().write({'password': password}) + invite.write({'used': True}) + user.sudo().write({ + 'account_status': 'activated', + 'first_login': False, + }) + + return _json_response({'message': 'Password set successfully'}) + + except Exception as e: + _logger.exception('invite_set_password failed') + return _error_response(str(e), 500) + # ------------------------------------------------------------------ # GET /api/onboarding/goals # ------------------------------------------------------------------ @@ -342,3 +464,32 @@ class EncoachSignupController(http.Controller): except Exception as e: _logger.exception('captcha_config failed') return _error_response(str(e), 500) + + # ------------------------------------------------------------------ + # POST /api/subscription/trial + # ------------------------------------------------------------------ + @http.route('/api/subscription/trial', type='http', auth='none', + methods=['POST'], csrf=False) + @jwt_required + def start_trial(self, **kw): + try: + user = request.env.user + ICP = request.env['ir.config_parameter'].sudo() + trial_days = int(ICP.get_param('encoach.trial_duration_days', '14')) + + from datetime import timedelta + trial_end = fields.Datetime.now() + timedelta(days=trial_days) + + user.sudo().write({ + 'account_status': 'trial', + }) + + return _json_response({ + 'message': f'Trial activated for {trial_days} days', + 'trial_end': str(trial_end), + 'status': 'trial', + }) + + except Exception as e: + _logger.exception('start_trial failed') + return _error_response(str(e), 500) diff --git a/custom_addons/encoach_signup/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_signup/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..aed1645e Binary files /dev/null and b/custom_addons/encoach_signup/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/models/__pycache__/otp.cpython-312.pyc b/custom_addons/encoach_signup/models/__pycache__/otp.cpython-312.pyc new file mode 100644 index 00000000..a5b1e5c2 Binary files /dev/null and b/custom_addons/encoach_signup/models/__pycache__/otp.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/models/__pycache__/student_profile.cpython-312.pyc b/custom_addons/encoach_signup/models/__pycache__/student_profile.cpython-312.pyc new file mode 100644 index 00000000..5fff9acc Binary files /dev/null and b/custom_addons/encoach_signup/models/__pycache__/student_profile.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_signup/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..c83e3926 Binary files /dev/null and b/custom_addons/encoach_signup/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/services/__pycache__/captcha_service.cpython-312.pyc b/custom_addons/encoach_signup/services/__pycache__/captcha_service.cpython-312.pyc new file mode 100644 index 00000000..95a1a428 Binary files /dev/null and b/custom_addons/encoach_signup/services/__pycache__/captcha_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_signup/services/__pycache__/otp_service.cpython-312.pyc b/custom_addons/encoach_signup/services/__pycache__/otp_service.cpython-312.pyc new file mode 100644 index 00000000..d7023efe Binary files /dev/null and b/custom_addons/encoach_signup/services/__pycache__/otp_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_taxonomy/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..62689f41 Binary files /dev/null and b/custom_addons/encoach_taxonomy/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_taxonomy/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..ef0f0761 Binary files /dev/null and b/custom_addons/encoach_taxonomy/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/controllers/__pycache__/taxonomy.cpython-312.pyc b/custom_addons/encoach_taxonomy/controllers/__pycache__/taxonomy.cpython-312.pyc new file mode 100644 index 00000000..0c28578d Binary files /dev/null and b/custom_addons/encoach_taxonomy/controllers/__pycache__/taxonomy.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_taxonomy/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..be44a61c Binary files /dev/null and b/custom_addons/encoach_taxonomy/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/models/__pycache__/domain.cpython-312.pyc b/custom_addons/encoach_taxonomy/models/__pycache__/domain.cpython-312.pyc new file mode 100644 index 00000000..65826f19 Binary files /dev/null and b/custom_addons/encoach_taxonomy/models/__pycache__/domain.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/models/__pycache__/learning_objective.cpython-312.pyc b/custom_addons/encoach_taxonomy/models/__pycache__/learning_objective.cpython-312.pyc new file mode 100644 index 00000000..cbc81cac Binary files /dev/null and b/custom_addons/encoach_taxonomy/models/__pycache__/learning_objective.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/models/__pycache__/subject.cpython-312.pyc b/custom_addons/encoach_taxonomy/models/__pycache__/subject.cpython-312.pyc new file mode 100644 index 00000000..ff6b79dd Binary files /dev/null and b/custom_addons/encoach_taxonomy/models/__pycache__/subject.cpython-312.pyc differ diff --git a/custom_addons/encoach_taxonomy/models/__pycache__/topic.cpython-312.pyc b/custom_addons/encoach_taxonomy/models/__pycache__/topic.cpython-312.pyc new file mode 100644 index 00000000..689e6856 Binary files /dev/null and b/custom_addons/encoach_taxonomy/models/__pycache__/topic.cpython-312.pyc differ diff --git a/custom_addons/encoach_vector/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_vector/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..bd937586 Binary files /dev/null and b/custom_addons/encoach_vector/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_vector/models/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_vector/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..3bfb7339 Binary files /dev/null and b/custom_addons/encoach_vector/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_vector/models/__pycache__/embedding.cpython-312.pyc b/custom_addons/encoach_vector/models/__pycache__/embedding.cpython-312.pyc new file mode 100644 index 00000000..54e2521d Binary files /dev/null and b/custom_addons/encoach_vector/models/__pycache__/embedding.cpython-312.pyc differ diff --git a/custom_addons/encoach_vector/services/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_vector/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..b8144575 Binary files /dev/null and b/custom_addons/encoach_vector/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_vector/services/__pycache__/embedding_service.cpython-312.pyc b/custom_addons/encoach_vector/services/__pycache__/embedding_service.cpython-312.pyc new file mode 100644 index 00000000..a127a5da Binary files /dev/null and b/custom_addons/encoach_vector/services/__pycache__/embedding_service.cpython-312.pyc differ diff --git a/custom_addons/encoach_vector/services/__pycache__/indexer.cpython-312.pyc b/custom_addons/encoach_vector/services/__pycache__/indexer.cpython-312.pyc new file mode 100644 index 00000000..1fa885d6 Binary files /dev/null and b/custom_addons/encoach_vector/services/__pycache__/indexer.cpython-312.pyc differ diff --git a/custom_addons/encoach_verification/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_verification/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..ee2bf7d4 Binary files /dev/null and b/custom_addons/encoach_verification/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_verification/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_verification/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..d6fda254 Binary files /dev/null and b/custom_addons/encoach_verification/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_verification/controllers/__pycache__/verify.cpython-312.pyc b/custom_addons/encoach_verification/controllers/__pycache__/verify.cpython-312.pyc new file mode 100644 index 00000000..c7cf933c Binary files /dev/null and b/custom_addons/encoach_verification/controllers/__pycache__/verify.cpython-312.pyc differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..828be3bf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + db: + image: pgvector/pgvector:pg16 + environment: + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + POSTGRES_DB: encoach + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + + odoo: + build: . + depends_on: + - db + ports: + - "8069:8069" + volumes: + - odoo-data:/var/lib/odoo + - ./custom_addons:/opt/odoo/custom_addons + environment: + - HOST=db + - USER=odoo + - PASSWORD=odoo + command: ["odoo", "--config=/etc/odoo/odoo.conf"] + +volumes: + pgdata: + odoo-data: diff --git a/docs/06-All-Credentials-Inventory.md b/docs/06-All-Credentials-Inventory.md deleted file mode 100644 index 3a62c046..00000000 --- a/docs/06-All-Credentials-Inventory.md +++ /dev/null @@ -1,190 +0,0 @@ -# EnCoach - Complete Credentials Inventory - -**Extracted From:** Cloud Run service environment variables (plaintext) -**Date:** March 8, 2026 - -> **IMPORTANT:** All credentials below were found stored as plaintext environment variables -> in Cloud Run service configurations. They are NOT in Secret Manager, NOT in .env files, -> and NOT in the code repos. They were set directly during deployment. - ---- - -## 1. AI Service Keys - -| Variable | Value | Service | Used By | -|---|---|---|---| -| `OPENAI_API_KEY` | `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` | OpenAI (GPT-4o, GPT-3.5) | ielts-be (prod + staging) | -| `AWS_ACCESS_KEY_ID` | `AKIAXXT434IN3J7YQQ66` | AWS Polly (TTS) | ielts-be (prod + staging) | -| `AWS_SECRET_ACCESS_KEY` | `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` | AWS Polly (TTS) | ielts-be (prod + staging) | -| `ELAI_TOKEN` | `KtzxETdcZesZtwl7JKiYQapRvp0b4zMG` | ELAI (avatar videos) | ielts-be (prod + staging) | -| `GPT_ZERO_API_KEY` | `0195b9bb24c5439899f71230809c74af` | GPTZero (AI detection) | ielts-be (prod + staging) | -| `HEY_GEN_TOKEN` | `MjY4MDE0MjdjZmNhNDFmYTlhZGRkNmI3MGFlMzYwZDItMTY5NTExNzY3MA==` | HeyGen (legacy) | ielts-be (prod + staging) | -| `ELEVENLABS_API_KEY` | `sk_01190f3d023f6abe585d2bae06557cf4e4b9e1cf257d4732` | ElevenLabs (TTS) | ielts-be (staging only) | -| `ELEVENLABS_MODEL` | `eleven_multilingual_v2` | ElevenLabs model | ielts-be (staging only) | -| `TTS_PROVIDER` | `elevenlabs` | TTS provider selector | ielts-be (staging only) | - ---- - -## 2. Database Credentials - -| Variable | Value | Service | Used By | -|---|---|---|---| -| `MONGODB_URI` | `mongodb+srv://user:JKpFBymv0WLv3STj@encoach.3ydyg.mongodb.net/?retryWrites=true&w=majority&appName=EnCoach` | MongoDB Atlas | All services | -| `MONGODB_DB` (prod) | `production` | MongoDB database name | ielts-be, ielts-ui (prod) | -| `MONGODB_DB` (staging) | `staging` | MongoDB database name | ielts-be, ielts-ui (staging) | -| `DATABASE_HOST` | `34.77.91.43` | MySQL (CMS) | encoach-cms | -| `DATABASE_NAME` | `encoach` | MySQL database | encoach-cms | -| `DATABASE_USERNAME` | `root` | MySQL user | encoach-cms | -| `DATABASE_PASSWORD` | `VN*n1yVLxswU` | MySQL password | encoach-cms | -| `DATABASE_URL` | `jdbc:mysql://34.77.91.43:3306/` | MySQL connection | encoach-cms | - ---- - -## 3. Authentication & Security Keys - -| Variable | Value | Service | Used By | -|---|---|---|---| -| `JWT_SECRET_KEY` | `6e9c124ba92e8814719dcb0f21200c8aa4d0f119a994ac5e06eb90a366c83ab2` | Backend JWT | ielts-be | -| `JWT_TEST_TOKEN` | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0In0.Emrs2D3BmMP4b3zMjw0fJTPeyMwWEBDbxx2vvaWguO0` | Backend test JWT | ielts-be | -| `BACKEND_JWT` | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0In0.Emrs2D3BmMP4b3zMjw0fJTPeyMwWEBDbxx2vvaWguO0` | Frontend→Backend JWT | ielts-ui | -| `SECRET_COOKIE_PASSWORD` | `S2NrN2aBBYCpFpULvK2vDPFwsPbixAH6` | iron-session cookie | ielts-ui | -| `ADMIN_JWT_SECRET` | `aM9l2NPCbOlIBBfGfWAJAw==` | Strapi admin JWT | encoach-cms | -| `JWT_SECRET` | `hexTrI8hLY8k5syUSucXcg==` | Strapi public JWT | encoach-cms | -| `APP_KEYS` | `qLCKRYNICu/zsCYF566pEQ==,SbgGbzHLzVih3yY/bCvG9w==,xJ27/S+YudYyftTuqPRG8g==,dMDRY3LHbBOLavygMJIeNg==` | Strapi app keys | encoach-cms | -| `API_TOKEN_SALT` | `OWtKC1U80X18NIFBUpJV0A==` | Strapi API token salt | encoach-cms | -| `TRANSFER_TOKEN_SALT` | `qTWhsKJZK/Z2X0hDPtvCPA==` | Strapi transfer token | encoach-cms | - ---- - -## 4. Firebase Credentials - -### Production - -| Variable | Value | Used By | -|---|---|---| -| `FIREBASE_PUBLIC_API_KEY` | `AIzaSyCdD3xqz5-25ZuRU_Tm2W7tBY9FtWCIfRU` | ielts-ui (prod) | -| `FIREBASE_AUTH_DOMAIN` | `storied-phalanx-349916.firebaseapp.com` | ielts-ui (prod) | -| `FIREBASE_STORAGE_BUCKET` | `storied-phalanx-349916.appspot.com` | ielts-ui, ielts-be (prod) | -| `FIREBASE_PROJECT_ID` | `storied-phalanx-349916` | ielts-ui, ielts-be (prod) | -| `FIREBASE_MESSAGING_SENDER_ID` | `785091389226` | ielts-ui (prod) | -| `FIREBASE_APP_ID` | `1:785091389226:web:77cab7f4990c76aa0d2053` | ielts-ui (prod) | -| `FIREBASE_CLIENT_EMAIL` | `tiago.ribeiro@ecrop.dev` | ielts-ui (prod) | -| `FIREBASE_CREDENTIALS` | `/app/firebase-configs/storied-phalanx-349916.json` | ielts-be (prod) | -| `FIREBASE_SCRYPT_B64_SIGNER_KEY` | `vbO3Xii2lajSeSkCstq3s/dCwpXP7J2YN9rP/KRreU2vGOT1fg+wzSuy1kIhBECqJHG82tmwAilSxLFFtNKVMA==` | ielts-ui, ielts-be (prod) | -| `FIREBASE_SCRYPT_B64_SALT_SEPARATOR` | `Bw==` | ielts-ui, ielts-be (prod) | -| `FIREBASE_SCRYPT_ROUNDS` | `8` | ielts-ui, ielts-be (prod) | -| `FIREBASE_SCRYPT_MEM_COST` | `14` | ielts-ui, ielts-be (prod) | - -### Staging - -| Variable | Value | Used By | -|---|---|---| -| `FIREBASE_PUBLIC_API_KEY` | `AIzaSyB1HDuvYOX18ZxpSgi2PmmVOaUu49CNYws` | ielts-ui (staging) | -| `FIREBASE_AUTH_DOMAIN` | `encoach-staging.firebaseapp.com` | ielts-ui (staging) | -| `FIREBASE_STORAGE_BUCKET` | `encoach-staging.appspot.com` | ielts-ui, ielts-be (staging) | -| `FIREBASE_PROJECT_ID` | `encoach-staging` | ielts-ui, ielts-be (staging) | -| `FIREBASE_MESSAGING_SENDER_ID` | `1078696515702` | ielts-ui (staging) | -| `FIREBASE_APP_ID` | `1:1078696515702:web:b8a5518dac09cf6e366757` | ielts-ui (staging) | -| `FIREBASE_CREDENTIALS` | `/app/firebase-configs/encoach-staging.json` | ielts-be (staging) | -| `FIREBASE_SCRYPT_B64_SIGNER_KEY` | `qjo/b5U5oNxA8o+PHFMZx/ZfG8ZQ7688zYmwMOcfZvVjOM6aHe4Jf270xgyrVArqLIQwFi7VkFnbysBjueMbVw==` | ielts-ui, ielts-be (staging) | - ---- - -## 5. Payment Gateway Credentials - -### Paymob (Production) - -| Variable | Value | Used By | -|---|---|---| -| `PAYMOB_API_KEY` | `ZXlKaGJHY2lPaUpJVXpVeE1pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmpiR0Z6Y3lJNklrMWxjbU5vWVc1MElpd2ljSEp2Wm1sc1pWOXdheUk2T1RFM0xDSnVZVzFsSWpvaWFXNXBkR2xoYkNKOS5HMnJTZXJ2WHR1UUEyNWVQN0tvTDBHaHJaUmM3VWx5RHhmV0huRDRPbkZpVExyV0xicnUzeVZSYXk2ZENqTnpXS1duWmJoZ2RYcUgwSGxnWkNUTi1aQQ==` | ielts-ui (prod) | -| `PAYMOB_PUBLIC_KEY` | `omn_pk_live_BkKVov5VSuLtTIqgcfya39ucEDcnX35l` | ielts-ui (prod) | -| `PAYMOB_SECRET_KEY` | `omn_sk_live_a669c50ebabb3d5c88135eee070a507258a7d10522214de89c123a4a5b25fd22` | ielts-ui (prod) | -| `PAYMOB_INTEGRATION_ID` | `1620` | ielts-ui (prod) | - -### Paymob (Staging / Test) - -| Variable | Value | Used By | -|---|---|---| -| `PAYMOB_PUBLIC_KEY` | `omn_pk_test_Oh9XcYe589i5FqXHLZ7yZGUkF2M8Vf55` | ielts-ui (staging) | -| `PAYMOB_SECRET_KEY` | `omn_sk_test_ee1e6e3f149b481f9b943334c680b7c3fa27e8f1800e111aae45cc829c92f8e5` | ielts-ui (staging) | -| `PAYMOB_INTEGRATION_ID` | `1540` | ielts-ui (staging) | - -### Stripe (Landing Page) - -| Variable | Value | Used By | -|---|---|---| -| `STRIPE_KEY` | `pk_test_51NzD5xFI67mXFum2XDMXiLu89SbCAMY5O0RnKjlU6XqyfboRVvFHI3f5OJHaxsrjjB7WqDYqN7Y3eF8mq3sF354F00l30L5GuJ` | encoach-landing-page | -| `STRIPE_SECRET` | `sk_test_51NzD5xFI67mXFum2Lzi2JtR8Th9zuA3CnoKKkAaOBiHmiHDQdGt7Pruj1Z89e4nF5eVNStL866twJLoVBUgfiaxZ00yqst8gkh` | encoach-landing-page | -| `STRIPE_PRICING_TABLE` | `prctbl_1O0hFcFI67mXFum2kEqiD57r` | encoach-landing-page | - ---- - -## 6. Email / SMTP Credentials - -| Variable | Value | Used By | -|---|---|---| -| `MAIL_USER` | `noreply@encoach.com` | ielts-ui | -| `MAIL_PASS` | `sovlwwwqvnenladl` | ielts-ui | -| `SMTP_HOST` | `smtp.gmail.com` | ielts-ui | - ---- - -## 7. CMS / Strapi Integration - -| Variable | Value | Used By | -|---|---|---| -| `STRAPI_URL` | `https://encoach-cms-zrwipjl2nq-ew.a.run.app` | encoach-landing-page | -| `STRAPI_TOKEN` | `1adf65151d6c8bf7491f4162d504dc9063ae750399fe8d4c841af20d2b3f8a3fbc6206ecb63bb9e9cbb9a00baac73a8547772ab0f16f0f7d04f4201f04ef72f9a2cb37fbadf5c347d8243a6733ac21589364e76b2fb5309386cb5aec52244f633b26d04faad5aca355df728200c86089d65e4162658a5b37ec1a6730d36fa383` | encoach-landing-page | -| `GCS_BUCKET_NAME` | `encoach-cms-media` | encoach-cms | -| `GCS_SERVICE_ACCOUNT` | *(base64-encoded GCP service account JSON with private key)* | encoach-cms | - ---- - -## 8. Internal Service URLs - -| Variable | Value | Used By | -|---|---|---| -| `BACKEND_URL` (prod) | `https://ielts-be-zrwipjl2nq-ew.a.run.app/api` | ielts-ui (prod) | -| `BACKEND_URL` (staging) | `https://ielts-be-zrwipjl2nq-zf.a.run.app/api` | ielts-ui (staging) | -| `WEBSITE_URL` | `https://encoach.com` | ielts-ui | -| `NEXT_PUBLIC_APP_URL` | `https://platform.encoach.com` | encoach-landing-page | -| `WEBHOOK_URL` | `https://encoach.com/api/stripe` | encoach-landing-page | -| `DATABASE_URL` | `jdbc:mysql://34.77.91.43:3306/` | encoach-cms | - ---- - -## 9. Application Config (Non-Secret) - -| Variable | Value | Used By | -|---|---|---| -| `ENVIRONMENT` (prod) | `platform` | ielts-ui (prod) | -| `ENVIRONMENT` (staging) | `staging` | ielts-ui (staging) | -| `PDF_VERSION` (prod) | `1.1.0` | ielts-ui (prod) | -| `PDF_VERSION` (staging) | `1.0.7` | ielts-ui (staging) | -| `EXCEL_VERSION` | `0.0.1` | ielts-ui | -| `HOST` | `0.0.0.0` | encoach-cms | - ---- - -## Where Each Service Gets Its Keys - -| Cloud Run Service | Region | Environment | Key Categories | -|---|---|---|---| -| **ielts-be** | europe-west1 | Production | AI keys, AWS, DB, Firebase, JWT | -| **ielts-be** | me-west1 | Staging | AI keys, AWS, DB, Firebase, JWT, ElevenLabs | -| **ielts-ui** | europe-west1 | Production | DB, Firebase, Paymob (live), SMTP, JWT | -| **ielts-ui** | us-central1 | Staging | DB, Firebase (staging), Paymob (test), SMTP, JWT | -| **encoach-frontend-staging** | me-central1 | Staging | DB, Firebase (staging), Paymob (test), SMTP, JWT | -| **encoach-cms** | europe-west1 | Production | MySQL, Strapi JWT, GCS service account | -| **encoach-landing-page** | europe-west1 | Production | Stripe (test), Strapi token | - ---- - -## Notes - -1. **Production and staging share the same OpenAI, AWS, ELAI, and GPTZero keys** — there is no separation of API keys between environments. -2. **Production and staging share the same MongoDB Atlas cluster** (`encoach.3ydyg.mongodb.net`) — only the database name differs (`production` vs `staging`). -3. **The Stripe keys on the landing page are test keys** (`pk_test_`, `sk_test_`) even in the production deployment. -4. **The `BACKEND_JWT` and `JWT_TEST_TOKEN` are identical** — the same test JWT is used for frontend-to-backend auth and for testing. -5. **The `GCS_SERVICE_ACCOUNT` in encoach-cms contains a full GCP service account private key** encoded in base64. -6. **The `FIREBASE_CLIENT_EMAIL` references `tiago.ribeiro@ecrop.dev`** — a developer's email from the previous development team. diff --git a/docs/Developer Workflow Manual.txt b/docs/Developer Workflow Manual.txt deleted file mode 100644 index 4d7f2827..00000000 --- a/docs/Developer Workflow Manual.txt +++ /dev/null @@ -1,156 +0,0 @@ - - - -# Developer Workflow Manual — EnCoach Backend & Frontend - -## One-Time Setup (Do This Once) - -**Step 1 — Clone the repositories** -```bash -git clone https://git.albousalh.com/devops/encoach_backend_new_v2.git -git clone https://git.albousalh.com/devops/encoach_frontend_new_v2.git -``` - -**Step 2 — Switch to the working branch** -```bash -cd encoach_backend_new_v2 -git checkout full_stack_dev - -cd ../encoach_frontend_new_v2 -git checkout full_stack_dev -``` - -**Step 3 — Install the `tea` CLI (to open PRs from terminal)** -```bash -# macOS -brew install tea - -# Or download directly: -# https://gitea.com/gitea/tea/releases - -# Configure it (run once): -tea login add --name encoach --url https://git.albousalh.com --token YOUR_TOKEN -``` -> To get your token: log in at `https://git.albousalh.com` → top-right avatar → Settings → Applications → Generate Token. - ---- - -## Every Day — Starting Work - -**Step 4 — Always pull the latest before coding** -```bash -git checkout full_stack_dev -git pull origin full_stack_dev -``` -> This makes sure you have the latest code before making changes. - ---- - -## Making Changes - -**Step 5 — Make your code changes locally** - -Work as normal in your editor. No restrictions on what you change. - -**Step 6 — If you add a new Python package**, add it to `new_project/requirements.txt`: -``` -openai>=1.50 -your-new-package>=x.x -``` - -**Step 7 — If you add a new Odoo addon**, place it under `new_project/custom_addons/your_module/` and make sure: -- The `__manifest__.py` lists **all** modules you reference via `comodel_name=` in its `depends` list. -- Notify Talal — new addons need a one-time manual install on the server (see end of this doc). - ---- - -## Pushing Your Work - -**Step 8 — Stage and commit your changes** -```bash -git add . -git commit -m "Short description of what you changed" -``` - -**Step 9 — Push to your working branch** -```bash -git push origin full_stack_dev -``` - -**Step 10 — Open a Pull Request** (asks Talal for review/approval) -```bash -tea pr create \ - --repo devops/encoach_backend_new_v2 \ - --head full_stack_dev \ - --base main \ - --title "Brief title of your changes" \ - --description "What you changed and why" -``` -> For frontend, replace `encoach_backend_new_v2` with `encoach_frontend_new_v2`. - -**Step 11 — Notify Talal** that a PR is waiting for his review. - ---- - -## After Talal Approves & Merges - -**Step 12 — Deployment is fully automatic.** You do not need to do anything on the server. - -What happens behind the scenes: -1. Talal merges the PR on `https://git.albousalh.com` -2. The server detects the merge to `main` -3. The deploy script pulls the latest code -4. Docker rebuilds the image (if `Dockerfile` or `requirements.txt` changed — takes ~10–15 min; otherwise ~1 min) -5. The container restarts with the new code - -**Step 13 — Verify it worked** by checking the live URL: -- Frontend: `http://5.189.151.117:3000` -- Backend API: `http://5.189.151.117:8069/api/login` - ---- - -## Checking Logs If Something Goes Wrong - -SSH into the server (ask Talal for credentials) and run: -```bash -# Backend logs -docker logs backend-v2-odoo --tail 50 - -# Frontend logs -docker logs encoach-frontend --tail 50 -``` - ---- - -## Special Case: Adding a New Odoo Addon - -New addons need a **one-time manual install** on the server (the database doesn't know about them yet). Tell Talal and he will run: -```bash -ssh root@5.189.151.117 -docker stop backend-v2-odoo -docker run --rm \ - --network backend-v2_default \ - -v /opt/encoach/backend-v2/odoo-docker.conf:/etc/odoo/odoo.conf:ro \ - -v /opt/encoach/backend-v2/new_project/custom_addons:/mnt/custom_addons:ro \ - -v /opt/encoach/backend-v2/new_project/enterprise-19:/mnt/enterprise:ro \ - -v /opt/encoach/backend-v2/new_project/openeducat_erp-19.0:/mnt/openeducat:ro \ - -v odoo-web-data:/var/lib/odoo \ - encoach-backend:latest \ - odoo -c /etc/odoo/odoo.conf -d encoach_v2 -i your_new_module --stop-after-init -docker start backend-v2-odoo -``` -> After the first install, all future deploys update it automatically. - ---- - -## Quick Reference - -| Action | Command | -|---|---| -| Pull latest | `git pull origin full_stack_dev` | -| Commit | `git add . && git commit -m "message"` | -| Push | `git push origin full_stack_dev` | -| Open PR (backend) | `tea pr create --repo devops/encoach_backend_new_v2 --head full_stack_dev --base main --title "..."` | -| Open PR (frontend) | `tea pr create --repo devops/encoach_frontend_new_v2 --head full_stack_dev --base main --title "..."` | -| View logs (backend) | `docker logs backend-v2-odoo --tail 50` | -| View logs (frontend) | `docker logs encoach-frontend --tail 50` | \ No newline at end of file diff --git a/docs/ENCOACH_USER_STORIES.md b/docs/ENCOACH_USER_STORIES.md deleted file mode 100644 index 0658c71c..00000000 --- a/docs/ENCOACH_USER_STORIES.md +++ /dev/null @@ -1,2125 +0,0 @@ -# EnCoach Platform -- Granular User Stories (English Only) - -**Document Version:** 2.0 -**Date:** April 2026 -**Status:** Active -- Ready for Development -**Scope:** English Language Only (General English + IELTS). Math/IT deferred to future phase. -**Audience:** Odoo Developer (full-stack, using Cursor IDE) - ---- - -## Companion Documents - -| Document | Version | Purpose | -|----------|---------|---------| -| `ENCOACH_WORKFLOWS_FRONTEND_SRS.md` | v1.1 | Frontend technical specification | -| `ENCOACH_WORKFLOWS_BACKEND_SRS.md` | v1.1 | Backend technical specification | -| `ENCOACH_USER_STORIES_OVERVIEW.md` | v1.0 | High-level overview (superseded by this document for development) | - ---- - -## How to Read This Document - -Each user story follows this format: - -> **US-[ACTOR]-[NUMBER]: [Title]** -> **As a** [single role], **I want to** [one specific action], **so that** [one clear benefit]. - -**One story = one function = one testable unit.** - -### Actor Prefixes - -| Prefix | Actor | Section | -|--------|-------|---------| -| SL | Self-Learning Student | Section 1 | -| ES | Entity/University Student | Section 2 | -| AD | Admin / Teacher | Section 3 | -| PV | Public Verifier | Section 3 (end) | - -### Priority Legend - -| Priority | Meaning | -|----------|---------| -| **Must Have** | Core functionality. Cannot launch without this. | -| **Should Have** | Important. Can be deferred briefly if needed. | -| **Could Have** | Desirable enhancement. Implement if time permits. | - ---- - -# SECTION 1 -- SELF-LEARNING STUDENT JOURNEY - -The complete end-to-end journey of an individual student who registers independently, pays for access, and learns on their own. - ---- - -## 1A. Registration - -### US-SL-01: Register a New Account - -**As a** self-learning student, **I want to** create an account by providing my name, email, password, and selecting "Self-Learner" as my role, **so that** I can access the platform independently. - -**Acceptance Criteria:** -- [ ] Registration page is accessible at `/register` without authentication -- [ ] Form fields: Full Name, Email, Password, Confirm Password, Role (select "Self-Learner"), CAPTCHA -- [ ] Password strength indicator updates in real time (Weak/Medium/Strong) -- [ ] Email uniqueness checked on blur; inline error if taken -- [ ] CAPTCHA must be completed before submission -- [ ] On success, redirect to `/verify-email?email={email}` -- [ ] On validation error, show field-level error messages - -**Flow:** -1. Student opens `/register` -2. Fills in all fields, selects "Self-Learner" role -3. Completes CAPTCHA -4. Clicks "Create Account" -5. System creates account, sends 6-digit OTP to email -6. Redirect to email verification page - -**Error / Edge Cases:** -- Duplicate email: inline error on blur -- CAPTCHA failure: "Please complete the CAPTCHA" -- Network error: toast "Something went wrong. Please try again." - -**SRS Reference:** Frontend 4, Backend 12 -**Priority:** Must Have - ---- - -### US-SL-02: Verify Email Address - -**As a** self-learning student who just registered, **I want to** enter a 6-digit verification code sent to my email, **so that** the platform confirms I own this email address. - -**Acceptance Criteria:** -- [ ] Page shows "We've sent a 6-digit verification code to {email}" -- [ ] 6-digit OTP input auto-focuses first digit, advances focus automatically -- [ ] "Verify" button enabled only when all 6 digits entered -- [ ] On valid OTP: redirect to `/onboarding` -- [ ] On invalid OTP: clear input, show "Invalid verification code. Please try again." -- [ ] On expired OTP: show "This code has expired. Please request a new one." -- [ ] "Resend code" disabled for 60 seconds after each send (countdown shown) -- [ ] Maximum 3 resend attempts; after 3: "Maximum resend attempts reached. Please contact support." -- [ ] Code expires after 15 minutes (countdown timer shown) - -**Flow:** -1. Student lands on `/verify-email?email={email}` -2. Checks email for OTP -3. Enters 6 digits -4. Clicks "Verify" -5. On success, proceeds to onboarding wizard - -**Error / Edge Cases:** -- Wrong code: input clears, retry allowed -- Code expired: must request new one -- All 3 resends exhausted: contact support -- Browser closed: returns to verification on next login (account still unverified) - -**SRS Reference:** Frontend 5, Backend 13 -**Priority:** Must Have - ---- - -## 1B. Onboarding - -### US-SL-03: Select Learning Goal - -**As a** self-learning student, **I want to** select my learning goal (General English, IELTS Academic, or IELTS General Training) in Step 1 of the onboarding wizard, **so that** the platform tailors my experience to my objective. - -**Acceptance Criteria:** -- [ ] Step 1 of 4 in the onboarding wizard at `/onboarding` -- [ ] Progress bar shows "Step 1 of 4" -- [ ] Radio card group displays available goals loaded from `GET /api/onboarding/goals` -- [ ] English-related options: General English, IELTS Academic, IELTS General Training -- [ ] Each card has: icon, title, description -- [ ] User must select one goal before clicking "Next" -- [ ] "Back" button is hidden on Step 1 - -**Flow:** -1. Student lands on onboarding wizard -2. Views available learning goals -3. Selects one goal (e.g., "IELTS Academic") -4. Clicks "Next" to proceed to Step 2 - -**SRS Reference:** Frontend 6, Backend 14 -**Priority:** Must Have - ---- - -### US-SL-04: Set Target Level and Timeline - -**As a** self-learning student, **I want to** set my target band/CEFR level and optionally an exam date in Step 2, **so that** the platform knows what I'm aiming for and by when. - -**Acceptance Criteria:** -- [ ] Step 2 of 4 in the onboarding wizard -- [ ] For IELTS goals: band score slider 4.0-9.0 in 0.5 steps -- [ ] For General English goals: CEFR level select (A1-C2) -- [ ] Optional exam date picker: "I have an upcoming exam on..." -- [ ] Checkbox: "I don't have a specific exam date" hides the date picker -- [ ] "Back" returns to Step 1 without losing data -- [ ] "Next" proceeds to Step 3 - -**Flow:** -1. Student sets target band/CEFR level -2. Optionally selects exam date or checks "no specific date" -3. Clicks "Next" - -**SRS Reference:** Frontend 6, Backend 14 -**Priority:** Must Have - ---- - -### US-SL-05: Set Study Preferences - -**As a** self-learning student, **I want to** configure my study hours per week, study mode, and learning style in Step 3, **so that** the platform personalises content delivery to my preferences. - -**Acceptance Criteria:** -- [ ] Step 3 of 4 in the onboarding wizard -- [ ] Hours per week: slider, 1-40, default 10. Shows estimated completion time -- [ ] Study mode: radio group -- "Self-study" or "With a teacher" -- [ ] Learning style: checkbox group (multi-select, at least one) -- Visual, Audio, Reading, Mixed -- [ ] "Back" returns to Step 2 without losing data -- [ ] "Next" proceeds to Step 4 - -**Flow:** -1. Student adjusts study hours slider -2. Selects study mode -3. Selects one or more learning styles -4. Clicks "Next" - -**SRS Reference:** Frontend 6, Backend 14 -**Priority:** Must Have - ---- - -### US-SL-06: Choose Placement Test Decision - -**As a** self-learning student, **I want to** decide whether to take the placement test now or skip it in Step 4, **so that** I can either get an accurate level assessment or start at a default level. - -**Acceptance Criteria:** -- [ ] Step 4 of 4 in the onboarding wizard -- [ ] Heading: "Ready to find your level?" -- [ ] Description explains the test (~20-30 minutes) -- [ ] "Take Placement Test Now" button: submits wizard data, navigates to `/student/placement` -- [ ] "Skip for Now" button: shows warning "Without a placement test, we'll start you at B1 (intermediate) level. You can take the test anytime from your dashboard." Submits wizard data, navigates to `/student/dashboard` -- [ ] On submission, account status changes to `activated` -- [ ] All 4 steps of wizard data submitted via `POST /api/onboarding/complete` - -**Flow:** -1. Student reads the placement test description -2. Chooses "Take Placement Test Now" or "Skip for Now" -3. Wizard data is submitted -4. Account is activated -5. Redirect to placement test or dashboard - -**Error / Edge Cases:** -- Browser closed mid-wizard: data lost, user restarts wizard on next login (account still `unactivated`) -- Skip chosen: default level B1, dashboard banner prompts test later - -**SRS Reference:** Frontend 6, Backend 14 -**Priority:** Must Have - ---- - -## 1C. Placement Test - -### US-SL-07: View Placement Test Briefing - -**As a** self-learning student, **I want to** see an overview of the placement test before starting, **so that** I know what to expect (sections, duration, adaptive nature). - -**Acceptance Criteria:** -- [ ] Briefing page at `/student/placement` -- [ ] Shows: subject (English), estimated duration (20-30 min), section count (4), adaptive note -- [ ] Dimension breakdown table: Grammar (~30 MCQ, ~8 min), Vocabulary (~20 items, ~5 min), Reading (~10 Q / 2 passages, ~10 min), Speaking (3 prompts, ~5 min) -- [ ] "Begin Test" button starts the test -- [ ] If an active incomplete session exists, "Begin Test" resumes from where the student left off - -**Flow:** -1. Student navigates to `/student/placement` -2. Reviews test overview -3. Clicks "Begin Test" -4. System creates CAT session and navigates to test interface - -**SRS Reference:** Frontend 7, Backend 15 -**Priority:** Must Have - ---- - -### US-SL-08: Take Placement Test -- Grammar Section - -**As a** self-learning student, **I want to** answer adaptive grammar questions one at a time, **so that** the system accurately assesses my grammar proficiency. - -**Acceptance Criteria:** -- [ ] Full-screen distraction-free interface (no sidebar) -- [ ] Top bar: elapsed timer, section name "Grammar", progress "Question X of ~30" -- [ ] Each question fetched one at a time from backend (no pre-loading) -- [ ] After each answer, next question adapts in difficulty (IRT algorithm) -- [ ] Question types: MCQ (single answer), MCQ (multiple answers) -- [ ] "Previous" button disabled (no going back in CAT) -- [ ] Auto-save every 10 seconds; "Saved" indicator shown -- [ ] When backend returns `section_complete: true`, show transition: "Grammar section complete. Next: Vocabulary" with countdown or "Continue" button - -**Flow:** -1. First grammar question appears -2. Student selects answer, clicks "Next" -3. Backend returns next (adapted) question -4. Repeat until section complete -5. Transition screen to Vocabulary section - -**Error / Edge Cases:** -- Network loss: auto-save ensures no data loss; resume on reconnection -- Browser close: resume from last save on next login - -**SRS Reference:** Frontend 8, Backend 15-16 -**Priority:** Must Have - ---- - -### US-SL-09: Take Placement Test -- Vocabulary Section - -**As a** self-learning student, **I want to** answer adaptive vocabulary questions, **so that** the system assesses my vocabulary range. - -**Acceptance Criteria:** -- [ ] Section name shows "Vocabulary", progress updates -- [ ] Question types: MCQ, gap fill, definition match -- [ ] Same CAT adaptive behaviour as Grammar section -- [ ] Auto-save every 10 seconds -- [ ] On section complete, transition to Reading section - -**Flow:** -1. First vocabulary question appears after Grammar transition -2. Student answers adaptive questions -3. Section completes, transition to Reading - -**SRS Reference:** Frontend 8, Backend 15-16 -**Priority:** Must Have - ---- - -### US-SL-10: Take Placement Test -- Reading Section - -**As a** self-learning student, **I want to** read passages and answer comprehension questions, **so that** the system assesses my reading proficiency. - -**Acceptance Criteria:** -- [ ] Section name shows "Reading", progress updates -- [ ] Split view: passage on left, questions on right (desktop). Scrollable passage above questions (mobile). -- [ ] Question types: MCQ, True/False/Not Given -- [ ] Passages progress from easy to difficult (CAT adaptation) -- [ ] Auto-save every 10 seconds -- [ ] On section complete, transition to Speaking section - -**Flow:** -1. First reading passage and questions appear -2. Student reads passage, answers questions -3. Repeat for second passage -4. Section completes, transition to Speaking - -**SRS Reference:** Frontend 8, Backend 15-16 -**Priority:** Must Have - ---- - -### US-SL-11: Take Placement Test -- Speaking Section - -**As a** self-learning student, **I want to** record spoken responses to 3 prompts, **so that** the system can evaluate my speaking ability via AI. - -**Acceptance Criteria:** -- [ ] Microphone permission prompt appears at the start of the section -- [ ] If permission denied: section skipped with note "Speaking section skipped. Your placement will be based on the other three sections." -- [ ] 3 speaking prompts, each showing: prompt text, "Start Recording" button, 60-second countdown -- [ ] During recording: waveform visualiser, "Stop" button -- [ ] After recording: playback controls, "Re-record" (max 1 re-record per prompt), "Submit Recording" -- [ ] Audio uploaded as blob via `POST /api/placement/speaking-upload` -- [ ] On final prompt submission, "Finish Test" button navigates to results page - -**Flow:** -1. Microphone permission requested -2. First speaking prompt displayed -3. Student clicks "Start Recording", speaks for up to 60 seconds -4. Clicks "Stop", reviews via playback -5. Optionally re-records once -6. Clicks "Submit Recording" -7. Repeats for prompts 2 and 3 -8. Clicks "Finish Test" - -**SRS Reference:** Frontend 8, Backend 18 -**Priority:** Must Have - ---- - -### US-SL-12: View Placement Results - -**As a** self-learning student, **I want to** see my overall CEFR level and per-dimension scores after completing the placement test, **so that** I understand my current proficiency. - -**Acceptance Criteria:** -- [ ] Results page at `/student/placement/results?session={id}` -- [ ] Overall CEFR level displayed prominently (e.g., "B2" with coloured badge) -- [ ] Per-dimension scores in a radar chart: Grammar, Vocabulary, Reading, Speaking -- [ ] Score table: Dimension, Score %, CEFR Level, Gap to Target -- [ ] If Speaking evaluation is pending: row shows "Pending" badge, polls `GET /api/placement/speaking-status` every 30 seconds until complete -- [ ] "Continue" button at the bottom navigates to learning path preview - -**Flow:** -1. Student lands on results page after test completion -2. Views overall CEFR level -3. Reviews per-dimension breakdown -4. Waits for Speaking evaluation if pending -5. Clicks "Continue" - -**Error / Edge Cases:** -- Speaking pending: "Pending" badge with polling -- Network error loading results: retry button - -**SRS Reference:** Frontend 9, Backend 15, 17 -**Priority:** Must Have - ---- - -### US-SL-13: View Personalised Learning Path Preview - -**As a** self-learning student, **I want to** see a personalised learning path based on my placement results BEFORE any payment prompt, **so that** I understand what I will study and can make an informed decision about subscribing. - -**Acceptance Criteria:** -- [ ] Learning path preview section on the results page (below scores) -- [ ] Course outline card: estimated duration, module count, study hours per week -- [ ] Skill gap modules listed by priority (largest gap first) with: module name, estimated hours, resource type icons (PDF, Video, Audio, Exercise), difficulty level -- [ ] "View Full Course Plan" button expands the detailed module breakdown -- [ ] This is shown BEFORE any payment prompt (key design principle) - -**Flow:** -1. Student views placement results -2. Scrolls down to learning path preview -3. Reviews proposed modules and estimated effort -4. Clicks "Continue" to proceed to payment options - -**SRS Reference:** Frontend 9, Backend 15 -**Priority:** Must Have - ---- - -### US-SL-14: Choose Payment or Access Option - -**As a** self-learning student, **I want to** choose between full subscription, free trial, or skip, **so that** I can start learning immediately or decide later. - -**Acceptance Criteria:** -- [ ] Three pricing cards at `/student/placement/access`: - - "Unlock Full Course" -> navigates to `/student/subscription` - - "Try Free for 7 Days" -> activates trial, navigates to dashboard - - "Continue to Dashboard" -> navigates to dashboard with persistent banner -- [ ] Free trial: limited access to first 3 modules only -- [ ] Skip: persistent banner on dashboard "Unlock your personalised course -- Subscribe now" - -**Flow:** -1. Student views three pricing options -2. Selects one -3. Proceeds to subscription, dashboard (trial), or dashboard (skip) - -**SRS Reference:** Frontend 10 -**Priority:** Should Have - ---- - -## 1D. Exam Taking - -### US-SL-15: Start an Assigned Exam Session - -**As a** self-learning student, **I want to** open an exam that has been assigned to me and enter the distraction-free exam interface, **so that** I can take the exam under controlled conditions. - -**Acceptance Criteria:** -- [ ] Full-screen interface at `/student/exam/:examId/session` (no sidebar, minimal header) -- [ ] Top bar: exam title, current section name, section countdown timer, overall progress bar -- [ ] All sections and questions pre-loaded (unlike CAT) -- [ ] First section starts immediately with its countdown timer -- [ ] Auto-save every 10 seconds; "Saved" indicator shown - -**Flow:** -1. Student navigates to the exam session -2. Exam loads with all sections -3. First section countdown begins -4. Student starts answering questions - -**Error / Edge Cases:** -- Exam not assigned: redirect with error "This exam is not available to you" -- Access window expired: "This exam's access window has closed" - -**SRS Reference:** Frontend 17, Backend 25 -**Priority:** Must Have - ---- - -### US-SL-16: Answer Listening Questions - -**As a** self-learning student taking an IELTS exam, **I want to** listen to audio recordings and answer questions for each Listening part, **so that** my listening comprehension is assessed. - -**Acceptance Criteria:** -- [ ] Audio player with play/pause, progress bar, volume control -- [ ] Audio plays once (IELTS standard). No replay unless configured for practice mode. -- [ ] Question types: MCQ (single), MCQ (multiple), gap fill -- [ ] Section timer counts down; warning at 5 min and 1 min remaining -- [ ] "Next" navigates to next question; "Previous" navigates back within the section -- [ ] "Flag for Review" marks the question with a flag icon -- [ ] On timer expiry: answers auto-submit, student moves to next section - -**Flow:** -1. Listening section begins, audio plays -2. Student answers questions while listening -3. Navigates between questions -4. Flags uncertain questions -5. Section ends (timer or manual submit) - -**SRS Reference:** Frontend 17, Backend 25 -**Priority:** Must Have - ---- - -### US-SL-17: Answer Reading Questions - -**As a** self-learning student taking an IELTS exam, **I want to** read passages and answer comprehension questions for each Reading part, **so that** my reading comprehension is assessed. - -**Acceptance Criteria:** -- [ ] Split view: passage on left, questions on right (desktop) -- [ ] Question types: MCQ, True/False/Not Given, gap fill, matching -- [ ] Section timer with warnings at 5 min and 1 min -- [ ] Previous/Next navigation within section -- [ ] "Flag for Review" available -- [ ] Auto-save every 10 seconds - -**Flow:** -1. Reading section begins -2. Student reads passage, answers questions -3. Navigates between questions and passages -4. Section ends (timer or manual submit) - -**SRS Reference:** Frontend 17, Backend 25 -**Priority:** Must Have - ---- - -### US-SL-18: Complete Writing Tasks - -**As a** self-learning student taking an IELTS exam, **I want to** write responses to Writing Task 1 and Task 2 using a rich text editor, **so that** my writing ability is assessed. - -**Acceptance Criteria:** -- [ ] Rich text editor with word count displayed -- [ ] Task 1: minimum 150 words (indicator turns red below minimum) -- [ ] Task 2: minimum 250 words (indicator turns red below minimum) -- [ ] Submission is NOT blocked by low word count (student may choose to submit anyway) -- [ ] Section timer with warnings -- [ ] Auto-save every 10 seconds - -**Flow:** -1. Writing section begins -2. Student reads Task 1 prompt, writes response -3. Monitors word count -4. Moves to Task 2, writes response -5. Section ends (timer or manual submit) - -**SRS Reference:** Frontend 17, Backend 25 -**Priority:** Must Have - ---- - -### US-SL-19: Record Speaking Responses in Exam - -**As a** self-learning student taking an IELTS exam, **I want to** record spoken responses for each Speaking part, **so that** my speaking ability is assessed. - -**Acceptance Criteria:** -- [ ] Same audio recording interface as placement test (US-SL-11) -- [ ] Part 1: Familiar topic questions -- [ ] Part 2: Cue card long turn (1 min prep + 2 min speaking) -- [ ] Part 3: Abstract discussion linked to Part 2 topic -- [ ] Microphone permission prompt -- [ ] Playback and re-record (max 1) per prompt -- [ ] Audio uploaded on submission - -**Flow:** -1. Speaking section begins -2. Student records responses for Parts 1, 2, and 3 -3. Reviews each recording -4. Submits recordings - -**SRS Reference:** Frontend 17, Backend 25 -**Priority:** Must Have - ---- - -### US-SL-20: Review and Submit Exam - -**As a** self-learning student, **I want to** review a summary of my answers before final submission, **so that** I can ensure I haven't missed any questions. - -**Acceptance Criteria:** -- [ ] Review summary page shows per section: answered count, unanswered count, flagged count -- [ ] Flagged questions listed with "Go to Question" links -- [ ] "Submit Exam" button with confirmation dialog: "Are you sure? You cannot change your answers after submission." -- [ ] After submission, navigate to submission confirmation page - -**Flow:** -1. Student completes all sections -2. Review summary appears -3. Student reviews flagged/unanswered items -4. Optionally returns to fix answers -5. Clicks "Submit Exam" -6. Confirmation dialog -> final submit - -**SRS Reference:** Frontend 17, Backend 25 -**Priority:** Must Have - ---- - -### US-SL-21: View Exam Results (Auto-Release) - -**As a** self-learning student whose exam has auto-release enabled, **I want to** see my results immediately after submission, **so that** I know my score right away. - -**Acceptance Criteria:** -- [ ] Results page at `/student/exam/:examId/results` -- [ ] Overall band score displayed prominently (e.g., "6.5") -- [ ] CEFR equivalent shown -- [ ] Per-skill radar chart and table: Skill, Band, CEFR, Gap to Target -- [ ] Listening and Reading: auto-scored, shown immediately -- [ ] Writing and Speaking: if pending teacher grading, show "Pending Review" with estimated wait time -- [ ] Feedback section: per-question feedback (expandable accordions per section) -- [ ] "Areas to improve" summary: top 3 weakness areas -- [ ] "Download PDF Report" button -- [ ] "View Recommended Course" button - -**Flow:** -1. Student is redirected to results page after submission -2. Sees auto-scored L/R results immediately -3. W/S show "Pending Review" until teacher grades them -4. Once all skills graded, full results display - -**SRS Reference:** Frontend 19, Backend 27 -**Priority:** Must Have - ---- - -### US-SL-22: Receive Post-Exam Routing (Pass) - -**As a** self-learning student who met or exceeded my target band, **I want to** see a congratulatory message with options to register for the official IELTS exam or continue studying, **so that** I know I've reached my goal. - -**Acceptance Criteria:** -- [ ] Action card: "Congratulations! You've reached your target." -- [ ] "Register for Official IELTS Exam" button -> navigates to `/student/exam-registration` -- [ ] "Continue Studying" button -> navigates to `/student/dashboard` - -**Flow:** -1. Student views results showing target met -2. Action card appears at bottom -3. Student chooses next step - -**SRS Reference:** Frontend 20 -**Priority:** Should Have - ---- - -### US-SL-23: Receive Post-Exam Routing (Below Target) - -**As a** self-learning student who scored below my target band, **I want to** see my skill gaps and an option to start adaptive training, **so that** I can work on my weaknesses. - -**Acceptance Criteria:** -- [ ] Action card: "Let's build a plan to reach your target." -- [ ] Skill gap summary shown inline -- [ ] "Start Adaptive Training" button -> navigates to `/student/course/generate?from=exam&examId={id}` - -**Flow:** -1. Student views results showing target not met -2. Action card with gap summary appears -3. Student clicks "Start Adaptive Training" - -**SRS Reference:** Frontend 20 -**Priority:** Should Have - ---- - -## 1E. Course Learning - -### US-SL-24: View Gap Analysis - -**As a** self-learning student, **I want to** see a detailed skill gap analysis from my exam or placement results, **so that** I understand which areas need the most work. - -**Acceptance Criteria:** -- [ ] Gap analysis page at `/student/course/generate` -- [ ] Bar chart: skills ranked by gap size (largest first), current vs target -- [ ] Table: Skill, Current Level, Target Level, Gap, Priority (High/Medium/Low), Est. Hours -- [ ] Question-type weaknesses expandable per skill -- [ ] Topic weaknesses grouped by category -- [ ] "Generate Course" button to auto-generate a course - -**Flow:** -1. Student navigates to gap analysis -2. Reviews skill gaps and weakness breakdowns -3. Clicks "Generate Course" - -**SRS Reference:** Frontend 21, Backend 29 -**Priority:** Must Have - ---- - -### US-SL-25: Start Auto-Generated Course - -**As a** self-learning student, **I want to** view an auto-generated course structure based on my gap analysis and start learning immediately, **so that** I don't have to wait for a teacher to build a course. - -**Acceptance Criteria:** -- [ ] Read-only course preview: title, duration estimate, module list with skill assignments, hours, resource icons -- [ ] "Start Course" button begins the course -- [ ] "Customize" button (optional) allows minor adjustments (reorder modules, skip low-priority skills) -- [ ] Course is created via `POST /api/course/auto-generate` - -**Flow:** -1. System generates course structure from gap analysis -2. Student reviews the proposed course -3. Clicks "Start Course" -4. Course page opens with modules available - -**SRS Reference:** Frontend 22, Backend 30 -**Priority:** Must Have - ---- - -### US-SL-26: Start AI-Generated General English Course - -**As a** self-learning student studying General English, **I want to** launch an AI-generated course tailored to my CEFR level and learning style, **so that** I receive personalised content without waiting for a teacher. - -**Acceptance Criteria:** -- [ ] Confirmation screen shows: Current CEFR Level, Target CEFR Level (editable), Learning Style (editable), Estimated Duration -- [ ] "Start AI Course" triggers content generation via `POST /api/ai-course/english/create` -- [ ] Progress indicator during generation: "Generating your personalised course... Step 2/5: Creating grammar exercises." (1-2 min) -- [ ] Three parallel module tracks: Grammar Track, Skills Track, Vocabulary Track -- [ ] Each module: title, completion status, estimated time, "Start" / "Continue" button -- [ ] Level-up celebration modal when a level is achieved - -**Flow:** -1. Student reviews profile settings -2. Clicks "Start AI Course" -3. Waits for generation (1-2 min) -4. Course appears with three parallel tracks -5. Student progresses through modules - -**SRS Reference:** Frontend 28, Backend 36 -**Priority:** Must Have - ---- - -### US-SL-27: Start AI-Generated IELTS Course - -**As a** self-learning student preparing for IELTS, **I want to** launch an AI-generated IELTS course that targets my weakest skills, **so that** I get focused practice aligned with real IELTS standards. - -**Acceptance Criteria:** -- [ ] Profile summary: Exam Type, Current Bands per skill, Target Band (editable), Skills ranked by gap, Weak question types -- [ ] "Start AI IELTS Course" triggers per-skill content generation -- [ ] Generation progress per skill: "Listening: Generating... Creating audio scripts for Parts 1-4" -- [ ] Four parallel skill paths: Writing, Reading, Speaking, Listening -- [ ] Readiness check modal when engine determines target band likely reached -- [ ] Practice exam option for band confirmation - -**Flow:** -1. Student reviews IELTS gap profile -2. Clicks "Start AI IELTS Course" -3. Waits for per-skill generation -4. Progresses through four skill paths -5. Takes practice exam when prompted - -**SRS Reference:** Frontend 29, Backend 37 -**Priority:** Must Have - ---- - -### US-SL-28: Progress Through Course Modules - -**As a** self-learning student, **I want to** progress through my course modules, viewing my completion status and unlocking new modules, **so that** I learn systematically. - -**Acceptance Criteria:** -- [ ] Course page at `/student/course/:courseId` -- [ ] Header: title, overall progress %, estimated time remaining -- [ ] Module list with collapsible sections per skill -- [ ] Each module: status (locked/available/in-progress/completed), progress bar, resource list with type icons and completion checkmarks -- [ ] "Start" / "Continue" button per module -- [ ] Adaptive indicators (if adaptive progression): "Recommended Next" badge, "Unlocked!" animation, "Skipped" badge for mastered modules - -**Flow:** -1. Student opens course page -2. Sees module list with statuses -3. Opens the next available module -4. Progresses through resources - -**SRS Reference:** Frontend 24, Backend 31 -**Priority:** Must Have - ---- - -### US-SL-29: View In-Platform Resources - -**As a** self-learning student, **I want to** view course resources (PDF, video, audio, exercises) directly on the platform without downloading, **so that** I can learn seamlessly. - -**Acceptance Criteria:** -- [ ] PDF: embedded viewer (browser native or react-pdf) -- [ ] Video: HTML5 `