Cannabis Freedom Index

Changelog 037d263

Fix survey-age use labels and qualify Australian sentinel prices

Preserve compact table and profile summaries; put age, source and sampling details in tooltips. Retain the Australian IDRS observation while excluding it from country price comparisons. Add export metadata and regression coverage. Refs #183, #184.

20 files of code, design or docs +252 −60 lines

Code, design and documents 20 files

datasets/datasets.yaml +8 −6
@@ -46,9 +46,10 @@
   when: national surveys from 1995 to 2024
   about: >-
-    The share of adults who used cannabis in the past year, from each country's most recent
+    The share of people in the survey age band who used cannabis in the past year, from each country's most recent
     national survey as reported to UNODC, and the number of people that share stands for today.
     The count uses a population of the same age band as the survey. Most surveys ask people aged
     15 to 64; where a survey used another age band, the band is given with the figure. Every
-    country and territory on the site has a row.
+    country and territory on the site has a row. Ages and survey years vary, and some surveys
+    include children. Rates are not age-standardised; do not compare countries directly.
   origin: >-
     Rebuilt on 23 October 2026 by scripts/extract-unodc.py from UNODC's spreadsheet, which is
@@ -71,9 +72,9 @@
     of 256 million cannabis users worldwide in 2024.
   profile:
-    headline: "{prevalence} of adults"
-    details: "{users} people · survey {period} · ages {ages} · {source}"
+    headline: "{prevalence} of the survey population"
+    details: "ages {ages} · survey {period} · {users} people · {source}"
   country: { column: country, by: id }
   fields:
-    prevalence: { column: prevalence, format: percent, about: "Share of adults: map, rankings, profile" }
+    prevalence: { column: prevalence, format: percent, about: "Past-year share in the reported age band; not age-standardised: map, rankings, profile" }
     users: { column: users, about: "Number of users: map, rankings, profile" }
     year: { column: year, about: "Survey year (the last year of a period), empty for estimates" }
@@ -84,4 +85,5 @@
     estimate_from: { column: estimate_from, type: text, about: "For a disputed territory with no survey: the country whose figure it takes" }
     population_15_64: { column: population_15_64, about: "Population aged 15 to 64, kept for reference" }
+    population_year: { column: population_year, about: "Year of the population used to derive user counts" }
     population_band: { column: population_band, about: "Population the user count is applied to" }
     population_ages: { column: population_ages, type: text, about: "Age band of population_band" }
@@ -89,5 +91,5 @@
   pages:
     - { label: the map of users, path: map/users/ }
-    - { label: the map of the share of adults, path: map/share-of-adults/ }
+    - { label: the map of reported cannabis use, path: map/share-of-adults/ }
     - { label: the map of use against freedom, path: map/use-against-freedom/ }
     - { label: the rankings, path: "#rankings" }

docs/DATA-MAINTENANCE.md +12 −1
@@ -76,4 +76,11 @@ remain available as price history.
 
 Use a unique id in the form `<country>-<market>-<date>`, adding a short suffix only when needed.
+A national report title does not establish a nationally representative sample. Identify the
+sampling population and geography in the note. A narrow sentinel sample, such as capital-city
+participants who regularly inject drugs, cannot serve as a national street-price comparison.
+Keep such an existing observation with `comparison: false`: it remains in source/history exports
+and the qualified profile observation list, but cannot supply a country headline, map, ranking
+or comparison value. Do not replace its numeric value without new evidence.
+
 Keep the source's own statistical description in the note: mean, median, mode, listed pharmacy
 price or derived price. If a package or ounce price is converted to one gram, say so in the note.
@@ -358,5 +365,5 @@ group) and are never part of the score.
 - To update a dataset, replace its CSV with the new edition (and the file name and
   citation in `datasets.yaml` if they change).
-- `unodc-cannabis-use.csv`: cannabis use for every country (share of adults, survey year or
+- `unodc-cannabis-use.csv`: cannabis use for every country (share in the survey age band, survey year or
   period, age band, number of users). Rebuilt by `python3 scripts/extract-unodc.py use` from
   the UNODC spreadsheet kept beside it (`unodc-wdr2026-1.2-prevalence-national.xlsx`, World
@@ -368,4 +375,8 @@ group) and are never part of the score.
   UNODC figures) or `estimate`. It feeds the Use views of the map, the rankings and the profiles, so every country file needs a
   row; the build stops if one is missing. A new country gets one when the script runs again.
+  Survey ages and years vary; some include children. Label rates with their actual age band,
+  never unconditionally as adults. Rates are not age-standardised: do not compare countries
+  directly or infer an 18+ rate without measured age-specific data. Keep original ages, periods
+  and source labels in exports alongside the population denominator used for user counts.
 - `unodc-cannabis-use-youth.csv`: past-year cannabis use among 15- and 16-year-olds, from
   `python3 scripts/extract-unodc.py youth` and the UNODC spreadsheet beside it

