Skip to content

fix: tolerate GHIN response drift (NaN stats + Temporary status)#39

Open
doyled-it wants to merge 1 commit into
n8io:mainfrom
doyled-it:fix/response-schema-drift
Open

fix: tolerate GHIN response drift (NaN stats + Temporary status)#39
doyled-it wants to merge 1 commit into
n8io:mainfrom
doyled-it:fix/response-schema-drift

Conversation

@doyled-it

Copy link
Copy Markdown

GHIN's live API has drifted from the current schema in two places, which causes getScores to throw on real accounts:

1. Statistics percents can be NaN (or "NaN" strings)

On rounds where the user didn't track fairway hits / miss directions, GHIN returns NaN for fairway_hits_percent, missed_{left,right,long,short}_percent. The current float (z.coerce.number()) schema rejects these.

Fix: added a floatSafe preprocessor that normalizes NaN / "NaN" / empty string / null to null, then validates as z.number().nullable(). Used only on the five fields that GHIN empirically sends as NaN; all other percents still use strict float.

2. score.status can be "Temporary"

GHIN introduced a pending state for scores that haven't been validated by the handicap committee yet. The enum only knew "Validated" | "UnderReview".

Fix: added "Temporary" → "TEMPORARY" to the enum and status map.

Repro

const ghin = new GhinClient({ username, password })
await ghin.golfers.getScores(Number(username), { count: 100 })
// ZodError: invalid_type at scores[N].statistics.fairway_hits_percent
//           invalid_enum_value at scores[M].status: received 'Temporary'

Scope

Schema-only change. No behavior change for responses that already fit the old schema. No dependencies touched.

GHIN's live API has diverged from the schema in two places:

1. statistics.{fairway_hits,missed_left,missed_right,missed_long,missed_short}_percent
   come back as NaN (or "NaN" strings) when a round has no tracked fairway/miss
   data. z.coerce.number() chokes on NaN. Added a floatSafe preprocessor that
   normalizes NaN / "NaN" / empty string to null, and used it on those fields.

2. scores[*].status now includes 'Temporary' for pending scores (before the
   handicap committee validates). Added 'Temporary' -> 'TEMPORARY' to the
   enum and status map.

Does not touch any other behavior.
@doyled-it doyled-it requested a review from n8io as a code owner April 21, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant