Skip to content

About

Unofficial personal Swiss Golf handicap dashboard for Cloudflare Pages.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Golf Handicap

A public, read-only Swiss Golf handicap dashboard for one owner. The site loads the owner's official WHS record automatically and presents the current handicap, low handicap, counting scores, a differential chart, and the latest 20 rounds.

Important

This is an independent, unofficial project. It is not affiliated with, endorsed by, or supported by Swiss Golf or GolfBox. It uses undocumented upstream behavior that may change or become unavailable without notice.

Live example

See the deployed project at golf-handicap.markwalder.net.

The current scoring record is also ranked from its best to worst Score Differential. The ranked chart identifies counting results, the newest and oldest rounds, and the divider between the eighth and ninth values.

The bottom section estimates how a future Stableford result could change the player's handicap using rating data from the most recent round on each course/tee combination in the public scoring history.

The browser never receives or asks for Swiss Golf credentials. A Cloudflare Pages Function reads them from encrypted secret bindings, fetches the owner's record, and exposes only the normalized handicap data intended for the public dashboard.

Architecture

Browser ──GET /api/whs-record──> Cloudflare Pages Function
                                      │
                                      ├── Workers KV snapshot
                                      │
                                      └── Swiss Golf / GolfBox service
                                          (credentials remain server-side)
  • Static assets live in public/. app.js coordinates configuration and data loading, while dashboard.js assembles small UI component instances from public/components/.
  • Components own their DOM roots and update them with DOM APIs. SVG and upstream-derived tooltip text are never assembled as markup strings.
  • functions/api/whs-record.js is the read-only public API.
  • The Function keeps the last successful record in Workers KV.
  • A record is refreshed from Swiss Golf after 15 minutes.
  • If Swiss Golf fails, the last snapshot is returned as stale data and another upstream attempt is delayed for 15 minutes.
  • Successful API responses may be cached publicly for 60 seconds.
  • The GolfBox token, Swiss Golf username, password, club identifier, and union identifier are never returned to the browser.

This project uses an undocumented Swiss Golf service that can change without notice. It does not submit scores or modify a player profile.

Local development

The default local server uses sanitized sample data and needs no credentials:

npm run dev:mock

Then open http://127.0.0.1:4174/.

Run all dependency-free JavaScript and Python tests with:

npm test
python -m unittest discover -s tests -v

Deploy your own

Each deployment represents one Swiss Golf account and requires its own Cloudflare secrets and Workers KV namespace.

See the deployment and operations guide for the complete fork-and-deploy procedure, Cloudflare Pages and custom-domain DNS configuration, verification, credential rotation, rollback, and troubleshooting.

Public data contract

Everything returned by GET /api/whs-record is intentionally public. Do not deploy the application for an account unless the player accepts publication of the following normalized Swiss Golf data:

  • Player name, home club, handicap, low handicap, soft cap, and hard cap.
  • Current scores and older historicalScores.
  • Round dates, tournaments, courses, tees, hole counts, forms of play, par, Course Rating, Slope Rating, handicapping scores, Score Differentials, PCC, course handicap, resulting handicap, counting status, and adjustments.
  • grossScore, netScore, and the complete holeScores array when Swiss Golf provides those values.

The same public record is persisted in Workers KV as the last successful snapshot. The response and KV snapshot exclude the Swiss Golf username and password, GolfBox token, clubId, and unionId.

The fork owner is responsible for obtaining the player's consent, protecting the account credentials, and complying with applicable service terms and data protection requirements. The MIT license applies to this repository's code; it does not grant permission to use upstream services, data, names, or trademarks.

Score normalization

scoreDifferential is always a finite number or null:

  • For current scores, Swiss Golf supplies a fixed-point value scaled by 10,000. A finite upstream zero is retained as the legitimate value 0; missing, empty, malformed, and non-finite values become null.
  • For historicalScores, scoreDifferential is always null. These are legacy handicap-history rows for which MySwissGolf does not present a Score Differential, even though the upstream response can contain a placeholder Differential: 0. A non-zero value in this array is also treated as unverified rather than claimed to be a computed differential.

