From 38168b287175a30e3656ea6e09b3433165c753db Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Tue, 4 Aug 2026 03:49:41 +0200 Subject: [PATCH] Dokumenter konkurranseklasser (ADR-041) i CHANGELOG/FEATURE_BACKLOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ARCHITECTURE_DECISIONS.md | 80 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 88 +++++++++++++++++++++++++++++++++++++++ FEATURE_BACKLOG.md | 47 +++++++++++++++++++++ 3 files changed, 215 insertions(+) diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index 95d928c..39f7198 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -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å) Disse må avklares før eller under de relevante fasene: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f43f46..24103b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7032,3 +7032,91 @@ Neste steg: så dette er samme build som scratch-verifisert, ikke i tillegg egenhendig bekreftet mot ekte produksjonsdata. Bekreft ved neste 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 + på `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. diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index edee380..f58a958 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -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` på `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 - 🔀 **Banedata:** API mot teeoff (ADR-004), IKKE direkte delt database. Direkte