diff --git a/082_course_poi_type_fairway_stonefence.sql b/082_course_poi_type_fairway_stonefence.sql new file mode 100644 index 0000000..f2a753f --- /dev/null +++ b/082_course_poi_type_fairway_stonefence.sql @@ -0,0 +1,25 @@ +-- ===================================================================== +-- TeeCup — migrasjon 082 +-- Nye POI-typer "fairway" og "stone_fence" (voll/steingjerde) +-- ===================================================================== +-- Konkret anledning (ADR-084 del 5): full reimport av Tjøme Golfklubb +-- sitt koordinat-regneark (standardisert kolonneformat, se ADR-084 del +-- 2-4) inneholdt to TYPE-verdier uten tilsvarende poi_type: "Fairway" +-- (enkeltpunkt-referanse midt i fairwayen) og "Voll" (jordvoll/ +-- steingjerde). Bruker lastet opp egne ikoner (fairway.png, +-- stonefence.png) og ba om at disse legges til som egne typer. +-- +-- `fairway` og `stone_fence` legges til course_poi_type-domenet +-- (migrasjon 079) -- gjelder automatisk begge koordinattabellene. +-- ===================================================================== + +\set ON_ERROR_STOP on + +ALTER DOMAIN course_poi_type DROP CONSTRAINT course_poi_type_check; +ALTER DOMAIN course_poi_type ADD CONSTRAINT course_poi_type_check + CHECK (VALUE IN ( + 'green', 'green_bunker', 'fairway_bunker', 'water', 'creek', + 'trees', 'marker_100', 'marker_150', 'marker_200', + 'dogleg', 'road', 'tee_front', 'tee_back', + 'rock', 'layup', 'landmark', 'fairway', 'stone_fence' + )); diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index aad0867..dba7877 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -8616,6 +8616,94 @@ teecup_frontend && up -d`, ingen migrasjon. Rene containerlogger, `https://teecup.golf/logg-inn` 200 OK. Ikon-mangel-lista er nå TOM -- alle klassifiserte hindringstyper har eget ikon. +### ADR-084 del 5: sortering, side_fairway-datafeil funnet, standardisert CSV-format (2026-08-18) + +Bruker rapporterte to nye avvik fra ekte bruk: (1) hindringslisten +sorteres "motsatt" av forventet, (2) hull 7 sitt vannhinder fremstår +som senter selv om det faktisk ligger til venstre. + +**(1) Sorteringsfiks:** `groupDiagramHazards()` (hole-target- +distance.tsx) manglet den eksplisitte "nærmest spilleren først"- +sorteringen ADR-083 sin opprinnelige flate liste hadde -- falt tilbake +til rå API-/innsettingsrekkefølge, som verken reflekterte avstand eller +oppdaterte seg når spilleren beveget seg. Fikset med +`entities.sort((a, b) => a.below - b.below)`. + +**(2) Data-diagnose:** kryssjekket alle punkter i den gamle CSV-en som +eksplisitt oppga venstre/høyre mot databasens `side_fairway` -- fant 12 +avvik (fjellknaus/vann/trær på hull 4/5/7/13/14/16), alle satt til +`center` i databasen når kilden sa noe annet. Mønster: punkter som IKKE +fulgte det vanlige "Forkant/Bakkant [side] [type]"-friteksten ble +tilsynelatende satt til `center` som fallback ved en tidligere import. +Bekreftet at lapping av enkeltpunkter ikke var veien videre -- for +upålitelig datagrunnlag til å stole på uten en fullstendig, ren kilde. + +**Standardisert CSV-format avtalt** (kolonner: `ID,HULL,TYPE,PLASS, +PUNKT,LAT/LNG`) -- erstatter den gamle friteksten som krevde skjør +NLP-aktig parsing. `ID` er kun unik INNENFOR ett hull (samme prinsipp +som `hazard_group` allerede var designet for), tom `ID` = enkeltstående +punkt (som Tee/Green), ingen ID-kollisjon selv om flere hull gjenbruker +samme fysiske hindring (bekreftet mønster fra tidligere runder). + +**Ingen migrasjon denne delen.** + +### ADR-084 del 6: full reimport av Tjøme (194 punkter), to nye poi_type (2026-08-18) + +Bruker leverte hele 18-hulls-fila i det avtalte formatet. Grundig +validering før noe ble kjørt (se scratchpad-analyse): fant og avklarte +med bruker seks konkrete ting -- to ukjente TYPE-verdier (`Fairway`, +`Voll`, løst med nye ikoner + migrasjon 082, se under), ett par uten +PUNKT til å skille forkant/bakkant (hull 15 sitt vann -- løst ved +brukerens egen regel: punktet som slutter på "683" er bakkant), én +tydelig kopier-lim-feil (hull 11 sin andre "Tee"-rad skulle vært +"Tre" -- bekreftet), seks rader med fortsatt ugyldige UTM/EPSG:25833- +koordinater (denne gangen løst med en ordentlig projeksjons- +transformasjon via `pyproj`, EPSG:25833→4326 -- konvergerte på under +en millimeters avvik fra forrige rundes elimineringsbaserte gjetning, +god kryssbekreftelse av begge metodene), og ett hull 18-veipunkt som +bevisst fjernes (fantes i databasen, ikke i den nye fila -- bekreftet +riktig av bruker). + +**Migrasjon 082:** `fairway` og `stone_fence` lagt til +`course_poi_type`-domenet -- bruker lastet opp egne ikoner +(`fairway.png`, `stonefence.png`) for disse. `stone_fence` er den +norske "Voll" (jordvoll/steingjerde) sitt engelske domenenavn. + +**Full reimport, ikke patch:** hull 4 sine green-/greenbunker- +koordinater viste seg vesentlig endret mellom gammel database og ny +fil (bl.a. det jeg i forrige runde identifiserte som "bakre +greenbunker" via elimineringslogikk var faktisk feil -- ekte bakre +greenbunker har helt andre koordinater i den nye, autoritative fila). +Dette gjorde inkrementell patching for risikabelt -- kjørte i stedet +`DELETE`+`INSERT` i én transaksjon for HELE banen (180 gamle rader +byttet ut med 194 nye), beregnet direkte fra kolonnene (ikke +friteksttolkning). Genererings-/valideringsskript i scratchpad +(engangs, ikke innsjekket). + +**PLASS="Bakre"** (kun hull 4 sin greenbunker) trengte ingen +særbehandling -- beregnet `alongPercent` viste at punktet geometrisk +ligger godt innenfor normalsonen (94-97%, ikke bak green sin bakkant +på 101%+), så det rendres helt vanlig i midtbanen uten å trigge +"bak green"-sonen fra del 2. Verdt å vite: "Bakre" er en beskrivende +etikett fra klubben, ikke nødvendigvis bokstavelig "forbi flagget". + +**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, full +`./scripts/run_backend_tests.sh` 111/111 (migrasjon 082). Egen +scratch-database seedet med EKTE Tjøme-data (kopiert fra produksjon, +read-only kilde) -- reimport-SQL-en kjørt mot scratch-kopien FØRST, +bekreftet 194/39 riktig, `poi_type`-fordeling matchet plan eksakt. +Visuell bekreftelse (scratch API+frontend) av `fairway`- og +`stone_fence`-ikonene på hull 2/3, samt at sorteringsfiksen virker +riktig med ekte reimportert data (stigende avstand bekreftet hull +1/2/3). Lys+mørk bekreftet. Scratch-stacken revet ned. + +**Rullet ut 2026-08-18** -- bruker bekreftet migrasjon, full reimport +og kode hver for seg. Migrasjon 082 og reimport-SQL (DELETE 180/ +INSERT 194, én transaksjon) kjørt mot ekte `teecup_db`, deretter +`docker compose build teecup_frontend && up -d`. Hull 7 sitt vann +bekreftet `side_fairway='left'` etter reimport (matcher kildedataen, +rettet fra den kjente `center`-feilen). + --- ## Utviklingsplan (rekkefølge) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e2faa6..49f5161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12232,3 +12232,48 @@ Neste steg: build teecup_frontend && up -d`, ingen migrasjon. Rene containerlogger, `https://teecup.golf/logg-inn` 200 OK. Ikon-mangel-lista er nå tom. + +105. **Sortering, side_fairway-diagnose, standardisert CSV-format + (ADR-084 del 5) — 2026-08-18.** Hindringslisten manglet en + eksplisitt "nærmest spilleren først"-sortering (falt tilbake til + rå API-rekkefølge) -- fikset med `sort((a,b) => a.below - b.below)` + i `groupDiagramHazards()`. Kryssjekket samtidig alle punkter i den + gamle CSV-en med eksplisitt venstre/høyre mot databasens + `side_fairway` -- fant 12 avvik (satt til `center` når kilden sa + noe annet), som bekreftet at enkeltpunkt-lapping ikke holdt. + Avtalte i stedet et standardisert kolonneformat + (`ID,HULL,TYPE,PLASS,PUNKT,LAT/LNG`) med bruker for fremtidig + reimport -- `ID` unik kun innenfor ett hull, samme prinsipp som + `hazard_group`. + +106. **Full reimport av Tjøme, to nye poi_type (ADR-084 del 6) — + 2026-08-18.** Bruker leverte hele 18-hulls-fila i det avtalte + formatet. Validering avdekket og avklarte seks ting: to nye + TYPE-verdier (`Fairway`/`Voll`, egne ikoner fra bruker + migrasjon + 082 for `fairway`/`stone_fence`), ett par uten PUNKT løst via + brukerens regel (siste siffer i koordinaten avgjør forkant/ + bakkant), én bekreftet kopier-lim-feil (hull 11), seks UTM/ + EPSG:25833-korrupte rader løst med ordentlig `pyproj`-projeksjon + (konvergerte på under 1mm avvik fra forrige rundes elimineringsvis + gjettede verdier -- god kryssbekreftelse), og ett bevisst fjernet + veipunkt (hull 18). + + Hull 4 sine green-/greenbunker-koordinater viste seg vesentlig + endret mellom database og ny fil -- inkludert det forrige runde + identifiserte som "bakre greenbunker" via eliminasjon, som var feil + koordinater. For risikabelt å patche inkrementelt -- kjørte i + stedet full `DELETE`+`INSERT` (180 gamle rader → 194 nye) i én + transaksjon, beregnet direkte fra kolonnene. + + **Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, full + `pytest` 111/111 (migrasjon 082). Reimport-SQL kjørt FØRST mot en + scratch-kopi av ekte Tjøme-data -- 194/39 riktig, fordeling + matchet plan eksakt. Visuell bekreftelse av `fairway`/ + `stone_fence`-ikonene og sorteringsfiksen med ekte reimportert + data (hull 1/2/3), lys+mørk. Scratch-stacken revet ned. + + **Rullet ut 2026-08-18** -- bruker bekreftet migrasjon, reimport + og kode hver for seg. Migrasjon 082 + reimport (DELETE 180/INSERT + 194, én transaksjon) kjørt mot ekte `teecup_db`, deretter `docker + compose build teecup_frontend && up -d`. Hull 7 sitt vann + bekreftet `side_fairway='left'` etter reimport. diff --git a/frontend/components/hole-diagram-view.tsx b/frontend/components/hole-diagram-view.tsx index e6e4d0c..481a9d9 100644 --- a/frontend/components/hole-diagram-view.tsx +++ b/frontend/components/hole-diagram-view.tsx @@ -12,7 +12,19 @@ import { MapPin, TriangleAlert } from "lucide-react" import { cn } from "@/lib/utils" -export type HazardKind = "sand" | "water" | "creek" | "rock" | "tree" | "dogleg" | "layup" | "landmark" | "road" | "generic" +export type HazardKind = + | "sand" + | "water" + | "creek" + | "rock" + | "tree" + | "dogleg" + | "layup" + | "landmark" + | "road" + | "fairway" + | "stone_fence" + | "generic" export type DiagramHazard = { key: string @@ -106,6 +118,8 @@ const _ICON_SRC: Partial> = { layup: "/hole-diagram/hazard-layup.png", landmark: "/hole-diagram/hazard-landmark.png", road: "/hole-diagram/hazard-road.png", + fairway: "/hole-diagram/hazard-fairway.png", + stone_fence: "/hole-diagram/hazard-stonefence.png", } function HazardIcon({ kind, size = 32 }: { kind: HazardKind; size?: number }) { diff --git a/frontend/components/hole-target-distance.tsx b/frontend/components/hole-target-distance.tsx index af3badd..0311be0 100644 --- a/frontend/components/hole-target-distance.tsx +++ b/frontend/components/hole-target-distance.tsx @@ -60,12 +60,26 @@ const _HAZARD_LABELS: Record = { layup: "Layup-punkt", landmark: "Landemerke", road: "Vei", + fairway: "Fairway", + stone_fence: "Voll", } // ADR-084: hvilket ikon en hindringstype får i diagrammet. "generic" // (typer uten dedikert ikon -- i dag ingen) faller tilbake til lucide // sin TriangleAlert -- se hole-diagram-view.tsx. -type HazardKind = "sand" | "water" | "creek" | "rock" | "tree" | "dogleg" | "layup" | "landmark" | "road" | "generic" +type HazardKind = + | "sand" + | "water" + | "creek" + | "rock" + | "tree" + | "dogleg" + | "layup" + | "landmark" + | "road" + | "fairway" + | "stone_fence" + | "generic" const _HAZARD_KIND: Record = { green_bunker: "sand", fairway_bunker: "sand", @@ -77,6 +91,8 @@ const _HAZARD_KIND: Record = { layup: "layup", landmark: "landmark", road: "road", + fairway: "fairway", + stone_fence: "stone_fence", } // Grupperer rå hindringspunkter til hindrings-ENTITETER for diagrammet. @@ -157,7 +173,13 @@ function groupDiagramHazards( location: p.location, }) } - return entities + // ADR-083-konvensjonen ("sortert nærmest spilleren først") manglet her -- + // uten denne falt rekkefølgen tilbake til rå API-/innsettingsrekkefølge, + // som ikke sier noe om avstand og heller ikke oppdaterer seg når + // spilleren beveger seg. `below` er alltid den nærmeste av forkant/ + // enkeltpunkt-avstanden, så stigende sortering på den gir riktig + // "nærmest spilleren først" for både parrede og enkeltstående hindringer. + return entities.sort((a, b) => a.below - b.below) } export function HoleTargetDistance({ diff --git a/frontend/public/hole-diagram/hazard-fairway.png b/frontend/public/hole-diagram/hazard-fairway.png new file mode 100644 index 0000000..386b732 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-fairway.png differ diff --git a/frontend/public/hole-diagram/hazard-stonefence.png b/frontend/public/hole-diagram/hazard-stonefence.png new file mode 100644 index 0000000..49cfd74 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-stonefence.png differ