Cannabis Freedom Index

Changelog a14f6e1

update

47 history entries added25 history entries edited12 history entries removed1 country profile edited18 files of code, design or docs +3,173 −283 lines

History changes.yaml

Added 47

Edited 25

Philippine House panels revive medical-cannabis legislation

description

The joint House committees on dangerous drugs and health approved a consolidated bill, subject to style and amendments, that would allow regulated access tolegalise medical cannabis in pharmaceutical dosage forms, ABS-CBN reported on 24 February 2026. The proposal hadsources read do not becomeshow it becoming law by October.

sources

ABS-CBN News — House panels OK consolidated bill to legalize medical cannabis (24 Feb 2026) 1 unchanged

checked

true

The Philippines updates its compassionate-permit rules

description

New healthAdministrative Order No. 2025-department0024, guidelines took effect for access to unregistered medicines throughavailing a compassionate special permit for the restricted use of covered pharmaceutical products and medical devices for human use, took effect on 10 December 2025. The case-specificFood and Drug Administration page read does not mention cannabis, so whether the route can cover an importedcovers cannabis-based medicine, but it didmedicines is not create a medical-cannabis programme or legalise the plantconfirmed.

checked

true

National study says Papua New Guinea is not ready for hemp

date

2024-09-18

description

TheA report for the National Narcotics Bureau, summarised by the National Research Institute reportedin September 2024, found demand for industrial -hemp products in Papua New Guinea but little public support for legallegalising its production and insufficient regulatorymarketing, and a widespread belief that the country lacks the capacity to regulate it. The government's development plan still calls for a futureIt recommended that hemp-policy framework; neither document production not be legalised until the country has that capacity. It did not legalise the crop.

checked

true

Uganda re-enacts its narcotics law

description

AfterOn 5 May 2023 the Constitutional Court annullednullified the 2015 Act on procedural grounds, parliament passed a new Narcotic Drugs and Psychotropic Substances (Control) Act of 2016 because Parliament had lacked a quorum when passing it. Possession remainsParliament ratified a crime with heavy fines94-clause replacement bill in August 2023, which allows licensed cultivation and medical use of marijuana and sets severe penalties, including asset forfeiture, long prison terms and heavy fines, for violations. The replacement is listed as the Narcotic Drugs and Psychotropic Substances (Control) Act, 2024.

sources

The Observer — Ugandans allowed to cultivate marijuana for medicinal purposes Daily Monitor — Govt appeals nullification of Narcotic Drugs Act 1 unchanged

checked

true

Singapore creates a capital offence for cannabis mixtures

date

1993-12-10

description

An amendment created the legal categoryThe Misuse of Drugs (Amendment) Act 1993 (Act 40 of 1993) introduced the term "cannabis mixture" to coverfor cannabis blendedbroken up and mixed with materialother vegetable matter such as tobacco. Trafficking, and made trafficking, importing orand exporting it offences. At the bill's second reading on 10 November 1993 the Minister for Home Affairs said a new capital offence would apply to more than 1,000 g became punishable by deathgrammes of cannabis mixture, against more than 500 grammes of cannabis alone, with 20 to 30 years and 15 strokes for 660 to 1,000 grammes.

checked

true

Singapore adds capital thresholds for cannabis

date

1990-01-15

description

TheAmendments introduced in 1990 brought in the mandatory death penalty was extended tofor trafficking, importing or exporting more than 500 g of in cannabis or 200 g of cannabis resin. This is when the present 500 g cannabis threshold entered the lawand opium, not withaccording to a study published in the firstHome Team Journal. The capital-drug provisions in 1975 threshold for cannabis was 500 g.

sources

MHA Home Team Journal, January 2020 special edition (Wayback copy) 1 unchanged

checked

true

Britain gives police the power to stop and search for drugs

date

1967-10-27

description

TheSection 6 of the Dangerous Drugs Act 1967 let police stop and search anyone they reasonably suspecteda constable who had reasonable grounds to suspect that a person was in possession of carrying drugs. Cannabis convictions rose quicklya drug unlawfully search that person, detain them for the search, and search any vehicle in which the power remains central to howconstable suspected the law is enforceddrug was.

sources

Dangerous Drugs Act 1967 (c. 82), section 6, royal assent 27 October 1967 — legislation.gov.uk 1 unchanged

checked

true

A single Dangerous Drugs Ordinance takes effect

description

The 1952Dangerous Drugs Ordinance of 1952 came into force across the combined Territoryon 25 August 1955 and survives as Chapter 228 of Papua and New Guinea, replacing the earlier separate territorial enactments's laws. It controlled manufactureIn its consolidated form it prohibits importing dangerous drugs except under licence for medicinal purposes, export and medicinal import, but did not yet clearly criminalise possession of locally grown marijuanaas later amended, possessing or conveying a dangerous drug or a plant from which one can be made.

checked

true

Canada separates trafficking from possession

description

An amendmentThe 1954 amendments to the Opium and Narcotic Drug Act created the offence of possession for the purpose of trafficking, withpunishable on indictment by up to 14 years in prison and, at the court's discretion, whipping. Simple possession kept its mandatory minimumThey also set the burden of proof on the accused in such cases. Possession remained punishable by at least six months in prison, up to seven years on indictment.

