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")