-- ===================================================================== -- 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;