§ 01О чём проектWhat this is
Заказчик готовит студентов к IELTS и накопил библиотеку готовых тестов в формате HTML. Нужна была платформа, где студент проходит Reading и Listening в том же виде, что и на реальном компьютерном экзамене, сразу видит результат с разбором, а прогресс копится в личном кабинете. The client prepares students for IELTS and had accumulated a library of ready tests in HTML format. What was needed was a platform where a student takes Reading and Listening exactly as on the real computer-based exam, sees the result with a breakdown right away, and where progress accrues in a personal account.
Задача была не «один экран с вопросами», а платформа целиком: импорт готового контента, движок прохождения, честный рейтинг, оплаты и админка. Права на учебный контент остаются у заказчика — я отвечал за платформу, код и архитектуру. Платформа развёрнута и открывается на bando.study; публичный запуск заказчик готовит. The task wasn't "one screen with questions" but a whole platform: content import, a test-taking engine, a fair leaderboard, payments and an admin panel. Rights to the learning content stay with the client — I was responsible for the platform, the code and the architecture. The platform is deployed and opens at bando.study; the client is preparing the public launch.
§ 02Как устроеноHow it's built
Платформа на Next.js: серверный рендеринг страниц, серверные API-роуты, данные в Supabase (Postgres). Логика прохождения теста живёт на сервере — браузер получает вопросы, но не ключи ответов до сдачи попытки. The platform runs on Next.js: server-rendered pages, server API routes, data in Supabase (Postgres). The test-taking logic lives on the server — the browser gets the questions, but not the answer keys, until the attempt is submitted.
- Два тренажёра — Reading и Listening — в формате реального экзамена: таймер, навигатор по вопросам, автосохранение ответов.Two trainers — Reading and Listening — in the real exam format: timer, question navigator, answer autosave.
- Импорт готовых тестов заказчика — платформа принимает подготовленные HTML-тесты и превращает их в интерактивные попытки.Import of the client's ready tests — the platform ingests prepared HTML tests and turns them into interactive attempts.
- Детерминированная проверка — по правилам, без нейросетей. Поэтому результат мгновенный и воспроизводимый: одинаковые ответы всегда дают одинаковый балл.Deterministic checking — by rules, no neural nets. That's why the result is instant and reproducible: the same answers always give the same score.
- Разбор результата по типам вопросов — видно, где именно студент теряет баллы.Result breakdown by question type — you can see exactly where the student loses points.
- Каталог с фильтрами, личный кабинет, лидерборд, бейджи, стрики за регулярность и реферальная программа.Filtered catalog, personal account, leaderboard, badges, streaks for consistency, and a referral program.
- Writing и Speaking с автоматической оценкой на базе Gemini (для Speaking — с разбором аудио) реализованы и ждут запуска за фиче-флагом.Writing and Speaking with automatic scoring powered by Gemini (with audio analysis for Speaking) are built and waiting behind a feature flag.
- Локальные оплаты — Payme, Click, Uzum. Интеграции написаны; включатся, когда заказчик подключит мерчант-ключи.Local payments — Payme, Click, Uzum. The integrations are written; they switch on once the client connects the merchant keys.
bando/ ├── app/ │ ├── (exam)/ # движок прохождения: таймер, навигатор# test-taking engine: timer, navigator │ ├── catalog/ # каталог тестов с фильтрами# test catalog with filters │ └── api/ # серверные роуты: сдача, скоринг, оплаты# server routes: submit, scoring, payments ├── lib/ │ ├── scoring/ # детерминированная проверка по типам вопросов# deterministic checking by question type │ ├── import/ # песочница для чужого HTML# sandbox for foreign HTML │ └── payments/ # Payme · Click · Uzum ├── db/ # Drizzle: 37 таблиц, 58 миграций# Drizzle: 37 tables, 58 migrations └── jobs/ # 8 фоновых крон-задач# 8 background cron jobs
lib/scoring.Fig. 4 · Structure. Answer checking and keys live on the server, in lib/scoring.§ 03Что было сложноWhat was hard
(1) Импорт чужого HTML. Тесты приходят готовыми HTML-файлами со своими встроенными скриптами. Запускать чужой код на своём сервере опасно, поэтому он исполняется в изолированной песочнице с лимитами по времени и памяти — из скриптов достаётся только логика подсчёта баллов, ничего лишнего выполниться не может. (1) Importing someone else's HTML. Tests arrive as ready HTML files with their own embedded scripts. Running foreign code on your own server is dangerous, so it executes in an isolated sandbox with time and memory limits — only the scoring logic is extracted from the scripts, nothing else can run.
(2) Защита ответов. Ключи и разборы физически не отдаются в браузер, пока студент не сдал попытку — иначе правильные ответы можно было бы подсмотреть в коде страницы. Проверка идёт на сервере, браузер получает только сам балл. (2) Protecting the answers. Keys and breakdowns are physically not sent to the browser until the student submits the attempt — otherwise the correct answers could be peeked at in the page source. Checking happens on the server; the browser receives only the score itself.
(3) Одновременные сдачи и честный рейтинг. Когда несколько студентов сдают попытки в один момент, легко получить гонку и испорченный рейтинг. Здесь стоят блокировки на уровне базы данных, а конкурентность проверяется адверсариальными тестами — специально сталкивающими сдачи друг с другом. (3) Concurrent submissions and a fair leaderboard. When several students submit at the same moment, it's easy to get a race and a corrupted ranking. Here there are database-level locks, and concurrency is verified with adversarial tests that deliberately collide submissions against each other.
(4) Два движка прохождения. Первый — точная реплика интерфейса реального компьютерного IELTS: тест открывается в изолированном окне без доступа к сети, чтобы всё было как на экзамене. Второй — собственный режим практики с подробным разбором. Один и тот же тест проходится в обоих. (4) Two test-taking engines. The first is an exact replica of the real computer-based IELTS interface: the test opens in an isolated window with no network access, so everything is like on the exam. The second is a custom practice mode with a detailed breakdown. The same test can be taken in both.
§ 04ЦифрыNumbers
За 5,5 недель от первого коммита до готовой платформы: 985 коммитов, ~75 000 строк TypeScript, 1814 автотестов и e2e-сценарии Playwright, 37 таблиц и 58 миграций, 46 страниц и 16 API-роутов, 8 фоновых крон-задач, 3 платёжные интеграции. In 5.5 weeks from the first commit to a finished platform: 985 commits, ~75,000 lines of TypeScript, 1,814 automated tests plus Playwright e2e scenarios, 37 tables and 58 migrations, 46 pages and 16 API routes, 8 background cron jobs, 3 payment integrations.
§ 05Что я вынесLessons learned
(а) Там, где правила известны заранее, детерминированная проверка надёжнее и дешевле нейросети: результат воспроизводим, мгновенен и его не надо перепроверять. AI я приберёг для Writing и Speaking, где без него действительно не обойтись. (a) Where the rules are known in advance, deterministic checking is more reliable and cheaper than a neural net: the result is reproducible, instant, and needs no re-checking. I saved AI for Writing and Speaking, where there's genuinely no way around it.
(б) Безопасность — это модель данных, а не заплатки поверх. Ключи ответов не уходят в браузер не потому, что «спрятаны», а потому что сервер их туда не отдаёт — так спроектировано. (b) Security is a data model, not patches on top. The answer keys don't reach the browser not because they're "hidden," but because the server doesn't send them there by design.
(в) «Платформа под ключ» — это импорт контента, экзамен, оплаты и админка, а не один экран с вопросами. Большая часть работы там, где пользователь её не видит. (c) A "turnkey platform" is content import, the exam, payments and an admin panel — not one screen with questions. Most of the work is where the user never sees it.