Build OpenPTE

KINDAreplaces $17.99/mosaves $215.88/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A practice platform for exam-style speaking, writing, reading and listening tasks, where the honest engineering problem is producing genuinely useful feedback on a spoken or written response without pretending to be the official scoring engine.

estimated effort multi-daythe files for this build are in the project pack

RuntimeTypeScript with Fastify and PostgreSQLSpeechLocal speech recognition with word timings and confidenceWritingRule-based checks plus a model for discourse, each reported separatelyTimingServer-authoritative task timersContentOriginal tasks written for the platform, never real exam items

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server and task delivery.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree locally

    Why Attempts, recognitions and observations with per-dimension queries.

    Get it Docker or your package manager. open ↗

    Verify psql connects and creates a table

  3. installfree, needs machine time

    Why Fluency feedback comes from timings and uncertainty from confidence, and sending learners' voice recordings to a service is a poor default.

    Get it whisper.cpp or an equivalent, confirmed to emit per-word timings and confidence rather than segments only. open ↗

    Verify A spoken response produces per-word timings and confidences offline

  4. have readyfree, and it is real work

    Why Real exam items are copyrighted, and a platform built on them cannot be shared and does not teach the skill.

    Get it Write tasks matching the published format descriptions rather than reproducing items. Aim for a handful per format before building the delivery.

    Verify At least five original tasks exist per format, with none copied from an exam

  5. have readyfree, and requires asking people

    Why The fairness requirement of this build is that an accent is never reported as an error, and that can only be tested against varied speakers.

    Get it Ask people who would actually use this, with their permission, and keep the recordings only as long as needed for testing.

    Verify Recordings from at least four first languages exist and none is reported as mispronunciation

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- tasks(id, skill, format, prompt_text, audio_path, time_limit_seconds, source) · source is always original
- attempts(id, user_id, task_id, started_at, submitted_at, response_text, response_audio_path)
- recognitions(attempt_id, word_ordinal, text, start_ms, end_ms, confidence) · the basis for speech feedback
- observations(id, attempt_id, dimension, detail, evidence_json) · dimension is fluency, pronunciation, grammar, content or discourse
- progress(user_id, dimension, week, observation_count, trend) · improvement over time, never a predicted score
- There is deliberately no column anywhere holding a predicted overall band, because a field like that gets displayed eventually.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/practiceAttempts and observations, including learners' voice recordings by reference.
AUDIO_DIRrequired./attempts/audioVoice recordings. Personal data, so retention and deletion apply to this directory too.
ASR_MODELrequired./models/ggml-medium.en.binThe local recogniser. Larger models give better timings and fairer confidence across accents.
AUDIO_RETENTION_DAYSrequired90How long voice recordings are kept. Shorter than the observations derived from them.
SHOW_PREDICTED_SCORErequiredneverPresent as a constant so the answer is visible in configuration. There is no other value.

The build, in order

  1. Tasks delivered under exam conditions

    A practice task runs with the same timing pressure as the real thing.

    1. The client shows a countdown. It does not own one.

      Files src/timer.ts

    2. Even if the browser is gone. The attempt still happened.

      Files src/attempts.ts

    3. An interrupted attempt is data, not something to hide.

      Files src/attempts.ts

    done when · tick each as it passes
    watch out
    • A timer running in the browser can be paused by opening the developer tools or simply losing focus, which turns timed practice into untimed practice and removes the only thing that makes it resemble the exam. Keep the clock on the server and treat the client display as a hint.
  2. Hearing what was actually said

    A spoken response becomes words with times and confidences.

    1. Both are needed. Fluency comes from timings, uncertainty from confidence.

      Files src/recognise.ts

    2. Test with several accents. This is the fairness requirement of the whole build.

      Files tests/accents.md

    3. A bad microphone is not a speaking weakness and must not be reported as one.

      Files src/audio-quality.ts

    done when · tick each as it passes
    watch out
    • Treating low recogniser confidence as mispronunciation punishes accents the recogniser was simply not trained on, which is both wrong and demoralising to exactly the people using the tool. Report low confidence as uncertain rather than incorrect, and never present the recogniser's difficulty as the speaker's failure.
  3. Feedback that names something specific

    Every observation points at a moment or a phrase in the response.

    1. A timestamp or a span. Validate it and reject the output otherwise.

      Files src/observations.tssrc/validate.test.ts

    2. No aggregate field. A number that exists will eventually be shown.

      Files src/schema.sql

    3. Feedback that only lists failures is demoralising and less useful.

      Files src/observations.ts

    done when · tick each as it passes
    watch out
    • Combining dimensions into a single figure recreates exactly the predicted score this build refuses to produce, and users will read it as one. Keep the dimensions apart in the model and in the interface, and make sure no aggregate exists anywhere that could be displayed as an overall result.
  4. Written responses, checked honestly

    An essay gets specific feedback on grammar, content and structure separately.

    1. Same essay, same findings, every time. Label them as mechanical.

      Files src/checks.ts

    2. An observation about a paragraph must include the paragraph.

      Files src/essay.ts

    3. So the user knows which finding is reliable and which is an opinion.

      Files src/feedback-view.ts

    done when · tick each as it passes
    watch out
    • Presenting a model's opinion about an essay's quality with the same authority as a spelling checker's finding misleads the user about how reliable each is, and a model asked to grade will confidently invent criteria. Label the deterministic checks and the model's observations differently, and require the model to quote the text it is describing.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more education.