2026-08-12 21:39:13 +02:00
|
|
|
"""
|
|
|
|
|
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)]
|
2026-08-13 21:24:25 +02:00
|
|
|
# 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")
|
2026-08-12 21:39:13 +02:00
|
|
|
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,
|
2026-08-13 21:24:25 +02:00
|
|
|
_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"),
|
2026-08-12 21:39:13 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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"],
|
|
|
|
|
)
|