Dokumenter konkurranseklasser (ADR-041) i CHANGELOG/FEATURE_BACKLOG

Fullfører dokumentasjonen for klasse-funksjonen (migrasjon 053, rullet
ut 2026-08-04): ny ADR-041 med de fire load-bærende beslutningene
(fritt navngitte klasser, delt tabell mellom turneringstyper,
standardutslag som frontend-bekvemmelighet, egen resultatliste kun i
individuelle turneringer), pluss fulle bygge-/verifiseringsoppføringer
i CHANGELOG.md og FEATURE_BACKLOG.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erol Haagenrud 2026-08-04 03:49:41 +02:00
parent 7bda1bf789
commit 38168b2871
3 changed files with 215 additions and 0 deletions

View file

@ -3819,6 +3819,86 @@ ikke noe brukeren har bedt om.
--- ---
## ADR-041: Konkurranseklasser ("Damer fra 44, Herrer fra 50+")
**Kontekst:** Brukeren spurte om det var mulig å sette opp runder i en
turnering slik at f.eks. damer spiller fra utslag 44 mens herrer spiller
fra 50+. Undersøkelse viste at DELER allerede virket (hvert
`match_participant`/`round_participant` har alltid hatt sitt eget
`tee_id`, uavhengig av de andre, med server-side validering av at
utslaget har rating for spillerens kjønn, ADR-029) — men det var et
manuelt valg per spiller hver gang, ingen gjenbrukbar "klasse"-
gruppering. Ingen "klasse"/kategori-konsept for konkurranse fantes fra
før noe sted i skjemaet (kun den urelaterte venne-kategoriseringen,
ADR-036).
**Beslutning A — Klasser er fritt navngitte, ikke en fast taksonomi.**
Organisator oppretter klasser med eget navn (f.eks. "Damer", "Herrer A",
"Junior") og et VALGFRITT standardutslag — ikke en kjønns- eller
alders-spesifikk datamodell. Bekreftet eksplisitt med bruker
(`AskUserQuestion`) framfor en fast kjønn+alder-kombinasjon: dekker
kjønn/alder/HCP eller annet uten at systemet må forstå forskjellen,
samme fleksibilitets-prinsipp som `friend_categorization` (ADR-036).
**Beslutning B — Delt tabell, ikke duplisert per turneringstype.** Ny
`tournament_class`-tabell (migrasjon 053) FK'et til `tournament`, ikke
en utvidelse av selve `tournament`-raden — samme sidetabell-mønster som
`tournament_sponsor`. Siden BÅDE lagturneringer (ADR-011) og
individuelle turneringer (ADR-037) peker til samme delte `tournament`-
tabell (`format_type`-diskriminator), dekker ÉN tabell begge uten
duplisering. Klassetilhørighet lever på `team_roster.class_id`
(lagturneringer) og `tournament_participant.class_id` (individuelle
turneringer) — begge nullable, `ON DELETE SET NULL` (samme prinsipp som
`tournament_round_bbb_hole`s FK-er, migrasjon 044): sletter man en
klasse, forsvinner ikke roster-/deltaker-radene, de mister bare
merkelappen.
**Beslutning C — Standardutslag er en frontend-bekvemmelighet, ikke en
ny backend-mekanisme.** `tee_id` er ALLEREDE required og satt per
deltaker (ikke per økt/runde) ved både `POST .../matches/{id}/
participants` og `POST .../rounds/{id}/participants` — klassens
standardutslag forhåndsvelger bare dette feltet i UI-et (fortsatt fritt
overstyrbart), backend-kontrakten er uendret. Samme mønster som
`new-round.tsx`s eksisterende kjønnsfilter/-default for personlige
runder. Forhåndsvelgingen sjekker eksplisitt at utslaget faktisk finnes
på DEN aktuelle øktens/rundens bane før det brukes — en klasses
standardutslag kan tilhøre en annen bane enn den som er i bruk akkurat
da.
**Beslutning D — Egen resultatliste KUN i individuelle turneringer,
aldri i lagturneringer.** Den mest substansielle avklaringen
(`AskUserQuestion`, med begrunnelse presentert først): i lagturneringer
er poeng knyttet til hele KAMPER (lag mot lag, `match.points_side_a/b`),
ikke enkeltspillere — å dele opp dette per klasse gir ikke naturlig
mening, og brukeren bekreftet eksplisitt at klasse der KUN skal foreslå
utslag. Det finnes fra før et smalt, PER-ØKT individuelt leaderboard for
singles/fourball-slagspill (`fetch_individual_leaderboard`,
`tournaments.py`) — bevisst IKKE utvidet eller gjenbrukt av denne runden,
det er en annen, allerede eksisterende funksjon med annet formål
(sesjonsscoped, format-begrenset). I individuelle turneringer (flatt
felt, ADR-037) er splitting derimot en naturlig match: `individual_
leaderboard`-endepunktet fikk `class_id`/`class_name` lagt til i
SELECT+respons, men selve summerings-/sorteringslogikken er UENDRET —
frontend grupperer den allerede korrekt sorterte listen visuelt (stabil
gruppering bevarer riktig rangering per klasse), ingen ny motorlogikk i
`handicap_engine.py` trengtes (Copenhagen/BBB var allerede scope-
agnostiske på deltaker-mengde).
**Konsekvens:** `053_tournament_classes.sql`. Ny klasse-CRUD i
`tournaments.py` (delt, siden `tournament`-tabellen er delt). `Roster
EntryUpdate` (tidligere KUN `is_captain: bool` påkrevd) lagt om til
`exclude_unset`-PATCH-semantikk. Ny `PATCH .../participants/{id}` i
`individual_tournaments.py` (fantes ikke før). Frontend: "Klasser"-kort
i BÅDE `tournament-detail.tsx` og `individual-tournament-detail.tsx`,
utslag-forhåndsutfylling i `session-blind-draw.tsx`s `AddSlotForm` og
`individual-tournament-detail.tsx`s `AssignRoundParticipantControl`,
klasse-gruppert `LeaderboardTab` (individuelle turneringer). Se
CHANGELOG.md 2026-08-04 for full bygge-/verifiseringsdetalj (inkl. tall-
for-tall-bekreftet leaderboard-rangering per klasse i ekte nettleser).
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt.
---
## Åpne spørsmål (ikke besluttet ennå) ## Åpne spørsmål (ikke besluttet ennå)
Disse må avklares før eller under de relevante fasene: Disse må avklares før eller under de relevante fasene:

