From 1bc6b0b74d9526591183fb5796b2683748de879b Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Tue, 18 Aug 2026 08:36:31 +0200 Subject: [PATCH] Sortering, side_fairway-fiks, ny poi_type fairway/stone_fence (ADR-084 del 5-6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hindringslisten manglet eksplisitt nærmest-spilleren-først-sortering -- fikset. Kryssjekk mot kildedata avdekket 12 kjente side_fairway-feil, løst via full reimport av Tjømes 194 koordinatpunkter fra et nytt, standardisert CSV-format (kjørt separat mot ekte teecup_db, ikke del av denne commiten). To nye hindringstyper (fairway/stone_fence) med egne ikoner. Co-Authored-By: Claude Sonnet 5 --- 082_course_poi_type_fairway_stonefence.sql | 25 +++++ ARCHITECTURE_DECISIONS.md | 88 ++++++++++++++++++ CHANGELOG.md | 45 +++++++++ frontend/components/hole-diagram-view.tsx | 16 +++- frontend/components/hole-target-distance.tsx | 26 +++++- .../public/hole-diagram/hazard-fairway.png | Bin 0 -> 3113 bytes .../public/hole-diagram/hazard-stonefence.png | Bin 0 -> 2946 bytes 7 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 082_course_poi_type_fairway_stonefence.sql create mode 100644 frontend/public/hole-diagram/hazard-fairway.png create mode 100644 frontend/public/hole-diagram/hazard-stonefence.png 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 0000000000000000000000000000000000000000..386b732f5f5b80ed6132152aa82ad125b24bae3a GIT binary patch literal 3113 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 D!u#E9 literal 0 HcmV?d00001 diff --git a/frontend/public/hole-diagram/hazard-stonefence.png b/frontend/public/hole-diagram/hazard-stonefence.png new file mode 100644 index 0000000000000000000000000000000000000000..49cfd746a74d33809f94507a020556a227bdedde GIT binary patch literal 2946 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