Add playing handicap and stroke allocation to game summary
- Calculate course handicap per player (WHS formula) in game_summary - Show Playing Handicaps card with HI, HC, and tee per player - Mark stroke-receiving holes with accent dots in strokes and skins scorecards - Add HC footer row to strokes scorecard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+28
-1
@@ -280,6 +280,32 @@ async def game_summary(game_id: str, request: Request, user: dict = Depends(get_
|
||||
if row["stroke_index"] is not None:
|
||||
hole_stroke_indices[row["hole_number"]] = row["stroke_index"]
|
||||
|
||||
# Playing handicap per player: HI × (slope / 113) + (course_rating − course_par)
|
||||
course_par = sum(hole_pars.get(h, 0) for h in holes if h in hole_pars)
|
||||
playing_handicaps: dict[str, int | None] = {}
|
||||
for p in players:
|
||||
hc = p.get("handicap_snapshot")
|
||||
slope = p.get("slope")
|
||||
rating = p.get("tee_rating")
|
||||
if hc is not None and slope and rating and course_par:
|
||||
playing_handicaps[p["id"]] = round(hc * (slope / 113) + (rating - course_par))
|
||||
else:
|
||||
playing_handicaps[p["id"]] = None
|
||||
|
||||
# Strokes received per player per hole (requires stroke index data on course)
|
||||
strokes_per_hole: dict[str, dict[int, int]] = {}
|
||||
played_with_si = {h: hole_stroke_indices[h] for h in holes if h in hole_stroke_indices}
|
||||
if played_with_si:
|
||||
n_si = len(played_with_si)
|
||||
for p in players:
|
||||
ph = playing_handicaps.get(p["id"])
|
||||
if ph is not None and ph > 0:
|
||||
base, extra = divmod(ph, n_si)
|
||||
strokes_per_hole[p["id"]] = {
|
||||
h: base + (1 if played_with_si[h] <= extra else 0)
|
||||
for h in holes if h in played_with_si
|
||||
}
|
||||
|
||||
skins_won, skins_hole_results, unclaimed = None, None, 0
|
||||
if game["format"] == "skins":
|
||||
carryover = bool(game["carryover"])
|
||||
@@ -291,8 +317,9 @@ async def game_summary(game_id: str, request: Request, user: dict = Depends(get_
|
||||
return templates.TemplateResponse("games/summary.html", {
|
||||
"request": request, "user": user, "game": dict(game),
|
||||
"players": players, "scores": scores, "totals": totals,
|
||||
"holes": holes, "hole_pars": hole_pars,
|
||||
"holes": holes, "hole_pars": hole_pars, "hole_stroke_indices": hole_stroke_indices,
|
||||
"results": results,
|
||||
"playing_handicaps": playing_handicaps, "strokes_per_hole": strokes_per_hole,
|
||||
"skins_won": skins_won, "skins_hole_results": skins_hole_results, "unclaimed": unclaimed,
|
||||
"carryover": bool(game["carryover"]),
|
||||
})
|
||||
|
||||
@@ -48,6 +48,37 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Playing handicaps #}
|
||||
{% if playing_handicaps %}
|
||||
<div class="card" style="margin-top:0.75rem; padding:0.75rem 1rem;">
|
||||
<div style="font-size:0.8rem; color:var(--text-muted); font-weight:500; margin-bottom:0.5rem; text-transform:uppercase; letter-spacing:0.05em;">Playing Handicaps</div>
|
||||
{% for p in players %}
|
||||
<div style="display:flex; align-items:center; gap:0.6rem; padding:0.35rem 0; {% if not loop.last %}border-bottom:1px solid var(--border);{% endif %} font-size:0.9rem;">
|
||||
<span>
|
||||
{% if p.avatar and p.avatar.startswith('/') %}
|
||||
<img src="{{ p.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||||
{% else %}
|
||||
{{ p.avatar or '👤' }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<span style="flex:1; font-weight:500;">{{ p.name }}</span>
|
||||
{% if p.handicap_snapshot is not none %}
|
||||
<span style="color:var(--text-muted);">HI {{ p.handicap_snapshot }}</span>
|
||||
{% endif %}
|
||||
{% set ph = playing_handicaps.get(p.id) %}
|
||||
{% if ph is not none %}
|
||||
<span style="font-weight:600; color:var(--accent); min-width:3rem; text-align:right;">HC {{ ph }}</span>
|
||||
{% else %}
|
||||
<span style="color:var(--text-muted); min-width:3rem; text-align:right;">HC —</span>
|
||||
{% endif %}
|
||||
{% if p.tee_name %}
|
||||
<span style="color:var(--text-muted); font-size:0.8rem; min-width:4rem; text-align:right;">{{ p.tee_name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Scorecard toggle #}
|
||||
<div class="scorecard-toggle" style="margin-top:1rem;">
|
||||
<button @click="view = 'strokes'" :class="{ active: view === 'strokes' }">Strokes</button>
|
||||
@@ -85,6 +116,7 @@
|
||||
{% set s = scores.get(h, {}).get(p.id, {}) %}
|
||||
{% set strokes = s.strokes if s else 0 %}
|
||||
{% set par = hole_pars.get(h, 0) %}
|
||||
{% set sr = strokes_per_hole.get(p.id, {}).get(h, 0) %}
|
||||
<td class="score-cell">
|
||||
{% if strokes %}
|
||||
{% if par %}
|
||||
@@ -108,6 +140,7 @@
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
{% if sr > 0 %}<sup style="color:var(--accent); font-size:0.6em; line-height:0;">{{ '·' * sr }}</sup>{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
@@ -126,6 +159,15 @@
|
||||
<td>{{ stroke_total.val or '—' }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% if playing_handicaps %}
|
||||
<tr>
|
||||
<td style="color:var(--text-muted); font-size:0.8rem;">HC</td>
|
||||
{% if hole_pars %}<td></td>{% endif %}
|
||||
{% for p in players %}
|
||||
<td style="color:var(--text-muted); font-size:0.8rem;">{{ playing_handicaps.get(p.id, '—') if playing_handicaps.get(p.id) is not none else '—' }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
@@ -198,6 +240,7 @@
|
||||
{% set s = scores.get(h, {}).get(p.id, {}) %}
|
||||
{% set strokes = s.strokes if s else 0 %}
|
||||
{% set par = hole_pars.get(h, 0) %}
|
||||
{% set sr = strokes_per_hole.get(p.id, {}).get(h, 0) %}
|
||||
<td class="score-cell">
|
||||
{% if strokes %}
|
||||
{% set is_winner = hr and hr.winner == p.id %}
|
||||
@@ -211,6 +254,7 @@
|
||||
{% else %}<span{% if is_winner %} class="skin-winner"{% endif %}>{{ strokes }}</span>{% endif %}
|
||||
{% else %}<span{% if is_winner %} class="skin-winner"{% endif %}>{{ strokes }}</span>{% endif %}
|
||||
{% else %}—{% endif %}
|
||||
{% if sr > 0 %}<sup style="color:var(--accent); font-size:0.6em; line-height:0;">{{ '·' * sr }}</sup>{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="score-cell">
|
||||
|
||||
Reference in New Issue
Block a user