From 6c93c5d600396ac95f42a280d487e58d87702f55 Mon Sep 17 00:00:00 2001 From: Yamen Ahmad Date: Tue, 7 Apr 2026 01:53:06 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20EnCoach=20V2=20=E2=80=94=20complete=20O?= =?UTF-8?q?WL=20refactor=20with=2015=20new=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full architectural refactor from React to Odoo OWL: - 15 new Odoo modules: signup, placement, exam_template, scoring, course_gen, entity_onboard, ai_course, quality_gate, ielts_validation, pdf_report, verification, math, it, portal, exam_session - 6 modified modules: core (new user fields), exam (template types), adaptive (events/paths/settings), branding (white-label), resources (CEFR/AI fields), taxonomy (Math+IT hierarchies) - ~79 new REST API endpoints across all controller packages - ~50 admin backend views (form/list/kanban/search/menu) - 5 custom OWL components: dashboard, content pool, exam validation, gap analysis, adaptive timeline - POS-inspired full-screen exam session with 9 question renderers - Student portal with 12 website pages (QWeb + OWL) - AI/ML services: IRT 3PL CAT engine, CEFR mapper, quality gates, IELTS validator, SymPy math scorer, speaking evaluator, adaptive engine - Seed data: IELTS/TOEFL/STEP/IC3 templates, 30+ sample questions, English/Math/IT taxonomy trees with 50+ topics - Updated requirements.txt with new dependencies Made-with: Cursor --- .gitignore | 9 + new_project/Developer Workflow Manual.txt | 156 ++ new_project/ENCOACH_WORKFLOWS_BACKEND_SRS.md | 2479 +++++++++++++++++ new_project/ENCOACH_WORKFLOWS_FRONTEND_SRS.md | 2109 ++++++++++++++ .../encoach_adaptive/__init__.py | 3 + .../encoach_adaptive/__manifest__.py | 25 + .../encoach_adaptive/controllers/__init__.py | 1 + .../encoach_adaptive/controllers/adaptive.py | 244 ++ .../encoach_adaptive/models/__init__.py | 8 + .../encoach_adaptive/models/adaptive_event.py | 19 + .../encoach_adaptive/models/adaptive_path.py | 16 + .../models/adaptive_settings.py | 15 + .../security/ir.model.access.csv | 18 + .../encoach_adaptive/services/__init__.py | 1 + .../services/adaptive_engine.py | 149 + .../static/src/css/signal_timeline.css | 54 + .../static/src/js/signal_timeline.js | 153 + .../static/src/xml/signal_timeline.xml | 177 ++ .../views/adaptive_event_views.xml | 69 + .../encoach_adaptive/views/adaptive_menus.xml | 22 + .../views/adaptive_path_views.xml | 46 + .../views/adaptive_settings_views.xml | 54 + .../views/signal_timeline_action.xml | 15 + .../encoach_ai_course/__init__.py | 3 + .../encoach_ai_course/__manifest__.py | 18 + .../encoach_ai_course/controllers/__init__.py | 1 + .../controllers/ai_course.py | 264 ++ .../encoach_ai_course/models/__init__.py | 2 + .../models/ai_generation_log.py | 25 + .../models/ai_ielts_generation_log.py | 26 + .../security/ir.model.access.csv | 3 + .../encoach_ai_course/services/__init__.py | 2 + .../services/english_pipeline.py | 109 + .../services/ielts_pipeline.py | 155 ++ .../views/ai_course_menus.xml | 16 + .../views/ai_generation_log_views.xml | 77 + .../views/ai_ielts_log_views.xml | 59 + .../encoach_branding/__init__.py | 2 + .../encoach_branding/__manifest__.py | 15 + .../encoach_branding/controllers/__init__.py | 1 + .../encoach_branding/controllers/branding.py | 225 ++ .../encoach_branding/models/branding.py | 40 + .../encoach_branding/views/branding_menus.xml | 10 + .../encoach_branding/views/branding_views.xml | 61 + .../encoach_core/__manifest__.py | 26 + .../encoach_core/models/__init__.py | 6 + .../encoach_core/models/encoach_entity.py | 62 + .../encoach_core/models/encoach_user.py | 86 + .../models/entity_level_mapping.py | 15 + .../encoach_core/security/ir.model.access.csv | 16 + .../encoach_core/static/src/css/dashboard.css | 16 + .../encoach_core/static/src/js/dashboard.js | 97 + .../encoach_core/static/src/xml/dashboard.xml | 162 ++ .../encoach_core/views/dashboard_action.xml | 13 + .../encoach_core/views/encoach_menus.xml | 15 + .../views/entity_level_mapping_views.xml | 52 + .../encoach_course_gen/__init__.py | 2 + .../encoach_course_gen/__manifest__.py | 26 + .../controllers/__init__.py | 1 + .../encoach_course_gen/controllers/course.py | 630 +++++ .../encoach_course_gen/models/__init__.py | 3 + .../models/course_extension.py | 21 + .../models/course_module.py | 35 + .../encoach_course_gen/models/gap_profile.py | 18 + .../security/ir.model.access.csv | 3 + .../static/src/css/gap_analysis.css | 40 + .../static/src/js/gap_analysis.js | 163 ++ .../static/src/xml/gap_analysis.xml | 170 ++ .../views/course_gen_menus.xml | 28 + .../views/course_module_views.xml | 76 + .../views/gap_analysis_action.xml | 15 + .../views/gap_profile_views.xml | 74 + .../encoach_entity_onboard/__init__.py | 2 + .../encoach_entity_onboard/__manifest__.py | 15 + .../controllers/__init__.py | 1 + .../controllers/entity_onboard.py | 292 ++ .../security/ir.model.access.csv | 1 + .../services/__init__.py | 2 + .../services/credential_service.py | 72 + .../services/csv_parser.py | 69 + .../custom_addons/encoach_exam/models/exam.py | 120 + .../encoach_exam_session/__init__.py | 1 + .../encoach_exam_session/__manifest__.py | 34 + .../controllers/__init__.py | 1 + .../controllers/exam_session.py | 30 + .../security/ir.model.access.csv | 1 + .../static/src/css/exam_session.css | 1241 +++++++++ .../static/src/js/exam_app.js | 423 +++ .../static/src/js/placement_app.js | 199 ++ .../static/src/js/question_renderers.js | 345 +++ .../static/src/xml/exam_app.xml | 302 ++ .../static/src/xml/placement_app.xml | 158 ++ .../static/src/xml/question_renderers.xml | 188 ++ .../views/exam_session_templates.xml | 29 + .../encoach_exam_template/__init__.py | 2 + .../encoach_exam_template/__manifest__.py | 39 + .../controllers/__init__.py | 3 + .../controllers/custom_exam.py | 364 +++ .../controllers/ielts_exam.py | 504 ++++ .../controllers/templates.py | 130 + .../data/ielts_templates.xml | 117 + .../data/sample_passages.xml | 90 + .../data/sample_questions.xml | 770 +++++ .../encoach_exam_template/models/__init__.py | 10 + .../models/audio_file.py | 30 + .../models/exam_assignment.py | 18 + .../models/exam_custom.py | 26 + .../models/exam_custom_section.py | 24 + .../models/exam_template.py | 27 + .../encoach_exam_template/models/passage.py | 46 + .../encoach_exam_template/models/question.py | 69 + .../encoach_exam_template/models/rubric.py | 18 + .../models/speaking_card.py | 24 + .../models/writing_prompt.py | 27 + .../security/ir.model.access.csv | 11 + .../static/src/css/content_pool.css | 36 + .../static/src/js/content_pool.js | 154 + .../static/src/js/exam_validation.js | 211 ++ .../static/src/xml/content_pool.xml | 224 ++ .../static/src/xml/exam_validation.xml | 132 + .../views/audio_file_views.xml | 102 + .../views/content_pool_action.xml | 15 + .../views/exam_assignment_views.xml | 82 + .../views/exam_custom_views.xml | 107 + .../views/exam_template_menus.xml | 62 + .../views/exam_template_views.xml | 103 + .../views/exam_validation_action.xml | 15 + .../views/passage_views.xml | 107 + .../views/question_views.xml | 130 + .../views/rubric_views.xml | 79 + .../views/speaking_card_views.xml | 105 + .../views/writing_prompt_views.xml | 97 + .../encoach_ielts_validation/__init__.py | 1 + .../encoach_ielts_validation/__manifest__.py | 15 + .../security/ir.model.access.csv | 1 + .../services/__init__.py | 1 + .../services/validator.py | 334 +++ .../custom_addons/encoach_it/__init__.py | 1 + .../custom_addons/encoach_it/__manifest__.py | 15 + .../encoach_it/security/ir.model.access.csv | 1 + .../encoach_it/services/__init__.py | 1 + .../encoach_it/services/code_scorer.py | 255 ++ .../custom_addons/encoach_math/__init__.py | 1 + .../encoach_math/__manifest__.py | 18 + .../encoach_math/security/ir.model.access.csv | 1 + .../encoach_math/services/__init__.py | 1 + .../encoach_math/services/math_scorer.py | 113 + .../encoach_pdf_report/__init__.py | 2 + .../encoach_pdf_report/__manifest__.py | 18 + .../controllers/__init__.py | 1 + .../encoach_pdf_report/controllers/reports.py | 55 + .../security/ir.model.access.csv | 1 + .../encoach_pdf_report/services/__init__.py | 1 + .../services/pdf_generator.py | 149 + .../encoach_placement/__init__.py | 3 + .../encoach_placement/__manifest__.py | 21 + .../encoach_placement/controllers/__init__.py | 1 + .../controllers/placement.py | 403 +++ .../encoach_placement/models/__init__.py | 2 + .../encoach_placement/models/cat_session.py | 28 + .../models/student_ability.py | 26 + .../security/ir.model.access.csv | 3 + .../encoach_placement/services/__init__.py | 2 + .../encoach_placement/services/cat_engine.py | 159 ++ .../encoach_placement/services/cefr_mapper.py | 83 + .../views/cat_session_views.xml | 76 + .../views/placement_menus.xml | 16 + .../views/student_ability_views.xml | 69 + .../custom_addons/encoach_portal/__init__.py | 1 + .../encoach_portal/__manifest__.py | 33 + .../encoach_portal/controllers/__init__.py | 1 + .../encoach_portal/controllers/portal.py | 136 + .../security/ir.model.access.csv | 1 + .../encoach_portal/static/src/css/portal.css | 139 + .../static/src/js/onboarding_wizard.js | 150 + .../static/src/xml/onboarding_wizard.xml | 145 + .../encoach_portal/views/portal_menus.xml | 33 + .../encoach_portal/views/templates.xml | 858 ++++++ .../encoach_quality_gate/__init__.py | 2 + .../encoach_quality_gate/__manifest__.py | 17 + .../encoach_quality_gate/models/__init__.py | 1 + .../models/ielts_standards_check.py | 22 + .../security/ir.model.access.csv | 2 + .../encoach_quality_gate/services/__init__.py | 1 + .../services/quality_checker.py | 156 ++ .../views/ielts_check_views.xml | 65 + .../views/quality_menus.xml | 10 + .../encoach_resources/models/resource.py | 67 + .../custom_addons/encoach_scoring/__init__.py | 3 + .../encoach_scoring/__manifest__.py | 20 + .../encoach_scoring/controllers/__init__.py | 3 + .../controllers/exam_session.py | 427 +++ .../encoach_scoring/controllers/grading.py | 372 +++ .../controllers/score_release.py | 129 + .../encoach_scoring/models/__init__.py | 4 + .../encoach_scoring/models/feedback.py | 16 + .../encoach_scoring/models/score.py | 30 + .../encoach_scoring/models/student_answer.py | 14 + .../encoach_scoring/models/student_attempt.py | 41 + .../security/ir.model.access.csv | 5 + .../encoach_scoring/services/__init__.py | 1 + .../services/speaking_evaluator.py | 67 + .../encoach_scoring/views/feedback_views.xml | 42 + .../encoach_scoring/views/score_views.xml | 44 + .../encoach_scoring/views/scoring_menus.xml | 22 + .../views/student_answer_views.xml | 45 + .../views/student_attempt_views.xml | 133 + .../custom_addons/encoach_signup/__init__.py | 3 + .../encoach_signup/__manifest__.py | 18 + .../encoach_signup/controllers/__init__.py | 1 + .../encoach_signup/controllers/auth.py | 277 ++ .../encoach_signup/models/__init__.py | 2 + .../encoach_signup/models/otp.py | 13 + .../encoach_signup/models/student_profile.py | 28 + .../security/ir.model.access.csv | 3 + .../encoach_signup/services/__init__.py | 2 + .../services/captcha_service.py | 33 + .../encoach_signup/services/otp_service.py | 59 + .../encoach_signup/views/otp_views.xml | 61 + .../encoach_signup/views/signup_menus.xml | 16 + .../views/student_profile_views.xml | 75 + .../encoach_taxonomy/__init__.py | 2 + .../encoach_taxonomy/__manifest__.py | 15 + .../encoach_taxonomy/controllers/__init__.py | 1 + .../encoach_taxonomy/controllers/taxonomy.py | 239 ++ .../data/math_it_taxonomy.xml | 304 ++ .../encoach_verification/__init__.py | 1 + .../encoach_verification/__manifest__.py | 15 + .../controllers/__init__.py | 1 + .../controllers/verify.py | 71 + .../security/ir.model.access.csv | 1 + new_project/encoach_workflows_v3.md | 587 ++++ new_project/requirements.txt | 19 + 233 files changed, 23446 insertions(+) create mode 100644 new_project/Developer Workflow Manual.txt create mode 100644 new_project/ENCOACH_WORKFLOWS_BACKEND_SRS.md create mode 100644 new_project/ENCOACH_WORKFLOWS_FRONTEND_SRS.md create mode 100644 new_project/custom_addons/encoach_adaptive/__init__.py create mode 100644 new_project/custom_addons/encoach_adaptive/__manifest__.py create mode 100644 new_project/custom_addons/encoach_adaptive/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_adaptive/controllers/adaptive.py create mode 100644 new_project/custom_addons/encoach_adaptive/models/__init__.py create mode 100644 new_project/custom_addons/encoach_adaptive/models/adaptive_event.py create mode 100644 new_project/custom_addons/encoach_adaptive/models/adaptive_path.py create mode 100644 new_project/custom_addons/encoach_adaptive/models/adaptive_settings.py create mode 100644 new_project/custom_addons/encoach_adaptive/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_adaptive/services/__init__.py create mode 100644 new_project/custom_addons/encoach_adaptive/services/adaptive_engine.py create mode 100644 new_project/custom_addons/encoach_adaptive/static/src/css/signal_timeline.css create mode 100644 new_project/custom_addons/encoach_adaptive/static/src/js/signal_timeline.js create mode 100644 new_project/custom_addons/encoach_adaptive/static/src/xml/signal_timeline.xml create mode 100644 new_project/custom_addons/encoach_adaptive/views/adaptive_event_views.xml create mode 100644 new_project/custom_addons/encoach_adaptive/views/adaptive_menus.xml create mode 100644 new_project/custom_addons/encoach_adaptive/views/adaptive_path_views.xml create mode 100644 new_project/custom_addons/encoach_adaptive/views/adaptive_settings_views.xml create mode 100644 new_project/custom_addons/encoach_adaptive/views/signal_timeline_action.xml create mode 100644 new_project/custom_addons/encoach_ai_course/__init__.py create mode 100644 new_project/custom_addons/encoach_ai_course/__manifest__.py create mode 100644 new_project/custom_addons/encoach_ai_course/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_ai_course/controllers/ai_course.py create mode 100644 new_project/custom_addons/encoach_ai_course/models/__init__.py create mode 100644 new_project/custom_addons/encoach_ai_course/models/ai_generation_log.py create mode 100644 new_project/custom_addons/encoach_ai_course/models/ai_ielts_generation_log.py create mode 100644 new_project/custom_addons/encoach_ai_course/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_ai_course/services/__init__.py create mode 100644 new_project/custom_addons/encoach_ai_course/services/english_pipeline.py create mode 100644 new_project/custom_addons/encoach_ai_course/services/ielts_pipeline.py create mode 100644 new_project/custom_addons/encoach_ai_course/views/ai_course_menus.xml create mode 100644 new_project/custom_addons/encoach_ai_course/views/ai_generation_log_views.xml create mode 100644 new_project/custom_addons/encoach_ai_course/views/ai_ielts_log_views.xml create mode 100644 new_project/custom_addons/encoach_branding/__init__.py create mode 100644 new_project/custom_addons/encoach_branding/__manifest__.py create mode 100644 new_project/custom_addons/encoach_branding/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_branding/controllers/branding.py create mode 100644 new_project/custom_addons/encoach_branding/models/branding.py create mode 100644 new_project/custom_addons/encoach_branding/views/branding_menus.xml create mode 100644 new_project/custom_addons/encoach_branding/views/branding_views.xml create mode 100644 new_project/custom_addons/encoach_core/__manifest__.py create mode 100644 new_project/custom_addons/encoach_core/models/__init__.py create mode 100644 new_project/custom_addons/encoach_core/models/encoach_entity.py create mode 100644 new_project/custom_addons/encoach_core/models/encoach_user.py create mode 100644 new_project/custom_addons/encoach_core/models/entity_level_mapping.py create mode 100644 new_project/custom_addons/encoach_core/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_core/static/src/css/dashboard.css create mode 100644 new_project/custom_addons/encoach_core/static/src/js/dashboard.js create mode 100644 new_project/custom_addons/encoach_core/static/src/xml/dashboard.xml create mode 100644 new_project/custom_addons/encoach_core/views/dashboard_action.xml create mode 100644 new_project/custom_addons/encoach_core/views/encoach_menus.xml create mode 100644 new_project/custom_addons/encoach_core/views/entity_level_mapping_views.xml create mode 100644 new_project/custom_addons/encoach_course_gen/__init__.py create mode 100644 new_project/custom_addons/encoach_course_gen/__manifest__.py create mode 100644 new_project/custom_addons/encoach_course_gen/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_course_gen/controllers/course.py create mode 100644 new_project/custom_addons/encoach_course_gen/models/__init__.py create mode 100644 new_project/custom_addons/encoach_course_gen/models/course_extension.py create mode 100644 new_project/custom_addons/encoach_course_gen/models/course_module.py create mode 100644 new_project/custom_addons/encoach_course_gen/models/gap_profile.py create mode 100644 new_project/custom_addons/encoach_course_gen/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_course_gen/static/src/css/gap_analysis.css create mode 100644 new_project/custom_addons/encoach_course_gen/static/src/js/gap_analysis.js create mode 100644 new_project/custom_addons/encoach_course_gen/static/src/xml/gap_analysis.xml create mode 100644 new_project/custom_addons/encoach_course_gen/views/course_gen_menus.xml create mode 100644 new_project/custom_addons/encoach_course_gen/views/course_module_views.xml create mode 100644 new_project/custom_addons/encoach_course_gen/views/gap_analysis_action.xml create mode 100644 new_project/custom_addons/encoach_course_gen/views/gap_profile_views.xml create mode 100644 new_project/custom_addons/encoach_entity_onboard/__init__.py create mode 100644 new_project/custom_addons/encoach_entity_onboard/__manifest__.py create mode 100644 new_project/custom_addons/encoach_entity_onboard/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_entity_onboard/controllers/entity_onboard.py create mode 100644 new_project/custom_addons/encoach_entity_onboard/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_entity_onboard/services/__init__.py create mode 100644 new_project/custom_addons/encoach_entity_onboard/services/credential_service.py create mode 100644 new_project/custom_addons/encoach_entity_onboard/services/csv_parser.py create mode 100644 new_project/custom_addons/encoach_exam/models/exam.py create mode 100644 new_project/custom_addons/encoach_exam_session/__init__.py create mode 100644 new_project/custom_addons/encoach_exam_session/__manifest__.py create mode 100644 new_project/custom_addons/encoach_exam_session/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_exam_session/controllers/exam_session.py create mode 100644 new_project/custom_addons/encoach_exam_session/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/css/exam_session.css create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/js/exam_app.js create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/js/placement_app.js create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/js/question_renderers.js create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/xml/exam_app.xml create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/xml/placement_app.xml create mode 100644 new_project/custom_addons/encoach_exam_session/static/src/xml/question_renderers.xml create mode 100644 new_project/custom_addons/encoach_exam_session/views/exam_session_templates.xml create mode 100644 new_project/custom_addons/encoach_exam_template/__init__.py create mode 100644 new_project/custom_addons/encoach_exam_template/__manifest__.py create mode 100644 new_project/custom_addons/encoach_exam_template/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_exam_template/controllers/custom_exam.py create mode 100644 new_project/custom_addons/encoach_exam_template/controllers/ielts_exam.py create mode 100644 new_project/custom_addons/encoach_exam_template/controllers/templates.py create mode 100644 new_project/custom_addons/encoach_exam_template/data/ielts_templates.xml create mode 100644 new_project/custom_addons/encoach_exam_template/data/sample_passages.xml create mode 100644 new_project/custom_addons/encoach_exam_template/data/sample_questions.xml create mode 100644 new_project/custom_addons/encoach_exam_template/models/__init__.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/audio_file.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/exam_assignment.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/exam_custom.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/exam_custom_section.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/exam_template.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/passage.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/question.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/rubric.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/speaking_card.py create mode 100644 new_project/custom_addons/encoach_exam_template/models/writing_prompt.py create mode 100644 new_project/custom_addons/encoach_exam_template/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_exam_template/static/src/css/content_pool.css create mode 100644 new_project/custom_addons/encoach_exam_template/static/src/js/content_pool.js create mode 100644 new_project/custom_addons/encoach_exam_template/static/src/js/exam_validation.js create mode 100644 new_project/custom_addons/encoach_exam_template/static/src/xml/content_pool.xml create mode 100644 new_project/custom_addons/encoach_exam_template/static/src/xml/exam_validation.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/audio_file_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/content_pool_action.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/exam_assignment_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/exam_custom_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/exam_template_menus.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/exam_template_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/exam_validation_action.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/passage_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/question_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/rubric_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/speaking_card_views.xml create mode 100644 new_project/custom_addons/encoach_exam_template/views/writing_prompt_views.xml create mode 100644 new_project/custom_addons/encoach_ielts_validation/__init__.py create mode 100644 new_project/custom_addons/encoach_ielts_validation/__manifest__.py create mode 100644 new_project/custom_addons/encoach_ielts_validation/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_ielts_validation/services/__init__.py create mode 100644 new_project/custom_addons/encoach_ielts_validation/services/validator.py create mode 100644 new_project/custom_addons/encoach_it/__init__.py create mode 100644 new_project/custom_addons/encoach_it/__manifest__.py create mode 100644 new_project/custom_addons/encoach_it/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_it/services/__init__.py create mode 100644 new_project/custom_addons/encoach_it/services/code_scorer.py create mode 100644 new_project/custom_addons/encoach_math/__init__.py create mode 100644 new_project/custom_addons/encoach_math/__manifest__.py create mode 100644 new_project/custom_addons/encoach_math/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_math/services/__init__.py create mode 100644 new_project/custom_addons/encoach_math/services/math_scorer.py create mode 100644 new_project/custom_addons/encoach_pdf_report/__init__.py create mode 100644 new_project/custom_addons/encoach_pdf_report/__manifest__.py create mode 100644 new_project/custom_addons/encoach_pdf_report/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_pdf_report/controllers/reports.py create mode 100644 new_project/custom_addons/encoach_pdf_report/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_pdf_report/services/__init__.py create mode 100644 new_project/custom_addons/encoach_pdf_report/services/pdf_generator.py create mode 100644 new_project/custom_addons/encoach_placement/__init__.py create mode 100644 new_project/custom_addons/encoach_placement/__manifest__.py create mode 100644 new_project/custom_addons/encoach_placement/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_placement/controllers/placement.py create mode 100644 new_project/custom_addons/encoach_placement/models/__init__.py create mode 100644 new_project/custom_addons/encoach_placement/models/cat_session.py create mode 100644 new_project/custom_addons/encoach_placement/models/student_ability.py create mode 100644 new_project/custom_addons/encoach_placement/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_placement/services/__init__.py create mode 100644 new_project/custom_addons/encoach_placement/services/cat_engine.py create mode 100644 new_project/custom_addons/encoach_placement/services/cefr_mapper.py create mode 100644 new_project/custom_addons/encoach_placement/views/cat_session_views.xml create mode 100644 new_project/custom_addons/encoach_placement/views/placement_menus.xml create mode 100644 new_project/custom_addons/encoach_placement/views/student_ability_views.xml create mode 100644 new_project/custom_addons/encoach_portal/__init__.py create mode 100644 new_project/custom_addons/encoach_portal/__manifest__.py create mode 100644 new_project/custom_addons/encoach_portal/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_portal/controllers/portal.py create mode 100644 new_project/custom_addons/encoach_portal/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_portal/static/src/css/portal.css create mode 100644 new_project/custom_addons/encoach_portal/static/src/js/onboarding_wizard.js create mode 100644 new_project/custom_addons/encoach_portal/static/src/xml/onboarding_wizard.xml create mode 100644 new_project/custom_addons/encoach_portal/views/portal_menus.xml create mode 100644 new_project/custom_addons/encoach_portal/views/templates.xml create mode 100644 new_project/custom_addons/encoach_quality_gate/__init__.py create mode 100644 new_project/custom_addons/encoach_quality_gate/__manifest__.py create mode 100644 new_project/custom_addons/encoach_quality_gate/models/__init__.py create mode 100644 new_project/custom_addons/encoach_quality_gate/models/ielts_standards_check.py create mode 100644 new_project/custom_addons/encoach_quality_gate/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_quality_gate/services/__init__.py create mode 100644 new_project/custom_addons/encoach_quality_gate/services/quality_checker.py create mode 100644 new_project/custom_addons/encoach_quality_gate/views/ielts_check_views.xml create mode 100644 new_project/custom_addons/encoach_quality_gate/views/quality_menus.xml create mode 100644 new_project/custom_addons/encoach_resources/models/resource.py create mode 100644 new_project/custom_addons/encoach_scoring/__init__.py create mode 100644 new_project/custom_addons/encoach_scoring/__manifest__.py create mode 100644 new_project/custom_addons/encoach_scoring/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_scoring/controllers/exam_session.py create mode 100644 new_project/custom_addons/encoach_scoring/controllers/grading.py create mode 100644 new_project/custom_addons/encoach_scoring/controllers/score_release.py create mode 100644 new_project/custom_addons/encoach_scoring/models/__init__.py create mode 100644 new_project/custom_addons/encoach_scoring/models/feedback.py create mode 100644 new_project/custom_addons/encoach_scoring/models/score.py create mode 100644 new_project/custom_addons/encoach_scoring/models/student_answer.py create mode 100644 new_project/custom_addons/encoach_scoring/models/student_attempt.py create mode 100644 new_project/custom_addons/encoach_scoring/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_scoring/services/__init__.py create mode 100644 new_project/custom_addons/encoach_scoring/services/speaking_evaluator.py create mode 100644 new_project/custom_addons/encoach_scoring/views/feedback_views.xml create mode 100644 new_project/custom_addons/encoach_scoring/views/score_views.xml create mode 100644 new_project/custom_addons/encoach_scoring/views/scoring_menus.xml create mode 100644 new_project/custom_addons/encoach_scoring/views/student_answer_views.xml create mode 100644 new_project/custom_addons/encoach_scoring/views/student_attempt_views.xml create mode 100644 new_project/custom_addons/encoach_signup/__init__.py create mode 100644 new_project/custom_addons/encoach_signup/__manifest__.py create mode 100644 new_project/custom_addons/encoach_signup/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_signup/controllers/auth.py create mode 100644 new_project/custom_addons/encoach_signup/models/__init__.py create mode 100644 new_project/custom_addons/encoach_signup/models/otp.py create mode 100644 new_project/custom_addons/encoach_signup/models/student_profile.py create mode 100644 new_project/custom_addons/encoach_signup/security/ir.model.access.csv create mode 100644 new_project/custom_addons/encoach_signup/services/__init__.py create mode 100644 new_project/custom_addons/encoach_signup/services/captcha_service.py create mode 100644 new_project/custom_addons/encoach_signup/services/otp_service.py create mode 100644 new_project/custom_addons/encoach_signup/views/otp_views.xml create mode 100644 new_project/custom_addons/encoach_signup/views/signup_menus.xml create mode 100644 new_project/custom_addons/encoach_signup/views/student_profile_views.xml create mode 100644 new_project/custom_addons/encoach_taxonomy/__init__.py create mode 100644 new_project/custom_addons/encoach_taxonomy/__manifest__.py create mode 100644 new_project/custom_addons/encoach_taxonomy/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_taxonomy/controllers/taxonomy.py create mode 100644 new_project/custom_addons/encoach_taxonomy/data/math_it_taxonomy.xml create mode 100644 new_project/custom_addons/encoach_verification/__init__.py create mode 100644 new_project/custom_addons/encoach_verification/__manifest__.py create mode 100644 new_project/custom_addons/encoach_verification/controllers/__init__.py create mode 100644 new_project/custom_addons/encoach_verification/controllers/verify.py create mode 100644 new_project/custom_addons/encoach_verification/security/ir.model.access.csv create mode 100644 new_project/encoach_workflows_v3.md create mode 100644 new_project/requirements.txt diff --git a/.gitignore b/.gitignore index 176f9188..36647579 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,12 @@ Thumbs.db # Docker *.tar + +# Frontend repo (separate repository) +new_project/encoach_frontend_new_v1/ + +# Local dev artifacts +miniconda3/ +pgdata/ +.conda-envs/ +.conda-pkgs/ diff --git a/new_project/Developer Workflow Manual.txt b/new_project/Developer Workflow Manual.txt new file mode 100644 index 00000000..4d7f2827 --- /dev/null +++ b/new_project/Developer Workflow Manual.txt @@ -0,0 +1,156 @@ + + + +# 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/new_project/ENCOACH_WORKFLOWS_BACKEND_SRS.md b/new_project/ENCOACH_WORKFLOWS_BACKEND_SRS.md new file mode 100644 index 00000000..515e02a5 --- /dev/null +++ b/new_project/ENCOACH_WORKFLOWS_BACKEND_SRS.md @@ -0,0 +1,2479 @@ +# EnCoach Platform -- Backend Software Requirements Specification (Odoo 19) + +**Document Version:** 1.1 +**Date:** April 2026 +**Status:** Active -- Ready for Development +**Source Document:** `encoach_workflows_v3.pdf` (v3.0, April 2026) +**Frontend Reference:** `ENCOACH_WORKFLOWS_FRONTEND_SRS.md` (v1.1) +**Audience:** Odoo Developer (full-stack, using Cursor IDE) +**Scope:** All new Odoo modules, database models, API endpoints, business logic, and AI integrations required to implement the 6 platform workflows, exam template paths (international + custom), adaptive learning engine (4 phases), Math/IT support, and white-labelling. + +--- + +## Implementation Context + +| Artifact | Location | +|----------|----------| +| **Frontend Repository** | `https://git.albousalh.com/devops/encoach_frontend_new_v2.git` (branch: `main`) | +| **Backend Repository** | `https://git.albousalh.com/devops/encoach_backend_new_v2.git` (branch: `main`) | +| **Staging Frontend** | `http://5.189.151.117:3000` | +| **Staging Backend (Odoo 19)** | `http://5.189.151.117:8069` | +| **Backend Stack** | Odoo 19, Python 3.11, PostgreSQL 16, Docker Compose | +| **AI Stack** | OpenAI GPT (4o, 3.5-turbo), OpenAI Whisper (base model, local), AWS Polly (neural TTS), FAISS + Sentence Transformers (all-MiniLM-L6-v2) | +| **LMS Foundation** | OpenEduCat 19 (14 community modules) | + +--- + +## Table of Contents + +**Part I -- Context and Baseline** +1. [Introduction](#1-introduction) +2. [Existing System Summary](#2-existing-system-summary) +3. [Module Architecture](#3-module-architecture) + +**Part II -- Database Schema** +4. [Content Tables](#4-content-tables) +5. [Exam Tables](#5-exam-tables) +6. [User and Profile Tables](#6-user-and-profile-tables) +7. [Course Tables](#7-course-tables) +8. [Progress and Results Tables](#8-progress-and-results-tables) +9. [Adaptive Learning Tables](#9-adaptive-learning-tables) +10. [Entity and Institutional Tables](#10-entity-and-institutional-tables) +11. [AI Generation Tables](#11-ai-generation-tables) + +**Part III -- Workflow 1: User Signup** +12. [CAPTCHA Integration](#12-captcha-integration) +13. [OTP Email Service](#13-otp-email-service) +14. [Onboarding Wizard Data Model](#14-onboarding-wizard-data-model) + +**Part IV -- Workflow 2: Placement Test** +15. [CAT Engine](#15-cat-engine) +16. [Question Bank with IRT Parameters](#16-question-bank-with-irt-parameters) +17. [CEFR Mapping Algorithm](#17-cefr-mapping-algorithm) +18. [Speaking AI Evaluation](#18-speaking-ai-evaluation) + +**Part V -- Workflow 3: Exam Configuration (International + Custom)** +19. [Exam Template Architecture](#19-exam-template-architecture) +20. [Fixed Template System (International)](#20-fixed-template-system-international) +21. [Content Pool Query Engine](#21-content-pool-query-engine) +22. [Assembly Modes](#22-assembly-modes) +23. [Custom Template System](#23-custom-template-system) +24. [Exam Validation Rules](#24-exam-validation-rules) + +**Part VI -- Workflow 4: General English Exam** +25. [Exam Session Management](#25-exam-session-management) +26. [Auto-Scoring Engine](#26-auto-scoring-engine) +27. [Score Release Gate](#27-score-release-gate) +28. [PDF Report Generation with QR Code](#28-pdf-report-generation-with-qr-code) + +**Part VII -- Workflow 5: Course Generation** +29. [Gap Analysis Engine](#29-gap-analysis-engine) +30. [Auto Course Structure Generation](#30-auto-course-structure-generation) +31. [Adaptive Progression Engine](#31-adaptive-progression-engine) + +**Part VIII -- Workflow 6: Entity Student Onboarding** +32. [CSV Parsing and Validation](#32-csv-parsing-and-validation) +33. [Bulk Account Creation](#33-bulk-account-creation) +34. [Credential Email Service](#34-credential-email-service) +35. [Entity Level Mapping](#35-entity-level-mapping) + +**Part IX -- AI Course Generation** +36. [General English AI Content Pipeline](#36-general-english-ai-content-pipeline) +37. [AI IELTS Content Pipeline](#37-ai-ielts-content-pipeline) +38. [Quality Gate Engine](#38-quality-gate-engine) +39. [IELTS Standards Validation Engine](#39-ielts-standards-validation-engine) + +**Part X -- Adaptive Learning Engine (4 Phases)** +40. [Phase 1 -- Rule-Based Engine (MVP)](#40-phase-1----rule-based-engine-mvp) +41. [Phase 2 -- IRT-Based CAT](#41-phase-2----irt-based-cat) +42. [Phase 3 -- Collaborative Filtering](#42-phase-3----collaborative-filtering) +43. [Phase 4 -- Full ML](#43-phase-4----full-ml) + +**Part XI -- White-Labelling** +44. [Entity Branding Model](#44-entity-branding-model) +45. [Subdomain Routing](#45-subdomain-routing) + +**Part XII -- Math and IT Backend** +46. [Subject-Specific Question Types](#46-subject-specific-question-types) +47. [Subject Taxonomy Extension](#47-subject-taxonomy-extension) + +**Part XIII -- API Endpoint Specification** +48. [Auth and Signup Endpoints](#48-auth-and-signup-endpoints) +49. [Placement Test Endpoints](#49-placement-test-endpoints) +50. [Exam Template Endpoints](#50-exam-template-endpoints) +51. [IELTS Exam Endpoints](#51-ielts-exam-endpoints) +52. [Custom Exam Endpoints](#52-custom-exam-endpoints) +53. [Exam Session Endpoints](#53-exam-session-endpoints) +54. [Grading Endpoints](#54-grading-endpoints) +55. [Course Generation Endpoints](#55-course-generation-endpoints) +56. [AI Course Generation Endpoints](#56-ai-course-generation-endpoints) +57. [Entity Onboarding Endpoints](#57-entity-onboarding-endpoints) +58. [Adaptive Engine Endpoints](#58-adaptive-engine-endpoints) +59. [Entity and Branding Endpoints](#59-entity-and-branding-endpoints) +60. [Score Release Endpoints](#60-score-release-endpoints) +61. [Report and Verification Endpoints](#61-report-and-verification-endpoints) +62. [Taxonomy Endpoints](#62-taxonomy-endpoints) + +**Part XIV -- AI/ML Service Integration** +63. [OpenAI GPT Integration](#63-openai-gpt-integration) +64. [OpenAI Whisper Integration](#64-openai-whisper-integration) +65. [Quality Gate Algorithms](#65-quality-gate-algorithms) +66. [IRT Mathematical Model](#66-irt-mathematical-model) + +--- + +# Part I -- Context and Baseline + +## 1. Introduction + +### 1.1 Purpose + +This document specifies every backend component required to implement the EnCoach platform workflows as defined in `encoach_workflows_v3.pdf` (v3.0). It covers Odoo modules, database models, API endpoints, business logic, AI/ML integrations, and the adaptive learning engine across all 4 implementation phases. + +### 1.2 Scope + +The backend must support 6 workflows, each with multiple phases: + +| # | Workflow | Key Backend Components | +|---|----------|----------------------| +| 1 | User Signup (Individual) | CAPTCHA verification, OTP email, onboarding data model | +| 2 | Placement Test | CAT engine with IRT, question bank calibration, speaking AI eval | +| 3 | IELTS Exam Configuration | Fixed template system, content pool query, assembly modes | +| 4 | General English Exam | Session management, auto-scoring, score release gate, PDF+QR | +| 5 | Course Generation | Gap analysis engine, auto course structure, adaptive progression | +| 6 | Entity Student Onboarding | CSV parsing, bulk account creation, credential emails | +| -- | AI Course Generation | General English + IELTS content pipelines, quality gates | +| -- | Adaptive Learning (4 phases) | Rule-based, IRT-CAT, collaborative filter, full ML | +| -- | White-Labelling | Entity branding, subdomain routing | +| -- | Math/IT | Subject-specific question types, taxonomy extension | + +### 1.3 Design Principles + +These principles from the workflow document MUST be enforced in the backend: + +1. **Exam type is always the root parameter.** All content queries, scoring algorithms, and course generation logic branch on `exam_type` (Academic vs General Training) first. +2. **All results stored on all paths.** Every exam attempt is written to `student_attempts` regardless of pass/fail. No early exit without a DB write. +3. **Shared content database.** The same content tables serve both exam and course workflows. Content is never duplicated. +4. **Entity data isolation.** All student data records include `entity_id` (nullable FK). `entity_id=null` means individual student. `entity_id=X` means institutional. Queries must be scoped by entity when the caller is an entity admin. +5. **Score release gate.** Results for official exams (`results_release_mode=manual_approval`) are NOT visible to students until an entity admin approves release. + +--- + +## 2. Existing System Summary + +### 2.1 Current Module Inventory + +The backend currently has 27 custom modules and 14 OpenEduCat modules: + +**Custom `encoach_*` modules (27):** + +| Module | Purpose | +|--------|---------| +| `encoach_core` | Base models, extends `res.users` | +| `encoach_api` | Main API controller package (16 controllers) | +| `encoach_lms_api` | LMS API controller package (29 controllers) | +| `encoach_adaptive_api` | Adaptive learning API (6 controllers) | +| `encoach_resources` | Resource management REST API | +| `encoach_taxonomy` | Subject taxonomy models | +| `encoach_adaptive` | Adaptive learning models | +| `encoach_adaptive_ai` | AI-powered adaptive features | +| `encoach_exam` | Exam engine models | +| `encoach_ai` | Core AI integration | +| `encoach_ai_generation` | AI content generation | +| `encoach_ai_grading` | AI grading engine | +| `encoach_ai_media` | AI media (TTS, video) | +| `encoach_courseware` | Chapter and material models | +| `encoach_communication` | Discussion boards, messaging | +| `encoach_notification` | Notification engine | +| `encoach_faq` | FAQ system | +| `encoach_approval` | Approval workflows | +| `encoach_assignment` | Assignment management | +| `encoach_classroom` | Classroom management | +| `encoach_stats` | Statistics and analytics | +| `encoach_training` | Training content | +| `encoach_subscription` | Subscriptions and payments | +| `encoach_registration` | Registration management | +| `encoach_ticket` | Support ticketing | +| `encoach_sis` | Student Information System integration | +| `encoach_branding` | White-label branding | + +**OpenEduCat modules (14):** +`openeducat_core`, `openeducat_timetable`, `openeducat_attendance`, `openeducat_exam`, `openeducat_assignment`, `openeducat_admission`, `openeducat_classroom`, `openeducat_facility`, `openeducat_fees`, `openeducat_library`, `openeducat_activity`, `openeducat_parent`, `openeducat_erp`, `theme_web_openeducat` + +### 2.2 Current API Routes + +~377 REST endpoints across 4 controller packages: + +| Package | Controllers | Route Count | +|---------|------------|-------------| +| `encoach_api` | 16 | ~100 | +| `encoach_lms_api` | 29 | ~200 | +| `encoach_adaptive_api` | 6 | ~40 | +| `encoach_resources` | 1 | ~10 | + +### 2.3 Docker Deployment + +```yaml +services: + db: + image: postgres:16 + environment: + POSTGRES_DB: encoach_v2 + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + odoo: + build: . + image: encoach-backend:latest + ports: + - "8069:8069" + volumes: + - ./new_project/custom_addons:/mnt/custom_addons:ro + - ./new_project/openeducat_erp-19.0:/mnt/openeducat:ro +``` + +--- + +## 3. Module Architecture + +### 3.1 New Modules Required + +The following new Odoo modules must be created to support the workflow features: + +| Module | Purpose | Dependencies | +|--------|---------|-------------| +| `encoach_signup` | CAPTCHA, OTP, onboarding wizard logic | `encoach_core` | +| `encoach_placement` | CAT engine, placement session, IRT scoring | `encoach_core`, `encoach_exam`, `encoach_adaptive` | +| `encoach_ielts_template` | Fixed IELTS template, content pool query, assembly | `encoach_exam`, `encoach_resources` | +| `encoach_scoring` | Auto-scoring, rubric scoring, score release gate | `encoach_exam`, `encoach_core` | +| `encoach_course_generation` | Gap analysis, auto course structure, module builder | `encoach_core`, `encoach_resources`, `encoach_adaptive` | +| `encoach_entity_onboarding` | CSV upload, bulk create, credential emails | `encoach_core`, `encoach_sis` | +| `encoach_ai_course` | AI course generation (English + IELTS pipelines) | `encoach_ai_generation`, `encoach_resources`, `encoach_adaptive` | +| `encoach_quality_gate` | Content quality validation engine | `encoach_ai_generation` | +| `encoach_ielts_validation` | IELTS-specific two-layer validation pipeline | `encoach_quality_gate`, `encoach_ielts_template` | +| `encoach_pdf_report` | PDF generation with QR code | `encoach_scoring` | +| `encoach_verification` | Public score verification | `encoach_scoring` | +| `encoach_math` | Math question types, formula storage | `encoach_exam`, `encoach_taxonomy` | +| `encoach_it` | IT question types, code execution | `encoach_exam`, `encoach_taxonomy` | + +### 3.2 Modified Existing Modules + +| Module | Modifications | +|--------|-------------| +| `encoach_core` | Add `first_login`, `account_source`, `entity_id` fields to `res.users` | +| `encoach_branding` | Add white-label configuration fields (logo, colours, subdomain, favicon) | +| `encoach_adaptive` | Extend with IRT ability model, engine phases, signal/decision logging | +| `encoach_exam` | Add `results_release_mode`, IRT parameters (`a`, `b`, `c`) to questions | +| `encoach_resources` | Add `cefr_level`, `grammar_topic`, `vocab_band`, `ai_generated`, `approved` fields | +| `encoach_taxonomy` | Extend for Math and IT subject hierarchies | + +--- + +# Part II -- Database Schema + +## 4. Content Tables + +### 4.1 `encoach.passage` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `id` | Integer (auto) | Yes | Primary key | +| `exam_type` | Selection (`academic`, `general_training`, `general_english`) | Yes | Root parameter | +| `section_num` | Integer | Yes | Passage position in section | +| `topic_category` | Char | Yes | e.g., "environment", "technology" | +| `body_text` | Text | Yes | Full passage content | +| `difficulty` | Selection (`easy`, `medium`, `hard`) | Yes | | +| `status` | Selection (`draft`, `active`, `retired`, `flagged`) | Yes | Default: `draft` | +| `word_count` | Integer | Yes | Computed on save | +| `ai_generated` | Boolean | No | Default: `False` | +| `approved` | Boolean | No | Default: `False` | +| `ielts_certified` | Boolean | No | Default: `False` | +| `generation_brief` | Jsonb | No | AI generation parameters | +| `validation_errors` | Jsonb | No | Quality gate error details | +| `cefr_level` | Selection (`pre_a1`,`a1`,`a2`,`b1`,`b2`,`c1`,`c2`) | No | For CEFR-tagged content | + +### 4.2 `encoach.audio.file` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `id` | Integer (auto) | Yes | | +| `exam_type` | Selection | Yes | | +| `part` | Integer (1--4) | Yes | Listening part number | +| `context_type` | Selection (`conversation`, `monologue`) | Yes | | +| `topic` | Char | Yes | | +| `audio_url` | Char | Yes | URL or attachment reference | +| `transcript` | Text | No | Full transcript text | +| `difficulty` | Selection | Yes | | +| `ai_generated` | Boolean | No | | +| `approved` | Boolean | No | | +| `ielts_certified` | Boolean | No | | +| `generation_brief` | Jsonb | No | | +| `validation_errors` | Jsonb | No | | + +### 4.3 `encoach.question` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `id` | Integer (auto) | Yes | | +| `skill` | Selection (`listening`, `reading`, `writing`, `speaking`, `grammar`, `vocabulary`) | Yes | | +| `source_id` | Many2one (passage/audio/prompt/card) | No | Link to source content | +| `question_type` | Selection | Yes | `mcq`, `mcq_multi`, `tfng`, `ynng`, `gap_fill`, `short_answer`, `form_completion`, `note_completion`, `map_labelling`, `matching`, `summary_completion`, `heading_matching`, `matching_features`, `numerical`, `expression`, `code_completion`, `code_output`, `sql_query` | +| `stem` | Text | Yes | Question text (supports KaTeX for Math) | +| `options` | Jsonb | No | For MCQ: `[{"label": "A", "text": "..."}, ...]` | +| `correct_answer` | Jsonb | Yes | For auto-scored: answer value(s). For numerical: `{"value": 4, "tolerance": 0.01}` | +| `marks` | Float | Yes | Default: 1.0 | +| `difficulty` | Selection | Yes | | +| `irt_a` | Float | No | IRT discrimination parameter | +| `irt_b` | Float | No | IRT difficulty parameter | +| `irt_c` | Float | No | IRT guessing parameter | +| `ai_generated` | Boolean | No | | +| `ielts_certified` | Boolean | No | | +| `format_validated` | Boolean | No | IELTS format compliance flag | +| `subject_id` | Many2one (`encoach.subject`) | No | For subject-agnostic question bank | +| `topic_id` | Many2one (`encoach.topic`) | No | | + +### 4.4 `encoach.writing.prompt` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `id` | Integer (auto) | Yes | | +| `exam_type` | Selection | Yes | | +| `task` | Selection (`task1`, `task2`) | Yes | | +| `writing_type` | Char | Yes | e.g., "opinion_essay", "formal_letter" | +| `prompt_text` | Text | Yes | | +| `visual_url` | Char | No | For Academic Task 1 (charts/graphs) | +| `rubric_id` | Many2one (`encoach.rubric`) | Yes | | +| `min_words` | Integer | Yes | 150 for T1, 250 for T2 | +| `model_answer` | Text | No | AI-generated model answer | +| `ai_generated` | Boolean | No | | +| `approved` | Boolean | No | | +| `ielts_certified` | Boolean | No | | +| `generation_brief` | Jsonb | No | | +| `validation_errors` | Jsonb | No | | + +### 4.5 `encoach.speaking.card` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `id` | Integer (auto) | Yes | | +| `part` | Integer (1--3) | Yes | Speaking part | +| `topic` | Char | Yes | | +| `questions` | Jsonb | Yes | List of question strings | +| `bullet_points` | Jsonb | No | For Part 2 cue card | +| `linked_card_id` | Many2one (`encoach.speaking.card`) | No | Part 3 linked to Part 2 | +| `rubric_id` | Many2one (`encoach.rubric`) | Yes | | +| `difficulty` | Selection | Yes | | +| `model_response` | Text | No | AI-generated model response | +| `ai_generated` | Boolean | No | | +| `approved` | Boolean | No | | +| `ielts_certified` | Boolean | No | | +| `generation_brief` | Jsonb | No | | +| `validation_errors` | Jsonb | No | | + +### 4.6 `encoach.rubric` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `id` | Integer (auto) | Yes | | +| `skill` | Selection | Yes | `writing` or `speaking` | +| `criteria` | Jsonb | Yes | e.g., `[{"name": "Task Achievement", "max_score": 9, "descriptors": {...}}]` | +| `exam_type` | Selection | No | IELTS-specific or general | + +### 4.7 `encoach.resource` (modified) + +Add fields to existing `encoach.resource` model: + +| New Field | Type | Notes | +|-----------|------|-------| +| `cefr_level` | Selection (`pre_a1`,`a1`,`a2`,`b1`,`b2`,`c1`,`c2`) | For CEFR-tagged content | +| `grammar_topic` | Char | e.g., "present_simple", "conditionals" | +| `vocab_band` | Char | e.g., "high_freq_1000", "academic_word_list" | +| `ai_generated` | Boolean | Default: `False` | +| `approved` | Boolean | Default: `False` | +| `subject_id` | Many2one (`encoach.subject`) | For multi-subject support | + +--- + +## 5. Exam Tables + +### 5.1 `encoach.exam` (modified) + +Add fields to existing exam model: + +| New Field | Type | Notes | +|-----------|------|-------| +| `results_release_mode` | Selection (`auto`, `manual_approval`) | Default: `auto`. Controls whether results are visible immediately or require admin approval. | +| `template_type` | Selection (`ielts_academic`, `ielts_general_training`, `general_english`, `toefl`, `step`, `ic3`, `custom`) | Fixed exam template identifier | +| `assembly_mode` | Selection (`auto`, `manual`, `hybrid`) | Question assembly mode | +| `target_band` | Float | Target band score | +| `randomize` | Boolean | Randomize question order | + +### 5.2 `encoach.exam.section` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `exam_id` | Many2one (`encoach.exam`) | | +| `skill` | Selection | | +| `part_number` | Integer | | +| `time_limit_sec` | Integer | Section time limit in seconds | +| `question_count` | Integer | Required question count | +| `content_id` | Many2one | Reference to passage/audio/prompt | + +### 5.3 `encoach.exam.template` (new) + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `name` | Char(200) | Template name | +| `type` | Selection (`international`, `custom`) | Discriminator for the two template paths | +| `editable` | Boolean | `False` for international (locked structure), `True` for custom | +| `active` | Boolean | Only active templates are listed | +| `subject_id` | Many2one (`encoach.taxonomy.subject`) | Subject this template belongs to | +| `entity_id` | Many2one (`encoach.entity`) | NULL for international (global), FK for custom (entity-scoped) | +| `teacher_id` | Many2one (`res.users`) | NULL for international, FK for custom | +| `structure` | JSON | Template structure definition (parts, skills, question counts, time limits) | +| `total_time_min` | Integer | Total exam duration in minutes | +| `pass_threshold` | Float | Minimum percentage to pass (optional) | +| `results_release_mode` | Selection (`auto`, `manual_approval`) | Score release mode | +| `randomize_questions` | Boolean | Whether to randomize question order | + +### 5.4 `encoach.exam.custom` (new) + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `title` | Char(200) | Exam title | +| `template_id` | Many2one (`encoach.exam.template`) | Optional: reusable template reference | +| `subject_id` | Many2one (`encoach.taxonomy.subject`) | Subject | +| `entity_id` | Many2one (`encoach.entity`) | Scoped to entity; NULL for personal | +| `teacher_id` | Many2one (`res.users`) | Creating teacher | +| `description` | Text | Exam instructions | +| `total_time_min` | Integer | Overall duration | +| `pass_threshold` | Float | Minimum score % | +| `results_release_mode` | Selection (`auto`, `manual_approval`) | | +| `randomize_questions` | Boolean | | +| `status` | Selection (`draft`, `published`, `archived`) | | + +### 5.5 `encoach.exam.custom.section` (new) + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `exam_id` | Many2one (`encoach.exam.custom`) | Parent custom exam | +| `title` | Char(200) | Section title | +| `skill` | Char(100) | Skill/category label | +| `question_count` | Integer | Required minimum question count | +| `time_limit_min` | Integer | Per-section time limit (optional) | +| `scoring_method` | Selection (`auto`, `rubric`, `mixed`) | | +| `sequence` | Integer | Display order | +| `question_ids` | Many2many (`encoach.question.bank`) | Assigned questions | + +### 5.6 `encoach.exam.assignment` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `exam_id` | Many2one (`encoach.exam`) | | +| `student_id` | Many2one (`res.users`) | | +| `batch_id` | Many2one (`op.batch`) | Optional: assign to whole batch | +| `access_start` | Datetime | Optional access window start | +| `access_end` | Datetime | Optional access window end | +| `status` | Selection (`assigned`, `started`, `completed`, `expired`) | | + +--- + +## 6. User and Profile Tables + +### 6.1 `res.users` (modified via `_inherit`) + +| New Field | Type | Notes | +|-----------|------|-------| +| `entity_id` | Many2one (`encoach.entity`) | Nullable. Links student to institution. | +| `first_login` | Boolean | Default: `True`. Set to `False` after first password reset. | +| `account_source` | Selection (`self_registered`, `entity_bulk_upload`) | How the account was created | +| `account_status` | Selection (`unactivated`, `activated`, `suspended`) | Default: `unactivated` until onboarding wizard completed | + +### 6.2 `encoach.student.profile` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `user_id` | Many2one (`res.users`) | | +| `cefr_level` | Selection | Current CEFR level from placement | +| `target_band` | Float | Target band/level | +| `learning_style` | Jsonb | `["visual", "reading"]` | +| `learning_goal` | Char | From onboarding wizard | +| `hours_per_week` | Integer | Study commitment | +| `study_mode` | Selection (`self_study`, `with_teacher`) | | +| `exam_date` | Date | Optional target exam date | +| `placement_completed` | Boolean | | +| `entity_id` | Many2one (`encoach.entity`) | Denormalized for query performance | + +### 6.3 `encoach.gap.profile` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | | +| `source_type` | Selection (`placement`, `exam`) | What generated this gap profile | +| `source_id` | Integer | ID of placement session or exam attempt | +| `skill_gaps` | Jsonb | `[{"skill": "writing", "current": 5.5, "target": 7.0, "gap": 1.5, "priority": "high", "hours": 40}]` | +| `question_type_weaknesses` | Jsonb | `[{"skill": "reading", "type": "tfng", "error_rate": 0.6}]` | +| `topic_weaknesses` | Jsonb | `[{"category": "environment", "error_count": 3, "total": 5}]` | +| `entity_id` | Many2one (`encoach.entity`) | | +| `created_at` | Datetime | | + +--- + +## 7. Course Tables + +### 7.1 `encoach.course` (modified) + +| New Field | Type | Notes | +|-----------|------|-------| +| `generation_source` | Selection (`manual`, `auto_gap`, `ai_english`, `ai_ielts`) | How the course was created | +| `gap_profile_id` | Many2one (`encoach.gap.profile`) | If generated from gap analysis | +| `progression_model` | Selection (`linear`, `parallel`, `adaptive`) | Module ordering model | +| `target_band` | Float | | +| `study_hours_week` | Integer | | +| `entity_id` | Many2one (`encoach.entity`) | | + +### 7.2 `encoach.course.module` (modified) + +| New Field | Type | Notes | +|-----------|------|-------| +| `cefr_target` | Selection | Target CEFR for this module | +| `auto_generated` | Boolean | AI-generated module | +| `generation_brief` | Jsonb | AI generation parameters | +| `completion_criteria` | Selection (`all_resources`, `score_threshold`, `teacher_approval`) | | +| `score_threshold` | Float | Required score if criteria = `score_threshold` | +| `prerequisite_module_id` | Many2one (`encoach.course.module`) | For linear/adaptive ordering | +| `status` | Selection (`locked`, `available`, `in_progress`, `completed`, `skipped`) | | + +--- + +## 8. Progress and Results Tables + +### 8.1 `encoach.student.attempt` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | | +| `exam_id` | Many2one (`encoach.exam`) | | +| `started_at` | Datetime | | +| `completed_at` | Datetime | | +| `status` | Selection (`in_progress`, `completed`, `scoring`, `scored`, `released`, `pending_approval`) | | +| `listening_band` | Float | | +| `reading_band` | Float | | +| `writing_band` | Float | | +| `speaking_band` | Float | | +| `overall_band` | Float | | +| `cefr_level` | Selection | Derived from overall band | +| `is_placement` | Boolean | First attempt used as placement diagnostic | +| `entity_id` | Many2one (`encoach.entity`) | Nullable FK | +| `released_at` | Datetime | When results were approved/released | +| `released_by` | Many2one (`res.users`) | Admin who approved release | + +### 8.2 `encoach.student.answer` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `attempt_id` | Many2one (`encoach.student.attempt`) | | +| `question_id` | Many2one (`encoach.question`) | | +| `answer` | Jsonb | Student's answer | +| `is_correct` | Boolean | For auto-scored items | +| `score` | Float | Points earned | +| `time_spent_ms` | Integer | Time on this question | +| `flagged` | Boolean | Student flagged for review | + +### 8.3 `encoach.score` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `attempt_id` | Many2one (`encoach.student.attempt`) | | +| `skill` | Selection | | +| `band_score` | Float | | +| `raw_score` | Float | | +| `max_score` | Float | | +| `cefr_level` | Selection | | +| `entity_id` | Many2one (`encoach.entity`) | | + +### 8.4 `encoach.feedback` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `attempt_id` | Many2one | | +| `question_id` | Many2one | | +| `feedback_text` | Text | Per-question feedback | +| `source` | Selection (`teacher`, `ai`) | | +| `rubric_scores` | Jsonb | For W/S: `{"task_achievement": 6, "coherence": 7, ...}` | +| `graded_by` | Many2one (`res.users`) | Teacher or AI | + +--- + +## 9. Adaptive Learning Tables + +### 9.1 `encoach.student.ability.model` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | | +| `subject_id` | Many2one (`encoach.subject`) | | +| `skill` | Selection | | +| `theta` | Float | IRT ability estimate | +| `sem` | Float | Standard Error of Measurement | +| `last_updated` | Datetime | | + +### 9.2 `encoach.cat.session` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | | +| `subject_id` | Many2one (`encoach.subject`) | | +| `started_at` | Datetime | | +| `completed_at` | Datetime | | +| `status` | Selection (`active`, `completed`, `abandoned`) | | +| `current_section` | Selection | Current dimension | +| `current_theta` | Float | Running ability estimate | +| `current_sem` | Float | Running SEM | +| `questions_answered` | Integer | | +| `autosave_data` | Jsonb | Last auto-saved state | + +### 9.3 `encoach.adaptive.event` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | | +| `course_id` | Many2one | | +| `event_type` | Selection (`signal`, `decision`) | | +| `signal_name` | Char | e.g., "quiz_score", "time_on_task", "retry_count" | +| `signal_value` | Float | | +| `decision` | Char | e.g., "serve_harder", "insert_micro_lesson", "skip_module", "teacher_alert" | +| `context` | Jsonb | Additional detail | +| `created_at` | Datetime | | + +### 9.4 `encoach.adaptive.path` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | | +| `course_id` | Many2one | | +| `module_queue` | Jsonb | Ordered list of module IDs the engine recommends | +| `source` | Selection (`placement`, `exam`, `ai_generated`) | What triggered this path | +| `next_generation_brief` | Jsonb | Parameters for next AI content generation | + +### 9.5 `encoach.adaptive.settings` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `teacher_id` | Many2one (`res.users`) | | +| `entity_id` | Many2one (`encoach.entity`) | Optional: entity-wide defaults | +| `step_up_threshold` | Float | Default: 0.85 | +| `step_down_threshold` | Float | Default: 0.50 | +| `micro_lesson_trigger` | Integer | Default: 2 | +| `module_skip_threshold` | Float | Default: 0.95 | +| `no_progress_alert_days` | Integer | Default: 3 | +| `max_retries` | Integer | Default: 3 | + +--- + +## 10. Entity and Institutional Tables + +### 10.1 `encoach.entity` (modified) + +| New Field | Type | Notes | +|-----------|------|-------| +| `type` | Selection (`university`, `school`, `corporate`, `government`) | | +| `logo_url` | Char | Entity logo path or URL | +| `logo_file` | Binary | Logo file attachment | +| `primary_color` | Char | Hex code, e.g., "#1a73e8" | +| `secondary_color` | Char | | +| `background_color` | Char | | +| `white_label_domain` | Char | e.g., "utas" for "utas.encoach.com" | +| `login_title` | Char | Custom login page title | +| `login_description` | Text | Custom login page text | +| `favicon` | Binary | Custom favicon | +| `results_release_mode` | Selection (`auto`, `manual_approval`) | Default: `auto` | + +### 10.2 `encoach.entity.level.mapping` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `entity_id` | Many2one (`encoach.entity`) | | +| `min_score` | Float | Minimum CEFR score | +| `max_score` | Float | Maximum CEFR score | +| `internal_level_name` | Char | e.g., "Level 1", "Foundation", "High Flyer" | +| `cefr_equivalent` | Selection | e.g., `a1`, `b1_b2`, `c1` | + +--- + +## 11. AI Generation Tables + +### 11.1 `encoach.ai.generation.log` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `student_id` | Many2one (`res.users`) | Optional: for student-specific generation | +| `course_type` | Selection (`general_english`, `ielts`) | | +| `brief` | Jsonb | Generation parameters sent to AI | +| `attempts` | Integer | Number of generation attempts (max 3) | +| `final_resource_id` | Many2one (`encoach.resource`) | Resulting resource | +| `approved_by` | Many2one (`res.users`) | Teacher who approved | +| `status` | Selection (`generating`, `quality_check`, `pending_review`, `approved`, `rejected`) | | +| `created_at` | Datetime | | + +### 11.2 `encoach.ai.ielts.generation.log` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `skill` | Selection | | +| `brief` | Jsonb | | +| `format_check_result` | Jsonb | Layer 1 validation results | +| `band_check_result` | Jsonb | CEFR band calibration results | +| `examiner_id` | Many2one (`res.users`) | IELTS examiner assigned | +| `status` | Selection (`generating`, `format_check`, `examiner_review`, `approved`, `rejected`) | | +| `attempts` | Integer | | + +### 11.3 `encoach.ielts.standards.check` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `content_id` | Integer | ID of passage/audio/prompt/card | +| `content_type` | Selection (`passage`, `audio`, `writing_prompt`, `speaking_card`) | | +| `check_type` | Selection (`format_compliance`, `band_calibration`, `answer_key_completeness`) | | +| `passed` | Boolean | | +| `error_detail` | Jsonb | Specific errors found | +| `checked_at` | Datetime | | + +--- + +# Part III -- Workflow 1: User Signup + +## 12. CAPTCHA Integration + +### 12.1 Implementation + +Add CAPTCHA verification to the registration endpoint. + +**Service: `encoach_signup/services/captcha.py`** + +```python +import requests +from odoo import api, models +from odoo.exceptions import ValidationError + +class CaptchaService(models.AbstractModel): + _name = 'encoach.captcha.service' + + def verify(self, token: str) -> bool: + """Verify CAPTCHA token with provider (reCAPTCHA v2 or hCaptcha).""" + secret = self.env['ir.config_parameter'].sudo().get_param('encoach.captcha_secret_key') + provider = self.env['ir.config_parameter'].sudo().get_param('encoach.captcha_provider', 'recaptcha') + + if provider == 'recaptcha': + url = 'https://www.google.com/recaptcha/api/siteverify' + else: + url = 'https://hcaptcha.com/siteverify' + + response = requests.post(url, data={'secret': secret, 'response': token}) + result = response.json() + return result.get('success', False) +``` + +**Configuration (System Parameters):** + +| Key | Value | Notes | +|-----|-------|-------| +| `encoach.captcha_provider` | `recaptcha` or `hcaptcha` | CAPTCHA provider | +| `encoach.captcha_secret_key` | Server-side secret key | Stored as `ir.config_parameter` | +| `encoach.captcha_site_key` | Client-side site key | Returned by `/api/config/captcha` | + +### 12.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| CAP-01 | CAPTCHA verification is mandatory for `POST /api/auth/register` when `account_source = self_registered`. | +| CAP-02 | Entity bulk-upload accounts (`account_source = entity_bulk_upload`) bypass CAPTCHA. | +| CAP-03 | If CAPTCHA verification fails, return HTTP 400 with `{"error": "CAPTCHA verification failed"}`. | + +--- + +## 13. OTP Email Service + +### 13.1 Implementation + +**Service: `encoach_signup/services/otp.py`** + +```python +import random +import hashlib +from datetime import datetime, timedelta + +class OTPService(models.AbstractModel): + _name = 'encoach.otp.service' + + def generate(self, email: str) -> str: + """Generate a 6-digit OTP, store hash, return plaintext for email.""" + otp = str(random.randint(100000, 999999)) + otp_hash = hashlib.sha256(otp.encode()).hexdigest() + expires_at = datetime.utcnow() + timedelta(minutes=15) + + self.env['encoach.otp'].create({ + 'email': email, + 'otp_hash': otp_hash, + 'expires_at': expires_at, + 'attempts': 0, + }) + return otp + + def verify(self, email: str, otp: str) -> bool: + """Verify OTP against stored hash.""" + otp_hash = hashlib.sha256(otp.encode()).hexdigest() + record = self.env['encoach.otp'].search([ + ('email', '=', email), + ('otp_hash', '=', otp_hash), + ('expires_at', '>', datetime.utcnow()), + ('used', '=', False), + ], limit=1) + if record: + record.used = True + return True + return False +``` + +### 13.2 OTP Data Model: `encoach.otp` + +| Field | Type | Notes | +|-------|------|-------| +| `email` | Char | | +| `otp_hash` | Char | SHA-256 hash of the OTP | +| `expires_at` | Datetime | 15 minutes from creation | +| `used` | Boolean | Default: `False` | +| `resend_count` | Integer | Max 3 | +| `created_at` | Datetime | | + +### 13.3 Email Template + +The OTP email uses Odoo's `mail.template` with: +- Subject: "EnCoach -- Email Verification Code" +- Body: "Your verification code is: {OTP}. This code expires in 15 minutes." +- Also include a clickable verification link: `{base_url}/verify-email?email={email}&otp={otp}` + +--- + +## 14. Onboarding Wizard Data Model + +### 14.1 Goals Endpoint + +`GET /api/onboarding/goals` returns available goals dynamically from the platform's exam template registry: + +```python +@http.route('/api/onboarding/goals', type='json', auth='user', methods=['GET']) +def get_goals(self): + templates = self.env['encoach.exam.template'].search([('active', '=', True)]) + goals = [{'id': t.code, 'title': t.name, 'description': t.description, 'icon': t.icon} + for t in templates] + # Add non-exam goals + goals.extend([ + {'id': 'mathematics', 'title': 'Mathematics', 'description': '...', 'icon': 'calculator'}, + {'id': 'it', 'title': 'Information Technology', 'description': '...', 'icon': 'code'}, + ]) + return {'goals': goals} +``` + +### 14.2 Complete Wizard Endpoint + +`POST /api/onboarding/complete` saves all wizard data to `encoach.student.profile` and changes account status to `activated`. + +--- + +# Part IV -- Workflow 2: Placement Test + +## 15. CAT Engine + +### 15.1 Algorithm + +The Computer Adaptive Test engine uses Item Response Theory (IRT) to select questions and estimate ability: + +1. **Initialize:** Set starting ability estimate `theta = 0.0` (median difficulty). Set SEM to maximum. +2. **Select question:** Choose the question from the bank whose difficulty parameter `b` is closest to current `theta`, from the current section's question pool, that the student has not yet seen. +3. **Score answer:** Check correctness. Update `theta` using Maximum Likelihood Estimation (MLE) or Expected A Posteriori (EAP) method. +4. **Update SEM:** Recalculate Standard Error of Measurement. +5. **Termination check:** If `SEM < 0.3` OR maximum questions reached for this section, end section. +6. **Next section:** Move to the next dimension (Grammar -> Vocabulary -> Reading -> Speaking). + +### 15.2 Python Implementation + +**Service: `encoach_placement/services/cat_engine.py`** + +```python +import math +import numpy as np + +class CATEngine: + """Computer Adaptive Test engine using 3-Parameter Logistic IRT model.""" + + def probability(self, theta: float, a: float, b: float, c: float) -> float: + """3PL IRT probability of correct response.""" + exponent = -a * (theta - b) + return c + (1 - c) / (1 + math.exp(exponent)) + + def information(self, theta: float, a: float, b: float, c: float) -> float: + """Fisher information for a question at given ability level.""" + p = self.probability(theta, a, b, c) + q = 1 - p + numerator = a**2 * (p - c)**2 * q + denominator = (1 - c)**2 * p + return numerator / denominator if denominator > 0 else 0 + + def select_next_question(self, theta: float, available_questions: list) -> dict: + """Select the question that provides maximum information at current theta.""" + best_question = max( + available_questions, + key=lambda q: self.information(theta, q['irt_a'], q['irt_b'], q['irt_c']) + ) + return best_question + + def update_theta(self, theta: float, responses: list, questions: list) -> tuple: + """Update ability estimate using MLE. Returns (new_theta, sem).""" + # Newton-Raphson iteration for MLE + for _ in range(20): + numerator = 0.0 + denominator = 0.0 + for resp, q in zip(responses, questions): + p = self.probability(theta, q['irt_a'], q['irt_b'], q['irt_c']) + numerator += q['irt_a'] * (resp - p) + denominator += q['irt_a']**2 * p * (1 - p) + if abs(denominator) < 1e-10: + break + theta += numerator / denominator + + # Calculate SEM + total_info = sum(self.information(theta, q['irt_a'], q['irt_b'], q['irt_c']) for q in questions) + sem = 1.0 / math.sqrt(total_info) if total_info > 0 else float('inf') + + return theta, sem + + def should_terminate(self, sem: float, questions_answered: int, max_questions: int) -> bool: + """Check if section should terminate.""" + return sem < 0.3 or questions_answered >= max_questions +``` + +### 15.3 Session Management + +The placement session persists in `encoach.cat.session`. Each answer submission: +1. Scores the answer +2. Updates `theta` and `sem` +3. Checks termination +4. Selects next question (if not terminated) +5. Auto-saves session state + +### 15.4 Subject-Agnostic Design + +The CAT engine is subject-agnostic. The `subject_id` on the session determines which question bank is queried. For Math, the dimensions are Arithmetic, Algebra, Geometry, Problem-Solving. For IT: Computer Basics, Programming Logic, Networking, Problem-Solving. + +--- + +## 16. Question Bank with IRT Parameters + +### 16.1 IRT Calibration + +Every question in the bank must have IRT parameters: + +| Parameter | Symbol | Range | Meaning | +|-----------|--------|-------|---------| +| Discrimination | `a` | 0.5 -- 2.5 | How well the item differentiates between ability levels | +| Difficulty | `b` | -3.0 -- 3.0 | Ability level at which P(correct) = 0.5 (for c=0) | +| Guessing | `c` | 0.0 -- 0.35 | Probability of correct response by guessing (MCQ: 0.25 for 4 options) | + +### 16.2 Initial Calibration Strategy + +For the initial launch, before real student data is available: +1. Set `a = 1.0` (default discrimination) for all items +2. Set `b` based on expert difficulty rating: Easy = -1.0, Medium = 0.0, Hard = 1.0 +3. Set `c = 0.25` for MCQ (4 options), `c = 0.0` for open-ended +4. After 100+ student responses per item, recalibrate using real response data + +--- + +## 17. CEFR Mapping Algorithm + +### 17.1 Theta to CEFR Mapping + +| Theta Range | CEFR Level | IELTS Band Equivalent | +|-------------|------------|----------------------| +| < -2.0 | Pre-A1 | -- | +| -2.0 to -1.0 | A1 | -- | +| -1.0 to 0.0 | A2 | 3.0 -- 3.5 | +| 0.0 to 1.0 | B1 | 4.0 -- 4.5 | +| 1.0 to 2.0 | B2 | 5.0 -- 5.5 | +| 2.0 to 3.0 | C1 | 6.0 -- 6.5 | +| > 3.0 | C2 | 7.0+ | + +### 17.2 Implementation + +```python +def theta_to_cefr(theta: float) -> str: + if theta < -2.0: return 'pre_a1' + elif theta < -1.0: return 'a1' + elif theta < 0.0: return 'a2' + elif theta < 1.0: return 'b1' + elif theta < 2.0: return 'b2' + elif theta < 3.0: return 'c1' + else: return 'c2' + +def theta_to_ielts_band(theta: float) -> float: + band = 3.0 + (theta + 2.0) * 1.0 # Linear mapping + return max(1.0, min(9.0, round(band * 2) / 2)) # Round to 0.5 +``` + +--- + +## 18. Speaking AI Evaluation + +### 18.1 Pipeline + +Speaking responses are evaluated asynchronously: + +1. Student uploads audio via `POST /api/placement/speaking-upload` +2. Audio stored in Odoo attachment system +3. Background job (Odoo `ir.cron` or queue) processes: + a. **Transcribe** using OpenAI Whisper (local `base` model) + b. **Evaluate** using OpenAI GPT-4o with a rubric prompt + c. **Score** on IELTS Speaking criteria: Fluency, Lexical Resource, Grammar, Pronunciation + d. **Store** scores to `encoach.score` and update `encoach.student.attempt` +4. Frontend polls for completion + +### 18.2 GPT Evaluation Prompt + +``` +You are an IELTS Speaking examiner. Evaluate the following spoken response transcript. + +Prompt: {prompt_text} +Transcript: {transcript} + +Score each criterion on a scale of 0-9 following official IELTS band descriptors: +1. Fluency and Coherence +2. Lexical Resource +3. Grammatical Range and Accuracy +4. Pronunciation (based on transcript analysis only) + +Return JSON: {"fluency": X, "lexical": X, "grammar": X, "pronunciation": X, "overall": X, "feedback": "..."} +``` + +--- + +# Part V -- Workflow 3: Exam Configuration (International + Custom) + +## 19. Exam Template Architecture + +The platform supports two distinct exam template paths. Both share the same exam session engine, grading pipeline, and score release infrastructure. The difference lies in who defines the exam structure and whether it can be modified. + +### 19.1 Two Template Paths + +| Aspect | International Templates | Custom Templates | +|--------|------------------------|-----------------| +| **Examples** | IELTS Academic, IELTS General Training, TOEFL, STEP, IC3 | University midterm, entity quiz, department exam | +| **Created by** | EnCoach development team (seeded via `__manifest__.py` data files during module installation) | Teacher or Entity Admin (via platform API) | +| **Structure modifiable?** | No -- parts, question counts, time limits, and skills are permanently locked | Yes -- fully configurable by the creator | +| **Question assembly** | Teacher fills fixed structural slots using Auto/Manual/Hybrid modes | Teacher adds questions freely to self-defined sections | +| **Scope** | Available to all entities and individual students | Scoped to the creating teacher's entity | +| **Data field** | `type = 'international'`, `editable = False` | `type = 'custom'`, `editable = True` | + +### 19.2 Unified Template Model: `encoach.exam.template` + +All templates (international and custom) are stored in the same Odoo model with a `type` discriminator field: + +| Field | Type | Notes | +|-------|------|-------| +| `name` | Char(200) | Template name | +| `type` | Selection | `international` or `custom` | +| `editable` | Boolean | `False` for international, `True` for custom | +| `active` | Boolean | Only active templates are listed | +| `subject_id` | Many2one(`encoach.taxonomy.subject`) | Subject this template belongs to | +| `entity_id` | Many2one(`encoach.entity`) | NULL for international (available everywhere), FK for custom (entity-scoped) | +| `teacher_id` | Many2one(`res.users`) | NULL for international, FK for custom | +| `total_time_min` | Integer | Total exam duration in minutes | +| `pass_threshold` | Float | Minimum percentage to pass (optional) | +| `results_release_mode` | Selection | `auto` or `manual_approval` | +| `randomize_questions` | Boolean | Whether to randomize question order | +| `created_at` | Datetime | Auto-set | + +### 19.3 API Endpoints + +| Method | Route | Description | +|--------|-------|-------------| +| `GET` | `/api/exam/templates` | List all templates. Accepts query params: `type=international\|custom`, `subject_id`. International templates are always included; custom templates are filtered by the current user's entity. | +| `GET` | `/api/exam/templates/:id` | Get template detail with sections | +| `POST` | `/api/exam/templates/custom` | Save a custom exam structure as a reusable template | + +### 19.4 Seeding International Templates + +International templates are seeded during module installation via Odoo data files (`data/ielts_templates.xml`). The developer defines each template's structure (parts, skills, question counts, time limits) as fixed records. These records have `editable = False` and `type = 'international'`. Teachers cannot modify these structures; they can only fill the question slots within them. + +--- + +## 20. Fixed Template System (International) + +### 20.1 IELTS Template Model: `encoach.exam.template` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | Integer (auto) | | +| `code` | Char | `ielts_academic`, `ielts_general_training`, `toefl`, `step`, `ic3` | +| `name` | Char | "IELTS Academic" | +| `structure` | Jsonb | Fixed structure definition (see below) | +| `active` | Boolean | | +| `editable` | Boolean | `False` for IELTS -- structure cannot be modified | + +### 20.2 IELTS Academic Structure (Stored in `structure` field) + +```json +{ + "skills": [ + { + "skill": "listening", + "parts": [ + {"part": 1, "questions": 10, "content_type": "conversation", "time_sec": 360}, + {"part": 2, "questions": 10, "content_type": "monologue", "time_sec": 360}, + {"part": 3, "questions": 10, "content_type": "conversation", "time_sec": 360}, + {"part": 4, "questions": 10, "content_type": "monologue", "time_sec": 480} + ], + "total_questions": 40, "total_time_sec": 1800 + }, + { + "skill": "reading", + "parts": [ + {"part": 1, "questions": 14, "text_type": "factual"}, + {"part": 2, "questions": 13, "text_type": "analytical"}, + {"part": 3, "questions": 13, "text_type": "argumentative"} + ], + "total_questions": 40, "total_time_sec": 3600 + }, + { + "skill": "writing", + "parts": [ + {"task": 1, "type": "visual_data", "min_words": 150, "time_sec": 1200}, + {"task": 2, "type": "essay", "min_words": 250, "time_sec": 2400} + ], + "total_time_sec": 3600 + }, + { + "skill": "speaking", + "parts": [ + {"part": 1, "duration_sec": 300, "format": "familiar_topics"}, + {"part": 2, "duration_sec": 240, "format": "cue_card"}, + {"part": 3, "duration_sec": 300, "format": "abstract_discussion"} + ], + "total_time_sec": 840 + } + ] +} +``` + +### 20.3 Enforcement + +The template structure is READ-ONLY. The `create_exam` endpoint uses the template to pre-populate `exam_sections`. Attempts to modify question counts, part counts, or time limits are rejected with HTTP 400. + +--- + +## 21. Content Pool Query Engine + +### 21.1 Query Logic + +When the content pool is requested for an IELTS exam section: + +```python +def get_content_pool(self, exam_id, skill, part, difficulty): + """Return 3-5x more items than needed, with filters applied.""" + exam = self.env['encoach.exam'].browse(exam_id) + student_id = exam.assignment_ids.mapped('student_id.id') + + domain = [ + ('skill', '=', skill), + ('difficulty', '=', difficulty), + ('status', '=', 'active'), + ] + + if student_id: + seen_ids = self.env['encoach.student.answer'].search([ + ('attempt_id.student_id', 'in', student_id), + ('question_id', '!=', False), + ]).mapped('question_id.id') + domain.append(('id', 'not in', seen_ids)) + + # Exclude flagged and retired + domain.extend([ + ('status', '!=', 'flagged'), + ('status', '!=', 'retired'), + ]) + + required_count = exam.template_id.get_question_count(skill, part) + pool_size = required_count * 4 # 4x oversampling + + questions = self.env['encoach.question'].search(domain, limit=pool_size) + + # Apply topic diversity: no more than 30% from any single topic + # Apply difficulty curve: mix of easy/medium/hard within the pool + + return questions +``` + +--- + +## 22. Assembly Modes + +### 22.1 Auto Assembly + +```python +def auto_assemble(self, exam_id): + """System selects questions to fill all structural slots.""" + exam = self.env['encoach.exam'].browse(exam_id) + for section in exam.section_ids: + pool = self.get_content_pool(exam_id, section.skill, section.part_number, exam.difficulty) + selected = self._apply_selection_algorithm(pool, section.question_count) + section.question_ids = [(6, 0, [q.id for q in selected])] + return exam +``` + +### 22.2 Manual Assembly + +The backend provides the content pool via `GET /api/exam/ielts/:id/content-pool`. The frontend sends selected question IDs via `PUT /api/exam/ielts/:id/sections/:sectionId/questions`. + +### 22.3 Hybrid Assembly + +The backend generates suggestions via `POST /api/exam/ielts/:id/suggest`. The frontend allows the teacher to accept, reject, or swap items. Final selection is submitted. + +--- + +## 23. Custom Template System + +### 23.1 Custom Template Creation + +Custom templates are created by teachers or entity admins through the platform API. Unlike international templates, the creator defines the entire exam structure. + +**Module:** `encoach_exam` (extends existing module) + +### 23.2 Custom Exam Model: `encoach.exam.custom` + +Custom exams reuse the `encoach.exam.template` model with `type = 'custom'` and `editable = True`. The key difference is that the `structure` field is populated from the teacher's input rather than from seeded data. + +| Field | Type | Notes | +|-------|------|-------| +| `title` | Char(200) | Exam title | +| `template_id` | Many2one(`encoach.exam.template`) | Links to the saved custom template (if teacher chose "Save as Template") | +| `subject_id` | Many2one(`encoach.taxonomy.subject`) | Subject (English, Math, IT, etc.) | +| `entity_id` | Many2one(`encoach.entity`) | Scoped to entity; NULL means personal exam | +| `teacher_id` | Many2one(`res.users`) | Creating teacher | +| `description` | Text | Exam purpose and instructions | +| `total_time_min` | Integer | Overall exam duration | +| `pass_threshold` | Float | Minimum score % to pass (optional) | +| `results_release_mode` | Selection | `auto` or `manual_approval` | +| `randomize_questions` | Boolean | Randomize question order per student | +| `status` | Selection | `draft`, `published`, `archived` | + +### 23.3 Custom Exam Sections: `encoach.exam.custom.section` + +| Field | Type | Notes | +|-------|------|-------| +| `exam_id` | Many2one(`encoach.exam.custom`) | Parent exam | +| `title` | Char(200) | Section title (e.g., "Part A -- Grammar") | +| `skill` | Char(100) | Skill/category label (free text or taxonomy reference) | +| `question_count` | Integer | Required minimum question count | +| `time_limit_min` | Integer | Per-section time limit (optional, shares global timer if not set) | +| `scoring_method` | Selection | `auto`, `rubric`, `mixed` | +| `sequence` | Integer | Display order | +| `question_ids` | Many2many(`encoach.question.bank`) | Assigned questions | + +### 23.4 Business Logic + +```python +class EncoachExamCustom(models.Model): + _name = 'encoach.exam.custom' + _description = 'Custom Exam' + + @api.model + def create_custom_exam(self, vals): + """Teacher creates a custom exam with full structural freedom.""" + vals['type'] = 'custom' + vals['editable'] = True + vals['teacher_id'] = self.env.user.id + vals['entity_id'] = self.env.user.entity_id.id or False + exam = self.create(vals) + for section_data in vals.get('sections', []): + self.env['encoach.exam.custom.section'].create({ + 'exam_id': exam.id, + **section_data + }) + return exam + + def save_as_template(self): + """Save this exam's structure as a reusable custom template.""" + return self.env['encoach.exam.template'].create({ + 'name': f"{self.title} Template", + 'type': 'custom', + 'editable': True, + 'entity_id': self.entity_id.id, + 'teacher_id': self.teacher_id.id, + 'structure': self._serialize_structure(), + }) +``` + +### 23.5 API Endpoints + +| Method | Route | Description | +|--------|-------|-------------| +| `POST` | `/api/exam/custom/create` | Create a custom exam with sections and questions | +| `GET` | `/api/exam/custom/:id` | Get custom exam detail | +| `PUT` | `/api/exam/custom/:id` | Update custom exam (only while in `draft` status) | +| `DELETE` | `/api/exam/custom/:id` | Delete custom exam (only while in `draft` status) | +| `POST` | `/api/exam/custom/:id/save-template` | Save this exam structure as a reusable template | + +### 23.6 Shared Infrastructure + +Custom exams reuse the same infrastructure as international template exams: +- **Exam session** (Section 25): same student-facing exam interface +- **Auto-scoring** (Section 26): same scoring pipeline for auto-scored questions +- **Rubric scoring** (Section 26): same AI-assisted rubric grading +- **Score release gate** (Section 27): same approval workflow +- **PDF reports** (Section 28): same report generation +- **Content pool** (Section 21): same question bank for browsing questions + +--- + +## 24. Exam Validation Rules + +### 24.1 Validation Checks + +The validation endpoint runs these checks (applies to both international and custom exams; custom exams use the teacher-defined constraints instead of template-fixed constraints): + +| Check | Rule | Severity | +|-------|------|----------| +| Question Count | Each section must have exactly the number of questions specified by the template | Error (blocks publish) | +| Media URLs | All audio_url and visual_url references must resolve (HTTP HEAD check) | Error | +| Answer Keys | All auto-scored questions must have `correct_answer` populated | Error | +| No Duplicates | No question ID appears in more than one section | Error | +| Rubrics | All Writing and Speaking tasks must have `rubric_id` linked | Error | +| Time Specification | Each section must have `time_limit_sec > 0` | Error | +| Content Diversity | No more than 40% of questions from same topic_category | Warning | + +### 24.2 Publishing Logic + +On publish (`status: draft -> published`): +1. Run all validation checks +2. If any errors exist, return HTTP 400 with validation report +3. If only warnings, allow publish with acknowledgement +4. Lock the exam for editing (no further question changes) +5. Create audit log entry + +--- + +# Part VI -- Workflow 4: General English Exam + +## 25. Exam Session Management + +### 23.1 Session Creation + +When a student starts an exam: +1. Create `encoach.student.attempt` with `status = in_progress` +2. Return all sections and questions (pre-loaded, unlike CAT) +3. Start session timer + +### 23.2 Auto-Save + +`POST /api/exam/:id/autosave` receives the current answer state every 10 seconds: + +```json +{ + "attempt_id": 42, + "answers": [ + {"question_id": 1, "answer": "B"}, + {"question_id": 2, "answer": "True"} + ], + "current_section": "listening", + "time_remaining_sec": 1523 +} +``` + +Auto-save updates `encoach.student.answer` records without closing the attempt. + +### 23.3 Section Time Enforcement + +When a section timer expires: +- All answers for that section are auto-submitted +- Unanswered questions are marked as blank (0 score) +- Student is moved to the next section + +--- + +## 26. Auto-Scoring Engine + +### 24.1 Scoring Logic + +```python +def auto_score(self, attempt_id): + """Auto-score Listening and Reading sections.""" + attempt = self.env['encoach.student.attempt'].browse(attempt_id) + for answer in attempt.answer_ids: + question = answer.question_id + if question.skill in ('listening', 'reading', 'grammar', 'vocabulary'): + answer.is_correct = self._check_answer(answer.answer, question.correct_answer, question.question_type) + answer.score = question.marks if answer.is_correct else 0.0 +``` + +### 24.2 Band Score Calculation + +```python +def calculate_band(self, raw_score: float, max_score: float, skill: str) -> float: + """Convert raw score to IELTS band score.""" + percentage = raw_score / max_score + # IELTS uses a conversion table (not a simple formula) + # This is a simplified approximation + band = 1.0 + (percentage * 8.0) + return round(band * 2) / 2 # Round to nearest 0.5 +``` + +The overall band is the arithmetic mean of the four skills, rounded to nearest 0.5. + +--- + +## 27. Score Release Gate + +### 25.1 Logic + +```python +def submit_exam(self, attempt_id): + """Process exam submission and determine visibility.""" + attempt = self.env['encoach.student.attempt'].browse(attempt_id) + self.auto_score(attempt_id) + self.calculate_bands(attempt_id) + + exam = attempt.exam_id + if exam.results_release_mode == 'auto': + attempt.status = 'released' + attempt.released_at = fields.Datetime.now() + elif exam.results_release_mode == 'manual_approval': + attempt.status = 'pending_approval' + # Notify entity admin + self._notify_entity_admin(attempt) +``` + +### 25.2 Release Endpoint + +`POST /api/scores/:attemptId/release` (admin only): +1. Verify caller is an admin for the student's entity +2. Set `status = released`, `released_at = now`, `released_by = caller` +3. Notify student via notification engine + +--- + +## 28. PDF Report Generation with QR Code + +### 26.1 Implementation + +Use Python `reportlab` library for PDF generation and `qrcode` library for QR codes. + +**Dependencies:** +``` +pip install reportlab qrcode[pil] +``` + +### 26.2 QR Code Content + +```python +import hashlib +import json + +def generate_verification_hash(self, attempt): + """Generate a signed verification hash for the QR code.""" + secret = self.env['ir.config_parameter'].sudo().get_param('encoach.verification_secret') + data = f"{attempt.student_id.id}:{attempt.exam_id.id}:{attempt.overall_band}:{attempt.completed_at}" + return hashlib.sha256(f"{data}:{secret}".encode()).hexdigest()[:32] + +def generate_qr_data(self, attempt): + """Generate QR code data URL.""" + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + hash_val = self.generate_verification_hash(attempt) + return f"{base_url}/verify/{hash_val}" +``` + +### 26.3 PDF Structure + +The PDF includes: entity logo (or EnCoach logo), student info, exam details, per-skill scores, overall band, CEFR level, performance summary, and the QR code at bottom-right. + +--- + +# Part VII -- Workflow 5: Course Generation + +## 29. Gap Analysis Engine + +### 27.1 Algorithm + +```python +def generate_gap_profile(self, source_type, source_id, student_id): + """Generate a gap profile from an exam attempt or placement session.""" + if source_type == 'exam': + attempt = self.env['encoach.student.attempt'].browse(source_id) + profile = self.env['encoach.student.profile'].search([('user_id', '=', student_id)]) + target = profile.target_band + + skill_gaps = [] + for skill in ['listening', 'reading', 'writing', 'speaking']: + current = getattr(attempt, f'{skill}_band') + gap = target - current + hours = self._estimate_hours(gap) + priority = 'high' if gap >= 1.5 else ('medium' if gap >= 1.0 else 'low') + skill_gaps.append({ + 'skill': skill, 'current': current, 'target': target, + 'gap': gap, 'priority': priority, 'hours': hours + }) + + # Sort by gap size descending + skill_gaps.sort(key=lambda x: x['gap'], reverse=True) + + # Analyse question-type and topic weaknesses + qt_weaknesses = self._analyse_question_types(attempt) + topic_weaknesses = self._analyse_topics(attempt) + + return self.env['encoach.gap.profile'].create({ + 'student_id': student_id, + 'source_type': source_type, + 'source_id': source_id, + 'skill_gaps': json.dumps(skill_gaps), + 'question_type_weaknesses': json.dumps(qt_weaknesses), + 'topic_weaknesses': json.dumps(topic_weaknesses), + 'entity_id': attempt.entity_id.id, + }) +``` + +--- + +## 30. Auto Course Structure Generation + +### 28.1 Individual Path + +```python +def auto_generate_course(self, gap_profile_id): + """Auto-generate a course from a gap profile (individual student path).""" + gap = self.env['encoach.gap.profile'].browse(gap_profile_id) + skill_gaps = json.loads(gap.skill_gaps) + + # Create course + course = self.env['encoach.course'].create({ + 'name': f"Training Plan -- {gap.student_id.name}", + 'generation_source': 'auto_gap', + 'gap_profile_id': gap_profile_id, + 'progression_model': 'adaptive', + 'target_band': gap.student_id.student_profile_id.target_band, + }) + + # Create sections and modules per skill + for skill_gap in skill_gaps: + if skill_gap['gap'] <= 0: + continue + section = self.env['encoach.course.section'].create({ + 'course_id': course.id, + 'skill': skill_gap['skill'], + 'estimated_hours': skill_gap['hours'], + }) + # Generate modules within section based on weakness analysis + self._generate_skill_modules(section, skill_gap, gap.question_type_weaknesses) + + return course +``` + +--- + +## 31. Adaptive Progression Engine + +This is the runtime component of the adaptive engine that manages module ordering during course delivery. It reads the adaptive settings (thresholds) and applies decisions. + +### 29.1 Decision Logic (Phase 1: Rule-Based) + +```python +def process_checkpoint(self, student_id, course_id, module_id, score): + """Process a module checkpoint score and make adaptive decisions.""" + settings = self._get_settings(student_id) + module = self.env['encoach.course.module'].browse(module_id) + + if score >= settings.module_skip_threshold: + # Skip to next module + self._log_event(student_id, course_id, 'decision', 'skip_module', score) + self._advance_to_next(student_id, course_id, skip=True) + + elif score >= settings.step_up_threshold: + # Serve harder content + self._log_event(student_id, course_id, 'decision', 'serve_harder', score) + self._advance_to_next(student_id, course_id) + + elif score < settings.step_down_threshold: + # Insert remedial content + self._log_event(student_id, course_id, 'decision', 'insert_remedial', score) + self._insert_remedial_module(student_id, course_id, module) + + # Check for stuck pattern + retry_count = self._get_retry_count(student_id, module_id) + if retry_count >= settings.micro_lesson_trigger: + self._log_event(student_id, course_id, 'decision', 'insert_micro_lesson', retry_count) + self._insert_micro_lesson(student_id, course_id, module) + + # Check no-progress alert + days_inactive = self._get_days_inactive(student_id, course_id) + if days_inactive >= settings.no_progress_alert_days: + self._log_event(student_id, course_id, 'decision', 'teacher_alert', days_inactive) + self._alert_teacher(student_id, course_id) +``` + +--- + +# Part VIII -- Workflow 6: Entity Student Onboarding + +## 32. CSV Parsing and Validation + +### 30.1 Validation Rules + +| Rule | Check | Severity | +|------|-------|----------| +| Required fields | `student_name`, `institutional_email`, `national_id` must be present | Error | +| Email format | Must be valid email format | Error | +| Duplicate email | No duplicate emails within the CSV or against existing accounts | Error | +| National ID format | Non-empty string | Error | +| Student ID | Optional but validated if present | Warning | +| Programme | Optional | Info | + +### 30.2 Implementation + +```python +@http.route('/api/entity/students/validate-csv', type='http', auth='user', methods=['POST'], csrf=False) +def validate_csv(self, **kwargs): + csv_file = request.httprequest.files.get('file') + reader = csv.DictReader(io.TextIOWrapper(csv_file, encoding='utf-8')) + + results = [] + existing_emails = set(self.env['res.users'].search([]).mapped('login')) + + for i, row in enumerate(reader, start=2): + errors = [] + if not row.get('student_name'): + errors.append('Missing required field: student_name') + if not row.get('institutional_email'): + errors.append('Missing required field: institutional_email') + elif not self._is_valid_email(row['institutional_email']): + errors.append('Invalid email format') + elif row['institutional_email'] in existing_emails: + errors.append('Duplicate email: account already exists') + if not row.get('national_id'): + errors.append('Missing required field: national_id') + + results.append({ + 'row': i, + 'student_name': row.get('student_name', ''), + 'email': row.get('institutional_email', ''), + 'status': 'error' if errors else 'valid', + 'issues': errors, + }) + + return json.dumps({'validation': results}) +``` + +--- + +## 33. Bulk Account Creation + +```python +def bulk_create_students(self, validated_rows, entity_id): + """Create student accounts from validated CSV data.""" + created = [] + for row in validated_rows: + user = self.env['res.users'].create({ + 'name': row['student_name'], + 'login': row['institutional_email'], + 'password': row['national_id'], + 'entity_id': entity_id, + 'first_login': True, + 'account_source': 'entity_bulk_upload', + 'account_status': 'activated', # No wizard needed + }) + # Create student profile + self.env['encoach.student.profile'].create({ + 'user_id': user.id, + 'entity_id': entity_id, + }) + created.append(user) + return created +``` + +--- + +## 34. Credential Email Service + +```python +def send_credentials(self, users, entity): + """Send credential notification emails to newly created students.""" + template = self.env.ref('encoach_entity_onboarding.email_credentials') + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + + for user in users: + template.send_mail(user.id, force_send=True, email_values={ + 'email_to': user.login, + 'subject': f'Welcome to {entity.name} Learning Platform', + 'body_html': f""" +

