A college-prediction and career-guidance platform for JEE and NEET aspirants at Physics Wallah. Ideated, pitched and built end-to-end by me — now serving 150,000+ active users with 200,000+ sign-ins.
Rank, category, gender and state go in; a filtered, paginated list of reachable colleges comes out. Twelve JEE counselling boards and eight NEET counsellings, each with its own cutoff schema and quota vocabulary, are unified into one result shape with per-row rank basis so the UI can say which rank produced each match.
Students photograph their OMR sheet. The image goes to object storage, a PyTorch-based parser running on a serverless function extracts 180 answers, and the server scores them against the booklet answer key with multi-accept and dropped-question rules. Aggregate stats per booklet update in real time.
Cloudflare Turnstile-gated OTP through PW's auth API, an HMAC-signed session cookie carrying product entitlements, and seamless adoption of an existing pw.live session so a logged-in PW student never sees a login screen. Sign-in writes back to pw.live too.
Checkout via PW's payment service, with every payment event re-verified server-to-server against the gateway's status API before access is granted. Receipts are generated once and idempotently, and a reconcile step catches payments that completed in another tab.
Twenty-five JSON datasets built by a set of scraper and normaliser scripts ship inside the container and are parsed once per process. No database read sits on the prediction path, which is why a prediction request is CPU-bound and cheap even under exam-result traffic spikes.
A per-student counselling document checklist with uploads, cached with a five-minute cache-aside layer, plus lead records per product so the counselling team sees who predicted what. Both fail open so a cache hiccup never blocks a student.
A single Next.js 16 service in a Docker container on Kubernetes, deployed through Jenkins. Route handlers own HTTP concerns, a request layer validates and orchestrates, and pure prediction engines compute over datasets held in module memory. MongoDB stores leads, payments and OMR submissions across four logical databases; Redis holds tokens, entitlement flags, rate-limit counters and live aggregates.
The session cookie lives 90 days but PW access tokens expire in about a week, and PW rotates the refresh token on every use. A single page load fires more than one session check, so two concurrent refreshes meant the loser presented an already-spent token and logged the student out at the exact moment the system was trying to keep them in.
A Redis SET NX lock per phone serialises refreshes. The rotated refresh token is stored before the new access token is used anywhere; losing the race returns a deferred result that keeps the session standing; a hard refusal deletes the stored token instead of retrying a dead credential on every page load; a PW outage fails open on the token already held. The rotated pair is also written back to the pw.live cookie, because the token just spent was pw.live's copy too.
A JEE candidate holds up to four ranks and a NEET candidate holds a rank plus a score. Each counselling board publishes cutoffs against a different one, in its own quota vocabulary, and some datasets use state ranks that cannot be compared to an all-India rank at all.
Instead of forcing one rank, every result row declares its rank basis and a display label, so the UI states which rank produced each comparison. NEET rows declare AIR or SCORE and probability, reach-slack and sort direction all invert per basis. Twelve adapter functions absorb per-board schema differences. Boards whose ranks are not comparable are excluded outright rather than approximated.
The gateway's webhook payload carries no signature, its browser redirect is user-controllable, it offers no metadata field to attach a user id, and it reports the same intermediate status for "still confirming" and "user abandoned checkout".
Two trust anchors applied identically on the redirect and webhook paths: every event is re-verified server-to-server against the authenticated status API, and the owner and product are recovered from our own pending row keyed on our transaction id, never from the payload. Create-order reconciles the most recent unconfirmed attempts concurrently before charging, catching an older attempt completing in a second tab. Transient webhook failures return 500 so the gateway retries.
Scoring an OMR sheet needs the image, the parser and the answer key. MongoDB is only needed for our analytics, yet looking up the image URL from Mongo meant a database outage would stop every student from getting their score.
The upload step returns an HMAC-signed token binding the storage URL to the verified phone with a six-hour TTL. The parse step reads the URL straight out of the token, so a student cannot aim the parser at an arbitrary URL or score someone else's sheet, and no DB read is needed. All persistence and aggregate updates moved into Next's after() so they run once the response has been sent.
A platform with six external dependencies has to decide, for each one, whether an outage should let users through or stop them. A blanket policy either locks everyone out during a blip or hands out paid access for free.
Fail open: rate limiter, checklist cache, lead writes, aggregate increments, PW token verification during an outage. Fail closed: paid-status checks when Redis answers but Mongo hiccups. Fail loud: when both stores are down the paid check throws a distinct error so the user sees "try again" instead of a false paywall. Entitlement checks return WRONG_NUMBER and BATCH_CHECK_FAILED as separate codes so a PW outage never accuses a student of using the wrong number.