teecup/app/golfapi_cache.py

130 lines
5.9 KiB
Python
Raw Permalink Normal View History

"""
Cache-chokepoint mot GolfAPI.io (ADR-064). ALL import-kode (org-scopet
`course` OG global `personal_course`) 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"],
)