appraisermetrics_report_service 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/README.md +78 -0
- data/Rakefile +2 -0
- data/appraisermetrics_report_service.gemspec +26 -0
- data/config/static_text.yml +44 -0
- data/lib/appraisermetrics_report_service.rb +8 -0
- data/lib/appraisermetrics_report_service/version.rb +3 -0
- data/lib/closed_sale.rb +706 -0
- data/lib/eval_report.rb +1192 -0
- data/lib/report_utils.rb +219 -0
- data/spec/lib/closed_sale_spec.rb +626 -0
- data/spec/lib/eval_report_spec.rb +799 -0
- data/spec/lib/report_utils_spec.rb +213 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_data/images/ag_sales.jpg +0 -0
- data/spec/test_data/images/profile1.png +0 -0
- data/spec/test_data/images/property_pic1.png +0 -0
- data/spec/test_data/images/property_pic2.png +0 -0
- data/spec/test_data/images/property_pic3.png +0 -0
- data/spec/test_data/images/property_pic4.png +0 -0
- data/spec/test_data/images/regional1.jpg +0 -0
- data/spec/test_data/images/regional2.jpg +0 -0
- data/spec/test_data/images/subject1.jpg +0 -0
- data/spec/test_data/images/subject2.jpg +0 -0
- data/spec/test_data/images/subject3.jpg +0 -0
- data/spec/test_data/images/subject4.jpg +0 -0
- data/spec/test_data/images/test_logo.png +0 -0
- data/spec/test_data/images/topo1.jpg +0 -0
- data/spec/test_data/images/topo2.jpg +0 -0
- data/spec/test_data/sampler.rb +1404 -0
- metadata +208 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
appraisermetrics-report-service
|
2
|
+
===============================
|
3
|
+
|
4
|
+
Report Service app for AM app.
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
## gem dependencies
|
9
|
+
* prawn - https://github.com/prawnpdf/prawn - license: choice of GPLv2 or GPLv3
|
10
|
+
* prawn/table - https://github.com/prawnpdf/prawn-table - license: choice of GPLv2 or GPLv3
|
11
|
+
* money - https://github.com/RubyMoney/money - license: https://github.com/RubyMoney/money/blob/master/LICENSE
|
12
|
+
* rspec for tests
|
13
|
+
* pdf-reader (for specs) - https://github.com/yob/pdf-reader - license: MIT
|
14
|
+
* the Date ruby module, should be included in any standard ruby app
|
15
|
+
|
16
|
+
|
17
|
+
## usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# will need a deploy key
|
21
|
+
gem 'appraisermetrics_report_service', git: 'https://github.com/StackPointCloud/appraisermetrics-report-service.git'
|
22
|
+
```
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'appraisermetrics_report_service'
|
26
|
+
```
|
27
|
+
|
28
|
+
## ClosedSale
|
29
|
+
```ruby
|
30
|
+
c = ClosedSale.new do
|
31
|
+
write_content(ruby_hash_object, images, office_logo) # => returns ClosedSaleObject which inherits from Prawn::Document
|
32
|
+
end
|
33
|
+
|
34
|
+
# ruby_hash_object is identical to Comp document, with references to the Mongo Shell removed (ISODate, ObjectID)
|
35
|
+
# images takes an array of image paths. [0] should be the profile image for the property
|
36
|
+
# office_logo takes a file path
|
37
|
+
```
|
38
|
+
|
39
|
+
## EvalReport
|
40
|
+
```ruby
|
41
|
+
# generate an eval report
|
42
|
+
r = EvalReport.new do
|
43
|
+
write_content(subject, comparables, images, logo) # images is a hash, subject is a hash, comparables is an array, logo is a filepath
|
44
|
+
end
|
45
|
+
|
46
|
+
# image hash construction: {subject_photos: [], regional_maps: [], topo_maps: [], ag_sales_map: String}
|
47
|
+
```
|
48
|
+
|
49
|
+
## Output
|
50
|
+
```ruby
|
51
|
+
# render it to file
|
52
|
+
r.render_file("some_name.pdf") => local directory pdf called "some_name" with extension .pdf
|
53
|
+
```
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# stringIO
|
57
|
+
StringIO.new(r.render) => <StringIO>
|
58
|
+
|
59
|
+
|
60
|
+
```
|
61
|
+
## Testing/Dev
|
62
|
+
* Rspec suite covers table methods invidually, as well as helper methods.
|
63
|
+
* The `pdf-reader` gem is used in specs to read content into tests
|
64
|
+
* The `Sampler` class is used to generate sample ruby objects to be used in specs.
|
65
|
+
|
66
|
+
To generate test pdf reports:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
irb
|
70
|
+
|
71
|
+
load './spec/test_data/sampler'
|
72
|
+
|
73
|
+
# closed sale
|
74
|
+
Sampler.test_closed_sale
|
75
|
+
|
76
|
+
# eval report
|
77
|
+
Sampler.test_eval_report
|
78
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'appraisermetrics_report_service/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "appraisermetrics_report_service"
|
8
|
+
spec.version = AppraisermetricsReportService::VERSION
|
9
|
+
spec.authors = ["StackPoint"]
|
10
|
+
spec.email = ["fletcher.wilkens1@gmail.com"] # feel free to change this
|
11
|
+
spec.summary = ["A service to generate pdf reports"]
|
12
|
+
spec.homepage = ""
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
20
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
21
|
+
spec.add_development_dependency "rspec"
|
22
|
+
spec.add_development_dependency "pdf-reader"
|
23
|
+
spec.add_dependency "prawn-table"
|
24
|
+
spec.add_dependency "prawn"
|
25
|
+
spec.add_dependency "money"
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
transmittal:
|
2
|
+
"In fulfillment of our agreement and under XI of FDIC Appraisal and Evaluation Guidelines setting forth circumstances under which evaluations of real property are allowed:
|
3
|
+
The Agencies’ appraisal regulations permit an institution to obtain an appropriate evaluation of real property collateral in lieu of an appraisal for transactions that qualify for certain exemptions. These exemptions include a transaction that:
|
4
|
+
\n \n
|
5
|
+
• Has a transaction value equal to or less than the appraisal threshold of $250,000.
|
6
|
+
\n \n
|
7
|
+
• Is a business loan with a transaction value equal to or less than the business loan threshold of $1 million, and is not dependent on the sale of, or rental income derived from, real estate as the primary source of repayment.
|
8
|
+
\n \n
|
9
|
+
• Involves an existing extension of credit at the lending institution, provided that:
|
10
|
+
\n \n
|
11
|
+
There has been no obvious and material change in market conditions or physical aspects of the property that threaten the adequacy of the institution’s real estate collateral protection after the transaction, even with the advancement of new monies; or
|
12
|
+
\n \n
|
13
|
+
There is no advancement of new monies other than funds necessary to cover reasonable closing costs.
|
14
|
+
\n \n
|
15
|
+
I hereby submit the attached report which contains the FDIC Evaluation Content Requirements identified in the scope of work section."
|
16
|
+
|
17
|
+
scope_of_work1:
|
18
|
+
"This Evaluation is presented in a concise report format, is intended to comply with the reporting requirements outlined under the Interagency Appraisal and Evaluation Guidelines issued on December 10, 2010, as previously set forth.
|
19
|
+
\n \nThe scope of this evaluation required collecting primary and secondary data relevant to the subject property, including its current and projected use.
|
20
|
+
\n \nThe evaluation provides an estimate of the property’s market value in its actual physical condition, use and zoning designation as of the effective date of the evaluation (the date that the analysis was completed) with any limiting conditions.
|
21
|
+
\n \nSale data have been selected based on similar significant variables thoroughly analyzed and confirmed by qualified professionals, including certified real estate appraisers, county assessors, buyers, sellers and Realtors, and are believed to be reliable qualifying market data and have been appropriately sourced.
|
22
|
+
\n \nA physical inspection of the subject property was made by the person signing this report based on the following research:"
|
23
|
+
|
24
|
+
scope_of_work2:
|
25
|
+
"From the research gathered, the person signing this report has completed and following analysis:"
|
26
|
+
|
27
|
+
scope_of_work3:
|
28
|
+
"The review and description of the area (neighborhood) including general and local market conditions have been provided.
|
29
|
+
\nThis evaluation employs only the Sales Comparison Approach. Any inclusion of income projections are for internal purposes and do not infer a net operating income from which a value can be derived."
|
30
|
+
|
31
|
+
valuation_process:
|
32
|
+
"The subject property has been valued “as-is” as of the effective date of the Evaluation. The analysis of value that follows is based on a relative comparison of the subject’s primary property type and secondary property type. This is accomplished by identifying the same property classifications on sales of similar-type properties located in the subject’s immediate vicinity that were being used in a similar manner to the subject when they sold. This analysis does not include an analysis of highest and best use because the evaluator is not an appraiser and because this evaluation in not an appraisal.
|
33
|
+
\n \nSimilar property types have been selected in order to represent a reasonably supported value of the subject property by indicators derived from recent sales of similar properties. This method employs generally accepted procedures and methods used to conclude an estimate of value, however should not be construed to meet appraisal standards, guidelines, and methodologies."
|
34
|
+
|
35
|
+
comp_method:
|
36
|
+
"An opinion of value has been developed by comparing the subject property to similar, recently sold properties in the surrounding or competing area. This approach relies on the idea that when a property is replaceable in the market its value tends to be set at the cost of acquiring an equally desirable substitute property, assuming that no costly delay is encountered in making the substitution.
|
37
|
+
\n \nBy analyzing sales with similar physical and economic properties that qualify as arm’s-length transactions between willing and knowledgeable buyers and sellers, we can identify value ranges and price trends."
|
38
|
+
|
39
|
+
val_method:
|
40
|
+
"This evaluation includes an estimate of value by direct comparison. The opinions and conclusion set forth herein are limited to the Client’s use and may not be understood without additional information contained in my work file or an expanded scope of work or a complete appraisal analysis which may produce a different result."
|
41
|
+
|
42
|
+
assumptions_conditions:
|
43
|
+
"THIS IS AN EVALUATION AND SHOULD NOT BE CONSIDERED TO BE AN APPRAISAL. This report complies with the Interagency Appraisal and Evaluation Guidelines Final Guidance that became effective on December 10, 2010, the date it was published in the Federal register, jointly by the Comptroller of the Currency, the Board of Governors of the Federal Reserve System, the Federal Deposit Insurance Corporation, the Department of the Treasury Office of Thrift Supervision and the National Credit Union Administration (“Federal Guidelines”). Accordingly this Report may be relied upon by Customer in accordance with the Federal Guidelines.
|
44
|
+
\n \nThis is an agricultural Evaluation Report. As such, it presents only brief discussions or statements of the data and analysis used to estimate the value conclusion. The scope of the assignment and report content is specific to the needs of the client and for the intended use as stated by the client. The Evaluator(s) listed below are not responsible for unauthorized use of this report."
|
data/lib/closed_sale.rb
ADDED
@@ -0,0 +1,706 @@
|
|
1
|
+
class ClosedSale < Prawn::Document
|
2
|
+
require 'prawn'
|
3
|
+
require 'prawn/table'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
include ReportUtils
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
super()
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_content(report_doc, images, office_logo) # the call to add content to pdf
|
13
|
+
font_size 6
|
14
|
+
@rep = report_doc
|
15
|
+
@property_images = images
|
16
|
+
@office_logo = office_logo
|
17
|
+
|
18
|
+
## page 1 ##
|
19
|
+
|
20
|
+
# render office logo image if present
|
21
|
+
render_logo
|
22
|
+
|
23
|
+
# rendering bounding boxes and tables
|
24
|
+
meta_data
|
25
|
+
property_class
|
26
|
+
profile_image
|
27
|
+
property_type_and_address
|
28
|
+
transaction_summary
|
29
|
+
land_classification
|
30
|
+
physical_overview
|
31
|
+
cash_flow
|
32
|
+
income_expense
|
33
|
+
|
34
|
+
start_new_page
|
35
|
+
|
36
|
+
## page 2 ##
|
37
|
+
comments
|
38
|
+
legal_transaction
|
39
|
+
# move to full width table
|
40
|
+
transaction_history
|
41
|
+
utilities
|
42
|
+
improvements
|
43
|
+
water_rights
|
44
|
+
|
45
|
+
start_new_page
|
46
|
+
## page 3 ##
|
47
|
+
water_distribution
|
48
|
+
crop_yield
|
49
|
+
estimated_productivity
|
50
|
+
perm_plantings
|
51
|
+
externalities
|
52
|
+
|
53
|
+
start_new_page
|
54
|
+
## page 4 ##
|
55
|
+
additional_images
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_logo
|
61
|
+
if @office_logo
|
62
|
+
image @office_logo, height: 25
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def meta_data
|
68
|
+
bounding_box([0, 690], width: 270, height: 55) do
|
69
|
+
meta_data = [
|
70
|
+
["Record Number:", @rep[:sequence]],
|
71
|
+
["Record Created By:", @rep[:record_created_by]],
|
72
|
+
["Date Created or Last Modified:", datemaker(@rep[:record_edited])]
|
73
|
+
]
|
74
|
+
|
75
|
+
table(meta_data) do # record meta data table with image
|
76
|
+
columns(0..1).width = 135
|
77
|
+
columns([0]).style(font_style: :bold)
|
78
|
+
cells.style(border_width: 0)
|
79
|
+
cells.padding = [1, 2.5]
|
80
|
+
end
|
81
|
+
stroke_bounds
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def profile_image
|
86
|
+
bounding_box([0, 635], width: 270, height: 275) do
|
87
|
+
if @property_images && @property_images.any?
|
88
|
+
image "#{@property_images.first}", width: 265
|
89
|
+
else
|
90
|
+
text "No Image"
|
91
|
+
end
|
92
|
+
stroke_bounds
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def land_classification
|
97
|
+
bounding_box([0, 360], width: 270, height: 180) do
|
98
|
+
classification_data = [
|
99
|
+
[{content: "Land Classification Breakdown", colspan: 4}],
|
100
|
+
["Land Class:", "Acres:", "$/Acre:", "Total:"],
|
101
|
+
]
|
102
|
+
|
103
|
+
land_classes = @rep[:landclassifications]
|
104
|
+
if land_classes # node could be nil
|
105
|
+
|
106
|
+
total_acres = 0 # value to add to in loop
|
107
|
+
total_dollar = 0
|
108
|
+
|
109
|
+
land_classes.each do |l|
|
110
|
+
if l # array could be empty
|
111
|
+
if l[:priceperacre] # priceperacre could be nil, avoid nomethod errors below by checking
|
112
|
+
m = Money.new(l[:priceperacre][:cents], l[:priceperacre][:currency_iso])
|
113
|
+
per_acre_string = m.format(no_cents: true)
|
114
|
+
total_string = (m * l[:numacres]).format(no_cents: true)
|
115
|
+
|
116
|
+
total_acres += no_nil_number(l[:numacres]) # add to total
|
117
|
+
total_dollar += m.to_f * l[:numacres]
|
118
|
+
|
119
|
+
classification_data.push(
|
120
|
+
[
|
121
|
+
l[:landclass],
|
122
|
+
'%.2f' % no_nil_number(l[:numacres]), # => adds .00 to acres
|
123
|
+
per_acre_string,
|
124
|
+
total_string
|
125
|
+
]
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end # end block
|
130
|
+
|
131
|
+
total_dollar_string = Money.new(total_dollar * 100, 'USD').format(no_cents: true)
|
132
|
+
total_per_acre = Money.new((total_dollar / total_acres) * 100, 'USD')
|
133
|
+
total_per_acre_string = total_per_acre.format(no_cents: true)
|
134
|
+
|
135
|
+
# push totals into table
|
136
|
+
classification_data.push(
|
137
|
+
["", "", "", ""], # line space
|
138
|
+
["Totals", '%.2f' % total_acres, total_per_acre_string, total_dollar_string]
|
139
|
+
)
|
140
|
+
|
141
|
+
if @rep[:imprv_unitprice]
|
142
|
+
improv_alloc = Money.new(@rep[:imprv_unitprice][:cents], 'USD')
|
143
|
+
else
|
144
|
+
improv_alloc = Money.new(0, 'USD')
|
145
|
+
end
|
146
|
+
|
147
|
+
classification_data.push([
|
148
|
+
"Total $ to Imprv", moneymaker(@rep[:total_imprv_budget], true),
|
149
|
+
"Imprv $/AC Alloc", improv_alloc.format(no_cents: :true)],
|
150
|
+
[{content: "Total Land and Improvement $/AC", colspan: 3}, (total_per_acre + improv_alloc).format(no_cents: :true)])
|
151
|
+
end
|
152
|
+
|
153
|
+
table(classification_data) do # land classification breakdown table
|
154
|
+
columns(0..1).width = 68
|
155
|
+
columns(2..3).width = 67
|
156
|
+
columns(1..3).style(align: :right)
|
157
|
+
cells.style(border_width: 0)
|
158
|
+
cells.padding = [1, 2.5]
|
159
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
160
|
+
row(1).style(align: :right, font_style: :bold, border_bottom_width: 0.5)
|
161
|
+
row(-3).style(border_bottom_width: 0.5, font_style: :bold)
|
162
|
+
end
|
163
|
+
stroke_bounds
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def cash_flow
|
168
|
+
bounding_box([0, 180], width: 270, height: 180) do
|
169
|
+
cash_flow_data = [
|
170
|
+
[{content: "Cash Flow - Production Analysis", colspan: 4}],
|
171
|
+
["Income Source", "Stable $/Unit", "Stablilized Yield/Unit", "No. of Source, i.e. AC"]
|
172
|
+
]
|
173
|
+
|
174
|
+
incomes = @rep[:incomes]
|
175
|
+
|
176
|
+
if incomes # node could be nil
|
177
|
+
incomes.each do |i|
|
178
|
+
if i # array could be nil
|
179
|
+
cash_flow_data.push(
|
180
|
+
[
|
181
|
+
i[:incomesrc],
|
182
|
+
moneymaker(i[:stabcashperunit], false),
|
183
|
+
'%.2f' % i[:stabilizedyield],
|
184
|
+
'%.2f' % i[:totalunits]
|
185
|
+
]
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
table(cash_flow_data) do
|
192
|
+
columns(0..1).width = 68
|
193
|
+
columns(2..3).width = 67
|
194
|
+
columns(1..3).style(align: :right, font_style: :bold)
|
195
|
+
column(0).style(align: :left, font_style: :bold)
|
196
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8)
|
197
|
+
cells.style(border_width: 0)
|
198
|
+
row(1).style(border_bottom_width: 0.5, align: :center, font_style: nil)
|
199
|
+
cells.padding = [1, 2.5]
|
200
|
+
end
|
201
|
+
stroke_bounds
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def comments
|
206
|
+
bounding_box([0, 690], width: 270, height: 125) do
|
207
|
+
comments_data = [
|
208
|
+
["Comments"],
|
209
|
+
[@rep[:general_comments]]
|
210
|
+
]
|
211
|
+
|
212
|
+
table(comments_data) do
|
213
|
+
row(0).style align: :center
|
214
|
+
column(0).width = 270
|
215
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
216
|
+
cells.padding = [1, 2.5]
|
217
|
+
cells.style(border_width: 0)
|
218
|
+
end
|
219
|
+
stroke_bounds
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def property_class
|
224
|
+
bounding_box([270, 690], width: 270, height: 55) do
|
225
|
+
property_data = [
|
226
|
+
["Property Classification:", @rep[:land_use_zone]],
|
227
|
+
["Transaction Type:", @rep[:type_of_transaction]],
|
228
|
+
["Property Name:", @rep[:property_name]]
|
229
|
+
]
|
230
|
+
|
231
|
+
table(property_data) do
|
232
|
+
column(0).style(align: :right)
|
233
|
+
column([1]).style(font_style: :bold)
|
234
|
+
columns(0..1).width = 135
|
235
|
+
cells.style(border_width: 0)
|
236
|
+
cells.padding = [1, 2.5]
|
237
|
+
end
|
238
|
+
stroke_bounds
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def property_type_and_address
|
243
|
+
bounding_box([270, 635], height: 145, width: 270) do
|
244
|
+
pos = @rep[:position] # lat and long
|
245
|
+
if pos
|
246
|
+
lat, long = pos[0], pos[1]
|
247
|
+
else
|
248
|
+
lat, long = " ", " "
|
249
|
+
end
|
250
|
+
|
251
|
+
property_inclusions = no_nil_array(@rep[:property_inclusions]).join(" ")
|
252
|
+
type_and_address_data = [
|
253
|
+
[{content: "Property Type and Address", colspan: 4}],
|
254
|
+
["Property Name:", @rep[:property_name], "Primary Land Use:", @rep[:primary_ag_use]],
|
255
|
+
["What does this property include?",
|
256
|
+
{content: property_inclusions, colspan: 3}],
|
257
|
+
["Address:", {content: "#{@rep[:property_address_number]}" + " #{@rep[:street_or_road_name]}", colspan: 3}],
|
258
|
+
["City:", @rep[:city], "County:", @rep[:ordinance_authority]],
|
259
|
+
["State:", no_nil_string(@rep[:county_state]).strip.chars.last(2).join(""), "Zip Code:", @rep[:zip_code]],
|
260
|
+
["Township/Range:","#{@rep[:townshipINT]}" + "#{@rep[:rangeINT]}", "Section(s):", @rep[:legal_description]],
|
261
|
+
["Latitude:", lat, "Longitude:", long],
|
262
|
+
[{content: "Property Location and Directions:" + "#{@rep[:directions_or_ownership_comments]}", colspan: 4}]
|
263
|
+
]
|
264
|
+
|
265
|
+
table(type_and_address_data) do
|
266
|
+
columns(0..1).width = 68
|
267
|
+
columns(2..3).width = 67
|
268
|
+
columns([0, 2]).style(align: :right)
|
269
|
+
columns([1, 3]).style(font_style: :bold)
|
270
|
+
cells.padding = [1, 2.5]
|
271
|
+
cells.style(border_width: 0)
|
272
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
273
|
+
end
|
274
|
+
stroke_bounds
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def transaction_summary
|
279
|
+
bounding_box([270, 490], height: 130, width: 270) do
|
280
|
+
price = moneymaker(@rep[:sale_price], true)
|
281
|
+
adjustment = moneymaker(@rep[:sale_adjustment], true)
|
282
|
+
ce_sale_price = moneymaker(@rep[:cesaleprice], true)
|
283
|
+
transaction_data = [
|
284
|
+
[{content: "Transaction Summary", colspan: 4}],
|
285
|
+
["Sale Price $:", price, "Sale Date:", datemaker(@rep[:sale_date])],
|
286
|
+
["Cond. Adjust $:", '(' + adjustment +')', "Adj. CE Sale $:", ce_sale_price],
|
287
|
+
["Unit Description:", @rep[:unit], "No. of Units:", '%.2f' % no_nil_number(@rep[:num_of_units])],
|
288
|
+
["Instrument No.:", @rep[:public_rec_ref_numer], "$/Unit:", moneymaker(@rep[:cesaleunitprice], true)],
|
289
|
+
["Seller:", @rep[:grantor], "Buyer:", @rep[:grantee]],
|
290
|
+
["Extent of Verification:", {content: no_nil_array(@rep[:verifications]).join(', '), colspan: 3}],
|
291
|
+
[{content: "#{@rep[:grantor]}" + " #{@rep[:information_source_contact]}",
|
292
|
+
colspan: 2}, "Date Inspected:", datemaker(@rep[:date_inspected])
|
293
|
+
]
|
294
|
+
]
|
295
|
+
|
296
|
+
table(transaction_data) do
|
297
|
+
columns(0..1).width = 68
|
298
|
+
columns(2..3).width = 67
|
299
|
+
columns([0, 2]).style align: :right
|
300
|
+
cells.padding = [1, 2.5]
|
301
|
+
cells.style(border_width: 0)
|
302
|
+
columns([1, 3]).style(font_style: :bold)
|
303
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
304
|
+
end
|
305
|
+
stroke_bounds
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def physical_overview
|
310
|
+
bounding_box([270, 360], height: 180, width: 270) do
|
311
|
+
overview_data = [
|
312
|
+
[{content: "Physical Overview", colspan: 4}],
|
313
|
+
["Farming Practice:", @rep[:farming_practices], {content: "Predominant Soil Type and Description:", colspan: 2}],
|
314
|
+
["Ann. Rainfall-Inches:", @rep[:avg_precipitation], {content: @rep[:soils], colspan: 2, rowspan: 3}],
|
315
|
+
["Avg Frost Free Days:", @rep[:growing_season]],
|
316
|
+
["Elevation(Range):", @rep[:elevation]],
|
317
|
+
["Predom Top Slope:", @rep[:topography], "Legal Access:", {content: @rep[:legal_access], rowspan: 2}],
|
318
|
+
["Corn Suitability Rating:", @rep[:CSR2]],
|
319
|
+
["Land Use Zone:", @rep[:land_use_zone], "Physical Access:", @rep[:physical_access]],
|
320
|
+
["Flood Zone:", no_nil_array(@rep[:flood_zone]).join(", "), "Wetlands:", @rep[:wetlands]],
|
321
|
+
[{content: "Land Development Potential, Change or Use, or Limitations Comments:", colspan: 4}],
|
322
|
+
[{content: "#{@rep[:development_potential]}" , colspan: 4}]
|
323
|
+
]
|
324
|
+
|
325
|
+
table(overview_data) do
|
326
|
+
columns(0..1).width = 68
|
327
|
+
columns(2..3).width = 67
|
328
|
+
columns([0, 2]).style(align: :right)
|
329
|
+
columns([1, 3]).style(font_style: :bold)
|
330
|
+
cells.padding = [1, 2.5]
|
331
|
+
cells.style(border_width: 0)
|
332
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
333
|
+
row(9).style(align: :left)
|
334
|
+
end
|
335
|
+
stroke_bounds
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def income_expense
|
340
|
+
bounding_box([270, 180], height: 180, width: 270) do
|
341
|
+
|
342
|
+
income = Money.new(no_nil_number(@rep[:eff_gross_income]) * 100, 'USD')
|
343
|
+
expense = Money.new(no_nil_number(@rep[:total_expenses]) * 100, 'USD')
|
344
|
+
noi = income - expense
|
345
|
+
noi_string = noi.format(no_cents: :true)
|
346
|
+
expense_ratio = "#{((expense / income) * 100).round(2)}" + "%"
|
347
|
+
|
348
|
+
income_expense_data = [
|
349
|
+
[{content: "Income/Expense Information", colspan: 4}],
|
350
|
+
["Total Farm Income:", income.format(no_cents: true), "Total Income to LL:", income.format(no_cents: true)],
|
351
|
+
["Total Farm Expenses:", expense.format(no_cents: true), "Total Exp pd by LL:", expense.format(no_cents: true)],
|
352
|
+
["NOI:", noi_string, "Expense Ratio:", expense_ratio],
|
353
|
+
["Cap Rate or OAR", "#{@rep[:overall_capitalization_rate]}%", " ", " "],
|
354
|
+
[{content: "Cash Flow Comments:", colspan: 4}],
|
355
|
+
[{content: @rep[:income_comments], colspan: 4}],
|
356
|
+
["Annual Water $/Acre:", moneymaker(@rep[:irrig_cost_per_acre], true)],
|
357
|
+
["Annual Pumping $/Acre:", moneymaker(@rep[:pumping_cost_per_acre], true)]
|
358
|
+
]
|
359
|
+
|
360
|
+
table(income_expense_data) do
|
361
|
+
columns(0..1).width = 68
|
362
|
+
columns(2..3).width = 67
|
363
|
+
columns(0..3).style(align: :right)
|
364
|
+
cells.padding = [1, 2.5]
|
365
|
+
cells.style(border_width: 0)
|
366
|
+
columns([1, 3]).style(font_style: :bold)
|
367
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
368
|
+
row([5, 6]).style(align: :left)
|
369
|
+
end
|
370
|
+
stroke_bounds
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def legal_transaction
|
375
|
+
bounding_box([270, 690], height: 125, width: 270) do
|
376
|
+
|
377
|
+
taxes = @rep[:taxes]
|
378
|
+
if taxes # node could be nil
|
379
|
+
if taxes[0] # array could be empty
|
380
|
+
tax_parcel_no = taxes[0][:tax_parcel_no]
|
381
|
+
assessed_value = moneymaker(taxes[0][:assessed_value], true)
|
382
|
+
real_estate_taxes = moneymaker(taxes[0][:RET], true)
|
383
|
+
end
|
384
|
+
else # deal with nil values to avoid no method errors
|
385
|
+
tax_parcel_no, assessed_value, real_estate_taxes = "", "", ""
|
386
|
+
end
|
387
|
+
|
388
|
+
legal_transaction_data = [
|
389
|
+
[{content: "Legal/Transaction Detail", colspan: 4}],
|
390
|
+
["Tax Parcel ID(s)", tax_parcel_no, " ", " "],
|
391
|
+
[" ", " ", " ", " "],
|
392
|
+
["Year of Assessment:", @rep[:year_of_assessment], "Year of RE Taxes:", @rep[:year_of_real_estate_taxes]],
|
393
|
+
["Assessed Value:", assessed_value, "RE Taxes", real_estate_taxes],
|
394
|
+
["Financing:", @rep[:financing_type], "Property Rights:", @rep[:property_rights]],
|
395
|
+
["At Market Trans?:", @rep[:atmarket_trans], "Days on Market:", @rep[:exposure_period]],
|
396
|
+
[{content: "Transaction History Comments:", colspan: 4}],
|
397
|
+
[{content: @rep[:listing_comments], colspan: 4}]
|
398
|
+
]
|
399
|
+
|
400
|
+
table(legal_transaction_data) do
|
401
|
+
columns(0..1).width = 68
|
402
|
+
columns(2..3).width = 67
|
403
|
+
columns([1, 3]).style(font_style: :bold)
|
404
|
+
cells.padding = [1, 2.5]
|
405
|
+
cells.style(border_width: 0)
|
406
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
407
|
+
end
|
408
|
+
stroke_bounds
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def transaction_history
|
413
|
+
bounding_box([0, 565], height: 125, width: 540) do
|
414
|
+
transaction_history_data = [
|
415
|
+
[{content: "Transaction History", colspan: 4}],
|
416
|
+
["Historic Transaction Type", "Transaction Description", "Date", "Sale Price, List Price, or List Price"]
|
417
|
+
]
|
418
|
+
|
419
|
+
transactions = @rep[:historyrecords]
|
420
|
+
|
421
|
+
if transactions # node could be nil
|
422
|
+
transactions.each do |t|
|
423
|
+
if t # array could be nil
|
424
|
+
transaction_history_data.push(
|
425
|
+
[t[:transType], t[:transDescr], datemaker(t[:transDate]), moneymaker(t[:price], true)]
|
426
|
+
)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
table(transaction_history_data) do
|
432
|
+
columns(0..3).width = 135
|
433
|
+
columns(0..3).style(align: :right, font_style: :bold)
|
434
|
+
cells.padding = [1, 2.5]
|
435
|
+
cells.style(border_width: 0)
|
436
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
437
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
438
|
+
end
|
439
|
+
stroke_bounds
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def utilities
|
444
|
+
bounding_box([0, 440], height: 125, width: 540) do
|
445
|
+
utilities_data = [
|
446
|
+
[{content: "Description of Utilities", colspan: 4}],
|
447
|
+
["Utility Description", "Service Availability", "Service Provider", "Comments"]
|
448
|
+
]
|
449
|
+
|
450
|
+
utilities = @rep[:utilities]
|
451
|
+
if utilities # node could be nil
|
452
|
+
utilities.each do |u|
|
453
|
+
if u # array could be empty
|
454
|
+
utilities_data.push(
|
455
|
+
[u[:description], u[:availbility], u[:provider], u[:comments]]
|
456
|
+
)
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
table(utilities_data) do
|
462
|
+
columns(0..3).width = 135
|
463
|
+
columns(0..3).style(align: :right, font_style: :bold)
|
464
|
+
cells.padding = [1, 2.5]
|
465
|
+
cells.style(border_width: 0)
|
466
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
467
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
468
|
+
end
|
469
|
+
stroke_bounds
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def improvements
|
474
|
+
bounding_box([0, 315], height: 220, width: 540) do
|
475
|
+
|
476
|
+
improvements_array = @rep[:improvements]
|
477
|
+
|
478
|
+
sorted_array = flip_improvements_array(improvements_array)
|
479
|
+
|
480
|
+
table(sorted_array) do
|
481
|
+
columns(0..7).width = 67.5
|
482
|
+
columns(1..7).style(font_style: :bold)
|
483
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
484
|
+
cells.padding = [1, 2.5]
|
485
|
+
cells.style(border_width: 0)
|
486
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
487
|
+
end
|
488
|
+
stroke_bounds
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def water_rights
|
493
|
+
bounding_box([0, 95], height: 95, width: 540) do
|
494
|
+
|
495
|
+
water_rights_data = [
|
496
|
+
[{content: "Water Rights", colspan: 8}],
|
497
|
+
["Water Right No.", "Water Right", "Water Source", "Priority Date", "Beneficial Use", "No. Acres Irrigated", "Annual Volume Ac-Ft", "Period of Use"]
|
498
|
+
]
|
499
|
+
|
500
|
+
rights = @rep[:waterrights]
|
501
|
+
if rights # node could be nil
|
502
|
+
rights.each do |r|
|
503
|
+
if r # array could be empty
|
504
|
+
water_rights_data.push(
|
505
|
+
[r[:waterrightNum], r[:waterRight], r[:waterSrc], datemaker(r[:priorityDate]), r[:purpose], r[:numIrrAcres], r[:annVolume]]
|
506
|
+
)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
table(water_rights_data) do
|
512
|
+
columns(0..7).width = 67.5
|
513
|
+
columns(0..7).style(font_style: :bold)
|
514
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
515
|
+
cells.padding = [1, 2.5]
|
516
|
+
cells.style(border_width: 0)
|
517
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
518
|
+
end
|
519
|
+
|
520
|
+
stroke_bounds
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def water_distribution
|
525
|
+
bounding_box([0, 690], height: 100, width: 540) do
|
526
|
+
|
527
|
+
distribution_data = [
|
528
|
+
[{content: "Water Distribution System", colspan: 8}],
|
529
|
+
["Water Distrib. Equip.", "Manuf.", "Make/Brand", "Type", "Description", "Yr. Manufactured", "Remaining Ec. Life", "No. Acres Irrigated"]
|
530
|
+
]
|
531
|
+
|
532
|
+
distributions = @rep[:waterdistributions]
|
533
|
+
|
534
|
+
if distributions # node could be nil
|
535
|
+
distributions.each do |d|
|
536
|
+
if d # array could be empty
|
537
|
+
distribution_data.push(
|
538
|
+
[d[:waterdistrEq], d[:manufacturer], d[:brand], d[:eqType], d[:descr], d[:yearManuf], d[:remainingEcLife], d[:irrAcres]]
|
539
|
+
)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
table(distribution_data) do
|
545
|
+
columns(0..7).width = 67.5
|
546
|
+
columns(0..7).style(font_style: :bold)
|
547
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
548
|
+
cells.padding = [1, 2.5]
|
549
|
+
cells.style(border_width: 0)
|
550
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
551
|
+
end
|
552
|
+
|
553
|
+
stroke_bounds
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def crop_yield
|
558
|
+
bounding_box([0, 590], height: 135, width: 540) do
|
559
|
+
|
560
|
+
crops = @rep[:crops]
|
561
|
+
|
562
|
+
crop_data = [
|
563
|
+
[{content: "Crop Yield Summary", colspan: 7}],
|
564
|
+
["Crop Year", "Commodity Identified", "Unit of Measure", "Average Yield", " ", " ", ", "]
|
565
|
+
]
|
566
|
+
|
567
|
+
if crops # node could be nil
|
568
|
+
crops.each do |c|
|
569
|
+
if c # array could be empty
|
570
|
+
crop_data.push(
|
571
|
+
[c[:year], c[:commodity], c[:unit], c[:avgyield]]
|
572
|
+
)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
table(crop_data) do
|
578
|
+
column(0).width = 135
|
579
|
+
columns(1..6).width = 67.5
|
580
|
+
columns(0..6).style(font_style: :bold)
|
581
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
582
|
+
cells.padding = [1, 2.5]
|
583
|
+
cells.style(border_width: 0)
|
584
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
585
|
+
end
|
586
|
+
stroke_bounds
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
def estimated_productivity
|
591
|
+
bounding_box([0, 455], height: 120, width: 540) do
|
592
|
+
estimatedproductivity = @rep[:estimatedproductivity]
|
593
|
+
|
594
|
+
production_data = [
|
595
|
+
[{content: "Estimated Productivity", colspan: 4}],
|
596
|
+
["Commodity Identification", "Unit Measure", "Estimated Yield", " "]
|
597
|
+
]
|
598
|
+
|
599
|
+
if estimatedproductivity && estimatedproductivity.any?
|
600
|
+
estimatedproductivity.each do |p|
|
601
|
+
production_data.push(
|
602
|
+
["#{p[:commodity]}", "#{p[:unit]}", "#{p[:estmtdyield]}"]
|
603
|
+
)
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
table(production_data) do
|
608
|
+
columns(0..3).width = 135
|
609
|
+
columns(0..3).style(font_style: :bold)
|
610
|
+
cells.padding = [1, 2.5]
|
611
|
+
cells.style(border_width: 0)
|
612
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
613
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
614
|
+
end
|
615
|
+
|
616
|
+
stroke_bounds
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def perm_plantings
|
621
|
+
bounding_box([0, 335], height: 135, width: 540) do
|
622
|
+
planting_data = [
|
623
|
+
[{content: "Permanent Plantings", colspan: 8}],
|
624
|
+
["Planting", "Variety", "Ac. Type", "No. of Acres", "Average Age", "Plants/Acre", "Unit Description", "Average Yield"]
|
625
|
+
]
|
626
|
+
|
627
|
+
plantings = @rep[:plantings]
|
628
|
+
|
629
|
+
if plantings # node could be nil
|
630
|
+
plantings.each do |p|
|
631
|
+
if p # array could be empty
|
632
|
+
planting_data.push(
|
633
|
+
[p[:planting], p[:variety], p[:acres], p[:numacres], p[:avgage], p[:plantsacre], p[:unitdescr1], p[:avgproduction]]
|
634
|
+
)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
table(planting_data) do
|
640
|
+
columns(0..7).width = 67.5
|
641
|
+
columns(0..7).style(font_style: :bold)
|
642
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
643
|
+
cells.padding = [1, 2.5]
|
644
|
+
cells.style(border_width: 0)
|
645
|
+
row(1).style(border_bottom_width: 0.5, font_style: nil)
|
646
|
+
end
|
647
|
+
|
648
|
+
stroke_bounds
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
def externalities
|
653
|
+
bounding_box([0, 200], height: 200, width: 540) do
|
654
|
+
|
655
|
+
extern_data = ReportUtils.conditional_externs_array(@rep) # instance method to populate dynamic array
|
656
|
+
table(extern_data) do
|
657
|
+
columns(0..3).width = 135
|
658
|
+
columns([1, 3]).style(font_style: :bold)
|
659
|
+
row(0).style(align: :center, background_color: 'd7d7d7', font_size: 8, font_style: :bold)
|
660
|
+
cells.padding = [1, 2.5]
|
661
|
+
cells.style(border_width: 0)
|
662
|
+
end
|
663
|
+
|
664
|
+
stroke_bounds
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def additional_images
|
669
|
+
if @property_images && @property_images.any?
|
670
|
+
if @property_images[1]
|
671
|
+
bounding_box([0, 690], height: 330, width: 250) do
|
672
|
+
image @property_images[1], width: 240
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
if @property_images[2]
|
677
|
+
bounding_box([270, 690], height: 330, width: 250) do
|
678
|
+
image @property_images[2], width: 240
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
if @property_images[3]
|
683
|
+
bounding_box([0, 340], height: 330, width: 250) do
|
684
|
+
image @property_images[3], width: 240
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
if @property_images[4]
|
689
|
+
bounding_box([270, 340], height: 330, width: 250) do
|
690
|
+
image @property_images[4], width: 240
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
# misc. notes
|
698
|
+
|
699
|
+
# the first page of the report is split into two columns using bounding boxes, each is 270 p wide
|
700
|
+
|
701
|
+
##### tables ######
|
702
|
+
# - 4 column layout [68, 67, 67, 68]
|
703
|
+
# - 3 column tables can be used if needed [90, 90, 90]
|
704
|
+
# - 2 column layout [135, 135]
|
705
|
+
# - for dynamic layouts within an existing layout, pass colspan and content to data array like so:
|
706
|
+
# - {content: "double span", colspan: 2} => spans two columns with content
|