Field math for the trades. Free, fast, no ads, no accounts, works offline.
roughlogic.com is 2,082 small, single-purpose calculators for electricians, plumbers, HVAC techs, carpenters, restoration techs, firefighters, surveyors, and dozens of other trades. Everything runs in your browser. Every answer comes from a published formula and cites its source.
- Type the job the way you'd say it, numbers and all:
voltage drop 120v 150 ft 12 awg 20a. Search by name if you'd rather, or open All calculators for the full list by trade. - The calculator opens with your numbers already in it, each box captioned with the words they came from. If one value is missing, the page asks for it in words.
- The answer sits at the top of the page, above the inputs that produced it, and updates as you type. There is no submit button. Tap Copy to take a value with you.
Open a calculator directly, without a question to fill it, and it starts empty -- each box captioned with the value the page's worked example uses (e.g. 150), so you can see the expected unit and magnitude before typing your own over it. A box whose value is a trade convention rather than a job number (30 °C ambient, a 0.61 orifice coefficient) opens holding that convention instead.
Every calculator's page is the same short thing, in this order. Nothing below the example is open until you ask for it:
| Title + one line | what it does |
| Run the calculator | opens it with the example already loaded |
| Example | the exact inputs and the exact answer |
| Collapsed detail | One Details, formula, and sources drop-down at the bottom of every page -- scope, formula, sources, and assumptions, all in the one place |
Here is that Example block, from Voltage Drop:
| You enter | You get | ||
|---|---|---|---|
| Phase | single | Voltage drop | 7.45 V |
| Material | copper | Percent drop | 3.11 % |
| AWG | 10 | ||
| Length one-way (ft) | 150 | ||
| Current (A) | 20 | ||
| Source voltage (V) | 240 |
Grounded in VD = 2 · I · R · L, with R from NEC Ch. 9 Table 8 -- the same line the page prints under Details, formula, and sources. Everything is named the way the calculator names it. The machine field names an AI agent passes (length_ft, drop_V) are listed inside the collapsed formula block, so the page stays readable and the contract stays published.
Your inputs live in the URL, so a calculator is bookmarkable and shareable with its numbers preloaded. After the first load it works with no signal. No account, no email, and no tracking. A user-initiated problem report is the one explicit exception to local-only data: it sends the displayed URL, inputs, results, and an optional 160-character note to a bounded Cloudflare D1 queue.
The whole catalog is available to AI agents (Claude Code, Claude Desktop, Cursor) through a local, zero-dependency MCP server that runs on your machine over stdio. No hosting, no network. Five tools (search_calculators, describe_calculator, run_calculator, answer_query, run_calculators) read straight from this repo, so the agent surface can never drift from the site. See mcp/README.md.
21 trade benches. Search spans all of them at once; the letters are internal group codes (I is reserved).
| Bench | Bench | ||
|---|---|---|---|
| A | Electrical | N | Stage & Live Production |
| B | Plumbing & Gas | O | Kitchen & Food Service |
| C | HVAC | P | Field, Backcountry & SAR |
| D | Water Damage & Mold Restoration | Q | Historical Reference Data |
| E | Carpentry & Construction | R | Accounting, Tax & Small-Business |
| F | Fire-Ground Engineering | T | Bench Science & Laboratory Math |
| G | Cross-Trade Utilities | X | Real Estate |
| H | Knowledge References | Y | Educators & K-12 |
| J | Trucking & Logistics | Z | Rigging & Heavy Lift |
| K | Mechanic - Auto, Marine, Aviation | ||
| L | Agriculture & Forestry | ||
| M | Water & Wastewater Operations |
One calculator is one formula on one screen. (In the source and the gate names below, a calculator is called a tile.) Every formula is transcribed in docs/derivations.md and every citation in docs/data-sources.md.
The hard part of a calculator catalog is not the arithmetic. It is proving, at scale, that every tile stays correct as the catalog grows. That is a build problem here: npm run lint runs 59 static gates before a change can land. Fifty-eight of them run for anyone who clones this repository; the fifty-ninth, check-ngrams, compares text against a private hash list of licensed code spans that is deliberately not published, and skips with a message when the list is absent.
| Gate | What it guarantees |
|---|---|
check-dimensions |
all 2,337 exported calculator functions carry a machine-parsed // dims: annotation and a malformed one fails the build -- but 3 of those annotations are stubs that declare a single opaque args on a function destructuring named inputs, so "declares its inputs" is true of 2,334 functions, not all of them. That count is pinned and may only fall -- but it has reached its floor: each of the three takes a parameter whose dimension a sibling selector picks at runtime, so no single declaration is right, and the gate names all three with the input change each would need. Two rules check the declarations that do exist: 7,901 keys named for a unit (_psi, _gpm, _rpm, _hp, _btu, _btuh, _ft, _in, _sqin, _hz, _ohm, _v, _w, _a, _ksi, _kip, _deg, _ft3, _fps) must declare that unit's dimension, and every key name must mean one quantity across all 78 modules -- 44 names are still declared two ways and each is listed by name in the gate (24 of them the undecided force-vs-mass reading of the pound, 20 genuine collisions like rho, slope and the refrigeration-versus-short tons). Together they caught 434 wrong declarations. It still checks the declaration, not the arithmetic against it -- verifying the expression would need a CAS, which this repo has no dependency on |
check-cross-validation |
every fixture's declared tolerance is inside its group's ceiling, or carries a written justification (3,431 checks) -- it polices how loose a check is allowed to be, not the numbers themselves |
cross-tile-invariants (unit test) |
tiles that share a computation agree to the floating-point floor, documented inverses round-trip, and monotonic relationships stay monotonic -- 401 assertions across the five shared-computation classes spec-v14 §10 names |
asked-phrasing (unit test) |
a tile that ranks first for its bare name still ranks first with a question wrapped around it -- the phrasing step 1 above teaches. The ranking harness's three older ground truths are aliases, names and ids, and not one carries a question word. Every 24th tile, three phrasings each |
| identity coverage (ranker) | a word matched only in a tile's prose description cannot outrank a tile the query names. Coverage sorts ahead of score and "anywhere" used to include the description, so a plywood question opened with a gas-cylinder tile whose description mentions "a sheet of plywood". Measured against all four ranking ground truths: every rate unchanged |
chip-routing (unit test) |
the four example questions on the home view route to a calculator instead of opening a disambiguation card, and the vague queries still ask. Read from index.html, so an edited chip is covered automatically. It reproduces in 40 ms what a browser spec costs a full CI run to catch |
integrity-banner (e2e) |
the threat model's promise that tampered data gives a visible warning, never silent corruption -- exercised, not asserted. It rewrites data/integrity.json so a real manifest fails its hash, then checks the reader is warned, that the warning is announced and names the dataset, and that the site keeps working |
answer-above-inputs (e2e) |
the answer renders above the inputs that produced it, measured as rendered position rather than DOM order -- a stylesheet can invert the page without touching the markup, and seeding order: -1 proves it |
check-bounds |
a fuzzer sweeps each tile's input domain; no NaN/∞, monotonicity where required |
check-worked-examples |
every tile's example reproduces a reference number it names a source for -- a published worked example where the publisher prints one, and the project's own derivation where none exists. 686 of them are first-principles: nobody publishes a worked example for Ohm's law or a footing area, so the derivation is the check |
check-example-parity |
the example a page prints is the example its calculator opens (1,797 tiles statically; the 285 that declare theirs inline are driven in a real browser by test/integration/example-parity-runtime.test.js, so the claim covers all 2,082) |
check-citation-coverage |
every tile names a real, dated source with all four required fields and no orphans. Freshness tracking is narrower: 1,472 tiles cite a source on a recheck calendar, and 610 cite a source no freshness tracker covers |
check-derivation-coverage |
72 formula families are derived in full in docs/derivations.md, and every tile is named there. The gate asserts the naming, which the generated per-tile index satisfies -- it does not establish that a given tile's formula is among the 72 |
check-dead-inputs |
no rendered field is silently ignored by the compute function, across the 2,084 computes that destructure their inputs. The computes that took a named object parameter the scan cannot see into are down to 0: the four that held that budget open were destructured on 2026-09-10, and the budget was ratcheted to match, so a compute may not go back to an opaque parameter without failing the gate |
check-fixture-keys / check-guard-only-inputs / check-render-output-keys |
each parses source rather than running it, so each has a set it cannot see into -- a rest parameter, a named object signature, a spread return. All three name that set and fail when it grows (25, 0 and 7). The first two were classified on 2026-09-10 and the drainable part of both was the same four computes, which took a single opaque object; destructuring them took the named-object budget to 0 and left check-fixture-keys at 25, which is its floor -- 21 reference tiles whose compute takes no parameter at all and 4 that dispatch on a shape through a rest parameter, neither of which has input keys to compare against. The seven spread returns are covered a second way, by running them against their worked examples |
check-tile-contract |
every tile is registered, crash-free, and matches its declared I/O shape |
check-shell-mobile |
zero page-level horizontal scroll on every shell at 320 px portrait. Landscape and 200% text zoom run over a representative sample -- every group hub, the home shell and an evenly-strided slice of tool shells -- because all shells come from one template and the only per-tile variable is string length |
check-feedback-loop |
every calculator retains the shared defensive D1 reporting path, and the three places a size limit is stated -- the browser's cap, the Worker's re-check, the D1 CHECK -- agree. Three layers is the design; each naming its own number was a drift that would land on a reader's bug report as a 500 |
check-tile-registries |
the new-tile checklist names every registry that holds all 2,082 ids, and calls no partial one mandatory |
check-us-defaults |
no calculator opens metric-first: every input label, factory field spec and output label whose parenthetical carries a metric unit needs a reviewed allowlist entry, and every entry must waive at least one live finding -- a dead exemption fails the build |
check-build-hermetic |
the build fetches nothing; every value in data/ is an in-tree constant, reviewable in a diff, and no shipped string claims otherwise |
build-citation-strings |
the source stamp a tile prints is the one docs/citation-discipline.md documents, word for word |
check-community-health |
the files GitHub renders on the repo page exist, resolve, and parse (a malformed issue form fails silently otherwise) |
check-notice-variants |
the notice naming who governs each answer matches what docs/notice-variants.md says it is |
The monthly and weekly data-refresh jobs are the one lane those gates used to miss. A pull request opened by a workflow using GITHUB_TOKEN triggers no further workflow runs, so every refresh PR sat with a CI run that never executed and faced only data:verify and the unit tests -- not the manifest, citation-freshness, verified-on-ledger or provenance-stamp gates that exist for the data path specifically. Since 2026-09-04 both scheduled jobs run npm run lint and the base-TIP stamp check themselves, before opening the PR, and check-ci-claims fails if either step is dropped.
CI adds three jobs per push: test (lint + unit tests + data-integrity verification + the provenance-stamp check against what the push moved from), then accessibility (the axe-core sweep over every SPA route, plus the shell sweep that covers each static page shape) and integration (the rest of the Playwright suite, plus the built-shell gates) in parallel. The two split the suite by title rather than both running it, so the 2,175-test axe pass executes once per push, not twice; npm run test:e2e still runs everything locally. A fourth Lighthouse job was removed on 2026-08-23 over an unpatched advisory in @lhci/cli; see docs/performance.md for what gates performance in its place. That table names, per gate, where it runs, and check-ci-claims now pins those placements: a gate that measures the built copy cannot be claimed under npm run lint, which runs before any build and skips with a WARN rather than failing. At runtime, integrity.js re-verifies the SHA-256 of every data manifest against data/integrity.json, and each shard against the hash its own manifest records, before the data reaches a calculation; the read-only posture means the worst case is a visible warning, never silent corruption.
Calculator execution is a client-side, offline-first static site with no accounts or analytics. One isolated Cloudflare Worker accepts user-initiated problem reports into D1; Turnstile loads only after the report dialog opens, and static requests remain asset-first.
The browser loads index.html + styles.css + app.js (router, search, theme, URL-hash state), which dynamic-imports one of 77 per-group calculator modules (calc-*.js) on first open, which reads the sharded JSON in data/. A service worker (sw.js) caches the shell and data shards keyed to the build hash, so the site works offline after the first load. The 2,104 static shells are not precached -- that is not a precache -- so a shell URL opened offline redirects to the app at the root, carrying the tile as the hash, which is why a bookmarked /tools/ohms-law/ opens Ohm's Law rather than a page whose relative stylesheet 404s.
At build time only (never in production), build-data.mjs emits the integrity-hashed data shards. It fetches nothing: every value it writes -- NIST constants, NOAA design temperatures, the NCEI WMM coefficients, FHFA loan limits, HUD fair-market rents, published bulletin tables -- is transcribed into an in-tree constant by a maintainer and reviewed in a diff, so what the build produces is a deterministic function of the repository and of nothing else. check-build-hermetic fails on any undeclared network call in the build or the lint chain, and on any shipped string that says a value was fetched or downloaded. Where a bundled value is modeled rather than transcribed -- the commodity price series is an anchor reading plus a shape, not the published monthly numbers -- the shard, the page and the citation all say so. And build-shells.mjs emits one zero-JS crawlable static shell per tile (2082) and per group, the catalog hub, the not-found page Cloudflare Pages serves for every unmatched path, plus a sitemap that carries 2105 URLs, each dated from a committed content-hash ledger rather than the build clock, so a page that has not changed does not claim it has.
The home payload gzips to well under the 100 KB budget. Opening a calculator dynamic-imports only that trade's module and only the data shards it needs. See docs/architecture.md and docs/seo.md.
npm ci # exact locked dev tooling; the site has zero runtime deps
npm run dev # build, then serve only dist/ on loopback
npm run build # emit dist/ (SPA + static shells + sitemap)
npm run lint # the full static-gate chain (59 checks) -- 56 run without the private n-gram list
npm test # unit tests (node --test)
npm run test:e2e # Playwright integration suite (needs a browser)The repo root holds the SPA entry (index.html, styles.css, app.js, sw.js), the calc-*.js modules, and shared UI helpers. data/ holds sharded JSON with per-folder manifests and integrity hashes. scripts/ holds build and gate tooling (never runs in production). specs/ is the numbered specification history; docs/ holds derivations, data sources, architecture, and the audit trail. See docs/maintainer-quickstart.md and docs/contributor-checklist.md.
Patches welcome. CONTRIBUTING.md has the hard constraints (zero runtime dependencies, no network at build time, US standards only) and the one command that has to be green before review. A wrong answer is the most valuable thing you can report: there is an issue template for it that asks for the inputs and the published source that settles it.
MIT. See LICENSE.


