Skip to content

feat(assignments): add AI-assisted grading for teacher assignments#282

Draft
mohamedlhaj69 wants to merge 13 commits into
Open-TutorAi:mainfrom
mohamedlhaj69:feat/assignments-ai-grading
Draft

feat(assignments): add AI-assisted grading for teacher assignments#282
mohamedlhaj69 wants to merge 13 commits into
Open-TutorAi:mainfrom
mohamedlhaj69:feat/assignments-ai-grading

Conversation

@mohamedlhaj69

Copy link
Copy Markdown

Description

Adds a full Assignments domain so teachers can create assignments with a grading rubric, students submit their work as a PDF, and teachers can request an AI-generated draft score and feedback before reviewing, editing, and confirming the final grade themselves. The AI is a drafting aid only — students never see the AI draft, only the teacher's finalized grade.

Added

Backend domain (learning/assignments/)

  • Assignment model: title, description, rubric, due date, owning teacher.
  • Submission model: filename, stored file path/size, extracted text, AI draft score/feedback, teacher's final score/feedback, status lifecycle (submittedneeds_manual_review / ai_graded / ai_grade_failedfinalized).
  • AssignmentRepository / SubmissionRepository with ownership-aware queries (get_by_user, get_by_assignment, get_by_assignment_and_user).
  • AssignmentService orchestrating: assignment CRUD, ownership verification, submission intake with text extraction, AI grading, and grade finalization.
  • PDF text extraction on upload (pypdf), run once at submission time and cached on the record rather than re-parsed on every read.
  • AI grading: builds a rubric + extracted-text prompt, resolves the teacher's chosen model through ProvidersService.resolve_provider() (same routing the tutor chat uses across Ollama/OpenAI-compatible providers), calls the provider, parses the JSON response into score + feedback, and degrades gracefully (ai_grade_failed) instead of crashing on any upstream failure.
  • Full REST surface at /api/v1/assignments: create/list/get assignments; submit/list/get submissions; POST .../ai-grade; PUT .../finalize; GET .../submissions/mine for a student's own view.
  • Role- and ownership-gated access throughout: only teachers can create assignments or grade; only the owning teacher can view/list/grade their own assignment's submissions; only the owning student can view or submit their own submission.

Teacher UI

  • Assignments list — teacher's own assignments, "New Assignment" action.
  • Create Assignment form — title, description, due date, and a rubric field explicitly labeled "Used by AI grading" so the connection to grading is visible up front.
  • Submissions List — one assignment's submissions in a table with status pills (Submitted / AI Graded / Needs Manual Review / Finalized) and current score.
  • Submission Review — the core workflow: extracted-text preview of the student's work, a model picker sourced from the same live provider model list the student chat uses, an "AI Suggest" action that produces a clearly-labeled draft, and an editable "Your Final Grade" section (pre-filled from the draft, always teacher-overridable) with a "Confirm Grade" action.
  • A /teacher section shell (sidebar + navbar layout) that didn't exist before this PR — the route existed but had no navigation chrome around it.

Student UI

  • My Assignments list — per-assignment status (Not submitted / Submitted / Graded), with the final score shown once graded.
  • Assignment Detail — file upload form before submission; after submission, shows the submitted file and, once finalized, the score and the teacher's written feedback. No AI-related text or fields appear anywhere in the student UI.

API client (ui/src/lib/apis/assignments/index.ts)

  • 9 functions covering the full surface above, following this codebase's existing client conventions (token-first args, TUTOR_API_BASE_URL, err.detail rejection on failure).

Tests

  • 38 backend tests across the repository, service, and router layers: success paths, auth/ownership denial (cross-teacher and cross-student), missing-resource 404s, validation errors, and AI-call failure handling (mocked upstream success and failure).
  • 11 frontend tests for the API client, covering every exported function including error propagation.

Changed

  • GET /assignments/{id}/submissions/{submission_id} now returns a different, strictly-typed response shape depending on caller role instead of one fixed shape (see Fixed, below).

Deprecated

  • None.

Removed

  • None.

