Atlantic & Eastern Pacific Hurricane ACE Tracker
Tracks Accumulated Cyclone Energy (ACE) for Atlantic and Eastern Pacific hurricane seasons with storm-by-storm data from 1991 onward. Publishes a web dashboard rebuilt every 3 hours from NOAA/NHC data. It's a hobby data site, not a forecast: for watches, warnings, and forecasts, go to the National Hurricane Center.
| Page | URL |
|---|---|
| Current Season Dashboard | https://aceofcanes.com |
| Season History (1991–present) | https://aceofcanes.com/history.html |
| Records Since 1991 | https://aceofcanes.com/records.html |
| What Is ACE? (explainer + calculator) | https://aceofcanes.com/what-is-ace.html |
Rebuilt every 3 hours, year-round. Hurricane seasons: Eastern Pacific May 15 – Nov 30, Atlantic Jun 1 – Nov 30. Each basin shows when its storm data is from ("Best-track data as of …"), which can lag the page build by a few hours; the build time is in the footer.
- Live season tracking — current season ACE rebuilt every 3 hours from NHC's real-time best track, with a data-as-of stamp (and relative "x hours ago") next to each basin's numbers
- Honest labels — in-season ACE is marked preliminary (it's revised in NOAA's spring HURDAT2 release), and landfalls estimated from a live track are marked est.
- Dual basin — Atlantic and Eastern Pacific, toggle between them on each page
- Dark/light mode — defaults to system preference for first-time visitors, persisted across sessions after that
- Season progress bar — shows current day and percent complete
- Landfall data — each storm shows where it made landfall and its intensity at the moment of impact (not just peak). Multi-landfall storms show all locations (e.g.
Florida (Cat 1) · Louisiana (Cat 3)). Storms that never made landfall are labeled Fish Storm. For the current season, landfalls come from the preliminary track and are marked est. until NHC's post-season report - NHC development alert — amber banner appears when NHC is tracking an area with Medium (≥40%) or High (≥70%) formation chances, with direct link to the NHC Tropical Weather Outlook
- Honest season comparisons — Season Insights show both same-date historical averages (what's typical by this point in the season) and full-season averages, so early-season numbers aren't misleadingly compared against totals that take six months to accumulate
- Storm track maps — interactive Leaflet maps per storm with intensity color-coding; expandable inline on the dashboard, with a loading indicator while tiles load
- Spaghetti model tracks — active storms overlay multi-model forecast tracks on the storm map, toggleable per storm. Each model is labeled with its run time (e.g.
GFS 18Z Sep 26), and a model whose latest run is more than 24 hours behind the newest run for that storm is left off - Wind speed unit toggle — switch the dashboard between knots, mph, and km/h; persisted across visits, defaults to knots
- Shareable storm links — copy a direct link to any storm on the dashboard via the 🔗 button on each row; opening the link auto-switches basin and expands that storm
- NHC forecast cone — active storms show their official 5-day forecast cone graphic with its advisory time, fetched from NHC once per run and served from our own domain (not hotlinked)
- Season history page — all seasons 1991–present in a sortable table with classification badges, top-5 highlights, per-year storm accordions with landfall data, and a long-term average row. Filter by decade (shareable links like
history.html#pacific&decade=2010s) - NHC storm reports — each storm on the history page links to its official NHC Tropical Cyclone Report, with season-level report links as a fallback
- Landfall ACE share — how much of each season's ACE came from storms that made landfall at tropical-storm strength or stronger vs. fish storms (a storm that only crossed land as a depression counts as a fish storm), compared with the 1991–present average
- Records page — highest single-storm ACE, longest-lived storm, strongest landfall, and earliest/latest-forming named storms since 1991 (formation = first tropical/subtropical storm point, not the first track point)
- Records in Play — the dashboard lists season records being set or within reach (latest first hurricane/major, lowest or most ACE for the date, fastest to 100 ACE), based on history only, never a forecast
- What Is ACE? page — formula, worked example, NOAA thresholds, an ACE calculator (kt, mph, or km/h), and facts from the site's own data
- Similar seasons — finds the 3 closest historical seasons by ACE accumulated through the same date
- Pace rank — shows where this season ranks among all historical seasons at this same calendar date, alongside the full-season rank
- ACE pace chart — plots the season's cumulative ACE against historical climatology (mean and 25th/75th percentile band) and last year's pace
- On this day in hurricane history — Season Insights surface the strongest historical storm (by ACE) active on today's calendar date in a past season
- NOAA classifications — Below Normal / Near Normal / Above Normal / Extremely Active
- Season projection widget — shows the daily ACE rate needed for the rest of the season to reach each remaining NOAA classification by Nov 30 (arithmetic, not a forecast)
- Accessible — keyboard-operable sort headers and storm/season rows (with
aria-sort,aria-expanded,aria-pressed), a visible focus ring on every page, and text and badge colors that meet WCAG AA contrast in both themes - No third-party CDN — Leaflet and Chart.js are self-hosted; if either ever fails to load, the map or chart says so instead of staying blank
Accumulated Cyclone Energy (ACE) measures total hurricane season activity by combining storm intensity and duration. It's calculated by squaring the maximum sustained wind speed (in knots) at each 6-hour synoptic time when the system is at tropical storm strength or higher, then summing across all storms.
Formula: ACE = Σ(V²max) × 10⁻⁴
A long-lived major hurricane contributes far more ACE than a brief tropical storm. NOAA uses seasonal ACE totals to classify activity levels.
| Classification | ACE |
|---|---|
| Extremely Active | ≥ 159 |
| Above Normal | 126 – 159 |
| Near Normal | 73 – 126 |
| Below Normal | < 73 |
- Python 3.11 or 3.12 (3.13+ isn't supported yet — see Troubleshooting)
- Internet connection (for fetching live data)
git clone https://github.com/jeremypfi/ace-tracker.git
cd ace-tracker
pip3 install -r requirements.txtpython3 ace_tracker.pyGenerates in data/:
ACE_Dashboard.html— current season dashboard (open in any browser)history.html— all-seasons history pagerecords.html— storm records since 1991what-is-ace.html— ACE explainer and calculator
python3 test_ace_tracker.py # unit + smoke tests
python3 verify_pr.py # unit tests + syntax check + a full live tracker runAll tests must pass before committing. Use the /pre-commit skill in Claude Code for the full checklist.
ace-tracker/
├── ace_data.py # Data fetch, ACE calc, plain-text report generation
├── ace_html.py # Dashboard, history, records, and What Is ACE? page rendering
├── ace_tracker.py # CLI entrypoint — wires ace_data.py + ace_html.py together
├── test_ace_tracker.py # unit + smoke tests
├── verify_pr.py # Consolidated PR check: tests + syntax check + a live tracker run
├── requirements.txt # Python dependencies
├── ace.png # Site logo (favicon + OG image)
├── ace_preview.png # 1200x630 social share card (copied into data/ at publish time)
├── landfall_cache.json # Cached landfall geocoding results (gitignored, built by CI)
├── CNAME # Custom domain for GitHub Pages (aceofcanes.com)
├── robots.txt # Search engine crawl rules
├── sitemap.xml # Sitemap for search engine indexing
├── CLAUDE.md # Project instructions for Claude Code
├── CONTRIBUTING.md # Contribution guidelines
├── images/ # Reference/marketing images
├── .claude/skills/ # /pre-commit, /season-start, /verify-data
├── .github/
│ ├── workflows/
│ │ ├── tests.yml # CI: runs tests on push/PR (Python 3.11, 3.12)
│ │ └── publish.yml # Scheduled: generates and deploys dashboard every 3 hours
│ ├── dependabot.yml # Automated dependency updates
│ └── CODEOWNERS # @jeremypfi must approve all PRs
├── SECURITY.md
└── data/ # Published as the site (html generated + gitignored; ace.png and vendor/ committed)
├── ace.png
├── ACE_Dashboard.html # copied to index.html at publish time
├── history.html
├── records.html
├── what-is-ace.html
├── vendor/ # Self-hosted Leaflet 1.9.4 and Chart.js 4.5.1, with licenses
└── cones/ # NHC forecast cone images, fetched fresh each run (gitignored)
- NOAA HURDAT2 — official historical best-track database (1991–present) via the Tropycal library
- NHC Real-time Best Track — current season preliminary data (
include_btk=Truein Tropycal), updated continuously during active storms - NHC Tropical Weather Outlook — XML feeds (
TWOAT.xml/TWOEP.xml) parsed every run for active disturbances with development potential - NHC CurrentStorms.json — active storm metadata used to locate and cache each storm's 5-day forecast cone graphic
- NOAA CPC — season classification thresholds and 1991–2020 climatological normals
- Natural Earth — 10m shapefiles (via Cartopy) for offline landfall geocoding
Historical storms (completed seasons) use the official HURDAT2 'L' landfall markers, which record the exact time and position of each landfall. These markers aren't added to the real-time best-track data until the post-season analysis, so active-season storms use a geographic fallback: track points are checked against Natural Earth shapefiles using exact point-in-polygon containment. Those landfalls are shown as est., and only landfalls at tropical-storm strength or stronger count toward the landfall ACE share. Results are cached in landfall_cache.json and persisted via GitHub Actions cache so geocoding only runs for new or updated storms. The Natural Earth shapefiles themselves (~20 MB) are also cached by the publish workflow (key naturalearth-10m-cultural-v1; bump the suffix to force a fresh download).
Key constants, mostly in ace_data.py (OUTPUT_FOLDER is the one exception — it stays in ace_tracker.py since only main() uses it):
| Constant | Purpose |
|---|---|
START_YEAR |
Earliest year included in historical data (default: 1991) |
OUTPUT_FOLDER |
Where HTML files are saved (default: data/) — defined in ace_tracker.py |
LANDFALL_CACHE_PATH |
Path to the landfall geocoding cache (default: repo root) |
BASINS |
Normal ACE values and average storm counts per basin |
BACKUP_DATA |
Fallback season data used if NOAA is unreachable |
SPAGHETTI_MODELS |
Forecast models drawn on active-storm maps |
SPAGHETTI_MAX_RUN_LAG_HOURS |
A model whose latest run trails the storm's newest model run by more than this is left off the map (default: 24) |
Script can't fetch data
The tracker falls back to BACKUP_DATA automatically. You'll see:
✗ Error loading Tropycal data → Using backup data (yearly totals only)
Landfall cache miss on first CI run
The landfall_cache.json is built on the first run and cached by GitHub Actions. The first run after a fresh clone will geocode all historical storms (~1 min extra). Every subsequent run reads from cache.
"Dropped N malformed row(s) … from HURDAT2 file" warning NOAA's HURDAT2 file sometimes ships rows with unparseable lat/lon fields, which would crash Tropycal's parser for the whole basin. The tracker drops those rows and names the storms they belong to, and says whether any is from 1991 onward. As of Sept 2026 they are AL211969 and AL231975: "all before 1991, so no effect on this site's data."
pkg_resources deprecation warning
Tropycal uses pkg_resources which is deprecated in setuptools 81+ and removed in setuptools 82+. requirements.txt pins setuptools<85 as a workaround, and CI excludes Python 3.13+ for the same reason. Monitor Tropycal releases for a fix.
- Built with Claude Code (Anthropic)
- Data from NOAA National Hurricane Center via Tropycal
- Maps powered by Leaflet (BSD-2) and the pace chart by Chart.js (MIT), both self-hosted from
data/vendor/with their licenses. To upgrade one, add a new versioned folder there, update the path andintegrityhash inace_html.py, andtest_ace_tracker.pychecks the file matches the hash. - Inspired by hurricane tracking communities
Questions or issues? Open an issue on GitHub or reach out to @jeremypfi
0 comments
log in to comment.