This repository contains statistical analysis of fatality data recorded in U.S. National Parks, sourced from the National Park Service. This work is conducted with respect and sensitivity toward those who lost their lives. The intent is purely analytical to better understand patterns that may inform safety awareness and policy and is not meant to sensationalize or trivialize any individual's death.
  • JavaScript 51.8%
  • Python 19.4%
  • CSS 14.6%
  • HTML 13.6%
  • Nix 0.6%
Find a file
paul 4fa84ad54e
All checks were successful
Deploy to Forgejo Pages / build-and-deploy (push) Successful in 18s
Updating graphs and axis ticks to ints
2026-05-04 19:53:59 -04:00
.forgejo/workflows Some big updates and adding map 2026-05-04 14:03:51 -04:00
content Adding new site! 2026-05-04 01:45:01 -04:00
data Some big updates and adding map 2026-05-04 14:03:51 -04:00
layouts Updating graphs and axis ticks to ints 2026-05-04 19:53:59 -04:00
scripts Making map boundary 2026-05-04 19:48:09 -04:00
static Updating graphs and axis ticks to ints 2026-05-04 19:53:59 -04:00
.gitignore Some big updates and adding map 2026-05-04 14:03:51 -04:00
hugo.yaml Fixing url 2026-05-04 01:52:26 -04:00
README.md Some big updates and adding map 2026-05-04 14:03:51 -04:00
requirements.txt Adding new site! 2026-05-04 01:45:01 -04:00
shell.nix Adding new site! 2026-05-04 01:45:01 -04:00
TODO.txt Making map boundary 2026-05-04 19:48:09 -04:00

NPS Mortality Dashboard

A static Hugo site that visualizes deaths in U.S. National Parks using the public NPS mortality dataset (CY 2007 — present).

Live site: https://nps.pub.opworks.org/

Disclaimer

This project is independent and not affiliated with, endorsed by, or sponsored by the U.S. National Park Service or any government agency. It is a personal visualization of data the NPS publishes openly at https://www.nps.gov/aboutus/mortality-data.htm.

Each row in the source spreadsheet represents a person who died. The intent of this dashboard is informed risk awareness and transparency — not spectacle. The source dataset does not contain names or other personally identifying information, and none is added here. If anything on the deployed site is inaccurate or feels misrepresented, please open an issue.

If you are in crisis, contact the U.S. Suicide & Crisis Lifeline by calling or texting 988.

What it shows

  • Total deaths (filtered)
  • An interactive OpenStreetMap of parks, sized by death count, with click filtering and "filter to visible parks" by current map bounds
  • Deaths by year (bar) — click a year to drill into months
  • Top 5 recreational activities (bar)
  • Deaths by age range (bar)
  • Unintentional deaths by cause group (bar)
  • Medical deaths by type (bar)
  • Intentional deaths by cause group (bar)
  • Deaths by intent (pie)
  • Deaths by sex (pie)

All charts and the map respond to the filter bar at the top — year range, intent, sex, age range, activity, park. Every chart is itself clickable to toggle the corresponding filter.

How it works

  1. scripts/build_data.py reads the source .xlsx from data/ and writes a normalized static/data/mortality.json. Categorical values are lower-cased and trimmed during normalization so variants like "Driving " and "Driving" collapse to a single key. The frontend re-capitalizes the first letter for display.
  2. scripts/geocode_parks.py looks up unique park names against the OpenStreetMap Nominatim API (≤1 request/second per their ToS) and caches results in data/park_locations.json. Subsequent runs only geocode parks that aren't in the cache. build_data.py then copies the cache into static/data/park_locations.json for the frontend.
  3. Hugo ships the JSON as a static asset. The dashboard (layouts/index.html
    • static/js/dashboard.js) loads it client-side and renders charts with Chart.js; the map (static/js/map.js) uses Leaflet with OpenStreetMap tiles.
  4. CI (.forgejo/workflows/pages.yml) re-runs the build script, geocodes any new parks, builds the site, and deploys to Forgejo Pages on every push to main.

Local development

nix-shell
python3 scripts/build_data.py     # one-time: generate the JSON
hugo server -D --bind 0.0.0.0     # http://localhost:1313/

Without Nix

You need Hugo (extended, ≥ 0.147) and Python 3.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

python3 scripts/build_data.py
hugo server -D --bind 0.0.0.0

Project layout

.
├── data/                              # source spreadsheet (committed)
│   ├── NPS-Mortality-Data-...xlsx
│   └── park_locations.json            # geocoded park lat/lon cache
├── scripts/
│   ├── build_data.py                  # xlsx → static/data/mortality.json
│   └── geocode_parks.py               # Nominatim → data/park_locations.json
├── static/
│   ├── css/style.css
│   ├── js/dashboard.js
│   ├── js/map.js
│   └── data/                          # generated, git-ignored
├── layouts/
│   ├── _default/baseof.html
│   ├── partials/{head,header,footer}.html
│   └── index.html                     # the dashboard
├── content/_index.md                  # placeholder home content
├── hugo.yaml                          # Hugo config
├── shell.nix                          # Nix dev shell
├── requirements.txt                   # Python deps (openpyxl)
└── .forgejo/workflows/pages.yml       # CI deploy

Updating the data

When NPS publishes a new spreadsheet:

  1. Drop the new file into data/ (replace the old one).
  2. Update the path in scripts/build_data.py (SRC = ...) if the filename changed.
  3. Commit and push — CI rebuilds and redeploys.

Deployment

Pushed to main triggers .forgejo/workflows/pages.yml, which:

  • Checkout code
  • Installs Hugo, runs hugo --minify
  • Build Site Data python3 scripts/build_data.py
  • Build static site
  • Publishes public/ to Forgejo Pages at nps.pub.opworks.org:8889

Data source

National Park Service Mortality Data, Calendar Years 2007 present.