Skip to content

Commit 2508f2f

Browse files
Merge pull request #12 from California-Data-Collaborative/fixTierRounding
Updated rounding for budget-based tiers closes #8
2 parents 8ae7e8a + f2a0bc0 commit 2508f2f

4 files changed

Lines changed: 63 additions & 19 deletions

File tree

R/rateParser.R

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ add_rate_part_to_frame <- function(df, name, name_list, class_rate, cust_class){
117117
df <- bind_cols( df, variable_bills )
118118
}
119119
else{
120-
df[[name]] <- eval_field_or_formula(df, rate_part)
120+
df[[name]] <- eval_field_or_formula(df, name, rate_part)
121121
}
122122

123123
return(df)
@@ -181,14 +181,31 @@ read_owrs_file <- function(filepath){
181181
#' the provided dataframe.
182182
#'
183183
#' @param df Data frame containing all data values referenced in field or formula.
184+
#' @param name Name of the rate part
184185
#' @param rate_part List representing a portion of a rate structure, defined in the
185186
#' \href{https://github.com/California-Data-Collaborative/Open-Water-Rate-Specification}{OWRS file}
186187
#'
187188
#' @return Either a single value or a vector the length of \code{df}. The result of
188189
#' evaluating a field or formula in the context of the dataframe.
189190
#'
190191
#' @keywords internal
191-
eval_field_or_formula <- function(df, rate_part){
192+
eval_field_or_formula <- function(df, name, rate_part){
193+
#if field/formula represent a budget, we round each of the budget components
194+
if(grepl("budget", name)){
195+
rate_part <- gsub("\\+", " + ", rate_part)
196+
rate_part <- gsub("\\*", " * ", rate_part)
197+
rate_part <- gsub("\\^", " ^ ", rate_part)
198+
199+
ls <- unlist(strsplit(rate_part, " +"))
200+
for(i in 1:length(ls)){
201+
s <- ls[i]
202+
if(!(s %in% c("+","*","^")))
203+
ls[i] <- paste("round(",s,")")
204+
}
205+
rate_part <- paste(ls, collapse=" ")
206+
}
207+
208+
192209
return(eval(parse(text=rate_part), df))
193210
}
194211

R/tier_calcs.R

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ calculate_variable_bill <- function(data, rate_type, start_name="tier_starts",
1212

1313
#call correct bill calculator function
1414
if(rate_type == "Tiered"){
15+
stopif(grepl("%", tier_start_str),
16+
paste("Tiers are formatted for budget-based rates, but commodity_charge is set to 'Tiered'.",
17+
"Please set 'commodity_charge: Budget' or change your tier_starts to billing units."))
18+
1519
tier_starts <- parse_numerics(tier_start_str)
1620
tier_prices <- parse_numerics(tier_price_str)
1721
#check that prices are same length as tiers
@@ -69,7 +73,7 @@ get_usage_in_tiers <- function(data, tier_starts, budget_based=FALSE){
6973
for(i in 1:(num_tiers-1) ){
7074
# tier_stars is a matrix if budget, else is a vector
7175
if(budget_based){
72-
t <- ceiling( tier_starts[,i+1] )
76+
t <- tier_starts[,i+1] + 1
7377
}
7478
else{
7579
t <- tier_starts[i+1]
@@ -128,16 +132,16 @@ get_budget_tiers <- function(data, tier_start_strs, budget_col){
128132
budget_tiers[,i] <- suppressWarnings(as.numeric(t))
129133
}
130134
else if(tolower(t) == "indoor"){
131-
budget_tiers[,i] <- data$indoor
135+
budget_tiers[,i] <- round(data$indoor)
132136
}
133137
else if(tolower(t) == "outdoor"){
134-
budget_tiers[,i] <- data$outdoor
138+
budget_tiers[,i] <- round(data$outdoor)
135139
}
136140
else if( grepl("%", t) ){
137141
percent <- as.numeric( gsub("[^0-9\\.]", "", t, "") )
138142
stopifnot(is.finite(percent))
139143

140-
budget_tiers[,i] <- (percent/100)*budget
144+
budget_tiers[,i] <- round((percent/100)*budget)
141145
}
142146
}
143147

man/eval_field_or_formula.Rd

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test_calculate_bills.R

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ rows[[8]] <- list("usage_ccf"=27.3,
6060
"et_amount"=4.8,
6161
"irrigable_area"=1300,
6262
"hhsize"=3)
63+
rows[[9]] <- list("usage_ccf"=27.3,
64+
"meter_size"='5/8"',
65+
"cust_class"="ERROR_CLASS4",
66+
"water_type"="POTABLE",
67+
"et_amount"=4.8,
68+
"irrigable_area"=1300,
69+
"hhsize"=3)
6370
df_test <- do.call(rbind.data.frame, rows[1:3])
6471

6572
yaml_rates <- '
@@ -110,14 +117,14 @@ rate_structure:
110117
tier_starts:
111118
- 0
112119
- indoor
113-
- 100%
114-
- 125%
120+
- 101%
121+
- 126%
115122
tier_prices:
116123
- 2.87
117124
- 4.29
118125
- 6.44
119126
- 10.07
120-
commodity_charge: Tiered
127+
commodity_charge: Budget
121128
bill: commodity_charge + service_charge
122129
IRRIGATION:
123130
service_charge:
@@ -242,6 +249,15 @@ rate_structure:
242249
- 6.33
243250
commodity_charge: Tiered
244251
bill: commodity_charge + service_charge
252+
ERROR_CLASS4:
253+
tier_starts:
254+
- 0
255+
- 101%
256+
tier_prices:
257+
- 4.07
258+
- 10.03
259+
commodity_charge: Tiered
260+
bill: commodity_charge
245261
'
246262
test_rates <- yaml.load(yaml_rates)
247263

@@ -253,15 +269,16 @@ calc <- function(df){
253269
manual_bill_1 <- 33 + (4.07*388) + (2*10 + 0*378)
254270
manual_bill_2 <- 11 + (2.87*14 + 4.29*6 + 6.44*5 + 10.07*2.3) + (2*10 + 0*17.3)
255271

256-
manual_budget_3 <- 0.7*4.8*4500*0.62*(1/748)
257-
manual_bill_3 <- 22 + 3.66*floor(manual_budget_3) + 6.33*(41 - floor(manual_budget_3) ) + 1.5*41
272+
manual_budget_3 <- round(0.7*4.8*4500*0.62*(1/748))
273+
manual_bill_3 <- 22 + 3.66*manual_budget_3 + 6.33*(41 - manual_budget_3 ) + 1.5*41
258274

259-
# manual_indoor_8 <- 60*3*30.4*(1/748)
260-
# manual_outdoor_8 <- 0.7 * 1300 * 4.8 * 0.62 * (1/748)
261-
# manual_budget_8 <- manual_indoor_8 + manual_outdoor_8
262-
# manual_bill_8 <- 11 + 2.87*floor(manual_indoor) +
263-
# 4.29*floor(manual_outdoor) +
264-
# 6.44*floor(1.25*manual_budget_8 - manual_budget_8) + 10.07*(27.3-1.25*manual_budget_8)
275+
manual_indoor_8 <- round(60*3*30.4*(1/748))
276+
manual_outdoor_8 <- round(0.7 * 1300 * 4.8 * 0.62 * (1/748))
277+
manual_budget_8 <- round(manual_indoor_8 + manual_outdoor_8)
278+
manual_bill_8 <- 11 + 2.87*manual_indoor_8 +
279+
4.29*manual_outdoor_8 +
280+
6.44*(round(1.26*manual_budget_8) - manual_budget_8) +
281+
10.07*(27.3-round(1.26*manual_budget_8))
265282

266283
manual_bills <- c(manual_bill_1, manual_bill_2, manual_bill_3)
267284

@@ -270,7 +287,7 @@ test_that("Individual bills calculated accurately", {
270287
expect_equal(calc(as.data.frame(rows[[1]]))$bill, manual_bill_1)
271288
expect_equal(calc(as.data.frame(rows[[2]]))$bill, manual_bill_2)
272289
expect_equal(calc(as.data.frame(rows[[3]]))$bill, manual_bill_3)
273-
# expect_equal(calc(as.data.frame(rows[[8]]))$bill, manual_bill_8)
290+
expect_equal(calc(as.data.frame(rows[[8]]))$bill, manual_bill_8)
274291
})
275292

276293
test_that("Bills accurate when summed accross customer classes", {
@@ -289,5 +306,9 @@ test_that("Error thrown when a field is missing", {
289306
expect_error(calc(as.data.frame(rows[[7]])), "is not present in the OWRS file for customer class")
290307
})
291308

309+
test_that("Error thrown when % tiers used in 'Tiered' rate", {
310+
expect_error(calc(as.data.frame(rows[[9]])), "Tiers are formatted for budget-based rates")
311+
})
312+
292313

293314

0 commit comments

Comments
 (0)