View file

@ -7032,3 +7032,91 @@ Neste steg:
så dette er samme build som scratch-verifisert, ikke i tillegg så dette er samme build som scratch-verifisert, ikke i tillegg
egenhendig bekreftet mot ekte produksjonsdata. Bekreft ved neste egenhendig bekreftet mot ekte produksjonsdata. Bekreft ved neste
faktiske turnering. faktiske turnering.
17. **Konkurranseklasser ("Damer fra 44, Herrer fra 50+") — BYGGET,
SCRATCH-VERIFISERT OG LIVE 2026-08-04, migrasjon 053.** Brukeren
spurte om det var mulig å sette opp runder i en turnering slik at
f.eks. damer spiller fra utslag 44 mens herrer spiller fra 50+.
Svaret var at DELER allerede virket (hver deltaker i en match/runde
fikk allerede sitt eget `tee_id`, backend validerte allerede at
utslaget hadde rating for spillerens kjønn) -- men et manuelt valg
per spiller hver gang, ingen gjenbrukbar "klasse". Brukeren ba om en
ekte klasse-mekanisme, "samt andre parametre som er relevante i en
slik problemstilling."
Full plan-modus-runde med to `AskUserQuestion`-avklaringer FØR
bygging (samme disiplin som ADR-037/ADR-039): (1) klasser er FRITT
NAVNGITTE (ikke bare kjønn) med et valgfritt standardutslag, ikke en
fast kjønn+alder-modell -- dekker kjønn/alder/HCP eller annet uten at
systemet må forstå forskjellen; (2) klasse gir EGEN RESULTATLISTE --
men KUN i individuelle turneringer (ADR-037, flatt felt). I
lagturneringer (ADR-011, to lag) er poeng knyttet til hele kamper,
ikke enkeltspillere -- brukeren bekreftet eksplisitt at klasse der
KUN skal foreslå utslag, ingen leaderboard-splitting (det finnes fra
før et smalt per-økt individuelt leaderboard for singles/fourball-
slagspill, `fetch_individual_leaderboard` -- IKKE noe denne runden
bygger videre på); (3) gjelder org-lagturneringer OG org-individuelle
turneringer, ikke frittstående personlige runder.
**Skjema** (`053_tournament_classes.sql`): ny delt `tournament_class`-
tabell (id/organization_id/tournament_id/name/default_tee_id,
`UNIQUE(tournament_id, name)`) -- delt mellom begge turneringstyper
siden begge peker til samme `tournament`-tabell (ADR-037s
`format_type`-mønster), unngår duplisering. To nye NULLABLE FK-
kolonner (`ON DELETE SET NULL`, samme mønster som `tournament_round_
bbb_hole`): `team_roster.class_id` og `tournament_participant.
class_id`. Samme `org_isolation`-RLS-loop som migrasjon 040.
**Backend**: ny klasse-CRUD (`GET/POST/PATCH/DELETE .../classes`) i
`tournaments.py` (delt fil, siden `tournament`-tabellen er delt).
`RosterEntryCreate`/`RosterEntry` fikk `class_id`/`class_name`;
`RosterEntryUpdate` (tidligere KUN `is_captain: bool` påkrevd)
lagt om til `exclude_unset`-PATCH-semantikk (samme mønster som
`TournamentUpdate`/`SponsorUpdate` fra presentasjons-panel-runden)
slik at klasse kan settes uten å tvinge et samtidig kaptein-valg.
`TournamentParticipantCreate`/`Out` (individual_tournaments.py) fikk
samme felt, pluss en NY `PATCH .../participants/{id}` (fantes ikke
før -- kun POST/GET/DELETE). `individual_leaderboard` utvidet med
`class_id`/`class_name` i SELECT+respons -- selve summerings-/
sorteringslogikken UENDRET, frontend grupperer den allerede sorterte
listen visuelt (stabil gruppering bevarer riktig rangering per
klasse, ingen ny motorlogikk i `handicap_engine.py` -- verifisert:
98/98 eksisterende enhetstester fortsatt grønne, ingen regresjon).
**Frontend**: "Klasser"-kort (opprett/slett, kaskaderende bane→
utslag-velger for standardutslag) i BÅDE `tournament-detail.tsx`
("Lag og spillere"-fanen) og `individual-tournament-detail.tsx`
(Oppsett-fanen). Utslag-forhåndsutfylling i to steder --
`session-blind-draw.tsx`s `AddSlotForm` (lagturneringer) og
`individual-tournament-detail.tsx`s `AssignRoundParticipantControl`
(individuelle turneringer) -- begge en `useEffect` som slår opp valgt
spillers klasse → standardutslag når spilleren velges, KUN hvis det
utslaget faktisk finnes på DENNE øktens/rundens bane (klassens
standardutslag kan tilhøre en annen bane), fortsatt fritt
overstyrbart. Ren frontend-bekvemmelighet, samme "gjenbruk et
eksisterende felt, ikke en ny backend-mekanisme"-prinsipp som
`new-round.tsx`s eksisterende kjønnsfilter/-default. `LeaderboardTab`
(individuelle turneringer) grupperer den mottatte, allerede sorterte
listen på `class_id`, hver klasse får egen seksjonsoverskrift +
egen 1/2/3-rangering INNAD i klassen -- ingen klasser opprettet =
identisk med tidligere flat visning, bakoverkompatibelt uten
migrasjonsflagg.
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-/frontend-container, ekte
nettleser-innlogging inkl. 2FA): full API-rundtur (klasse-CRUD,
roster-/deltaker-PATCH med class_id, `ON DELETE SET NULL` bekreftet
ved klasseslettelse), ekte nettleser-klikk gjennom BEGGE
turneringstyper -- lagturnering: opprettet klasser via UI-skjemaet
(kaskaderende bane→utslag bekreftet), tildelte klasse via
dropdown-menyen, bekreftet utslag forhåndsvalgt korrekt idet en
spiller med klasse ble valgt i `AddSlotForm` (Kari→44, Ola→56);
individuell turnering: samme mønster i `AssignRoundParticipantControl`
(Bjørn Ege→56), pluss det klasse-delte leaderboardet bekreftet
visuelt korrekt -- "DAMER"-seksjon viste Kari (72 slag, rang 1) foran
Siv (108 slag, rang 2), "HERRER" viste Ola alene (90 slag, rang 1),
tallene håndregnet og stemte eksakt (par/+18/+36 mot faktisk
innsendt score). `test_isolation.sql` 12/12 uendret, ekte
typesjekket produksjonsbuild (fanget og rettet ETT reelt
TypeScript-funn under selve verifiseringen: `classes`-propen manglet
`TeamColumn` i `session-blind-draw.tsx` -- `AddSlotForm` ligger i
en underkomponent som ikke automatisk arver overordnet komponents
state, måtte tres eksplisitt gjennom).
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: migrasjon
053 mot ekte `teecup_db`, `docker compose up -d --build teecup_api
teecup_frontend`, begge containere boot-et rent, `/health`/
`/dashboard` → 200.

