tests/testthat/test_calculate_bills.R

library(RateParser)
library(yaml)
context("Calculate Bills")

#create test data
rows <- list()
rows[[1]] <- list("usage_ccf"=388,
                 "meter_size"='3"',
                 "cust_class"="COMMERCIAL",
                 "water_type"="POTABLE",
                 "et_amount"=0,
                 "irrigable_area"=0,
                 "hhsize"=0)
rows[[2]] <- list("usage_ccf"=27.3,
                 "meter_size"='5/8"',
                 "cust_class"="RESIDENTIAL_SINGLE",
                 "water_type"="POTABLE",
                 "et_amount"=4.8,
                 "irrigable_area"=1300,
                 "hhsize"=3)
rows[[3]] <- list("usage_ccf"=41,
                 "meter_size"='1"',
                 "cust_class"="IRRIGATION",
                 "water_type"="RECYCLED",
                 "et_amount"=4.8,
                 "irrigable_area"=4500,
                 "hhsize"=0)
rows[[4]] <- list("usage_ccf"=41,
                  "meter_size"='1"',
                  "cust_class"="RESIDENTIAL_MULTI",
                  "water_type"="RECYCLED",
                  "et_amount"=4.8,
                  "irrigable_area"=4500,
                  "hhsize"=0)
rows[[5]] <- list("usage_ccf"=388,
                  "meter_size"='3"',
                  "cust_class"="ERROR_CLASS1",
                  "water_type"="POTABLE",
                  "et_amount"=0,
                  "irrigable_area"=0,
                  "hhsize"=0)
rows[[6]] <- list("usage_ccf"=388,
                  "meter_size"='3"',
                  "cust_class"="ERROR_CLASS2",
                  "water_type"="POTABLE",
                  "et_amount"=0,
                  "irrigable_area"=0,
                  "hhsize"=0)
rows[[7]] <- list("usage_ccf"=388,
                  "meter_size"='3"',
                  "cust_class"="ERROR_CLASS3",
                  "water_type"="POTABLE",
                  "et_amount"=0,
                  "irrigable_area"=0,
                  "hhsize"=0)
rows[[8]] <- list("usage_ccf"=27.3,
                  "meter_size"='5/8"',
                  "cust_class"="RESIDENTIAL_BUDGETBASED",
                  "water_type"="POTABLE",
                  "et_amount"=4.8,
                  "irrigable_area"=1300,
                  "hhsize"=3)
rows[[9]] <- list("usage_ccf"=27.3,
                  "meter_size"='5/8"',
                  "cust_class"="ERROR_CLASS4",
                  "water_type"="POTABLE",
                  "et_amount"=4.8,
                  "irrigable_area"=1300,
                  "hhsize"=3)
df_test <- do.call(rbind.data.frame, rows[1:3])

yaml_rates <- '
metadata:
  effective_date: 2016-03-01
  utility_name: "City of Example"
  bill_frequency: bimonthly