Fixed

  • AI grading previously called a hardcoded model name against only the OpenAI provider config slot, ignoring Ollama entirely and never checking whether that provider was even enabled — so it silently failed whenever the hardcoded model wasn't actually available. It now resolves the model through ProvidersService.resolve_provider(), exactly like the tutor chat does, and the teacher picks the model explicitly.
  • GET /assignments/{id}/submissions/{submission_id} previously returned the AI draft fields (ai_score, ai_feedback, extracted_text) to the owning student as well as the teacher. It's now structurally impossible for a student-facing response to include them, enforced by a dedicated Pydantic model (MySubmissionResponse) rather than relying on the UI to simply not display them.

Security

  • AI draft fields are excluded from every student-facing response at the backend serialization layer, not filtered client-side — a student inspecting network traffic directly cannot see them either.
  • All submission and assignment access is ownership-checked server-side (teacher-owns-assignment, student-owns-submission), independent of what the frontend requests.

Breaking Changes

  • None.

Additional Information

  • Built test-first: repository → service → router → frontend client, each layer's tests written before its implementation (see commit history for the red/green pairs).
  • Found a real, pre-existing bug in a pattern copied from supports.py: async for chunk in file doesn't work against this project's installed FastAPI/Starlette version. Fixed in this PR's own upload handler; supports.py's own upload endpoint likely has the same latent bug and has zero test coverage exercising that path — worth a follow-up fix outside this PR.
  • No classroom/roster domain exists yet, so students currently see all assignments rather than ones scoped to "their" classes — an intentional, known simplification pending that domain being built.
  • AI grading requires at least one enabled provider (OpenAI-compatible or Ollama) with a real, reachable model; without one, grading degrades to a clear manual-only state rather than failing hard or hanging.

Screenshots

Screenshot 2026-07-02 234613 Screenshot 2026-07-02 234711 Screenshot 2026-07-02 234728 Screenshot 2026-07-02 234748 Screenshot 2026-07-02 234823

Test plan:

  • pytest tests/test_assignments.py -q — 38 tests: repository, service, and router coverage (success, auth/ownership, missing resource, validation, AI-failure fallback)
  • pytest tests/test_contract_coverage.py -q — every new UI fetch() maps to a real backend route
  • pytest -q (full suite) — no regressions
  • npm run test:frontend — 11 API client tests + existing component tests, no regressions
  • ruff check / ruff format --check, ESLint / Prettier — clean
  • npm run i18n:parse — locale files regenerated and committed

@mohamedlhaj69 mohamedlhaj69 changed the title Feat/assignments ai grading feat(assignments): add AI-assisted grading for teacher assignments Jul 2, 2026
@mohamedlhaj69 mohamedlhaj69 force-pushed the feat/assignments-ai-grading branch from d1d0018 to b83665f Compare July 3, 2026 18:05
@Oumaima-elkhoummassi

Copy link
Copy Markdown

Hi @mohamedlhaj69
Documentation review — no documentation file found in docs/ for this PR.

What's missing: there's no file in docs/ for this feature. For a feature this size (assignment CRUD, submission workflow, AI grading, finalization, teacher UI, student UI), this means at minimum:

  • A technical doc: all API endpoints at /api/v1/assignments (what they accept, what they return, who can call them), the Assignment and Submission data models, the status lifecycle (submitted → ai_graded / needs_manual_review / ai_grade_failed → finalized), and how AI grading resolves the provider/model
  • A user guide: teacher flow (create assignment with rubric, view submissions, request AI suggestion, confirm final grade) and student flow (submit PDF, view final grade and feedback)

Could you add these before this is ready for review?

@pr-elhajji

Copy link
Copy Markdown
Contributor

Hi, thk for your contribution

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces an end-to-end Assignments feature spanning backend domain + REST API and a new teacher/student UI workflow, including AI-assisted draft grading intended for teachers only.

Changes:

  • Added backend Assignments domain (models, repositories, service) and mounted /api/v1/assignments router.
  • Added UI routes/components for teacher assignment creation + submission review, and student assignment listing/detail + PDF submission.
  • Added frontend API client + tests, plus backend tests for repository/service/router behavior.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
