Changelog 82a4635
#191 leave use-to-freedom ratios undefined at zero
6 files of code, design or docs +31 −6 lines
Code, design and documents 6 files
docs/research/issue-corrections-186-204-2026-10-27.md +8 −0
@@ -18,2 +18,10 @@ series requires matched local numerator/denominator counts over identical comple intervals, validated historical API availability and rate limits; it must use a separate definition/file. Do not rescale these percentages to pretend to recover it. + +## #191 — Exact use/freedom quotient + +Zero index scores have an undefined quotient, represented by null and a hatched map +with an explicit tooltip explanation. Positive scores below one retain their actual +unrounded denominator. Regression covers scores 0, 0.5 and 1 plus missing prevalence. +The map lede and legend state the zero-score exclusion. Survey ages/years and estimate +markers remain qualified; this ratio is not an age-standardised comparison.
package.json +1 −1
@@ -14,5 +14,5 @@ "history:test": "node --test scripts/history-scope.test.cjs", "seo:audit": "node scripts/seo-audit.mjs", - "answers:test": "node --test scripts/answers.test.mjs scripts/profile-claims.test.mjs", + "answers:test": "node --test scripts/answers.test.mjs scripts/profile-claims.test.mjs scripts/use-freedom-ratio.test.mjs", "survey:test": "node --test scripts/survey.test.mjs", "articles:backlog": "node scripts/article-backlog.cjs",
scripts/use-freedom-ratio.test.mjs +10 −0
@@ -0,0 +1,10 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { useFreedomRatio } from '../src/lib/use-freedom-ratio.js'; + +test('zero freedom is undefined; positive scores below one keep their real denominator (#191)', () => { + assert.equal(useFreedomRatio(5, 0), null); + assert.equal(useFreedomRatio(5, 0.5), 10); + assert.equal(useFreedomRatio(5, 1), 5); + assert.equal(useFreedomRatio(null, 1), null); +});
src/components/HomePage.astro +6 −4
@@ -86,13 +86,14 @@ const MED_ORDER = ["yes", "limited", "no"]; // What the browser needs for the map: scores only, no prose. // `ratio` is the share of people who use cannabis divided by the index score: how far use runs -// ahead of freedom. A score below 1 counts as 1, so that a zero does not divide. +// ahead of freedom. A zero score has no defined quotient and is shown as unavailable. // `culture` is how many atlas entries a country appears in, with the names of the first few // (most important first), for the map's Culture view. // `ds` holds the country's figures from the outside datasets (datasets.js), for the Society views. +import { useFreedomRatio } from "../lib/use-freedom-ratio.js"; const mapData = places.map((c) => { const { id, num, name, flag, raw, score, rank, s, use, planting, clubs, driving, visitors, rec, med } = c; const atlas = cultureOf(c); return { id, num, name, flag, raw, score, rank, s, planting, clubs, driving, visitors, rec, med, fe: estimateNote(c), prices: SHOW_PRICES ? flatLatestPricesFor(id) : {}, - use: { ...use, ages: useAges(use), ratio: +(use.prevalence / Math.max(raw, 1)).toFixed(2), culture: atlas.length }, + use: { ...use, ages: useAges(use), ratio: useFreedomRatio(use.prevalence, raw), culture: atlas.length }, atlas: atlas.slice(0, 4).map((e) => e.title), ds: datasetMapValues(c) }; }); @@ -111,5 +112,5 @@ const USE_SCALES = { label: "Use relative to freedom", steps: [[0, "Under 0.15"], [0.15, "0.15–0.3"], [0.3, "0.3–0.5"], [0.5, "0.5–1"], [1, "1 or more"]], - note: "Survey-age cannabis-use share divided by the index score. Ages and survey years vary. Rates are not age-standardised; do not compare countries directly.", + note: "Survey-age cannabis-use share divided by the unrounded index score. Undefined at score zero (hatched on the map). Ages and survey years vary. Rates are not age-standardised; do not compare countries directly.", }, culture: { @@ -937,6 +938,7 @@ const columnGroups = [["Shown by default", BASE_COLUMNS], ...["Score segments", const usersText = c.use.users == null ? "—" : `${c.use.estimate || c.use.users_approximate ? "~" : ""}${compact(c.use.users)}`; if (metric === "ratio") { - return head(c.flag, c.name, `${rateMark}${c.use.ratio}`, useColor(c)) + return head(c.flag, c.name, c.use.ratio == null ? null : `${rateMark}${c.use.ratio}`, useColor(c)) + row(`Share aged ${c.use.ages}`, `${rateMark}${c.use.prevalence}%`) + row("Index", `${c.score} / 100`) + + (c.use.ratio == null ? para("Ratio unavailable: the index score is zero.") : "") + para(USE_SCALES.ratio.note) + foot(c); }
src/lib/map-views.js +1 −1
@@ -23,5 +23,5 @@ export const MAP_VIEWS = [ lede: "The share of people in each survey’s age band who used cannabis in the past year. Ages and years vary, and some surveys include children. Rates are not age-standardised; do not compare countries directly. The stronger the blue, the higher the reported share." }, { slug: "use-against-freedom", metric: "ratio", title: "Cannabis use against cannabis freedom, by country", - lede: "The share of people who use cannabis divided by the country's index score: where use runs far ahead of what the law allows." }, + lede: "The share of people who use cannabis divided by the country's unrounded index score (undefined at zero): where use runs far ahead of what the law allows." }, { slug: "culture", metric: "culture", title: "Cannabis culture around the world", lede: "How much of each country's cannabis culture the atlas covers: the words for the plant, the customs and faiths it belongs to, and the people who shaped its laws. The stronger the blue, the more entries." },
src/lib/use-freedom-ratio.js +5 −0
@@ -0,0 +1,5 @@ +// A zero index has no finite quotient. Do not add a pseudocount to the score. +export function useFreedomRatio(prevalence, score) { + if (!Number.isFinite(prevalence) || !Number.isFinite(score) || score <= 0) return null; + return +(prevalence / score).toFixed(2); +}