2026-07-16 14:38:42 +02:00
|
|
|
"""
|
|
|
|
|
DB-bevisst bro mellom handicap_engine.py (ren, uten DB-avhengigheter) og
|
|
|
|
|
Postgres. Se ADR-005 (allowance er konfig), ADR-014 (fire uavhengige brytere),
|
|
|
|
|
ADR-007 (handicap_index_snapshot fryses i team_roster).
|
|
|
|
|
|
|
|
|
|
Kjent, bevisst begrensning (se plan): hvis en side aldri når forventet
|
|
|
|
|
deltakerantall, eller en deltakers tee mangler en matchende tee_rating-rad,
|
|
|
|
|
beregnes handicap for den siden ALDRI automatisk her -- ingen bakgrunnsjobb
|
|
|
|
|
prøver på nytt. Dette løses senere (sannsynligvis en organisator-overstyring).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
|
|
from asyncpg import Connection
|
|
|
|
|
|
|
|
|
|
from handicap_engine import (
|
|
|
|
|
AllowanceStrategy,
|
|
|
|
|
CombinedPercentage,
|
|
|
|
|
DEFAULT_MATCHPLAY_ALLOWANCES,
|
|
|
|
|
Format,
|
|
|
|
|
PerPlayerPercentage,
|
|
|
|
|
RankedSplit,
|
|
|
|
|
WeightedLowHigh,
|
|
|
|
|
course_handicap_raw,
|
|
|
|
|
match_play_strokes,
|
|
|
|
|
round_half_up,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Formater der ENHETEN som deler én Playing Handicap er SIDEN (foursome-paret,
|
|
|
|
|
# greensome-paret, hele scramble-laget) -- ikke spilleren. Singles/fourball er
|
|
|
|
|
# ikke med her: der er enheten spilleren, og beregnes individuelt.
|
Ferdig og live. Kort oppsummert:
Bygget: hele frontend for ADR-039-spillformene — utvidet spilleform-velgeren i "Ny runde" til alle åtte formater med skins-konfig, en ny SidesPanel i "Spillere og runde" (opprett/slett sider, tildel/fjern spillere med sanntids kapasitetssperre), en ny FormatResultPanel (løpende matchstatus for match/fourball/foursome/greensome/scramble, skins-tavle for skins), setup_complete-gating av "Fullfør runde", og et eget delt-ball-scorekort+veiviser for foursome/greensome/scramble.
Testet reelt i nettleser (Chrome DevTools mot en isolert scratch-backend, ikke bare typesjekk) — spilte gjennom en komplett match-, skins- og foursome-runde fra bunnen av. Fant og fikset to reelle stale-state-buger underveis (setup-melding og matchstatus ble stående utdatert etter side-tildeling til de ikke lenger refetchet runden). Skins- og foursome-handicap-matematikken kryssjekket for hånd og stemte eksakt.
Rullet ut mot ekte systemer — ingen migrasjon, kun teecup_frontend bygget på nytt, begge containere boot-et rent, teeoff.no upåvirket. ADR-039 er dermed helt ferdig, backend og frontend.
2026-07-28 13:00:50 +02:00
|
|
|
SIDE_IS_UNIT = {"foursome", "greensome", "scramble_2", "scramble_4"}
|
2026-07-16 14:38:42 +02:00
|
|
|
|
|
|
|
|
FORMAT_UNIT_SIZE: dict[str, int] = {
|
|
|
|
|
"singles": 1,
|
|
|
|
|
"fourball": 1,
|
|
|
|
|
"foursome": 2,
|
|
|
|
|
"greensome": 2,
|
|
|
|
|
"scramble_2": 2,
|
|
|
|
|
"scramble_4": 4,
|
|
|
|
|
}
|
|
|
|
|
|
All eight new turneringsformater have backend + engine complete and scratch-verified — Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, and High-low-high. Built and tested one format at a time (engine tests → migration → API wiring → scratch verification) exactly per the approved plan:
Verification totals: handicap_engine.py unit tests went from 63 → 98 (all passing), plus 400+ scratch-API checks across the eight formats' test scripts, covering both frittstående runder and the appropriate org-scoped system (team or individual) for each. test_isolation.sql stayed 12/12 throughout — migrations 041–050 are purely additive.
A few real things surfaced and were fixed during testing:
Nassau can be blocked by the existing "match already decided" lock (ADR-012) if the overall 18-hole match closes early — documented as a known v1 limitation, not fixed (would mean loosening an established safety guard).
Københavner's points depend on all 3 players at once, breaking the usual "recompute one participant" pattern — handled with a dedicated recompute path.
High-low-high doesn't fit the existing win/loss/halved match cache, so it gets its own dedicated read endpoint (like Nassau) — verified digit-for-digit against your own worked example ("1-1 etter hull 1", "2-1 til lag 2").
Two missing-column crashes (caught in scratch, never reached anything resembling "done").
2026-07-30 14:54:11 +02:00
|
|
|
# Nye formater denne runden (2026-07-30) som er strukturelt IDENTISKE med et
|
|
|
|
|
# eksisterende format -- ingen egen HCP-/allowance-logikk, bare et annet
|
|
|
|
|
# selvdeklarert navn. Løses opp HELT FØRST i parse_allowance_config/
|
|
|
|
|
# compute_and_store_side_handicaps/relative_strokes_for_match, slik at resten
|
|
|
|
|
# av disse funksjonene (og SIDE_IS_UNIT/FORMAT_UNIT_SIZE/Format-enumet)
|
|
|
|
|
# aldri ser aliasnavnet. Chapman/Pinehurst ("Try all", bekreftet av bruker):
|
|
|
|
|
# delt ball, én score per hull per side, CombinedPercentage 50% -- identisk
|
|
|
|
|
# med foursome, kun regler for HVEM som slår hvilket slag (ikke sporet i
|
|
|
|
|
# skjemaet, verken her eller for foursome).
|
|
|
|
|
ENGINE_FORMAT_ALIASES: dict[str, str] = {
|
|
|
|
|
"chapman": "foursome",
|
|
|
|
|
# Shamble (2026-07-30) -- variabel lagstørrelse (2-4), enheten er
|
|
|
|
|
# SPILLEREN (som fourball/singles), ikke siden -- "singles" sin
|
|
|
|
|
# PerPlayerPercentage(1.00) er telle-agnostisk (regner ett tall per
|
|
|
|
|
# spiller uavhengig av hvor mange andre er på laget), i motsetning til
|
|
|
|
|
# foursome/greensome/scramble sine faste antalls-strategier.
|
|
|
|
|
# Kildens hullscore-regel (beste N av M) bruker rå brutto, ikke HCP-
|
|
|
|
|
# justert netto -- course_handicap/playing_handicap beregnes og lagres
|
|
|
|
|
# likevel her (for visning/et fremtidig steg), men brukes IKKE av
|
|
|
|
|
# selve hullscore-utregningen (se scoring.py sin _side_net shamble-gren).
|
|
|
|
|
"shamble": "singles",
|
|
|
|
|
# Money Ball/Lone Ranger (2026-07-30) -- FAST 4-manns lag, enheten er
|
|
|
|
|
# SPILLEREN (som Shamble/fourball), ikke siden. Rå brutto brukes for
|
|
|
|
|
# selve hullscoren (se scoring.py sin _compute_money_ball_hole_results),
|
|
|
|
|
# course_handicap/playing_handicap beregnes og lagres likevel for
|
|
|
|
|
# visning (samme begrunnelse som Shamble).
|
|
|
|
|
"money_ball": "singles",
|
|
|
|
|
# High-low-high (2026-07-30) -- 2 lag à 2, individuell ball. course_
|
|
|
|
|
# handicap/playing_handicap beregnes som fourball (samme "alle fire
|
|
|
|
|
# samlet i én match_play_strokes-pool"-regel, R&A) -- selve poeng-
|
|
|
|
|
# rangeringen (high/low per hull) er en HELT NY, egen beregning (se
|
|
|
|
|
# app/routers/rounds.py sin _build_high_low_high_result), ikke del av
|
|
|
|
|
# denne aliaseringen.
|
|
|
|
|
"high_low_high": "fourball",
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-16 14:38:42 +02:00
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class AllowanceConfig:
|
|
|
|
|
use_handicap: bool
|
|
|
|
|
use_course_handicap: bool
|
|
|
|
|
use_matchplay_handicap: bool
|
|
|
|
|
strategy: AllowanceStrategy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _strategy_from_json(format_: str, spec: dict | None) -> AllowanceStrategy:
|
|
|
|
|
if spec is None:
|
|
|
|
|
return DEFAULT_MATCHPLAY_ALLOWANCES[Format(format_)]
|
|
|
|
|
kind = spec["type"]
|
|
|
|
|
if kind == "per_player":
|
|
|
|
|
return PerPlayerPercentage(spec["percentage"])
|
|
|
|
|
if kind == "combined":
|
|
|
|
|
return CombinedPercentage(spec["percentage"])
|
|
|
|
|
if kind == "weighted_low_high":
|
|
|
|
|
return WeightedLowHigh(spec["low_weight"], spec["high_weight"])
|
|
|
|
|
if kind == "ranked_split":
|
|
|
|
|
return RankedSplit(tuple(spec["weights"]))
|
|
|
|
|
raise ValueError(f"Ukjent allowance-strategitype: {kind!r}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_allowance_config(format_: str, allowance_override: dict | None) -> AllowanceConfig:
|
|
|
|
|
"""ADR-014: fire uavhengige brytere. Manglende nøkler = på/standard."""
|
All eight new turneringsformater have backend + engine complete and scratch-verified — Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, and High-low-high. Built and tested one format at a time (engine tests → migration → API wiring → scratch verification) exactly per the approved plan:
Verification totals: handicap_engine.py unit tests went from 63 → 98 (all passing), plus 400+ scratch-API checks across the eight formats' test scripts, covering both frittstående runder and the appropriate org-scoped system (team or individual) for each. test_isolation.sql stayed 12/12 throughout — migrations 041–050 are purely additive.
A few real things surfaced and were fixed during testing:
Nassau can be blocked by the existing "match already decided" lock (ADR-012) if the overall 18-hole match closes early — documented as a known v1 limitation, not fixed (would mean loosening an established safety guard).
Københavner's points depend on all 3 players at once, breaking the usual "recompute one participant" pattern — handled with a dedicated recompute path.
High-low-high doesn't fit the existing win/loss/halved match cache, so it gets its own dedicated read endpoint (like Nassau) — verified digit-for-digit against your own worked example ("1-1 etter hull 1", "2-1 til lag 2").
Two missing-column crashes (caught in scratch, never reached anything resembling "done").
2026-07-30 14:54:11 +02:00
|
|
|
format_ = ENGINE_FORMAT_ALIASES.get(format_, format_)
|
2026-07-16 14:38:42 +02:00
|
|
|
override = allowance_override or {}
|
|
|
|
|
return AllowanceConfig(
|
|
|
|
|
use_handicap=override.get("use_handicap", True),
|
|
|
|
|
use_course_handicap=override.get("use_course_handicap", True),
|
|
|
|
|
use_matchplay_handicap=override.get("use_matchplay_handicap", True),
|
|
|
|
|
strategy=_strategy_from_json(format_, override.get("strategy")),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def compute_and_store_side_handicaps(
|
|
|
|
|
conn: Connection,
|
|
|
|
|
match_id: str,
|
|
|
|
|
team_side: str,
|
|
|
|
|
format_: str,
|
|
|
|
|
config: AllowanceConfig,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Beregn og lagre course_handicap/playing_handicap for én side i en match.
|
|
|
|
|
|
|
|
|
|
Kalles fra matches.py sin add_participant RETT ETTER hver innsetting.
|
|
|
|
|
Avgjør selv om den er klar til å beregne:
|
|
|
|
|
- singles/fourball (enhet = spiller): beregner uansett, for hver
|
|
|
|
|
deltaker som finnes (idempotent -- trygt å kalle flere ganger).
|
|
|
|
|
- foursome/greensome/scramble (enhet = side): beregner KUN når siden har
|
|
|
|
|
nøyaktig FORMAT_UNIT_SIZE[format] deltakere OG alle har en matchende
|
|
|
|
|
tee_rating -- ellers returnerer den uten å skrive noe (se
|
|
|
|
|
modul-docstring).
|
Update Todos
Diagnostisere HCP-bug mot ekte data (gjort)
Fiks 1: filtrer valgte spillere bort fra listen (blind draw)
Fiks 3: tallvelger for slagregistrering (scorekort)
Fiks 4: hardkode full_18-scope i handicap-utregning (bekreftet bug)
Punkt 2: migrasjon 014 + kode (courses.py, handicap.py, matches.py, tournaments.py, frontend)
Scratch-verifisere punkt 2 (fletting, import, validering, remap)
Skrive ADR-029 + oppdatere FEATURE_BACKLOG.md
Presentere full plan og be om bekreftelse (ekte migrasjon + deploy)
Alle fire punkter er nå bygget og grundig scratch-verifisert. Oppsummering før jeg ber om bekreftelse på å rulle ut mot ekte systemer:
1 — Spillerliste: Fikset. Valgte spillere filtreres nå bort i stedet for å bare vises gråtonet.
3 — Tallvelger: Fikset. Ny StrokePicker (1–9, med utvidbar "10+"→10–19).
4 — HCP-bug: Bekreftet ekte kodebug (ikke bare synlighet). Jeg leste ekte produksjonsdata for testøkten din: front_9 + foursome, og alle fire deltakerne hadde NULL i handicap-feltene. Årsak: handicap-utregningen lette etter en tee-rating scopet til øktens hole_config, men slike rader lages i praksis kun med full_18-omfang — så den fant aldri noe for en front_9-økt. Din beskrevne formel (kombinert hcp/2, laveste side til 0 mottatte slag, resten fordelt fra stroke index 1) var allerede riktig implementert — bugen var at den aldri kjørte. Fikset og verifisert: identisk bruttoscore på begge sider ga nå et ikke-delt resultat, som beviser at hcp faktisk brukes.
2 — Tee/kjønn: Du hadde helt rett i presiseringen din — dette var større enn jeg først trodde. Kjønn er nå flyttet fra selve utslaget til ratingen (ny migrasjon 014_tee_gender_to_rating.sql), tee-valget i blind draw er blitt helt automatisk (ingen kjønnsvelger lenger), og manglende kjønn/rating avvises tydelig i stedet for å gjettes. Fikk egen ADR-029. Verifisert i fem separate runder, inkludert mot ekte teeoff-data (Borregaard) og en gjenskaping av akkurat Tjøme-mønsteret.
Bifunn, ikke fikset: en liten, urelatert 500-krasj (stroke-registrering på en bane uten registrerte hull) — vil du at jeg tar den også, eller lar den ligge som egen sak?
Utrullingsplan — dette er første gang i denne runden noe rører ekte data:
Migrasjon 014_tee_gender_to_rating.sql mot ekte teecup_db — slår sammen Tjømes 8 tee-rader til 4 (bevarer alle ratinger, reparerer alle referanser; mekanisk identisk med det jeg nettopp verifiserte i scratch)
2026-07-20 08:53:53 +02:00
|
|
|
|
|
|
|
|
Bruker ALLTID tee-ens full_18-rating (ADR-008), uavhengig av øktens
|
|
|
|
|
hole_config -- front/back-9-fordelingen skjer senere, ved selve
|
|
|
|
|
slagtildelingen (`allocate_over_played_holes` i scoring.py), ikke her.
|
|
|
|
|
FIKSET 2026-07-19: joinet tidligere mot øktens hole_config som
|
|
|
|
|
rating-scope direkte, som gjorde at en front_9/back_9-økt ALDRI fikk
|
|
|
|
|
beregnet handicap i praksis -- ingen front_9/back_9-scopet tee_rating
|
|
|
|
|
har noensinne blitt opprettet (kun full_18, se ADR-019 Beslutning D og
|
|
|
|
|
tee-endepunktet), så joinen fant alltid null rader. Bekreftet mot ekte
|
|
|
|
|
produksjonsdata (en front_9 foursome-økt med NULL course_handicap/
|
|
|
|
|
playing_handicap på alle fire deltakere).
|
|
|
|
|
|
|
|
|
|
ADR-029 (2026-07-19): ratingen er også kjønnsspesifikk nå (`tee_rating.
|
|
|
|
|
gender`) -- ett fysisk utslag (`tee`) kan ha én rating per kjønn, aldri
|
|
|
|
|
én felles. Joines derfor også mot spillerens `player.gender`. Krever at
|
|
|
|
|
`matches.py` sin `add_participant` har validert FØR innsetting at
|
|
|
|
|
spilleren har et registrert kjønn OG at valgt utslag faktisk har en
|
|
|
|
|
matchende rating -- denne funksjonen forventer at det alltid stemmer,
|
|
|
|
|
den validerer ikke selv (samme mønster som for manglende
|
|
|
|
|
handicap_index_snapshot).
|
2026-07-16 14:38:42 +02:00
|
|
|
"""
|
All eight new turneringsformater have backend + engine complete and scratch-verified — Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, and High-low-high. Built and tested one format at a time (engine tests → migration → API wiring → scratch verification) exactly per the approved plan:
Verification totals: handicap_engine.py unit tests went from 63 → 98 (all passing), plus 400+ scratch-API checks across the eight formats' test scripts, covering both frittstående runder and the appropriate org-scoped system (team or individual) for each. test_isolation.sql stayed 12/12 throughout — migrations 041–050 are purely additive.
A few real things surfaced and were fixed during testing:
Nassau can be blocked by the existing "match already decided" lock (ADR-012) if the overall 18-hole match closes early — documented as a known v1 limitation, not fixed (would mean loosening an established safety guard).
Københavner's points depend on all 3 players at once, breaking the usual "recompute one participant" pattern — handled with a dedicated recompute path.
High-low-high doesn't fit the existing win/loss/halved match cache, so it gets its own dedicated read endpoint (like Nassau) — verified digit-for-digit against your own worked example ("1-1 etter hull 1", "2-1 til lag 2").
Two missing-column crashes (caught in scratch, never reached anything resembling "done").
2026-07-30 14:54:11 +02:00
|
|
|
format_ = ENGINE_FORMAT_ALIASES.get(format_, format_)
|
2026-07-16 14:38:42 +02:00
|
|
|
participants = await conn.fetch(
|
|
|
|
|
"""
|
|
|
|
|
SELECT mp.id::text AS id,
|
|
|
|
|
tr.handicap_index_snapshot::float AS handicap_index_snapshot,
|
|
|
|
|
tee_rating.course_rating::float AS course_rating,
|
|
|
|
|
tee_rating.slope_rating::float AS slope_rating,
|
|
|
|
|
tee_rating.par AS par
|
|
|
|
|
FROM match_participant mp
|
|
|
|
|
JOIN team_roster tr ON tr.id = mp.team_roster_id
|
Update Todos
Diagnostisere HCP-bug mot ekte data (gjort)
Fiks 1: filtrer valgte spillere bort fra listen (blind draw)
Fiks 3: tallvelger for slagregistrering (scorekort)
Fiks 4: hardkode full_18-scope i handicap-utregning (bekreftet bug)
Punkt 2: migrasjon 014 + kode (courses.py, handicap.py, matches.py, tournaments.py, frontend)
Scratch-verifisere punkt 2 (fletting, import, validering, remap)
Skrive ADR-029 + oppdatere FEATURE_BACKLOG.md
Presentere full plan og be om bekreftelse (ekte migrasjon + deploy)
Alle fire punkter er nå bygget og grundig scratch-verifisert. Oppsummering før jeg ber om bekreftelse på å rulle ut mot ekte systemer:
1 — Spillerliste: Fikset. Valgte spillere filtreres nå bort i stedet for å bare vises gråtonet.
3 — Tallvelger: Fikset. Ny StrokePicker (1–9, med utvidbar "10+"→10–19).
4 — HCP-bug: Bekreftet ekte kodebug (ikke bare synlighet). Jeg leste ekte produksjonsdata for testøkten din: front_9 + foursome, og alle fire deltakerne hadde NULL i handicap-feltene. Årsak: handicap-utregningen lette etter en tee-rating scopet til øktens hole_config, men slike rader lages i praksis kun med full_18-omfang — så den fant aldri noe for en front_9-økt. Din beskrevne formel (kombinert hcp/2, laveste side til 0 mottatte slag, resten fordelt fra stroke index 1) var allerede riktig implementert — bugen var at den aldri kjørte. Fikset og verifisert: identisk bruttoscore på begge sider ga nå et ikke-delt resultat, som beviser at hcp faktisk brukes.
2 — Tee/kjønn: Du hadde helt rett i presiseringen din — dette var større enn jeg først trodde. Kjønn er nå flyttet fra selve utslaget til ratingen (ny migrasjon 014_tee_gender_to_rating.sql), tee-valget i blind draw er blitt helt automatisk (ingen kjønnsvelger lenger), og manglende kjønn/rating avvises tydelig i stedet for å gjettes. Fikk egen ADR-029. Verifisert i fem separate runder, inkludert mot ekte teeoff-data (Borregaard) og en gjenskaping av akkurat Tjøme-mønsteret.
Bifunn, ikke fikset: en liten, urelatert 500-krasj (stroke-registrering på en bane uten registrerte hull) — vil du at jeg tar den også, eller lar den ligge som egen sak?
Utrullingsplan — dette er første gang i denne runden noe rører ekte data:
Migrasjon 014_tee_gender_to_rating.sql mot ekte teecup_db — slår sammen Tjømes 8 tee-rader til 4 (bevarer alle ratinger, reparerer alle referanser; mekanisk identisk med det jeg nettopp verifiserte i scratch)
2026-07-20 08:53:53 +02:00
|
|
|
JOIN player p ON p.id = tr.player_id
|
|
|
|
|
JOIN tee_rating ON tee_rating.tee_id = mp.tee_id
|
|
|
|
|
AND tee_rating.scope = 'full_18'
|
|
|
|
|
AND tee_rating.gender = p.gender
|
2026-07-16 14:38:42 +02:00
|
|
|
WHERE mp.match_id = $1 AND mp.team_side = $2::team_side
|
|
|
|
|
""",
|
|
|
|
|
match_id,
|
|
|
|
|
team_side,
|
|
|
|
|
)
|
|
|
|
|
|
Ferdig og live. Kort oppsummert:
Bygget: hele frontend for ADR-039-spillformene — utvidet spilleform-velgeren i "Ny runde" til alle åtte formater med skins-konfig, en ny SidesPanel i "Spillere og runde" (opprett/slett sider, tildel/fjern spillere med sanntids kapasitetssperre), en ny FormatResultPanel (løpende matchstatus for match/fourball/foursome/greensome/scramble, skins-tavle for skins), setup_complete-gating av "Fullfør runde", og et eget delt-ball-scorekort+veiviser for foursome/greensome/scramble.
Testet reelt i nettleser (Chrome DevTools mot en isolert scratch-backend, ikke bare typesjekk) — spilte gjennom en komplett match-, skins- og foursome-runde fra bunnen av. Fant og fikset to reelle stale-state-buger underveis (setup-melding og matchstatus ble stående utdatert etter side-tildeling til de ikke lenger refetchet runden). Skins- og foursome-handicap-matematikken kryssjekket for hånd og stemte eksakt.
Rullet ut mot ekte systemer — ingen migrasjon, kun teecup_frontend bygget på nytt, begge containere boot-et rent, teeoff.no upåvirket. ADR-039 er dermed helt ferdig, backend og frontend.
2026-07-28 13:00:50 +02:00
|
|
|
if format_ in SIDE_IS_UNIT:
|
2026-07-16 14:38:42 +02:00
|
|
|
expected = FORMAT_UNIT_SIZE[format_]
|
|
|
|
|
total_on_side = await conn.fetchval(
|
|
|
|
|
"SELECT count(*) FROM match_participant WHERE match_id = $1 AND team_side = $2::team_side",
|
|
|
|
|
match_id,
|
|
|
|
|
team_side,
|
|
|
|
|
)
|
|
|
|
|
if total_on_side != expected or len(participants) != expected:
|
|
|
|
|
return # ikke komplett ennå, eller mangler tee_rating for noen
|
|
|
|
|
|
|
|
|
|
if not participants:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not config.use_handicap:
|
|
|
|
|
for p in participants:
|
|
|
|
|
await conn.execute(
|
|
|
|
|
"UPDATE match_participant SET course_handicap = 0, playing_handicap = 0 WHERE id = $1",
|
|
|
|
|
p["id"],
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
course_handicaps = [
|
|
|
|
|
course_handicap_raw(p["handicap_index_snapshot"], p["slope_rating"], p["course_rating"], p["par"])
|
|
|
|
|
if config.use_course_handicap
|
|
|
|
|
else p["handicap_index_snapshot"]
|
|
|
|
|
for p in participants
|
|
|
|
|
]
|
|
|
|
|
|
Ferdig og live. Kort oppsummert:
Bygget: hele frontend for ADR-039-spillformene — utvidet spilleform-velgeren i "Ny runde" til alle åtte formater med skins-konfig, en ny SidesPanel i "Spillere og runde" (opprett/slett sider, tildel/fjern spillere med sanntids kapasitetssperre), en ny FormatResultPanel (løpende matchstatus for match/fourball/foursome/greensome/scramble, skins-tavle for skins), setup_complete-gating av "Fullfør runde", og et eget delt-ball-scorekort+veiviser for foursome/greensome/scramble.
Testet reelt i nettleser (Chrome DevTools mot en isolert scratch-backend, ikke bare typesjekk) — spilte gjennom en komplett match-, skins- og foursome-runde fra bunnen av. Fant og fikset to reelle stale-state-buger underveis (setup-melding og matchstatus ble stående utdatert etter side-tildeling til de ikke lenger refetchet runden). Skins- og foursome-handicap-matematikken kryssjekket for hånd og stemte eksakt.
Rullet ut mot ekte systemer — ingen migrasjon, kun teecup_frontend bygget på nytt, begge containere boot-et rent, teeoff.no upåvirket. ADR-039 er dermed helt ferdig, backend og frontend.
2026-07-28 13:00:50 +02:00
|
|
|
if format_ in SIDE_IS_UNIT:
|
2026-07-16 14:38:42 +02:00
|
|
|
playing = config.strategy.playing_handicap(course_handicaps)
|
|
|
|
|
for p, ch in zip(participants, course_handicaps):
|
|
|
|
|
await conn.execute(
|
|
|
|
|
"UPDATE match_participant SET course_handicap = $1, playing_handicap = $2 WHERE id = $3",
|
|
|
|
|
round_half_up(ch),
|
|
|
|
|
playing,
|
|
|
|
|
p["id"],
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
for p, ch in zip(participants, course_handicaps):
|
|
|
|
|
playing = config.strategy.playing_handicap([ch])
|
|
|
|
|
await conn.execute(
|
|
|
|
|
"UPDATE match_participant SET course_handicap = $1, playing_handicap = $2 WHERE id = $3",
|
|
|
|
|
round_half_up(ch),
|
|
|
|
|
playing,
|
|
|
|
|
p["id"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def relative_strokes_for_match(
|
|
|
|
|
conn: Connection, match_id: str, format_: str, config: AllowanceConfig
|
|
|
|
|
) -> dict[str, int]:
|
|
|
|
|
"""Relative slag per scoringsenhet i matchen.
|
|
|
|
|
|
|
|
|
|
Nøkkel = match_participant_id for singles/fourball (fourball: ALLE fire
|
|
|
|
|
spillerne i matchen samlet i én liste før match_play_strokes -- jf. R&A-
|
|
|
|
|
regelen om at laveste av alle fire spiller av scratch, ikke side-vis).
|
|
|
|
|
Nøkkel = team_side ('a'/'b') for foursome/greensome/scramble.
|
|
|
|
|
|
|
|
|
|
Returnerer {} hvis handicap ikke er beregnet for én eller flere enheter
|
|
|
|
|
ennå (se compute_and_store_side_handicaps) -- kalleren tolker det som
|
|
|
|
|
"kan ikke avgjøre noen hull ennå".
|
|
|
|
|
"""
|
All eight new turneringsformater have backend + engine complete and scratch-verified — Chapman, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble, Money Ball, and High-low-high. Built and tested one format at a time (engine tests → migration → API wiring → scratch verification) exactly per the approved plan:
Verification totals: handicap_engine.py unit tests went from 63 → 98 (all passing), plus 400+ scratch-API checks across the eight formats' test scripts, covering both frittstående runder and the appropriate org-scoped system (team or individual) for each. test_isolation.sql stayed 12/12 throughout — migrations 041–050 are purely additive.
A few real things surfaced and were fixed during testing:
Nassau can be blocked by the existing "match already decided" lock (ADR-012) if the overall 18-hole match closes early — documented as a known v1 limitation, not fixed (would mean loosening an established safety guard).
Københavner's points depend on all 3 players at once, breaking the usual "recompute one participant" pattern — handled with a dedicated recompute path.
High-low-high doesn't fit the existing win/loss/halved match cache, so it gets its own dedicated read endpoint (like Nassau) — verified digit-for-digit against your own worked example ("1-1 etter hull 1", "2-1 til lag 2").
Two missing-column crashes (caught in scratch, never reached anything resembling "done").
2026-07-30 14:54:11 +02:00
|
|
|
format_ = ENGINE_FORMAT_ALIASES.get(format_, format_)
|
Ferdig og live. Kort oppsummert:
Bygget: hele frontend for ADR-039-spillformene — utvidet spilleform-velgeren i "Ny runde" til alle åtte formater med skins-konfig, en ny SidesPanel i "Spillere og runde" (opprett/slett sider, tildel/fjern spillere med sanntids kapasitetssperre), en ny FormatResultPanel (løpende matchstatus for match/fourball/foursome/greensome/scramble, skins-tavle for skins), setup_complete-gating av "Fullfør runde", og et eget delt-ball-scorekort+veiviser for foursome/greensome/scramble.
Testet reelt i nettleser (Chrome DevTools mot en isolert scratch-backend, ikke bare typesjekk) — spilte gjennom en komplett match-, skins- og foursome-runde fra bunnen av. Fant og fikset to reelle stale-state-buger underveis (setup-melding og matchstatus ble stående utdatert etter side-tildeling til de ikke lenger refetchet runden). Skins- og foursome-handicap-matematikken kryssjekket for hånd og stemte eksakt.
Rullet ut mot ekte systemer — ingen migrasjon, kun teecup_frontend bygget på nytt, begge containere boot-et rent, teeoff.no upåvirket. ADR-039 er dermed helt ferdig, backend og frontend.
2026-07-28 13:00:50 +02:00
|
|
|
if format_ in SIDE_IS_UNIT:
|
2026-07-16 14:38:42 +02:00
|
|
|
rows = await conn.fetch(
|
|
|
|
|
"""
|
|
|
|
|
SELECT DISTINCT ON (team_side) team_side::text AS unit, playing_handicap
|
|
|
|
|
FROM match_participant
|
|
|
|
|
WHERE match_id = $1
|
|
|
|
|
ORDER BY team_side
|
|
|
|
|
""",
|
|
|
|
|
match_id,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
rows = await conn.fetch(
|
|
|
|
|
"SELECT id::text AS unit, playing_handicap FROM match_participant WHERE match_id = $1",
|
|
|
|
|
match_id,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not rows or any(r["playing_handicap"] is None for r in rows):
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
absolutes = [r["playing_handicap"] for r in rows]
|
|
|
|
|
relative = match_play_strokes(absolutes) if config.use_matchplay_handicap else absolutes
|
|
|
|
|
return {r["unit"]: v for r, v in zip(rows, relative)}
|