Files
full_encoach_platform/docs/ASSIGNMENT_WORKFLOW.md
Yamen Ahmad 3b62075d7e feat(platform): course-plan student visibility, multi-voice TTS, OCR sources, branches
Backend (encoach_ai_course):
- workbook_attempt model + scoring + REST endpoints for student attempts
- dialogue_parser splits scripts by speaker, classifies gender, strips labels
- media_service: multi-voice TTS via Polly/ElevenLabs, ffmpeg concatenation,
  manual media upload endpoint (audio/image/video) with size validation
- source_indexer: OCR fallback (pytesseract + pdf2image) for scanned PDFs,
  page-streaming to stay under memory limit
- exercise_extractor + rag_context for RAG-grounded interactive workbooks
- course_plan_pipeline: v2 generator that grounds week material on indexed
  sources and persists grounded_on_json metadata
- security: access rules for new models

Backend (encoach_lms_api):
- branches model + controller (entity-scoped LMS branches)
- classroom_ext + course_ext (assignment + section workflow)
- classrooms controller: students/teachers/assign-course endpoints

Frontend:
- StudentDashboard: surface assigned AI course plans alongside enrollments;
  enrolled-courses stat now counts plans+enrollments
- InteractiveWorkbook + PlanReader components
- AdminCoursePlanDetail: media drawer with upload buttons (audio/image/video),
  hidden file inputs, upload mutation
- AdminBranches page + sidebar entry
- coursePlan/lms/classrooms services + types updated for new endpoints
- i18n: studentDash.myCoursePlans/noCoursePlans (en + ar)

Infra & docs:
- odoo.conf: bump memory limits to 4G/5G for OCR + sentence-transformers
- .gitignore: ignore *.tsbuildinfo
- docs/ASSIGNMENT_WORKFLOW.{md,pdf}
- smoke_*.py end-to-end tests for assignment workflow, entity isolation,
  course-plan RAG pipeline

Made-with: Cursor
2026-04-28 00:17:51 +04:00

10 KiB
Raw Blame History

EnCoach LMS — Assignment Workflow

Audience: Entity admins managing students, teachers, classrooms, sections, and batches. Scope: Single-entity workflow. Cross-entity isolation is enforced by the API and is documented at the end. Goal: Get every student into the right batch (course × section × term) with the right teachers, in the fewest clicks.


1. The mental model

EnCoach uses four canonical entities that build on each other. Read this once — it makes every screen self-explanatory afterwards.

Entity What it represents Owns
Classroom A homeroom: a physical room repurposed as a class group (e.g. "Grade 7-A") Roster of students, homeroom teachers, the courses studied here
Course A subject taught (e.g. "Physics 102") Course sections (A/B/C…), syllabus, learning objectives
Course Section A timetabled instance of a course (e.g. "Physics 102 — Section A") Optional capacity, sequence, branch
Batch The intersection of a classroom × course × section × term The actual enrolled students and the teachers who teach this slice

Rule of thumb: the classroom is the anchor. You set up the classroom once (roster + homeroom teachers), then you bind one or more (course, section, term) tuples to it. Each binding becomes a batch which automatically inherits the roster and homeroom teachers. Per-batch overrides are available when you need them.

Entity (school / academy)
  └── Classroom (homeroom)              ← step 1 & 2
        ├── Roster (students)           ← step 1
        ├── Homeroom teachers           ← step 2
        └── Courses                     ← step 3 (assign a course → cascade)
              └── Batch (course × section × term)   ← step 4 (per-batch tuning)
                    ├── Enrolled students   (= roster, overridable)
                    └── Teachers            (= homeroom teachers, overridable)

2. The 4-step workflow

The Classrooms page is the single entry point. Each tab is a numbered step. The workflow guide at the top of the detail pane shows green check marks as you complete each step.

Step 1 — Roster

Where: Classrooms → pick a classroom → tab 1. Roster

Action: Click Manage Roster and pick the students who belong to this homeroom.

Why it matters: The roster cascades into every batch you create later. Set this once; downstream steps populate themselves.

API: POST /api/classrooms/{id}/students with { student_ids: [...], mode: "set" | "add" }

Step 2 — Faculty

Where: tab 2. Faculty

Action: Click Manage Faculty and pick the homeroom teachers.

Why it matters: When you assign a course in step 3, these teachers are automatically attached to the new batch. You can override per batch later.

API: POST /api/classrooms/{id}/teachers with { teacher_ids: [...], mode: "set" | "add" }

Step 3 — Courses (cascade)

Where: tab 3. Courses

Action: Click Assign Course, then:

  1. Pick a course from the dropdown.
  2. (Optional) Pick a section. Sections are loaded dynamically from /api/courses/{course_id}/sections. Leave on Auto to use the whole course.
  3. (Optional) Enter a term key (e.g. 2026-T1).
  4. Click Assign + Cascade.

What the server does in one transaction:

  • Creates a canonical batch (classroom × course × section × term) (or reuses an existing one).
  • Auto-enrolls every student from the classroom roster into the batch.
  • Attaches the homeroom teachers to the batch.

API: POST /api/classrooms/{id}/assign-course with { course_id, section_id?, term_key?, teacher_ids? }. Cross-entity course/section/teacher IDs are rejected with HTTP 403.

Step 4 — Batches (per-batch tuning)

Where: tab 4. Batches

Action: Each batch row has a Manage button. The dialog has two tabs:

4a. Students

