Fix 9-hole handicap calculation when back-nine holes are missing from DB

When only front-nine holes are entered in course_holes (common setup),
course_par_full summed to ~36 instead of ~72, inflating the 9-hole
playing handicap by roughly 2x before halving. Fix by using
course_par_played×2 as the 18-hole par estimate in both the backend
create_game and the new_game confirmation preview.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rolf
2026-03-23 21:31:10 +01:00
parent 2c0717948a
commit 26c3280a92
2 changed files with 8 additions and 6 deletions
+3 -4
View File
@@ -292,7 +292,6 @@ async def create_game(request: Request, holes_count: int = Form(...),
# Pre-fetch course holes for handicap calculation # Pre-fetch course holes for handicap calculation
course_par_played = 0 # par for the holes being played course_par_played = 0 # par for the holes being played
course_par_full = 0 # par for all holes on the course (needed for 9-of-18 halving)
course_total_holes = holes_count # assume matches unless we find otherwise course_total_holes = holes_count # assume matches unless we find otherwise
hole_stroke_indices: dict[int, int] = {} hole_stroke_indices: dict[int, int] = {}
if course_id: if course_id:
@@ -305,14 +304,14 @@ async def create_game(request: Request, holes_count: int = Form(...),
(course_id,), (course_id,),
) as c: ) as c:
for row in await c.fetchall(): for row in await c.fetchall():
course_par_full += row["par"] or 0
if row["hole_number"] <= holes_count: if row["hole_number"] <= holes_count:
course_par_played += row["par"] or 0 course_par_played += row["par"] or 0
if row["stroke_index"] is not None: if row["stroke_index"] is not None:
hole_stroke_indices[row["hole_number"]] = row["stroke_index"] hole_stroke_indices[row["hole_number"]] = row["stroke_index"]
# For a 9-hole game on an 18-hole course the tee ratings are 18-hole values. # For a 9-hole game on an 18-hole course the tee ratings are 18-hole values.
# Per WHS: compute 18-hole course handicap, then halve it. # Per WHS: compute 18-hole course handicap using played-par×2 as the 18-hole par, then halve.
# Using played-par×2 avoids relying on back-nine holes being present in course_holes.
nine_of_eighteen = (holes_count == 9 and course_total_holes == 18) nine_of_eighteen = (holes_count == 9 and course_total_holes == 18)
played_holes = list(range(1, holes_count + 1)) played_holes = list(range(1, holes_count + 1))
@@ -331,7 +330,7 @@ async def create_game(request: Request, holes_count: int = Form(...),
playing_handicap = None playing_handicap = None
strokes_allocation_json = None strokes_allocation_json = None
par_for_formula = course_par_full if nine_of_eighteen else course_par_played par_for_formula = course_par_played * 2 if nine_of_eighteen else course_par_played
if hc is not None and tee_id and par_for_formula: if hc is not None and tee_id and par_for_formula:
async with db.execute("SELECT slope, rating FROM course_tees WHERE id = ?", (tee_id,)) as c: async with db.execute("SELECT slope, rating FROM course_tees WHERE id = ?", (tee_id,)) as c:
tee_row = await c.fetchone() tee_row = await c.fetchone()
+5 -2
View File
@@ -65,9 +65,12 @@
const player = this.getPlayer(uid); const player = this.getPlayer(uid);
const tee = this.getTee(uid); const tee = this.getTee(uid);
const course = COURSES_DATA.find(c => c.id === this.courseId); const course = COURSES_DATA.find(c => c.id === this.courseId);
if (!course || !tee || player.handicap == null || !course.par) return null; if (!course || !tee || player.handicap == null) return null;
const nineOfEighteen = parseInt(this.holesCount) === 9 && course.holes === 18; const nineOfEighteen = parseInt(this.holesCount) === 9 && course.holes === 18;
const ph = Math.round(player.handicap * (tee.slope / 113) + (tee.rating - course.par)); // For 9-of-18, use par_9×2 as the 18-hole par so back-nine holes don't need to be in DB.
const par = nineOfEighteen ? course.par_9 * 2 : course.par;
if (!par) return null;
const ph = Math.round(player.handicap * (tee.slope / 113) + (tee.rating - par));
return nineOfEighteen ? Math.round(ph / 2) : ph; return nineOfEighteen ? Math.round(ph / 2) : ph;
}, },
goToConfirm() { this.step = 3; }, goToConfirm() { this.step = 3; },