proptax 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,292 @@
1
+ ---
2
+ title: 2018 Property Assessment Analysis
3
+ author: Prepared by reports@taxreformyyc.com
4
+ geometry: margin=1.5cm
5
+ ---
6
+
7
+ ```{r loadLibraries, echo=FALSE, message=FALSE}
8
+ # Load the required libraries
9
+ library(knitr)
10
+ library(scales)
11
+ library(formattable)
12
+ library(ggplot2)
13
+ ```
14
+
15
+ ```{r defineConstants, echo=FALSE}
16
+ # Constants
17
+ address <- "<%= address %>"
18
+ myAssessedValue <- <%= assessed_value %>
19
+ csvFile <- "<%= csv_file %>"
20
+
21
+ # Get the house number and street name
22
+ m <- regexpr("^\\d+", address, perl=TRUE)
23
+ myHouseNumber <- regmatches(address, m)
24
+ #myHouseNumber <- gsub("[^\\d]+", "", address, perl=TRUE)
25
+ myStreetName <- gsub(".*[\\d]", "", address, perl=TRUE)
26
+
27
+ # How do this factors impact an assessment?
28
+ unknownImpact <- c('Taxation.Status', 'Assessment.Class',
29
+ 'Property.Type', 'Property.Use', 'Valuation.Approach',
30
+ 'Market.Adjustment', 'Community', 'Market.Area', 'Sub.Neighbourhood.Code..SNC.',
31
+ 'Sub.Market.Area', 'Influences', 'Land.Use.Designation', 'Building.Count',
32
+ 'Building.Type.Structure', 'Year.of.Construction', 'Quality', 'Basement.Suite',
33
+ 'Walkout.Basement', 'Garage.Type', 'Fireplace.Count',
34
+ 'Constructed.On.Original.Foundation', 'Modified.For.Disabled',
35
+ 'Old.House.On.New.Foundation', 'Basementless', 'Penthouse')
36
+
37
+ # These factors are informational and don't affect the assessment
38
+ noImpact <- c('Roll.Number', 'Location.Address')
39
+ ```
40
+
41
+ ```{r loadData, echo=FALSE}
42
+ # Load data
43
+ data <- read.csv(csvFile, header=TRUE)
44
+ ```
45
+
46
+ ```{r getMetaData, echo=FALSE}
47
+ streetAddresses <- data[,noImpact[2]]
48
+
49
+ # Remove the street names from the addresses
50
+ #houseNumbers <- as.numeric(gsub("[^\\d]+", "", streetAddresses, perl=TRUE))
51
+ m <- regexpr("^\\d+", streetAddresses, perl=TRUE)
52
+ houseNumbers <- as.numeric(regmatches(streetAddresses, m))
53
+ ```
54
+
55
+ \center
56
+ `r paste(min(houseNumbers), "-", max(houseNumbers), myStreetName, sep=" ")`
57
+
58
+ \flushleft
59
+
60
+ # Synopsis
61
+
62
+ This analysis pertains to the property located at **`r address`**.
63
+ It documents the treatment of the assessment data provided by the City of
64
+ Calgary for the pertinent property and those adjacent on the street
65
+ (i.e., buildings `r paste(min(houseNumbers), "-", max(houseNumbers), sep=" ")`)
66
+ The data under investigation was obtained from
67
+ [assessmentsearch.calgary.ca](https://assessmentsearch.calgary.ca) and
68
+ is presented alongside this document in a consolidated CSV file.
69
+
70
+ # Approach Overview
71
+
72
+ The data investigated in this analysis consists of properties constrained to
73
+ one street on one city block. No properties were omitted to ensure the integrity
74
+ of this report and its conclusion.
75
+
76
+ Given that there are many factors contained in the data whose precise impact on
77
+ assessed values are unknown, commonalities are identified and omitted from
78
+ consideration, as all such factors should have identical impact on the
79
+ final assessment.
80
+
81
+ Factors that vary are identified and presented for consideration, as
82
+ transparency and integrity is of the utmost importance. Again, the precise
83
+ impact of these factors on the final assessment is unknown, as the weights
84
+ assigned by the City of Calgary are not divulged in the assessment data they
85
+ provide.
86
+
87
+ Having acknowledged the factors that cannot easily be quantified, the focus
88
+ turns to the properties' lot sizes, total developed area, and assessed values.
89
+ By visualizing the relationship between these quantifiable factors, the
90
+ pertinent property's assessed value is contrasted with those of neighbouring
91
+ properties. The conclusion of this analysis is drawn from the underlying data.
92
+
93
+ # Identify common factors
94
+
95
+ Many of the factors that impact the assessments are identical. This data can
96
+ safely be removed from consideration, as the impact on the assessed values
97
+ should be the same for all the properties under investigation.
98
+
99
+ ```{r commonFactors, echo=FALSE}
100
+ # Identify common factors
101
+ headers <- c()
102
+ identical <- c()
103
+ displayHeaders <- c()
104
+ for (col in colnames(data)) {
105
+ values = data[,col][!duplicated(data[,col])]
106
+ if (length(values) == 1) {
107
+ headers <- append(headers, col)
108
+ displayHeaders <-append(displayHeaders, gsub("\\.", " ", col, perl=TRUE))
109
+ identical <- append(identical, toString(values))
110
+ }
111
+ }
112
+ commonFactors <- data.frame(headers, displayHeaders, identical)
113
+ ```
114
+
115
+ Here, **`r length(commonFactors$headers)`** common factors can safely be
116
+ removed from the data set:
117
+
118
+ `r kable(data.frame(commonFactors$displayHeaders, commonFactors$identical),
119
+ col.names=c('Factors', 'Identical Values'), align=c('l', 'r'))`
120
+
121
+ ```{r removeCommonFactors, echo=FALSE}
122
+ # Remove common factors
123
+ data <- data[,!(names(data) %in% commonFactors$headers)]
124
+ ```
125
+
126
+
127
+ # Identify unknown and non-impacting factors
128
+
129
+ Of the **`r length(colnames(data))`** remaining columns, some cannot be
130
+ quantified. Others certainly impact the assessed value of a property, but the
131
+ assessment data provided by the City of Calgary does not reveal to what extent.
132
+
133
+ ## Non-impacting factors
134
+
135
+ ```{r noImpactDisplayHeaders, echo=FALSE}
136
+ # Remove the dots from the header name
137
+ noImpactDisplayHeaders <- gsub("\\.", " ", noImpact, perl=TRUE)
138
+ ```
139
+
140
+ These factors cannot be quantified and are administrative in purpose:
141
+
142
+ `r kable(noImpactDisplayHeaders, col.names=c('Non-Impacting Factors'))`
143
+
144
+ ```{r removeIrrelevantFactors, echo=FALSE}
145
+ data <- data[,!(names(data) %in% noImpact)]
146
+ ```
147
+
148
+ These are removed and the remaining **`r length(colnames(data))`** columns are
149
+ carried forward.
150
+
151
+ ## Unknown factors
152
+
153
+ The impact these remaining columns have on assessment values is unknown:
154
+
155
+ ```{r identifyUnknowns, echo=FALSE}
156
+ # Identify unknown factors
157
+ unknownFactors <- names(data[,names(data) %in% unknownImpact])
158
+ ```
159
+
160
+ `r kable(gsub("\\.", " ", unknownFactors), col.names=c('Unknown Factors'))`
161
+
162
+ The variability within these unknown columns is presented here in the interest
163
+ of transparency:
164
+
165
+ ```{r consolidateUnknownFactors, echo=FALSE}
166
+ consolidatedUnknownFactors <- data[,(names(data) %in% c(unknownFactors))]
167
+ rownames(consolidatedUnknownFactors) <- houseNumbers
168
+ ```
169
+
170
+ `r kable(consolidatedUnknownFactors[order(as.numeric(row.names(consolidatedUnknownFactors))),],
171
+ align=c(rep('c', length(unknownFactors))),
172
+ row.names=TRUE,
173
+ col.names=gsub("\\.", " ", unknownFactors))`
174
+
175
+ These undoubtedly have an impact on the valuation, but their precise weighting
176
+ and significance are not presented in the assessment data provided by the City
177
+ of Calgary. As such, they are removed from the dataset.
178
+
179
+ ```{r removeUnknownFactors, echo=FALSE}
180
+ # Remove unknown factors from data set
181
+ data <- data[,!(names(data) %in% unknownFactors)]
182
+ ```
183
+
184
+ The remaining **`r length(colnames(data))`** columns contain the following data:
185
+
186
+ ```{r addRowNamesToData, echo=FALSE}
187
+ # Add row names to data
188
+ rownames(data) <- houseNumbers
189
+ ```
190
+
191
+ `r kable(data[order(as.numeric(row.names(data))),], row.names=TRUE, col.names=gsub("\\.", " ", colnames(data)))`
192
+
193
+ # Visualization
194
+
195
+ The raw data presented above is summarized in Figure 1. It
196
+ illustrates the disparity between the assessed property values. The pertinent
197
+ property is coloured red.
198
+
199
+ The blue line running through the graph is _best fit_ for the visualized model.
200
+ It serves as a predictor, or indicator, as to where the properties in question
201
+ should be positioned.
202
+
203
+ The pertinent property's overassessment is determined by measuring the distance
204
+ between the red point and the blue line.
205
+
206
+
207
+ ```{r adjustValues, echo=FALSE}
208
+ # Sum each property's lot size and total developed space
209
+ areaTotals <- rowSums(data[,-1])
210
+
211
+ # Isolate all assessed values
212
+ assessedValues <- data[,1]
213
+
214
+ # Plot the best fit regression line
215
+ reg <- lm(assessedValues~areaTotals)
216
+
217
+ # Plot distances between points and the regression line
218
+ assessedDifferences <- residuals(reg)
219
+ adjustedValues <- predict(reg)
220
+
221
+ # Reconcile adjusted property values
222
+ adjustedProperties <- data.frame(houseNumbers, adjustedValues, assessedDifferences)
223
+ ```
224
+
225
+ ```{r generatePlot, fig.cap=paste(myHouseNumber, "Overassessment", " "), fig.width=12, echo=FALSE}
226
+ plot.title <- paste(min(houseNumbers), "-", max(houseNumbers), myStreetName, sep=" ")
227
+ plot.subtitle = 'Current Assessed Property Values'
228
+ ggplot(data, aes(x=areaTotals, y=assessedValues)) +
229
+ # Plot title
230
+ ggtitle(bquote(atop(bold(.(plot.title)), atop(italic(.(plot.subtitle)), "")))) +
231
+ theme(plot.title=element_text(size=24, hjust = 0.5)) +
232
+
233
+ # Axis labels
234
+ ylab("Assessed House Values on your Street") +
235
+ xlab("Total House and Lot Size (Sq. Feet)") +
236
+ theme(axis.title.x=element_text(size=18, face="bold", margin=margin(20,0,0,0)),
237
+ axis.title.y=element_text(size=18, face="bold", margin=margin(0,20,0,0))) +
238
+
239
+ # Axis tick labels
240
+ scale_x_continuous(labels=comma) +
241
+ <% if opts.ylimit? %>
242
+ scale_y_continuous(labels=dollar, breaks=pretty_breaks(n=10),
243
+ limits=c(min(assessedValues)-<%= opts.ylimit %>, max(assessedValues)+<%= opts.ylimit %>)) +
244
+ <% else %>
245
+ scale_y_continuous(labels=dollar, breaks=pretty_breaks(n=10)) +
246
+ <% end %>
247
+ # Scatter plot points
248
+ geom_point(shape=ifelse(assessedValues==myAssessedValue, 16, 1),
249
+ size=ifelse(assessedValues==myAssessedValue, 5, 4),
250
+ colour=ifelse(assessedValues==myAssessedValue, "red", "blue")) +
251
+
252
+ # Point labels
253
+ geom_text(aes(label=houseNumbers), hjust=0.5, vjust=-2, size=5) +
254
+ geom_text(aes(label=paste("$", accounting(assessedValues, format="d"), sep="")),
255
+ hjust=0.5, vjust=-1.2) +
256
+
257
+ # Best fit line
258
+ geom_smooth(method=lm) #+
259
+ ```
260
+
261
+ # Conclusion
262
+
263
+ The data investigated in this analysis describes the factors considered in
264
+ assessing the properties located at
265
+ `r paste(min(houseNumbers), "-", max(houseNumbers), myStreetName, sep=" ")`.
266
+ It was collected and provided by the City of Calgary. This report set out to
267
+ quantify the disparity between the pertinent property (i.e., `r address`) and
268
+ the neighbouring properties on its street.
269
+
270
+ Common factors were identified and eliminated from the analysis. Similarly,
271
+ varying factors were identified, catalogued, and removed from consideration. The
272
+ City of Calgary's property reports do not describe how these characteristic
273
+ features are quantified and weighted in determining a property's assessed
274
+ value. As such, they could not be included.
275
+
276
+ The conclusions that follow are drawn from comparing lot sizes, total developed
277
+ area, and assessed values. The underlying data and the overall approach have
278
+ been presented in full.
279
+
280
+ ## Overvaluation: `r currency(adjustedProperties[houseNumbers==myHouseNumber,]$assessedDifferences)`
281
+
282
+ This investigation compared the assessed values with lot sizes and developed
283
+ square footage. It has revealed that that the pertinent property is overvalued
284
+ by
285
+ **`r currency(adjustedProperties[houseNumbers==myHouseNumber,]$assessedDifferences)`**.
286
+
287
+ ## Corrected Assessed Value: `r currency(adjustedProperties[houseNumbers==myHouseNumber,]$adjustedValues)`
288
+
289
+ In order to bring the pertinent property's assessed value in line with those of
290
+ its immediate neighbours, it must be reassessed at
291
+ **`r currency(adjustedProperties[houseNumbers==myHouseNumber,]$adjustedValues)`**.
292
+
@@ -0,0 +1,31 @@
1
+ require 'thor/group'
2
+ require 'csv'
3
+ module Proptax
4
+ module Generators
5
+ class Report < Thor::Group
6
+ include Thor::Actions
7
+ attr_accessor :address, :assessed_value, :y_axis_limits
8
+
9
+ argument :csv_file, :type => :string
10
+ argument :opts
11
+
12
+ def create_report_dir
13
+ empty_directory('reports')
14
+ FileUtils.cp(csv_file, "reports")
15
+ end
16
+
17
+ def copy_report_template
18
+ CSV.foreach(csv_file, headers: true) do |row|
19
+ self.address = row['Location Address']
20
+ self.assessed_value = row['Current Assessed Value']
21
+ file_name = address.gsub(/\s/, '_')
22
+ template("#{opts.template}.Rmd", "reports/#{file_name}.Rmd")
23
+ end
24
+ end
25
+
26
+ def self.source_root
27
+ File.dirname(__FILE__) + "/report"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Proptax
2
+ VERSION = "0.0.1"
3
+ end
data/lib/proptax.rb ADDED
@@ -0,0 +1,153 @@
1
+ require "proptax/version"
2
+
3
+ module Proptax
4
+ class Consolidator
5
+
6
+ require 'csv'
7
+ require 'pathname'
8
+
9
+ #
10
+ # Headers for publicly available assessment data for 2018.
11
+ #
12
+ Headers = ['Current Assessed Value', 'Roll Number', 'Location Address', 'Taxation Status',
13
+ 'Assessment Class', 'Property Type', 'Property Use', 'Valuation Approach',
14
+ 'Market Adjustment', 'Community', 'Market Area', 'Sub Neighbourhood Code (SNC)',
15
+ 'Sub Market Area', 'Influences', 'Land Use Designation', 'Assessable Land Area',
16
+ 'Building Count', 'Building Type/Structure', 'Year of Construction',
17
+ 'Quality', 'Total Living Area Above Grade', 'Living Area Below Grade', 'Basement Suite',
18
+ 'Walkout Basement', 'Garage Type', 'Garage Area', 'Fireplace Count',
19
+ 'Constructed On Original Foundation', 'Modified For Disabled', 'Old House On New Foundation',
20
+ 'Basementless', 'Penthouse']
21
+
22
+ #
23
+ # Keys that can be assigned multiple values
24
+ #
25
+ MultiFieldHeaders = ['Influences']
26
+
27
+ #
28
+ # Section headers for publicly available assessment data for 2018.
29
+ #
30
+ Sections = ['Assessment Details', 'Assessment Approach', 'Location Details', 'Land Details', 'Building Details']
31
+
32
+ #
33
+ # Convert a directory containing 2018 PDF assessment reports to text and
34
+ # write the relevant CSV information to stdout
35
+ #
36
+ # @param string - path to directory containing PDFs
37
+ #
38
+ # @return nil
39
+ #
40
+ def self.process(dir_name)
41
+ # `strip!': can't modify frozen String (RuntimeError)
42
+ #dir_name.strip!
43
+ dir_name = dir_name.strip
44
+ if dir_name.empty?
45
+ puts "No directory specified"
46
+ return
47
+ end
48
+
49
+ begin
50
+ path = Pathname.new(dir_name).realpath
51
+ puts Headers.to_csv if Dir.exists?(path)
52
+ Dir.glob("#{path}/*.{pdf,PDF}") do |pdf|
53
+ data = `pdftotext "#{pdf}" -`
54
+ puts parse(data).to_csv
55
+ end
56
+ rescue
57
+ puts "Directory #{dir_name} does not exist"
58
+ end
59
+ end
60
+
61
+ #
62
+ # Take text-converted 2018 assessment reports and extract the relevant data
63
+ # into an array.
64
+ #
65
+ # @param string - text converted 2018 assessment report
66
+ #
67
+ # @return array
68
+ #
69
+ def self.parse(text)
70
+ csv_hash = {}
71
+ current_header = nil
72
+ text.split(/\n/).each do |line|
73
+ # Replace non-breaking spaces with space and strip
74
+ line = line.gsub(/\u00a0/, ' ').strip
75
+
76
+ # Remove soft hyphens
77
+ line.gsub!(/\u00AD/, '')
78
+ ## This is untested
79
+ line.gsub!(/â\\200\\224/, '-')
80
+
81
+ # Remove trailing colon
82
+ line.gsub!(/:+$/, "");
83
+
84
+ next if line.empty?
85
+
86
+ if current_header
87
+ if Sections.include? line
88
+ current_header = nil
89
+ next
90
+ end
91
+
92
+ if MultiFieldHeaders.include? current_header
93
+ if csv_hash[current_header].nil?
94
+ csv_hash[current_header] = clean(line, current_header)
95
+ else
96
+ csv_hash[current_header] += "/#{clean(line, current_header)}"
97
+ end
98
+ else
99
+ csv_hash[current_header] = clean(line, current_header)
100
+ current_header = nil
101
+ end
102
+ elsif Headers.include? line
103
+ current_header = line
104
+ #
105
+ # Need a rasterized report (a la, Windows 7)
106
+ #
107
+ # For tesseract-extracted text
108
+ # else
109
+ # Headers.each do |header|
110
+ # current_header = line[Regexp.new("^#{Regexp.escape(header)}")]
111
+ # break if current_header
112
+ # end
113
+ # if current_header
114
+ # line = line.gsub(Regexp.new("^#{Regexp.escape(current_header)}"), '').strip
115
+ # csv_hash[current_header] = clean(line, current_header)
116
+ # current_header = nil
117
+ # end
118
+ end
119
+ end
120
+ Headers.map { |header| csv_hash[header] || '0' }
121
+ end
122
+
123
+ #
124
+ # Return square footage only when given spacial data
125
+ #
126
+ # @param string - the text data to be cleaned
127
+ # @param string - the header associated with the text data
128
+ #
129
+ # @return string
130
+ #
131
+ def self.clean(text, header)
132
+ case header
133
+ when 'Current Assessed Value'
134
+ text.gsub!(/,/, '')
135
+ when 'Assessable Land Area',
136
+ 'Total Living Area Above Grade',
137
+ 'Living Area Below Grade',
138
+ 'Garage Area'
139
+ text = text.split(' ')[0].gsub(/,/, '')
140
+ when 'Basement Suite',
141
+ 'Walkout Basement',
142
+ 'Constructed On Original Foundation',
143
+ 'Modified For Disabled',
144
+ 'Old House On New Foundation',
145
+ 'Basementless',
146
+ 'Penthouse',
147
+ 'Market Adjustment'
148
+ text = text == 'Yes' ? 'T' : 'F'
149
+ end
150
+ text
151
+ end
152
+ end
153
+ end
data/proptax.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "proptax/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "proptax"
8
+ spec.version = Proptax::VERSION
9
+ spec.authors = ["Daniel Bidulock"]
10
+ spec.email = ["daniel@capitolhill.ca"]
11
+
12
+ spec.summary = %q{Automatically process and visualize residential property data collected by the City of Calgary in 2018.}
13
+ spec.description = %q{This software produces reports from property data collected and provided by the City of Calgary. The versions used in previous years have won three victories before Calgary's Assessment Review Board, the quasi-judicial body that rules on property tax appeals.}
14
+ spec.homepage = "https://github.com/TaxReformYYC/report-generator-2018"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ # if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+ # else
21
+ # raise "RubyGems 2.0 or newer is required to protect against " \
22
+ # "public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.15"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rspec", "~> 3.0"
35
+ spec.add_development_dependency "aruba", "~> 0.14.3"
36
+
37
+ spec.add_dependency "thor"
38
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proptax
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Bidulock
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-01-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aruba
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.14.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: This software produces reports from property data collected and provided
84
+ by the City of Calgary. The versions used in previous years have won three victories
85
+ before Calgary's Assessment Review Board, the quasi-judicial body that rules on
86
+ property tax appeals.
87
+ email:
88
+ - daniel@capitolhill.ca
89
+ executables:
90
+ - proptax
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - ".travis.yml"
97
+ - Gemfile
98
+ - LICENSE
99
+ - README.md
100
+ - Rakefile
101
+ - bin/console
102
+ - bin/setup
103
+ - exe/proptax
104
+ - lib/R/filter_csv.R
105
+ - lib/proptax.rb
106
+ - lib/proptax/cli.rb
107
+ - lib/proptax/generators/report.rb
108
+ - lib/proptax/generators/report/cherry-picked.Rmd
109
+ - lib/proptax/generators/report/default.Rmd
110
+ - lib/proptax/version.rb
111
+ - proptax.gemspec
112
+ homepage: https://github.com/TaxReformYYC/report-generator-2018
113
+ licenses: []
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.6.8
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Automatically process and visualize residential property data collected by
135
+ the City of Calgary in 2018.
136
+ test_files: []