PaperCheckThe storyPDFCheck a paper
Engineering notes

PaperCheck: how a one-person project became a three-cloud system

A tool that checks research papers and student reports for AI-sounding writing and for plagiarism. Built by one student in about a week, run on free tiers and student credit, and used by hundreds of students. This is the full architecture, how the pieces fit, what broke, and why the design looks the way it does.

500+users in the first week
650+checks in the first week
1.7Mwords checked on the busiest day
1 to 3 minfor a typical check now
3 cloudsCloudflare, Azure, GitHub

1The system at a glance

The site and the money logic run on Cloudflare's free tier. The heavy AI work runs on a separate Python service on Azure that sleeps when nobody is using it. Everything that must survive that sleep (accounts, usage, finished reports) lives in the Cloudflare database, never on the engine.

Student browser, phone AI assistant Claude, ChatGPT, Cursor through an MCP server CLOUDFLARE (free tier) Static site check page, report view, guides Worker API (the gateway) verifies the login, free and paid word limits payments, cancel and refund rules serves reports, talks to the engine with a secret key hosts the MCP endpoint for AI assistants D1 database accounts, usage, jobs payments, saved reports (7 days, compressed) Cron, every minute keeps the engine awake saves finished reports sends result emails AZURE CONTAINER APPS Engine API (FastAPI) job queue, progress, cancel one server, sleeps when idle AI detector fine-tuned RoBERTa-base on CPU scores every sentence in context two checks run at the same time Plagiarism check cited arXiv papers, longest copied runs Job state (local disk) wiped when idle; reports copied out open API MCP HTTPS + key wake + poll ClerkGoogle login Cashfreepayments Resend + Brevoresult emails arXivpapers to compare against
Figure 1. Three layers: the Cloudflare gateway owns users and money, the Azure engine does the computing, and small managed services handle login, payments, email and source papers. The engine never sees a user account.

2What happens when someone checks a paper

The gateway is built so that nobody is charged until the engine has accepted the job, and every failure path ends in a refund. The page can be closed at any point and the check keeps running.

Browser Worker Database Engine Email 1 upload (Word text is read in the browser) 2 count the words, nothing is charged yet 3 charge a free check or paid words 4 start the job, get a job id 5 poll every 4 s (progress, queue position) 6 finished: the report comes back 7 save a compressed copy (7 days) 8 email the link to the report 9 reopen later from "Your reports" The copy in the database answers even when the engine is asleep
Figure 2. The nine steps. Steps 7 and 9 exist because the engine forgets everything when it sleeps, so finished reports live in the database.

3How the detector turns text into a number

The score is a measurement of sentences, not a verdict. Each sentence is scored together with the two before and the two after it, because AI-like writing shows up in patterns across sentences, not in a single one.

Read the fileWord, PDF, text,markdown Keep the proseabstract to references,no tables or lists Clean the textquotes, dashes andodd characters Split sentences"et al." and "Fig."do not end a sentence Add context2 sentences beforeand 2 after Fine-tuned modelRoBERTa-base on CPU,one score per sentence Flag by thresholda sentence is flagged when itsscore passes a strict cut-offchosen on held-out human text Headline numbershare of all words that sit inflagged sentences, shown as aband: Low, Some or High 250-word chunk viewa second view like institutionaltools use: a chunk counts when28% of its words are flagged Highlights and tipsevery flagged sentence is markedwith the reason and a rewritesuggestion to apply yourself Plagiarism branch (runs beside the AI check)Find the papers the document cites on arXiv, download their text, then count the longest verbatim runs per sentence(6 to 12 words by strictness). A match with a paper that is not cited is flagged first.
Figure 3. From file to number. The threshold is picked on held-out human writing so that honest text is rarely flagged; the score is shown as an estimate with a band, never as proof.

4The part nobody sees: money and failure handling

Because the engine can crash, time out or go to sleep, every paid or free check has a clear end state, and each failure state returns the user's words exactly once. A claim row in the database makes the refund safe even if two requests race.

Queued Running Done Error Cancelled Lost free worker finished crash or timeout user cancels server slept Rules Done never refunds, even if the report is opened again days later. Error, Cancelled and Lost give back the free check or the paid words, once. "Once" is enforced by a claim row, so a double click or a retry cannot refund twice. Nothing is charged until the engine has accepted the job. A cancel stops the work on the server, then refunds, never the other way round.
Figure 4. The check lifecycle and its refund rules. These paths were tested with a fake database and a fake engine before launch.