ui/src/tests/apis/assignments.test.ts Vitest coverage for the new assignments API client.
ui/src/routes/teacher/assignments/create/+page.svelte Teacher route wiring for assignment creation page component.
ui/src/routes/teacher/assignments/+page.svelte Teacher assignments index route wiring.
ui/src/routes/teacher/assignments/[assignmentId]/submissions/+page.svelte Teacher submissions list route wiring.
ui/src/routes/teacher/assignments/[assignmentId]/submissions/[submissionId]/+page.svelte Teacher submission review route wiring.
ui/src/routes/teacher/+layout.svelte New teacher layout shell (sidebar/navbar + role guarding).
ui/src/routes/student/assignments/[id]/+page.svelte Student assignment detail route wiring.
ui/src/lib/i18n/locales/fr-FR/translation.json Added/updated i18n keys for assignments UI (FR).
ui/src/lib/i18n/locales/en-US/translation.json Added/updated i18n keys for assignments UI (EN).
ui/src/lib/i18n/locales/ar-MA/translation.json Added/updated i18n keys for assignments UI (AR).
ui/src/lib/components/teacher/pages/SubmissionsList.svelte Teacher view listing submissions for an assignment.
ui/src/lib/components/teacher/pages/SubmissionReview.svelte Teacher submission grading UI, including AI draft request and finalization.
ui/src/lib/components/teacher/pages/Assignments.svelte Teacher assignments list UI + navigation to create/review flows.
ui/src/lib/components/teacher/pages/AssignmentCreate.svelte Teacher form to create assignments (title/description/rubric/due date).
ui/src/lib/components/student/pages/Assignments.svelte Student assignments list UI + status derived from submissions.
ui/src/lib/components/student/pages/AssignmentDetail.svelte Student assignment detail + PDF upload submission UI.
ui/src/lib/components/student/elements/Sidebar.svelte Adds teacher “Assignments” nav entry in the existing sidebar component.
ui/src/lib/apis/assignments/index.ts New typed assignments API client (CRUD + submissions + AI-grade + finalize).
tests/test_assignments.py Backend tests across repository/service/router for assignments + submissions + AI-grade/finalize.
learning/assignments/service.py Core orchestration for submission intake, PDF text extraction, AI grading, and finalization.
learning/assignments/repository.py Repository helpers for assignment/submission lookup patterns.
learning/assignments/__init__.py New domain package initializer.
gateway/http/routers/assignments.py New FastAPI router for assignments/submissions, including AI-grade and finalize actions.
gateway/http/dependencies.py Adds DI factory for AssignmentService.
gateway/http/app.py Registers the assignments router under /api/v1.
data/models/assignment.py New ORM models: Assignment and Submission.
data/models/__init__.py Registers the new ORM models for metadata discovery.

<tr
class="border-b border-gray-200 dark:border-gray-700 text-xs uppercase text-gray-500 dark:text-gray-400"
>
<th class="px-6 py-3">{$i18n.t('Student')}</th>
submission.status
] ?? statusStyle.submitted}"
>
{submission.status.replace('_', ' ')}
Comment on lines +63 to +69
const assignments = await getAssignments(token);
rows = await Promise.all(
assignments.map(async (assignment) => ({
assignment,
submission: await getMySubmission(token, assignment.id)
}))
);
Comment on lines +179 to +180
except Exception:
return self.submissions.update(submission_id, status="ai_grade_failed")
Comment on lines +35 to +42
return (
self.session.query(Submission)
.filter(
Submission.assignment_id == assignment_id,
Submission.user_id == user_id,
)
.first()
)
Comment on lines +135 to +136
@router.post("/{assignment_id}/submissions", response_model=SubmissionResponse)
async def submit_work(
return svc.get_my_submission(current_user.id, assignment_id)


@router.get("/{assignment_id}/submissions/{submission_id}")
if current_user.role == "teacher":
return svc.list_assignments_for_teacher(current_user.id)
# No classroom/roster concept yet — students see every assignment.
return svc.assignments.get_all()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants