# heatline — Method (auditable)

*An independent study by Artur Scartazzini. Built entirely from public data. This
document is written to be checked line by line by a climate scientist. Every input has
a named dataset and a re-runnable command; every choice of threshold has a stated
justification and a sensitivity test.*

Last regenerated: see `data/robustness.json → generated`.

---

## 1. The question, stated precisely

> For how many **person·days per year** is a human body pushed outside its
> thermal-comfort band — and how has that changed since the satellite era began?

We deliberately do **not** measure income, mortality, or adaptation. We measure
**exposure**: the raw climatic load on the body, counted in people and days.

**Definitions**

| term | definition |
|---|---|
| Comfort band | UTCI **9 °C – 26 °C** = "no thermal stress" (ISB Commission 6 categories). |
| Heat-stress day | a calendar day whose **daily-maximum UTCI > 26 °C**. |
| Cold-stress day | a calendar day whose **daily-minimum UTCI < 9 °C**. |
| person·day | one person living one stress day. `Σ (stress days in year × population)`. |
| Trend | last-5-year mean vs first-5-year mean of the 86-year record (1940s baseline). |

Population is held **fixed** across all years, so any change in the series is the
**climate signal alone**, never urban growth.

---

## 2. Data sources (exact)

| layer | dataset | provider | access | resolution | period |
|---|---|---|---|---|---|
| Thermal comfort | `derived-utci-historical-timeseries` (ARCO, CSV point output) — the UTCI product of **ERA5-HEAT** | Copernicus C3S / CDS | free CDS account, `~/.cdsapirc` | 0.25° grid node nearest each city, **hourly** | 1940–2025 |
| National population | `SP.POP.TOTL` | World Bank | open API | national | 2023 |
| City sample | 127 cities / 83 countries | — | `data/cities.json` | point | — |
| Population grid | **GHS-POP E2020** (R2023A, WGS84 30″) aggregated to 0.25° | JRC GHSL | open download, no account | 0.25° (173,243 populated cells, 7.84 B people) | 2020 |
| Grid climate field | same UTCI dataset at **+300 extra points** chosen greedily by population×distance | Copernicus C3S | same CDS account | 427 sample points total; population-weighted mean distance **124 km** | 1940–2025 |
| City heat & green (Atlas) | Landsat 9 Collection-2 Level-2 (ST_B10, SR_B4/B5) | USGS via MS Planetary Computer STAC | keyless | 30 m | recent clear scene |

UTCI is delivered in **kelvin**; we convert `°C = K − 273.15`. The comfort thresholds
(26 °C / 9 °C) are the official category boundaries of the Universal Thermal Climate
Index, not a choice of ours — see §5 for what happens if we move them anyway.

---

## 3. Pipeline (re-runnable)

```
cities.json ─┐
             ├─(A) utci_official.py ──► raw/utci/<CC>_<City>.csv   (hourly UTCI, cached)
             │                     └──► heatline_utci.data.js       (sample aggregation)
             ├─(B) scale_population.mjs ─► heatline.data.js         (scaled to national pop) ★ served
             └─(C) sensitivity.py ──────► robustness.json           (band + sampling uncertainty)
```

**(A) Download + count** — `build/utci_official.py`
For each city it pulls the hourly UTCI series at the nearest grid node (area box
±0.13° guarantees one node), caches the CSV, reduces each day to (max, min), and counts
heat/cold days per year. Aggregates to country (`Σ days × city-pop`) and global.

**(B) Scale to population** — `build/scale_population.mjs`
The cities *sample the climate*; this step multiplies each country's per-resident signal
by its **whole national population** (World Bank), so a person·day represents the nation,
not the sample. Writes `meta.coverage` (share of humanity) and `meta.repPop`.

**(C) Robustness** — `build/sensitivity.py`
Re-counts the headline under five heat thresholds and three cold thresholds directly from
the cached hourly data (no re-download), and computes the city→nation sampling spread.
Writes `data/robustness.json`, consumed by the site's "How robust is the number?" panel.

**(D) The gridded count** — `build/grid_cells.py` → `build/sample_points.py` →
`build/pull_grid.py` → `build/grid_person_days.py`
The sample-independent cross-check. GHS-POP is aggregated to the UTCI 0.25° grid
(reconstructed world population: 7.84 B — matches the official total). The UTCI field is
sampled at 427 points (the 127 cities + 300 points chosen greedily by population×distance,
bringing 80% of humanity within 200 km of a real UTCI series). Every populated cell is
assigned its nearest sample point (Voronoi), and person·days = Σ cell-pop × stress-days.
No national scaling, no country boundaries — just people × the climate where they live.

One command: **`make`** (see `Makefile`). `make robustness` re-runs (C) offline in ~20 s.

---

## 4. Current result

