Three engineers, one on-premise machine, and the first digital examination ever conducted by the Andhra Pradesh CID.

Approximately 7,000 candidates were assessed over three days on a single Intel i9 node inside the exam center. Every submission was captured, converted, signed, merged, and machine-evaluated — with results delivered in hours.

Client
AP Police · CID
Sector
Government
Engagement
Department first
Role
Tech Lead · team of 3
01 At a glance

An exam mandate with zero margin for error.

The numbers describe the constraint better than any narrative can. One node, thousands of candidates, a department conducting its first online exam under the public eye.

0
Candidates examined across three days of slots.
0
Submission delivery. No candidate's exam was lost.
0
On-premise i9 node served the entire workload.
0
Skills evaluated: typing, Word, Excel, PowerPoint.
02 The Challenge

A workforce untrained for digital exams. A timeline that didn’t care.

The Andhra Pradesh Crime Investigation Department needed to assess practical computing proficiency across thousands of candidates. The cohort spanned the full skill spectrum — from officers encountering a structured digital exam for the first time, to younger recruits entirely comfortable with computers.

The exam was a real workplace test: typing speed and accuracy, document drafting in Microsoft Word, spreadsheet handling in Excel, presentation creation in PowerPoint. The platform had to ingest whatever candidates produced, without negotiation. File sizes, formats, and content were completely unpredictable.

The mandate was unambiguous. The exam had to run inside the center on government-provided hardware — a single Intel i9 desktop class machine — with no cloud fallback, no horizontal scaling, and no second chances. Every login traceable. Every submission auditable. Every result reproducible. A failure on day one would be a press story by morning.

Candidates were grouped into slots across three days to keep the concurrency manageable and give the conversion and evaluation pipelines time to drain between waves.

i.

Computer-novice candidates

Uploads arrived in unpredictable sizes — from tiny correct files to multi-hundred-megabyte documents loaded with embedded screenshots. The system had to accept all of it.

ii.

One machine, on-premise

A single Intel i9 served the entire examination — auth, web, ingestion, conversion, storage. No horizontal scaling, no managed cloud services, no graceful regional fallback.

iii.

Government-grade integrity

Every authentication, every upload, every conversion had to be atomic, observable, and recoverable. Half-counted submissions or unrecorded logins were not acceptable failure modes.

03 The Approach

A tight monolith with explicit boundaries between every subsystem.

The single-machine constraint pushed the design toward a Next.js monolith with clear seams — auth, exam delivery, ingestion, conversion, evaluation — each one replaceable in isolation if it failed.

The submission pipeline

Every candidate’s artefacts flowed through these seven stages. Each stage was idempotent and recoverable.
01

Authenticate

Hallticket plus bcrypt-hashed date of birth. JWT issued for forty-five minutes.

02

Reveal

Question paper unlocked by super-admin at slot start. Time-bounded.

03

Capture

Typing test plus Word, Excel, PowerPoint uploads via multipart.

04

Convert

Headless LibreOffice rendered every document into PDF on the node.

05

Sign

pdf-lib appended a signature page to every converted output.

06

Merge

One signed PDF per hall-ticket, assembled by pdf-merger.

07

Seal

Submission row flipped atomically; re-entry blocked at the database.

The stack

Every dependency was chosen for predictability under a one-shot, on-premise constraint.
Framework
Next.js
15.3 · App Router
UI
React
19
Language
TypeScript
5.x
Styling
Tailwind CSS
4
ORM
Prisma
6.11
Database
PostgreSQL
on-premise
Auth
NextAuth.js
4.24 · JWT
Hashing
bcrypt
DOB at rest
Conversion
libreoffice-convert
soffice spawn
PDF
pdf-lib · jspdf
signature page
Merge
pdf-merger-js
per hallticket
Uploads
formidable
multipart
Evaluator
Python
post-slot batch
Doc parsing
python-docx · pptx
openpyxl
04 The Architecture

One node. Five subsystems. One job.

The whole platform fit on a single machine. The discipline was keeping the seams clean enough that any one subsystem could be restarted without taking down the others.

