All design decisions

Technology stack

The pupil’s browser records audio. Cloudflare hosts the app and stores its data, while RunPod processes speech on a GPU. Each layer below explains the tools we use and what the choice costs us.

From a pupil’s voice to teacher review

Phil-IRI · Grades 4–6English and FilipinoSteps 1–7 follow one recording

  1. A pupil records a Phil-IRI passage in the READSmart web app on a phone or tablet.
  2. The browser uploads the audio straight to private Cloudflare R2 through a short-lived signed link from the Worker API.
  3. The Worker, which runs the Next.js app through OpenNext, saves the recording in D1 with Drizzle and queues a processing job.
  4. The Queue wakes RunPod Serverless. The GPU worker leases the job through the Worker API, where D1 holds the lease, and receives a signed link to the audio with the reference passage.
  5. On RunPod, Silero VAD finds speech, OpenAI Whisper transcribes it, WhisperX aligns the words to the audio, JiWER compares the transcript with the passage, and exported coefficients calibrate WCPM, with grade and language as required features.
  6. The GPU posts its results to protected Worker API endpoints and the Worker writes them to D1. The GPU never opens D1 directly.
  7. The Worker reads the result from D1 for the teacher dashboard, where a teacher reviews it.

The web app

Pupil’s phone or tablet · teacher’s browser

Step 1. Record a passage

MediaRecorder captures the microphone. IndexedDB keeps pending classroom submissions so they can retry.

Audio goes straight to storage through a signed upload link

Hosting, data & storage

Cloudflare · the Worker controls access

Step 2. Upload to R2

The Worker signs a short-lived upload link with aws4fetch; the browser sends the audio straight to private R2.

Step 3. Save the recording; queue the job

The Worker runs the Next.js app through OpenNext. It writes the recording to D1 with Drizzle and queues a job.

The Queue asks for GPU work

Getting a recording processed

Cloudflare to RunPod

Step 4. Wake RunPod; lease the job

A Queue delivery wakes RunPod Serverless. The GPU worker leases the job through the Worker API; D1 holds the lease.

Cron Triggers · every minute

Maintains jobs and retention, retries eligible work and wakes RunPod while jobs remain.

A signed audio link and the reference passage

Speech processing & measurement

RunPod Serverless GPU: Python, PyTorch and CUDA in a Docker image

Step 5. Transcribe, align, score and calibrate

Whisper’s model size is a configuration value, chosen by experiment.

  1. Silero VAD

    Find speech

  2. OpenAI Whisper

    Transcribe

  3. WhisperX

    Align words to the audio

  4. JiWER

    Compare with the passage: WER and raw WCPM

  5. WCPM calibration

    Exported coefficients; grade and language are required features

Local research · RTX 4060, 8 GB

Compares Whisper model sizes, fits the WCPM calibration with scikit-learn and checks agreement with ICC and Bland–Altman. Its exported coefficients feed the calibration stage.

Results return through protected Worker API endpoints

Step 6. Write the results

The Worker writes the results to D1. The GPU never opens D1 directly.

The Worker reads D1

Step 7. A teacher reviews the result

The dashboard shows the measurements beside the recording. Word highlighting is an unvalidated assistive comparison; the teacher decides.

Alongside assessment

Practice stories and releases run separately from the recording path.

Original practice & illustrations

Worker API to Workers AI, through AI Gateway except the picture call

Stories and comprehension questions

Queues

Queues the pupil's request; the story is made in the background while a progress bar shows each step.

Background job
Mistral Small 3.1 24B

Writes the passage, rewriting until it fits the grade's word and readability ranges.

Labels, fact review, questions
Llama 3.3 70B

Labels the passage, flags unchecked facts for the teacher and writes the questions.

Content moderation
Llama Guard 3 8B

Passage covers

FLUX.2 [klein] 9B

Draws the scene in the style of two sample covers, a new pair each time. Sent straight to Workers AI: the gateway cannot take its multipart input.

Cover review
Llama 4 Scout

Checks safety, fit and style; a rejected cover is drawn again, up to three tries.

R2Private, version-linked covers

