Products we run

HamPrep

Everything for the Canadian amateur-radio Basic exam: the official question bank as an app that works offline, a formula cheatsheet, a study page for every question, and live analytics over Canada’s callsign registry — five surfaces on one Cloudflare Worker.

The HamPrep landing page: free Canadian Basic amateur-radio exam practice with official ISED questions and a live count of registered Canadian callsigns.

The itch

We studied for the Basic exam ourselves. The material deserved better software.

HamPrep exists because we sat this exam. Canada’s regulator publishes the entire question bank — all 984 questions the real exam draws from — but it publishes them as a raw data file, and studying from a raw data file is miserable. So we turned the whole thing into software: the questions, explanations for every answer, the formulas, and eventually the national callsign registry itself.

What we built

Five public surfaces that feel like one product.

The exam app is the core: installable, offline-capable, progress kept on your device (signing in syncs it, but is never required). Three modes match how people actually learn — Learn shows each answer with a “why?” for every wrong option; Practice builds adaptive sets that prioritize unseen questions, then ones you missed, then your weak areas; and the Mock Exam is 100 questions in 60 minutes under the real pass marks, with a readiness score that treats untouched categories as worst-case rather than flattering you.

Around it: study pages — every question, answer, and explanation as a fast indexable page, built from the same data the app uses; a KaTeX-rendered formula cheatsheet; marketing pages that ship zero JavaScript; and callsign stats — live analytics over Canada’s amateur-radio registry, from qualification mix to province leaderboards to whether the vanity callsign you want is still free.

HamPrep on a phone: free Canadian Basic exam practice with official ISED questions.
hamprep.ca on the phone it gets installed to

How it works

One Worker, one deploy. Three surfaces are baked at build time, two render live.

hamprep.ca five surfaces · one Cloudflare Worker · one deploy Marketing pages prerendered · zero JS Cheatsheet every formula, prerendered Study pages all 984 questions, indexable Exam app installable · works offline · progress stays on the device Callsign stats live analytics over Canada’s amateur registry plain boxes are baked at build time; outlined ones render live, same Worker ISED question bank 984 official questions, committed as JSON one file, two readers Daily ingest 07:00 UTC: the callsign registry, fetched & diffed signed events
Five surfaces, one Worker, one deploy. The question bank is a single committed JSON file read by both the offline app and a hundred indexable study pages — the two can never drift. The stats side is fed by a daily, signed ingest of the official registry.

Consolidation was the founding decision — the very first commit merges what had been separate projects into one app on one Cloudflare Worker. Static surfaces are baked at build time and served from the edge; the app and the stats render live from the same Worker, backed by two databases (study data and registry data) and one cache. One repository, one pipeline, one deploy — the operational overhead of five products for the price of one.

The hard part: trusting a noisy data source

The official registry dump sometimes loses records for a day. Announcing releases naively would cry wolf weekly.

The stats side diffs the official registry dump every morning to detect new licences and released callsigns. The catch: the dump occasionally drops records that reappear the next day. A naive “absent today means released” would emit false releases on every hiccup — and a callsign-availability feature built on false releases is worse than none.

day 1 day 2 day 3 one bad day present missing streak 1 back again streak resets nothing announced it was just a bad dump a real release missing streak 1 missing streak 2 missing streak 3 released trusted on the third miss and if the dump comes back unchanged (a cached copy), no streak advances at all — a repeat of yesterday’s file is not new evidence.
A callsign must be missing three daily runs in a row before it counts as released; one reappearance resets the streak. Tested exactly this way: “emits release event on the third consecutive miss.”

Decisions worth noting

  • The mock exam is assembled like the real one. One question drawn from each of the 100 subtopics — exactly how the regulator builds the actual exam. We rejected proportional sampling because it under-weights the regulations section and can double-draw a subtopic, which the real exam never does.
  • One source of truth for the bank. The same committed JSON feeds the offline app and every indexable study page. There is no second copy to fall out of date.
  • Local-first progress. Your study history lives on your device and works offline; signing in syncs it idempotently. The app never holds your progress hostage to an account.
  • Statistics that sum to 100%. Operators hold overlapping qualifications, so the stats classify each by their highest one — a deliberate correctness fix over counting overlapping flags and quietly double-counting people.
  • Official data end-to-end. The question bank and the registry both come straight from the regulator. Nothing is invented, and the ingest identifies itself honestly with a descriptive user agent.

Built with

  • Astro
  • React
  • Cloudflare Workers

This is how we run our own products

The same standards go into client work — cloud, network, and security engagements with a fixed scope and a fixed price, delivered as code you own.