Code, design and documents 18 files
.env.example +4 −0
@@ -0,0 +1,4 @@
+# Local settings. Copy to .env (which is not committed).
+
+# Show the incomplete flower-price data in the interface. Leave unset in production.
+PUBLIC_SHOW_PRICES=true
.gitignore +1 −0
@@ -3,2 +3,3 @@ dist/
.astro/
.claude/
+.env
README.md +12 −2
@@ -40,4 +40,5 @@ Built with [Astro](https://astro.build). Pushing to `main` deploys to GitHub Pag
| `segments.yaml` | The six segments, their weights and level descriptions |
| `changes.yaml` | The History page: laws and events that moved a country's score, with the scores before, plus treaties; the home page shows the latest four |
+| `prices.yaml` | Sourced flower prices by country, date and market (street or medical) |
| `src/` | The website: pages, components, styles |
| `docs/DATA-MAINTENANCE.md` | How the country profiles and the history are checked and extended, and what is still open |
@@ -66,4 +67,13 @@ sources:
Please include a source link. Or just [open an issue](https://github.com/burningtree/cannabis-freedom-index/issues).
+### Contributing flower prices
+
+Add sourced per-gram observations to `prices.yaml`. Only two markets are used: `street` for
+illicit or non-prescribed flower, and `medical` for prescribed flower. Keep the original currency,
+add the normalized USD value, and append new dates instead of deleting historical observations.
+
+See [the price-data instructions](docs/DATA-MAINTENANCE.md#adding-prices) for the complete field
+example, source-quality rules, currency conversion and validation checklist.
+
## Disclaimer
@@ -73,6 +83,6 @@ Scores are editorial estimates, not legal advice. Check current local law before
- **Code:** [MIT](LICENSE).
-- **Data** (everything in `countries/`, `changes.yaml` and `segments.yaml`): [CC BY 4.0](LICENSE-DATA). Credit "Cannabis Freedom Index" and link to https://cannabisfreedom.fyi/.
+- **Data** (everything in `countries/`, `changes.yaml`, `prices.yaml` and `segments.yaml`): [CC BY 4.0](LICENSE-DATA). Credit "Cannabis Freedom Index" and link to https://cannabisfreedom.fyi/.
- Data from others keeps its own terms: population from the World Bank and Our World in Data (CC BY 4.0), cannabis-use rates from the UN World Drug Report as collected on Wikipedia, map shapes from Natural Earth (public domain).
-The scores and history can be downloaded as CSV or JSON at https://cannabisfreedom.fyi/data/, and score changes are listed in the [changelog](https://cannabisfreedom.fyi/changelog/) and its RSS feed.
+The scores, history and prices can be downloaded as CSV or JSON at https://cannabisfreedom.fyi/data/, and score changes are listed in the [changelog](https://cannabisfreedom.fyi/changelog/) and its RSS feed.
docs/DATA-MAINTENANCE.md +115 −3
@@ -1,10 +1,10 @@
-# Maintaining the data: country profiles and history
+# Maintaining the data: country profiles, history and prices
-How to check, correct and extend the two data sets behind the site. Written as a handover: it
+How to check, correct and extend the data sets behind the site. Written as a handover: it
records how the work has been done so far, what was learned, and what is still open.
## The task in one paragraph
-The site holds three things per country, and they only make sense together:
+The legal index holds three connected things per country:
1. **What the law is now** — the profile in `countries/*.yaml`, with six segment scores that add
@@ -23,4 +23,7 @@ And when research turns up something that changes a country's current situation,
its scores and the timeline are updated together, never just one of them.
+Flower prices are a fourth, independent data set. They provide market context but do not affect
+the freedom score.
+
## Where things are
@@ -29,4 +32,5 @@ its scores and the timeline are updated together, never just one of them.
| `countries/<id>.yaml`, `countries/{us,ca,au}/index.yaml` | One profile per country: status, six scores, a text and sources per segment, context, sources |
| `changes.yaml` | The history, newest first. The header comment documents every field |
+| `prices.yaml` | Flower-price observations, kept separately for street and medical markets |
| `segments.yaml` | The six segments, their weights and what each score level means |
| `src/content.config.ts` | The schema. The build fails with a clear message when data breaks it |
@@ -38,4 +42,98 @@ batch. The dev server (`npm run dev`) does **not** pick up edits to `changes.yam
until it is restarted.
+## Adding prices
+
+Prices are deliberately simple: **street flower** and **medical flower**, always per gram. Do not
+add resin, hash, rosin, oil, edibles or a separate recreational category.
+
+- `street` means illicit or otherwise non-prescribed flower.
+- `medical` means prescribed flower supplied through the medical system.
+
+Append a new observation to `prices.yaml`; do not replace an older valid value. The newest date
+for each country and market is shown on the map, rankings and country page. Older observations
+remain available as price history.
+
+### Required fields
+
+```yaml
+- id: cz-street-2023
+ country: cz # lowercase ISO 3166-1 alpha-2 code
+ date: "2023" # YYYY, YYYY-MM or YYYY-MM-DD
+ market: street # street or medical
+ currency: CZK
+ price: 200 # per gram in the original currency
+ usd: 9.17 # normalized USD per gram
+ note: Most commonly reported national price; based on 266 police records.
+ source:
+ title: Report title
+ url: https://example.com/report
+```
+
+Use a unique id in the form `<country>-<market>-<date>`, adding a short suffix only when needed.
+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.
+
+### Choosing a price
+
+Prefer, in order:
+
+1. National statistics, drug observatories, health ministries or official pharmacy lists.
+2. EUDA, UN or a national survey with a stated method.
+3. A peer-reviewed study or a transparent national market dataset.
+
+Use a national figure where possible. Do not use one city, one shop, one police seizure, a bulk
+trafficking valuation or an anonymous anecdote as the country's price. Do not turn a range into a
+midpoint unless the source itself reports that midpoint as its estimate. If only a range or weak
+source exists, leave the country as **No data**.
+
+For medical flower, say whether the figure includes tax, pharmacy preparation, consultation,
+prescription or delivery fees. When several current flower products are listed, a simple average
+is acceptable if the note explains exactly which products and package prices were included.
+
+### Known bulk sources
+
+- **EUDA Statistical Bulletin** (price, purity and potency, retail herbal cannabis, mean) covers most
+ European countries. The site blocks scripted downloads; open it in a normal browser and use the
+ table's "Download as Excel" option or read the table view.
+- **UNODC World Drug Report annex 8.1** (`WDR_2026/Annex/8.1_Prices_and_purities_of_drugs.xlsx`,
+ government submissions for 2020–2024; the 2024 edition covers 2018–2022) covers many non-European countries. Use rows with
+ `Cannabis herb`, `Retail`, unit `Grams` and a `Typical` value; skip rows with only a min/max
+ range. Prices are already in USD at the time's exchange rate, so record `currency: USD` and say so
+ in the note. Treat outliers against the country's earlier submissions with suspicion (several
+ countries report a figure that jumps tenfold between years).
+
+- **Older UNODC annexes.** The World Drug Report 2020 annex PDF
+ (`wdr.unodc.org/wdr2020/field/Annex/8.1._Prices_and_Purities_of_drugs.pdf`, 2014–2018, in local
+ currency) and the 2017 cannabis sheet (`unodc.org/wdr2017/field/8.2_Price_Purity_Cannabis.xlsx`,
+ the latest figure per country back to 2004) fill countries the newer editions miss. All editions
+ from 2017 to 2026 have been mined for countries without a price.
+- **Blocked:** EUDA pages and PDFs, the CICAD supply report and tandfonline sit behind a bot check
+ or return 403 to both `curl` and the in-app browser. Save the file into the project to read it.
+
+### Normalizing to US dollars
+
+`price` always preserves the source currency. `usd` is the comparison value used by the map and
+rankings. Use the common exchange-rate date documented at the top of `prices.yaml`, not the
+historical exchange rate from the observation's year. Round `usd` to two decimal places.
+
+ECB rates are quoted as currency units per euro. Convert with:
+
+```text
+usd = price / local_currency_per_eur * usd_per_eur
+```
+
+For euro prices, multiply directly by the documented EUR/USD rate. If the currency is not covered
+by the ECB, cite the alternative exchange-rate source in the observation note.
+
+### Updating and checking
+
+- A newly published price is a new observation; keep the old one for history.
+- A factual correction to an existing observation should edit that observation rather than add a
+ duplicate.
+- Never mix street and medical observations when deciding which value is newest.
+- Run `npm run build` after editing. The schema rejects invalid markets, dates and values.
+- Spot-check `/data/prices.json`, `/api/<country>.json`, the country page, rankings and both price
+ map modes.
+
## How a history entry works
@@ -172,4 +270,9 @@ node scripts/data-status.cjs chain de # one country's full timeline
- **Search tool limits:** more than about ten web searches in one go start failing with
rate-limit errors. Batches of five work.
+- **UN documents load through the official document system.** `digitallibrary.un.org` is blocked,
+ but `https://documents.un.org/api/symbol/access?s=<symbol>&l=en&t=pdf` returns the PDF for a
+ document symbol and `pdftotext` reads it. The 1955–58 cannabis surveys are E/CN.7/286 (South
+ Africa) and Add.1–12 (Basutoland, Bechuanaland, Swaziland, the Rhodesias, Brazil, Angola,
+ Mozambique, Morocco, India); each has a "National legal provisions" part listing the laws.
- **Blocked sources:** the UN Digital Library (403), UNODC Bulletin on Narcotics pages (404),
Library of Congress (403), TNI Spanish pages (403) and most PDFs cannot be read by the fetch
@@ -236,4 +339,13 @@ be opened and were set back to unchecked. The wider October 2026 batch of "check
been audited yet.
+**Unchecked list (open item 3), worked through in the same session:** 22 of 41 entries are now checked,
+most after corrections (see git diff of `changes.yaml`). Still unchecked, with the reason:
+Papua New Guinea 1970 and 2022 (source PDFs blocked; The National, 3 Dec 2021, confirms Parliament passed
+the Controlled Substance Bill), Djibouti 1996 (law number only on cannabisregulations.ai), Iran 1959
+(Sensi Seeds only), Estonia/Latvia/Lithuania 1940 (no source says when Soviet drug law applied),
+Iraq 1933, Eswatini 1922, Botswana 1922 (sources blocked), Germany 1872 (no source found).
+Two entries were rebuilt around what the source actually says: Laos 1996 became an estimated first
+restriction, and the Norway "1965 tinctures" entry became Norway's first cannabis seizure of 1965.
+
## Reporting back
prices.yaml +1975 −0
@@ -0,0 +1,1975 @@
+# Cannabis flower prices per gram. `market` is intentionally limited to:
+# street — illicit or non-prescribed flower
+# medical — prescribed flower supplied through the medical system
+#
+# Keep older observations: the newest date becomes the displayed price automatically.
+# `usd` is the comparable dollar value used throughout the site. These values use the
+# European Central Bank reference rates from 2 October 2026 (EUR/USD 1.1225).
+
+- id: au-street-2026
+ country: au
+ date: "2026"
+ market: street
+ currency: AUD
+ price: 20
+ usd: 13.88
+ note: National median for one gram of non-prescribed hydroponic and bush cannabis.
+ source:
+ title: UNSW National Drug and Alcohol Research Centre — Australian Drug Trends 2026
+ url: https://www.unsw.edu.au/research/ndarc/resources/australian-drug-trends-2026-key-findings-from-the-idrs
+
+- id: al-street-2020
+ country: al
+ date: "2020"
+ market: street
+ currency: EUR
+ price: 4
+ usd: 4.49
+ note: Mean retail price in the latest exact country-level figure found, from a regional dataset covering 2018–2020.
+ source:
+ title: EUDA — Drug situation in the Western Balkans 2022
+ url: https://www.euda.europa.eu/system/files/media/publications/documents/14949/TD0322106ENN.pdf
+
+- id: bg-street-2025
+ country: bg
+ date: "2025"
+ market: street
+ currency: BGN
+ price: 18
+ usd: 10.33
+ note: National median street price for one gram of herbal cannabis. USD conversion uses the fixed rate of BGN 1.95583 per euro.
+ source:
+ title: Bulgarian National Focal Point — street-level drug price trends, 2013–2025
+ url: https://www.nfp-drugs.bg/en/price-at-street-level-of-some-drugs-in-bulgaria-over-the-period-trend-bgn/
+
+- id: de-medical-2026-03
+ country: de
+ date: "2026-03"
+ market: medical
+ currency: EUR
+ price: 4.52
+ usd: 5.07
+ note: Average price per gram of medical cannabis flower dispensed through the Bloomwell platform in Q1 2026.
+ source:
+ title: Bloomwell — Cannabis Barometer Q1 2026
+ url: https://info.bloomwell.de/hubfs/Barometer/E%20The%20Cannabis%20Barometer%20Q1%202026.pdf
+
+- id: dk-medical-2026-10
+ country: dk
+ date: "2026-10"
+ market: medical
+ currency: DKK
+ price: 127.20
+ usd: 19.10
+ note: Unweighted average per gram across the five dry-flower products available in the official pharmacy list; package prices include VAT and the dispensing fee.
+ source:
+ title: Danish Medicines Agency — current medicinal cannabis products and prices
+ url: https://medicinpriser.dk/Default.aspx?id=422
+
+- id: ie-street-2025
+ country: ie
+ date: "2025"
+ market: street
+ currency: EUR
+ price: 20
+ usd: 22.45
+ note: Garda national retail-market valuation for the lowest-denomination cannabis-herb deal.
+ source:
+ title: Houses of the Oireachtas — Garda illicit-drug retail valuations
+ url: https://www.oireachtas.ie/en/debates/question/2025-01-22/1064/
+
+- id: fr-street-2023
+ country: fr
+ date: "2023"
+ market: street
+ currency: EUR
+ price: 10
+ usd: 11.23
+ note: National price per gram for herbal cannabis reported for 2023.
+ source:
+ title: OFDT — Drugs and addictions, key data 2025
+ url: https://www.euda.europa.eu/system/files/documents/2025-06/dacc-2025_en_vf.pdf
+
+- id: gb-medical-2026-04
+ country: gb
+ date: "2026-04"
+ market: medical
+ currency: GBP
+ price: 6.73
+ usd: 8.88
+ note: UK market average for prescribed medical cannabis flower; consultation, prescription and delivery fees are not included.
+ source:
+ title: Cannabis Health — UK medical cannabis cost guide
+ url: https://cannabishealthnews.co.uk/2024/08/15/how-much-does-medical-cannabis-cost-in-the-uk-releafs-price-plans-explained/
+
+- id: it-medical-2026
+ country: it
+ date: "2026"
+ market: medical
+ currency: EUR
+ price: 9
+ usd: 10.10
+ note: Official tariff for cannabis inflorescences, introduced in 2017 and still listed by the Ministry; pharmacy preparation costs may affect the final patient total.
+ source:
+ title: Italian Ministry of Health — magistral cannabis preparations
+ url: https://www.salute.gov.it/new/it/tema/medicinali-stupefacenti-e-precursori-di-droghe/prescrizione-di-preparazioni-magistrali-base-di/?paragraph=0
+
+- id: lu-street-2024
+ country: lu
+ date: "2024"
+ market: street
+ currency: EUR
+ price: 10
+ usd: 11.23
+ note: Average price reported for herbal cannabis by participants in Luxembourg's 2024 European Web Survey on Drugs.
+ source:
+ title: Luxembourg Ministry of Health — National Drug Report 2025
+ url: https://santesecu.public.lu/dam-assets/fr/publications/r/rapport-national-drug-2025/rapport-relis2024-en.pdf
+
+- id: nl-medical-2024
+ country: nl
+ date: "2024"
+ market: medical
+ currency: EUR
+ price: 6.48
+ usd: 7.27
+ note: Official purchase price of €32.40 per 5 g container; excludes VAT and the pharmacy prescription fee, so the patient retail total may be higher.
+ source:
+ title: Dutch Office of Medicinal Cannabis — price change from February 2024
+ url: https://www.cannabisbureau.nl/actueel/nieuws/2024/01/31/prijswijziging-per-1-februari-2024
+
+- id: pt-street-2024
+ country: pt
+ date: "2024"
+ market: street
+ currency: EUR
+ price: 5.04
+ usd: 5.66
+ note: National mean price for herbal cannabis in trafficking and trafficking-consumption cases.
+ source:
+ title: ICAD — Portugal Annual Drug Situation Report 2024, annex
+ url: https://www.icad.pt/DocumentList/GetFile?id=1292&languageId=1
+
+- id: pl-medical-2025
+ country: pl
+ date: "2025"
+ market: medical
+ currency: PLN
+ price: 46.42
+ usd: 11.90
… 1817 more lines
src/components/CountryArticle.astro +3 −0
@@ -6,4 +6,6 @@ import { tierOf, tierColor } from "../lib/tiers.js";
import CountryChanges from "./CountryChanges.astro";
import CountryChart from "./CountryChart.astro";
+import PricePanel from "./PricePanel.astro";
+import { SHOW_PRICES } from "../lib/config.js";
import { answersFor } from "../lib/answers.js";
@@ -118,4 +120,5 @@ const circ = 2 * Math.PI * r;
</dl>
)}
+ {SHOW_PRICES && !sub && <PricePanel id={c.id} />}
{scored.length > 0 && <CountryChart history={scored} />}
</div>
src/components/PricePanel.astro +47 −0
@@ -0,0 +1,47 @@
+---
+// 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 { PRICE_MARKETS, priceHistoryFor, latestPricesFor, formatPrice } from "../lib/prices.js";
+
+const { id } = Astro.props;
+const history = priceHistoryFor(id);
+const latest = latestPricesFor(id);
+const older = history.filter((p) => latest[p.market] !== p);
+const LABEL = { street: "Street price", medical: "Medical price" };
+const year = (p) => p.date.slice(0, 4);
+const shown = PRICE_MARKETS.filter((m) => latest[m]);
+---
+{history.length > 0 && (
+ <dl class="c-use c-price">
+ {PRICE_MARKETS.map((market) => {
+ const p = latest[market];
+ return (
+ <div>
+ <dt>{LABEL[market]}</dt>
+ {p
+ ? <dd title={p.note}>${p.usd.toFixed(2)}<small> / g</small></dd>
+ : <dd class="none">no data</dd>}
+ {p && <span class="c-price-local">{p.currency !== "USD" ? `${formatPrice(p).replace("/g", "")} · ` : ""}{year(p)}</span>}
+ </div>
+ );
+ })}
+ <p>One gram of flower, the latest figure found, converted to US dollars.</p>
+ {older.length > 0 && (
+ <details class="c-price-older">
+ <summary>Earlier prices ({older.length})</summary>
+ <ul>
+ {older.map((p) => (
+ <li>
+ <span>{year(p)} · {p.market}</span>
+ <b>${p.usd.toFixed(2)}</b>
+ <a href={p.source.url} target="_blank" rel="noopener noreferrer">source</a>
+ </li>
+ ))}
+ </ul>
+ </details>
+ )}
+ <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>
+ </dl>
+)}
src/content.config.ts +20 −1
@@ -142,3 +142,22 @@ const changes = defineCollection({
});
-export const collections = { countries, units, segments, changes };
+// prices.yaml — one sourced flower-price observation. Add older observations at any time;
+// the site derives the latest street and medical value for every country.
+const prices = defineCollection({
+ loader: file("prices.yaml"),
+ schema: z
+ .object({
+ id: z.string().regex(/^[a-z0-9-]+$/),
+ country: z.string().regex(/^[a-z]{2}$/),
+ date: z.string().regex(/^\d{4}(-\d{2}(-\d{2})?)?$/, "use YYYY-MM-DD, YYYY-MM or YYYY"),
+ market: z.enum(["street", "medical"]),
+ currency: z.string().regex(/^[A-Z]{3}$/),
+ price: z.number().positive(), // price per gram in `currency`
+ usd: z.number().positive(), // same observation normalised to USD per gram
+ note: z.string().min(1).optional(),
+ source,
+ })
+ .strict(),
+});
+
+export const collections = { countries, units, segments, changes, prices };
src/lib/config.js +7 −0
@@ -0,0 +1,7 @@
+// Site-wide switches, read from the environment at build time.
+
+// Flower prices are still incomplete (see prices.yaml), so they are hidden from the interface
+// unless PUBLIC_SHOW_PRICES=true is set: the map's price colouring, the rankings column, the
+// country price panel, the compare rows and the download card. Set it in a local .env file (see
+// .env.example); the production deploy does not set it, so prices stay hidden there.
+export const SHOW_PRICES = import.meta.env.PUBLIC_SHOW_PRICES === "true";
src/lib/exports.js +15 −0
@@ -2,4 +2,5 @@
import { SEGMENTS, REC_LABEL, MED_LABEL, places, changes, formatDate, dateTime } from "./countries.js";
import { tierOf } from "./tiers.js";
+import { priceObservations } from "./prices.js";
export const SITE = "https://cannabisfreedom.fyi";
@@ -42,4 +43,18 @@ export const historyRows = changes.map((ch) => ({
}));
+// One row per sourced price observation, newest first. Older rows form the price history.
+export const priceRows = priceObservations.map((p) => ({
+ country: p.country.slug,
+ place: p.country.name,
+ date: p.date,
+ market: p.market,
+ price_per_gram: p.price,
+ currency: p.currency,
+ usd_per_gram: p.usd,
+ note: p.note ?? "",
+ source: p.source.title,
+ source_url: p.source.url,
+}));
+
// Changes that moved a score and have a real date: the changelog and the feed.
export const scoreChanges = changes.filter((ch) => ch.score && !ch.estimate);
src/lib/prices.js +46 −0
@@ -0,0 +1,46 @@
+// Loads append-only flower-price observations and derives each country's latest market price.
+import { getCollection } from "astro:content";
+import { places, dateTime } from "./countries.js";
+
+export const PRICE_MARKETS = ["street", "medical"];
+export const PRICE_MARKET_LABEL = { street: "Street flower", medical: "Medical flower" };
+
+const bySlug = new Map(places.map((c) => [c.slug, c]));
+export const priceObservations = (await getCollection("prices"))
+ .map(({ data }) => {
+ const country = bySlug.get(data.country);
+ if (!country) throw new Error(`prices.yaml refers to unknown country "${data.country}"`);
+ return { ...data, country };
+ })
+ .sort((a, b) => dateTime(b.date) - dateTime(a.date));
+
+const seen = new Set();
+for (const p of priceObservations) {
+ const key = `${p.country.slug}/${p.market}/${p.date}`;
+ if (seen.has(key)) throw new Error(`prices.yaml has duplicate observation "${key}"`);
+ seen.add(key);
+}
+
+export function priceHistoryFor(id) {
+ const slug = id.toLowerCase();
+ return priceObservations.filter((p) => p.country.slug === slug);
+}
+
+export function latestPricesFor(id) {
+ const latest = {};
+ for (const p of priceHistoryFor(id)) {
+ latest[p.market] ??= p;
+ }
+ return latest;
+}
+
+export function flatLatestPricesFor(id) {
+ const latest = latestPricesFor(id);
+ return Object.fromEntries(PRICE_MARKETS.flatMap((market) => {
+ const p = latest[market];
+ return p ? [[market, { usd: p.usd, price: p.price, currency: p.currency, date: p.date }]] : [];
+ }));
+}
+
+export const formatPrice = (p) => p ? `${p.price.toLocaleString("en-GB", { maximumFractionDigits: 2 })} ${p.currency}/g` : "—";
+export const formatUsdPrice = (p) => p ? `$${p.usd.toFixed(2)}/g` : "—";
src/pages/api/[id].json.js +3 −0
@@ -2,4 +2,6 @@
// YAML in /countries. The compare page fetches these on demand.
import { places } from "../../lib/countries.js";
+import { flatLatestPricesFor } from "../../lib/prices.js";
+import { SHOW_PRICES } from "../../lib/config.js";
export function getStaticPaths() {
@@ -14,4 +16,5 @@ export function GET({ props }) {
note: c.note,
reviewed: c.details.reviewed,
+ ...(SHOW_PRICES && { prices: flatLatestPricesFor(c.id) }),
segments: c.details.segments.map(entry),
context: c.details.context.map((x) => x && entry(x)),
src/pages/compare.astro +17 −3
@@ -2,7 +2,9 @@
import Base from "../layouts/Base.astro";
import { SEGMENTS, CONTEXT_NAMES, REC_LABEL, MED_LABEL, countries, places } from "../lib/countries.js";
+import { flatLatestPricesFor } from "../lib/prices.js";
+import { SHOW_PRICES } from "../lib/config.js";
const light = places.map(({ id, slug, name, flag, region, raw, score, rank, s, rec, med }) =>
- ({ id, slug, name, flag, region, raw, score, rank, s, rec, med }));
+ ({ id, slug, name, flag, region, raw, score, rank, s, rec, med, prices: SHOW_PRICES ? flatLatestPricesFor(id) : {} }));
const meta = {
segments: SEGMENTS.map((s) => ({ name: s.name, weight: s.weight })),
@@ -19,10 +21,10 @@ const presets = [
];
---
-<Base title="Compare countries" description="Compare cannabis freedom scores, rules and sources for several countries side by side.">
+<Base title="Compare countries" description={`Compare cannabis freedom scores${SHOW_PRICES ? ", prices" : ""}, rules and sources for several countries side by side.`}>
<main class="compare-page">
<section class="hero hero-sm">
<p class="eyebrow">Compare</p>
<h1>Compare countries side by side</h1>
- <p class="lede">Pick up to six countries to see their scores, the exact rules behind each segment, and the sources — in one table. The address bar updates as you go, so a comparison can be shared as a link.</p>
+ <p class="lede">Pick up to six countries to see their scores{SHOW_PRICES && ", latest sourced prices"}, exact rules and sources — in one table. The address bar updates as you go, so a comparison can be shared as a link.</p>
</section>
@@ -109,4 +111,7 @@ const presets = [
: "";
const prose = (entry) => entry ? `<p class="cmp-text">${esc(entry.t)}</p>${sources(entry)}` : "";
+ const priceCell = (p) => p
+ ? `<div class="cmp-val"><b>$${p.usd.toFixed(2)}</b><span>/g</span></div><div class="cmp-sub">${p.price} ${p.currency}/g · ${p.date}</div>`
+ : `<span class="cmp-sub">No data</span>`;
// Mark the highest value in a row, unless everyone ties.
const bestOf = (values) => {
@@ -142,4 +147,13 @@ const presets = [
if (showText) rows.push(row("In short", cs.map((c) => `<p class="cmp-text">${esc(details.get(c.id)?.note ?? "")}</p>`)));
+ const priceRows = [
+ ["Street flower", "street"],
+ ["Medical flower", "medical"],
+ ].filter(([, key]) => cs.some((c) => c.prices[key]));
+ if (priceRows.length) {
+ rows.push(`<tr class="cmp-divider"><th colspan="${cs.length + 1}">Latest prices · per gram</th></tr>`);
+ priceRows.forEach(([label, key]) => rows.push(row(label, cs.map((c) => priceCell(c.prices[key])))));
+ }
+
rows.push(`<tr class="cmp-divider"><th colspan="${cs.length + 1}">Scored segments</th></tr>`);
META.segments.forEach((seg, i) => {
src/pages/data/index.astro +7 −5
@@ -2,6 +2,7 @@
// Downloads: the scores and the history as CSV and JSON, with the licence.
import Base from "../../layouts/Base.astro";
-import { scoreRows, historyRows } from "../../lib/exports.js";
+import { scoreRows, historyRows, priceRows } from "../../lib/exports.js";
import { lastUpdated, formatDate } from "../../lib/countries.js";
+import { SHOW_PRICES } from "../../lib/config.js";
const base = import.meta.env.BASE_URL;
@@ -10,12 +11,13 @@ const files = [
{ name: "scores", title: "Scores", rows: scoreRows, about: "One row per country or disputed territory: index score, the six segment scores, rank, tier, legal status, population and estimated cannabis users." },
{ name: "history", title: "History", rows: historyRows, about: "One row per history entry: date, country, category, title, description, the score before and after, how far it has been checked, and its sources." },
-];
+ { name: "prices", title: "Flower prices", rows: priceRows, about: "One row per sourced flower-price observation: country, date, street or medical market, local price and US dollars per gram." },
+].filter((f) => SHOW_PRICES || f.name !== "prices");
… 19 more linessrc/pages/data/prices.csv.js +3 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/pages/data/prices.json.js +3 −0
Not shown: a picture, a generated file, or past the length shown per change.
src/pages/index.astro +74 −23
Not shown: a picture, a generated file, or past the length shown per change.
src/styles/global.css +26 −0
Not shown: a picture, a generated file, or past the length shown per change.