View file

@ -3967,6 +3967,53 @@ IKKE stilt til V0 ennå. Egen, senere, liten vurdering om ønskelig.
--- ---
## Konkurranseklasser ("Damer fra 44, Herrer fra 50+") — ✅ HELT FERDIG, BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-04 (ADR-041)
Brukeren spurte om det var mulig å sette opp runder i en turnering slik
at f.eks. damer spiller fra utslag 44 mens herrer spiller fra 50+. Full
plan-modus-runde med to `AskUserQuestion`-avklaringer FØR bygging (samme
disiplin som ADR-037/039) — se ADR-041 for de fire load-bærende
beslutningene (fritt navngitte klasser, delt tabell mellom begge
turneringstyper, standardutslag som ren frontend-bekvemmelighet, og —
den mest substansielle avklaringen — egen resultatliste KUN i
individuelle turneringer, aldri i lagturneringer siden poeng der er
knyttet til hele kamper).
Kort: ny `tournament_class`-tabell (migrasjon 053, org-isolert RLS) +
nullable `class_id``team_roster`/`tournament_participant` (`ON
DELETE SET NULL`). Organisator oppretter klasser med navn + valgfritt
standardutslag i et nytt "Klasser"-kort (BÅDE `tournament-detail.tsx` og
`individual-tournament-detail.tsx`). Standardutslaget forhåndsvelges
automatisk når en spiller med klasse legges til en match/runde (fortsatt
overstyrbart) — INGEN endring i selve `tee_id`-kontrakten, som allerede
var required og satt per deltaker. I individuelle turneringer deler
`individual_leaderboard` seg nå i egne, riktig rangerte seksjoner per
klasse; i lagturneringer er klasse bevisst KUN et utslag-forslag, ingen
leaderboard-endring (bekreftet med bruker — matchpoeng er lag-mot-lag,
ikke enkeltspiller-atomisk).
**Scratch-/browserverifisert grundig** (isolert `teecup_app_scratch`-
rolle + engangs API-/frontend-container, ekte nettleser-innlogging inkl.
2FA): full API-rundtur, ekte klikk gjennom BEGGE turneringstyper —
kaskaderende bane→utslag-velger ved klasse-opprettelse, dropdown-
klassetildeling, utslag-forhåndsutfylling bekreftet i `AddSlotForm` OG
`AssignRoundParticipantControl`, og det klasse-delte leaderboardet
tall-for-tall bekreftet korrekt (Damer: Kari 72 slag rang 1, Siv 108
slag rang 2; Herrer: Ola 90 slag rang 1 — alle mot håndregnet
slagsum). 98/98 eksisterende `handicap_engine.py`-enhetstester uendret
(ingen ny motorlogikk), `test_isolation.sql` 12/12 uendret. Ett reelt
TypeScript-funn under selve verifiseringen (ikke antatt riktig fra
implementasjonen alene): `classes`-propen manglet på `TeamColumn` i
`session-blind-draw.tsx` (egen underkomponent, arver ikke overordnet
komponents state automatisk) — fanget av produksjonsbuildens
typesjekk, rettet før utrulling.
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: migrasjon
053 mot ekte `teecup_db`, `docker compose up -d --build teecup_api
teecup_frontend`. Full detalj i CHANGELOG.md 2026-08-04 og ADR-041.
---
## Bevisst endret fra opprinnelige (Gemini-)råd ## Bevisst endret fra opprinnelige (Gemini-)råd
- 🔀 **Banedata:** API mot teeoff (ADR-004), IKKE direkte delt database. Direkte - 🔀 **Banedata:** API mot teeoff (ADR-004), IKKE direkte delt database. Direkte