From cb562177516c62d3b22fa7c50ad5b337abbdb155 Mon Sep 17 00:00:00 2001 From: fosterwei Date: Mon, 12 Oct 2026 10:35:32 +0000 Subject: [PATCH] Give each compound with published doses its own calculator page Nineteen new pages at /calculators/, plus an index, modelled on the per-peptide calculators that peptidecalcs.com ranks with. The arithmetic is identical for every peptide, so the arithmetic cannot be what makes nineteen pages worth nineteen URLs. What differs is the doses published studies of that compound actually used, and this site already holds those as cited claims with an evidence tier on every record. Each page puts that table under the calculator: what the paper administered, in which species, with a link to the paper. No generic calculator can do that, and no competitor cites a study for its numbers. That same fact decides which compounds get a page. A compound qualifies only if its record is written and reviewed and carries at least two distinct quoted dose figures. Nineteen of thirty-four clear it. The rest would be this page with the name changed, which is how a programmatic set becomes index bloat, and the site has already noindexed seven pages on that principle. The pages keep the refusal the generic tool has: no body weight, no condition, no returned dose. Showing what a trial gave is reporting, which is what every record here does. Suggesting what a reader should take would be advice, which none of them does. Pages where no human dose exists say so in the lede rather than letting an animal figure imply one. The calculator itself is the existing component, so the arithmetic has one implementation and one set of tests. test_calculators.mjs now covers every generated page, not just the three generic tools: form present, output present, dose table non-empty, and the script computing the known 5 mg / 2 mL / 250 mcg case. 36 assertions, up from 17. Verified by stripping a form and by emptying a dose table, each of which exits 1. Without this, one template change would silently empty nineteen pages at once, which is exactly how the unit converter and vial planner shipped as headings with no calculator. The sitemap emits them explicitly, because they derive from the compounds collection rather than being a collection of their own and nothing else would have listed them. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/test_calculators.mjs | 37 ++++++++++ src/lib/calculators.ts | 59 ++++++++++++++++ src/pages/calculators/[slug].astro | 104 +++++++++++++++++++++++++++++ src/pages/calculators/index.astro | 67 +++++++++++++++++++ src/pages/sitemap.xml.ts | 12 +++- 5 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/lib/calculators.ts create mode 100644 src/pages/calculators/[slug].astro create mode 100644 src/pages/calculators/index.astro diff --git a/scripts/test_calculators.mjs b/scripts/test_calculators.mjs index c8675a8..c8204f1 100644 --- a/scripts/test_calculators.mjs +++ b/scripts/test_calculators.mjs @@ -84,6 +84,43 @@ check('vial: works without a volume', check('vial: empty input guarded', render(V, { 'v-mass': '', 'v-vol': 2, 'v-want': 250, 'v-freq': 7 }, 'v-out'), 'Enter a positive'); +// Every per-compound calculator runs the same script, so one broken render or a +// template change that drops the form would take all of them out at once and +// nothing else in the build would notice: a page with no
still builds, +// still validates and still enters the sitemap. That is exactly how the unit +// converter and vial planner shipped empty. +const CALC_DIR = path.join(DIST, 'calculators'); +if (fs.existsSync(CALC_DIR)) { + const slugs = fs.readdirSync(CALC_DIR, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => d.name); + if (slugs.length === 0) fails.push('dist/calculators exists but contains no compound pages'); + for (const slug of slugs) { + const file = path.join(CALC_DIR, slug, 'index.html'); + if (!fs.existsSync(file)) { fails.push(`calculators/${slug}: not built`); continue; } + const html = fs.readFileSync(file, 'utf8'); + if (!/`); continue; } + if (!/`); continue; } + // The dose table is the only reason this page exists rather than a link to + // the generic tool, so an empty one means the page should not have shipped. + const tableRows = (html.match(/[\s\S]*?<\/tbody>/i) || [''])[0].match(//gi) || []; + if (tableRows.length === 0) fails.push(`calculators/${slug}: dose table is empty`); + const m = html.match(/