diff --git a/app.R b/app.R index f6916fd..d385f1a 100644 --- a/app.R +++ b/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( + "", address, "
", + "Sale Date: ", listed_date, "
", + "Price: ", price_fmt, "
", + "Sq Ft: ", sqft, "
", + "$/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() %>% diff --git a/data-raw/update_sales.R b/data-raw/update_sales.R new file mode 100644 index 0000000..084c938 --- /dev/null +++ b/data-raw/update_sales.R @@ -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")