docs/research/issues-183-184-survey-and-price-scope-2026-10-26.md +70 −0
@@ -0,0 +1,70 @@
+# Survey ages and Australian price sampling — issues 183–184
+
+## Evidence
+
+The unchanged UNODC extract in `datasets/unodc-cannabis-use.csv` reports these
+latest age bands: Bangladesh 7+ (3.1%, 2018), India 10–75 (2.83%, 2018),
+Australia 14+ (11.5%, 2022/23), Colombia 12–65 (2.68%, 2019). Those are the
+survey denominators, not measured 18+ prevalence. No rates, counts, ages, periods
+or source codes were recalculated. The original Bangladesh report URL in #183
+returned 404; no claim is made to reading that report. This correction follows
+the age fields already preserved in the site's UNODC extract.
+
+Read the complete substantive UNSW NDARC 2026 IDRS release, including methodology,
+representativeness warning and cannabis section. It explicitly describes people
+who regularly inject illicit drugs recruited in every Australian capital city,
+and warns against general-population interpretation. It reports A$20 per gram
+for hydroponic and bush cannabis separately. The observation's A$20 and converted
+US$13.88 remain unchanged; neither number becomes a representative national price.
+https://www.unsw.edu.au/research/ndarc/resources/australian-drug-trends-2026-key-findings-from-the-idrs
+
+## Changes
+
+Use metadata, map text, chart labels and accessible names now describe the source
+age band. Chart hover cards retain the original survey period and show ages.
+Country percentile bars and raw cross-country comparison deltas are suppressed
+for the mixed-age cannabis-use dataset. Comparison limits are available beside
+the relevant charts and in map/dataset metadata and tooltips. The legacy
+`share-of-adults` URL remains available to preserve existing links.
+
+Following the user's screenshots, ranking cells remain numbers only. Their
+existing title tooltips contain age, period, source and the comparison caveat.
+The country summary has a short past-year-share label and source line; detailed
+calculation/source information and the caveat remain in the info tooltip.
+The dataset card's caveat also lives in a source tooltip. Original survey ages,
+period/source, population age band/year/denominator and an explicit lack of age
+standardisation are now included in score CSV/JSON exports.
+
+The Australian IDRS observation is retained with `comparison: false` and an
+explicit sample description. A shared selector excludes ineligible observations
+from country price headlines, maps, ranking columns and compare API data, while
+retaining them in history and JSON/CSV price exports. With the optional prices
+interface enabled, its qualified A$20 observation is listed separately and its
+full sample description is in a source tooltip. The flag does not imply that
+all other observations are representative probability surveys: existing sources
+retain their own statistical definitions and notes.
+
+Read the maintenance and writing guidance; added maintenance rules distinguishing
+a national report title from a representative sample, explaining comparison
+eligibility and prohibiting invented 18+ prevalence.
+
+## Validation
+
+All 90 tests pass: 11 chart/data-semantics regressions, 40 history tests and 39
+answer/profile tests. The default production build passed; the final build with
+`PUBLIC_SHOW_PRICES=true` passed (4,327 pages). Isolated output directories avoid
+collisions with concurrent history builds. Extra generated build copies were
+removed after a disk-space failure, then the final full build succeeded.
+
+Rendered checks verify all four age-band examples, unchanged prevalence/user
+counts, population-year metadata, chart tooltip ages and original periods,
+qualified Australian observation retention, and omission of its street price
+from map/ranking/compare/API data. Browser checks verify compact ranking cells,
+the Northern Mariana Islands summary and source tooltip, Bangladesh ages 7+
+in its tooltip, and Australia’s separate observation and sampling tooltip.
+Raw prevalence CSV is byte-for-byte unchanged; no scores changed.
+
+Regression tests cover child-inclusive
+labels, compact cells and tooltips, preserved A$20 value, ineligible latest-price
+selection (including fallback to an older eligible observation), and removal of
+survey percentiles/deltas. Existing history and answer/profile suites also run.

prices.yaml +6 −1
@@ -14,5 +14,10 @@
   price: 20
   usd: 13.88
-  note: National median for one gram of non-prescribed hydroponic and bush cannabis.
+  comparison: false
+  note: >-
+    Median of A$20 per gram for both non-prescribed hydroponic and bush cannabis, reported by
+    the 2026 national IDRS sentinel sample of people who regularly inject illicit drugs, recruited
+    across Australian capital cities. Not a representative national street price; excluded from
+    country price comparisons.
   source:
     title: UNSW National Drug and Alcohol Research Centre — Australian Drug Trends 2026

scripts/chart-semantics.test.cjs +66 −0
@@ -95,2 +95,68 @@ test('police comparisons are within country and treatment stocks are excluded (#
   assert.match(page, /Croatia is excluded/);
 });
+
+test('survey labels preserve child-inclusive age bands without adult claims (#183)', async () => {
+  const { useAges, useShareLabel, USE_COMPARISON_NOTE } = await import('../src/lib/use-labels.js');
+  const text = fs.readFileSync('datasets/unodc-cannabis-use.csv', 'utf8').trim().split(/\r?\n/);
+  const header = text.shift().split(',');
+  const rows = text.map((line) => Object.fromEntries(line.split(',').map((v, i) => [header[i], v])));
+  for (const [country, ages, prevalence] of [['bd', '7+', '3.1'], ['in', '10–75', '2.83'], ['au', '14+', '11.5'], ['co', '12–65', '2.68']]) {
+    const row = rows.filter((r) => r.country === country).at(-1);
+    assert.equal(useAges(row), ages);
+    assert.equal(row.prevalence, prevalence);
+    assert.equal(useShareLabel(row), `${prevalence}% of people aged ${ages}`);
+    assert.doesNotMatch(useShareLabel(row), /adult/i);
+  }
+  assert.match(useAges({}), /assumed/);
+  assert.match(USE_COMPARISON_NOTE, /not age-standardised/);
+  const defs = yaml.load(fs.readFileSync('datasets/datasets.yaml', 'utf8'));
+  const use = defs.find((d) => d.id === 'cannabis_use');
+  assert.doesNotMatch(use.profile.headline + use.about + use.fields.prevalence.about, /of adults/i);
+  const charts = fs.readFileSync('src/pages/charts.astro', 'utf8');
+  assert.doesNotMatch(charts, /share of adults who used cannabis|Past-year use among adults|Share of adults<|Adults who use cannabis/);
+  assert.match(charts, /useAges\(c.use\)/);
+  assert.match(charts, /USE_COMPARISON_NOTE/);
+});
+
+test('compact prevalence cells and profiles keep age and sampling detail in tooltips (#183)', () => {
+  const home = fs.readFileSync('src/components/HomePage.astro', 'utf8');
+  const share = home.match(/<td[^>]*data-col="share"[^]*?<\/td>/)[0];
+  assert.match(share, /title=\{useNote\(c\)\}/);
+  assert.doesNotMatch(share, /<small>|useAges/);
+  assert.match(home, /useShareLabel\(c.use\)/);
+  assert.match(home, /useDetail\(c\).*USE_COMPARISON_NOTE/);
+  const profile = fs.readFileSync('src/components/CountryArticle.astro', 'utf8');
+  assert.match(profile, /<dt>Past-year share<\/dt>/);
+  assert.match(profile, /<SourceTip><span>Past-year cannabis use among people aged \{useAges\(use\)\}/);
+  assert.doesNotMatch(profile, /<p>\{USE_COMPARISON_NOTE\}<\/p>/);
+});
+
+test('sentinel observations stay in history but cannot replace country comparison prices (#184)', async () => {
+  const { latestComparablePrices } = await import('../src/lib/price-selection.js');
+  const observations = yaml.load(fs.readFileSync('prices.yaml', 'utf8'));
+  const australian = observations.filter((p) => p.country === 'au' && p.market === 'street');
+  const idrs = australian.find((p) => p.id === 'au-street-2026');
+  assert.equal(idrs.price, 20);
+  assert.equal(idrs.usd, 13.88);
+  assert.equal(idrs.comparison, false);
+  assert.match(idrs.note, /sentinel sample.*people who regularly inject illicit drugs/s);
+  assert.match(idrs.note, /capital cities.*Not a representative national street price/s);
+  assert.equal(latestComparablePrices(australian).street, undefined);
+  const older = { market: 'street', date: '2025', usd: 10 };
+  const medical = { market: 'medical', usd: 9 };
+  const sentinel = { market: 'street', date: '2026', usd: 20, comparison: false };
+  assert.deepEqual(latestComparablePrices([sentinel, medical, older]), { medical, street: older });
+  assert.deepEqual(latestComparablePrices([sentinel]), {});
+  assert.equal(australian.length, 1); // retained, rather than deleted or given a made-up replacement
+  const loader = fs.readFileSync('src/lib/prices.js', 'utf8');
+  assert.match(loader, /return latestComparablePrices\(priceHistoryFor\(id\)\)/);
+  assert.match(fs.readFileSync('src/lib/exports.js', 'utf8'), /comparison: p.comparison/);
+});
+
+test('raw survey rates do not acquire country percentiles or comparison deltas (#183)', () => {
+  const text = fs.readFileSync('src/lib/dataset-text.js', 'utf8');
+  assert.match(text, /if \(d.id === "cannabis_use"\) return null/);
+  const compare = fs.readFileSync('src/pages/compare.astro', 'utf8');
+  assert.match(compare, /id !== "cannabis_use" && k > 0 && first \? diff/);
+  assert.match(compare, /META.useComparisonNote/);
+});