- **Gridded count (headline): ≈2.0 trillion person·days of heat stress per year**, weighing
  **100% of humanity** (GHS-POP, 7.84 B) by the climate where each person lives. The
  average human's year now carries heat stress on **260 days** — five days in seven.
- **Cross-validation:** the independent city-sample estimate scaled to national populations
  (92% coverage) gives **1.88 T** — the two methods agree within **8%**.
- Trend (1940s baseline): heat **rising**, cold **retreating** — the temperate/northern
  world shows the sharpest *relative* rise (Sweden +82%, Ireland +94%, Netherlands +45%,
  Germany +42%), while the humid lowland tropics have sat at the ceiling **since the
  1940s** (~365 days/yr: DR Congo, Indonesia, Malaysia, Singapore, Lagos… — Manaus has
  had no days left to give for the whole record). A new finding from the longer record:
  the **high-altitude refuges are warming fastest of all** (Bolivia +192%, Ecuador +113%),
  even though their absolute exposure stays low (Bogotá 14 days/yr).

---

## 5. Uncertainty & sensitivity (the honest part)

Generated by `sensitivity.py` from the raw hourly record; see `data/robustness.json`.

**Structural — where is the comfort line?** A ±2 °C reanalysis bias is equivalent to
moving the threshold, so this single test covers both.

| heat threshold | person·days/yr | vs official | 86-yr trend |
|---:|---:|---:|---:|
| 24 °C | 2.02 T | +7% | +2% |
| **26 °C (official)** | **1.88 T** | **0%** | **+2%** |
| 28 °C | 1.71 T | −9% | +4% |
| 30 °C | 1.50 T | −20% | +4% |
| 32 °C (severe) | 1.27 T | −32% | +7% |

**Reading:** sliding the comfort line ±2 °C moves the headline only **−9% / +7%**. Even
counting *only severe* heat stress (>32 °C) leaves **1.27 trillion** person·days. Crucially
the **rising trend is positive at every threshold and steeper for extreme heat** — the
"it's climbing" finding does not depend on the line we drew. (The global trend looks small
because the tropics — most of the total — have been at the ceiling since the 1940s; the
change concentrates in the temperate world and at the severe end.) Cold shows the mirror:
the decline (~−10%) is stable across 7/9/11 °C.

**Sampling — cities as a sample of national climate.** For the 22 countries with ≥2
sampled cities, the coefficient of variation of days/resident across a country's cities
has a **median of ~18%**. Treat that as the per-country sampling uncertainty; it largely
averages down in the global aggregate but is stated honestly per country.

---

## 6. Known limitations (declared on the site too)

1. **Exposure ≠ harm.** We count climatic load, not health outcomes; air-conditioning,
   shade and acclimatization all modulate the actual burden. This is a floor, not a toll.
2. **Fixed comfort band for everyone.** A single 9–26 °C band ignores physiological
   acclimatization (a body in Lagos vs Oslo). §5 bounds the effect; a follow-up with an
   acclimatization-relative band is the clearest next scientific step.
3. **The climate field is sampled, not exhaustive.** The gridded count weighs all of
   humanity (GHS-POP at 0.25°), but its UTCI field is sampled at 427 points (pop-weighted
   mean distance 124 km) and assigned by nearest neighbour. Densifying the field (more CDS
   points, or the full per-km UTCI × GHS-POP product) narrows this further; the 8%
   agreement with the independent national-scaling method bounds the error today.
4. **Pre-satellite reanalysis (1940–1978) carries larger uncertainty** than the satellite
   era; we use the full ERA5-HEAT record (1940→) and treat the 1940s as baseline.
5. Recent years use the Copernicus **near-real-time** UTCI stream; we stop at the last
   complete calendar year.

---

## 7. Reproduce

```bash
# one command (needs a free CDS account in ~/.cdsapirc for the first, downloading run)
make

# offline, using the cached hourly UTCI already in data/raw/utci/:
make robustness      # re-runs the sensitivity/uncertainty analysis (~20 s)
make scale           # re-applies national-population scaling
make serve           # http://localhost:4173/heatline/
```

Data dictionary for the served file (`data/heatline.data.js`, `window.HEATLINE`):

| field | meaning |
|---|---|
| `years` | `[1940 … 2025]` |
| `global.heatPD[i]` / `coldPD[i]` | world person·days in year `i`, national-scaled |
| `countries[].iso3` / `code` / `name` | identifiers |
| `countries[].pop` / `natPop` | national population (World Bank 2023) |
| `countries[].heatPD[i]` / `coldPD[i]` | national person·days per year |
| `meta.coverage` | represented population ÷ 8.1 B |
| `meta.cities` / `countries` | sample size |

---

*Contact for co-authorship or an institutional home: artur.scartazzini@gmail.com.*
