尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Golf OS is a golf course-management and tracking app built on a shot-dispersion simulator. Golfers consistently underestimate how widely they miss, then pick clubs, aim points and tees for the shot they hoped to hit rather than the pattern they produce. The app plans each shot from your real miss pattern, keeps score, tracks your handicap, and shows where your bag leaks strokes. The simulator generates statistically realistic shots, validated against a golfer's launch-monitor data, so the analytics could be built and tested before real users had logged a single shot.
Live app: golf-os-ten.vercel.app
Built as a school capstone extension by a 1.9-4 handicap golfer whose own launch-monitor data calibrates and validates the model. Python and Claude Code were used for the coding.
Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS and Supabase, deployed on Vercel. Three tabs:
| Tab | Route | What it does |
|---|---|---|
| Play | / | The course planner. Search any course, stand anywhere on a satellite map, and see which club and aim give the fewest expected strokes for your dispersion. Start a round to keep score hole by hole. Course shapes come from OpenStreetMap; you can mark hazards the map is missing. |
| Rounds | /rounds | Logged rounds with differentials. The handicap index follows the World Handicap System and recalculates when rounds change. |
| You | /you | Handicap, strokes gained and trends (/you/stats), practice and drills (/you/practice), your bag (/you/bag: your misses, compare, shots) and settings (/you/settings). |
Other routes: /welcome (first-run setup), /login, /offline. A round in progress is saved on the device and syncs when the connection returns.
| Piece | What it answers |
|---|---|
| Synthetic golfer model (Python) | "Generate realistic shots for a golfer of handicap H, or with these carries, or fitted to my real shots." |
| Statistical validation | "Do the simulated shots look like real ones, including on sessions the model never saw? How many real shots does a golfer need before a fitted profile can be trusted?" |
| Aim-point optimizer | "Given my dispersion and this hole's hazards, is aiming at the pin actually the best strategy, and by how much?" |
Browser tools (Play, /you/bag) | Course planner with club and aim recommendations, live dispersion charts, two-golfer comparison, and a tee recommender with real course lookup. |
| Supabase backend | Golfer profiles, simulated and real shots, and SQL views for dispersion stats and club-gapping analysis. |
Two-sample Kolmogorov–Smirnov tests on carry and offline distance:
Wedges are the weakest clubs (the 56° is borderline, p ≈ 0.06 held out). This shows the model generalizes across sessions for one golfer, not across golfers. See Limitations.
Bootstrap 95% confidence intervals on mean carry reach ±2 yards at about 35 shots for an iron and about 140 for the driver, matching the textbook n ≈ (1.96·s / h)². The driver needs about four times as many shots because its spread is about twice as large. Clubs with fewer than ~15–20 real shots should be treated as low-confidence.
A grid search over 55 aim points (3,000 simulated shots each) on a hole with a bunker front-left and water right, then re-tested on a fresh, paired 10,000-shot sample. Left panel: whole hole. Middle: zoom on the pin versus the recommended aim. Right: expected strokes for every aim point searched.
| Golfer (7-iron) | Aim at pin | Recommended aim | Strokes saved per approach (95% CI) |
|---|---|---|---|
| Calibrated 1.9–4 handicap | 2.673 | 5 yd right of pin, pin high | 0.024 (0.014 – 0.033), p < 0.001 |
| One synthetic 22 handicap | 2.877 | 5 yd left, 5 yd short | 0.037 (0.027 – 0.046), p < 0.001 |
Why does the calibrated golfer aim right? His natural miss is about 4 yd left of the aim line, so aiming 5 yd right re-centers the pattern on the pin (though it puts a bit more ball near the water). The saving is real but small.
A single random golfer is not "the 22 handicap", so the study was repeated for 200 random golfers at each of handicaps 3, 10 and 22:
The hole geometry and stroke costs are illustrative placeholders, not a real course or a cited strokes-gained table. "Expected strokes" means strokes remaining after the approach shot lands, so it excludes the approach shot itself. The results demonstrate the method and its statistics; they are not yet course-accurate advice. Full numbers:
STATISTICAL_ANALYSIS.md.
Every shot is built from layered random effects (a small mixed-effects model):
offline = carry × tan(start_line) + curve. Start line is an angle (scales with club distance); curve is a percentage of carry. This identity matched measured offline distance at r ≈ 0.9998 across three independent datasets.A golfer can be built from a handicap, a handicap plus a few known carries, a skill tier, or a profile fitted directly to real shots:
SyntheticGolfer.from_handicap(8.5)
SyntheticGolfer.from_handicap_and_carries(8, {"Driver": 250, "7-Iron": 160})
SyntheticGolfer.from_profile_json("reference_data/synthetic_profile.json")
Parameters are anchored to published sources where they exist (Broadie's Golfmetrics direction-error SDs, TrackMan 2024 Tour carries, GOLFTEC's dispersion-by-handicap study, Shot Scope/Arccos aggregates) and to the author's own launch-monitor data otherwise. The evidence-versus-assumption breakdown is in shot-pattern-simulator/HANDOFF.md.
| Folder | What is in it |
|---|---|
shot-pattern-simulator/ | The Python project. The simulator model, calibration, statistical validation, and aim-point analysis. Everything in the research results is produced here. |
shot-pattern-simulator/reference_data/ | Input data: synthetic shots (synthetic_shots.csv) and the profile fitted to them (synthetic_profile.json), both reproducible with make_synthetic_fixtures.py, and a reference synthetic dataset used as a sanity check. Real launch-monitor data is not committed. |
shot-pattern-simulator/output/ | Generated plots and CSVs (git-ignored; recreate by running the scripts). |
app/ | The Next.js routes. app/api/courses/ proxies the free OpenGolfAPI for course search. |
components/ | React components, grouped by screen (planner/, play/, rounds/, you/, bag/, simulator/). |
lib/ | Tested TypeScript logic: golfer/ (browser port of the handicap model), course/ (lies, strokes gained, club ranking), dispersion/, rounds/ (including the World Handicap System calculation), tbox/ (tee recommendation). |
supabase/ | SQL schemas, analytics views, and example queries. Run manually in the Supabase SQL editor. |
shot-pattern-simulator/)| Script | Purpose | Run it |
|---|---|---|
synthetic_golfer.py | The model. The SyntheticGolfer class and all calibration tables. About 90% of the project's logic. | imported by the others |
generate_shots.py | Command-line front end: choose a golfer, generate shots to CSV, draw the dispersion plot. | python generate_shots.py --handicap 8 --carry Driver=250 --n-shots 10000 --show |
menu.py | Interactive version of the above with numbered prompts; prints the equivalent command. | python menu.py |
parse_sessions.py | Turns raw launch-monitor session exports into one tidy shots CSV and flags partial swings. | python parse_sessions.py "<folder>" --out output/real_shots.csv |
calibrate.py | Fits the model's parameters from real shots (start line, curve, carry spread, curve→carry slope) into a JSON profile. | python calibrate.py reference_data/synthetic_shots.csv --out my_profile.json --min-shots 10 |
validate_against_reference.py | Cross-check against an independent reference synthetic dataset (not ground truth). | python validate_against_reference.py |
statistical_analysis.py | Validation suite: t-test and KS test, bootstrap CIs, power analysis, distribution-overlap figure, held-out (by session) validation, uncalibrated baseline. | python statistical_analysis.py |
aim_point_optimizer.py | Grid search of 55 aim points on an illustrative hole, fresh-sample paired confirmation, and the three-panel hole figure. | python aim_point_optimizer.py |
aim_point_population.py | Repeats the aim-point study across 200 random golfers at handicaps 3, 10 and 22. | python aim_point_population.py |
Detailed numeric results for the last three are in STATISTICAL_ANALYSIS.md.
| Route | Needs seeded Supabase data? |
|---|---|
/you/bag/misses — dispersion charts with polygon QA | Yes |
/you/bag/compare — two-golfer overlay + sample-size convergence demo | Yes |
/ — the course planner | Optional (falls back to a handicap-based golfer); course shapes come from OpenStreetMap |
cd shot-pattern-simulator
pip install -r requirements.txt
python menu.py
Regenerate every figure and result table:
python statistical_analysis.py
python aim_point_optimizer.py
python aim_point_population.py
npm install
npm test # 841 unit tests in 66 files
npm run dev # http://localhost:3000
Create a free Supabase project and add your keys to .env.local (never committed):
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # server only: course cache and API rate limits
Then run the SQL files in supabase/ (and supabase/migrations/) in the Supabase SQL editor. Personal tables are per-user with row-level security, so sign in at /login to log rounds and practice. The simulator pages need simulated_shots_schema.sql, real_shots_schema.sql and simulated_shots_views.sql. npm run dev:clean clears .next before starting.
A one-command seed script is not included yet. The seeded tables were generated from the calibrated profile in the author's own (uncommitted) launch-monitor data.
The schema lives in supabase/migrations/, applied in filename order with the Supabase CLI. 0001_baseline.sql is the whole schema as of the move to tracked migrations; the loose SQL files it replaced are in supabase/legacy/ for history.
supabase login
supabase link --project-ref <your-project-ref>
supabase db push --include-all
On a new, empty project that is all you need. On the existing project, which already has the 0001 schema, mark it applied first instead of running it:
supabase migration repair --status applied 0001
supabase db push --include-all
--include-all is needed because the older 2026... migrations sort after 0001 to 0004. If those two were applied by hand in the SQL editor, repair them as applied too (supabase migration repair --status applied 20261012000000 20261012000100). Add new changes as a new file in supabase/migrations/ (supabase migration new <name>), never by editing an applied one.
Stated up front, because they bound what the results can claim:
shot-pattern-simulator/reference_data/synthetic_shots.csv is synthetic: 589 generated shots, 13 clubs, 23 sessions, with the shape and statistics of a launch-monitor export (a 3-handicap from a fixed seed). The author's real shots are kept out of the repo.Released under the MIT License. Real-shot data and course data carry the attributions listed above.
docs/images/| The figures shown in this README. |