""" Cache-chokepoint mot GolfAPI.io (ADR-064). ALL import-kode (org-scopet `course` OG global `personal_course`) MÅ gå via get_or_fetch_golfapi_course() her -- ALDRI kalle golfapi_client direkte fra en import-endepunkt. Sjekker `golfapi_course`-cachen FØRST, kaller GolfAPI (course + coordinates, 2 kall) KUN hvis banen mangler -- kritisk for det begrensede kall-budsjettet (GolfAPIs avtale tillater eksplisitt permanent caching av hentet banedata, "no need to call the API to fetch the same course multiple times"). Ren DB-glue rundt den transportlag-rene golfapi_client.py -- samme ansvarsdeling som courses.py allerede har rundt teeoff_client.py. """ from . import golfapi_client # GolfAPIs rå tallkoder -> TeeCups lesbare tekst-enum (golfapi_course_coordinate.poi_type). _POI_TYPE_BY_CODE = { 1: "green", 2: "green_bunker", 3: "fairway_bunker", 4: "water", 5: "trees", 6: "marker_100", 7: "marker_150", 8: "marker_200", 9: "dogleg", 10: "road", 11: "tee_front", 12: "tee_back", } _LOCATION_BY_CODE = {1: "front", 2: "middle", 3: "back"} _SIDE_FW_BY_CODE = {1: "left", 2: "center", 3: "right"} async def get_or_fetch_golfapi_course(conn, golfapi_course_id: str) -> None: """Idempotent -- no-op (0 API-kall) hvis banen allerede er cachet fra en TIDLIGERE import, uansett hvilken organisasjon/bruker som gjorde den.""" already_cached = await conn.fetchval("SELECT 1 FROM golfapi_course WHERE course_id = $1", golfapi_course_id) if already_cached: return course = await golfapi_client.get_course(golfapi_course_id) coordinates = await golfapi_client.get_coordinates(golfapi_course_id) num_holes = int(course["numHoles"]) async with conn.transaction(): await conn.execute( """ INSERT INTO golfapi_course (course_id, club_id, club_name, course_name, city, country, num_holes, has_gps, num_coordinates, golfapi_updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, to_timestamp($10)) """, golfapi_course_id, course["clubID"], course["clubName"], course.get("courseName") or "Bane", course.get("city"), course.get("country"), num_holes, bool(int(course.get("hasGPS") or 0)), len(coordinates), int(course["timestampUpdated"]) if course.get("timestampUpdated") else None, ) pars_men = course["parsMen"] indexes_men = course["indexesMen"] pars_women = course.get("parsWomen") or [None] * num_holes indexes_women = course.get("indexesWomen") or [None] * num_holes for i in range(num_holes): await conn.execute( """ INSERT INTO golfapi_course_hole (golfapi_course_id, hole_number, par_men, index_men, par_women, index_women) VALUES ($1, $2, $3, $4, $5, $6) """, golfapi_course_id, i + 1, pars_men[i], indexes_men[i], pars_women[i], indexes_women[i], ) for t in course.get("tees", []): lengths = [t.get(f"length{n}") for n in range(1, num_holes + 1)] # Reelt funn (Larvik/Nesbyen, 2026-08-13): GolfAPI returnerer # tomme STRENGER ("") for manglende rating-felt på enkelte # baner -- IKKE bare for kvinner (opprinnelig antakelse, speilet # fra ADR-019s teeoff-import), Nesbyen manglet `slopeMen` også. # `.get(...) is not None` alene lot int()/numeric-kolonnene # krasje på disse. Normaliser "" -> None FØR bruk, for BEGGE # kjønn (migrasjon 068 gjorde course_rating_men/slope_men # nullable av samme grunn -- kolonnene tåler nå det cachen her # faktisk kan produsere). def _num(key: str): v = t.get(key) return None if v is None or v == "" else v slope_men = _num("slopeMen") slope_women = _num("slopeWomen") await conn.execute( """ INSERT INTO golfapi_course_tee (golfapi_course_id, golfapi_tee_id, name, color_hex, lengths_meters, course_rating_men, slope_men, course_rating_women, slope_women, course_rating_men_front9, course_rating_men_back9, course_rating_women_front9, course_rating_women_back9) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) """, golfapi_course_id, t["teeID"], t.get("teeName") or "Tee", t.get("teeColor"), lengths, _num("courseRatingMen"), int(slope_men) if slope_men is not None else None, _num("courseRatingWomen"), int(slope_women) if slope_women is not None else None, _num("courseRatingMenFront9"), _num("courseRatingMenBack9"), _num("courseRatingWomenFront9"), _num("courseRatingWomenBack9"), ) for c in coordinates: poi_type = _POI_TYPE_BY_CODE.get(int(c["poi"])) if poi_type is None: continue # ukjent/fremtidig GolfAPI-POI-kode -- hopp over, ikke feil hele importen await conn.execute( """ INSERT INTO golfapi_course_coordinate (golfapi_course_id, hole_number, poi_type, location, side_fairway, latitude, longitude) VALUES ($1, $2, $3, $4, $5, $6, $7) """, golfapi_course_id, int(c["hole"]), poi_type, _LOCATION_BY_CODE.get(int(c["location"])) if c.get("location") is not None else None, _SIDE_FW_BY_CODE.get(int(c["sideFW"])) if c.get("sideFW") is not None else None, c["latitude"], c["longitude"], )