totalAdjustment is a finite number or null. Numeric upstream values and numeric strings are normalized without scaling; missing, empty, malformed, and non-finite values become null. TotalAdjustment is an undocumented GolfBox field, and this project does not assign a more specific meaning or unit to it. The sanitized record examined for this decision contained only the string "0", which is exposed as numeric 0.

The historical rule is based on a sanitized July 2026 comparison. All 187 available historical rows, spanning July 2013 through September 2020, contained Differential: 0 across widely varying scores. MySwissGolf displayed the same rows as legacy history with Exact HCP and HCP-relevance columns, omitted Score Differential entirely, and retained duplicate rows returned by the service. Current score rows used the separate WHS presentation and supplied computed fixed-point differentials. No player identifiers, credentials, tokens, or screenshots are retained in the repository.

This changes the earlier public response: historical scoreDifferential: 0 becomes null, and string adjustments such as "0" become numbers. Cached snapshots are migrated to this contract when read.

Export and analysis guidance

Exports, charts, rankings, tooltips, and AI analysis must exclude a scoreDifferential whose value is null and describe it as unavailable when a label is needed. They must not coerce it to zero or infer scratch-level play. A numeric totalAdjustment may be copied as an opaque upstream value, but it must not be given a more specific interpretation unless future evidence establishes the field's semantics.

The handicap history chart uses only Swiss Golf's official per-round newHandicapIndex values across current and legacy rows. It does not reconstruct missing values. Rounds without an official update are omitted from the chart; historical Score Differentials remain labeled unavailable.

Handicap prediction

Predictions run locally in the browser and assume a PCC of 0.0. The course selector includes a course/tee/hole-count combination only when its most recent round has valid par, Course Rating, and Slope Rating values. Its latest eligible combination is selected by default.

For 18-hole Stableford rounds, the projected Score Differential is:

(113 / Slope Rating)
× (par + Course Handicap - (Stableford points - 36) - Course Rating - PCC)

For 9-hole rounds, the same calculation uses 18 points as the baseline and half the PCC. The result for the nine holes played is combined with an estimated expected 9-hole differential:

0.52 × current handicap + 1.2

That estimate was derived from 11 recent current-method 9-hole rows in the examined Swiss Golf record. A linear fit produced 0.5165 × handicap + 1.2469; the simpler formula above had a maximum residual below 0.05 against the official published differentials in those rows. It also reproduces the public WHS examples for handicaps of 14.0 and 15.0.

The new handicap calculation uses the applicable lowest-score table, removes the oldest result once 20 are present, applies an exceptional-score reduction when triggered, and then applies the supplied soft and hard cap values. Future PCC and Handicap Committee adjustments are inherently not predictable, so the UI labels every result as an estimate and Swiss Golf remains authoritative. Player insights are calculated beyond the 13 displayed rows, beginning at zero Stableford points and continuing until the first improving result is found. The player-specific course comparison is the difference between the unrounded Course Handicap and the portable Handicap Index. Its standard-rated benchmark uses a Slope Rating of 113 and a Course Rating equal to par.

Privacy and security model

The handicap record shown by this application is intentionally public. The KV snapshot persists that public record so ordinary page visits do not repeatedly log in to Swiss Golf. Cloudflare processes the Function request and stored snapshot as the hosting provider.

  • Credentials are encrypted Cloudflare bindings and are never sent by browser code.
  • Credentials and GolfBox tokens are not written to application logs or KV.
  • The endpoint accepts GET only and is not a general-purpose proxy.
  • Upstream failures expose a generic error, not upstream response details.
  • The page includes CSP, HSTS, frame, MIME-sniffing, referrer, and permissions protections.
  • No analytics, external fonts, CDN scripts, or third-party browser resources are included.

Command-line reference client

swissgolf_fetch.py remains as a diagnostic and reverse-engineering reference. It implements the same unofficial read-only login and WHS archive flow outside Cloudflare. It is not used by the deployed website.

The recommended form prompts for the password without displaying it:

python .\swissgolf_fetch.py 7000-123456

To save the normalized record locally:

python .\swissgolf_fetch.py 7000-123456 --output .\whs-record.json

The default transport mirrors the Swiss Golf website's CORS proxy. A native client can test the direct GolfBox route using --transport direct.

Project governance

License

The project is available under the MIT License.

About

Unofficial personal Swiss Golf handicap dashboard for Cloudflare Pages.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages