中文 Mandarin Teacher
A single-user Mandarin learning engine I built for myself — spaced repetition driven by an append-only event log, with speech scoring on my own GPU. One learner, one machine, zero recurring costs.
HSK coverage, by ear
Denominators are the official HSK level sizes, not the corpus — coverage is only claimed against the real target. The corpus currently spans HSK 1–3: 810 sentences with 6,480 audio clips across 503 words, and it grows ahead of the learning frontier.
How it works
The event log is the source of truth
Every rep is one row in an append-only log — SQLite triggers physically refuse updates and deletes. Scheduling state is a cache rebuilt by replaying the log, so a scheduling bug can never destroy history: fix the code, replay, and the past is reinterpreted rather than lost.
Scheduling that separates skills
FSRS spaced repetition, with a card per (word, skill) pair — hearing 累 and saying 累 are tracked as different memories, because they are. Listening gates speaking: nothing is asked of the mouth before the ear can verify it. New vocabulary is capped per day; past the cap, the system serves practice on known words instead of more novelty.
Speech scoring on local hardware
Attempts are transcribed by faster-whisper (large-v3, CUDA) and pitch-tracked with Praat. Tone feedback does not depend on the transcript: MFCC features aligned by dynamic time warping carry native syllable boundaries onto the learner's audio, so tones are judged even when recognition fails. And a recogniser failure is never graded as a learner failure — undecodable or hallucinated results are discarded, not scored.
Honest grading
The interface commits before it reveals. Quiz answers are graded server-side, dictation withholds the text until the answer is locked in, and replays are counted against the grade automatically — the learner never has to be disciplined enough to self-report.
Local by construction
Everything — database, scheduler, models, audio — runs on one machine. Voice recordings never leave it, and there is no per-request bill anywhere in the loop: the marginal cost of a rep is electricity.
TypeScript · Hono · React · SQLite (node:sqlite) · ts-fsrs · Python · faster-whisper · Praat/parselmouth · CUDA