Exam Center i9 On-Premise Node Post-Slot Pipeline Candidate Browser Anti-cheat layer NextAuth + JWT Hallticket · bcrypt(DOB) Next.js App Router Exam UI · REST APIs RBAC Middleware USER / ADMIN / SUPER LibreOffice Conversion soffice · pdf-lib · pdf-merger PostgreSQL via Prisma Users · Submissions · Scores Local Filesystem original · pdf · merged Python Evaluator python-docx · pptx openpyxl · diff login exam RBAC convert read / write persist
05 The Incident

When a four-hundred-megabyte Word document met the i9.

Real systems get punched. What matters is whether the punch lands, whether the team recovers, and whether the failure mode disappears for good.

Postmortem · Resolved
Day 1 · Peak slot

Memory exhaustion during a peak slot.

Severity
High
Data loss
None
Submissions lost
Zero
Outcome
Stable thereafter
Symptom

During a peak slot, new file uploads stopped progressing. The Node process became unresponsive to new HTTP traffic, the LibreOffice conversion queue stalled, and a small number of candidates near the end of their window were unable to finalize submissions before the server stopped accepting writes.

Root Cause

A subset of candidates — many encountering a structured exam environment for the first time — produced oversized documents, often Word and PowerPoint files containing full-resolution embedded images and screenshots. Each conversion spawned a headless soffice process. When several of these oversized conversions ran concurrently, their combined working set exhausted system RAM, pushing the box into heavy swap and starving the Node event loop.

Immediate Mitigation
  • Process recycle on the conversion worker to release leaked memory.
  • Serialized the conversion queue — one heavy convert in flight per node.
  • Deferred oversized files to off-peak conversion so the live slot stayed responsive.
  • Coordinated with invigilators to extend the affected candidates’ submission window by a small buffer.
Permanent Fix
  • Pre-upload validation for file size and embedded media before bytes hit disk.
  • Conversion concurrency cap tuned to available RAM, not core count.
  • Backpressure in the upload API so the browser sees queueing instead of timeouts.
  • Per-file conversion telemetry, surfaced before each subsequent slot.
What Did Not Happen

No submission was lost. Every candidate who reached the upload step has a row in Submission, a signed PDF on disk, and a final merged artefact ready for evaluation. The atomic flag flip in the submission transaction made partial state impossible — either the submission was complete or it wasn’t, never half-counted.

06 The Evaluation

Scoring seven thousand candidates in hours, not weeks.

Manual evaluation at this volume would have taken a team of examiners several weeks. A purpose-built Python pipeline replaced that work with a reproducible, auditable batch run.

DOC

Microsoft Word

Headings, paragraph structure, formatting fidelity, content match against the model answer.

python-docx
XLS

Microsoft Excel

Formula correctness, cell values, sheet structure, conditional formatting.

openpyxl
PPT

PowerPoint

Slide count, layout adherence, text content, image placement, animations where required.

python-pptx
TXT

Typing Test

Typed text diffed against the reference paragraph — accuracy, words-per-minute, error rate.

stdlib · diff
evaluator/run_slot.py Python
# Evaluate every submitted hall-ticket for a given slot
from evaluator import word, excel, ppt, typing
from db import session, models

def evaluate_slot(slot_id):
    candidates = models.User.in_slot(slot_id)
    for c in candidates:
        w = word.score(c.word_file,  key="WORD")
        x = excel.score(c.excel_file, key="EXCEL")
        p = ppt.score(c.ppt_file,   key="PPT")
        t = typing.score(c.text_file, key="TYPING")

        session.persist(
            user_id=c.id,
            word=w, excel=x, ppt=p, typing=t,
            total=sum([w, x, p, t]),
        )

if __name__ == "__main__":
    for slot in models.Slot.finished_today():
        evaluate_slot(slot.id)
We built it for a department running its first online exam, on one machine, in front of seven thousand people. The work was to make that fact invisible.
Tech Lead · APCID engineering team
07 The Results

A department’s first digital exam, delivered.

Three days. One on-premise machine. Three engineers. A workforce of seven thousand candidates assessed and scored before the dust settled.

7,000+
Candidates examined
Across multiple slots over three days, on a single on-premise node.
100%
Submission delivery
Zero lost exams, even during the day-one incident.
Hours
From last slot to scores
Automated Python evaluation replaced weeks of manual examiner work.