5How changes reach production

Developerlaptop, tests first Git pusha branch per image GitHub Actionsdownload model weights (private)verify the checksumbuild the container image Container registryprivate images Container Appsscale 0 to 1 wrangler deployabout 10 seconds Cloudflare Worker and sitegateway, pages, cron, database Why GitHub builds the image: the cloudprovider's own builder is not available onstudent plans, and a 2.6 GB upload over aslow connection would take hours.
Figure 5. Two independent delivery paths. The gateway ships in seconds; the engine ships as a verified container image.

6What broke, and the numbers behind each fix

Time for one check, in minutes beforetypical 6.8 slowest tenth 34.7 and up to 58 after1 to 3 (4,000 to 9,000 words) Cloud cost per day at peak, in rupees before6,600 afterabout 130 (estimate, same traffic) Bars are drawn to scale within each chart.
Figure 6. Measured on the same kind of papers and traffic. The cost "after" is an estimate from the measured cost per check.
ProblemWhat was really happeningFixEffect
Checks took 10 to 60 minutesInside the container the AI library used the host machine's core count as its thread count, not the container's 4 vCPUs, so threads fought each otherSet the thread count explicitlyabout 10x faster
Four models per checkOnly one model set the headline number; the other three added nothing the user sawRun only that modelabout 86% less compute
Queue blocked for an hourA stuck check held its worker slot until a 1-hour timeout; 21 did in a dayReal timeout, automatic refundslots freed in minutes
Cost jumped from about 60 to 6,600 rupees a dayDuplicate server versions all scaling up, and a job scanner re-reading about 900 old folders on billed file storage every 30 sOne version, remember finished jobsfile operations down 90%
Database nearly out of free writesOne query rewrote a device row on every poll: 94% of the 100,000 daily writesWrite once, refresh every 6 hours88% to a few percent
Hosting account switched itself offThe student account has a hard spending capMoved the engine to another account; reports saved in the databaseidentical scores on 341 of 341 sentences
Result emails stopped for hoursA function was called but never defined, so the email loop crashed every minuteFixed, plus a test that now sends one email per finished checkevery check gets an email

7Design decisions and their trade-offs

DecisionWhyCost of that choice
Gateway on Cloudflare Workers and D1Free, fast everywhere, no servers to run, built-in cronStrict free-tier limits: 100,000 database writes a day, 500 MB, small per-run limits
Separate Python engineThe AI model needs Python and a heavy library; the gateway should stay tinyTwo systems to keep in sync, and a network hop
One fine-tuned model, not an ensembleFaster, cheaper, and the extra models did not change what users seeLess diversity of opinion; honesty depends on good threshold tuning
Scale to zero, one server onlyYou pay only while checks run; cold start is about 4 secondsNo spare capacity, and state is local to the one server
Reports saved in the gateway databaseThe engine's disk is wiped when it sleepsReports are stored for 7 days, and storage is compressed to stay in the free tier
Refund claims instead of "if failed then refund"Retries and double clicks must never pay twiceOne extra table row per refund
Scores shown as bands and estimatesDetectors disagree and none is proofLess dramatic than a single big number, but honest

8How it was tested

The model

The cut-off was chosen on held-out human writing from several domains (academic papers, encyclopedia text, non-native English writing, learner essays) so honest text is rarely flagged. The target was at most 2 to 3 percent of human documents flagged per domain, and the live setting measured between 0 and 2.1 percent. A held-out set of real student-style AI text checked that detection did not collapse.

The system

34 automated tests run against a fake database and a fake engine, including the dangerous cases: a finished report after the engine sleeps, a lost check refunded once, an old report never refunded, retries that never send two emails. A migration was proven by re-scoring a real production paper on the new host and matching all 341 sentences exactly, and by running four real papers at once.

9Stack

Cloudflare WorkersD1 (SQLite)Clerk loginCashfree paymentsResend + Brevo email Python 3.12FastAPIPyTorch (CPU)TransformersRoBERTa-base, fine-tunedMCP server Azure Container AppsGitHub ActionsarXiv

10What I would do next

Keep the model loaded between checks instead of reloading it each time (saves 10 to 15 seconds). Try a faster number format (int8) after a proper accuracy check. Move the engine to a permanent free host so a spending cap can never switch it off. Put cost and quota alarms in on day one of the next project.

Honest limits. Scores are estimates, not proof, and different checkers give different numbers. Plagiarism is checked against arXiv only, not the whole web. This is a one-person project and the code and the trained model are private.