Pre-checked with the batch's actual enrollments. Use this when you need a subset of the homeroom in this batch (e.g. half the homeroom is in section A, the other half is in section B). One click on reset to classroom roster re-syncs back to the cascade.

APIs:

  • POST /api/batches/{id}/students — add students
  • POST /api/batches/{id}/students/remove — remove students

4b. Teachers

Pre-checked with the batch's current teachers. Use this when sections are taught by different teachers, or when a substitute takes over for one batch. One click on reset to homeroom teachers re-syncs.

APIs:

  • GET /api/batches/{id}/teachers
  • POST /api/batches/{id}/teachers with { teacher_ids: [...], mode: "set" | "add" }
  • POST /api/batches/{id}/teachers/remove

3. Worked example

Goal: enroll 24 Grade-7 students into Physics 102 (sections A and B), with Ms. Salem teaching A and Mr. Khaled teaching B.

# Step What you do Result
1 Create classroom Classrooms → New Classroom → "Grade 7" Empty classroom shell.
2 Roster Tab 1 → Manage Roster → pick all 24 students Classroom now has 24 students.
3 Homeroom teachers Tab 2 → Manage Faculty → add Ms. Salem and Mr. Khaled Both are default teachers.
4 Assign Section A Tab 3 → Assign Course → Physics 102 → Section A → 2026-T1 → Assign + Cascade Batch "Grade 7 — Physics 102 — A" is created with 24 students and 2 teachers.
5 Assign Section B Tab 3 → Assign Course → Physics 102 → Section B → 2026-T1 → Assign + Cascade Batch "Grade 7 — Physics 102 — B" is created with 24 students and 2 teachers.
6 Split the roster Tab 4 → on Section A's batch click Manage → tab Students → uncheck students 1324 → Save Section A has 12 students.
7 Split the roster Tab 4 → on Section B's batch click Manage → tab Students → uncheck students 112 → Save Section B has 12 students.
8 Pin teacher per section Tab 4 → on Section A's batch click Manage → tab Teachers → keep only Ms. Salem → Save Section A taught by Ms. Salem.
9 Pin teacher per section Tab 4 → on Section B's batch click Manage → tab Teachers → keep only Mr. Khaled → Save Section B taught by Mr. Khaled.

Total: 9 clicks beyond the picker dialogs. Steps 69 only happen when you need per-section overrides; if every section has the whole homeroom and the same teachers, you stop at step 5.


4. Where assignments come from (the cascade chain)

classroom.student_ids          ──┐
                                  ├─→ batch.student_ids       (on assign-course)
section binding                  ─┘                            (auto-cascade)
                                                                ↓
                                    per-batch override           batch.student_ids
                                    via Tab 4 → Manage           (final state)

classroom.teacher_ids          ──┐
                                  ├─→ batch.teacher_ids       (on assign-course)
                                 ─┘                            (auto-cascade)
                                                                ↓
                                    per-batch override           batch.teacher_ids
                                    via Tab 4 → Manage           (final state)
  • The cascade only runs on assign-course. After that, the batch and the classroom roster diverge independently. Adding a new student to the classroom after a batch exists does not enroll them automatically — re-run the assignment or use Tab 4 → Manage.
  • mode: "add" on roster/faculty mutations unions; mode: "set" replaces. The UI uses set everywhere, so what you see is what you get.

5. Entity isolation guarantees

Every endpoint above is wrapped in the same multi-tenant guard:

  1. The user's entity_ids defines their scope. A user with one or more entities is always entity-scoped, even if they hold Odoo system groups.
  2. Foreign-key IDs sent by the client (course, section, classroom, student, teacher) are validated to belong to the target entity. Cross-entity IDs return HTTP 403.
  3. Model-level @api.constrains rejects direct ORM writes that mix entities — so even shell scripts can't poison the data.

This is verified by the smoke_entity_isolation.py script, which asserts every cross-entity write attempt returns 403 and every cross-entity read leaks zero records.


6. Reference: endpoints used

Method Path Purpose
POST /api/classrooms/{id}/students Set/add classroom roster
DELETE /api/classrooms/{id}/students Remove from roster
POST /api/classrooms/{id}/teachers Set/add homeroom teachers
GET /api/courses/{id}/sections List sections for a course
POST /api/courses/{id}/sections/generate-defaults Generate A/B/C sections
POST /api/classrooms/{id}/assign-course Bind course → create batch + cascade
DELETE /api/classrooms/{id}/courses/{course_id} Detach course (keeps existing batch)
GET /api/batches/{id}/students List batch enrollments
POST /api/batches/{id}/students Add students to a batch
POST /api/batches/{id}/students/remove Remove students from a batch
GET /api/batches/{id}/teachers List batch teachers
POST /api/batches/{id}/teachers Set/add batch teachers
POST /api/batches/{id}/teachers/remove Remove batch teachers

All endpoints require a Bearer token from POST /api/login.


7. Smoke tests shipped with the platform

Script Verifies
smoke_assignment_workflow.py The full 4-step flow against a live backend: classroom → roster → teachers → assign-course → per-batch override. Asserts the cascade and the override are persisted server-side.
smoke_entity_isolation.py An entity-A admin cannot read or modify entity-B records via any of the endpoints above.
seed_dynamic_sections_demo_via_api.py Idempotently seeds a demo classroom + course-with-sections + batches using only the public API.

Run from the repo root with .conda-envs/odoo19/bin/python <script>.py.


Last updated: April 2026. Backend module: encoach_lms_api. Frontend page: frontend/src/pages/ClassroomsPage.tsx.