rate_structure:
  RESIDENTIAL_SINGLE:
    service_charge:
      depends_on: meter_size
      values:
        5/8": 11
        1": 22
        3": 33
    tier_starts:
      - 0
      - 15
      - 21
      - 26
    tier_prices:
      - 2.87
      - 4.29
      - 6.44
      - 10.07
    commodity_charge: Tiered
    sewer_tier_starts:
      - 0
      - 11
    sewer_charge: Tiered
    sewer_tier_prices:
      - 2
      - 0
    bill: commodity_charge + service_charge + sewer_charge
  RESIDENTIAL_BUDGETBASED:
    service_charge:
      depends_on: meter_size
      values:
        5/8": 11
        1": 22
        3": 33
    gpcd: 60
    landscape_factor: 0.7
    days_in_period: 30.4
    indoor: "gpcd*hhsize*days_in_period*(1/748)"
    outdoor: "landscape_factor*et_amount*irrigable_area*0.62*(1/748)"
    budget: "indoor+outdoor"
    tier_starts:
      - 0
      - indoor
      - 101%
      - 126%
    tier_prices:
      - 2.87
      - 4.29
      - 6.44
      - 10.07
    commodity_charge: Budget
    bill: commodity_charge + service_charge
  IRRIGATION:
    service_charge:
      depends_on: meter_size
      values:
        5/8": 11
        1": 22
        3": 33
    budget: outdoor
    commodity_charge: Budget
    tier_prices:
      depends_on: water_type
      values:
        POTABLE:
          - 4.07
          - 10.03
        RECYCLED:
          - 3.66
          - 6.33
    tier_starts:
      - 0
      - outdoor
    bill: commodity_charge + service_charge + sewer_charge
    outdoor: et_factor * irrigable_area * et_amount * 0.62 * (1/748)
    et_factor: 0.7
    sewer_charge: 1.5*usage_ccf
  COMMERCIAL:
    service_charge:
      depends_on: meter_size
      values:
        5/8": 11
        1": 22
        3": 33
    tier_starts:
      depends_on: meter_size
      values:
        5/8":
          - 0
          - 211
        1":
          - 0
          - 211
        3":
          - 0
          - 611
    tier_prices:
      depends_on: water_type
      values:
        POTABLE:
          - 4.07
          - 10.03
        RECYCLED:
          - 3.66
          - 6.33
    commodity_charge: Tiered
    sewer_budget: 10
    sewer_tier_starts:
      - 0
      - 101%
    sewer_tier_prices:
      - 2
      - 0
    sewer_charge: Budget
    bill: commodity_charge + service_charge + sewer_charge
  ERROR_CLASS1:
    tier_prices:
      depends_on: water_type
      values:
        POTABLE:
        - 4.07
        - 10.03
        RECYCLED:
        - 3.66
        - 6.33
    commodity_charge: Tiered
    bill: commodity_charge
  ERROR_CLASS2:
      tier_prices:
        depends_on: water_type
        values:
          POTABLE:
          - 4.07
          - 10.03
          RECYCLED:
          - 3.66
          - 6.33
      commodity_charge: Tiered
      tier_starts:
        depends_on: meter_size
        values:
          5/8":
            - 0
            - 211
          1":
            - 0
            - 211
          3":
            - 0
            - 611
      bill: commodity_charge
  ERROR_CLASS3:
    tier_starts:
      depends_on: meter_size
      values:
        5/8":
        - 0
        - 211
        1":
        - 0
        - 211
        3":
        - 0
        - 611
    tier_prices:
      depends_on: water_type
      values:
        POTABLE:
        - 4.07
        - 10.03
        RECYCLED:
        - 3.66
        - 6.33
    commodity_charge: Tiered
    bill: commodity_charge + service_charge
  ERROR_CLASS4:
    tier_starts:
      - 0
      - 101%
    tier_prices:
      - 4.07
      - 10.03
    commodity_charge: Tiered
    bill: commodity_charge
'
test_rates <- yaml.load(yaml_rates)


calc <- function(df){
  calculate_class_bill(df, test_rates)
}

manual_bill_1 <- 33 + (4.07*388) + (2*10 + 0*378)
manual_bill_2 <- 11 + (2.87*14 + 4.29*6 + 6.44*5 + 10.07*2.3) + (2*10 + 0*17.3)

manual_budget_3 <- round(0.7*4.8*4500*0.62*(1/748))
manual_bill_3 <- 22 + 3.66*manual_budget_3 + 6.33*(41 - manual_budget_3 ) + 1.5*41

manual_indoor_8 <- round(60*3*30.4*(1/748))
manual_outdoor_8 <- round(0.7 * 1300 * 4.8 * 0.62 * (1/748))
manual_budget_8 <- round(manual_indoor_8 + manual_outdoor_8)
manual_bill_8 <- 11 + 2.87*manual_indoor_8 +
                  4.29*manual_outdoor_8 +
                  6.44*(round(1.26*manual_budget_8) - manual_budget_8) +
                  10.07*(27.3-round(1.26*manual_budget_8))

manual_bills <- c(manual_bill_1, manual_bill_2, manual_bill_3)


test_that("Individual bills calculated accurately", {
 expect_equal(calc(as.data.frame(rows[[1]]))$bill, manual_bill_1)
 expect_equal(calc(as.data.frame(rows[[2]]))$bill, manual_bill_2)
 expect_equal(calc(as.data.frame(rows[[3]]))$bill, manual_bill_3)
 expect_equal(calc(as.data.frame(rows[[8]]))$bill, manual_bill_8)
})

test_that("Bills accurate when summed accross customer classes", {
  expect_equal( sum(calculate_bill(df_test, test_rates)$bill), sum(manual_bills) )
})

test_that("Error thrown when a class is not defined in rate file", {
  expect_error(calc(as.data.frame(rows[[4]])), "No rate information for customer class")
})

test_that("Error thrown when tier starts or prices are not present in a tiered rate structure", {
  expect_error(calc(as.data.frame(rows[[5]])), "is not present in the OWRS file for customer class")
})

test_that("Error thrown when a field is missing", {
  expect_error(calc(as.data.frame(rows[[7]])), "is not present in the OWRS file for customer class")
})

test_that("Error thrown when % tiers used in 'Tiered' rate", {
  expect_error(calc(as.data.frame(rows[[9]])), "Tiers are formatted for budget-based rates")
})
California-Data-Collaborative/RateParser documentation built on May 6, 2019, 9:27 a.m.