Your account has been created on the {entity.name} learning platform.

+

Login URL: {base_url}/login

+

Username: {user.login}

+

Temporary Password: Your National ID number

+

You will be asked to set a new password on first login.

+ """, + }) +``` + +--- + +## 35. Entity Level Mapping + +### 33.1 Mapping Application + +When placement results are stored for an entity student, the system also maps to the entity's internal levels: + +```python +def apply_entity_level_mapping(self, student_id, cefr_score): + """Map CEFR score to entity's internal level system.""" + student = self.env['res.users'].browse(student_id) + if not student.entity_id: + return None + + mappings = self.env['encoach.entity.level.mapping'].search([ + ('entity_id', '=', student.entity_id.id), + ('min_score', '<=', cefr_score), + ('max_score', '>=', cefr_score), + ], limit=1) + + if mappings: + return { + 'internal_level': mappings.internal_level_name, + 'cefr_equivalent': mappings.cefr_equivalent, + } + return None +``` + +--- + +# Part IX -- AI Course Generation + +## 36. General English AI Content Pipeline + +### 34.1 Pipeline Steps + +1. **Receive generation brief** from student profile (CEFR level, skill, grammar topic, vocab band, learning style) +2. **Generate content** using OpenAI GPT (see Section 59 for prompts) +3. **Auto-tag content** with CEFR level, grammar topic, vocab band, skill, resource type +4. **Quality gate** (Section 36): readability, CEFR calibration, grammar accuracy, length +5. **Teacher review** (if quality gate passes): approve or edit +6. **Store to DB** with `approved = true` + +### 34.2 Content Types Generated + +| Type | GPT Prompt Category | Output Format | +|------|-------------------|---------------| +| Reading passages | Generate passage at CEFR level with comprehension questions | `encoach.passage` + `encoach.question` | +| Grammar exercises | Generate exercises with explanations and worked examples | `encoach.resource` (type=exercise) | +| Speaking prompts | Generate contextual speaking prompts | `encoach.speaking.card` | +| Vocabulary sets | Generate contextual vocabulary with definitions and usage | `encoach.resource` (type=vocabulary) | + +--- + +## 37. AI IELTS Content Pipeline + +### 35.1 Two-Layer Validation + +This pipeline has an additional validation layer compared to General English: + +1. **Generate IELTS-specific content** using GPT with strict format constraints +2. **Layer 1: Automated IELTS standards check** (Section 37) + - Format compliance (word counts, part structure, question types) + - CEFR band calibration + - Answer key and rubric completeness + - If fail: return to GPT with error details (max 3 attempts) +3. **Layer 2: IELTS examiner review** (human) + - Content source gate: AI-generated = mandatory review, entity-uploaded = spot-check + - Examiner verifies: accuracy, difficulty, alignment with IELTS conditions, cultural sensitivity + - If reject: return to step 1 with examiner notes +4. **Store** with `approved = true` AND `ielts_certified = true` + +--- + +## 38. Quality Gate Engine + +### 36.1 Checks + +```python +class QualityGateEngine: + def check(self, content_type, content, target_cefr): + results = { + 'readability': self._check_readability(content, target_cefr), + 'cefr_calibration': self._check_cefr(content, target_cefr), + 'grammar_accuracy': self._check_grammar(content), + 'length_compliance': self._check_length(content, content_type), + } + passed = all(r['passed'] for r in results.values()) + return {'passed': passed, 'checks': results} + + def _check_readability(self, content, target_cefr): + """Flesch-Kincaid readability score mapped to CEFR level.""" + fk_score = textstat.flesch_kincaid_grade(content) + expected_range = CEFR_FK_RANGES[target_cefr] # e.g., B2: (8, 12) + passed = expected_range[0] <= fk_score <= expected_range[1] + return {'passed': passed, 'score': fk_score, 'expected': expected_range} + + def _check_length(self, content, content_type): + """Check word count is within expected range.""" + word_count = len(content.split()) + expected = CONTENT_LENGTH_RANGES[content_type] + passed = expected['min'] <= word_count <= expected['max'] + return {'passed': passed, 'count': word_count, 'expected': expected} +``` + +**Dependency:** `pip install textstat` + +--- + +## 39. IELTS Standards Validation Engine + +### 37.1 Format Compliance Checks + +```python +class IELTSValidationEngine: + IELTS_WORD_LIMITS = { + 'reading_passage_academic': {'min': 700, 'max': 850}, + 'reading_passage_general': {'min': 300, 'max': 500}, + 'writing_task1': {'min_words_required': 150}, + 'writing_task2': {'min_words_required': 250}, + } + + IELTS_QUESTION_COUNTS = { + 'listening': [10, 10, 10, 10], # Parts 1-4 + 'reading': [14, 13, 13], # Passages 1-3 + } + + def validate_format(self, content_type, content, skill, part): + """Check IELTS format compliance.""" + errors = [] + + if content_type == 'passage': + word_count = len(content['body_text'].split()) + limits = self.IELTS_WORD_LIMITS.get(f'reading_passage_{content["exam_type"]}') + if limits and not (limits['min'] <= word_count <= limits['max']): + errors.append(f"Word count {word_count} outside IELTS range {limits['min']}-{limits['max']}") + + if content_type == 'question_set': + expected = self.IELTS_QUESTION_COUNTS.get(skill, [])[part - 1] + actual = len(content['questions']) + if actual != expected: + errors.append(f"Expected {expected} questions for {skill} part {part}, got {actual}") + + return {'passed': len(errors) == 0, 'errors': errors} +``` + +--- + +# Part X -- Adaptive Learning Engine (4 Phases) + +## 40. Phase 1 -- Rule-Based Engine (MVP) + +**Complexity:** Low +**Dependencies:** None (no ML libraries required) + +### 38.1 Scope + +- Teacher sets explicit thresholds via `encoach.adaptive.settings` +- Engine reads performance signals after each module checkpoint +- Engine makes decisions based on simple if/then rules +- All decisions logged to `encoach.adaptive.event` + +### 38.2 Signals Read + +| Signal | Source | Type | +|--------|--------|------| +| Quiz score | Module checkpoint exercises | Percentage (0-100) | +| Error rate | Per-question correctness | Percentage | +| Retry count | How many times student retried an exercise | Integer | +| Time on task | Time spent on each resource/module | Milliseconds | +| Video completion | Percentage of video watched | Percentage | +| Module completion | Whether module criteria met | Boolean | + +### 38.3 Decisions Made + +| Decision | Trigger | Action | +|----------|---------|--------| +| Serve harder content | Score >= step_up_threshold | Unlock next-difficulty module | +| Serve easier content | Score < step_down_threshold | Insert prerequisite module | +| Insert micro-lesson | Retry count >= micro_lesson_trigger | Generate targeted micro-lesson | +| Skip module | Pre-test score >= module_skip_threshold | Mark module as skipped, advance | +| Teacher alert | Days inactive >= no_progress_alert_days | Create notification for teacher | +| Resource type switch | Learning style mismatch detected | Serve alternative resource type | + +--- + +## 41. Phase 2 -- IRT-Based CAT + +**Complexity:** Medium +**Dependencies:** `numpy`, `scipy` + +### 39.1 Scope + +- CAT engine (Section 15) is fully operational for placement tests +- Question bank is calibrated with real IRT parameters (a, b, c) +- Ability estimates are stored and updated in `encoach.student.ability.model` +- SEM-based termination replaces fixed question counts + +### 39.2 Calibration Process + +After 100+ student responses per item, recalibrate IRT parameters: + +```python +def recalibrate(self, question_id): + """Recalibrate IRT parameters using real response data.""" + responses = self.env['encoach.student.answer'].search([('question_id', '=', question_id)]) + if len(responses) < 100: + return # Not enough data + + # Extract response vector and ability estimates + data = [(r.is_correct, r.attempt_id.student_id.ability_model_id.theta) for r in responses] + + # Use scipy.optimize to fit 3PL model + from scipy.optimize import minimize + # ... MLE fitting logic +``` + +--- + +## 42. Phase 3 -- Collaborative Filtering + +**Complexity:** High +**Dependencies:** `numpy`, `scipy`, `scikit-learn` + +### 40.1 Scope + +- Build a student-student similarity matrix based on ability profiles +- Predict which resources/modules will be most effective for a student based on similar students' outcomes +- "Students like you also benefited from..." recommendations + +### 40.2 Algorithm Outline + +```python +def get_recommendations(self, student_id, course_id): + """Collaborative filtering based on ability model similarity.""" + student_model = self.env['encoach.student.ability.model'].search([('student_id', '=', student_id)]) + all_models = self.env['encoach.student.ability.model'].search([]) + + # Compute cosine similarity between student ability vectors + # Find top-K similar students + # Recommend resources that worked best for similar students + # "Worked best" = highest score improvement after consumption +``` + +--- + +## 43. Phase 4 -- Full ML + +**Complexity:** Very High +**Dependencies:** `numpy`, `scipy`, `scikit-learn`, `tensorflow` or `pytorch` + +### 41.1 Scope + +- **Dropout risk prediction:** Predict which students are likely to abandon their course +- **Optimal study time:** Predict the best time of day/week for each student to study +- **Next best question:** Predict which question will provide the most learning value + +### 41.2 Models Required + +| Model | Input Features | Output | Training Data | +|-------|---------------|--------|---------------| +| Dropout Risk | days_inactive, score_trend, login_frequency, module_completion_rate | probability (0-1) | Historical student completion data | +| Study Time Optimizer | login_times, score_by_time_of_day, session_durations | recommended_time_slots | Historical session + score data | +| Next Best Question | current_theta, question_bank_features, response_history | question_id | Student response logs | + +--- + +# Part XI -- White-Labelling + +## 44. Entity Branding Model + +See Section 10.1 for the `encoach.entity` model with branding fields. + +### 42.1 Branding API + +`GET /api/entity/branding` (authenticated): Returns the branding for the current user's entity: + +```python +@http.route('/api/entity/branding', type='json', auth='user', methods=['GET']) +def get_branding(self): + user = request.env.user + if not user.entity_id: + return {'branding': None} # Use default + + entity = user.entity_id + return { + 'branding': { + 'logo_url': entity.logo_url, + 'primary_color': entity.primary_color, + 'secondary_color': entity.secondary_color, + 'background_color': entity.background_color, + 'login_title': entity.login_title, + 'login_description': entity.login_description, + } + } +``` + +--- + +## 45. Subdomain Routing + +If the entity has a `white_label_domain` configured (e.g., "utas"), the platform should be accessible at `utas.encoach.com`. This requires: + +1. **Nginx configuration:** Wildcard subdomain `*.encoach.com` pointing to the same frontend +2. **Frontend logic:** On load, read the subdomain, call `GET /api/entity/branding?domain={subdomain}`, apply branding +3. **Backend lookup:** `GET /api/entity/branding?domain=utas` resolves to the entity with `white_label_domain = "utas"` + +--- + +# Part XII -- Math and IT Backend + +## 46. Subject-Specific Question Types + +### 44.1 Math Question Types + +| Type | `question_type` Value | Scoring Logic | +|------|----------------------|---------------| +| Numerical | `numerical` | Compare answer to `correct_answer.value` within `tolerance` | +| Expression | `expression` | Symbolic comparison using `sympy` (e.g., `x^2 + 2x + 1` == `(x+1)^2`) | +| Matrix | `matrix` | Element-wise comparison | +| Graph | `graph` | Compare function definition string | + +**Dependency for symbolic math:** `pip install sympy` + +### 44.2 IT Question Types + +| Type | `question_type` Value | Scoring Logic | +|------|----------------------|---------------| +| Code Completion | `code_completion` | String match or AST comparison | +| Code Output | `code_output` | Compare predicted output to actual | +| SQL Query | `sql_query` | Execute against test DB, compare result sets | + +### 44.3 Code Execution Sandbox (Phase 2+) + +For IT exercises requiring code execution, use a sandboxed execution environment. Options: +- Docker container with restricted permissions +- WebAssembly-based execution (Pyodide for Python) +- External code execution API (e.g., Judge0) + +Initial implementation can use output prediction (no execution) and add execution in Phase 2. + +--- + +## 47. Subject Taxonomy Extension + +### 45.1 Math Taxonomy (pre-loaded data) + +``` +Mathematics +├── Arithmetic (Pre-A1 to A2) +│ ├── Basic Operations +│ ├── Fractions and Decimals +│ └── Percentages +├── Algebra (B1 to B2) +│ ├── Linear Equations +│ ├── Quadratic Equations +│ └── Functions +├── Geometry (B1 to C1) +│ ├── 2D Shapes +│ ├── 3D Shapes +│ └── Trigonometry +└── Statistics (B2 to C2) + ├── Probability + ├── Data Analysis + └── Distributions +``` + +### 45.2 IT Taxonomy (pre-loaded data) + +``` +Information Technology +├── Computer Basics (Pre-A1 to A2) +│ ├── Hardware +│ ├── Software +│ └── Operating Systems +├── Programming (B1 to C1) +│ ├── Variables and Data Types +│ ├── Control Flow +│ ├── Functions +│ └── Data Structures +├── Networking (B1 to B2) +│ ├── Network Fundamentals +│ ├── IP Addressing +│ └── Security +└── Databases (B2 to C2) + ├── Relational Databases + ├── SQL + └── Database Design +``` + +--- + +# Part XIII -- API Endpoint Specification + +## 48. Auth and Signup Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/auth/register` | None | Register new user with CAPTCHA | +| POST | `/api/auth/check-email` | None | Check if email exists | +| POST | `/api/auth/verify-email` | None | Verify OTP code | +| POST | `/api/auth/resend-otp` | None | Resend OTP (max 3) | +| GET | `/api/onboarding/goals` | User | Get available learning goals | +| POST | `/api/onboarding/complete` | User | Save wizard data, activate account | +| GET | `/api/config/captcha` | None | Get CAPTCHA site key and provider | + +--- + +## 49. Placement Test Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/placement/start` | User | Start CAT session, get first question | +| POST | `/api/placement/answer` | User | Submit answer, get next question | +| POST | `/api/placement/autosave` | User | Auto-save current state | +| POST | `/api/placement/speaking-upload` | User | Upload speaking audio (multipart) | +| GET | `/api/placement/speaking-status` | User | Poll speaking evaluation status | +| GET | `/api/placement/results` | User | Get placement results | +| GET | `/api/placement/learning-path` | User | Get generated learning path preview | + +--- + +## 50. Exam Template Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/exam/templates` | Teacher/Admin | List all templates. Query params: `type=international\|custom`, `subject_id`. International templates always included; custom templates filtered by user's entity. | +| GET | `/api/exam/templates/:id` | Teacher/Admin | Get template detail with section structure | +| POST | `/api/exam/templates/custom` | Teacher/Admin | Save a custom exam structure as a reusable template | + +--- + +## 51. IELTS Exam Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/exam/ielts/create` | Admin/Teacher | Create IELTS exam | +| GET | `/api/exam/ielts/:id` | Admin/Teacher | Get exam details | +| PUT | `/api/exam/ielts/:id` | Admin/Teacher | Update exam (status, publish) | +| GET | `/api/exam/ielts/:id/content-pool-count` | Admin/Teacher | Get available content counts | +| GET | `/api/exam/ielts/:id/content-pool` | Admin/Teacher | Get content pool with filters | +| POST | `/api/exam/ielts/:id/auto-assemble` | Admin/Teacher | Auto-select questions | +| POST | `/api/exam/ielts/:id/suggest` | Admin/Teacher | Get hybrid suggestions | +| PUT | `/api/exam/ielts/:id/sections/:sectionId/questions` | Admin/Teacher | Set section questions | +| GET | `/api/exam/ielts/:id/validate` | Admin/Teacher | Run validation checks | +| POST | `/api/exam/ielts/:id/assign` | Admin/Teacher | Assign to students/batches | + +--- + +## 52. Custom Exam Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/exam/custom/create` | Teacher/Admin | Create a custom exam with sections and questions | +| GET | `/api/exam/custom/:id` | Teacher/Admin | Get custom exam detail | +| PUT | `/api/exam/custom/:id` | Teacher/Admin | Update custom exam (only while `draft`) | +| DELETE | `/api/exam/custom/:id` | Teacher/Admin | Delete custom exam (only while `draft`) | +| POST | `/api/exam/custom/:id/save-template` | Teacher/Admin | Save exam structure as reusable template | +| GET | `/api/exam/custom/:id/validate` | Teacher/Admin | Run validation checks on custom exam | +| POST | `/api/exam/custom/:id/assign` | Teacher/Admin | Assign custom exam to students/batches | + +--- + +## 53. Exam Session Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/exam/:id/session` | Student | Load exam session | +| POST | `/api/exam/:id/autosave` | Student | Auto-save answers | +| POST | `/api/exam/:id/submit` | Student | Submit exam | +| GET | `/api/exam/:id/results` | Student | Get results (if released) | + +--- + +## 54. Grading Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/grading/queue` | Admin/Teacher | Get submissions pending grading | +| GET | `/api/grading/:attemptId` | Admin/Teacher | Get submission for grading | +| POST | `/api/grading/:attemptId/submit` | Admin/Teacher | Submit grade | +| POST | `/api/grading/ai-suggest` | Admin/Teacher | Get AI grade suggestion | + +--- + +## 55. Course Generation Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/course/gap-analysis` | User | Get gap analysis from exam/placement | +| POST | `/api/course/auto-generate` | User | Auto-generate course from gap profile | +| POST | `/api/course/create` | Admin/Teacher | Manually create course | +| GET | `/api/course/:id` | User | Get course with modules and progress | +| PUT | `/api/course/:id` | Admin/Teacher | Update course (publish) | +| POST | `/api/course/:id/progress` | Student | Report resource completion | +| POST | `/api/course/:id/checkpoint` | Student | Submit checkpoint exercise | +| POST | `/api/course/:id/post-test` | System | Assign post-course assessment | + +--- + +## 56. AI Course Generation Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/ai-course/english/create` | User | Start AI English course generation | +| POST | `/api/ai-course/ielts/create` | User | Start AI IELTS course generation | +| GET | `/api/ai-course/:id/quality` | Admin/Teacher | Get quality gate results | +| POST | `/api/ai-course/:id/approve` | Admin/Teacher | Approve AI-generated content | +| POST | `/api/ai-course/:id/reject` | Admin/Teacher | Reject with notes | +| GET | `/api/ai-course/:id/validation` | Admin | Get IELTS validation status | + +--- + +## 57. Entity Onboarding Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/entity/students/validate-csv` | Admin | Upload and validate CSV | +| POST | `/api/entity/students/bulk-create` | Admin | Create accounts from validated CSV | +| POST | `/api/entity/students/send-credentials` | Admin | Send credential emails | +| POST | `/api/entity/students/:id/resend-credentials` | Admin | Resend to one student | +| POST | `/api/entity/students/resend-all-pending` | Admin | Resend to all pending | +| POST | `/api/entity/students/sis-import` | Admin | Import from SIS | + +--- + +## 58. Adaptive Engine Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/adaptive/dashboard` | Admin | Get engine dashboard data | +| GET | `/api/adaptive/students` | Admin/Teacher | Get students with adaptive profiles | +| GET | `/api/adaptive/student/:id/signals` | Admin/Teacher | Get signal timeline for student | +| GET | `/api/adaptive/settings` | Teacher | Get current threshold settings | +| PUT | `/api/adaptive/settings` | Teacher | Update threshold settings | + +--- + +## 59. Entity and Branding Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/entity/:id/level-mapping` | Admin | Get level mapping | +| PUT | `/api/entity/:id/level-mapping` | Admin | Update level mapping | +| GET | `/api/entity/:id/branding` | Admin | Get branding settings | +| PUT | `/api/entity/:id/branding` | Admin | Update branding settings | +| GET | `/api/entity/branding` | User | Get branding for current user's entity | +| GET | `/api/entity/branding?domain=:subdomain` | None | Get branding by subdomain | + +--- + +## 60. Score Release Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/scores/pending` | Admin | Get scores pending approval | +| POST | `/api/scores/:attemptId/release` | Admin | Approve and release scores | +| POST | `/api/scores/:attemptId/reject` | Admin | Reject with reason | + +--- + +## 61. Report and Verification Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/reports/exam/:attemptId/pdf` | User | Generate and download PDF report | +| GET | `/api/verify/:hash` | None | Public score verification | + +--- + +## 62. Taxonomy Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/taxonomy/subjects` | User | Get available subjects | +| GET | `/api/taxonomy/tree` | Admin | Get full taxonomy tree | +| POST | `/api/taxonomy/node` | Admin | Create taxonomy node | +| PUT | `/api/taxonomy/node/:id` | Admin | Update taxonomy node | +| DELETE | `/api/taxonomy/node/:id` | Admin | Delete taxonomy node | + +--- + +# Part XIV -- AI/ML Service Integration + +## 63. OpenAI GPT Integration + +### 59.1 Content Generation Prompts + +**General English Reading Passage:** +``` +Generate a reading passage for English learners at CEFR level {cefr_level}. +Topic: {topic} +Word count: {min_words}-{max_words} +Include {question_count} comprehension questions in these formats: {question_types} +Provide answer keys for all questions. +Return JSON: {"passage": "...", "questions": [...], "answer_keys": [...]} +``` + +**IELTS Writing Task 1 Prompt (Academic):** +``` +Generate an IELTS Academic Writing Task 1 prompt. +Target band: {target_band} +Topic: {topic} +Include: +1. A description of visual data (chart/graph/table/diagram) +2. The prompt text (exactly as it would appear on the IELTS exam) +3. A model answer at band {target_band} level (minimum 150 words) +4. Scoring rubric notes +Return JSON format. +``` + +**IELTS Speaking Cue Card (Part 2):** +``` +Generate an IELTS Speaking Part 2 cue card. +Target band: {target_band} +Topic category: {category} +Include: +1. The main topic +2. 3-4 bullet points for the candidate to address +3. A model response (2 minutes, at band {target_band} level) +4. 3 follow-up questions for Part 3 (abstract discussion linked to the topic) +Return JSON format. +``` + +### 59.2 Configuration + +| Parameter | Value | +|-----------|-------| +| `encoach.openai_api_key` | Stored in `ir.config_parameter` | +| `encoach.openai_model_content` | `gpt-4o` (for content generation) | +| `encoach.openai_model_grading` | `gpt-4o` (for AI grading) | +| `encoach.openai_model_fast` | `gpt-3.5-turbo` (for tagging, classification) | + +--- + +## 64. OpenAI Whisper Integration + +### 60.1 Speech-to-Text + +```python +import whisper + +class WhisperService: + def __init__(self): + self.model = whisper.load_model("base") + + def transcribe(self, audio_path: str) -> str: + result = self.model.transcribe(audio_path) + return result["text"] +``` + +The Whisper model runs locally (no API call) to avoid costs and latency for frequent speaking evaluations. + +--- + +## 65. Quality Gate Algorithms + +### 61.1 CEFR-to-Flesch-Kincaid Mapping + +| CEFR Level | Flesch-Kincaid Grade Range | +|------------|---------------------------| +| A1 | 1--3 | +| A2 | 3--5 | +| B1 | 5--8 | +| B2 | 8--12 | +| C1 | 12--15 | +| C2 | 15+ | + +### 61.2 Grammar Accuracy Check + +Use a grammar checking library (e.g., `language_tool_python`) to detect grammar errors in AI-generated content: + +```python +import language_tool_python + +tool = language_tool_python.LanguageTool('en-US') + +def check_grammar(text): + matches = tool.check(text) + error_count = len(matches) + return {'passed': error_count == 0, 'errors': [m.message for m in matches]} +``` + +**Dependency:** `pip install language_tool_python textstat` + +--- + +## 66. IRT Mathematical Model + +### 62.1 Three-Parameter Logistic (3PL) Model + +The probability that a student with ability `theta` answers correctly a question with parameters `a`, `b`, `c`: + +``` +P(theta) = c + (1 - c) / (1 + exp(-a * (theta - b))) +``` + +Where: +- `a` = discrimination (slope at inflection point) +- `b` = difficulty (theta value where P = (1+c)/2) +- `c` = pseudo-guessing (lower asymptote) + +### 62.2 Maximum Likelihood Estimation + +After each response, update theta using Newton-Raphson iteration: + +``` +theta_new = theta_old + (sum of a_i * (u_i - P_i)) / (sum of a_i^2 * P_i * Q_i) +``` + +Where `u_i` = response (1=correct, 0=incorrect), `P_i` = probability at current theta, `Q_i` = 1 - P_i. + +### 62.3 Standard Error of Measurement + +``` +SEM = 1 / sqrt(sum of I_i(theta)) +``` + +Where `I_i(theta)` is the Fisher information of item `i` at the current theta estimate. + +--- + +## End of Document + +**Document Version:** 1.1 +**New Odoo Modules:** 13 +**Modified Existing Modules:** 6 +**New Database Tables:** 18+ (includes `encoach.exam.template`, `encoach.exam.custom`, `encoach.exam.custom.section`) +**Modified Existing Tables:** 8+ +**New API Endpoints:** ~79 (includes exam template + custom exam endpoints) +**AI/ML Components:** OpenAI GPT, Whisper, IRT/CAT engine, quality gate algorithms, collaborative filtering, ML models +**Adaptive Engine Phases:** 4 (Rule-based, IRT-CAT, Collaborative, Full ML) +**Exam Template Paths:** 2 (International -- locked structure, seeded by developer; Custom -- fully teacher-configurable) +**Python Dependencies:** `numpy`, `scipy`, `sympy`, `textstat`, `language_tool_python`, `reportlab`, `qrcode`, `whisper`, `scikit-learn` + +This document covers the complete backend implementation required by `encoach_workflows_v3.pdf`. The developer should implement these features alongside the existing 27 custom modules and 14 OpenEduCat modules. diff --git a/new_project/ENCOACH_WORKFLOWS_FRONTEND_SRS.md b/new_project/ENCOACH_WORKFLOWS_FRONTEND_SRS.md new file mode 100644 index 00000000..93b8ad92 --- /dev/null +++ b/new_project/ENCOACH_WORKFLOWS_FRONTEND_SRS.md @@ -0,0 +1,2109 @@ +# EnCoach Platform -- Frontend Software Requirements Specification + +**Document Version:** 1.1 +**Date:** April 2026 +**Status:** Active -- Ready for Development +**Source Document:** `encoach_workflows_v3.pdf` (v3.0, April 2026) +**Audience:** Odoo Developer (full-stack, using Cursor IDE) +**Scope:** All new frontend pages, components, and UI flows required to implement the 6 platform workflows, exam template paths (international + custom), adaptive learning engine, Math/IT support, and white-labelling. + +--- + +## Implementation Context + +| Artifact | Location | +|----------|----------| +| **Frontend Repository** | `https://git.albousalh.com/devops/encoach_frontend_new_v2.git` (branch: `main`) | +| **Backend Repository** | `https://git.albousalh.com/devops/encoach_backend_new_v2.git` (branch: `main`) | +| **Staging Frontend** | `http://5.189.151.117:3000` | +| **Staging Backend (Odoo 19)** | `http://5.189.151.117:8069` | +| **Frontend Stack** | React 18, TypeScript, Vite 5, shadcn/ui, TanStack Query v5, React Router v6 | +| **Backend Stack** | Odoo 19, Python 3.11, PostgreSQL 16, Docker Compose | + +--- + +## Table of Contents + +**Part I -- Context and Baseline** +1. [Introduction](#1-introduction) +2. [Existing System Summary](#2-existing-system-summary) +3. [Architecture and Conventions](#3-architecture-and-conventions) + +**Part II -- Workflow 1: User Signup (Individual)** +4. [Registration Page](#4-registration-page) +5. [Email Verification Page](#5-email-verification-page) +6. [Onboarding Wizard](#6-onboarding-wizard) + +**Part III -- Workflow 2: Placement Test** +7. [Placement Test Briefing](#7-placement-test-briefing) +8. [CAT Test Interface](#8-cat-test-interface) +9. [Placement Results and Learning Path Preview](#9-placement-results-and-learning-path-preview) +10. [Payment and Access Options](#10-payment-and-access-options) + +**Part IV -- Workflow 3: Exam Configuration (International + Custom)** +11. [Exam Template Paths](#11-exam-template-paths) +12. [International Template -- IELTS Exam Initialization](#12-international-template----ielts-exam-initialization) +13. [Per-Skill Configuration Panels](#13-per-skill-configuration-panels) +14. [Content Pool and Question Assembly](#14-content-pool-and-question-assembly) +15. [Exam Assembly and Validation](#15-exam-assembly-and-validation) +16. [Custom Template -- Exam Creation](#16-custom-template----exam-creation) + +**Part V -- Workflow 4: General English Exam** +17. [Exam Session Interface](#17-exam-session-interface) +18. [Grading and Score Release](#18-grading-and-score-release) +19. [Results Display and Reporting](#19-results-display-and-reporting) +20. [Result Routing](#20-result-routing) + +**Part VI -- Workflow 5: Course Generation** +21. [Gap Analysis Dashboard](#21-gap-analysis-dashboard) +22. [Course Structure Configuration](#22-course-structure-configuration) +23. [Module Builder](#23-module-builder) +24. [Course Delivery and Progress](#24-course-delivery-and-progress) + +**Part VII -- Workflow 6: Entity Student Onboarding** +25. [Admin Bulk Upload Interface](#25-admin-bulk-upload-interface) +26. [Credential Delivery Dashboard](#26-credential-delivery-dashboard) +27. [Entity Student First Login](#27-entity-student-first-login) + +**Part VIII -- AI Course Generation** +28. [General English AI Course Generation](#28-general-english-ai-course-generation) +29. [AI IELTS Course Generation](#29-ai-ielts-course-generation) + +**Part IX -- Adaptive Learning Engine UI** +30. [Adaptive Engine Dashboard](#30-adaptive-engine-dashboard) +31. [Engine Signals and Decision Visualization](#31-engine-signals-and-decision-visualization) + +**Part X -- Entity Management and White-Labelling** +32. [Entity Level Mapping Configuration](#32-entity-level-mapping-configuration) +33. [White-Label Branding Settings](#33-white-label-branding-settings) + +**Part XI -- Math and IT Subject Support** +34. [Math Content Rendering](#34-math-content-rendering) +35. [IT Content Rendering](#35-it-content-rendering) +36. [Subject-Agnostic Placement and Courses](#36-subject-agnostic-placement-and-courses) + +**Part XII -- Score Release and Reporting** +37. [Admin Score Approval Queue](#37-admin-score-approval-queue) +38. [PDF Report with QR Code](#38-pdf-report-with-qr-code) +39. [Public Score Verification Page](#39-public-score-verification-page) + +**Part XIII -- Technical Specifications** +40. [New Pages Inventory](#40-new-pages-inventory) +41. [New Services Inventory](#41-new-services-inventory) +42. [New Types Inventory](#42-new-types-inventory) +43. [API Contracts](#43-api-contracts) + +--- + +# Part I -- Context and Baseline + +## 1. Introduction + +### 1.1 Purpose + +This document specifies every frontend page, component, and user interaction required to implement the EnCoach platform workflows as defined in `encoach_workflows_v3.pdf` (v3.0). It is the single source of truth for frontend development of the workflow features. + +### 1.2 Scope + +The document covers 6 workflows, the adaptive learning engine (4 phases), Math/IT subject support, white-labelling, and score release/reporting. Each section describes: + +- **What the user sees** -- page layout, components, form fields +- **What the user does** -- interactions, validations, navigation flow +- **What the frontend calls** -- API endpoints, request/response shapes +- **Error and edge cases** -- validation messages, loading states, empty states + +### 1.3 Audience + +This document is written for a full-stack Odoo developer who will implement both the frontend React code and the backend Odoo modules. The developer uses Cursor IDE and has access to both the frontend and backend repositories. + +### 1.4 Design Principles (from Workflow Document) + +| # | Principle | Impact on Frontend | +|---|-----------|-------------------| +| 1 | Exam type is always the root parameter | Every exam/course flow starts with an exam type selector (Academic vs General Training) | +| 2 | Learning path shown before payment | Student sees their personalised course plan BEFORE any payment prompt | +| 3 | Teacher as supervisor, not builder | In adaptive mode, teacher UI shows oversight controls (thresholds, alerts), not manual sequencing | +| 4 | All results stored on all paths | Every exam attempt shows in student history regardless of pass/fail | +| 5 | Shared content database | Content pool browser shows all content once -- never duplicated across exam and course views | +| 6 | White-labelling support | All student-facing pages apply entity branding (logo, colours) when student is linked to an entity | + +### 1.5 Workflow Coverage + +| # | Workflow | Status | Sections | +|---|----------|--------|----------| +| 1 | User Signup (Individual) | To Build | 4--6 | +| 2 | Placement Test | To Build | 7--10 | +| 3 | IELTS / English Exam Configuration | To Build | 11--14 | +| 4 | General English Exam Generation | To Build | 15--18 | +| 5 | Course Generation | To Build | 19--22 | +| 6 | Entity Student Onboarding | To Build | 23--25 | +| -- | AI Course Generation (General English + IELTS) | To Build | 26--27 | +| -- | Adaptive Learning Engine UI | To Build | 28--29 | +| -- | Entity / White-Label Management | To Build | 30--31 | +| -- | Math and IT Subject Support | To Build | 32--34 | +| -- | Score Release and Reporting | To Build | 35--37 | + +--- + +## 2. Existing System Summary + +The platform already has a working frontend with the following inventory. New workflow pages will be built alongside and integrated with this existing codebase. + +### 2.1 Current Inventory + +| Category | Count | Details | +|----------|-------|---------| +| Page Components | 93 | 28 root, 33 admin, 19 student, 14 teacher | +| API Service Modules | 37 | `src/services/*.service.ts` | +| TypeScript Type Files | 29 | `src/types/*.ts` | +| TanStack Query Hooks | 21 | `src/hooks/queries/*.ts` | + +### 2.2 Existing Page Structure + +- **Public:** `/login`, `/register`, `/forgot-password`, `/apply`, `/faq`, `/exam/...` +- **Student** (`/student/*`): dashboard, courses, assignments, adaptive flow, courseware, discussions +- **Teacher** (`/teacher/*`): courses, assignments, attendance, chapters, AI workbench +- **Admin** (`/admin/*`): LMS dashboard, courses, students, platform admin, users, exams, taxonomy + +### 2.3 Key Existing Components + +- `ProtectedRoute` -- role-based route gating (`student`, `teacher`, `admin`, `corporate`, `mastercorporate`, `agent`, `developer`) +- `StudentLayout`, `TeacherLayout`, `AdminLmsLayout` -- role-specific navigation shells +- `AuthContext` -- JWT authentication context +- `api-client.ts` -- centralised Axios instance with `/api` proxy to Odoo backend +- `query-client.ts` -- TanStack Query client configuration + +### 2.4 Tech Stack + +| Technology | Version | Purpose | +|------------|---------|---------| +| React | 18 | UI framework | +| TypeScript | 5.8 | Type safety | +| Vite | 5 | Build tool (SWC plugin) | +| React Router | 6 | Client-side routing | +| TanStack Query | 5 | Server state management | +| shadcn/ui + Radix | Latest | Component library | +| Tailwind CSS | 3 | Styling | +| react-hook-form + Zod | Latest | Form management and validation | +| recharts | Latest | Charts and data visualization | +| lucide-react | Latest | Icons | + +--- + +## 3. Architecture and Conventions + +### 3.1 File Organization + +All new files must follow the existing project structure: + +``` +src/ + pages/ + admin/ # Admin-facing pages (exam config, bulk upload, score approval) + student/ # Student-facing pages (exam session, placement, course delivery) + teacher/ # Teacher-facing pages (content review, grading queue) + PublicPage.tsx # Public pages (score verification, signup) + services/ + workflow-name.service.ts # API service module + types/ + workflow-name.ts # TypeScript interfaces + hooks/ + queries/ + useWorkflowName.ts # TanStack Query hooks + components/ + ui/ # Reusable shadcn primitives + workflow-name/ # Workflow-specific components +``` + +### 3.2 API Communication Pattern + +Every API call follows the existing pattern: + +```typescript +// src/services/example.service.ts +import apiClient from "@/lib/api-client"; + +export const exampleService = { + getItems: () => apiClient.get("/api/example/items"), + createItem: (data: CreateItemRequest) => apiClient.post("/api/example/items", data), +}; +``` + +All endpoints are proxied through Vite dev server: `/api` maps to `http://127.0.0.1:8069`. + +### 3.3 State Management Pattern + +```typescript +// src/hooks/queries/useExample.ts +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { exampleService } from "@/services/example.service"; + +export const useExampleItems = () => + useQuery({ queryKey: ["example", "items"], queryFn: exampleService.getItems }); + +export const useCreateExample = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: exampleService.createItem, + onSuccess: () => queryClient.invalidateQueries({ queryKey: ["example"] }), + }); +}; +``` + +### 3.4 Form Pattern + +All forms use `react-hook-form` with `Zod` validation schemas: + +```typescript +const schema = z.object({ name: z.string().min(1), email: z.string().email() }); +type FormData = z.infer; + +const form = useForm({ resolver: zodResolver(schema) }); +``` + +### 3.5 Routing Convention + +New routes are added to `src/App.tsx` within the appropriate `ProtectedRoute` wrapper: + +```tsx +}> + }> + } /> + + +``` + +--- + +# Part II -- Workflow 1: User Signup (Individual) + +## 4. Registration Page + +**Route:** `/register` +**Access:** Public (unauthenticated) +**Replaces/Enhances:** Existing `/register` page + +### 4.1 Page Layout + +The registration page contains a single centered card with the following fields: + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Full Name | Text input | Required, min 2 characters | | +| Email | Email input | Required, valid email format | Checked for uniqueness on blur | +| Password | Password input | Required, min 8 characters | Strength indicator bar (weak/medium/strong) | +| Confirm Password | Password input | Must match password | | +| Role | Radio group | Required, one of: `academic_student`, `self_learner`, `teacher` | Labels: "Academic Student", "Self-Learner", "Teacher" | +| CAPTCHA | CAPTCHA widget | Required | Integration with reCAPTCHA v2 or hCaptcha. Only shown for individual self-registration; entity bulk-upload accounts bypass this. | +| Submit | Button | All fields valid + CAPTCHA verified | Text: "Create Account" | + +### 4.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| REG-01 | Password strength indicator updates in real time as user types. Strength levels: Weak (red, < 8 chars or no variety), Medium (yellow, 8+ chars with 2 of: uppercase, lowercase, number, symbol), Strong (green, 8+ chars with 3+ of: uppercase, lowercase, number, symbol). | +| REG-02 | Email uniqueness is checked on blur via `POST /api/auth/check-email`. If email exists, show inline error: "An account with this email already exists." | +| REG-03 | CAPTCHA token is included in the registration request body. Backend verifies the token before creating the account. | +| REG-04 | On successful submission, the backend returns a 201 status. Frontend navigates to `/verify-email?email={email}`. | +| REG-05 | On validation error (422), show field-level error messages returned by the backend. | +| REG-06 | A "Already have an account? Sign in" link navigates to `/login`. | + +### 4.3 API Contract + +**Request:** `POST /api/auth/register` + +```json +{ + "name": "string", + "email": "string", + "password": "string", + "role": "academic_student | self_learner | teacher", + "captcha_token": "string" +} +``` + +**Response (201):** +```json +{ + "success": true, + "message": "Account created. Please verify your email.", + "user_id": 42 +} +``` + +--- + +## 5. Email Verification Page + +**Route:** `/verify-email` +**Access:** Public (unauthenticated) +**Query Params:** `?email={email}` + +### 5.1 Page Layout + +Centered card with: + +- Instruction text: "We've sent a 6-digit verification code to {email}" +- 6-digit OTP input (using `input-otp` component already in the project) +- "Verify" button +- "Resend code" link with countdown timer (disabled for 60 seconds after each send) +- Attempt counter text: "Resend attempts: {count}/3" + +### 5.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| VERIFY-01 | OTP input auto-focuses on the first digit. Each digit field advances focus to the next on input. | +| VERIFY-02 | When all 6 digits are entered, the "Verify" button becomes enabled. Optionally, auto-submit on 6th digit. | +| VERIFY-03 | On success (200), navigate to `/onboarding` (the onboarding wizard). | +| VERIFY-04 | On invalid OTP (400), show error: "Invalid verification code. Please try again." Clear the input. | +| VERIFY-05 | On expired OTP (410), show error: "This code has expired. Please request a new one." | +| VERIFY-06 | "Resend code" calls `POST /api/auth/resend-otp`. Maximum 3 resend attempts. After 3 attempts, show: "Maximum resend attempts reached. Please contact support." | +| VERIFY-07 | OTP expires after 15 minutes. Timer is shown below the input: "Code expires in MM:SS". | + +### 5.3 API Contract + +**Verify:** `POST /api/auth/verify-email` +```json +{ "email": "string", "otp": "string" } +``` + +**Resend:** `POST /api/auth/resend-otp` +```json +{ "email": "string" } +``` + +--- + +## 6. Onboarding Wizard + +**Route:** `/onboarding` +**Access:** Authenticated, account status = `unactivated` +**Guard:** If account is already `activated`, redirect to role-appropriate dashboard. + +### 6.1 Wizard Structure + +A multi-step wizard with 4 steps, a progress bar at the top showing the current step (1/4, 2/4, 3/4, 4/4), and Back/Next buttons at the bottom. + +#### Step 1 -- Learning Goal + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Goal | Radio card group | Required, select one | Options are dynamic, loaded from `GET /api/onboarding/goals`. Each option has an icon, title, and description. Examples: General English, IELTS Academic, IELTS General Training, TOEFL, STEP, Mathematics, IT, Other Certification. | + +#### Step 2 -- Target and Timeline + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Target Level/Band | Slider or select | Required | For IELTS: band score slider 4.0--9.0 in 0.5 steps. For General English: CEFR level select (A1--C2). For Math/IT: proficiency level select (Beginner/Intermediate/Advanced). | +| Exam Date | Date picker | Optional | "I have an upcoming exam on..." If set, system calculates study timeline. | +| No Exam Date | Checkbox | -- | "I don't have a specific exam date" -- hides date picker. | + +#### Step 3 -- Study Preferences + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Hours per Week | Slider | Required, 1--40 | Default: 10. Shows estimated completion time based on gap. | +| Study Mode | Radio group | Required | Options: "Self-study", "With a teacher". | +| Learning Style | Checkbox group (multi-select) | Required, at least one | Options: Visual, Audio, Reading, Mixed. Icons for each. | + +#### Step 4 -- Placement Test + +| Content | Description | +|---------|-------------| +| Heading | "Ready to find your level?" | +| Description | "Take a short placement test (~20-30 minutes) so we can create a personalised learning plan for you." | +| Primary action | "Take Placement Test Now" -- navigates to `/student/placement` | +| Secondary action | "Skip for Now" -- displays a warning: "Without a placement test, we'll start you at B1 (intermediate) level. You can take the test anytime from your dashboard." | + +### 6.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| WIZ-01 | Wizard state is preserved in local component state. Data is NOT submitted until the user completes all 4 steps. | +| WIZ-02 | "Back" button on Step 1 is hidden. "Back" on Steps 2--4 returns to the previous step without losing data. | +| WIZ-03 | On Step 4 completion (either "Take Test" or "Skip"), the wizard data is submitted via `POST /api/onboarding/complete`. | +| WIZ-04 | On success, account status changes to `activated`. If "Take Test" was chosen, navigate to `/student/placement`. If "Skip", navigate to `/student/dashboard`. | +| WIZ-05 | The goals list in Step 1 is fetched from the backend and is dynamic. If the platform adds a new exam template, it appears automatically. | +| WIZ-06 | If the user closes the browser mid-wizard and logs in again, they are redirected back to `/onboarding` (because account status is still `unactivated`). | + +### 6.3 API Contract + +**Goals list:** `GET /api/onboarding/goals` +```json +{ + "goals": [ + { "id": "ielts_academic", "title": "IELTS Academic", "description": "...", "icon": "graduation-cap" }, + { "id": "general_english", "title": "General English", "description": "...", "icon": "book-open" }, + { "id": "mathematics", "title": "Mathematics", "description": "...", "icon": "calculator" } + ] +} +``` + +**Complete wizard:** `POST /api/onboarding/complete` +```json +{ + "goal": "ielts_academic", + "target_band": 7.0, + "exam_date": "2026-09-15", + "hours_per_week": 10, + "study_mode": "self_study", + "learning_style": ["visual", "reading"], + "placement_decision": "take_now | skip" +} +``` + +--- + +# Part III -- Workflow 2: Placement Test + +## 7. Placement Test Briefing + +**Route:** `/student/placement` +**Access:** Authenticated, role = `student` +**Prerequisite:** Onboarding wizard completed (account = `activated`) + +### 7.1 Page Layout + +Full-width card with: + +- **Subject selector** (if student goal supports multiple subjects): shown only if the student's profile has Math or IT as goal. Default subject is pre-selected based on onboarding wizard goal. +- **Test overview card:** Subject name, estimated duration "20--30 minutes", dimension count "4 sections", adaptive note "The test adapts to your level -- questions get harder or easier based on your answers." +- **Dimension breakdown table:** + +| Dimension | Questions | Content | Duration | +|-----------|-----------|---------|----------| +| Grammar | ~30 MCQ | Tenses, articles, prepositions, clauses | ~8 min | +| Vocabulary | ~20 items | Definition match, gap fill, collocations | ~5 min | +| Reading | ~10 Q / 2 passages | TF/NG, MCQ -- passages progress easy to difficult | ~10 min | +| Speaking | 3 prompts (optional) | Record 60-second responses | ~5 min | + +- **"Begin Test" button** (primary, large) +- **Note for entity students:** "This test is mandatory. Your results will be shared with your institution." + +### 7.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| BRIEF-01 | For Math subject, the dimension breakdown changes to: Arithmetic (~20 MCQ), Algebra (~15 items), Geometry (~10 Q), Problem Solving (~10 Q). For IT: Computer Basics (~20 MCQ), Programming Logic (~15 items), Networking (~10 Q), Problem Solving (~10 Q). | +| BRIEF-02 | "Begin Test" calls `POST /api/placement/start` which creates a CAT session and returns a `session_id`. Navigate to `/student/placement/test?session={session_id}`. | +| BRIEF-03 | If an active placement session already exists (user refreshed), resume from where they left off. | + +--- + +## 8. CAT Test Interface + +**Route:** `/student/placement/test` +**Access:** Authenticated, role = `student`, active placement session +**Query Params:** `?session={session_id}` + +### 8.1 Page Layout + +Full-screen test interface with no navigation sidebar (distraction-free mode): + +- **Top bar:** Timer (counting up, showing elapsed time), current section name (e.g., "Grammar"), progress indicator (question X of estimated ~Y), "Pause" button (if allowed) +- **Main area:** Question display area (adapts to question type) +- **Bottom bar:** "Previous" button (disabled for CAT -- no going back), "Next" / "Submit Answer" button, section progress dots + +### 8.2 Question Type Renderers + +Each question type requires a specific renderer component: + +| Question Type | Renderer | Used In | +|---------------|----------|---------| +| MCQ (single answer) | Radio button list with A/B/C/D labels | Grammar, Vocabulary, Reading | +| MCQ (multiple answers) | Checkbox list | Reading | +| Gap fill | Text input embedded in a sentence | Vocabulary | +| True/False/Not Given | 3-option radio for each statement | Reading | +| Definition match | Drag-and-drop or select matching | Vocabulary | +| Audio recording | Microphone button, waveform display, playback, re-record | Speaking | +| Reading passage + questions | Split view: passage on left, questions on right (desktop). Scrollable passage above questions (mobile). | Reading | + +### 8.3 CAT-Specific Behaviour + +| ID | Requirement | +|----|-------------| +| CAT-01 | After each answer submission, the frontend calls `POST /api/placement/answer` with the answer. The backend returns the next question (which may be harder or easier based on the IRT algorithm). | +| CAT-02 | The frontend does NOT preload all questions. Each question is fetched one at a time from the backend. Show a brief loading spinner between questions (skeleton loader). | +| CAT-03 | The progress indicator shows an estimated count (e.g., "Question 12 of ~30") because the CAT engine may terminate early if SEM reaches precision. | +| CAT-04 | When the backend returns `section_complete: true`, show a transition screen: "Grammar section complete. Next: Vocabulary" with a 3-second countdown or "Continue" button. | +| CAT-05 | The Speaking section shows a microphone permission prompt. If denied, skip the section with a note: "Speaking section skipped. Your placement will be based on the other three sections." | +| CAT-06 | For speaking prompts, show: prompt text, a "Start Recording" button, a 60-second countdown timer, waveform visualizer during recording, "Stop" button, playback controls, "Re-record" option (max 1 re-record per prompt), and "Submit Recording" button. | +| CAT-07 | Audio recordings are uploaded to the backend as blobs via `POST /api/placement/speaking-upload`. | +| CAT-08 | On the final question of the last section, the "Submit" button shows "Finish Test". After clicking, navigate to `/student/placement/results?session={session_id}`. | + +### 8.4 Auto-Save + +| ID | Requirement | +|----|-------------| +| SAVE-01 | All answers are auto-saved to the backend every 10 seconds via `POST /api/placement/autosave`. | +| SAVE-02 | A small "Saved" indicator appears at the bottom of the screen after each successful auto-save. | +| SAVE-03 | If the browser closes mid-test, the student can resume from the last saved position when they log in again. | + +### 8.5 API Contracts + +**Start session:** `POST /api/placement/start` +```json +{ "subject": "english | math | it" } +``` +Response: +```json +{ "session_id": "uuid", "first_question": { "id": 1, "type": "mcq", "section": "grammar", "stem": "...", "options": [...], "estimated_total": 30 } } +``` + +**Submit answer:** `POST /api/placement/answer` +```json +{ "session_id": "uuid", "question_id": 1, "answer": "B", "time_spent_ms": 12340 } +``` +Response: +```json +{ "next_question": { ... }, "section_complete": false, "test_complete": false, "progress": { "current": 13, "estimated_total": 30 } } +``` + +**Auto-save:** `POST /api/placement/autosave` +```json +{ "session_id": "uuid", "current_answer": { "question_id": 15, "answer": "C" } } +``` + +**Upload speaking:** `POST /api/placement/speaking-upload` (multipart/form-data) +Fields: `session_id`, `prompt_id`, `audio_file` (blob) + +--- + +## 9. Placement Results and Learning Path Preview + +**Route:** `/student/placement/results` +**Access:** Authenticated, role = `student` +**Query Params:** `?session={session_id}` + +### 9.1 Page Layout + +Results page with two main sections: + +#### Section A -- Your Results + +- **Overall CEFR level** displayed prominently (e.g., "B2" with a coloured badge) +- **Per-dimension scores** in a radar chart (Grammar, Vocabulary, Reading, Speaking) +- **Score table:** + +| Dimension | Score | CEFR Level | Gap to Target | +|-----------|-------|------------|---------------| +| Grammar | 72% | B2 | +0.5 to C1 | +| Vocabulary | 58% | B1 | +1.0 to B2 | +| Reading | 65% | B2 | +0.5 to C1 | +| Speaking | Pending | -- | AI evaluation in progress | + +- **Note for entity students:** If `results_release_mode = manual_approval`, show: "Your results are being reviewed by your institution. You will be notified when they are released." Hide the actual scores until approved. + +#### Section B -- Your Personalised Learning Path + +- **Course outline card** with estimated duration, module count, and study hours per week +- **Skill gap modules** listed by priority (largest gap first) with: + - Module name + - Estimated hours + - Resource types (icons: PDF, Video, Audio, Exercise) + - Difficulty level +- **"View Full Course Plan" button** -- expands to show detailed module breakdown +- **Key design principle:** This learning path is shown BEFORE any payment prompt. + +### 9.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| RES-01 | Results are fetched from `GET /api/placement/results?session_id={id}`. | +| RES-02 | If Speaking evaluation is still in progress (`speaking_status: "pending"`), show a "Pending" badge. Poll `GET /api/placement/speaking-status?session_id={id}` every 30 seconds until complete. When complete, update the Speaking row in the table. | +| RES-03 | The learning path preview is fetched from `GET /api/placement/learning-path?session_id={id}`. | +| RES-04 | Entity students whose results are pending approval see a simplified view with a "Results Pending" message. | +| RES-05 | For Math/IT subjects, CEFR levels are replaced with proficiency levels (Beginner/Intermediate/Advanced/Expert). | +| RES-06 | "Continue" button at the bottom navigates to the payment/access page (Section 10). | + +--- + +## 10. Payment and Access Options + +**Route:** `/student/placement/access` +**Access:** Authenticated, role = `student` + +### 10.1 Page Layout + +Three pricing cards side by side: + +| Card | Title | Description | CTA | +|------|-------|-------------|-----| +| Full Access | "Unlock Full Course" | Full subscription or one-time payment. Complete access to all modules, AI tutoring, and practice exams. | "Subscribe Now" | +| Free Trial | "Try Free for 7 Days" | Limited access: first 3 modules only. Upgrade prompt after trial. | "Start Free Trial" | +| Skip | "Continue to Dashboard" | Dashboard access only. No course access. Prompted to subscribe later. | "Skip for Now" | + +### 10.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| PAY-01 | "Subscribe Now" navigates to `/student/subscription` (existing subscription page). | +| PAY-02 | "Start Free Trial" calls `POST /api/subscription/trial` and navigates to `/student/dashboard`. | +| PAY-03 | "Skip for Now" navigates to `/student/dashboard`. A persistent banner appears on the dashboard: "Unlock your personalised course -- Subscribe now" with a dismiss option. | +| PAY-04 | Entity students bypass this page entirely. Their access is controlled by the institution. | + +--- + +# Part IV -- Workflow 3: Exam Configuration (International + Custom) + +The platform supports two distinct exam template paths. Both paths share the same exam session, grading, and score release infrastructure (Part V). The key difference is who defines the exam structure. + +## 11. Exam Template Paths + +### 11.1 Two Paths Overview + +| Aspect | Path 1: International Templates | Path 2: Custom Templates | +|--------|--------------------------------|--------------------------| +| **Examples** | IELTS Academic, IELTS General Training, TOEFL, STEP, IC3 | University midterm, entity-specific exam, department quiz | +| **Who creates the template** | EnCoach development team (pre-loaded during deployment) | Teacher or Entity Admin (via the platform UI) | +| **Structure modifiable?** | No -- parts, question counts, time limits, and skills are permanently locked | Yes -- fully configurable by the creator | +| **Question assembly** | Teacher fills fixed structural slots using Auto/Manual/Hybrid modes | Teacher adds questions freely to their own defined sections | +| **Available to** | All entities and individual students | Only the creating entity or teacher's students | +| **Added via** | Developer seeds template data into `encoach.exam.template` during module installation | Teacher/Admin uses the Custom Exam Creation UI (Section 16) | + +### 11.2 Template Selection Page + +**Route:** `/admin/exam/create` +**Access:** Authenticated, role = `admin` or `teacher` + +The entry point for all exam creation is a template selection page: + +- **Section A -- International Exam Templates:** A grid of cards showing pre-loaded templates (IELTS Academic, IELTS General Training, TOEFL, STEP, IC3). Each card shows: template name, icon, skill count, total questions, total time. A "Locked" badge indicates the structure cannot be modified. Clicking a card navigates to the international template wizard (Section 12). + +- **Section B -- Custom Exam:** A single "Create Custom Exam" card with a plus icon. Clicking navigates to the custom exam creation form (Section 16). + +### 11.3 Functional Requirements + +| ID | Requirement | +|----|-------------| +| TMPL-01 | Available international templates are loaded from `GET /api/exam/templates?type=international`. Only templates with `active = true` are shown. | +| TMPL-02 | The "Create Custom Exam" option is always available to teachers and admins. | +| TMPL-03 | International template cards display a "Locked" badge and tooltip: "This exam structure is defined by the exam body and cannot be modified." | + +--- + +## 12. International Template -- IELTS Exam Initialization + +**Route:** `/admin/exam/ielts/create` +**Access:** Authenticated, role = `admin` or `teacher` + +### 11.1 Page Layout + +A step-by-step wizard (stepper component) with 5 phases matching the workflow document. + +#### Phase 1 -- Exam Initialization Form + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Exam Type | Toggle or radio | Required | Options: "IELTS Academic", "IELTS General Training" | +| Target Skills | Checkbox group (multi-select) | Required, at least one | Options: Listening, Reading, Writing, Speaking. Pre-checked: all four. | +| Exam Title | Text input | Required, max 200 chars | Auto-generated suggestion: "IELTS [Academic/General] -- [Date]" | +| Target Band | Slider | Required, 4.0--9.0 in 0.5 steps | Default: 6.5 | +| Difficulty | Select | Required | Options: Easy, Medium, Hard. Mapped to band ranges. | +| Randomization | Toggle | Default: ON | "Randomize question order within each section" | +| Assembly Mode | Radio cards | Required | **Auto:** System builds entire exam from rules. **Manual:** Teacher handpicks every item. **Hybrid:** System suggests, teacher refines. | +| Score Release Mode | Radio | Required | **Auto-release:** Student sees results immediately. **Manual approval:** Entity admin must approve before student sees results. | + +### 11.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| IELTS-01 | The exam structure (parts per skill, question counts per part) is fixed by the IELTS template and CANNOT be modified. A read-only info box shows: "IELTS Structure: Listening (4 parts, 40 Q), Reading (3 passages, 40 Q), Writing (2 tasks), Speaking (3 parts, 11-14 min)". | +| IELTS-02 | "Next" button saves the initialization data via `POST /api/exam/ielts/create` and returns an `exam_id`. Navigates to Phase 2. | +| IELTS-03 | Assembly mode selection changes the behaviour of Phase 3 (content retrieval). | + +### 11.3 API Contract + +**Create IELTS exam:** `POST /api/exam/ielts/create` +```json +{ + "exam_type": "academic | general_training", + "skills": ["listening", "reading", "writing", "speaking"], + "title": "string", + "target_band": 6.5, + "difficulty": "easy | medium | hard", + "randomize": true, + "assembly_mode": "auto | manual | hybrid", + "results_release_mode": "auto | manual_approval" +} +``` + +--- + +## 13. Per-Skill Configuration Panels + +**Route:** `/admin/exam/ielts/:examId/skills` +**Access:** Authenticated, role = `admin` or `teacher` + +### 12.1 Page Layout + +Tabbed interface with one tab per selected skill. Each tab shows the fixed structure for that skill: + +#### Listening Tab +| Part | Questions | Content Type | Time | +|------|-----------|-------------|------| +| Part 1 | 10 | Conversation (2 speakers, everyday context) | ~6 min | +| Part 2 | 10 | Monologue (social/everyday context) | ~6 min | +| Part 3 | 10 | Conversation (2-4 speakers, academic context) | ~6 min | +| Part 4 | 10 | Monologue (academic lecture) | ~8 min | + +#### Reading Tab (Academic) +| Passage | Questions | Text Type | +|---------|-----------|-----------| +| Passage 1 | 13--14 | Factual/descriptive | +| Passage 2 | 13--14 | Discursive/analytical | +| Passage 3 | 13--14 | Complex argumentative | + +#### Reading Tab (General Training) +| Section | Questions | Text Type | +|---------|-----------|-----------| +| Section 1 | 13--14 | Short texts (everyday) | +| Section 2 | 13--14 | Workplace texts | +| Section 3 | 13--14 | General interest (longer) | + +#### Writing Tab +| Task | Type | Min Words | +|------|------|-----------| +| Task 1 | Academic: describe visual data. General: write a letter. | 150 | +| Task 2 | Essay (both types) | 250 | + +#### Speaking Tab +| Part | Duration | Format | +|------|----------|--------| +| Part 1 | 4--5 min | Familiar topic questions | +| Part 2 | 3--4 min | Cue card long turn (1 min prep + 2 min speaking) | +| Part 3 | 4--5 min | Abstract discussion linked to Part 2 topic | + +### 12.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| SKILL-01 | The structure tables above are READ-ONLY. The teacher/admin cannot change the number of parts, questions per part, or time limits. These are fixed IELTS template values. | +| SKILL-02 | Each skill tab shows the available content pool count: "Available: 245 questions for Listening Part 1". Fetched from `GET /api/exam/ielts/:examId/content-pool-count`. | +| SKILL-03 | A "Configure Content" button on each tab navigates to the content pool view (Section 13) for that specific skill/part. | +| SKILL-04 | A summary panel on the right shows overall exam completion status: how many questions are filled vs required per skill. | + +--- + +## 14. Content Pool and Question Assembly + +**Route:** `/admin/exam/ielts/:examId/content` +**Access:** Authenticated, role = `admin` or `teacher` + +### 13.1 Page Layout + +Split view: +- **Left panel (60%):** Content pool browser with filters +- **Right panel (40%):** Selected questions for the current section + +#### Content Pool Browser + +| Filter | Type | Options | +|--------|------|---------| +| Skill | Dropdown | Listening, Reading, Writing, Speaking | +| Part | Dropdown | Depends on skill (e.g., Part 1--4 for Listening) | +| Difficulty | Slider | Easy / Medium / Hard | +| Topic | Multi-select tags | Loaded from backend | +| Status | Checkbox | Available, Seen (by this student), Flagged, Retired | +| Search | Text input | Search question text/stem | + +Content items displayed as cards with: preview text (truncated), difficulty badge, topic tags, usage count, quality rating. + +#### Assembly Behaviour by Mode + +| Mode | Left Panel | Right Panel | +|------|------------|-------------| +| **Auto** | Greyed out (system selects automatically). "System has selected 40 questions. Review below." | Shows system-selected questions. Teacher can "Swap" individual items. | +| **Manual** | Full browsing. Teacher clicks "Add" on each item. | Shows teacher-selected items with drag-and-drop reordering. Count badge: "12/40 selected". | +| **Hybrid** | System pre-populates suggestions (highlighted). Teacher can accept, reject, or swap. | Shows suggested items. "Accept All" or individual accept/reject buttons. | + +### 13.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| POOL-01 | The content pool is fetched from `GET /api/exam/ielts/:examId/content-pool` with filter parameters. Returns 3--5x more items than needed. | +| POOL-02 | Content filters exclude: items seen by the assigned student (if student is specified), flagged items, retired items. Topic diversity and difficulty curve are applied by the backend. | +| POOL-03 | In Auto mode, `POST /api/exam/ielts/:examId/auto-assemble` triggers the backend to select questions. Frontend displays the result and allows the teacher to swap items. | +| POOL-04 | In Manual mode, the right panel tracks a running count. The teacher cannot proceed until all structural slots are filled (e.g., 40/40 for Listening). | +| POOL-05 | In Hybrid mode, `POST /api/exam/ielts/:examId/suggest` returns suggested items. Teacher reviews and confirms. | +| POOL-06 | "Save & Continue" persists the selection via `PUT /api/exam/ielts/:examId/sections/:sectionId/questions`. | + +--- + +## 15. Exam Assembly and Validation + +**Route:** `/admin/exam/ielts/:examId/validate` +**Access:** Authenticated, role = `admin` or `teacher` + +### 14.1 Page Layout + +Full exam preview with validation report: + +#### Validation Checklist + +| Check | Status | Description | +|-------|--------|-------------| +| Question Count | Pass/Fail | "Listening: 40/40, Reading: 40/40, Writing: 2/2, Speaking: 3 parts" | +| Media URLs | Pass/Fail | All audio files and images are accessible | +| Answer Keys | Pass/Fail | All auto-scored items have correct answers defined | +| No Duplicates | Pass/Fail | No question appears in multiple sections | +| Rubrics | Pass/Fail | Writing and Speaking tasks have rubrics linked | +| Time Specification | Pass/Fail | Each section has a valid time limit | + +- **Red items** block publishing. **Yellow items** are warnings (can proceed with acknowledgement). +- "Preview Exam" button opens a read-only student-view preview of the exam. + +#### Teacher Review Panel (Manual/Hybrid only) + +- Expandable sections per skill with all selected questions +- Swap, reorder, replace actions per question +- Inline passage/audio preview + +#### Publish Actions + +| Button | Action | +|--------|--------| +| "Save as Draft" | `PUT /api/exam/ielts/:examId` with `status: "draft"` | +| "Publish" | `PUT /api/exam/ielts/:examId` with `status: "published"`. Only enabled if all validation checks pass. | +| "Assign to Students" | Opens a student/batch selector dialog. Calls `POST /api/exam/ielts/:examId/assign`. | + +### 14.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| VAL-01 | Validation runs automatically when the page loads via `GET /api/exam/ielts/:examId/validate`. | +| VAL-02 | After publishing, the exam is locked for editing. A "Duplicate" option allows creating a copy for modification. | +| VAL-03 | "Assign to Students" shows a searchable student list and batch list. Multiple students and batches can be selected. Each assignment includes an optional access window (start date, end date). | + +--- + +## 16. Custom Template -- Exam Creation + +**Route:** `/admin/exam/custom/create` +**Access:** Authenticated, role = `admin` or `teacher` + +### 16.1 Page Layout + +A form-based exam builder where the teacher defines the entire exam structure from scratch. + +#### Step 1 -- Exam Properties + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Exam Title | Text input | Required, max 200 chars | | +| Subject | Select | Required | Loaded from taxonomy: English, Mathematics, IT, etc. | +| Description | Textarea | Optional | Exam purpose and instructions for students | +| Total Time | Number (minutes) | Required, min 5 | Overall exam duration | +| Pass Threshold | Percentage slider | Optional, 0--100 | Minimum score to pass. If not set, no pass/fail routing. | +| Score Release Mode | Radio | Required | **Auto-release:** student sees results immediately. **Manual approval:** entity admin must approve. | +| Randomize Questions | Toggle | Default: OFF | Randomize question order for each student | + +#### Step 2 -- Section Builder + +The teacher adds one or more sections. Each section is a collapsible panel: + +| Field | Type | Validation | Notes | +|-------|------|------------|-------| +| Section Title | Text input | Required | e.g., "Part A -- Grammar", "Section 2 -- Essay" | +| Skill/Category | Select | Optional | Tag this section with a skill (Listening, Reading, Writing, Speaking, Grammar, Vocabulary, or custom label) | +| Question Count | Number | Required, min 1 | How many questions this section will contain | +| Time Limit | Number (minutes) | Optional | Per-section time limit. If not set, section shares the overall exam timer. | +| Scoring Method | Select | Required | **Auto-scored:** MCQ, gap fill, TF/NG (system grades automatically). **Rubric-scored:** Writing, Speaking (teacher or AI grades). **Mixed:** section contains both types. | + +- **"Add Section"** button: adds a new section panel +- Sections can be reordered via drag-and-drop +- Sections can be deleted (with confirmation dialog) + +#### Step 3 -- Question Assignment + +For each section, the teacher adds questions using the same content pool browser (Section 14) or by creating new questions inline: + +- **"Browse Content Pool"** button: opens the shared content pool with filters (subject, skill, difficulty, topic) +- **"Create New Question"** button: opens an inline question form (question type, stem, options, correct answer, marks, difficulty) +- Running count: "8/10 questions added" +- The teacher can add questions beyond the defined count (extras are held as alternates for randomization) + +#### Step 4 -- Review and Publish + +Same validation and publish flow as Section 15 (Exam Assembly and Validation), but the validation rules adapt: +- Question count per section must meet the defined minimum +- If scoring method is "Rubric-scored", rubrics must be linked +- Time limits must be valid +- Answer keys must exist for all auto-scored questions + +### 16.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| CUSTOM-01 | Custom exam creation calls `POST /api/exam/custom/create` with the full exam structure (properties + sections + questions). Returns an `exam_id`. | +| CUSTOM-02 | There are NO locked structural constraints. The teacher has full control over section count, question count, time limits, and skill assignments. | +| CUSTOM-03 | Custom exams are scoped to the creating teacher's entity. If the teacher has no entity, the exam is personal (visible only to that teacher's students). | +| CUSTOM-04 | Custom exams use the same exam session interface (Section 17), grading pipeline (Section 18), score release gate (Section 18), and results display (Section 19) as international template exams. | +| CUSTOM-05 | A "Save as Template" option allows the teacher to save a custom exam structure as a reusable template for future exams. This creates a record in `encoach.exam.template` with `editable = true` and `type = custom`. | +| CUSTOM-06 | "My Templates" section on the template selection page (Section 11.2) shows the teacher's saved custom templates. | + +### 16.3 API Contracts + +**Create custom exam:** `POST /api/exam/custom/create` +```json +{ + "title": "UTAS English Midterm -- Spring 2026", + "subject_id": 1, + "description": "...", + "total_time_min": 90, + "pass_threshold": 60, + "results_release_mode": "auto", + "randomize": false, + "sections": [ + { + "title": "Part A -- Grammar", + "skill": "grammar", + "question_count": 20, + "time_limit_min": 30, + "scoring_method": "auto", + "question_ids": [101, 102, 103] + }, + { + "title": "Part B -- Writing", + "skill": "writing", + "question_count": 2, + "time_limit_min": 60, + "scoring_method": "rubric", + "question_ids": [201, 202] + } + ] +} +``` + +**Save as template:** `POST /api/exam/templates/custom` +```json +{ + "name": "UTAS English Midterm Template", + "structure": { "sections": [...] }, + "editable": true, + "type": "custom" +} +``` + +**Get teacher's custom templates:** `GET /api/exam/templates?type=custom` + +--- + +# Part V -- Workflow 4: General English Exam + +## 17. Exam Session Interface + +**Route:** `/student/exam/:examId/session` +**Access:** Authenticated, role = `student`, exam assigned to this student + +### 15.1 Page Layout + +Full-screen distraction-free interface (no sidebar, minimal header): + +- **Top bar:** Exam title, current section name, section timer (countdown), overall progress bar +- **Main area:** Question renderer (varies by question type -- see Section 8.2 for renderers) +- **Bottom bar:** "Previous" button, "Next" button, "Flag for Review" button, "Submit Section" button (on last question of section) +- **Section transitions:** Brief screen between sections showing: "Section Complete. Next: [Section Name]. Time limit: [X minutes]." + +### 15.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| EXAM-01 | The exam is loaded from `GET /api/exam/:examId/session`. Returns all sections, time limits, and questions (pre-loaded, unlike CAT). | +| EXAM-02 | Each section has its own countdown timer. When time expires, answers for that section are auto-submitted and the student moves to the next section. Warning at 5 minutes and 1 minute remaining. | +| EXAM-03 | Answers are auto-saved every 10 seconds via `POST /api/exam/:examId/autosave`. | +| EXAM-04 | "Flag for Review" marks a question with a flag icon. Flagged questions appear in a review panel before final submission. | +| EXAM-05 | Before final submission, show a review summary: answered count, unanswered count, flagged count, per section. "Submit Exam" button confirms submission. | +| EXAM-06 | After submission, navigate to a "Submission Confirmation" page. If `results_release_mode = auto`, show results immediately (Section 17). If `manual_approval`, show "Your exam has been submitted. Results will be available after review." | +| EXAM-07 | For Writing tasks: rich text editor with word count. Minimum word count indicator (turns red below 150 for Task 1, below 250 for Task 2). | +| EXAM-08 | For Speaking tasks: audio recording interface (same as placement test -- Section 8.2). | +| EXAM-09 | For Listening tasks: audio player with play/pause, progress bar, volume control. Audio plays once (IELTS standard). Replay is NOT allowed unless the exam is configured for practice mode. | + +--- + +## 18. Grading and Score Release + +**Route:** `/student/exam/:examId/status` (student view) +**Route:** `/admin/exam/:examId/grading` (admin/teacher view) + +### 16.1 Student View + +| State | Display | +|-------|---------| +| Submitted, auto-scoring in progress | "Your Listening and Reading scores are being calculated..." (spinner) | +| L/R scored, W/S pending teacher review | Show L/R band scores. W/S shows "Pending Review" with estimated wait time. | +| Fully scored, release = auto | Show all scores immediately (Section 17). | +| Fully scored, release = manual_approval | "Your results are complete and under review by your institution. You will be notified when they are released." | +| Released by admin | Show all scores (Section 17). | + +### 16.2 Admin/Teacher Grading Queue + +Page layout: Table of submissions pending grading. + +| Column | Content | +|--------|---------| +| Student Name | Link to student profile | +| Exam Title | Link to exam config | +| Skill | Writing or Speaking (the skill needing manual grading) | +| Submitted At | Timestamp | +| Status | "Pending" / "In Progress" / "Graded" | +| Action | "Grade Now" button | + +**Grading Interface:** When "Grade Now" is clicked, show: +- Student's response (essay text for Writing, audio playback for Speaking) +- Rubric criteria panel with score selectors for each criterion +- For IELTS Writing: Task Achievement, Coherence and Cohesion, Lexical Resource, Grammatical Range and Accuracy (each scored 0--9) +- For IELTS Speaking: Fluency and Coherence, Lexical Resource, Grammatical Range and Accuracy, Pronunciation (each scored 0--9) +- "AI Grade Suggestion" button: calls `POST /api/grading/ai-suggest` and populates the rubric with AI-suggested scores. Teacher can accept or override. +- "Submit Grade" button: saves the score and moves to the next submission in the queue. + +--- + +## 19. Results Display and Reporting + +**Route:** `/student/exam/:examId/results` +**Access:** Authenticated, role = `student`, results released + +### 17.1 Page Layout + +#### Score Summary Card +- **Overall Band Score** (large, prominent): e.g., "6.5" +- **CEFR Equivalent:** e.g., "C1 -- Effective Operational Proficiency" +- **Per-Skill Radar Chart:** Listening, Reading, Writing, Speaking on four axes +- **Per-Skill Table:** + +| Skill | Band | CEFR | Gap to Target | +|-------|------|------|---------------| +| Listening | 7.0 | C2 | -- | +| Reading | 6.0 | C1 | +1.0 | +| Writing | 5.5 | B2 | +1.5 | +| Speaking | 6.5 | C1 | +0.5 | + +#### Feedback Section +- Per-question feedback (expandable accordions per section) +- Weak areas highlighted with recommendations +- "Areas to improve" summary: top 3 weakness areas with linked learning resources + +#### Actions +- **"Download PDF Report"** button: generates PDF with QR code (Section 36) +- **"View Recommended Course"** button: navigates to course generation (Section 19) +- **"Retake Exam"** button (if practice mode): creates a new exam attempt + +--- + +## 20. Result Routing + +Embedded within the results page (Section 17), a prominent action card at the bottom: + +### 18.1 Pass / Sufficient Score Path + +Card title: "Congratulations! You've reached your target." +- "Register for Official IELTS Exam" button -- navigates to `/student/exam-registration` +- "Continue Studying" button -- navigates to `/student/dashboard` + +### 18.2 Below Threshold Path + +Card title: "Let's build a plan to reach your target." +- "Start Adaptive Training" button -- navigates to `/student/course/generate?from=exam&examId={id}` +- Skill gap summary shown inline (from gap analysis) +- Teacher notification note: "Your teacher has been notified of your results." + +--- + +# Part VI -- Workflow 5: Course Generation + +## 21. Gap Analysis Dashboard + +**Route:** `/student/course/generate` +**Access:** Authenticated, role = `student` (individual path) or `admin`/`teacher` (entity path) +**Query Params:** `?from=exam&examId={id}` or `?from=placement&sessionId={id}` + +### 19.1 Page Layout + +#### Skill Gap Visualization +- **Bar chart:** Skills ranked by gap size (largest first). Each bar shows current level vs target. +- **Table view:** + +| Skill | Current | Target | Gap | Priority | Est. Hours | +|-------|---------|--------|-----|----------|-----------| +| Writing | 5.5 (B2) | 7.0 (C2) | 1.5 | High | 40 | +| Reading | 6.0 (C1) | 7.0 (C2) | 1.0 | Medium | 25 | +| Speaking | 6.5 (C1) | 7.0 (C2) | 0.5 | Low | 15 | +| Listening | 7.0 (C2) | 7.0 (C2) | 0.0 | -- | 0 | + +- **Question-type weaknesses:** Expandable per skill. Shows error rate per question type (e.g., "MCQ: 85% correct, Gap Fill: 42% correct"). +- **Topic weaknesses:** Grouped by category (e.g., "Environment: 3/5 wrong, Technology: 1/5 wrong"). + +### 19.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| GAP-01 | Gap analysis is fetched from `GET /api/course/gap-analysis?source={exam|placement}&source_id={id}`. | +| GAP-02 | For individual students, the "Generate Course" button is shown. Clicking it triggers automatic course structure generation (Section 20). | +| GAP-03 | For entity students, the page is view-only. A message states: "Your teacher will configure your course based on these results." | + +--- + +## 22. Course Structure Configuration + +**Route:** `/admin/course/configure/:courseId` (entity path) or `/student/course/configure/:courseId` (individual path) + +### 20.1 Individual Student Path (Auto-Generated) + +The system auto-generates a course structure. The student sees a read-only preview: + +- Course title (auto-generated: e.g., "IELTS Preparation -- Writing & Reading Focus") +- Duration estimate +- Module list with skill assignments, hours, and resource type icons +- "Start Course" button -- begins the course immediately +- "Customize" button (optional) -- allows minor adjustments (reorder modules, skip low-priority skills) + +### 20.2 Entity/Teacher Path (Manual Configuration) + +Admin/Teacher configurable form: + +| Field | Type | Validation | +|-------|------|------------| +| Course Title | Text input | Required | +| Exam Type | Select | IELTS Academic, IELTS General Training, General English | +| Target Band/Level | Slider/Select | Required | +| Duration | Number (weeks) | Required, 1--52 | +| Study Hours/Week | Slider | Required, 1--40 | +| Skills | Checkbox group | Pre-selected from gap analysis (gap >= 1.0). Teacher can override. | +| Skill Weightings | Sliders per skill | Hours per skill. Informed by gap size. Must sum to total estimated hours. | +| Progression Model | Radio cards | **Linear:** Fixed module order. **Parallel:** All sections active simultaneously. **Adaptive:** Engine manages order based on performance. | + +### 20.3 Functional Requirements + +| ID | Requirement | +|----|-------------| +| COURSE-01 | Individual path: `POST /api/course/auto-generate` with `source_id` (exam or placement session). Returns a pre-built course structure. | +| COURSE-02 | Entity path: `POST /api/course/create` with teacher-configured parameters. Returns a `course_id` for module building. | +| COURSE-03 | Progression model "Adaptive" requires the adaptive engine to be active. If not yet deployed, show a note: "Adaptive mode will be available after the engine is deployed. Defaulting to Parallel." | + +--- + +## 23. Module Builder + +**Route:** `/admin/course/:courseId/modules` +**Access:** Authenticated, role = `admin` or `teacher` + +### 21.1 Page Layout + +Each skill section is a collapsible panel containing its modules: + +``` +Writing Section (40 hours, 5 modules) + ├── Module 1: Task Achievement Basics (8h) + │ └── Resources: [PDF] [Video] [Exercise] [AI-Generated] + ├── Module 2: Coherence and Cohesion (8h) + │ └── Resources: [PDF] [Exercise] + └── ... more modules +Reading Section (25 hours, 3 modules) + ├── Module 1: Skimming and Scanning (10h) + └── ... +``` + +#### Module Configuration + +| Field | Type | Notes | +|-------|------|-------| +| Module Title | Text input | Required | +| Estimated Hours | Number | Required | +| Resources | Resource attachment panel | Browse and attach: PDF, Video, Audio, Exercise, AI-generated content. Uses shared content database. | +| Practice Exercises | Exercise builder | Add practice questions with answer keys. | +| Completion Criteria | Select | Options: "Complete all resources", "Score 70%+ on exercises", "Teacher approval" | +| Prerequisite Module | Select (optional) | For linear/adaptive progression | + +### 21.2 Functional Requirements + +| ID | Requirement | +|----|-------------| +| MOD-01 | Resources are selected from the shared content database via a search/browse modal. `GET /api/resources?skill={skill}&difficulty={level}&type={type}`. | +| MOD-02 | AI-generated resources can be requested inline: "Generate AI Content" button opens a brief form (topic, difficulty, type) and calls `POST /api/ai/generate-resource`. | +| MOD-03 | Modules can be reordered via drag-and-drop. | +| MOD-04 | "Publish Course" button on the module builder page publishes the course and assigns it to the student(s). Calls `PUT /api/course/:courseId` with `status: "published"`. | + +--- + +## 24. Course Delivery and Progress + +**Route:** `/student/course/:courseId` +**Access:** Authenticated, role = `student` + +### 22.1 Student Course View + +- **Course header:** Title, overall progress percentage, estimated time remaining +- **Module list:** Collapsible sections per skill. Each module shows: + - Title, status (locked/available/in-progress/completed), progress bar + - Resource list with type icons and completion checkmarks + - "Start" / "Continue" button +- **Adaptive indicators** (when progression = Adaptive): + - "Recommended Next" badge on the module the engine suggests + - "Unlocked!" animation when a new module becomes available + - "Skipped" badge if the engine determines the student doesn't need a module (95%+ score) + +### 22.2 Resource Viewer + +In-platform viewers for each content type: + +| Type | Viewer | Implementation | +|------|--------|----------------| +| PDF | Embedded PDF viewer (browser native or `react-pdf`) | Full-screen overlay or inline | +| Video | HTML5 `