INC-002: One online assessment, three roles, one portal that couldn't model it
With my co-LSC from the technical team, I designed Ghost OA: a temporary 'conductor' that runs one shared OA and then distributes the results back to each real role. We shipped the backend the...
severity: SEV-2 | kind: operational | date: 2026-07-10 | duration: backend same night / UI in 6 days
Impact
At the start of the internship season, a recruiting company announced a single online assessment covering 3 roles with overlapping shortlists. The portal could only run one OA per role, which meant uploading the same attendance three times and the same students appearing in every role's room sheet.
Summary
With my co-LSC from the technical team, I designed Ghost OA: a temporary 'conductor' that runs one shared OA and then distributes the results back to each real role. We shipped the backend the same night and ran it through manual API calls, then built a proper UI six days later so coordinators could run it themselves.
+- - - - - - - - - -+ registered ∩ attended : GHOST OA : +--(∩)--> ROLE A : 1 candidate list :--+--(∩)--> ROLE B : / 1 attendance : +--(∩)--> ROLE C +- - - - - - - - - -+ deleted after distribute
A only -> appears in A only in list, no role -> skipped
Timeline
- 10 Jul: co-LSC from the industry team flags it: one company, one OA, three roles, overlapping shortlisted students
- The portal's design assumed 1 OA = 1 role; running three separate OA flows would mean duplicate room allocations and three attendance uploads that could drift apart
- Designed Ghost OA: a temporary job record, flagged as a ghost, linked to the real roles, reusing the existing job model so all OA tooling worked on it unchanged
- 10 Jul, 21:47 IST: backend shipped (create, distribute, delete), no UI yet
- Ran ghost OAs through manual API calls with an admin session in Postman during the first days of the season
- 16 Jul: full coordinator UI shipped and added to the sidebar; coordinators could now run it end to end without a developer
Root cause
A design assumption in the data model: every online assessment belonged to exactly one role. Real recruiters don't work that way.
What didn't work
Running three independent OA flows (duplicate students across room sheets, attendance uploaded three times)
Resolution
- Create: pick the company, select 2+ real roles, set the test date
- Upload the shared OA candidate list once: a student registered for ANY linked role is valid
- Finalize registration and OA appearing on the ghost
- Upload attendance once
- Distribute: for each real role, intersect its registered students with the ghost's attendance and write only those results into that role, finalising its OA steps and copying the test date
- Delete: the ghost is permanently removed, leaving only clean per-role data
Action items
- [x] Edge case: a student registered for Role A but not Role B appears only in Role A's results
- [x] Edge case: a student in the OA list but registered for no linked role is skipped
- [x] Coordinator UI so the flow no longer needs a developer mid-season
Lesson
_Under a deadline, ship the smallest correct backend first and operate it by hand. Once the flow is proven in real use, build the UI. Reusing the existing model with a flag, instead of inventing a new one, meant every existing OA tool worked on day one._
credits: Built with my co-LSC (technical team); problem flagged by my co-LSC (industry team).
tags: system-design, data-modeling, node, mongodb, react, placements