Teacher approvalApproved readings and covers become available to pupils.

Building & checking the application

Tests, builds and release

TypeScript, ESLint, Vitest and Playwright check each change. Workers Builds builds the main branch from GitHub and deploys it.

GitHubWorkers BuildsWorkers

READSmart system diagram. Steps 1–7 follow one recording from the pupil’s device to teacher review. The two lower lanes run alongside it: practice stories, which a teacher approves before pupils see them, and the release pipeline. Text monograms mark tools without a sourced logo.

The web app

Browser + Cloudflare Workers

One responsive web app serves pupils, teachers, and administrators. The browser handles recording and interaction; server code checks access and coordinates the work.

Next.js 16, React 19 & TypeScript
Next.js App Router provides pages, server rendering, and API route handlers. React builds the interface, while TypeScript checks the shapes of data shared across the web application.
Tailwind CSS 4 & custom CSS
Tailwind supplies layout and styling utilities through PostCSS. Shared CSS defines READSmart’s colors, typography, reading layouts, and responsive components.
PWA & browser APIs
A web app manifest and service worker support the PWA. MediaRecorder captures microphone audio; IndexedDB holds pending classroom submissions for retry when a connection returns. Speech recognition still needs the server.
Auth.js & classroom accounts
Auth.js handles credential-based sign-in. Application checks scope access to the person, role, school, and classroom. Teachers create student credentials, so pupils do not need personal email accounts.
Research pages
react-markdown and remark-gfm render the published research findings, including tables and links.
Trade-off
A shared web app reduces separate device-specific builds, but microphone support, browser storage, and connectivity still vary across devices.

Hosting, data & storage

Cloudflare

The application, structured records, and stored audio have distinct responsibilities. This keeps large recordings out of the database and access decisions in the API.

Workers & OpenNext
OpenNext adapts the Next.js application to Cloudflare Workers. Workers serve the application and its API; static assets deliver the built browser files.
D1 & Drizzle ORM
Cloudflare D1 stores SQLite tables for accounts, classrooms, passages, jobs, and results. Drizzle gives the TypeScript application typed queries and a shared schema; Drizzle Kit generates SQL migrations.
R2 & aws4fetch
Private R2 buckets hold classroom audio, temporary demo audio, and generated practice artwork. aws4fetch signs upload URLs so a browser can send audio directly to R2. The Worker checks the recording and stores its metadata.
Email Sending
The Cloudflare EMAIL binding sends staff verification, password-reset, and onboarding messages from the application.
Trade-off
This arrangement uses one hosting platform for the web services, but it depends on Cloudflare bindings, service limits, and deployment tooling. Audio and database retention must be coordinated.

Getting a recording processed

Cloudflare → RunPod → Worker API

Uploading audio and analysing it are separate steps. A durable job record lets the system track processing, recover interrupted work, and return results to the right recording.

Queues & D1 job leases
A Cloudflare Queue delivery wakes the RunPod endpoint. The GPU worker claims an eligible job through the Worker API; D1 holds the authoritative job state and processing lease.
Cron Triggers
A scheduled Worker runs every minute to maintain jobs and retention, retry eligible work, and wake RunPod while queued work remains.
Protected result endpoints
The GPU pipeline downloads the authorised audio and posts its measurements back through the Worker API. It never connects to D1 directly. The dashboard reads the saved result for teacher review.
Trade-off
Results are asynchronous. Queue time, GPU startup, and processing add delay; retries and leases are needed when either service is interrupted.

Speech processing & measurement

RunPod Serverless GPU + local research environment

GPU compute handles the work that would be too demanding for a pupil’s phone. The RunPod handler reuses the Python processing pipeline, which can also run as a local polling worker.

