Appearance
OA Simulator
The OA (Online Assessment) Simulator reproduces the format real companies use for first-round screening — because the actual value to a student isn't just "practice questions," it's practicing under the exact structural constraints of a D.E. Shaw or Flipkart OA: fixed time budgets per section, no going back, mixed question types in one sitting.
The three routes
| Route | Component | Role |
|---|---|---|
/oa/build | OABuilder | compose an exam: sections, question pools, timers, marking scheme |
/oa/exam | OAExam | the timed attempt itself |
/oa/result | OAResult | post-exam analytics, per-question review, Regrade with AI |
Templates persist to users/{uid}/oaTemplates; a template can also be published to the world-readable publicOAs/{templateId} collection so one authored exam is attemptable by anyone. Attempts live in users/{uid}/oaAttempts.
What it reproduces
- Configurable marking schemes — per-difficulty score tiers and negative marking, matching how real OAs actually score (verified against the real D.E. Shaw format: 3 sequential coding problems with individual timers, 10 technical MCQs, 10 aptitude, 0.25 negative marking, no backtracking).
- Per-question and per-section timer modes with strict sequential flow — once a section's time is up, or you move to the next section, you cannot return. The transition is one-way and locked;
NextSectionConfirmgates it explicitly so it's never accidental. - Mixed question types in one exam — a single attempt can pull from DSA (Judge0 runner), CS Fundamentals (AI-graded concept questions), and Aptitude (MCQ/numeric), because real OAs mix all three.
Timer integrity
The timer's source of truth is the server-recorded startedAt timestamp in Firestore, not client-side state. If the timer lived only in React state, a refresh — accidental or intentional — would reset it. Deriving elapsed time from a server timestamp on every load closes that gap, and Firebase Auth persistence is indexedDBLocalPersistence specifically so a refresh doesn't also log the user out mid-exam (see Authentication).
Per-question timers store their own started/locked markers on the attempt document, so even question-level countdowns survive a reload with no drift beyond network latency.
Anti-cheat
useAntiCheat + OAPermissionsGate detect tab-switching and enforce fullscreen during a timed attempt. This is a UX/integrity feature, not a security boundary — it's explicitly scoped as discouraging casual cheating during self-practice, not as a proctoring product. That's an honest distinction worth drawing: "how do you stop cheating" has a very different real answer for a practice tool than for a company's hiring pipeline.
Submission handling during the exam
Coding submissions inside an OA don't block the UI on judging. Each submit writes to users/{uid}/submissions with an oaAttemptId tag, and the background judging Worker picks it up on its cron, claims it with an updateTime precondition (so two Worker invocations can't double-judge), and calls the same Pages Function judging path the interactive runner uses — one judging implementation, not two. The student keeps moving; verdicts land on the attempt via onSnapshot. Details in Cloudflare Workers.
AI-graded questions
CS Fundamentals concept questions (evaluationMethod: ai) route through ConceptPracticeRunner, which grades free-text answers against the rubric authored in the question's <!--RUBRIC--> / <!--EXPECTED--> sections via functions/grade-concept.js. Because a single AI provider going down mid-exam would be a bad experience, grading goes through the nine-provider fallback chain (Gemini → Groq → Cerebras → Mistral → NVIDIA → GitHub Models → Cohere → Ollama → OpenRouter) — if one fails or rate-limits, the next is tried automatically, invisibly to the student. See AI Pipeline.
MCQ answers are graded deterministically — no model involved — the AI layer is only for free-text.
Result analytics & Regrade
OAResult re-fetches the full (heavy) body of each attempted question — via the same on-demand getQuestion() path used everywhere — to build topic-wise weak-area breakdowns after the exam ends, rather than carrying every question's full content through the exam session. The scoring itself is computed client-side by oaEngine.js#computeAttemptResult from the raw per-question records; the Worker judges, the result page scores.
The Regrade with AI button re-runs grading for AI-evaluated answers after the fact — useful when a provider degraded mid-exam or a rubric was updated — writing the fresh verdicts back onto the attempt.
Bugs this section has already paid for
undefinedfield writes — Firestore rejects documents containingundefined. Attempt writes now strip/default every optional field beforesetDoc; this was a live failure, not a lint nit.- Light-mode CSS conflicts — exam chrome was originally styled against dark tokens only; the fix moved every hardcoded color to the shared CSS variables.
Both are catalogued with their symptoms in Troubleshooting.