Based on UTAS GE1 Course Outline structure (Reading/Writing 10hrs + Listening/Speaking 8hrs)
New Models:
- encoach.course.plan.deliverable: Explicit learning outcome tracking by week/skill
- encoach.course.plan.resource.dep: Resource dependencies (textbooks, videos, etc.)
- encoach.course.plan.assignment: Assign plans to classes/students with progress tracking
- encoach.course.plan.assignment.deliverable: Per-student deliverable completion status
Extended Models:
- course.plan.material: Added media fields (media_type, media_asset_url, media_asset_id,
media_generation_prompt, media_metadata_json) for rich content
- New material types: video_lesson, audio_recording, image_visual, interactive, assessment
AI Agent Tools (agent_tools.py):
- deliverables.detect: Parse course outlines (like GE1 PDF) and extract structured outcomes
- deliverables.fetch: Get deliverables for AI to reference when generating
- resources.fetch: Check available resources before generating content
- resources.save: Persist resource dependencies
- media.suggest_visuals: AI suggests images/diagrams for materials
- media.generate_image: Generate educational images (DALL-E integration ready)
- media.generate_audio: Generate TTS audio (ElevenLabs/Polly integration ready)
- assignment.*: Create assignments and track progress
Pipeline Enhancements (course_plan_pipeline.py):
- generate_deliverables_from_outline(): Parse PDF/text outlines into structured deliverables
- generate_week_materials_with_resources(): Resource-aware content generation
- suggest_media_for_material(): AI visual aid suggestions
- generate_media_for_material(): Actual image/audio generation
New AI Agents (agents_defaults.xml):
- deliverable_detector: Parses GE1-style outlines, extracts deliverables week-by-week
- media_generator: Creates images/audio for teaching materials
- Updated course_planner & course_week_materials with resource tools
REST APIs (course_plan.py):
POST /api/ai/course-plan/<id>/deliverables/detect - Parse outline
GET /api/ai/course-plan/<id>/deliverables - List deliverables
PUT /api/ai/course-plan/deliverables/<id> - Update status
GET /api/ai/course-plan/<id>/resources - List resources
POST /api/ai/course-plan/<id>/resources - Add resource
POST /api/ai/course-plan/materials/<id>/media/suggest - Get visual suggestions
POST /api/ai/course-plan/materials/<id>/media/generate - Generate image/audio
POST /api/ai/course-plan/<id>/assignments - Assign to class/student
GET /api/ai/course-plan/<id>/assignments - List assignments
GET /api/ai/course-plan/assignments/<id> - Get with progress
PUT /api/ai/course-plan/assignments/<id>/deliverables/<del_id> - Update status
Security: Added ir.model.access.csv entries for all new models
Made-with: Cursor
Produces an Odoo-compatible <db>.zip restorable via /web/database/manager
on the live VPS (or curl -F backup_file=@... /web/database/restore).
Why this script instead of POST /web/database/backup:
the Odoo HTTP backup endpoint shells out to pg_dump from $PATH and on
this host that returns "Command pg_dump not found" because pg_dump
lives in the conda env that Odoo wasn't launched from. This script
calls pg_dump directly with PGBIN, copies the filestore, builds a
correct manifest.json (odoo_dump, db_name, version, pg_version,
modules), and zips the three artefacts in the exact layout Odoo's
restore code expects.
Layout matches Odoo's own backup output:
<db>.zip
|- dump.sql (pg_dump --no-owner --format=p)
|- manifest.json (odoo_dump=1, version, pg_version, installed modules)
|- filestore/ (data_dir/filestore/<db>/ contents)
Defaults are encoach_v2 / yamenahmad / /tmp/odoo-data, all overridable
via env vars (DB, DB_USER, DATA_DIR, PGBIN, OUT_DIR). Output filename
is encoach_v2_YYYYMMDD_HHMMSS.zip in ./backups/.
Verified: latest run produced encoach_v2_20260425_031949.zip — 30 MB
compressed, 815 entries, 633 tables x 633 COPY data blocks, 575
filestore files, 94 modules in manifest.
Made-with: Cursor
Explains why /web/database/manager restores appear to succeed but the
live site keeps serving the old data (target DB name mismatch / Odoo
not restarted / dbfilter pinned), and ships a 6-step SSH-based
recovery procedure with safety-net backup, filestore handling, and
verification checklist tied to the known QA fixtures.
Made-with: Cursor
Production QA reported "Submit failed 413" on /generation and plain
"Upload failed" (no detail) on resource upload. Root cause is the
outer reverse-proxy nginx on the VPS — still on default
`client_max_body_size 1m` — rejecting requests before they reach
Odoo. The inner docker-nginx and Odoo limits were already raised in
earlier commits; only the VPS proxy was left unpatched.
UI side
- Add `describeApiError(err, fallback, context)` helper in
lib/api-client.ts. Maps common HTTP codes to human-readable,
actionable toast descriptions (413 → "ask ops to raise nginx
client_max_body_size", 504 → "server took too long, retry", 502
→ "Odoo is restarting", 401 → "sign in again", 4xx/5xx → server
error body + status code).
- Use it in GenerationPage submit, ResourceManager upload,
TeacherLibrary upload, CustomExamCreate save/publish. Replaces
four slightly-different ad-hoc mappings with one source of truth.
Deploy side
- Add deploy/nginx-vps.conf.example: full TLS reverse-proxy template
with the body/timeout knobs that must match Odoo's limit_request
(128 MB) and limit_time_real (900 s).
- Add docs/DEPLOY_413_504_FIX.md: step-by-step remediation guide for
the team lead covering the 3 layers that can block large bodies
(outer nginx, Cloudflare, Odoo worker) and how to verify each.
Made-with: Cursor
E2E QA surfaced a silent-drop bug in the exam-schedule endpoint: the
frontend's student picker sends `op.student.id` values (that's what
/api/students returns), but `encoach.exam.schedule.student_ids` is a
Many2many → res.users. The controller was writing the op.student id
straight through, so schedules linked to whichever res.users row
happened to share that integer id (OdooBot, other staff, etc.) — the
target student got 0 assignments and never saw the exam.
- exam_schedules.py: add `_resolve_student_user_ids` and use it in the
create and update endpoints. Falls back to treating unmatched ids as
direct res.users ids for back-office callers.
- AssignmentsPage.tsx: add a search input + larger list to the
Individual Students picker; long lists were leaving Sarah unreachable
inside the inner scroll container during QA.
Made-with: Cursor
First-visit users now always land on the English UI regardless of
navigator.language. Arabic remains one click away via the toggle, and
the user's explicit pick is persisted to localStorage (encoach-lang)
and honoured on every subsequent load.
- src/i18n/index.ts: set lng: "en", drop navigator/htmlTag from the
detector order so an Arabic-locale browser no longer silently boots
the UI in Arabic before the user has chosen.
- src/lib/api-client.ts: mirror the same policy for the Accept-Language
header sent to the backend, so AI-generated content stays English on
first visit instead of echoing the browser locale.
Made-with: Cursor
Addresses the QA notes on the end-to-end approval flow. Highlights:
Backend
- encoach_ai/generation_submit: create encoach.approval.request on submit so
submitted exams actually reach the approver queue (previously only the
workflow id was stored on the exam, leaving approvers with an empty inbox).
Initial exam status flips to pending_approval instead of draft.
- encoach_exam_template/approval_workflows:
* /api/approval-users filters out students (user_type='student',
op_student_id set, share=True) so the approver dropdown only lists staff.
* _ser_request enriches requests with target_name / target_status and the
current stage approver for UI badges.
* list_requests supports mine=1 / requester=1 so approvers only see queue
items awaiting their action.
* approve_request / reject_request now transition the underlying
encoach.exam.custom to published / rejected on final approval.
- encoach.exam.custom.status: add pending_approval and rejected states to
match the approval workflow transitions.
Frontend UX
- GenerationPage: rubric field shows "Auto-graded — no rubric" for Listening
and Reading (rubrics only apply to Writing / Speaking). Divider dropdowns
now carry explicit help text explaining None / Line / Space / Page Break
and where to write prompts. Selecting an official exam structure
auto-populates the required tasks/sections/parts, the delete button is
hidden for essential tasks, and submit is blocked if the user dropped
below the structure's required count.
- RubricsPage: "Add Criterion" is now a dropdown with IELTS-specific
presets (Task Achievement, Coherence & Cohesion, Fluency & Coherence, …)
keyed off the selected skill, plus a "Custom (blank row)" fallback.
- AdminCourses: added tooltips and helper copy clarifying Difficulty vs
CEFR Level in the Create Course dialog.
- CustomExamCreate: validate the whole form before Save/Publish, surface
backend error messages (413/504/401) instead of a generic toast, read the
exam id from the correct response field, and avoid marking Publish as
failed when only the optional Save-as-Template step errors.
- ResourceManager / TeacherLibrary: upload toast now shows a concrete
reason (HTTP 413 / 504 / 401) instead of a generic "Upload failed".
Config (504 / 413 / resource upload fixes)
- odoo.conf + odoo-docker.conf: raise limit_time_cpu to 600s,
limit_time_real to 900s, limit_request to 128 MB, and memory caps so
/api/exam/generation/submit and multipart resource uploads stop hitting
504 / 413 during QA.
- frontend/Dockerfile (nginx): add client_max_body_size 128m, bump
proxy_read_timeout / proxy_send_timeout to 900s, and disable request
buffering so large AI/resource payloads stream through the proxy.
Made-with: Cursor
Database dumps list 12 openeducat_* modules as installed (openeducat_core,
openeducat_admission, openeducat_assignment, openeducat_attendance,
openeducat_activity, openeducat_classroom, openeducat_exam, openeducat_facility,
openeducat_fees, openeducat_library, openeducat_parent, openeducat_timetable),
but the `backend/openeducat_erp-19.0/` folder was previously in .gitignore.
When a teammate restored the dump on the VPS Odoo failed with "module not
found" on every openeducat_* row in ir_module_module.
- Remove `backend/openeducat_erp-19.0/` from .gitignore and vendor the full
LGPL-3 OpenEduCat Community 19.0 tree (14 modules: 12 core + theme + erp
meta-module). `odoo.conf` and `odoo-docker.conf` already reference this
path in `addons_path`, so restores now succeed on a fresh VPS with no
extra bootstrap step.
- Strip the nested .git metadata that came with the upstream download so
the tree is vendored flat (not as a submodule pointer).
Made-with: Cursor
Frontend
- i18n: install tailwindcss-rtl, Cairo font, RTL-aware direction in index.css.
- Language toggle: localize aria-label / menu label, persist choice, update
document dir synchronously.
- Sidebar: add `side` prop so the drawer pins to the right in RTL; wire up
AdminLmsLayout, RoleLayout (student/teacher) and AppSidebar to pass
side = i18n.dir() === 'rtl' ? 'right' : 'left'.
- AdminLmsLayout: convert every nav item from hard-coded title to titleKey,
translate group labels (incl. the collapsible Training), breadcrumbs,
user menu (Profile / Settings / Logout), help button and toggle aria
labels; replace physical mr-/right- utilities with logical me-/end-.
- AI components (AiTipBanner, AiInsightsPanel, AiAlertBanner, AiSearchBar,
AiAssistantDrawer): apply dir="auto" at the container level, localize
titles, loading / error / empty states.
- Dashboards (admin / student / teacher): wrap numeric values in <bdi>,
localize dates via ar-EG, fix flex direction for KPI and assignment cards.
- UI primitives (breadcrumb, calendar, carousel, dropdown-menu, menubar,
context-menu, pagination, sidebar): flip chevrons in RTL via a scoped
CSS rule, swap pl-/pr-/ml-/mr- for ps-/pe-/ms-/me-.
- Add logical-direction helpers and bidirectional isolation classes.
Locales
- Expand en.ts and ar.ts with full `nav`, `sidebarGroup`, `breadcrumb`,
`userMenu`, `chrome`, `ai`, and dashboard key sets; keep key parity.
API client
- `api-client.ts` reads the active language from localStorage/i18n and sends
`Accept-Language` on every request so the backend can localize AI output.
Backend (encoach_ai)
- openai_service: add _LANGUAGE_NAMES, normalize_language, language-aware
system prompt injection for every OpenAI call.
- coach_service + controllers (coach_controller, ai_controller): thread
the requested language from headers / user locale down to OpenAIService.
- ai_feedback: fix latent registry error by pointing course_id at op.course
instead of the non-existent encoach.course.
Other
- .gitignore: ignore runtime odoo logs and local caches.
Made-with: Cursor
- docs/WORK_REPORT.md: recap of the Phase 0/1/2/3 hardening sprint —
what shipped per phase, branding update, repo migration to the new
full_encoach_platform repo, per-commit file-churn, verification
results (tsc, build, Playwright), known gaps, and an operator
checklist for production promotion.
- docs/USER_MANUAL.md: end-user guide for every role (Student, Teacher,
Admin, Corporate, Agent, Developer). Walks through every portal URL,
the AI coach / IELTS / adaptive flows, human-in-the-loop exam review,
AI prompt editor, feedback triage, GDPR Privacy Center, language +
theme toggles, and troubleshooting.
Made-with: Cursor
- .github/workflows/ci.yml: two jobs — frontend (tsc --noEmit, lint, build,
Playwright) and backend (Postgres 16 + odoo:19 --test-enable
--test-tags encoach_api) — catches regressions before merge.
- docs/adr/: start an Architecture Decision Record trail with
0001 canonical directory layout, 0002 JWT refresh flow,
0003 paginated response envelope, 0004 RAG metadata + chunking.
- docs/PROJECT_SUMMARY.md §21 Hardening Release: full recap of the AI
quality loop, compliance, Paymob, i18n, and CI work shipped in this
drop, plus new DB tables, REST routes, frontend routes, verification
results, and operator-facing configuration.
- README.md refreshed for the v4 split-repo doctrine and the new feature
surface.
- new_project/DEPRECATED.md: formal retirement notice pointing at
backend/ as the canonical tree.
Made-with: Cursor
Roadmap P0
- Ship /logo.svg fallback and rewire asset references (superseded later by
the project-manager PNG in a separate commit).
Roadmap P1
- Response envelope alignment ({items,total,page,size}) across services
and query hooks.
- Approval/ticket UX updates tied to the new backend rollback semantics.
Roadmap P2
- React.lazy + Vite manualChunks to split vendor-radix/charts/icons/forms
from the main bundle and cut initial JS.
- Fix the 181 tsc errors (PaginationParams class + per-service generics).
- Auto-refresh flow for JWT refresh tokens wired into api-client.ts.
Roadmap P3
- i18n: i18next + language detector, en/ar locales, RTL auto-switch,
LanguageToggle component in RoleLayout and AdminLmsLayout.
- GDPR: PrivacyCenter page for data export and right-to-erasure, backed
by gdpr.service.ts; logout via AuthContext after erasure.
- Human-in-the-loop exam review UI: admin ExamReviewQueue + ExamReviewDetail
pages, useExamReview hooks, exam-review.service.ts.
- AI quality loop: AIPromptEditor (versioning + render preview),
AIFeedbackButtons for students, AIFeedbackTriage admin page, dedicated
services, hooks, and types.
- Dark mode: ThemeProvider + ThemeToggle + chart color tokens.
- Accessibility: DialogDescription on every DialogContent; alert-dialog
and sheet updates.
- Retire dead pages: ExamPage marketing, Index, ProfilePage import.
- Playwright e2e scaffolding: playwright.config.ts + e2e/login.spec.ts
smoke tests, npm scripts test:e2e / test:e2e:install.
Made-with: Cursor
- Replace public/logo-icon.png with the approved EnCoach wordmark
(icon + "EnCoach" + tagline "Unlock your potential with AI powered platform").
- Login page shows the full branded logo at h-36 and drops the duplicate
text heading since the wordmark is now baked into the asset.
- Student/Teacher/Admin sidebars show a small rounded icon when collapsed
and the full wordmark at h-14 when expanded, removing the previous
duplicated "EnCoach" text span.
- og:image in index.html already points at /logo-icon.png so social cards
pick up the new branding automatically.
Made-with: Cursor
Wire the three admin Reports pages (/admin/student-performance,
/admin/stats-corporate, /admin/record) to a new
encoach_lms_api/controllers/reports.py that aggregates from
encoach.student.attempt.
* /api/reports/student-performance: per-student band averages + CEFR,
with entity / level / search filters.
* /api/reports/stats-corporate: by_module bar, N-month trend line,
CEFR distribution pie, entity comparison bar, threshold + entity
filters and meta.attempts_considered for UI.
* /api/reports/record: paginated attempt history with entity / user /
period filters, EX-### exam codes, duration derived from start/end.
* /api/reports/filters: shared picker returning only entities /
students that actually have attempts.
Frontend: new reports.service.ts, all three pages rewritten to hit
these endpoints; Recharts graphs now read live data, CSV export added
on Student Performance and Record.
Seeding: seed_reports.py completes any in_progress attempts and
backfills 6 months of historical attempts across 3 entities so the
trend / distribution / KPI panels have meaningful data. Idempotent.
Tests: test_reports_flows.py (25/25 PASS) covers shape, filters,
pagination. Regressions still green: Configuration 24/24, Support
29/29, Training 26/26. Browser-verified on localhost:8080 with admin
login — all 4 tabs in Stats Corporate render, Student Performance
shows real students + KPIs, Record shows 28 attempts with filter +
CSV export working.
Docs: new §20 in PROJECT_SUMMARY.md documenting scope, artifacts,
seeding, test results, and gotchas (encoach.exam.custom uses `title`
not `name`; encoach.entity requires `code`; in_progress attempts are
excluded from aggregates).
Made-with: Cursor
Audit of the admin Configuration section (FAQ Manager, Notification Rules,
Approval Config) surfaced three contract mismatches and one missing schema.
FAQ
* Backend `encoach.faq.category.audience` / `encoach.faq.item.audience`
rejected the `both` and `entity` values emitted by the FAQ Manager
UI. Widened the Selection so the UI's full vocabulary round-trips.
* FAQ item `video_url` was already accepted by the UI but silently
dropped by the controller; now persisted via a new `video_url`
field on `encoach.faq.item` and serialized on list/get responses.
Notification Rules
* Added `days_before`, `frequency`, `channel`, `entity_id` to
`encoach.notification.rule`. These are the exact fields the admin
form collects; prior to this change they were serialized into the
JSON body, ignored by the controller, and omitted from responses,
leaving the Active switch permanently off and the table columns
blank.
* Controller now translates `active` <-> `is_active` both ways so the
new frontend contract and legacy callers coexist. Missing required
fields return HTTP 400 instead of 500.
Approval Workflows
* The controller had been hand-rolling raw SQL against three tables
(`encoach_approval_workflow`, `_stage`, `_request`) that no Odoo
model declared, so the tables never existed. List() was guarded and
returned empty; create() would 500 with "relation does not exist".
* Introduced real ORM models in `encoach_exam_template/models/approval.py`
plus access rights, which auto-provision the tables on -u.
* Rewrote the controller to use ORM, added PATCH, and emitted both
`items` and `results` in the list envelope so the frontend's
PaginatedResponse reader and legacy callers both work. Step
payloads now carry `max_days`, `auto_escalate`, and
`notification_email` end to end.
* Frontend `approvalsService` and `ApprovalWorkflowConfig` updated to
send the full stage shape + `allow_bypass`, tolerate both envelope
keys, and validate at least one approver before submit.
Schema delta applied via `./run.sh -u encoach_exam_template,encoach_lms_api`.
Verified with new `test_config_flows.py`: 24/24 passing. Regression runs
on `test_support_flows.py` (29/29) and `test_training_flows.py` (26/26)
remain green.
Made-with: Cursor
Flip the source-of-truth model: the two split repos at git.albousalh.com are
now the authoritative origins for each half of the stack; this workspace
(`odoo19/`) is demoted to a developer working tree / full-stack monorepo
mirror.
- Header banner rewritten: canonical repos stated up front with links; mark
the monorepo mirror as secondary.
- §6 restructured into 6.1–6.8: layout diagram, reordered remotes table
(backend-v4 + frontend-v4 first), feature workflow that ends in publishing
to the canonical repos (monorepo push is now optional), mandatory
`git subtree split + push` commands for both halves, per-repo "what's
shipped / what's not", VPS team-lead quickstart, safety rules, creds.
Made-with: Cursor
- Header banner: add 2026-04-19 entry for the release to backend-v4 /
frontend-v4 split repos; state upfront that this monorepo is the single
source of truth.
- §6 rewritten: layout diagram, remotes table, feature-branch → commit →
publish workflow, exact `git subtree split` commands used to fast-forward
frontend-v4/main and backend-v4/main, notes on what each split repo does
and does not contain, and the safety rules for files that must never be
committed (pgdata backups, build caches, env files).
Made-with: Cursor
- Replace section list with checkbox-based section type selector (Social Conversation, Social Monologue, Academic Discussion, Academic Monologue) matching old platform
- Add Set Up Exercises wizard for Listening sections with listening-specific exercise types
- Add exercise display with edit/delete/clear-all for each listening section
- Add Save/Discard/Edit buttons and read-only mode for listening context textarea
- Generalize exercise wizard and edit dialog to work with both Reading and Listening modules
Made-with: Cursor
- Add per-section instructions field to exercise wizard with sensible defaults
- Group exercises by type with section headers showing instructions
- Pass type_instructions to backend AI prompt for context-aware generation
- Add instructions editing in exercise edit dialog
- Update Exercise interface to include instructions field
Made-with: Cursor
- Fix ELAI video generation (correct render endpoint, script splitting for 60s limit)
- Fix speaking script generation error handling and empty response display
- Add custom exam list API (GET /api/exam/custom/list)
- Add assignments REST API (list, create, get)
- Add rubrics REST API (list, create)
- Enhance Generation page: dynamic exam structures, auto-module selection, preview dialog, audio player
- Improve submit feedback with exam ID and status in toast notifications
- Fix ExamsListPage to show both custom exams and exam sessions
- Connect RubricsPage to backend API with fallback data
- Add Dockerfile, docker-compose.yml, requirements.txt for deployment
- Fix placement, grading, scoring, and auth controllers
- Add ErrorBoundary component for frontend resilience
- Add QA report and credentials documentation
Made-with: Cursor
- Add student_progress, course_section models and module_resources M2M
- Activate encoach_resources module with security, views, and ielts_certified field
- Add password strength indicator JS on registration page
- Rewrite onboarding wizard step 4 as placement test prompt with skip option
- Dynamic goal list fetched from exam templates in DB with fallback
- B1 default CEFR level assigned when placement test is skipped
- Teacher review dashboard with 2 API endpoints for AI content approval
- IELTS examiner review interface with review_status, examiner_notes fields
- Content source gate: mandatory review for AI, spot-check for certified
- Resource-to-learning-style matching in adaptive engine
- Daily cron job for teacher no-progress alerts via mail.activity
- Entity student placement redirect on dashboard access
- Fix Odoo 19 compat: search view groups, ir.cron fields, OWL xml templates
Made-with: Cursor
- Fix jwt_required decorator to set request.env user context via
update_env() instead of passing user as kwarg (fixes null student_id
on attempt creation for auth='none' API routes)
- Build standalone exam session page with timer, question navigator,
auto-save, and instant score display (no dependency on web.frontend_layout)
- Add exam_delivery.js for client-side exam interaction (MCQ selection,
section navigation, countdown, autosave, submit & results)
- Generate per-session JWT in controller for API calls from the exam page
Made-with: Cursor
- Migrate 5 OWL components from deprecated useService("rpc") to useService("orm") for Odoo 19
- Fix _paginate() signature mismatch across 6 controllers (dual calling convention)
- Fix taxonomy API serializers referencing non-existent fields (icon, difficulty)
- Fix branding controller validate_token() called with wrong arguments
- Add .sudo() to exam template/question model access under auth='none'
- Restore missing encoach_core files (security groups, permissions seed, core models)
- Create encoach_api shared controller utilities module
- Add Entity list/form/search views and menu items
- Add Taxonomy (Subjects/Domains/Topics) views and menu items
- Fix deprecated XML group expand="0" patterns across 15 view files
- Remove stale model references from adaptive __init__.py and access CSV
- Update .gitignore to exclude local dev artifacts
Tested: 97% pass rate (64/66 tests) across UI navigation, CRUD, and API endpoints.
Made-with: Cursor
Extends odoo:19.0 with all requirements from new_project/requirements.txt
(openai, torch, sentence-transformers, faiss-cpu, etc.) so they are
baked into the image at build time — no more manual pip installs needed.
Updates docker-compose.yml to use build: . and image: encoach-backend:latest.
Made-with: Cursor
Infrastructure files for staging deployment pipeline.
The post-receive hook on the staging server will build and
deploy automatically when PRs are merged to main.
Made-with: Cursor