Files
encoach_frontend_new_v2/new_project/ENCOACH_WORKFLOWS_BACKEND_SRS.md
Yamen Ahmad 6c93c5d600 feat: EnCoach V2 — complete OWL refactor with 15 new modules
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
2026-04-07 01:53:06 +04:00

94 KiB

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
  2. Existing System Summary
  3. Module Architecture

Part II -- Database Schema 4. Content Tables 5. Exam Tables 6. User and Profile Tables 7. Course Tables 8. Progress and Results Tables 9. Adaptive Learning Tables 10. Entity and Institutional Tables 11. AI Generation Tables

Part III -- Workflow 1: User Signup 12. CAPTCHA Integration 13. OTP Email Service 14. Onboarding Wizard Data Model

Part IV -- Workflow 2: Placement Test 15. CAT Engine 16. Question Bank with IRT Parameters 17. CEFR Mapping Algorithm 18. Speaking AI Evaluation

Part V -- Workflow 3: Exam Configuration (International + Custom) 19. Exam Template Architecture 20. Fixed Template System (International) 21. Content Pool Query Engine 22. Assembly Modes 23. Custom Template System 24. Exam Validation Rules

Part VI -- Workflow 4: General English Exam 25. Exam Session Management 26. Auto-Scoring Engine 27. Score Release Gate 28. PDF Report Generation with QR Code

Part VII -- Workflow 5: Course Generation 29. Gap Analysis Engine 30. Auto Course Structure Generation 31. Adaptive Progression Engine

Part VIII -- Workflow 6: Entity Student Onboarding 32. CSV Parsing and Validation 33. Bulk Account Creation 34. Credential Email Service 35. Entity Level Mapping

Part IX -- AI Course Generation 36. General English AI Content Pipeline 37. AI IELTS Content Pipeline 38. Quality Gate Engine 39. IELTS Standards Validation Engine

Part X -- Adaptive Learning Engine (4 Phases) 40. Phase 1 -- Rule-Based Engine (MVP) 41. Phase 2 -- IRT-Based CAT 42. Phase 3 -- Collaborative Filtering 43. Phase 4 -- Full ML

Part XI -- White-Labelling 44. Entity Branding Model 45. Subdomain Routing

Part XII -- Math and IT Backend 46. Subject-Specific Question Types 47. Subject Taxonomy Extension

Part XIII -- API Endpoint Specification 48. Auth and Signup Endpoints 49. Placement Test Endpoints 50. Exam Template Endpoints 51. IELTS Exam Endpoints 52. Custom Exam Endpoints 53. Exam Session Endpoints 54. Grading Endpoints 55. Course Generation Endpoints 56. AI Course Generation Endpoints 57. Entity Onboarding Endpoints 58. Adaptive Engine Endpoints 59. Entity and Branding Endpoints 60. Score Release Endpoints 61. Report and Verification Endpoints 62. Taxonomy Endpoints

Part XIV -- AI/ML Service Integration 63. OpenAI GPT Integration 64. OpenAI Whisper Integration 65. Quality Gate Algorithms 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

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

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

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:

@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

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

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)

{
  "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:

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

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

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:

{
  "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

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

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

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

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

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

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)

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

@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

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

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"""
                <p>Your account has been created on the {entity.name} learning platform.</p>
                <p><strong>Login URL:</strong> {base_url}/login</p>
                <p><strong>Username:</strong> {user.login}</p>
                <p><strong>Temporary Password:</strong> Your National ID number</p>
                <p>You will be asked to set a new password on first login.</p>
            """,
        })

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:

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

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

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:

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

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:

@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

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:

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.