src/components/CountryArticle.astro +5 −4
@@ -12,4 +12,5 @@ import CountryDatasets from "./CountryDatasets.astro";
 import CountryDatasetTab from "./CountryDatasetTab.astro";
 import { datasetsOf } from "../lib/datasets.js";
+import { USE_COMPARISON_NOTE } from "../lib/use-labels.js";
 import SourceTip from "./SourceTip.astro";
 import SourceCitation from "./SourceCitation.astro";
@@ -186,13 +187,13 @@ const culture = cultureOf(c);
         )}
         <div>
-          <dt>Share aged {useAges(use)}</dt>
+          <dt>Past-year share</dt>
           <dd>{use.estimate ? "about " : ""}{use.prevalence}%</dd>
         </div>
-        <p>{useFrom
+        <p>{use.estimate ? "This site’s estimate" : `UNODC · survey ${use.period ?? use.year}`}</p>
+        <SourceTip><span>Past-year cannabis use among people aged {useAges(use)}. </span>{useFrom
           ? `This site's figure: there is no survey of ${c.name} alone, so ${useFrom.name}'s rate from its ${useFrom.use.period ?? useFrom.use.year} survey is applied to an estimate of the people aged 15–64 here.`
           : use.estimate
           ? `This site's figure: there is no national survey, so the median rate of surveyed countries in ${c.region} is applied to people aged 15–64.`
-          : `People who used cannabis in the past year, from a ${use.period ?? use.year} survey${use.users != null ? `, applied to today's population aged ${useAges({ ages: use.population_ages || use.ages })}` : ""}${use.users_approximate ? ". The population is approximate: the survey ages cut through an age group, or were not recorded and ages 15–64 are used" : ""}${use.source === "earlier" ? ". An earlier figure: UNODC's 2026 table has none for this country" : ""}.`}</p>
-        <SourceTip>{use.source === "wdr2026"
+          : `People who used cannabis in the past year, from a ${use.period ?? use.year} survey${use.users != null ? `, applied to today's population aged ${useAges({ ages: use.population_ages || use.ages })}` : ""}${use.users_approximate ? ". The population is approximate: the survey ages cut through an age group, or were not recorded and ages 15–64 are used" : ""}${use.source === "earlier" ? ". An earlier figure: UNODC's 2026 table has none for this country" : ""}.`}<br />{USE_COMPARISON_NOTE}<br />{use.source === "wdr2026"
           ? <><a href="https://www.unodc.org/unodc/en/data-and-analysis/world-drug-report-2026-annex.html" target="_blank" rel="noopener noreferrer">UNODC World Drug Report 2026</a>, annex table 1.2</>
           : use.source === "earlier"

src/components/CountryDatasetTab.astro +5 −3
@@ -5,4 +5,6 @@
 // datasets.yaml; the figure over the years; where the country stands among every country the
 // dataset covers; and links to its map and its entry on the sources page.
+import SourceTip from "./SourceTip.astro";
+import { USE_COMPARISON_NOTE } from "../lib/use-labels.js";
 import { DATASETS, datasetsOf } from "../lib/datasets.js";
 import { viewByMetric } from "../lib/map-views.js";
@@ -48,5 +50,5 @@ const rows = Object.values(DATASETS).filter((d) => own[d.id]).map((d) => {
   // a series also lists a few earlier years, oldest first
   const earlier = series ? [...(d.profile.years ?? [])].filter((y) => y < row.year).sort((a, b) => a - b).map((y) => series.find((r) => r.year === y)).filter(Boolean).map((r) => fill(d, r, d.profile.each ?? `{${Object.keys(d.fields)[0]}} in {year}`)) : [];
-  return { d, head: headline(d, row), year: row?.year, details: [fill(d, row, d.profile.details), ...earlier].filter(Boolean).join(" · "), note: d.notes[c.slug], chart: series && trend(d, series), stand: standing(d) };
+  return { d, head: headline(d, row), year: row?.year, details: [fill(d, row, d.profile.details), ...earlier].filter(Boolean).join(" · "), note: d.id === "cannabis_use" ? USE_COMPARISON_NOTE : d.notes[c.slug], chart: series && trend(d, series), stand: standing(d) };
 });
 const groups = GROUPS.map((name) => ({ name, rows: rows.filter((r) => groupOf(r.d) === name) })).filter((g) => g.rows.length);
@@ -67,5 +69,5 @@ const groups = GROUPS.map((name) => ({ name, rows: rows.filter((r) => groupOf(r.
           {g.rows.map(({ d, head, year, details, note, chart, stand }) => (
             <li class:list={["ds-card", { toned: POLARITY[d.id] }]} id={`ds-${d.id}`}
-              style={stand ? (POLARITY[d.id] ? `--ds:${toneAt(stand.at, POLARITY[d.id])};--lo:var(--t${POLARITY[d.id] > 0 ? 1 : 5});--hi:var(--t${POLARITY[d.id] > 0 ? 5 : 1})` : `--ds:var(--u${stand.step})`) : undefined}>
+              style={stand ? (POLARITY[d.id] ? `--ds:${toneAt(stand.at, POLARITY[d.id])};--lo:var(--t${POLARITY[d.id] > 0 ? 1 : 5});--hi:var(--t${POLARITY[d.id] > 0 ? 5 : 1})` : `--ds:var(--u${stand.step})`) : d.id === "cannabis_use" ? "position:relative" : undefined}>
               <p class="ds-kicker"><span>{d.credit ?? d.publisher}{year != null && ` · ${year}`}</span></p>
               <h4>{d.name}</h4>
@@ -92,5 +94,5 @@ const groups = GROUPS.map((name) => ({ name, rows: rows.filter((r) => groupOf(r.
               )}
               {details && <p class="ds-details">{details}</p>}
-              {note && <p class="ds-details ds-note">{note}</p>}
+              {note && (d.id === "cannabis_use" ? <SourceTip>{note}</SourceTip> : <p class="ds-details ds-note">{note}</p>)}
               <p class="ds-links">
                 {mapHref(d) && <a href={mapHref(d)}>See the map <span aria-hidden="true">→</span></a>}

src/components/HomePage.astro +9 −7
@@ -8,4 +8,5 @@ import Tools from "./Tools.astro";
 import { survey } from "../lib/survey.js";
 import AverageChart from "../components/AverageChart.astro";
+import { useShareLabel, USE_COMPARISON_NOTE } from "../lib/use-labels.js";
 import { useAges, SEGMENTS, DATA_SNAPSHOT, REC_LABEL, REC_ORDER, REC_HELP, MED_LABEL, MED_HELP, countries, regions, worldAvg, lastUpdated, formatDate, subunitsOf, places, trendOf, changeDate, TREND_YEARS, estimateNote } from "../lib/countries.js";
 import { TIERS, tierColor, tierOf } from "../lib/tiers.js";
@@ -62,5 +63,5 @@ const count = (fn) => countries.filter(fn).length;
 // 48,000,000 → "48M", 770,000 → "770k"
 const compact = (n) => (n >= 1e6 ? `${+(n / 1e6).toPrecision(2)}M` : n >= 1e3 ? `${+(n / 1e3).toPrecision(2)}k` : String(n));
-const useNote = (c) => {
+const useDetail = (c) => {
   const from = c.use.estimate_from && places.find((x) => x.id === c.use.estimate_from.toUpperCase());
   if (from) return `This site's figure: no survey of ${c.name} alone, so ${from.name}'s rate, ${c.use.prevalence}% of people aged 15–64, is applied to an estimate of the people here of those ages.`;
@@ -69,6 +70,7 @@ const useNote = (c) => {
   if (c.use.users == null) return `${c.use.prevalence}% of people aged ${ages} (${when}). No user count: the population for that age band is missing.`;
   if (c.use.users_approximate) return `About ${compact(c.use.users)}: ${c.use.prevalence}% aged ${ages} (${when}), on an approximate population of those ages.`;
-  return `${c.use.prevalence}% of people aged ${ages} used cannabis in the past year (${when} survey)`;
+  return `${useShareLabel(c.use)} used cannabis in the past year (${when} survey)`;
 };
+const useNote = (c) => `${useDetail(c)}. Source: ${c.use.source === "wdr2026" ? "UNODC World Drug Report 2026" : c.use.source === "earlier" ? "earlier UNODC figure" : "this site’s estimate"}. ${USE_COMPARISON_NOTE}`;
 const stats = [
   [places.length, "territories scored"],
@@ -109,5 +111,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: "Share of adults who use cannabis, divided by the index score. A high value means many people use cannabis where the law gives them little freedom to.",
+    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.",
   },
   culture: {
@@ -148,5 +150,5 @@ const datasetViews = (group) => MAPPED.filter((ds) => ds.map.group === group).ma
 const MAP_GROUPS = [
   { name: "Freedom", views: [["index", "Overall index"], ...SEGMENTS.map((s, i) => [String(i), s.name])] },
-  { name: "Use", views: [["users", "Number of users"], ["prevalence", "Share of adults"], ["ratio", "Share of users ÷ index"], ...datasetViews("Use")] },
+  { name: "Use", views: [["users", "Number of users"], ["prevalence", "Survey share"], ["ratio", "Share of users ÷ index"], ...datasetViews("Use")] },
   { name: "Law", views: [...Object.entries(LAW_SECTIONS).map(([key, section]) => [`status:${key}`, section.name]), ...datasetViews("Law")] },
   { name: "Society", views: [...datasetViews("Society"), ["culture", "Atlas entries"]] },
@@ -167,5 +169,5 @@ const statusCol = (key, label, vocab, helpIndex) => ({
 });
 const BASE_COLUMNS = [
-  { key: "region", label: "Region" }, { key: "users", label: "Users" }, { key: "share", label: "Share of adults" },
+  { key: "region", label: "Region" }, { key: "users", label: "Users" }, { key: "share", label: "Survey share" },
   ...(SHOW_PRICES ? [{ key: "price", label: "Price per gram" }] : []),
   { key: "rec", label: "Recreational" }, { key: "med", label: "Medical" },
@@ -303,5 +305,5 @@ const columnGroups = [["Shown by default", BASE_COLUMNS], ...["Score segments",
             {scale.steps.map(([, text], i) => <span><i class="sw" style={`background:var(--u${i})`}></i>{text}</span>)}
             <span><i class="sw sw-nodata"></i>No data</span>
-            <span class="legend-note">{scale.note ?? "Adults who used cannabis in the past year, from each country's latest survey (most ask ages 15–64). A ~ is this site's estimate where a place has no survey of its own: the median of surveyed countries in the region, or for a disputed territory the rate of the country it is part of."}</span>
+            <span class="legend-note">{scale.note ?? "People who used cannabis in the past year, within each survey’s age band. Ages and years vary; rates are not age-standardised. Do not compare countries directly. A ~ is this site's estimate where a place has no survey of its own: the median of surveyed countries in the region, or for a disputed territory the rate of the country it is part of."}</span>
           </div>
         ))}
@@ -382,5 +384,5 @@ const columnGroups = [["Shown by default", BASE_COLUMNS], ...["Score segments",
               <th data-sort="region" data-col="region" class="hide-sm">Region</th>
               <th data-sort="users" data-col="users" class="num-col hide-md" title="People who used cannabis in the past year">Users</th>
-              <th data-sort="share" data-col="share" class="num-col hide-md" title="Share of adults who used cannabis in the past year, mostly aged 15–64">Share</th>
+              <th data-sort="share" data-col="share" class="num-col hide-md" title="Past-year use in each survey’s age band. Ages and years vary; not age-standardised.">Share</th>
               {SHOW_PRICES && <th data-sort="price" data-col="price" class="num-col hide-md" title="Latest sourced street-flower price, normalised to US dollars per gram">Price/g</th>}
               <th data-sort="rec" data-col="rec" class="hide-sm">Recreational</th>

src/components/PricePanel.astro +10 −4
@@ -2,4 +2,5 @@
 // What a gram of flower costs, in the same box style as the cannabis-users figures.
 // Shown only when at least one sourced price exists for the country.
+import SourceTip from "./SourceTip.astro";
 import { PRICE_MARKETS, priceHistoryFor, latestPricesFor, formatPrice } from "../lib/prices.js";
 
@@ -7,5 +8,6 @@ const { id } = Astro.props;
 const history = priceHistoryFor(id);
 const latest = latestPricesFor(id);
-const older = history.filter((p) => latest[p.market] !== p);
+const older = history.filter((p) => p.comparison && latest[p.market] !== p);
+const excluded = history.filter((p) => !p.comparison);
 const LABEL = { street: "Street price", medical: "Medical price" };
 const year = (p) => p.date.slice(0, 4);
@@ -26,5 +28,5 @@ const shown = PRICE_MARKETS.filter((m) => latest[m]);
       );
     })}
-    <p>One gram of flower, the latest figure found, converted to US dollars.</p>
+    <p>One gram of flower, the latest figure eligible for country comparisons, converted to US dollars.</p>
     {older.length > 0 && (
       <details class="c-price-older">
@@ -41,7 +43,11 @@ const shown = PRICE_MARKETS.filter((m) => latest[m]);
       </details>
     )}
-    <p class="c-use-src">Source: {shown.map((m, i) => (
+    {excluded.length > 0 && <div class="c-price-excluded" style="grid-column:1 / -1">
+      <p>Other observations · excluded from comparisons</p>
+      <ul>{excluded.map((p) => <li style="position:relative"><b>{formatPrice(p)}</b> · {year(p)} · {p.market}<SourceTip>{p.note} <a href={p.source.url} target="_blank" rel="noopener noreferrer">{p.source.title}</a></SourceTip></li>)}</ul>
+    </div>}
+    {shown.length > 0 && <p class="c-use-src">Source: {shown.map((m, i) => (
       <Fragment>{i > 0 && "; "}<a href={latest[m].source.url} target="_blank" rel="noopener noreferrer">{latest[m].source.title}</a>{shown.length > 1 && ` (${m})`}</Fragment>
-    ))}.</p>
+    ))}.</p>}
   </dl>
 )}

src/content.config.ts +1 −0
@@ -222,4 +222,5 @@ const prices = defineCollection({
       usd: z.number().positive(),   // same observation normalised to USD per gram
       note: z.string().min(1).optional(),
+      comparison: z.boolean().default(true), // false retains the observation but excludes country price comparisons
       source: webSource,
     })

src/lib/countries.js +1 −1
@@ -54,5 +54,5 @@ const scoreOf = (s) => s.reduce((a, v, i) => a + segmentPoints(i, v), 0);
 const scoreList = (scores) => SEGMENTS.map((seg) => scores[seg.id]);
 // the age band a cannabis-use figure is for: the survey's own, or 15–64 where none is given
-export const useAges = (use) => (use.ages ? use.ages.replace("-", "–") : "15–64");
+export { useAges } from "./use-labels.js";
 // cannabis use comes from its dataset, which must have a row for every place. A place cannot have
 // more people of an age band, or more users, than people: Northern Cyprus and Transnistria once

src/lib/dataset-text.js +3 −0
@@ -4,4 +4,5 @@
 // Datasets tab (CountryDatasetTab.astro) and the compare page's data (src/pages/api/[id].json.js).
 import { places } from "./countries.js";
+import { useAges } from "./use-labels.js";
 
 const num = (n) => n.toLocaleString("en-GB");
@@ -28,4 +29,5 @@ export const fill = (c, d, row, template = "") => template.split(" · ").map((pa
 // the headline's first figure, with any sign before it ("€"), is set large: [figure, the words after it]
 export const headline = (c, d, row) => {
+  if (d.id === "cannabis_use") return [figure(c, d, row, "prevalence"), `of people aged ${useAges(row)}`];
   const m = d.profile.headline.match(/^([^{]*)\{(\w+)\}(.*)$/);
   return m ? [m[1] + (figure(c, d, row, m[2]) ?? ""), fill(c, d, row, m[3])] : ["", fill(c, d, row, d.profile.headline)];
@@ -66,4 +68,5 @@ export const groupOf = (d) => GROUP_OF[d.id] ?? d.map?.group ?? "Context";
 // latest year of a series): the share of them with a lower figure. Null for text and ranks.
 export const standing = (c, d) => {
+  if (d.id === "cannabis_use") return null; // differing survey ages cannot support a country percentile
   const name = headField(d), f = d.fields[name];
   if (!name || (f && (f.type === "text" || f.format === "rank"))) return null;

src/lib/exports.js +8 −0
@@ -32,4 +32,11 @@ export const scoreRows = places.map((c) => ({
   users: c.use.users,
   users_share_percent: c.use.prevalence,
+  users_survey_ages: c.use.ages ?? "",
+  users_survey_period: c.use.period ?? c.use.year ?? "",
+  users_source: c.use.source,
+  users_age_standardised: false,
+  users_population_ages: c.use.population_ages ?? "",
+  users_population_band: c.use.population_band ?? "",
+  users_population_year: c.use.population_year ?? "",
   users_estimated: Boolean(c.use.estimate),
   reviewed: c.details.reviewed,
@@ -119,4 +126,5 @@ export const priceRows = priceObservations.map((p) => ({
   currency: p.currency,
   usd_per_gram: p.usd,
+  comparison: p.comparison,
   note: p.note ?? "",
   source: p.source.title,

src/lib/map-views.js +2 −2
@@ -20,6 +20,6 @@ export const MAP_VIEWS = [
   { slug: "users", metric: "users", title: "Cannabis users by country",
     lede: "How many people used cannabis in the past year. Each survey rate is applied to a population of the same age band. The stronger the blue, the more users." },
-  { slug: "share-of-adults", metric: "prevalence", title: "Share of adults who use cannabis, by country",
-    lede: "The share of adults who used cannabis in the past year, from each country's latest national survey (most ask people aged 15 to 64). The stronger the blue, the higher the share." },
+  { slug: "share-of-adults", metric: "prevalence", title: "Past-year cannabis use, by country",
+    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." },

src/lib/price-selection.js +10 −0
@@ -0,0 +1,10 @@
+// Keep all observations in history, but only eligible observations can colour a country or
+// supply its price headline. The caller supplies history newest first.
+export function latestComparablePrices(history) {
+  const latest = {};
+  for (const price of history) {
+    if (price.comparison === false) continue;
+    latest[price.market] ??= price;
+  }
+  return latest;
+}

src/lib/prices.js +2 −5
@@ -1,3 +1,4 @@
 // Loads append-only flower-price observations and derives each country's latest market price.
+import { latestComparablePrices } from "./price-selection.js";
 import { getCollection } from "astro:content";
 import { places, dateTime } from "./countries.js";
@@ -28,9 +29,5 @@ export function priceHistoryFor(id) {
 
 export function latestPricesFor(id) {
-  const latest = {};
-  for (const p of priceHistoryFor(id)) {
-    latest[p.market] ??= p;
-  }
-  return latest;
+  return latestComparablePrices(priceHistoryFor(id));
 }
 

src/lib/use-labels.js +4 −0
@@ -0,0 +1,4 @@
+// A prevalence rate belongs to its source age band; it is not automatically an adult rate.
+export const useAges = (use) => use.ages ? use.ages.replaceAll("-", "–") : "15–64 (assumed)";
+export const USE_COMPARISON_NOTE = "Survey ages and years vary, and some surveys include children. Rates are not age-standardised; do not compare countries directly.";
+export const useShareLabel = (use) => `${use.prevalence}% of people aged ${useAges(use)}`;

src/pages/charts.astro +22 −22
@@ -6,4 +6,5 @@
 // a dataset to its map. A small script at the end adds hover cards with the detail behind a mark.
 import { firstRestrictionSample, HISTORY_TOPICS, topicCounts, DEVELOPMENT_MEASURES, matchedPriceWage, matchedThcPanel, policeTrend, annualTreatment } from "../lib/chart-semantics.js";
+import { useAges, useShareLabel, USE_COMPARISON_NOTE } from "../lib/use-labels.js";
 import Base from "../layouts/Base.astro";
 import AverageChart from "../components/AverageChart.astro";
@@ -144,5 +145,4 @@ const U1 = Math.ceil(Math.max(...used.map((c) => c.use.prevalence)) / 5) * 5;
 const uOf = (v) => 100 - pct(v, 0, U1);
 const useTicks = Array.from({ length: U1 / 5 + 1 }, (_, i) => i * 5);
-const useLow = useBands.find((b) => b.t.min === 5) ?? useBands[0], useHigh = useBands.at(-1);
 // spread each band's dots sideways so they don't hide each other: a small swarm around the column's centre
 for (const b of useBands) {
@@ -461,5 +461,4 @@ function c_usable(c) { return c.use && !c.use.estimate && Number.isFinite(c.use.
 const capFit = corr(capacity);
 const CY1 = Math.ceil(Math.max(...capacity.map((d) => d.y)) / 5) * 5;
-const strongStrict = capacity.filter((d) => d.x >= 70 && d.c.raw < 15), strongMild = capacity.filter((d) => d.x >= 70 && d.c.raw >= 25);
 const capNamed = pickLabels([...capacity].sort((a, b) => b.y - a.y).map((d) => ({ key: d.c.id, x: d.x, y: 100 - pct(d.y, 0, CY1) })), 14);
 
@@ -638,5 +637,5 @@ const MEASURES = [
   ...DEVELOPMENT_MEASURES.map(({ label, href, field }) => ["Wealth and development", label, href, (c) => dev(c, field)]),
   ["Wealth and development", "Research papers per person (log)", "#research", (c) => { const r = research.find((x) => x.c === c); return r ? Math.log10(r.per) : null; }],
-  ["Use and drugs", "Adults who use cannabis", "#use", (c) => (c.use && !c.use.estimate ? c.use.prevalence : null)],
+  ["Use and drugs", "Reported cannabis use (ages vary)", "#use", (c) => (c.use && !c.use.estimate ? c.use.prevalence : null)],
   ["Use and drugs", "Teenagers who use cannabis (past year)", "map/youth-cannabis-use/", (c) => latestSince(c.series.youth_use, "past_year", 2010)?.past_year],
   ["Use and drugs", "Students who have tried it (Europe)", "#teen-trends", (c) => latestSince(c.series.espad_lifetime, "lifetime", 2024)?.lifetime],
@@ -776,5 +775,5 @@ const chartData = {
   law: lawGrid.map((row) => row.map((g) => g.map((c) => c.flag + " " + c.name))),
   lawNames: { rec: REC_ORDER.map((k) => REC_LABEL[k]), med: MED_ORDER.map((k) => MED_LABEL[k]) },
-  use: Object.fromEntries(used.map((c) => [c.slug, [c.flag + " " + c.name, c.use.prevalence, c.use.year ?? null, Math.round(c.raw), tierOf(c.raw).name]])),
+  use: Object.fromEntries(used.map((c) => [c.slug, [c.flag + " " + c.name, c.use.prevalence, c.use.period ?? c.use.year ?? null, Math.round(c.raw), tierOf(c.raw).name, useAges(c.use)]])),
   stripes: { from: S0, rows: stripes.map((s) => [s.c.slug, s.c.flag + " " + s.c.name, s.steps, s.banned]) },
   ridges: ridges.map((r) => [r.year, +r.mean.toFixed(1), r.n100]),
@@ -799,5 +798,5 @@ const chartData = {
   work: Object.fromEntries(workGram.map((w) => [w.c.slug, [w.c.flag + " " + w.c.name, Math.round(w.minutes), w.price, w.priceYear, w.wage, w.wageYear, Math.round(w.c.raw)]])),
   drink: Object.fromEntries(drink.map((d) => [d.c.slug, [d.c.flag + " " + d.c.name, d.x, d.r.year, Math.round(d.y), tierOf(d.y).name]])),
-  capacity: Object.fromEntries(capacity.map((d) => [d.c.slug, [d.c.flag + " " + d.c.name, Math.round(d.x), d.y, d.c.use.year ?? null, Math.round(d.c.raw), tierOf(d.c.raw).name]])),
+  capacity: Object.fromEntries(capacity.map((d) => [d.c.slug, [d.c.flag + " " + d.c.name, Math.round(d.x), d.y, d.c.use.period ?? d.c.use.year ?? null, Math.round(d.c.raw), tierOf(d.c.raw).name, useAges(d.c.use)]])),
   teen: teen.map((t) => [t.c.slug, t.c.flag + " " + t.c.name, t.pts]),
   teenAvg: teenAvg.map(([y, v]) => [y, +v.toFixed(1)]),
@@ -805,5 +804,5 @@ const chartData = {
   mosaic: mosaic.map((c) => [c.slug, `${c.flag} ${c.name}`, Math.round(c.raw), c.s, c.details.segments.map((x) => x.t.length > 170 ? x.t.slice(0, 168).replace(/\s\S*$/, "") + " …" : x.t)]),
   segNames: SEGMENTS.map((x) => x.name),
-  tiles: Object.fromEntries(userList.map((u) => [u.c.slug, [u.c.flag + " " + u.c.name, u.v, u.c.use.prevalence, Math.round(u.c.raw), tierOf(u.c.raw).name]])),
+  tiles: Object.fromEntries(userList.map((u) => [u.c.slug, [u.c.flag + " " + u.c.name, u.v, u.c.use.prevalence, Math.round(u.c.raw), tierOf(u.c.raw).name, useAges(u.c.use), u.c.use.period ?? u.c.use.year ?? null]])),
   graft: Object.fromEntries(graft.map((d) => [d.c.slug, [d.c.flag + " " + d.c.name, d.x, d.r.year, Math.round(d.y), tierOf(d.y).name]])),
   faith: Object.fromEntries(faiths.flatMap((f) => f.list.map((c) => [c.slug, [c.flag + " " + c.name, f.label, Math.round(c.raw), tierOf(c.raw).name]]))),
@@ -936,5 +935,5 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
       <p class="ch-kicker"><a href="#users-treemap">{kick("users-treemap")}</a></p>
       <h2>{usersShareBanned}% of about {Math.round(usersTotal / 1e6)} million people who use cannabis, counted country by country here, live where it is under prohibition</h2>
-      <p class="ch-dek">Each rectangle is a country, sized by how many adults used cannabis in the past year and coloured by its band. Prohibition does not keep the users away; it decides what happens to them.</p>
+      <p class="ch-dek">Each rectangle is a country, sized by how many people in the survey age band used cannabis in the past year and coloured by its band. Prohibition does not keep the users away; it decides what happens to them.</p>
       <div class="ch-frame ch-treemap" data-no-preview>
         {tiles.map((t) => (
@@ -945,6 +944,6 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
         ))}
       </div>
-      <p class="ch-source">Each country's latest survey rate, applied to a World Bank population of the same age band. Where that band cuts through an age group, or the survey age was not recorded, the count is approximate; the second case uses ages 15–64. Countries with no survey take this site's median of surveyed countries in the same region. These country figures add up to about {Math.round(usersTotal / 1e6)} million people. The <a href="https://data.unodc.org/wdr2026search">UNODC World Drug Report 2026</a> puts the world total at 256 million cannabis users in 2024. The share above is of this site's sum. {userList.length} countries. <a href={`${base}sources/#dataset-cannabis_use`}>About the dataset</a>.</p>
-      <p class="ch-more"><a href={`${base}map/users/`}>Users on the map →</a> <a href={`${base}map/share-of-adults/`}>Share of adults →</a></p>
+      <p class="ch-source">Each country's latest survey rate, applied to a World Bank population of the same age band. {USE_COMPARISON_NOTE} Where that band cuts through an age group, or the survey age was not recorded, the count is approximate; the second case uses ages 15–64. Countries with no survey take this site's median of surveyed countries in the same region. These country figures add up to about {Math.round(usersTotal / 1e6)} million people. The <a href="https://data.unodc.org/wdr2026search">UNODC World Drug Report 2026</a> puts the world total at 256 million cannabis users in 2024. The share above is of this site's sum. {userList.length} countries. <a href={`${base}sources/#dataset-cannabis_use`}>About the dataset</a>.</p>
+      <p class="ch-more"><a href={`${base}map/users/`}>Users on the map →</a> <a href={`${base}map/share-of-adults/`}>Reported use →</a></p>
     </section>
     {chapterAt.has("law") && <h2 class="ch-chapter">{chapterAt.get("law")}</h2>}
@@ -1353,6 +1352,6 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
     <section class="ch" id="use">
       <p class="ch-kicker"><a href="#use">{kick("use")}</a></p>
-      <h2>Where the law is milder, more people say they use cannabis: {fmt1(useHigh.median)}% against {fmt1(useLow.median)}%</h2>
-      <p class="ch-dek">Each dot is a country, in the column of its band, at the share of adults who used cannabis in the past year. The bar is each band's middle value. Countries often eased the law where use was already common, and use is more readily admitted where it is legal, so this shows no cause.</p>
+      <h2>Reported cannabis use by legal band</h2>
+      <p class="ch-dek">Each dot shows past-year cannabis use within that country’s survey age band. The bars are the middle reported values in each legal band. {USE_COMPARISON_NOTE} The dots and bars do not establish an effect of the law.</p>
       <div class="ch-frame ch-use" data-no-preview>
         <div class="ch-y">{useTicks.map((t) => <span style={`top:${uOf(t)}%`}>{t}%</span>)}</div>
@@ -1368,5 +1367,5 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
                 <a class="ch-dot" href={`${base}country/${p.c.slug}/#datasets`} data-use={p.c.slug}
                   style={`left:calc(${cx}% + ${p.lane} * var(--lane));top:${uOf(p.y)}%;--c:${tierColor(b.t.min)}`}
-                  aria-label={`${p.c.name}: ${p.y}% of adults used cannabis in the past year`}></a>
+                  aria-label={`${p.c.name}: ${useShareLabel(p.c.use)} used cannabis in the past year; survey ${p.c.use.period ?? p.c.use.year}`}></a>
               ))}
             </>;
@@ -1375,5 +1374,5 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
         <div class="ch-use-x">{useBands.map((b) => <span><b>{b.t.name}</b><small>{b.dots.length} countries</small></span>)}</div>
       </div>
-      <p class="ch-source">Past-year use among adults from the <a href={`${base}sources/#dataset-cannabis_use`}>UNODC</a>, the latest survey of each country; {used.length} countries with a survey, estimates left out.</p>
+      <p class="ch-source">Past-year use in each survey’s age band from the <a href={`${base}sources/#dataset-cannabis_use`}>UNODC</a>, the latest survey of each country; {used.length} countries with a survey, estimates left out.</p>
       <p class="ch-more"><a href={`${base}map/share-of-adults/`}>Use on the map →</a> <a href={`${base}map/youth-cannabis-use/`}>Use among young people →</a></p>
     </section>
@@ -1751,6 +1750,6 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
     <section class="ch" id="state-capacity">
       <p class="ch-kicker"><a href="#state-capacity">{kick("state-capacity")}</a></p>
-      <h2>{capFit.r > 0.3 ? "The more capable the state, the more cannabis is used, not less" : capFit.r < -0.3 ? "More capable states see less cannabis use" : "How capable a state is says little about how much cannabis its people use"} (r = {capFit.r.toFixed(2)})</h2>
-      <p class="ch-dek">Each dot is a country: across, the World Bank's measure of government effectiveness, 0 to 100; up, the share of adults who used cannabis in the past year; colour, its band. Even the strongest states do not keep use down: among the most effective ones, {strongStrict.length} under prohibition and {strongMild.length} with milder laws sit across the same range. Richer countries also survey more, and people there admit use more readily.</p>
+      <h2>Government effectiveness and reported cannabis use (r = {capFit.r.toFixed(2)})</h2>
+      <p class="ch-dek">Each dot is a country: across, the World Bank’s measure of government effectiveness, 0 to 100; up, past-year cannabis use within the survey’s age band; colour, its legal band. {USE_COMPARISON_NOTE} This unadjusted correlation does not establish a relationship with government effectiveness.</p>
       <div class="ch-frame ch-scatter ch-wide-y" data-no-preview>
         <div class="ch-y">{Array.from({ length: CY1 / 5 + 1 }, (_, i) => i * 5).map((t) => <span style={`top:${100 - pct(t, 0, CY1)}%`}>{t}%</span>)}</div>
@@ -1762,5 +1761,5 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
           {capacity.map((d) => (
             <a class:list={["ch-dot", { named: capNamed.has(d.c.id), left: d.x > 78 }]} href={`${base}country/${d.c.slug}/#datasets`} data-capacity={d.c.slug}
-              style={`left:${d.x}%;top:${100 - pct(d.y, 0, CY1)}%;--c:${tierColor(d.c.raw)}`} aria-label={`${d.c.name}: effectiveness ${Math.round(d.x)}, use ${d.y}%`}>
+              style={`left:${d.x}%;top:${100 - pct(d.y, 0, CY1)}%;--c:${tierColor(d.c.raw)}`} aria-label={`${d.c.name}: effectiveness ${Math.round(d.x)}; ${useShareLabel(d.c.use)}; survey ${d.c.use.period ?? d.c.use.year}`}>
               {capNamed.has(d.c.id) && <span>{d.c.name}</span>}
             </a>
@@ -1898,4 +1897,5 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
     {chapterAt.has("what-goes-with-it") && <h2 class="ch-chapter">{chapterAt.get("what-goes-with-it")}</h2>}
     <section class="ch" id="what-goes-with-it">
+      <p class="ch-source">For reported cannabis-use rates: {USE_COMPARISON_NOTE}</p>
       <p class="ch-kicker"><a href="#what-goes-with-it">{kick("what-goes-with-it")}</a></p>
       <h2>Across the world, {strongest.label.replace(/ \(.*\)$/, "").toLowerCase()} goes with cannabis freedom most closely (r = {strongest.r.toFixed(2)}); in fewer countries, {strongestNarrow.label.replace(/ \(.*\)$/, "").toLowerCase()} does even more (r = {strongestNarrow.r.toFixed(2)})</h2>
@@ -1991,6 +1991,6 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
     // use and the law: the survey behind a dot
     document.querySelectorAll("[data-use]").forEach((a) => card(a, () => {
-      const [name, prev, year, score, tier] = D.use[a.dataset.use];
-      return `<b>${esc(name)}</b><span class="ch-tip-row">Used cannabis in the past year<em>${prev}%</em></span>${year ? `<span class="ch-tip-row">Survey<em>${year}</em></span>` : ""}`
+      const [name, prev, year, score, tier, ages] = D.use[a.dataset.use];
+      return `<b>${esc(name)}</b><span class="ch-tip-row">Past-year use, ages ${esc(ages)}<em>${prev}%</em></span>${year ? `<span class="ch-tip-row">Survey<em>${year}</em></span>` : ""}`
         + `<span class="ch-tip-row">Score today<em>${score} · ${esc(tier)}</em></span><span class="ch-tip-go">Open its datasets →</span>`;
     }));
@@ -2221,6 +2221,6 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
     // state capacity
     document.querySelectorAll("[data-capacity]").forEach((a) => card(a, () => {
-      const [name, ge, use, year, score, tier] = D.capacity[a.dataset.capacity];
-      return `<b>${esc(name)}</b><span class="ch-tip-row">Government effectiveness<em>${ge} of 100</em></span><span class="ch-tip-row">Used cannabis in the past year${year ? `, ${year}` : ""}<em>${use}%</em></span><span class="ch-tip-row">Score<em>${score} · ${esc(tier)}</em></span><span class="ch-tip-go">Open its datasets →</span>`;
+      const [name, ge, use, year, score, tier, ages] = D.capacity[a.dataset.capacity];
+      return `<b>${esc(name)}</b><span class="ch-tip-row">Government effectiveness<em>${ge} of 100</em></span><span class="ch-tip-row">Past-year use, ages ${esc(ages)}${year ? `, ${year}` : ""}<em>${use}%</em></span><span class="ch-tip-row">Score<em>${score} · ${esc(tier)}</em></span><span class="ch-tip-go">Open its datasets →</span>`;
     }));
 
@@ -2252,6 +2252,6 @@ const kick = (id) => { const i = sections.findIndex(([x]) => x === id); return `
     // treemap
     document.querySelectorAll("[data-tile]").forEach((a) => card(a, () => {
-      const [name, users, prev, score, tier] = D.tiles[a.dataset.tile];
-      return `<b>${esc(name)}</b><span class="ch-tip-row">People who use cannabis<em>${users.toLocaleString("en-GB")}</em></span><span class="ch-tip-row">Share of adults<em>${prev}%</em></span><span class="ch-tip-row">Score<em>${score} · ${esc(tier)}</em></span><span class="ch-tip-go">Open its datasets →</span>`;
… 5 more lines
src/pages/compare.astro +4 −2

Not shown: a picture, a generated file, or past the length shown per change.

src/pages/data/index.astro +4 −2

Not shown: a picture, a generated file, or past the length shown per change.

    ↑↓ to move, ↵ to open.