I play golf. I am not good at golf. But I have a Garmin Approach R10 launch monitor, a Python interpreter, and too much free time, so naturally I spent way more time building a dashboard to analyze my swing data than I did actually swinging a club.
The result is jgamblin/golf, a self-hosted analytics pipeline that turns your Garmin Golf app CSV exports into a multi-page GitHub Pages dashboard with zero ongoing cost and no third-party accounts required.
The Problem
The Garmin Golf app is fine. You hit balls on the range, you see your carry distances, you feel bad about your smash factor, you go home. But the data is all locked in the app, and the app doesn’t really want to help you get better. It wants to show you pretty numbers and sell you more Garmin products.
I wanted to actually use my data. So I started exporting CSVs and staring at them, which is honestly just how most of my projects start.

What It Does
Drop your range session CSV exports from the Garmin Golf app into a Data/ folder, run one command, and get a full multi-page static dashboard:
- Overview: Session trend, miss-direction chart, session table
- Club Lab: Per-club drilldown with path/face infographic, path cloud scatter, consistency heatmap
- Session Replay: Full stat breakdown for every session
- Gapping: Carry ladder, target-error chart, progress to goal chart, environment context
- Coaching: Ranked recommendations with confidence scores
- Data Quality: Flagged-shot trends and miss reasons
Push to GitHub and GitHub Actions automatically deploys everything to GitHub Pages on every commit. The live version of my own data is at jgamblin.github.io/golf.
The Parts I’m Actually Proud Of
Consistency Score (0–100). Rather than eyeballing my shot dispersion and feeling vague shame, the pipeline calculates a single per-club number from four R10 signals: carry distance CV, lateral deviation standard deviation, face-to-path standard deviation, and swing tempo CV. The weights are calibrated against your own bag so the score is comparable across sessions even if Garmin updates their firmware and changes something upstream.
Personal Smash Factor Benchmark. Every generic golf app compares your smash factor to some industry average that was probably set by a Tour player. This pipeline calculates your personal 90th-percentile smash factor per club from your own historical shots and only flags a strike-quality issue when you fall more than 0.08 below your own best. The benchmark grows as you improve, which I find both motivating and annoying.
Gapping Analysis. This one is genuinely useful. The pipeline compares carry distances across your bag pair-by-pair and flags bunching (clubs within 7 yards of each other, or whose carry standard-deviation windows overlap) and distance voids (gaps greater than 25 yards). It also generates aspirational carry targets so you can see a “Progress To Goal” slope chart for every club in your bag.
Environment Context. Temperature, humidity, air pressure, and air density are all pulled from your session data to calculate how many yards of carry you gain or lose per degree Fahrenheit. Turns out I hit it shorter in the cold. Groundbreaking.
Explainable Recommendations. The coaching page ranks up to eight recommendations per build, and every single one includes a confidence score, a confidence reason, and a priority explanation. No black-box models, no vibes. Just your R10 data with thresholds I can tell you about:
| Signal | Threshold |
|---|---|
| Avg lateral deviation | ≥ 10 yds → reduce miss |
| Face-to-path std dev | ≥ 6° → tighten clubface variance |
| Smash vs. personal 90th-pct | gap ≥ 0.08 → improve contact |
| Tempo std dev | ≥ 0.35 → stabilise tempo |
Quick Start
# Requires Python 3.11+
pip install -e .
# Export a session CSV from the Garmin Golf app → drop it in Data/
golf-build build --data-dir Data --output-dir output/site
open output/site/index.html
If you use uv (and you should):
uv sync
uv run golf-build build
One More Thing
The pipeline also works with Trackman 4, Rapsodo MLM2, SkyTrak, and FlightScope/Mevo+. Column name mapping is handled through a single field_aliases.yaml config file, so adding a new monitor or adapting to a Garmin app update that renames columns is a one-liner, no Python required.
All the code is at github.com/jgamblin/golf. If you have a launch monitor and too much data and not enough insight, give it a shot (pun intended, I’m sorry).
As always, reach out on Twitter/X at @jgamblin if you have questions or ideas.