Add Sales tab with map and crosstalk table; weekly cron at 11:45pm Sunday
All checks were successful
Deploy stAndrews / deploy (push) Successful in 4s
All checks were successful
Deploy stAndrews / deploy (push) Successful in 4s
This commit is contained in:
72
app.R
72
app.R
@@ -17,6 +17,13 @@ listings <- readRDS("./data/listings.rds") |>
|
||||
ppsf_fmt = scales::dollar(price_per_sqft)
|
||||
)
|
||||
shared_listings <- SharedData$new(listings, key = ~address)
|
||||
sales <- readRDS("./data/sales.rds") |>
|
||||
arrange(desc(listed_date)) |>
|
||||
mutate(
|
||||
price_fmt = scales::dollar(price),
|
||||
ppsf_fmt = scales::dollar(price_per_sqft)
|
||||
)
|
||||
shared_sales <- SharedData$new(sales, key = ~address)
|
||||
last_sale_date <- format(attr(owners, "last_sale_date"), "%Y-%m-%d")
|
||||
sbdvn <- sf::st_read("./data/plats/plats.shp")
|
||||
beaches <- readRDS("./data/beaches.rds")
|
||||
@@ -93,6 +100,11 @@ ui <- f7Page(
|
||||
title = "Listings",
|
||||
icon = f7Icon("tag_fill")
|
||||
),
|
||||
f7PanelItem(
|
||||
tabName = "Sales",
|
||||
title = "Sales",
|
||||
icon = f7Icon("dollarsign_circle_fill")
|
||||
),
|
||||
f7PanelItem(
|
||||
tabName = "Resources",
|
||||
title = "Resources",
|
||||
@@ -249,6 +261,22 @@ ui <- f7Page(
|
||||
DTOutput("listings_table")
|
||||
)
|
||||
),
|
||||
#### sales ----
|
||||
f7Tab(
|
||||
title = "Sales",
|
||||
tabName = "Sales",
|
||||
icon = f7Icon("dollarsign_circle_fill"),
|
||||
f7Card(
|
||||
title = "Map:",
|
||||
divider = TRUE,
|
||||
leafletOutput("sales_map")
|
||||
),
|
||||
f7Card(
|
||||
title = "Recent Sales:",
|
||||
divider = TRUE,
|
||||
DTOutput("sales_table")
|
||||
)
|
||||
),
|
||||
#### services ----
|
||||
f7Tab(
|
||||
title = "Resources",
|
||||
@@ -504,6 +532,50 @@ server <- function(input, output) {
|
||||
)
|
||||
})
|
||||
|
||||
# sales map ----
|
||||
output$sales_map <- renderLeaflet({
|
||||
leaflet(shared_sales) |>
|
||||
addProviderTiles("CartoDB.Voyager") |>
|
||||
addPolygons(
|
||||
data = sbdvn,
|
||||
color = "red",
|
||||
weight = 2,
|
||||
opacity = 0.5,
|
||||
fillOpacity = 0.2,
|
||||
label = ~sub_name
|
||||
) |>
|
||||
addMarkers(
|
||||
lat = ~latitude,
|
||||
lng = ~longitude,
|
||||
popup = ~paste0(
|
||||
"<b>", address, "</b><br>",
|
||||
"Sale Date: ", listed_date, "<br>",
|
||||
"Price: ", price_fmt, "<br>",
|
||||
"Sq Ft: ", sqft, "<br>",
|
||||
"$/Sq Ft: ", ppsf_fmt
|
||||
)
|
||||
) |>
|
||||
setView(lng = -82.362253, lat = 27.076199, zoom = 16)
|
||||
})
|
||||
|
||||
# sales table ----
|
||||
output$sales_table <- renderDT(server = FALSE, {
|
||||
datatable(
|
||||
shared_sales,
|
||||
colnames = c("Date", "Address", "Sq Ft", "Price (raw)", "$/Sq Ft (raw)",
|
||||
"Lat", "Lng", "Price", "$/Sq Ft"),
|
||||
rownames = FALSE,
|
||||
options = list(
|
||||
pageLength = 10,
|
||||
searching = FALSE,
|
||||
dom = 't',
|
||||
columnDefs = list(
|
||||
list(visible = FALSE, targets = c(3, 4, 5, 6))
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
# beach map ----
|
||||
output$beach_map <- renderLeaflet({
|
||||
leaflet() %>%
|
||||
|
||||
61
data-raw/update_sales.R
Normal file
61
data-raw/update_sales.R
Normal file
@@ -0,0 +1,61 @@
|
||||
# update_sales.R
|
||||
# Pull the 10 most recent arm's-length sales in St. Andrews from
|
||||
# SCPA_Parcels_Sales_CSV.zip (Sarasota.csv). Joins to geometry_lookup
|
||||
# for coordinates. Downloads the zip fresh each run.
|
||||
# Input: data-raw/addresses/geometry_lookup.rds (static)
|
||||
# Output: data/sales.rds
|
||||
|
||||
library(readr)
|
||||
library(dplyr)
|
||||
library(stringr)
|
||||
library(sf)
|
||||
|
||||
subdivisions <- c(
|
||||
"8120", "8113", "8171", "8195", "8221",
|
||||
"8163", "8240", "8159", "8149", "8110", "8254", "8215", "8143"
|
||||
)
|
||||
|
||||
geometry_lookup <- readRDS("./data-raw/addresses/geometry_lookup.rds")
|
||||
|
||||
# ── Download and extract Sarasota.csv ─────────────────────────────────────────
|
||||
zip_path <- "./data-raw/property/SCPA_Parcels_Sales_CSV.zip"
|
||||
options(timeout = 300)
|
||||
download.file(
|
||||
url = "https://www.sc-pa.com/downloads/SCPA_Parcels_Sales_CSV.zip",
|
||||
destfile = zip_path,
|
||||
mode = "wb"
|
||||
)
|
||||
|
||||
csv_con <- unz(zip_path, "Parcel_Sales_CSV/Sarasota.csv")
|
||||
|
||||
# ── Load and filter ───────────────────────────────────────────────────────────
|
||||
sales <-
|
||||
read_csv(csv_con, show_col_types = FALSE) |>
|
||||
filter(SUBD %in% subdivisions) |>
|
||||
filter(QUAL_CODE %in% c("01", "03")) |>
|
||||
filter(SALE_AMT > 0, LIVING > 0) |>
|
||||
mutate(
|
||||
listed_date = as.Date(SALE_DATE, format = "%m/%d/%Y"),
|
||||
address = str_squish(paste(LOCN, LOCS, LOCCITY, LOCSTATE, LOCZIP)),
|
||||
sqft = as.integer(LIVING),
|
||||
price = as.integer(SALE_AMT),
|
||||
price_per_sqft = round(price / sqft, 0),
|
||||
account_number = str_trim(ACCOUNT)
|
||||
) |>
|
||||
arrange(desc(listed_date)) |>
|
||||
slice_head(n = 10) |>
|
||||
select(account_number, listed_date, address, sqft, price, price_per_sqft)
|
||||
|
||||
# ── Join geometry ─────────────────────────────────────────────────────────────
|
||||
sales <- sales |>
|
||||
inner_join(geometry_lookup, by = "account_number") |>
|
||||
st_as_sf(sf_column_name = "geom") |>
|
||||
mutate(
|
||||
longitude = st_coordinates(geom)[, 1],
|
||||
latitude = st_coordinates(geom)[, 2]
|
||||
) |>
|
||||
st_drop_geometry() |>
|
||||
select(listed_date, address, sqft, price, price_per_sqft, latitude, longitude)
|
||||
|
||||
cat("Sales written:", nrow(sales), "\n")
|
||||
saveRDS(sales, "./data/sales.rds")
|
||||
Reference in New Issue
Block a user