teecup/065_golfapi_courses.sql

146 lines
8 KiB
MySQL
Raw Permalink Normal View History

-- =====================================================================
-- TeeCup — migrasjon 065
-- GolfAPI.io som tredje banekilde (ADR-064) -- baner utenfor TeeOffs
-- dekning (f.eks. Tjøme Golfklubb, bekreftet IKKE i TeeOff 2026-08-12).
-- =====================================================================
-- GolfAPI-kall koster ekte, begrenset budsjett (kjøpt 20 kall, ~2/bane)
-- -- ULIKT TeeOffs frie, offentlige API. Derfor: ETT delt, globalt
-- cache-lag (golfapi_course + underliggende hull/tee/koordinat-tabeller)
-- som ALDRI hentes på nytt for samme fysiske bane (GolfAPIs avtale
-- tillater eksplisitt permanent caching). Både den org-scopede
-- turneringsmodellen (course/hole/tee/tee_rating) OG den globale
-- personal_course-katalogen (migrasjon 020) kopierer INN fra denne
-- cachen ved eksplisitt import -- selve cachen eies ikke av noen av de
-- to, den er en tredje, uavhengig katalog.
--
-- Koordinatene (grønn front/midt/bak, hindringer, tee-punkter) kopieres
-- ALDRI inn per import -- de er ikke en del av HCP-beregningen (ingen
-- reproduserbarhets-hensyn som par/rating har via ADR-007/ADR-019), så
-- rangefinder-oppslag leser alltid direkte fra golfapi_course_coordinate
-- via banens lagrede GolfAPI-courseID. Én fysisk bane = ett
-- koordinatsett, uansett hvor mange ganger den importeres.
-- =====================================================================
\set ON_ERROR_STOP on
-- ---------------------------------------------------------------------
-- Delt GolfAPI-cache (global, ingen organization_id/owner -- én rad per
-- fysisk bane som NOEN i TeeCup noensinne har importert)
-- ---------------------------------------------------------------------
-- GolfAPIs courseID brukes direkte som PK (globalt unik hos dem, ingen
-- grunn til en egen surrogate-nøkkel).
CREATE TABLE golfapi_course (
course_id text PRIMARY KEY,
club_id text NOT NULL,
club_name text NOT NULL,
course_name text NOT NULL,
city text,
country text,
num_holes smallint NOT NULL CHECK (num_holes BETWEEN 1 AND 18),
-- Alltid hentet med measureUnit=m (golfapi_client.py) -- ingen yard-data
-- lagres, unngår enhets-fellen research allerede advarte om.
has_gps boolean NOT NULL DEFAULT false,
num_coordinates integer NOT NULL DEFAULT 0,
golfapi_updated_at timestamptz, -- deres timestampUpdated, til evt. fremtidig re-sync-vurdering
fetched_at timestamptz NOT NULL DEFAULT now()
);
-- Par/SI per hull. Kun menns verdier er kanonisk brukt av kopier-inn-
-- stegene under (TeeCups eksisterende hole/personal_course_hole har
-- ingen kjønnsdelt par -- samme forenkling som TeeOff-importen, ADR-019).
-- Kvinners verdier caches likevel, i tilfelle en fremtidig utvidelse.
CREATE TABLE golfapi_course_hole (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
golfapi_course_id text NOT NULL REFERENCES golfapi_course(course_id) ON DELETE CASCADE,
hole_number smallint NOT NULL CHECK (hole_number BETWEEN 1 AND 18),
par_men smallint NOT NULL CHECK (par_men BETWEEN 3 AND 6),
index_men smallint NOT NULL CHECK (index_men BETWEEN 1 AND 18),
par_women smallint CHECK (par_women BETWEEN 3 AND 6),
index_women smallint CHECK (index_women BETWEEN 1 AND 18),
UNIQUE (golfapi_course_id, hole_number),
UNIQUE (golfapi_course_id, index_men)
);
CREATE TABLE golfapi_course_tee (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
golfapi_course_id text NOT NULL REFERENCES golfapi_course(course_id) ON DELETE CASCADE,
golfapi_tee_id text NOT NULL,
name text NOT NULL,
color_hex text,
-- Meter per hull, indeks 0 = hull 1 ... indeks 17 = hull 18.
lengths_meters smallint[] NOT NULL,
course_rating_men numeric(4,1) NOT NULL,
slope_men smallint NOT NULL CHECK (slope_men BETWEEN 55 AND 155),
course_rating_women numeric(4,1),
slope_women smallint CHECK (slope_women BETWEEN 55 AND 155),
course_rating_men_front9 numeric(4,1),
course_rating_men_back9 numeric(4,1),
course_rating_women_front9 numeric(4,1),
course_rating_women_back9 numeric(4,1),
UNIQUE (golfapi_course_id, golfapi_tee_id),
CHECK (array_length(lengths_meters, 1) BETWEEN 1 AND 18)
);
-- Rangefinder-datalaget: grønn front/midt/bak, hindringer, tee-punkter.
-- poi_type/green_location/side_fairway holdes som tekst+CHECK (ikke
-- egne enum-typer) -- samme "kan utvides uten ALTER TYPE"-begrunnelse
-- som 001 allerede bruker for `format`.
CREATE TABLE golfapi_course_coordinate (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
golfapi_course_id text NOT NULL REFERENCES golfapi_course(course_id) ON DELETE CASCADE,
hole_number smallint NOT NULL CHECK (hole_number BETWEEN 1 AND 18),
poi_type text NOT NULL CHECK (poi_type IN (
'green', 'green_bunker', 'fairway_bunker', 'water',
'trees', 'marker_100', 'marker_150', 'marker_200',
'dogleg', 'road', 'tee_front', 'tee_back'
)),
-- GolfAPIs `location`-felt er generisk (front/midt/bak for POI-et),
-- ikke kun for 'green' -- f.eks. hadde Tjømes bunkere også front/bak.
-- Ikke navngitt green_location -- ville feilaktig antydet at kun
-- grønn-punkter har denne verdien.
location text CHECK (location IN ('front', 'middle', 'back')),
side_fairway text CHECK (side_fairway IN ('left', 'center', 'right')),
latitude double precision NOT NULL CHECK (latitude BETWEEN -90 AND 90),
longitude double precision NOT NULL CHECK (longitude BETWEEN -180 AND 180)
);
CREATE INDEX ON golfapi_course_hole (golfapi_course_id);
CREATE INDEX ON golfapi_course_tee (golfapi_course_id);
CREATE INDEX ON golfapi_course_coordinate (golfapi_course_id, hole_number);
GRANT SELECT, INSERT, UPDATE, DELETE ON golfapi_course TO teecup_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON golfapi_course_hole TO teecup_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON golfapi_course_tee TO teecup_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON golfapi_course_coordinate TO teecup_app;
-- ---------------------------------------------------------------------
-- Kopier-inn-mål 1: org-scopet turneringsbane (course), ny kilde
-- ---------------------------------------------------------------------
-- Samme mønster som ADR-019s 'official' (teeoff) -- men 'international'
-- betyr GolfAPI, IKKE live-oppslag (Beslutning i ADR-064: GolfAPI-kall
-- koster budsjett, teeoffs gjør ikke -- derfor kopier-inn her også).
ALTER TYPE course_source ADD VALUE 'international';
-- CHECK-constraint kan ikke endres direkte -- droppes og gjenopprettes
-- med samme betingelse utvidet symmetrisk. Navnet (course_check) er
-- Postgres sitt auto-genererte navn for det opprinnelige, unavngitte
-- table-level CHECK-et i 001 -- bekreftet mot ekte teecup_db, ikke gjettet.
ALTER TABLE course DROP CONSTRAINT course_check;
ALTER TABLE course ADD CONSTRAINT course_check
CHECK (source NOT IN ('official', 'international') OR external_course_ref IS NOT NULL);
-- ---------------------------------------------------------------------
-- Kopier-inn-mål 2: global personal_course-katalog, ny provenance-kolonne
-- ---------------------------------------------------------------------
-- round.course_source trenger INGEN endring -- en GolfAPI-importert bane
-- blir en helt vanlig personal_course-rad (course_source='custom' i
-- round, uendret), kun med denne kolonnen satt for å (a) hindre
-- dupliserte importer, (b) la rangefinder-oppslag finne koordinatene.
ALTER TABLE personal_course
ADD COLUMN external_golfapi_course_id text REFERENCES golfapi_course(course_id);
CREATE UNIQUE INDEX personal_course_golfapi_ref_unique
ON personal_course (external_golfapi_course_id)
WHERE external_golfapi_course_id IS NOT NULL;