diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a6bfc29..4e46533 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -320,7 +320,12 @@ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" https://teecup.teeoff.no/dashboard)", "Bash(curl -s https://teecup.teeoff.no/manifest.webmanifest)", "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" https://teecup.teeoff.no/sw.js)", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" https://teecup.teeoff.no/icons/icon-512.png)" + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" https://teecup.teeoff.no/icons/icon-512.png)", + "Bash(disown)", + "Bash(python3 -c \"import httpx\")", + "Bash(curl -s http://localhost:18800/health)", + "Bash(python3 /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/test_hcp_fix.py)", + "Bash(python3 /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/test_tee_gender.py)" ], "additionalDirectories": [ "/opt/teeoff/deploy", diff --git a/014_tee_gender_to_rating.sql b/014_tee_gender_to_rating.sql new file mode 100644 index 0000000..f265e8b --- /dev/null +++ b/014_tee_gender_to_rating.sql @@ -0,0 +1,84 @@ +-- ===================================================================== +-- TeeCup — kjønn hører til RATINGEN, ikke selve utslaget (migrasjon 014) +-- ===================================================================== +-- Reist av brukeren 2026-07-19: en golfbane har IKKE fysisk kjønnsdelte +-- utslag -- begge kjønn kan som regel spille fra ethvert utslag. Det +-- eneste som faktisk varierer per kjønn er om klubben har VALGT å slope +-- (rate) et gitt utslag for det respektive kjønnet (noen klubber sloper +-- bevisst ikke det lengste utslaget for damer). `tee.gender` (migrasjon +-- 001) konflaterte feilaktig utslaget og ratingen -- hvert fysisk utslag +-- ble lagret som TO separate `tee`-rader (én per kjønn), bekreftet mot +-- ekte importert Tjøme-data (fire utslag, hver som et rent m/f-par). +-- +-- Denne migrasjonen flytter `gender` fra `tee` til `tee_rating` (der det +-- faktisk hører hjemme -- én fysisk tee kan ha 0, 1 eller 2 +-- kjønnsspesifikke ratinger), og slår sammen eksisterende kjønns-par-rader +-- til én fysisk tee-rad. Se ARCHITECTURE_DECISIONS.md ADR-029. +-- +-- Kjørt trygt mot ekte data FORDI (bekreftet FØR skriving, read-only): +-- alle eksisterende tee-rader er rene navnepar (m+f, ingen 'x'), ingen +-- gruppe har mer enn to rader. +-- ===================================================================== + +\set ON_ERROR_STOP on + +BEGIN; + +ALTER TABLE tee_rating ADD COLUMN gender text; + +-- Fyll inn gender på HVER eksisterende tee_rating-rad fra sin (ennå +-- urørte) eier-tee, FØR noe slås sammen eller slettes. +UPDATE tee_rating tr +SET gender = t.gender +FROM tee t +WHERE t.id = tr.tee_id; + +-- Selve golfregelen: en WHS-rating er alltid for ett bestemt kjønn -- 'x' +-- (som var gyldig på PLAYER-nivå) gir ikke mening for en rating. Krasjer +-- høyt her (NOT NULL + CHECK) hvis noen ukjent/uventet rad skulle mangle +-- et gyldig kjønn -- fanger det FØR det stille blir en umulig rating, +-- ikke etterpå. +ALTER TABLE tee_rating ALTER COLUMN gender SET NOT NULL; +ALTER TABLE tee_rating ADD CONSTRAINT tee_rating_gender_check CHECK (gender IN ('m', 'f')); + +-- Den GAMLE unikheten (tee_id, scope) må vekk FØR sammenslåingen under -- +-- å flytte en "duplikat"-tee sin rating over til beholder-tee-en kolliderer +-- ellers midlertidig med beholderens EGEN eksisterende (tee_id, scope)-rad +-- (samme scope, men snart ulikt kjønn -- det er jo poenget). Den NYE, +-- kjønnsbevisste unikheten legges til ETTER sammenslåingen, når den faktisk +-- kan håndheves riktig. +ALTER TABLE tee_rating DROP CONSTRAINT tee_rating_tee_id_scope_key; + +-- Slå sammen "duplikat"-utslag (samme bane+navn, hittil ulikt kjønn på +-- selve tee-raden) til ÉN fysisk tee-rad. Velger MIN(id) per +-- (course_id, name) som "beholderen". +CREATE TEMP TABLE tee_merge_map AS +SELECT t.id AS old_id, + (SELECT min(t2.id::text)::uuid FROM tee t2 + WHERE t2.course_id = t.course_id AND t2.name = t.name) AS keeper_id +FROM tee t; + +UPDATE tee_rating tr +SET tee_id = m.keeper_id +FROM tee_merge_map m +WHERE tr.tee_id = m.old_id AND m.old_id <> m.keeper_id; + +UPDATE match_participant mp +SET tee_id = m.keeper_id +FROM tee_merge_map m +WHERE mp.tee_id = m.old_id AND m.old_id <> m.keeper_id; + +DELETE FROM tee t +USING tee_merge_map m +WHERE t.id = m.old_id AND m.old_id <> m.keeper_id; + +DROP TABLE tee_merge_map; + +-- Unikhet er nå per (tee_id, scope, gender) -- ett utslag kan ha BÅDE en +-- herre- og en dame-rating for samme scope. +ALTER TABLE tee_rating ADD CONSTRAINT tee_rating_tee_id_scope_gender_key UNIQUE (tee_id, scope, gender); + +ALTER TABLE tee DROP CONSTRAINT tee_gender_check; +ALTER TABLE tee DROP COLUMN gender; + +COMMIT; diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index ccbc3e3..3c98fea 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -1312,6 +1312,99 @@ for oppfølging, se FEATURE_BACKLOG.md sin PWA-tabell og CLAUDE.md sin --- +## ADR-029: Kjønn hører til tee-RATINGEN, ikke selve utslaget + +Reist av brukeren 2026-07-19, som del av en runde med fire rapporterte +UX-/korrekthetshull fra faktisk testing av blind draw og scorekort. Startet +som en antatt frontend-fiks ("tee-valget bør følge spillerens kjønn +automatisk"), men brukeren presiserte at premisset mitt var feil: en +golfbane har IKKE fysisk kjønnsdelte utslag -- begge kjønn kan som regel +spille fra ethvert utslag. Det eneste som faktisk varierer per kjønn er om +klubben har VALGT å slope (rate) et gitt utslag for det respektive kjønnet +(noen klubber sloper bevisst ikke det lengste utslaget for damer). + +**Bekreftet problem, ikke antatt:** `tee.gender` (migrasjon 001) la kjønn på +selve utslaget, ikke ratingen. Sjekket mot ekte, importert produksjonsdata +(Tjøme Golfklubb, ADR-019): alle fire utslagene lå som RENE navnepar -- +"32"/m + "32"/f, "44"/m + "44"/f, osv. -- to separate `tee`-rader for +akkurat samme fysiske utslag. Blind draw-skjermen viste dette som "velg +Dame- eller Herre-tee", som om det var to ulike steder å slå fra. + +**Beslutning A -- `gender` flyttes fra `tee` til `tee_rating`.** Ett fysisk +utslag (`tee`, identifisert kun ved navn) kan ha 0, 1 eller 2 +kjønnsspesifikke ratinger. `tee_rating` sin unikhet endres fra +`(tee_id, scope)` til `(tee_id, scope, gender)`. `gender` på `tee_rating` +er `NOT NULL CHECK IN ('m','f')` -- 'x' (gyldig på PLAYER-nivå) gir ikke +mening for en WHS-rating, som alltid er for ett bestemt kjønn. + +**Beslutning B -- tee-valget blir helt automatisk, ingen manuell +kjønnsvelger (bekreftet med bruker, valgte det anbefalte alternativet).** +Organisator/kaptein velger KUN fysisk utslag i blind draw +(`session-blind-draw.tsx`, ingen "H"/"D"-suffiks lenger). Riktig +kjønnsspesifikk rating løses AUTOMATISK server-side fra spillerens +registrerte `player.gender` -- `app/handicap.py` sin +`compute_and_store_side_handicaps` joiner nå `tee_rating` på +`(tee_id, scope='full_18', gender = player.gender)` i tillegg til den +tidligere ADR-028-fiksen (alltid full_18, uavhengig av hole_config). + +**Beslutning C -- manglende rating/kjønn feiler tydelig FØR innsetting, +ingen stille fallback (bekreftet med bruker, valgte det anbefalte +alternativet).** `matches.py` sin `add_participant` validerer, når +`config.use_handicap` er sann: (1) spilleren har et registrert kjønn -- +avvist med `VALIDATION_FAILED` hvis ikke ("sett kjønn på spilleren +først"), (2) valgt utslag har faktisk en `tee_rating` for NØYAKTIG det +kjønnet -- avvist med `VALIDATION_FAILED` hvis ikke ("dette utslaget har +ingen dame/herre-rating -- velg et annet utslag"). Samme "fail loudly"- +prinsipp som ADR-019 sin importvalidering og den eksisterende +handicap_index-sjekken (2026-07-18) rett ved siden av. `_remap_course` +(bane-bytte midt i en økt) fikk samme presisering: matcher fortsatt på +tee-navn, men sjekker nå at den NYE tee-en har en rating for hver berørte +spillers kjønn, ikke bare at navnet finnes. + +**Konsekvens for import/opprettelse:** ADR-019 sin teeoff-import +(`import_official_course`) lager nå ÉN `tee`-rad per fysisk teeoff-utslag +(tidligere: to, én per kjønn), med inntil to `tee_rating`-rader under. +Manuell tee-opprettelse (`POST .../courses/{id}/tees`) redesignet +tilsvarende: `TeeCreate.ratings` er nå en liste (1-2 elementer, distinkte +kjønn) i stedet for ett flatt kjønn+rating-sett -- ingen reell +bruker-/produksjonsdata brukte dette endepunktet fra før (kun Tjøme, som +er offisielt importert), så ingen bakoverkompatibilitet var nødvendig. + +**Migrasjon `014_tee_gender_to_rating.sql`:** flytter `gender` til +`tee_rating`, slår deretter sammen eksisterende kjønns-par-tee-rader til +én fysisk tee-rad per (bane, navn) -- velger laveste id som "beholder", +flytter alle `tee_rating`- og `match_participant.tee_id`-referanser dit, +sletter duplikatene. Bekreftet TRYGT å kjøre mot ekte data FØR skriving +(read-only sjekk): alle 8 eksisterende tee-rader (Tjøme) er rene m/f-par, +ingen `gender='x'`-rader, ingen gruppe med mer enn to rader. + +**Scratch-verifisert grundig, flere runder:** (1) selve +fletting-migrasjonen kjørt mot syntetisk data som gjenskaper Tjøme- +mønsteret nøyaktig, inkl. én `match_participant`-rad som pekte til +DUPLIKATEN (ikke beholderen) -- bekreftet korrekt reparert til beholderens +id etterpå, og et utslag med KUN én rating (ingen duplikat) forblir +urørt. (2) Manuell tee-opprettelse: to ratinger på ett utslag, kun én +rating (simulerer "klubben har ikke slopet dette kjønnet"), duplikat +kjønn i samme innsending avvist. (3) `GET tees` viser riktig sammenslått +struktur (2 fysiske utslag, ikke 3 rader). (4) `add_participant`: kvinne ++ utslag med dame-rating lykkes; mann + utslag som KUN har dame-rating +avvist tydelig; spiller uten registrert kjønn avvist tydelig; en full +kjønnsblandet singel-match scoret korrekt end-to-end. (5) Offisiell +import kjørt mot EKTE `teeoff_api` (Borregaard Golfklubb) -- bekreftet +4 fysiske utslag importert med begge kjønnsratinger hver, ikke 8 doble +rader. (6) `_remap_course`: bane-bytte til en bane UTEN matchende +kjønnsrating avvist tydelig, bane-bytte til en bane MED matchende +rating lykket. + +**Status: ✅ BYGGET OG SCRATCH-VERIFISERT 2026-07-19, IKKE ENNÅ RULLET UT** +mot ekte `teecup_db` -- migrasjon 014 endrer ekte produksjonsdata +(Tjøme-utslagene slås faktisk sammen), venter derfor på eksplisitt +brukerbekreftelse før den kjøres mot den ekte databasen, i tråd med +CLAUDE.md sin regel om å alltid vise planen og vente på bekreftelse før +noe som migrerer ekte data kjøres. + +--- + ## Åpne spørsmål (ikke besluttet ennå) Disse må avklares før eller under de relevante fasene: diff --git a/CLAUDE.md b/CLAUDE.md index 51b41cc..ee87039 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1747,6 +1747,150 @@ Ferdig og verifisert: Verifisert: `/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/ `icons/icon-512.png` alle 200 over ekte https, `teeoff.no` upåvirket. +- **Fire nye hull rapportert fra faktisk testing av blind draw + scorekort + (foursome), DIAGNOSTISERT OG NOTERT, IKKE fikset (2026-07-19):** rett + etter PWA-runden. Full detalj i FEATURE_BACKLOG.md sin nye seksjon + "Rapporterte hull, blind draw + scorekort (2026-07-19)". Kort: + 1. **Bekreftet root cause:** valgt spiller forsvinner ikke fra + nedtrekkslisten i `AddSlotForm` (`session-blind-draw.tsx`) — den + merkes kun `disabled` på ` - {roster.map((p) => ( - - ))} + {roster + .filter((p) => !disabledPlayerIds.has(p.id)) + .map((p) => ( + + ))}