Python, Docker, PyTorch & CUDA
A Docker image packages the pipeline and its audio tools for RunPod Serverless. PyTorch uses CUDA on the GPU. FFmpeg converts audio; SoundFile supports audio input, output, and duration measurement.
Silero VAD, OpenAI Whisper & WhisperX
Silero VAD detects speech regions. OpenAI Whisper transcribes the recording, and WhisperX aligns words with audio timestamps. Whisper model size is configurable and evaluated through the model-size experiment.
JiWER & recording-level scoring
JiWER supports word error rate measurement. The pipeline compares the transcript with the reference passage and calculates raw reading measurements. Word highlighting is an unvalidated assistive view of differences, not mispronunciation detection.
scikit-learn, NumPy, pandas & Pingouin
The research workflow uses these tools for feature preparation, calibration regression, and agreement analysis. Grade and language are required calibration features. Runtime WCPM correction uses exported coefficients when applicable; it does not train a new model for each reading. Continuous agreement is assessed with ICC and Bland–Altman analysis.
Trade-off
GPU processing introduces compute cost and startup time. Transcripts, alignment, and calibrated measurements can still be wrong, so teachers retain final judgement.

Original practice & illustrations

Cloudflare Workers AI

Practice generation is a separate AI workflow from oral-reading assessment. It creates draft reading material and artwork within consent, school-policy, and usage limits.

Mistral Small 3.1, Llama 3.3 & Llama Guard 3
Workers AI runs Mistral Small 3.1 24B to write each passage as plain text, rewriting until it fits the grade's word and readability ranges; in our September 2026 tests it kept to a word count and wrote natural Filipino where Llama 3.3 70B wrote 8–45 words when asked for 55–200. Llama 3.3 70B then labels the passage in JSON mode, reviews its facts and writes the comprehension questions; Llama Guard 3 8B moderates. Fact-review findings are flagged for the approving teacher rather than discarding the story.
FLUX.2 [klein] 9B & Llama 4 Scout
FLUX.2 [klein] 9B draws each passage cover from a literal scene written by the labelling step, with two of four READSmart sample covers sent as style references. The pair changes with every picture, and the prompt asks for new characters and a new setting, because with a plain style request the model redrew a reference's family and room. It replaced FLUX.1 Schnell, whose text-only brief gave flatter pictures than the site's artwork; a picture now costs about $0.016 instead of $0.002. FLUX.2 takes multipart input, which AI Gateway cannot receive from the Workers binding, so the picture call goes straight to Workers AI and READSmart counts it in its own usage records. Llama 4 Scout reviews each image against the reading for child safety, faithfulness and style. A rejected image is drawn again, up to three tries within the image limits, and a teacher can ask for a new picture later. Version-linked artwork is stored privately in R2.
Cloudflare Queues & background jobs
A story request returns at once: the Worker reserves it and queues it, and the queue consumer writes, checks and saves the story in the background, recording each stage (writing, checking, saving, questions, picture). Pupils see a progress bar and, on Stories and Home, a notice when the story is ready or waiting for the teacher, even after leaving the page.
AI Gateway & review controls
Cloudflare AI Gateway provides inference observability and spending controls for the text and review calls. Application quotas and budget reservations bound every call, including the pictures that bypass the gateway, and generated readings require teacher approval before pupil access.
Trade-off
Automated review can miss factual, language, or visual errors. Matching passage metrics does not establish validated reading-level equivalence; educator review and learner validation remain necessary.

Building & checking the application

Development and release tooling

Changes are checked with component tests, browser tests, and a Cloudflare build: Workers Builds builds the main branch from GitHub and deploys it. The Python pipeline has separate checks for scoring and its API payloads.

pnpm, TypeScript & ESLint
pnpm manages the web dependencies and scripts. TypeScript checks types, and ESLint checks source-code conventions and common mistakes.
Vitest, Testing Library & Playwright
Vitest runs focused logic and component tests, with Testing Library and jsdom for rendered interfaces. Playwright exercises flows in a real browser. Python checks cover pipeline behavior and data contracts.
Wrangler, OpenNext & uv/pip
Wrangler manages Cloudflare development and deployment operations, including D1 migrations. OpenNext builds the Worker application. uv/pip manage Python dependencies; Docker packages the RunPod runtime.
Trade-off
Passing software checks does not establish educational validity. Deployment checks and research validation answer different questions, and both need evidence.

Technology names and logos belong to their respective owners. Their use here describes the implementation, not sponsorship. Logos: Simple Icons (CC0) and the projects’ own marks.