diff --git a/.github/actions/prepare-ptaxsim/action.yaml b/.github/actions/prepare-ptaxsim/action.yaml index bd5fcf7..758fb3a 100644 --- a/.github/actions/prepare-ptaxsim/action.yaml +++ b/.github/actions/prepare-ptaxsim/action.yaml @@ -8,6 +8,9 @@ inputs: ASSUMED_ROLE: description: AWS role used for S3 actions outputs: + PTAXSIM_DB_DIR: + description: "PTAXSIM database directory on runner" + value: ${{ steps.set_db_dir.outputs.PTAXSIM_DB_DIR }} PTAXSIM_VERSION: description: "PTAXSIM database version" value: ${{ steps.version_db.outputs.PTAXSIM_VERSION }} @@ -18,6 +21,14 @@ runs: - name: Checkout uses: actions/checkout@v4 + - name: Set database directory + id: set_db_dir + run: | + PDIR=$RUNNER_TEMP + echo "PTAXSIM_DB_DIR=$PDIR" >> $GITHUB_ENV + echo "PTAXSIM_DB_DIR=$PDIR" >> $GITHUB_OUTPUT + shell: bash + - name: Get database version id: version_db run: | @@ -30,7 +41,7 @@ runs: uses: actions/cache@v3.3.2 id: cache_db with: - path: ptaxsim.db.bz2 + path: ${{ env.PTAXSIM_DB_DIR }}/ptaxsim.db.bz2 key: ${{ format('{0}-{1}', env.PTAXSIM_VERSION, hashFiles('DESCRIPTION')) }} enableCrossOsArchive: true @@ -47,23 +58,27 @@ runs: run: | aws s3 cp ${{ inputs.PTAXSIM_DB_BASE_URI }}/ptaxsim-${{ env.PTAXSIM_VERSION }}.db.bz2 ptaxsim.db.bz2 --quiet shell: bash + working-directory: ${{ env.PTAXSIM_DB_DIR }} - name: Unpack database (Linux) if: runner.os == 'Linux' run: | sudo apt-get install -y pbzip2 - pbzip2 -dk ${{ github.workspace }}/ptaxsim.db.bz2 + pbzip2 -dk ptaxsim.db.bz2 shell: bash + working-directory: ${{ env.PTAXSIM_DB_DIR }} - name: Unpack database (macOS) if: runner.os == 'macOS' run: | brew install pbzip2 - pbzip2 -dk ${{ github.workspace }}/ptaxsim.db.bz2 + pbzip2 -dk ptaxsim.db.bz2 shell: bash + working-directory: ${{ env.PTAXSIM_DB_DIR }} - name: Unpack database (Windows) if: runner.os == 'Windows' run: | - 7z x ${{ github.workspace }}\ptaxsim.db.bz2 + 7z x ptaxsim.db.bz2 shell: cmd + working-directory: ${{ env.PTAXSIM_DB_DIR }} diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 532692b..691fd43 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -19,7 +19,6 @@ jobs: - {os: ubuntu-latest, r: 'release'} - {os: ubuntu-latest, r: 'oldrel-1'} env: - PTAXSIM_DB_PATH: ${{ github.workspace }}/ptaxsim.db R_KEEP_PKG_SOURCE: yes # Required for OIDC access to S3 @@ -48,10 +47,15 @@ jobs: needs: check - name: Prepare PTAXSIM database + id: prep_ptaxsim_db uses: ./.github/actions/prepare-ptaxsim with: ASSUMED_ROLE: ${{ secrets.AWS_IAM_ROLE_TO_ASSUME_ARN }} + - name: Set PTAXSIM database path + run: echo "PTAXSIM_DB_PATH=${{ steps.prep_ptaxsim_db.outputs.PTAXSIM_DB_DIR }}/ptaxsim.db" >> $GITHUB_ENV + shell: bash + - name: Check R package uses: r-lib/actions/check-r-package@v2 with: diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 133749c..60817e0 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -10,8 +10,6 @@ name: pkgdown jobs: build-pkgdown-site: runs-on: ubuntu-latest - env: - PTAXSIM_DB_PATH: ${{ github.workspace }}/ptaxsim.db # Required for OIDC access to S3 permissions: @@ -37,10 +35,15 @@ jobs: needs: website - name: Prepare PTAXSIM database + id: prep_ptaxsim_db uses: ./.github/actions/prepare-ptaxsim with: ASSUMED_ROLE: ${{ secrets.AWS_IAM_ROLE_TO_ASSUME_ARN }} + - name: Set PTAXSIM database path + run: echo "PTAXSIM_DB_PATH=${{ steps.prep_ptaxsim_db.outputs.PTAXSIM_DB_DIR }}/ptaxsim.db" >> $GITHUB_ENV + shell: bash + - name: Build pkgdown site run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index cf19ee0..7f334de 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -8,8 +8,6 @@ name: test-coverage jobs: test-coverage: runs-on: ubuntu-latest - env: - PTAXSIM_DB_PATH: ${{ github.workspace }}/ptaxsim.db # Required for OIDC access to S3 permissions: @@ -32,10 +30,15 @@ jobs: needs: coverage - name: Prepare PTAXSIM database + id: prep_ptaxsim_db uses: ./.github/actions/prepare-ptaxsim with: ASSUMED_ROLE: ${{ secrets.AWS_IAM_ROLE_TO_ASSUME_ARN }} + - name: Set PTAXSIM database path + run: echo "PTAXSIM_DB_PATH=${{ steps.prep_ptaxsim_db.outputs.PTAXSIM_DB_DIR }}/ptaxsim.db" >> $GITHUB_ENV + shell: bash + - name: Test coverage run: | covr::codecov( diff --git a/R/tax_bill.R b/R/tax_bill.R index 5ebca11..887cd01 100644 --- a/R/tax_bill.R +++ b/R/tax_bill.R @@ -194,6 +194,7 @@ tax_bill <- function(year_vec, # Calculate the exemption effect by subtracting the exempt amount from # the total taxable EAV dt[, agency_tax_rate := agency_total_ext / as.numeric(agency_total_eav)] + dt[, agency_tax_rate := replace(agency_tax_rate, is.nan(agency_tax_rate), 0)] dt[, tax_amt_exe := exe_total * agency_tax_rate] dt[, tax_amt_pre_exe := round(eav * agency_tax_rate, 2)] dt[, tax_amt_post_exe := round(tax_amt_pre_exe - tax_amt_exe, 2)] diff --git a/tests/testthat/test-tax_bill.R b/tests/testthat/test-tax_bill.R index 16250c1..8799219 100644 --- a/tests/testthat/test-tax_bill.R +++ b/tests/testthat/test-tax_bill.R @@ -244,4 +244,10 @@ test_that("agnostic to input data.table row order", { ) }) +test_that("Returns 0 for agency with base/levy of 0", { + expect_false( + any(is.nan(tax_bill(2022, c("12283000140000", "12284120030000"))$final_tax)) + ) +}) + DBI::dbDisconnect(ptaxsim_db_conn) diff --git a/vignettes/appeals.Rmd b/vignettes/appeals.Rmd index afe9907..18c2937 100644 --- a/vignettes/appeals.Rmd +++ b/vignettes/appeals.Rmd @@ -50,6 +50,14 @@ library(sf) ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), here("./ptaxsim.db")) ``` +```{r, echo=FALSE} +# This is needed to build the vignette using GitHub Actions +ptaxsim_db_conn <- DBI::dbConnect( + RSQLite::SQLite(), + Sys.getenv("PTAXSIM_DB_PATH") +) +``` + ## Gathering PINs of interest To determine the impact of appeals, we first need a way to gather all the properties (PINs) in Schaumburg. Fortunately, PTAXSIM's database has all the data required to accomplish this task. @@ -228,7 +236,7 @@ sb_pins_summ <- sb_pins_all %>% ), idx = (med_av / med_av[stage == "2018\nboard"]) * 100 ) -``` +``` Finally, we can plot the index over time to see how reassessment and the subsequent appeals at each stage impacted assessed values. diff --git a/vignettes/exemptions.Rmd b/vignettes/exemptions.Rmd index 58b6e60..e108381 100644 --- a/vignettes/exemptions.Rmd +++ b/vignettes/exemptions.Rmd @@ -13,7 +13,7 @@ knitr::opts_chunk$set( # Introduction -Property tax exemptions are savings that lower a homeowner’s property tax bill. They work by reducing or freezing the taxable value (Equalized Assessed Value, or EAV) of a home. For example, the most common exemption, the Homeowner Exemption, reduces EAV by \$10,000. +Property tax exemptions are savings that lower a homeowner’s property tax bill. They work by reducing or freezing the taxable value (Equalized Assessed Value, or EAV) of a home. For example, the most common exemption, the Homeowner Exemption, reduces EAV by \$10,000. Once the exemption EAV has been subtracted from the property's EAV, the final EAV is multiplied by the local tax rate to get the final property tax bill. Since local tax rates can vary significantly by area, the actual effective value of each exemption likewise varies geographically. @@ -41,6 +41,14 @@ library(ptaxsim) ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), here("./ptaxsim.db")) ``` +```{r, echo=FALSE} +# This is needed to build the vignette using GitHub Actions +ptaxsim_db_conn <- DBI::dbConnect( + RSQLite::SQLite(), + Sys.getenv("PTAXSIM_DB_PATH") +) +``` + The PIN we'll use is **25-32-114-005-0000**, the same PIN whose bill is shown above. This is a small, single-family property in Calumet with a very typical exemption situation, just a Homeowner Exemption. We can use the `tax_bill()` function to get every bill for this PIN from the last 15 years. These bills will _include_ any reduction from exemptions the PIN received. @@ -183,7 +191,7 @@ The exemption amount for this PIN has increased in tandem with increases in the We can also use PTAXSIM to answer hypotheticals. For example, how would this PIN's bill history change if the Homeowner Exemption increased from \$10,000 to \$15,000 in 2018? -To find out, we again create a modified PIN input to pass to `tax_bill()`. This time, we increase the Homeowner Exemption to $15,000 for all years after 2018. +To find out, we again create a modified PIN input to pass to `tax_bill()`. This time, we increase the Homeowner Exemption to $15,000 for all years after 2018. ```{r} exe_dt_2 <- lookup_pin(2006:2020, "25321140050000") %>% @@ -293,7 +301,7 @@ We're using `data.table` syntax here because it's much faster than `dplyr` when t_bills_w_exe <- tax_bill(t_years, t_pins)[, stage := "With exemptions"] ``` -Unlike a single PIN, removing exemptions from many PINs means that the base (the amount of total taxable value available) will change substantially. In order to accurately model the effect of removing exemptions, we need to fully recalculate the base of each district by adding the sum of taxable value recovered from each PIN. +Unlike a single PIN, removing exemptions from many PINs means that the base (the amount of total taxable value available) will change substantially. In order to accurately model the effect of removing exemptions, we need to fully recalculate the base of each district by adding the sum of taxable value recovered from each PIN. To start, we use the `lookup_pin()` function to recover the total EAV of exemptions for each PIN. @@ -367,7 +375,7 @@ t_no_exe_summ <- rbind(t_bills_w_exe, t_bills_no_exe)[ ] ``` -Finally, we can plot the average bill with and without exemptions by property type. +Finally, we can plot the average bill with and without exemptions by property type.
@@ -427,7 +435,7 @@ Exemptions in Calumet have significantly increased in both volume and amount (vi Conversely, Calumet's commercial property owners have picked up an increasingly large share of the overall tax burden since 2006. In 2019, the average commercial property paid about $1,100 more than they would have if exemptions did not exist. -## Changing exemptions +## Changing exemptions PTAXSIM can also answer hypotheticals about large areas. For example, how would the average residential tax bill in Calumet change if the Senior Exemption increased by $5,000 and the Senior Freeze Exemption was removed? diff --git a/vignettes/introduction.Rmd b/vignettes/introduction.Rmd index d8d1960..ffeec82 100644 --- a/vignettes/introduction.Rmd +++ b/vignettes/introduction.Rmd @@ -49,6 +49,14 @@ library(ptaxsim) ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), here("ptaxsim.db")) ``` + ```{r, echo=FALSE} +# This is needed to build the vignette using GitHub Actions +ptaxsim_db_conn <- DBI::dbConnect( + RSQLite::SQLite(), + Sys.getenv("PTAXSIM_DB_PATH") +) + ``` + ## The main function - `tax_bill()` {#main-arguments} PTAXSIM has a single primary function - `tax_bill()` - with two *required* arguments: diff --git a/vignettes/mapping.Rmd b/vignettes/mapping.Rmd index bbe4e93..93c2274 100644 --- a/vignettes/mapping.Rmd +++ b/vignettes/mapping.Rmd @@ -41,6 +41,14 @@ library(tidyr) ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), here("./ptaxsim.db")) ``` +```{r, echo=FALSE} +# This is needed to build the vignette using GitHub Actions +ptaxsim_db_conn <- DBI::dbConnect( + RSQLite::SQLite(), + Sys.getenv("PTAXSIM_DB_PATH") +) +``` + ## Single taxing district We're going to use the the Village of Ford Heights as our example taxing district, since it's relatively small and uncomplicated. @@ -362,7 +370,7 @@ fhm_pins <- DBI::dbGetQuery( ) ``` -This gives us about 2,300 PINs, roughly 300 more than the Village of Ford Heights has alone. We can fetch and prepare the geometries the same way we did for the single district case. +This gives us about 2,300 PINs, roughly 300 more than the Village of Ford Heights has alone. We can fetch and prepare the geometries the same way we did for the single district case. ```{r} fhm_pins_geo <- lookup_pin10_geometry( diff --git a/vignettes/reassessment.Rmd b/vignettes/reassessment.Rmd index feb8817..3645eb8 100644 --- a/vignettes/reassessment.Rmd +++ b/vignettes/reassessment.Rmd @@ -46,7 +46,15 @@ library(tidyr) ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), here("./ptaxsim.db")) ``` -The example PIN we’ll use is **16-26-406-015-0000**. This is a small, single-family property in West Chicago without any exemptions. We can use the `tax_bill()` function to get every bill for this PIN from 2006 to 2021. +```{r, echo=FALSE} +# This is needed to build the vignette using GitHub Actions +ptaxsim_db_conn <- DBI::dbConnect( + RSQLite::SQLite(), + Sys.getenv("PTAXSIM_DB_PATH") +) +``` + +The example PIN we’ll use is **16-26-406-015-0000**. This is a small, single-family property in West Chicago without any exemptions. We can use the `tax_bill()` function to get every bill for this PIN from 2006 to 2021. ```{r} p <- tax_bill(2006:2021, "16264060150000") @@ -744,7 +752,7 @@ cw_fut_agency_tots <- DBI::dbGetQuery( ## Levies -We also need to recalculate levies for the whole county based on our assumption: +We also need to recalculate levies for the whole county based on our assumption: - All levies will change by their average change of the last 3 years @@ -768,7 +776,7 @@ cw_fut_levies <- lookup_agency( ## Preparing inputs -Now that we've calculated our hypothetical future values, we need to transform the data into the format excepted by `tax_bill()`. We do this by replicating the input format provided by each `lookup_` function from PTAXSIM. +Now that we've calculated our hypothetical future values, we need to transform the data into the format excepted by `tax_bill()`. We do this by replicating the input format provided by each `lookup_` function from PTAXSIM. ```{r} # Prep pin_dt. Keep only Ward 22 pins diff --git a/vignettes/tifs.Rmd b/vignettes/tifs.Rmd index 503be04..1122a2d 100644 --- a/vignettes/tifs.Rmd +++ b/vignettes/tifs.Rmd @@ -41,6 +41,14 @@ library(sf) ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), here("./ptaxsim.db")) ``` +```{r, echo=FALSE} +# This is needed to build the vignette using GitHub Actions +ptaxsim_db_conn <- DBI::dbConnect( + RSQLite::SQLite(), + Sys.getenv("PTAXSIM_DB_PATH") +) +``` + ## Gathering PINs of interest To determine the TIF's impact, we first need a way to gather all the properties (PINs) in Wheeling. Fortunately, PTAXSIM's database has all the data required to accomplish this task. @@ -188,7 +196,7 @@ wh_pins_map The map shows the TIF district highlighted in purple. It covers mostly commercial buildings and a small park. The primary development is called the [Wheeling Town Center](https://thewheelingtowncenter.com/). -### A quick aside: tax codes +### A quick aside: tax codes The Wheeling Town Center II TIF was created by layering an additional taxing district boundary onto existing districts. When this happened, a new ***tax code*** was created. A tax code is a 5-digit number that identifies the unique combination of overlapping tax districts for a given area.