All projects

IELTS Writing Corrections

Replaced a Google-Docs correction flow with in-platform IELTS essay marking: tutors highlight and annotate spans and score four bands, and students get a colour-coded corrected essay.

Role
Full-stack engineer (API, tutor workspace, student view)
System
EasyPeasyFluent / Edusogno: api-v2, tutor-v3, academy-v2
An essay with colour-coded highlighted spans and a band-score card

Results at a glance

  • Tutors select any span of an essay, tag it (grammar, vocabulary, spelling, punctuation, coherence), and add a note and a suggested rewrite.
  • Band scoring on the real IELTS scale (whole and half bands, four criteria, computed overall), with Save Draft that never reaches the student.
  • Students see their essay with colour-coded, tappable highlights, the band card, and the tutor's feedback, and a notification deep-links straight to it.
  • Legacy Google-Doc and AI-only corrections still render as before; the token-in-URL iframe is gone for new corrections.

Overview

Students preparing for IELTS submit Task 1 and Task 2 essays, and a tutor corrects them. Before this project, the correction lived in a Google Doc. I moved it into the platform as structured data: highlighted spans with error types, notes and suggestions, four band scores, and overall feedback.

Architecture

API (Laravel). A writing_corrections table stores each span: error type, selected text, UTF-16 start and end offsets, note, and suggestion, anchored per question so Task 1 and Task 2 stay separate. Band scores and overall feedback live on the student's exercise. Tutor endpoints cover create, amend, and delete for spans, plus submit. The student endpoint is owner-scoped and returns an empty shape until the tutor submits.

Tutor app (React). A submissions inbox (pending, corrected, all, with search) and a correction workspace: select text, tag it, add a note and suggestion, then score and submit.

Student app (React). The corrected essay rendered natively, with colour-coded spans per error type. Tapping a span shows the note and the suggested rewrite. Nested spans stay tappable because every covering correction is kept per segment, narrowest first.

Decisions and fixes

The highlight that jumped to the wrong word

In Chrome, a double-click selects a word plus its trailing space. The API's global TrimStrings middleware trimmed selected_text but left the offsets alone, so the stored span no longer matched its own text. Both frontends then re-anchored it to the first occurrence of the word in the essay.

I fixed it on both sides: the tutor app trims the selection range client-side (the offsets move with the text), and the API excludes selected_text from trimming. There's an HTTP-level test that goes through the middleware.

Only the booked tutor corrects

The first version let any tutor correct any essay. That matched an older feature, but not how students actually book: a student books a correction session with a specific tutor. The service now resolves the booking for each essay and applies it on every tutor path, including the inbox. A tutor who opens someone else's essay gets a clear message and a way back to their inbox, not a generic 403 toast.

Drafts never leak

Save Draft stores feedback and whichever bands are set so far, without marking the essay corrected or notifying the student. Once submitted, drafts are refused, so a half-scored card can never reach a student. Submitting also completes the exercise and writes the overall band as its score, so the student no longer sees "Awaiting score" next to "Corrected".

Scoring on the scale

The first scoring UI was four dropdowns of nineteen values each. I replaced it with the ten bands in a row plus a half toggle, and an overall card that says what's still missing. The band-to-controls arithmetic lives in one helper that's tested against every value the API accepts.

Results

  • Structured, searchable corrections instead of shared documents
  • A tutor inbox that also surfaces essays submitted without a booking
  • A native corrected-essay view for students, in all seven live locales
  • 38 tests across the tutor writing suites at the access-rule pass, with unit and integration suites green in the student app

What I took from it

  • Store text anchors as a self-consistent triple (text, start, end), and don't let any layer trim one without the others.
  • Access rules belong on every path. 'Any tutor can correct' was a parity decision until the booking model said otherwise.
  • Pick the control that matches the value. A band is a whole number and maybe a half, not a 19-item dropdown.
Henry Iddirisu

Henry Iddirisu

AI product engineer · Accra, Ghana · Remote

More projects

LaravelLaravel ReverbPHP

Lesson Whiteboard

Role: Full-stack engineer (sole developer across all three codebases)

A live whiteboard for online language lessons, built end to end across a Laravel API, Reverb WebSockets, the tutor app, and the st…

Next.jsReactTypeScript

Checkout Payments and Student Billing

Role: Full-stack engineer (payment step; billing API extensions; student and…

The payment step of a European e-learning checkout (Stripe, Klarna, Alma, PagoLight, SeQura), plus the billing API and student bil…