Add active listings tab from RentCast API
- update_listings.R: pulls 1-mile radius, clips to plat boundary - Listings tab: table with listed date, address, sqft, price, $/sqft - Weekly cron: Sunday 11:30pm (30 min after owners refresh) - httr2 added to renv.lock
This commit is contained in:
38
app.R
38
app.R
@@ -8,7 +8,8 @@ library(leafpop)
|
|||||||
library(DT)
|
library(DT)
|
||||||
|
|
||||||
# load data ----
|
# load data ----
|
||||||
owners <- readRDS("./data/owners.rds")
|
owners <- readRDS("./data/owners.rds")
|
||||||
|
listings <- readRDS("./data/listings.rds")
|
||||||
last_sale_date <- format(attr(owners, "last_sale_date"), "%Y-%m-%d")
|
last_sale_date <- format(attr(owners, "last_sale_date"), "%Y-%m-%d")
|
||||||
sbdvn <- sf::st_read("./data/plats/plats.shp")
|
sbdvn <- sf::st_read("./data/plats/plats.shp")
|
||||||
venice_bndry <- readRDS("./data/venice.rds")
|
venice_bndry <- readRDS("./data/venice.rds")
|
||||||
@@ -87,6 +88,11 @@ ui <- f7Page(
|
|||||||
title = "Owners",
|
title = "Owners",
|
||||||
icon = f7Icon("person_2_fill")
|
icon = f7Icon("person_2_fill")
|
||||||
),
|
),
|
||||||
|
f7PanelItem(
|
||||||
|
tabName = "Listings",
|
||||||
|
title = "Listings",
|
||||||
|
icon = f7Icon("tag_fill")
|
||||||
|
),
|
||||||
f7PanelItem(
|
f7PanelItem(
|
||||||
tabName = "Resources",
|
tabName = "Resources",
|
||||||
title = "Resources",
|
title = "Resources",
|
||||||
@@ -244,6 +250,17 @@ ui <- f7Page(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
#### listings ----
|
||||||
|
f7Tab(
|
||||||
|
title = "Listings",
|
||||||
|
tabName = "Listings",
|
||||||
|
icon = f7Icon("tag_fill"),
|
||||||
|
f7Card(
|
||||||
|
title = "Active Listings:",
|
||||||
|
divider = TRUE,
|
||||||
|
DTOutput("listings_table")
|
||||||
|
)
|
||||||
|
),
|
||||||
#### services ----
|
#### services ----
|
||||||
f7Tab(
|
f7Tab(
|
||||||
title = "Resources",
|
title = "Resources",
|
||||||
@@ -456,6 +473,25 @@ server <- function(input, output) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# listings table ----
|
||||||
|
output$listings_table <- renderDT({
|
||||||
|
datatable(
|
||||||
|
listings |>
|
||||||
|
select(listed_date, address, sqft, price, price_per_sqft) |>
|
||||||
|
mutate(
|
||||||
|
price = scales::dollar(price),
|
||||||
|
price_per_sqft = scales::dollar(price_per_sqft)
|
||||||
|
),
|
||||||
|
colnames = c("Listed", "Address", "Sq Ft", "Price", "$/Sq Ft"),
|
||||||
|
rownames = FALSE,
|
||||||
|
options = list(
|
||||||
|
pageLength = 25,
|
||||||
|
searching = FALSE,
|
||||||
|
dom = 't'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
# venice map ----
|
# venice map ----
|
||||||
output$venice_map <- renderLeaflet({
|
output$venice_map <- renderLeaflet({
|
||||||
leaflet() %>%
|
leaflet() %>%
|
||||||
|
|||||||
68
data-raw/update_listings.R
Normal file
68
data-raw/update_listings.R
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# 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")
|
||||||
60
renv.lock
60
renv.lock
@@ -1625,6 +1625,66 @@
|
|||||||
"Maintainer": "Hadley Wickham <hadley@posit.co>",
|
"Maintainer": "Hadley Wickham <hadley@posit.co>",
|
||||||
"Repository": "P3M"
|
"Repository": "P3M"
|
||||||
},
|
},
|
||||||
|
"httr2": {
|
||||||
|
"Package": "httr2",
|
||||||
|
"Version": "1.2.2",
|
||||||
|
"Source": "Repository",
|
||||||
|
"Title": "Perform HTTP Requests and Process the Responses",
|
||||||
|
"Authors@R": "c( person(\"Hadley\", \"Wickham\", , \"hadley@posit.co\", role = c(\"aut\", \"cre\")), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")), person(\"Maximilian\", \"Girlich\", role = \"ctb\") )",
|
||||||
|
"Description": "Tools for creating and modifying HTTP requests, then performing them and processing the results. 'httr2' is a modern re-imagining of 'httr' that uses a pipe-based interface and solves more of the problems that API wrapping packages face.",
|
||||||
|
"License": "MIT + file LICENSE",
|
||||||
|
"URL": "https://httr2.r-lib.org, https://github.com/r-lib/httr2",
|
||||||
|
"BugReports": "https://github.com/r-lib/httr2/issues",
|
||||||
|
"Depends": [
|
||||||
|
"R (>= 4.1)"
|
||||||
|
],
|
||||||
|
"Imports": [
|
||||||
|
"cli (>= 3.0.0)",
|
||||||
|
"curl (>= 6.4.0)",
|
||||||
|
"glue",
|
||||||
|
"lifecycle",
|
||||||
|
"magrittr",
|
||||||
|
"openssl",
|
||||||
|
"R6",
|
||||||
|
"rappdirs",
|
||||||
|
"rlang (>= 1.1.0)",
|
||||||
|
"vctrs (>= 0.6.3)",
|
||||||
|
"withr"
|
||||||
|
],
|
||||||
|
"Suggests": [
|
||||||
|
"askpass",
|
||||||
|
"bench",
|
||||||
|
"clipr",
|
||||||
|
"covr",
|
||||||
|
"docopt",
|
||||||
|
"httpuv",
|
||||||
|
"jose",
|
||||||
|
"jsonlite",
|
||||||
|
"knitr",
|
||||||
|
"later (>= 1.4.0)",
|
||||||
|
"nanonext",
|
||||||
|
"otel (>= 0.2.0)",
|
||||||
|
"otelsdk (>= 0.2.0)",
|
||||||
|
"paws.common (>= 0.8.0)",
|
||||||
|
"promises",
|
||||||
|
"rmarkdown",
|
||||||
|
"testthat (>= 3.1.8)",
|
||||||
|
"tibble",
|
||||||
|
"webfakes (>= 1.4.0)",
|
||||||
|
"xml2"
|
||||||
|
],
|
||||||
|
"VignetteBuilder": "knitr",
|
||||||
|
"Config/Needs/website": "tidyverse/tidytemplate",
|
||||||
|
"Config/testthat/edition": "3",
|
||||||
|
"Config/testthat/parallel": "true",
|
||||||
|
"Config/testthat/start-first": "resp-stream, req-perform",
|
||||||
|
"Encoding": "UTF-8",
|
||||||
|
"RoxygenNote": "7.3.3",
|
||||||
|
"NeedsCompilation": "no",
|
||||||
|
"Author": "Hadley Wickham [aut, cre], Posit Software, PBC [cph, fnd], Maximilian Girlich [ctb]",
|
||||||
|
"Maintainer": "Hadley Wickham <hadley@posit.co>",
|
||||||
|
"Repository": "CRAN"
|
||||||
|
},
|
||||||
"janitor": {
|
"janitor": {
|
||||||
"Package": "janitor",
|
"Package": "janitor",
|
||||||
"Version": "2.2.1",
|
"Version": "2.2.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user