# update_listings.R # Weekly update: pull active listings from RentCast within 1 mile of # St. Andrews center, clip to plat boundary, save data/listings.rds. # Input: .Renviron (RENTCAST_API_KEY) # data/plats/plats.shp # Output: data/listings.rds library(httr2) library(jsonlite) library(sf) library(dplyr) # ── API key ─────────────────────────────────────────────────────────────────── api_key <- Sys.getenv("RENTCAST_API_KEY") if (identical(api_key, "")) stop("Missing RENTCAST_API_KEY in .Renviron") # ── Pull listings ───────────────────────────────────────────────────────────── resp <- request("https://api.rentcast.io/v1/listings/sale") |> req_headers( "Accept" = "application/json", "X-Api-Key" = api_key ) |> req_url_query( latitude = 27.076199, longitude = -82.362253, radius = 1, status = "Active", limit = 500 ) |> req_retry(max_tries = 3) |> req_perform() if (resp_status(resp) >= 400) stop("RentCast API error: HTTP ", resp_status(resp), "\n", resp_body_string(resp)) listings_raw <- jsonlite::fromJSON(resp_body_string(resp), flatten = TRUE) cat("Fetched:", nrow(listings_raw), "listings\n") # ── Clip to St. Andrews plat boundary ──────────────────────────────────────── plats <- sf::st_read("./data/plats/plats.shp", quiet = TRUE) |> sf::st_union() listings_sf <- sf::st_as_sf( listings_raw, coords = c("longitude", "latitude"), crs = 4326, remove = FALSE ) in_plat <- lengths(sf::st_within(listings_sf, plats)) > 0 listings <- listings_raw[in_plat, ] cat("After plat clip:", nrow(listings), "listings\n") # ── Select and clean columns ────────────────────────────────────────────────── listings <- listings |> transmute( listed_date = as.Date(listedDate), address = formattedAddress, sqft = as.numeric(squareFootage), price = as.numeric(price), price_per_sqft = round(price / sqft, 0), latitude, longitude ) |> arrange(desc(listed_date)) cat("Listings saved:", nrow(listings), "\n") saveRDS(listings, "./data/listings.rds")