sources

Special Senate Committee on Illegal Drugs — Cannabis: Report, Vol. 4 (history of Canadian drug legislation) 1 unchanged

checked

true

Jamaica makes prison mandatory for ganja

description

An amendment to the Dangerous Drugs Law requiredIn 1941 Jamaica mandated a one-year prison sentenceterm for possessing ganja, with no option of a fine. Penalties were raised again in the 1960spossession, use and the mandatory term was only removed incultivation of cannabis, according to Harm Reduction International. A 1972 amendment removed mandatory imprisonment.

sources

Harm Reduction International — Jamaica case study (2025) 1 unchanged

checked

true

Soviet drug law is imposed on Estonia

description

Estonia was annexed by the Soviet Union, and in 1940. The Soviet criminal law, including its ban on growingUnion had banned the unauthorised cultivation of cannabis in 1934, applied from then onbut the sources read do not confirm when or how Soviet drug law began to apply in Estonia.

Soviet drug law is imposed on Latvia

description

Latvia was annexed by the Soviet Union, and in 1940. The Soviet criminal law, including its ban on growingUnion had banned the unauthorised cultivation of cannabis in 1934, applied from then onbut the sources read do not confirm when or how Soviet drug law began to apply in Latvia.

Soviet drug law is imposed on Lithuania

description

Lithuania was annexed by the Soviet Union, and in 1940. The Soviet criminal law, including its ban on growingUnion had banned the unauthorised cultivation of cannabis in 1934, applied from then onbut the sources read do not confirm when or how Soviet drug law began to apply in Lithuania.

The Territory of New Guinea restricts cannabis extracts

description

TheA Dangerous Drugs Ordinance treated extracts1927 for the Territory of New Guinea, published in the Commonwealth Gazette of 11 August 1927, listed galenical preparations (extract and tincturestincture) of Indian hemp asamong the dangerous drugs, prohibited their unlicensed import and limited licences to. Imports were licensed for medicinal purposes only. The ordinance was to commence on a date fixed by the Administrator. The restriction covered the former Territory of New Guinea, not yet the whole of present-day Papua New Guinea, and did not create a general offence for locally grown marijuana.

checked

true

France bans cannabis in French Equatorial Africa

description

The Conversation lists French colonial government prohibited cannabis across French Equatorial Africa, in 1926 among the places where French authorities adopted a ban or repressive regulation of cannabis. The federation that included what is now the Central African Republic. The article does not describe the measure itself.

checked

true

France bans cannabis in French Equatorial Africa

description

The Conversation lists French colonial government prohibited cannabis across French Equatorial Africa, in 1926 among the places where French authorities adopted a ban or repressive regulation of cannabis. The federation that included what is now Congo. The article does not describe the measure itself.

checked

true

France bans cannabis in French Equatorial Africa

description

The Conversation lists French colonial government prohibited cannabis across French Equatorial Africa, in 1926 among the places where French authorities adopted a ban or repressive regulation of cannabis. The federation that included what is now Gabon. The article does not describe the measure itself.

checked

true

France bans cannabis in French Equatorial Africa

description

The Conversation lists French colonial government prohibited cannabis across French Equatorial Africa, in 1926 among the places where French authorities adopted a ban or repressive regulation of cannabis. The federation that included what is now Chad. The article does not describe the measure itself.

checked

true

Swaziland's Opium and Habit-forming Drugs Act takes effect

date

1922-06-23

description

The colonial law banned importing, cultivating, producing or possessing habit-forming drugs without a permit. A 1955 United Nations survey confirms that it applied to the cannabis plant, cannabis and its resin. Courts in Eswatini still prosecute dagga possession and cultivation under this 1922 Act.

sources

United Nations — Survey of the cannabis situation in Swaziland, E/CN.7/286/Add.3 (1955) 1 unchanged

checked

true

Bechuanaland restricts habit-forming drugs

date

1922-06-23

description

The Opium and Habit-forming Drugs RegulationForming Drug Proclamation restricted the import, export, production and use of habit-forming drugs in the Bechuanaland Protectorate, on the same day as the matching law in Swaziland and in the year South Africa prohibited dagga. A 1955 United Nations survey confirms that it applied to the cannabis plant, with possession and use limited to medical purposes.

sources

United Nations — Survey of the cannabis situation in Bechuanaland, E/CN.7/286/Add.2 (1955) 1 unchanged

checked

true

Portugal bans cannabis in Angola

date

1913-05-13

description

A colonial law prohibited cannabis in Angola. It was aimed at colonial troops and also meant to push farmers towards growing tobacco. A United Nations survey names the order as Portaria No. 508 of 13 May 1913.

sources

United Nations — Survey of the cannabis situation in Angola, E/CN.7/286/Add.9 (1956) 2 unchanged

Removed 12

Countries and states 1 file

Uganda countries/ug.yaml

segments › possession › text

Possession is a crime under the Narcotic Drugs and Psychotropic Substances (Control) Act 2024, re-enacted after the Constitutional Court annulled the 20152016 law on procedural groundsfor lack of a quorum. It carries heavy fines and prison terms measured in years.

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 lines
src/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.

    ↑↓ to move, ↵ to open.