<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[building a High-Fidelity Exam Simulator: the full story]]></title><description><![CDATA[building a High-Fidelity Exam Simulator: the full story]]></description><link>https://uniexamer.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ae1d59e233c22e3a031c4e8/67bb820c-9581-47d7-9ace-7b57216d746f.png</url><title>building a High-Fidelity Exam Simulator: the full story</title><link>https://uniexamer.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 04 Nov 2026 04:12:46 GMT</lastBuildDate><atom:link href="https://uniexamer.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Engineering UniExamer: 7 Technical Challenges Behind an Exam Practice Platform on Next.js and Cloudflare Workers]]></title><description><![CDATA[How UniExamer(uniexamer.com) solved offline answer saving, Cloudflare Workers Free CPU limits, hreflang,KaTeX parsing and AI-assisted question review.]]></description><link>https://uniexamer.hashnode.dev/engineering-uniexamer-technical-challenges</link><guid isPermaLink="true">https://uniexamer.hashnode.dev/engineering-uniexamer-technical-challenges</guid><category><![CDATA[Next.js]]></category><category><![CDATA[cloudflare-worker]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[SEO]]></category><category><![CDATA[edtech]]></category><dc:creator><![CDATA[uniexamer]]></dc:creator><pubDate>Wed, 28 Oct 2026 11:39:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae1d59e233c22e3a031c4e8/dbafeca3-e266-45e4-8750-6630dcff9078.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>UniExamer is a practice platform for university admission tests, including the Bocconi Online Test, CISIA TOLC, Politecnico di Torino TIL, Digital SAT, Enhanced ACT, LSAT, GMAT and GRE. It's built with Next.js 16, React 19, TypeScript, Supabase PostgreSQL, Cloudflare Workers (via OpenNext), next-intl and KaTeX. It serves five languages and more than 1,000 reviewed practice questions.</p>
<p>This article covers seven technical problems we ran into between September and October 2026, and how we solved each one. Several of them were bugs we shipped and then found. We describe those too.</p>
<ol>
<li>Exams change; past results must not The problem. Admission tests change format: the GMAT Focus Edition, the shorter GRE, the Enhanced ACT, Bocconi's penalty rules. Our own questions change as well, whenever a review finds a fault. But a student's result from last week has to stay exactly as it was: the same questions, the same text, the same scoring.</li>
</ol>
<p>What we did.</p>
<p>Exams are data. Each exam is a versioned configuration file (sections, timing, navigation, calculator policy, scoring, skill taxonomy, plus an explicit list of what couldn't be verified). The assessment engine is a set of pure functions for scoring, navigation, timing and seeded question selection, with no exam hardcoded. Attempts pin versions. Each attempt records the exam configuration version and every question's version. Editing a question bumps its version, so earlier attempts keep the text they were shown. Scoring rules live in configuration. When we found that Bocconi publishes a heavier -0.33 penalty for wrong answers on three-option critical-thinking questions, while our engine charged -0.2 everywhere, the fix was a data rule (itemPenaltyOverrides, matched on domain and option count), not a branch in the scoring code. Results recorded before the fix aren't rescored. Lesson: if your domain has external rules that change, make them versioned data from day one. That's what made adding TOLC-E, TOLC-F, TOLC-I and the PoliTo TIL a matter of new configuration files, not new features.</p>
<ol start="2">
<li>Answer saving that survives tunnels, reloads and closed tabs The problem. A timed practice section is stressful. If a dropped connection loses an answer, students stop trusting the product. A naive "send on change" approach has a second, quieter bug: requests arrive out of order. A slow request carrying an older answer can land after a newer one and overwrite it.</li>
</ol>
<p>What we did.</p>
<p>Device first, server second. Every answer and navigation step is written to localStorage (keyed by account and attempt) before it's sent, and stays there until the server confirms it. When the player opens, it reconciles what the device kept against the server's state and resends pending answers oldest first, one at a time. Guarded writes with a logical clock. Each answer carries the player's server-anchored clock. The database writes an answer only if its clock is ahead of the stored one, checked in the same guarded SQL statement as the write. A stale answer is reported back with the answer the server holds. A duplicate succeeds without writing, so a retry never counts time twice. The server still decides. Nothing is replayed into a closed section, a locked answer or another account's attempt. An answer delivered after the deadline is refused, even if it was made in time on a disconnected device. Honest status. The UI says saving, saved, offline or failed, and only says "saved" once the server has confirmed every answer. Retries back off from 2 s to 30 s and resume when the connection comes back. Lesson: offline support is two problems, persistence and ordering. Solving only the first can corrupt data.</p>
<ol start="3">
<li>Fitting a Next.js app into Cloudflare Workers Free's 10 ms CPU limit This was our hardest operational problem.</li>
</ol>
<p>Background. We deploy Next.js to Cloudflare Workers with OpenNext, and connect to Supabase PostgreSQL through Hyperdrive. A Worker can't share a database pool between requests, so each request opens a small pool. One early surprise: behind Hyperdrive, Supabase's transaction pooler left connections stuck "still in a transaction". The session pooler works.</p>
<p>The problem. The Workers Free plan allows 10 ms of CPU per request. When we crawled our own sitemap the way Googlebot does (back-to-back requests), 9 to 15 of 29 pages answered 503 with Cloudflare error 1102 ("Worker exceeded resource limits"). The same page could alternate between 200 and 1102 in six consecutive fetches. Crawlers that keep getting 503s slow down, and pages that keep failing eventually drop out of the index. For a site that depends on search traffic, that's an existential problem.</p>
<p>Why trimming wouldn't work. Workers Observability over 24 hours (2,381 requests) showed a median of 28 ms CPU for successful requests and a p90 of 378 ms. The home page used 381 ms, and even robots.txt used 49 ms, because every request boots the Next.js server. No amount of optimisation would get a full Next.js render under 10 ms.</p>
<p>What we did: serve public pages as static assets. Cloudflare answers a request that matches a static asset before it runs the Worker, and an asset costs no Worker CPU. So our deploy script now uploads twice:</p>
<p>Deploy the new build. Wait for it to answer, then fetch every public sitemap page in all five languages, plus robots.txt, sitemap.xml, llms.txt and the icons, as an anonymous visitor. Write those responses into the Worker's static assets and deploy again. The parts of a page that depend on the visitor moved to the browser:</p>
<p>The header became a client component that shows the learner menu when the browser's own storage remembers a session. Every server-rendered page keeps that note in step, and signing out clears it. The home page's "continue studying" line comes from a small API call, made only when a session is remembered. Page-view counting moved to a lightweight /api/views beacon, because the server no longer sees those page loads. Language detection, which a proxy used to do, runs as an inline script that applies the locale cookie or browser language. Result: three full crawls of the sitemap plus 40 back-to-back fetches all answered 200, with a median of about 75 ms. Before, time to first byte had ranged from 0.2 to 4.6 seconds. Sign-in, practice, pricing and checkout still run on the Worker.</p>
<p>Gotcha: static assets are served for any hostname before the Worker's own redirects run. So the deploy script first checks that www and the old domain already redirect through Cloudflare Redirect Rules. If they don't, it refuses to save static pages, which would otherwise create duplicate content on the old domain.</p>
<ol start="4">
<li>A rate limiter anyone could bypass with one header The problem. Our sign-in and sign-up rate limiter identified clients by the last X-Forwarded-For entry. That's correct behind Vercel, which appends the real client address. On Cloudflare Workers nothing appends to that header: it reaches the app exactly as the client wrote it. A client could send a new made-up address with every request and never hit the limit. We confirmed this on the live site with a forged header.</li>
</ol>
<p>What we did. On Workers, the client address now comes from CF-Connecting-IP, which Cloudflare sets itself and won't accept from a client. Everywhere else, a client-sent CF-Connecting-IP is ignored. The runtime check lives in one module, so the database layer and the rate limiter agree on where the code is running.</p>
<p>Lesson: "which header holds the real client IP" depends on the platform. Re-check it every time you change hosts.</p>
<ol start="5">
<li>Five languages, and canonicals that would have hidden four of them The problem. UniExamer serves every public page in English, Italian, Turkish, Spanish and Polish (/exams/gmat, /it/exams/gmat, …). Each page set its own metadata with an English canonical, and in Next.js a page's alternates replaces the layout's. So /it/exams/gmat told search engines that /exams/gmat was the real page, and no page carried hreflang at all. The Italian, Turkish, Spanish and Polish pages would have been folded into the English ones and left out of the index.</li>
</ol>
<p>What we did.</p>
<p>Every page now builds its metadata with a single helper, pageAlternates(params, path). It returns the page's own language URL as canonical, all five language versions as hreflang alternates, and English as x-default. og:url follows the canonical. The sitemap lists every page once per language (155 entries), each entry naming all its language versions. This is how Google supports hreflang in sitemaps. A unit test fails if any page sets a canonical without going through the helper. Lesson: for SEO bugs, write a test, not just a fix. A metadata regression is invisible in the browser and can stay hidden for months.</p>
<ol start="6">
<li>When a dollar sign broke maths in 46 published fields The problem. Questions are written in Markdown with inline TeX, rendered by KaTeX. Prices inside formulas were written as $$80$. Our inline-maths scanner ended the formula at the escaped dollar, so KaTeX received a lone backslash and showed a parse error. Worse, everything up to the next dollar sign was typeset as maths, turning ordinary sentences into italics across 46 fields of published SAT, ACT, GMAT and GRE questions.</li>
</ol>
<p>What we did. An escaped dollar inside a formula now belongs to the TeX. An escaped dollar outside a formula is still a literal dollar sign. The function that decides whether a page contains maths uses the same pattern, so the KaTeX font preload stays consistent with what's actually rendered. Validators now also check currency in maths. We had also seen KaTeX fonts loading late and shifting layouts, so pages that contain maths preload exactly the fonts they need.</p>
<ol start="7">
<li>Making AI-assisted questions trustworthy: blind review and bias scans Our questions are original and drafted with AI assistance, so the quality question isn't whether we can write them but how we know they're right.</li>
</ol>
<p>The pipeline.</p>
<p>Draft into an in_review state, following an authoring brief. Run automated scans for rendering damage, broken maths, accessibility text and answer cues. Normalise option order, so the position of the correct answer carries no signal. Export a blind batch, with the key, explanation and distractor notes removed. Two independent solvers answer each question from the blind file only. Compare their answers with the key mechanically, through the same scoring engine the app uses. Publish only if both agree with the key and neither finds a second defensible answer. Any disagreement quarantines the question; it's never silently corrected. A bias we found in our own bank. A scan showed the correct answer was the longest choice far more often than chance: 65% of LSAT items, 58% of GMAT, 55% of TOLC-E. That's a well-known test-taking cue. In the 37 worst items (key at least 30% longer than any other choice), we rewrote the distractors to match the key's length and specificity without changing any key. Then every one went back through blind review. Afterwards the rates fell to about 50% for LSAT and GMAT and 34% for TOLC-E, and the length-cue scan now runs on every new wave.</p>
<p>Accessibility regressions from shuffling. Normalising option order once left screen-reader descriptions listing choices in their pre-shuffle order. The fix bumped each affected question's version, and a validator rule (accessibility-claims-order) now blocks the fault from coming back.</p>
<p>Translation is a separate review. Italian versions of the TOLC questions are published only after a translation check of a blinded export, and each attempt records which language the student saw.</p>
<p>Bonus: framework upgrades on Windows Upgrading to Next.js 16 broke the Cloudflare build three times:</p>
<p>Turbopack's file tracing kept only pg-cloudflare's Node stub, so OpenNext couldn't bundle the Workers build of pg. Adding pg-cloudflare to serverExternalPackages made OpenNext copy the whole package. On Windows, Turbopack reached better-sqlite3 through a symlink that OpenNext recreates, and Windows refuses that outside Developer Mode (EPERM). The Cloudflare build now uses webpack; local dev and next build keep Turbopack. Every rendered page then returned 500, because Next.js 16.4 reads its preview keys from server/preview-props.json, which our OpenNext version didn't inline into the Worker. Upgrading OpenNext to 1.20.12 fixed it. Search and AI visibility (SEO and GEO) checklist we ended up with For anyone building a content-led product today, here's what <a href="http://uniexamer.com">uniexamer.com</a> does for both search engines and AI assistants:</p>
<p>Self-referencing canonicals and hreflang in five languages, plus a per-language sitemap. Organization, WebSite, BreadcrumbList and Article structured data, describing only what the page visibly says, with alternateName for the old brand name. robots.txt that allows search, user-triggered and training AI crawlers on public pages and keeps everyone out of private areas. An llms.txt map of public pages, generated from the same data as the sitemap. IndexNow submissions after each deploy, so Bing (and therefore Copilot) picks up changes quickly. A 1200 × 630 share image for every exam and guide, with a test that fails if one is missing. Pages written for answer engines: each exam page opens with a direct summary, a facts table and the date the facts were checked against the test maker. Indexing controlled by an environment flag, so preview deployments are never indexed. FAQ What tech stack does UniExamer use? Next.js 16 and React 19 with TypeScript, Tailwind CSS, Supabase PostgreSQL (SQLite for local development), Cloudflare Workers via OpenNext with Hyperdrive, next-intl for five languages, KaTeX for maths, Stripe for billing, and Vitest and Playwright (with axe accessibility checks) for testing.</p>
<p>How did UniExamer get around the Cloudflare Workers Free CPU limit? By saving every public page as a static asset at deploy time. Cloudflare serves matching assets before running the Worker, so public pages cost no Worker CPU and stopped returning error 1102.</p>
<p>How are UniExamer's practice questions checked? Each question is solved blind by two independent solvers, without the answer key, and published only if both agree with the key. Disagreements are quarantined, and automated scans check for answer-length cues, rendering faults and accessibility errors.</p>
<p>Where can I see the result? At <a href="http://uniexamer.com">uniexamer.com</a>. Start with the exam list, the Bocconi Online Test hub or the CISIA TOLC hub.</p>
]]></content:encoded></item><item><title><![CDATA[How We Built UniExamer: Admission Test Practice That Only Says What It Can Source]]></title><description><![CDATA[(uniexamer.com): why we built an admission test practice platform verified against official sources, and what we refused to fake.]]></description><link>https://uniexamer.hashnode.dev/how-we-built-uniexamer</link><guid isPermaLink="true">https://uniexamer.hashnode.dev/how-we-built-uniexamer</guid><category><![CDATA[edtech]]></category><category><![CDATA[startup]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[education]]></category><category><![CDATA[Build In Public]]></category><dc:creator><![CDATA[uniexamer]]></dc:creator><pubDate>Wed, 28 Oct 2026 11:24:03 GMT</pubDate><content:encoded><![CDATA[<p><strong>UniExamer (</strong><a href="http://uniexamer.com"><strong><u>uniexamer.com</u></strong></a><strong>) is an independent practice platform for university admission tests.</strong> It covers the Bocconi Online Test (bachelor, law and master's forms), CISIA TOLC-E, TOLC-F and TOLC-I, the Politecnico di Torino TIL-I and TIL-A, the Digital SAT, the Enhanced ACT, the LSAT, the GMAT and the GRE. It has more than 1,000 original practice questions, and two independent solvers checked each one before it was published. Guides, sample questions and a diagnostic test are free for every exam. The site runs in English, Italian, Turkish, Spanish and Polish.</p>
<p>We wrote the first line of code on 22 September 2026, and the site went live at <a href="http://uniexamer.com">uniexamer.com</a> on 27 October 2026. This is the story of those five weeks: what we set out to build, the rules we gave ourselves, and why one of those rules was to never make up a number.</p>
<h2><strong>The problem: test prep that sounds certain and isn't</strong></h2>
<p>We started with international students applying to Bocconi in Milan. Many of them also take the SAT, a TOLC or a graduate test like the GMAT. When you look for practice material for these exams, you keep finding the same three problems:</p>
<ol>
<li><strong>Made-up scores.</strong> Plenty of sites will turn your practice result into an "estimated SAT score" or a "GMAT 685". No test maker publishes its raw-to-scaled conversion, and the digital SAT isn't even scored by counting correct answers. So those numbers are guesses dressed up as measurements.</li>
<li><strong>Outdated or blended formats.</strong> Exam formats change: the shorter GRE, the GMAT Focus Edition, the Enhanced ACT, the digital SAT. Old practice material often mixes the old and new rules without saying which is which.</li>
<li><strong>Unclear rules for Italian admission tests.</strong> For the Bocconi test and the CISIA TOLC, the most important facts (section timing, negative marking, which areas a penalty applies to) are scattered across PDFs and official pages. Forum posts repeat them, and get them wrong.</li>
</ol>
<p>So the brief we wrote for ourselves had one organising principle:</p>
<blockquote>
<p><strong>The product does not say anything it cannot source.</strong></p>
</blockquote>
<h2><strong>The rules we built in from day one</strong></h2>
<p>Most of our product decisions follow from that principle. These rules are written into the code and the content pipeline, not just a style guide:</p>
<ul>
<li><strong>Every exam fact has a source and a date.</strong> Section counts, timing, navigation rules, calculator policy and scoring are checked against the test maker's own pages. Each fact records the URL and the date we checked it. Where a test maker doesn't publish a rule, UniExamer says so and turns off any feature that would depend on it.</li>
<li><strong>No invented scaled scores and no percentiles.</strong> Results report accuracy, timing and performance by skill: things we can actually measure. Our page on <a href="https://uniexamer.com/about/how-scoring-works"><u>how scoring works</u></a> explains why.</li>
<li><strong>No copied questions.</strong> Every question is original. Questions are drafted with AI assistance and then go through a review pipeline (below), and the <a href="https://uniexamer.com/about/editorial-standards"><u>editorial standards</u></a> say so openly.</li>
<li><strong>No "competitive score" presented as a requirement.</strong> If a university publishes an actual minimum, we label it as a minimum. If a number is only an observed average, we label it as an observation.</li>
<li><strong>No simulation we can't deliver.</strong> A full-length timed simulation appears only when the exam's rules are verified <em>and</em> the reviewed question bank can fill the whole blueprint without repeating a question. Otherwise the page shows the reason instead of a broken mock exam.</li>
<li><strong>Independence, stated plainly.</strong> UniExamer isn't affiliated with College Board, ACT, LSAC, GMAC, ETS, Università Bocconi, Politecnico di Torino or CISIA, and every page says so.</li>
</ul>
<h2><strong>Week 1: six exams, one engine</strong></h2>
<p>The first version supported six exams: the Bocconi Online Test (undergraduate and law), Digital SAT, Enhanced ACT, LSAT, GMAT and GRE. The key architectural decision was that <strong>exams are data, not code</strong>. Each exam is a versioned configuration file describing its sections, timing, navigation, scoring and skill taxonomy. A single assessment engine reads those files, and no exam is hardcoded anywhere.</p>
<p>That decision paid off over and over. When we later added the CISIA TOLC tests and the Politecnico di Torino TIL, they were new configuration files, not new features. When Bocconi's published penalty for three-option critical-thinking questions turned out to be -0.33 rather than the default -0.2, we fixed it in configuration data.</p>
<p>The first commit contained 96 original questions. Each one had been solved by a second reviewer working from a "blind" copy, with the answer key, explanation and distractor notes removed. If a solver disagreed with the key, the question was quarantined, not quietly corrected.</p>
<h2><strong>Weeks 1–2: making practice feel safe</strong></h2>
<p>A practice platform is only useful if students trust it with a timed session. Over the next few days we built the parts students actually touch:</p>
<ul>
<li><strong>A focused practice player</strong> that hides the site navigation during a session and keeps the timer and controls fixed in place.</li>
<li><strong>Answer saving that survives a dropped connection.</strong> Every answer is saved on the device first and kept until the server confirms it. A reload, a train tunnel or a closed tab doesn't lose work.</li>
<li><strong>A mistake notebook, retries and "unseen questions only" practice</strong>, so a learner can revisit what they got wrong without being served the same items again.</li>
<li><strong>Study plans and readiness checks</strong> that only give advice when there is enough evidence to justify it.</li>
<li><strong>Accessibility as a release requirement</strong>: keyboard navigation, screen-reader descriptions that read answer choices in their on-screen order, zoom and reflow checks, and automated axe tests.</li>
</ul>
<h2><strong>Weeks 2–3: Italian admission tests and a much bigger question bank</strong></h2>
<p>Our first users were applying to Italian universities, so the next exams were obvious:</p>
<ul>
<li><a href="https://uniexamer.com/exams/cisia-tolc"><strong><u>CISIA TOLC</u></strong></a>: TOLC-E (economics), TOLC-F (pharmacy) and later TOLC-I (engineering).</li>
<li><a href="https://uniexamer.com/exams/politecnico-di-torino-til"><strong><u>Politecnico di Torino TIL</u></strong></a>: TIL-I and TIL-A.</li>
<li><a href="https://uniexamer.com/exams/bocconi-online-test"><strong><u>Bocconi Online Test</u></strong></a>, now split into a Bachelor &amp; Law hub and a separate <a href="https://uniexamer.com/exams/bocconi-master"><u>Bocconi Master</u></a> hub for the graduate test.</li>
</ul>
<p>We grew the question bank in "waves". In one wave in October we published 238 new questions across TOLC-I, TOLC-F, the Digital SAT and both Bocconi tests. All 238 had two agreeing blind solves. By the end of October the bank held <strong>more than 1,000 published questions</strong>.</p>
<p>More content also meant finding our own mistakes. An audit showed that in some exams the correct answer was suspiciously often the longest choice, a classic test-taking cue. We rewrote 37 questions so the cue went away. A Bocconi audit found a gap between what our scoring page said and what the engine did, and we fixed the engine. Each fix bumps the question's version number, so a student's past attempt keeps the exact text they saw. (Our companion engineering article, <em>Engineering UniExamer</em>, covers how.)</p>
<h2><strong>Weeks 3–5: going live, then going international</strong></h2>
<p>The platform first ran on Vercel, then moved to <strong>Cloudflare Workers</strong> with a <strong>Supabase PostgreSQL</strong> database. On 7 October we opened the public pages to search engines, and we built paid plans on Stripe next to the free tier.</p>
<p>On 27 October the project got its permanent name and address: <strong>UniExamer, at</strong> <a href="http://uniexamer.com"><strong>uniexamer.com</strong></a>. The working name had been "Examer", so the site tells search engines that both names refer to the same organisation. The same day:</p>
<ul>
<li>Every public page became available in <strong>English, Italian, Turkish, Spanish and Polish</strong>, each language with its own address.</li>
<li>Italian versions of the TOLC questions went live after a separate translation check, because CISIA runs the TOLC in Italian.</li>
<li>We opened the public pages to AI search crawlers and AI assistants as well as traditional search engines, because students now ask ChatGPT, Perplexity, Gemini and Copilot about these exams as often as they Google them.</li>
</ul>
<h2><strong>What we learned</strong></h2>
<p><strong>Honesty is a feature you have to engineer.</strong> "Don't invent scores" sounds like a copywriting rule. In practice it meant scoring code with item-level penalty rules, versioned questions, configurations that list what couldn't be verified, and validators and tests that fail when the content or a page's metadata drifts from the source data.</p>
<p><strong>Reviewing AI-assisted content takes more work than drafting it.</strong> Drafting a question takes minutes. Blind solving, cue scans, accessibility checks and translation checks are where quality comes from, and they are most of the work.</p>
<p><strong>Small constraints shape architecture.</strong> Free hosting tiers, slow first page loads and search crawlers that fetch many pages back to back pushed us toward serving public pages as static files. That made the site faster for everyone.</p>
<h2><strong>Try it</strong></h2>
<ul>
<li>Browse all exams: <a href="https://uniexamer.com/exams"><u>uniexamer.com/exams</u></a></li>
<li>Bocconi Online Test format and negative marking: <a href="https://uniexamer.com/exams/bocconi-online-test"><u>Bocconi hub</u></a> · <a href="https://uniexamer.com/guides/bocconi-test-negative-marking-when-to-guess"><u>When to guess on the Bocconi test</u></a></li>
<li>CISIA TOLC (TOLC-E, TOLC-F, TOLC-I): <a href="https://uniexamer.com/exams/cisia-tolc"><u>TOLC hub</u></a></li>
<li>Why a practice test can't give you a real SAT score: <a href="https://uniexamer.com/guides/why-practice-tests-cannot-give-you-a-real-sat-score"><u>the guide</u></a></li>
</ul>
<p>Guides, sample questions and a diagnostic test are free for every exam. At the time of writing (October 2026), payments aren't switched on yet, so every exam and practice format is open to everyone.</p>
<h2><strong>FAQ</strong></h2>
<p><strong>What is UniExamer?</strong> UniExamer (<a href="http://uniexamer.com">uniexamer.com</a>, previously called Examer) is an independent online practice platform for university admission tests. It offers original practice questions, timed practice, exam format guides and progress tracking for the Bocconi Online Test, CISIA TOLC, Politecnico di Torino TIL, Digital SAT, Enhanced ACT, LSAT, GMAT and GRE.</p>
<p><strong>Is UniExamer free?</strong> Exam guides, sample questions and a diagnostic test for every exam are free. Paid plans (Single exam and All access) add topic practice, timed sections, full simulations and the study plan; see <a href="https://uniexamer.com/pricing"><u>pricing</u></a>. At the time of writing (October 2026), payments aren't switched on yet, so everything is open to everyone.</p>
<p><strong>Are UniExamer's questions official?</strong> No. All questions are original, written with AI assistance and published only after two independent blind solves agree with the answer key. UniExamer isn't affiliated with any test maker.</p>
<p><strong>Does UniExamer predict my SAT, GMAT or GRE score?</strong> No, on purpose. Test makers don't publish their raw-to-scaled conversions, so any predicted score would be invented. UniExamer reports accuracy, timing and performance by skill instead.</p>
<p><strong>Which languages does UniExamer support?</strong> English, Italian, Turkish, Spanish and Polish. Some TOLC questions are also available in Italian, after a translation check.</p>
<p><strong>Does UniExamer cover the Bocconi negative marking rules?</strong> Yes. The scoring applies Bocconi's published penalties, including the heavier -0.33 penalty on three-option critical-thinking questions in the bachelor test. Where Bocconi leaves a rule unclear, the page says so.</p>
<p>\</p>
]]></content:encoded></item><item><title><![CDATA[Building a High-Fidelity Exam Simulator: Handling Negative Marking and Time Gates in Next.js]]></title><description><![CDATA[Building a quiz app is the quintessential "Hello World" of full-stack tutorials. You render a question, map over four string options, and increment a score…]]></description><link>https://uniexamer.hashnode.dev/building-a-high-fidelity-exam-simulator-handling-negative-marking-and-time-gates-in-next-js</link><guid isPermaLink="true">https://uniexamer.hashnode.dev/building-a-high-fidelity-exam-simulator-handling-negative-marking-and-time-gates-in-next-js</guid><dc:creator><![CDATA[uniexamer]]></dc:creator><pubDate>Wed, 28 Oct 2026 11:04:02 GMT</pubDate><content:encoded><![CDATA[<p>Building a quiz app is the quintessential "Hello World" of full-stack tutorials. You render a question, map over four string options, and increment a score counter.</p>
<p>But when you try to build a psychometric exam simulator for competitive European university admissions—where a single test determines if a student gets into a top-tier engineering or economics program—the standard quiz architecture completely falls apart.</p>
<p>When we set out to build <a href="https://uniexamer.com/"><strong>UniExamer</strong></a>, an adaptive mock testing engine for exams like the Bocconi Test and CISIA TOLC, we realized that serving random questions wasn't enough. We had to enforce the harsh realities of standardized testing: strict section-by-section time lockouts, complex negative marking algorithms, and dynamic question generation that doesn't hallucinate.</p>
<p>Here is how we architected the core testing engine.</p>
<h3>1. The Penalty Scoring Algorithm</h3>
<p>Unlike standard US exams (like the SAT), European exams heavily penalize guessing. The CISIA TOLC-I deducts \(-0.25\) for wrong answers, while medical exams like the IMAT deduct \(-0.40\).</p>
<p>If you use standard React state to track a simple <code>score++</code>, you ruin the student's pacing feedback loop. We had to build a scoring utility that calculates Expected Value (EV) and tracks omissions separately from failures.</p>
<p>TypeScript</p>
<pre><code class="language-plaintext">// Simplified Scoring Utility
export function calculateSectionScore(
  answers: UserAnswer[], 
  penalty: number = 0.25, 
  reward: number = 1.0
) {
  return answers.reduce((total, answer) =&gt; {
    if (answer.status === 'OMITTED') return total; 
    if (answer.status === 'CORRECT') return total + reward;
    return total - penalty; // Deduct for incorrect
  }, 0);
}
</code></pre>
<p>By isolating omitted answers, our analytics dashboard can tell a student: <em>"You lost 3.5 points to negative marking. You need to increase your skip rate."</em></p>
<h3>2. Enforcing Section Time Gates</h3>
<p>Standard quiz apps use a single global timer. But exams like the TOLC enforce strict time gates (e.g., exactly 50 minutes for Mathematics, then you are locked out, even if you have 10 minutes left in Logic).</p>
<p>To handle this safely in a modern web framework like <a href="https://nextjs.org/">Next.js</a>, you cannot rely on client-side <code>setTimeout</code> or <code>setInterval</code>, because users can pause execution by switching tabs.</p>
<p>Instead, we capture a server-side timestamp when a section begins. Every time the user submits a batch of answers or navigates between questions, the client checks the delta against the server time. If <code>currentTime - sectionStartTime &gt; sectionDuration</code>, the UI aggressively unmounts the current section and forces the transition to the next, preventing any late submissions from reaching the database.</p>
<h3>3. Taming LLMs with Strict JSON Schemas</h3>
<p>The hardest part of an AI exam simulator is preventing the LLM from generating generic trivia. Admissions tests use highly specific formats, like "Data Sufficiency," where options A through E are permanently fixed logical conditions.</p>
<p>To ensure 100% output reliability, we abandoned raw prompt generation and moved to strict <a href="https://json-schema.org/">JSON Schema</a> constraints. By forcing the LLM to output a parsed object containing <code>distractor_analysis</code> (explaining exactly <em>why</em> a wrong answer was generated based on a common mathematical error), the quality of the generated mocks skyrocketed.</p>
<p>Building an educational platform isn't just about serving content; it's about simulating the psychological pressure of test day. When the architecture respects the rules of the test, the students actually learn how to pass</p>
]]></content:encoded></item></channel></rss>