marty 0.5.36 → 0.5.38

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ class Marty::GridIndexBoolean < Marty::Base
2
+ validates_presence_of :created_dt, :data_grid_id, :attr, :index
3
+ validates_inclusion_of :key, in: [true, false]
4
+ end
@@ -0,0 +1,3 @@
1
+ class Marty::GridIndexInt4range < Marty::Base
2
+ validates_presence_of :created_dt, :data_grid_id, :attr, :key, :index
3
+ end
@@ -0,0 +1,3 @@
1
+ class Marty::GridIndexInteger < Marty::Base
2
+ validates_presence_of :created_dt, :data_grid_id, :attr, :key, :index
3
+ end
@@ -0,0 +1,3 @@
1
+ class Marty::GridIndexNumrange < Marty::Base
2
+ validates_presence_of :created_dt, :data_grid_id, :attr, :key, :index
3
+ end
@@ -0,0 +1,3 @@
1
+ class Marty::GridIndexString < Marty::Base
2
+ validates_presence_of :created_dt, :data_grid_id, :attr, :index
3
+ end
@@ -0,0 +1,15 @@
1
+ class Marty::NameValidator < ActiveModel::Validator
2
+ def validate(entry)
3
+ raise "need field option" unless options[:field]
4
+ field = options[:field].to_sym
5
+ value = entry.send(field)
6
+
7
+ return if value.nil?
8
+
9
+ # disallow leading, trailing, >1 internal spaces, special chars (|)
10
+ if value =~ /\A\s|\s\z|\A.*\s\s.*\z|.*\|.*/
11
+ entry.errors[field] =
12
+ I18n.t("activerecord.errors.messages.extraneous_spaces")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ class CreateMartyDataGrids < McflyMigration
2
+ include Marty::Migrations
3
+
4
+ def change
5
+ table_name = "marty_data_grids"
6
+
7
+ create_table table_name do |t|
8
+ t.string :name, null: false
9
+ # 2-dimensional array which holds grid data
10
+ t.jsonb :data
11
+ t.jsonb :metadata
12
+ t.string :data_type, null: true
13
+ t.boolean :lenient, null: false, default: false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ class CreateMartyGridIndexNumranges < ActiveRecord::Migration
2
+ include Marty::Migrations
3
+
4
+ def change
5
+ table_name = "marty_grid_index_numranges"
6
+
7
+ # drop deprecated version
8
+ execute("DROP TABLE IF EXISTS #{table_name}")
9
+
10
+ create_table table_name do |t|
11
+ t.datetime :created_dt, null: false
12
+ t.references :data_grid, null: false
13
+ t.string :attr, null: false
14
+ t.numrange :key, null: false
15
+ t.integer :index, null: false
16
+ end
17
+
18
+ # FIXME: not sure if this index is appropriate for our queries.
19
+ # May need to break it up.
20
+ add_index table_name,
21
+ [:created_dt, :data_grid_id, :attr],
22
+ name: "index_#{table_name}"
23
+ add_index table_name, :key, using: "GIST"
24
+
25
+ add_fk table_name, :data_grids
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ class CreateMartyGridIndexInt4ranges < ActiveRecord::Migration
2
+ include Marty::Migrations
3
+
4
+ def change
5
+ table_name = "marty_grid_index_int4ranges"
6
+
7
+ # drop deprecated version
8
+ execute("DROP TABLE IF EXISTS #{table_name}")
9
+
10
+ create_table table_name do |t|
11
+ t.datetime :created_dt, null: false
12
+ t.references :data_grid, null: false
13
+ t.string :attr, null: false
14
+ t.int4range :key, null: false
15
+ t.integer :index, null: false
16
+ end
17
+
18
+ # FIXME: not sure if this index is appropriate for our queries.
19
+ # May need to break it up.
20
+ add_index table_name,
21
+ [:created_dt, :data_grid_id, :attr],
22
+ name: "index_#{table_name}"
23
+ add_index table_name, :key, using: "GIST"
24
+
25
+ add_fk table_name, :data_grids
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ class CreateMartyGridIndexIntegers < ActiveRecord::Migration
2
+ include Marty::Migrations
3
+
4
+ def change
5
+ table_name = "marty_grid_index_integers"
6
+
7
+ # drop deprecated version
8
+ execute("DROP TABLE IF EXISTS #{table_name}")
9
+
10
+ create_table table_name do |t|
11
+ t.datetime :created_dt, null: false
12
+ t.references :data_grid, null: false
13
+ t.string :attr, null: false
14
+ t.integer :key, array: true, null: false
15
+ t.integer :index, null: false
16
+ end
17
+
18
+ # FIXME: not sure if this index is appropriate for our queries.
19
+ # May need to break it up.
20
+ add_index table_name,
21
+ [:created_dt, :data_grid_id, :attr],
22
+ name: "index_#{table_name}"
23
+ add_index table_name, :key, using: "GIN"
24
+
25
+ add_fk table_name, :data_grids
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ class CreateMartyGridIndexStrings < ActiveRecord::Migration
2
+ include Marty::Migrations
3
+
4
+ def change
5
+ table_name = "marty_grid_index_strings"
6
+
7
+ # drop deprecated version
8
+ execute("DROP TABLE IF EXISTS #{table_name}")
9
+
10
+ create_table table_name do |t|
11
+ t.datetime :created_dt, null: false
12
+ t.references :data_grid, null: false
13
+ t.string :attr, null: false
14
+ t.text :key, array: true
15
+ t.integer :index, null: false
16
+ end
17
+
18
+ # FIXME: not sure if this index is appropriate for our queries.
19
+ # May need to break it up.
20
+ add_index table_name,
21
+ [:created_dt, :data_grid_id, :attr],
22
+ name: "index_#{table_name}"
23
+ add_index table_name, :key, using: "GIN"
24
+
25
+ add_fk table_name, :data_grids
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ class CreateMartyGridIndexBooleans < ActiveRecord::Migration
2
+ include Marty::Migrations
3
+
4
+ def change
5
+ table_name = "marty_grid_index_booleans"
6
+
7
+ # drop deprecated version
8
+ execute("DROP TABLE IF EXISTS #{table_name}")
9
+
10
+ create_table table_name do |t|
11
+ t.datetime :created_dt, null: false
12
+ t.references :data_grid, null: false
13
+ t.string :attr, null: false
14
+ t.boolean :key, null: false
15
+ t.integer :index, null: false
16
+ end
17
+
18
+ # FIXME: not sure if this index is appropriate for our queries.
19
+ # May need to break it up.
20
+ add_index table_name,
21
+ [:created_dt, :data_grid_id, :attr],
22
+ name: "index_#{table_name}"
23
+ add_index table_name, :key
24
+
25
+ add_fk table_name, :data_grids
26
+ end
27
+ end
@@ -73,12 +73,11 @@ module Marty::Migrations
73
73
 
74
74
  def self.write_view(target_dir, target_view, klass, jsons, excludes, extras)
75
75
  colnames = klass.columns_hash.keys
76
- user_id_cols = ["user_id", "o_user_id"]
77
- excludes += user_id_cols
76
+ excludes += ["user_id", "o_user_id"]
78
77
  joins = ["join marty_users u on main.user_id = u.id",
79
78
  "left join marty_users ou on main.o_user_id = ou.id"]
80
- columns = ["concat(u.firstname, ' ', u.lastname) AS user_name",
81
- "concat(ou.firstname, ' ', ou.lastname) AS obsoleted_user"]
79
+ columns = ["u.login AS user_name",
80
+ "ou.login AS obsoleted_user"]
82
81
  jointabs = {}
83
82
  colnames.each do |c|
84
83
  if jsons[c]
@@ -1,3 +1,3 @@
1
1
  module Marty
2
- VERSION = "0.5.36"
2
+ VERSION = "0.5.38"
3
3
  end
@@ -0,0 +1,19 @@
1
+ class Gemini::State < ActiveRecord::Base
2
+ extend Marty::Enum
3
+
4
+ self.table_name_prefix = "gemini_"
5
+
6
+ validates_presence_of :name, :full_name
7
+ validates_uniqueness_of :name, :full_name
8
+
9
+ delorean_fn :lookup, sig: 1 do
10
+ |name|
11
+ self.find_by_name(name)
12
+ end
13
+
14
+ def to_s
15
+ name
16
+ end
17
+
18
+ # FIXME: prevent deletion/update
19
+ end
@@ -69,7 +69,7 @@ module Dummy
69
69
 
70
70
  config.marty.system_account = 'marty'
71
71
  config.marty.local_password = 'marty'
72
- config.marty.class_list = []
72
+ require 'class_list'
73
73
  config.marty.roles = [
74
74
  :admin,
75
75
  :dev,
@@ -0,0 +1,8 @@
1
+ class CreateGeminiStates < ActiveRecord::Migration
2
+ def change
3
+ create_table :gemini_states do |t|
4
+ t.string :name, null: false
5
+ t.string :full_name, null: false
6
+ end
7
+ end
8
+ end
@@ -8,3 +8,70 @@ Gemini::MortgageType.create(name: "FHA")
8
8
  Gemini::MortgageType.create(name: "VA")
9
9
  Gemini::MortgageType.create(name: "USDA/Rural Housing")
10
10
  Gemini::StreamlineType.seed
11
+
12
+ ######################################################################
13
+
14
+ STATES ||=
15
+ [
16
+ [ "Alabama", "AL" ],
17
+ [ "Alaska", "AK" ],
18
+ [ "Arizona", "AZ" ],
19
+ [ "Arkansas", "AR" ],
20
+ [ "California", "CA" ],
21
+ [ "Colorado", "CO" ],
22
+ [ "Connecticut", "CT" ],
23
+ [ "Delaware", "DE" ],
24
+ [ "District Of Columbia", "DC" ],
25
+ [ "Florida", "FL" ],
26
+ [ "Georgia", "GA" ],
27
+ [ "Hawaii", "HI" ],
28
+ [ "Idaho", "ID" ],
29
+ [ "Illinois", "IL" ],
30
+ [ "Indiana", "IN" ],
31
+ [ "Iowa", "IA" ],
32
+ [ "Kansas", "KS" ],
33
+ [ "Kentucky", "KY" ],
34
+ [ "Louisiana", "LA" ],
35
+ [ "Maine", "ME" ],
36
+ [ "Maryland", "MD" ],
37
+ [ "Massachusetts", "MA" ],
38
+ [ "Michigan", "MI" ],
39
+ [ "Minnesota", "MN" ],
40
+ [ "Mississippi", "MS" ],
41
+ [ "Missouri", "MO" ],
42
+ [ "Montana", "MT" ],
43
+ [ "Nebraska", "NE" ],
44
+ [ "Nevada", "NV" ],
45
+ [ "New Hampshire", "NH" ],
46
+ [ "New Jersey", "NJ" ],
47
+ [ "New Mexico", "NM" ],
48
+ [ "New York", "NY" ],
49
+ [ "North Carolina", "NC" ],
50
+ [ "North Dakota", "ND" ],
51
+ [ "Ohio", "OH" ],
52
+ [ "Oklahoma", "OK" ],
53
+ [ "Oregon", "OR" ],
54
+ [ "Pennsylvania", "PA" ],
55
+ [ "Rhode Island", "RI" ],
56
+ [ "South Carolina", "SC" ],
57
+ [ "South Dakota", "SD" ],
58
+ [ "Tennessee", "TN" ],
59
+ [ "Texas", "TX" ],
60
+ [ "Utah", "UT" ],
61
+ [ "Vermont", "VT" ],
62
+ [ "Virginia", "VA" ],
63
+ [ "Washington", "WA" ],
64
+ [ "West Virginia", "WV" ],
65
+ [ "Wisconsin", "WI" ],
66
+ [ "Wyoming", "WY" ],
67
+
68
+ # US Territories (FIXME: incomplete)
69
+ [ "American Samoa", "AS" ],
70
+ [ "Guam", "GU" ],
71
+ [ "Puerto Rico", "PR" ],
72
+ [ "Virgin Islands", "VI" ],
73
+ ]
74
+
75
+ STATES.each { |s| Gemini::State.create(full_name: s[0], name: s[1]) }
76
+
77
+ ######################################################################
@@ -1,3 +1,3 @@
1
1
  module ClassList
2
- Rails.configuration.marty.class_list = ["Gemini::FannieBup"]
2
+ Rails.configuration.marty.class_list = ["Gemini::FannieBup", "Marty::DataGrid"]
3
3
  end
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require "class_list"
3
2
 
4
3
  module Marty
5
4
 
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require "class_list"
3
2
 
4
3
  module Marty
5
4
 
@@ -7,8 +7,8 @@ $$ language plpgsql;
7
7
  drop view if exists vw_marty_postings;
8
8
  create or replace view vw_marty_postings as
9
9
  select
10
- concat(u.firstname, ' ', u.lastname) AS user_name,
11
- concat(ou.firstname, ' ', ou.lastname) AS obsoleted_user,
10
+ u.login AS user_name,
11
+ ou.login AS obsoleted_user,
12
12
  main.id,
13
13
  main.group_id,
14
14
  main.created_dt,
@@ -0,0 +1,614 @@
1
+ require 'spec_helper'
2
+
3
+ module Marty::DataGridSpec
4
+ describe DataGrid do
5
+
6
+ G1 =<<EOS
7
+ state\tstring\tv\t\t
8
+ ltv\tnumrange\tv\t\t
9
+ fico\tnumrange\th\t\t
10
+
11
+ \t\t>=600<700\t>=700<750\t>=750
12
+ CA\t<=80\t1.1\t2.2\t3.3
13
+ TX|HI\t>80<=105\t4.4\t5.5\t6.6
14
+ NM\t<=80\t1.2\t2.3\t3.4
15
+ MA\t>80<=105\t4.5\t5.6\t
16
+ \t<=80\t11\t22\t33
17
+ EOS
18
+
19
+ G2 =<<EOS
20
+ units\tinteger\tv\t\t
21
+ ltv\tnumrange\tv\t\t
22
+ cltv\tnumrange\th\t\t
23
+ fico\tnumrange\th\t\t
24
+
25
+ \t\t>=100<110\t>=110<120\t>=120
26
+ \t\t>=600<700\t>=700<750\t>=750
27
+ 1|2\t<=80\t1.1\t2.2\t3.3
28
+ 1|2\t>80<=105\t4.4\t5.5\t6.6
29
+ 3|4\t<=80\t1.2\t2.3\t3.4
30
+ 3|4\t>80<=105\t4.5\t5.6\t6.7
31
+ EOS
32
+
33
+ G3 = File.open(File.expand_path("../srp_data.csv", __FILE__)).read
34
+
35
+ G4 =<<EOS
36
+ lenient
37
+ hb_indicator\tboolean\tv
38
+ cltv\tnumrange\th
39
+
40
+ \t<=60\t>60<=70\t>70<=75\t>75<=80\t>80<=85\t>85<=90\t>90<=95\t>95<=97
41
+ true\t-0.750\t-0.750\t-0.750\t-1.500\t-1.500\t-1.500\t\t
42
+ EOS
43
+
44
+ G5 =<<EOS
45
+ ltv\tnumrange\tv\t\t
46
+
47
+ <=115\t-0.375
48
+ >115<=135\t-0.750
49
+ EOS
50
+
51
+ G6 =<<EOS
52
+ ltv\tnumrange\th
53
+
54
+ <=115\t>115<=135
55
+ -0.375\t-0.750
56
+ EOS
57
+
58
+ G7 =<<EOS
59
+ string
60
+ hb_indicator\tboolean\tv
61
+ cltv\tnumrange\th
62
+
63
+ \t<=60\t>60<=70\t>70<=75\t>75<=80\t>80<=85\t>85<=90\t>90<=95\t>95<=97
64
+ true\tThis\tis\ta\ttest\tof\tstring type\t\t
65
+ EOS
66
+
67
+ G8 =<<EOS
68
+ Marty::DataGrid
69
+ ltv\tnumrange\tv\t\t
70
+
71
+ <=115\tG1
72
+ >115<=135\tG2
73
+ >135<=140\tG3
74
+ EOS
75
+
76
+ G9 =<<EOS
77
+ state\tstring\tv
78
+ ltv\tnumrange\tv
79
+
80
+ CA|TX\t>80\t123
81
+ \t>80\t456
82
+ EOS
83
+
84
+ Ga =<<EOS
85
+ dg\tMarty::DataGrid\tv\t\t
86
+
87
+ G1|G2\t7
88
+ G3\t8
89
+ EOS
90
+
91
+ Gb =<<EOS
92
+ property_state\tGemini::State\tv\t\t
93
+
94
+ CA|TX\t70
95
+ GA\t80
96
+ MN\t90
97
+ EOS
98
+
99
+ Gc =<<EOS
100
+ Marty::DataGrid
101
+ property_state\tGemini::State\tv\t\t
102
+
103
+ CA|TX\tGb
104
+ EOS
105
+
106
+ Gd =<<EOS
107
+ hb_indicator\tboolean\tv
108
+
109
+ true\t456
110
+ false\t123
111
+ EOS
112
+
113
+ before(:each) do
114
+ #Mcfly.whodunnit = Marty::User.find_by_login('marty')
115
+ marty_whodunnit
116
+ end
117
+
118
+ def lookup_grid_helper(pt, gridname, params, follow=false)
119
+ dg=Marty::DataGrid.lookup(pt, gridname)
120
+ res=dg.lookup_grid_distinct_entry(pt, params, nil, follow)
121
+ [res["result"], res["name"]]
122
+ end
123
+
124
+ describe "validations" do
125
+ it "a basic data grid should load ok" do
126
+ dg_from_import("G1", G1)
127
+ dg_from_import("G2", G2)
128
+ dg_from_import("G3", G3)
129
+ dg_from_import("G8", G8)
130
+ dg_from_import("Ga", Ga)
131
+
132
+ expect(Marty::DataGrid.lookup('infinity', "G1").name).to eq "G1"
133
+ expect(Marty::DataGrid.lookup('infinity', "G2").name).to eq "G2"
134
+ expect(Marty::DataGrid.lookup('infinity', "G3").name).to eq "G3"
135
+ end
136
+
137
+ it "should not allow dup attr names" do
138
+ g_bad = G1.sub(/fico/, "ltv")
139
+
140
+ expect {
141
+ dg_from_import("G2", g_bad)
142
+ }.to raise_error(ActiveRecord::RecordInvalid)
143
+ end
144
+
145
+ it "should not allow dup grid names" do
146
+ dg_from_import("G1", G1)
147
+
148
+ expect {
149
+ dg_from_import("G1", G2)
150
+ }.to raise_error(ActiveRecord::RecordInvalid)
151
+ end
152
+
153
+ it "should not allow extra attr rows" do
154
+ g_bad = "x\tnumrange\th\t\t\n" + G1
155
+
156
+ expect {
157
+ dg_from_import("G2", g_bad)
158
+ }.to raise_error(RuntimeError)
159
+ end
160
+
161
+ it "should not allow dup row/col key combos" do
162
+ g_bad = G1 + G1.split("\n").last + "\n"
163
+ expect {
164
+ dg_from_import("G2", g_bad)
165
+ }.to raise_error(ActiveRecord::RecordInvalid)
166
+
167
+ g_bad = G2 + G2.split("\n").last + "\n"
168
+ expect {
169
+ dg_from_import("G2", g_bad)
170
+ }.to raise_error(ActiveRecord::RecordInvalid)
171
+ end
172
+
173
+ it "NULL keys are only allowed on string fields" do
174
+ g_bad = G2.sub(/1\|2/, "")
175
+
176
+ expect {
177
+ dg_from_import("G2", g_bad)
178
+ }.to raise_error(ActiveRecord::RecordInvalid)
179
+
180
+ g_bad = G2.sub(/<=80/, "")
181
+
182
+ expect {
183
+ dg_from_import("G2", g_bad)
184
+ }.to raise_error(ActiveRecord::RecordInvalid)
185
+ end
186
+
187
+ it "Unknown keys for typed grids should raise error" do
188
+ g_bad = G8.sub(/G3/, "XXXXX")
189
+
190
+ expect {
191
+ dg_from_import("G8", g_bad)
192
+ }.to raise_error(RuntimeError)
193
+
194
+ g_bad = G8.sub(/DataGrid/, "Division")
195
+
196
+ expect {
197
+ dg_from_import("G8", g_bad)
198
+ }.to raise_error(RuntimeError)
199
+ end
200
+
201
+ it "Unknown keys for grid headers should raise error" do
202
+ g_bad = Ga.sub(/G3/, "XXXXX")
203
+
204
+ expect {
205
+ dg_from_import("Ga", g_bad)
206
+ }.to raise_error(RuntimeError)
207
+
208
+ g_bad = Ga.sub(/DataGrid/, "Division")
209
+
210
+ expect {
211
+ dg_from_import("Ga", g_bad)
212
+ }.to raise_error(RuntimeError)
213
+ end
214
+ end
215
+
216
+ describe "lookup" do
217
+ before(:each) do
218
+ ["G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "Ga", "Gb",
219
+ "Gc", "Gd"].each { |g|
220
+ dg_from_import(g, "Marty::DataGridSpec::#{g}".constantize)
221
+ }
222
+ end
223
+
224
+ it "should handle boolean lookups" do
225
+ res = [true, false].map { |hb_indicator|
226
+ lookup_grid_helper('infinity',
227
+ "Gd",
228
+ {"hb_indicator" => hb_indicator,
229
+ },
230
+ )
231
+ }
232
+ expect(res).to eq [[456.0, "Gd"], [123.0, "Gd"]]
233
+ end
234
+
235
+ it "should handle basic lookups" do
236
+ res = lookup_grid_helper('infinity',
237
+ "G3",
238
+ {"amount" => 160300,
239
+ "state" => "HI",
240
+ },
241
+ )
242
+ expect(res).to eq [1.655,"G3"]
243
+
244
+ [3,4].each {
245
+ |units|
246
+ res = lookup_grid_helper('infinity',
247
+ "G2",
248
+ {"fico" => 720,
249
+ "units" => units,
250
+ "ltv" => 100,
251
+ "cltv" => 110.1,
252
+ },
253
+ )
254
+ expect(res).to eq [5.6,"G2"]
255
+ }
256
+
257
+ dg = Marty::DataGrid.lookup('infinity', "G1")
258
+
259
+ h = {
260
+ "fico" => 600,
261
+ "state" => "RI",
262
+ "ltv" => 10,
263
+ }
264
+
265
+ res = lookup_grid_helper('infinity', "G1", h)
266
+ expect(res).to eq [11,"G1"]
267
+
268
+ dg.update_from_import("G1", G1.sub(/11/, "111"))
269
+
270
+ res = lookup_grid_helper('infinity', "G1", h)
271
+ expect(res).to eq [111,"G1"]
272
+ end
273
+
274
+ it "should result in error when there are multiple cell hits" do
275
+ expect {
276
+ lookup_grid_helper('infinity',
277
+ "G2",
278
+ {"fico" => 720,
279
+ "ltv" => 100,
280
+ "cltv" => 110.1,
281
+ },
282
+ )
283
+ }.to raise_error(RuntimeError)
284
+ end
285
+
286
+ it "should return nil when matching data grid cell is nil" do
287
+ res = lookup_grid_helper('infinity',
288
+ "G1",
289
+ {"fico" => 800,
290
+ "state" => "MA",
291
+ "ltv" => 81,
292
+ },
293
+ )
294
+ expect(res).to eq [nil,"G1"]
295
+ end
296
+
297
+ it "should handle string wildcards" do
298
+ res = lookup_grid_helper('infinity',
299
+ "G1",
300
+ {"fico" => 720,
301
+ "state" => "GU",
302
+ "ltv" => 80,
303
+ },
304
+ )
305
+ expect(res).to eq [22,"G1"]
306
+ end
307
+
308
+ it "should handle matches which also have a wildcard match" do
309
+ dg_from_import("G9", G9)
310
+
311
+ res = lookup_grid_helper('infinity',
312
+ "G9",
313
+ {"state" => "CA", "ltv" => 81},
314
+ )
315
+ expect(res).to eq [123,"G9"]
316
+
317
+ res = lookup_grid_helper('infinity',
318
+ "G9",
319
+ {"state" => "GU", "ltv" => 81},
320
+ )
321
+ expect(res).to eq [456,"G9"]
322
+ end
323
+
324
+ it "should handle nil attr values to match wildcard" do
325
+ dg_from_import("G9", G9)
326
+
327
+ res = lookup_grid_helper('infinity',
328
+ "G9",
329
+ {"state" => nil, "ltv" => 81},
330
+ )
331
+ expect(res).to eq [456,"G9"]
332
+
333
+ expect {
334
+ res = lookup_grid_helper('infinity',
335
+ "G9",
336
+ {"state" => "CA", "ltv" => nil},
337
+ )
338
+ }.to raise_error(RuntimeError)
339
+ end
340
+
341
+ it "should handle boolean keys" do
342
+ res = lookup_grid_helper('infinity',
343
+ "G4",
344
+ {"hb_indicator" => true,
345
+ "cltv" => 80,
346
+ },
347
+ )
348
+ expect(res).to eq [-1.5,"G4"]
349
+
350
+ res = lookup_grid_helper('infinity',
351
+ "G4",
352
+ {"hb_indicator" => false,
353
+ "cltv" => 80,
354
+ },
355
+ )
356
+ expect(res).to eq [nil,"G4"]
357
+ end
358
+
359
+ it "should handle vertical-only grids" do
360
+ res = lookup_grid_helper('infinity',
361
+ "G5",
362
+ {"ltv" => 80},
363
+ )
364
+ expect(res).to eq [-0.375,"G5"]
365
+ end
366
+
367
+ it "should handle horiz-only grids" do
368
+ res = lookup_grid_helper('infinity',
369
+ "G6",
370
+ {"ltv" => 80, "conforming" => true},
371
+ )
372
+ expect(res).to eq [-0.375,"G6"]
373
+ end
374
+
375
+ it "should handle string typed data grids" do
376
+ expect(Marty::DataGrid.lookup('infinity', "G7").data_type).to eq "string"
377
+
378
+ res = lookup_grid_helper('infinity',
379
+ "G7",
380
+ {"hb_indicator" => true,
381
+ "cltv" => 80,
382
+ },
383
+ )
384
+ expect(res).to eq ["test","G7"]
385
+ end
386
+
387
+ it "should handle DataGrid typed data grids" do
388
+ expect(Marty::DataGrid.lookup('infinity', "G8").data_type).to eq "Marty::DataGrid"
389
+ g1 = Marty::DataGrid.lookup('infinity', "G1")
390
+
391
+ res = lookup_grid_helper('infinity',
392
+ "G8",
393
+ {"ltv" => 80,
394
+ },
395
+ )
396
+ expect(res).to eq [g1,"G8"]
397
+ end
398
+
399
+ it "should handle multi DataGrid lookups" do
400
+ expect(Marty::DataGrid.lookup('infinity', "G8").data_type).to eq "Marty::DataGrid"
401
+ g1 = Marty::DataGrid.lookup('infinity', "G1")
402
+
403
+ h = {
404
+ "fico" => 600,
405
+ "state" => "RI",
406
+ "ltv" => 10,
407
+ }
408
+
409
+ g1_res = lookup_grid_helper('infinity', "G1", h)
410
+ expect(g1_res).to eq [11,"G1"]
411
+
412
+ res = lookup_grid_helper('infinity',
413
+ "G8",
414
+ h,true
415
+ )
416
+ expect(g1_res).to eq res
417
+ end
418
+
419
+ it "should handle DataGrid typed data grids" do
420
+ g1 = Marty::DataGrid.lookup('infinity', "G1")
421
+
422
+ res = lookup_grid_helper('infinity',
423
+ "Ga",
424
+ {"dg" => g1,
425
+ },
426
+ )
427
+ expect(res).to eq [7,"Ga"]
428
+
429
+ # should be able to lookup bu name as well
430
+ res = lookup_grid_helper('infinity',
431
+ "Ga",
432
+ {"dg" => "G2",
433
+ },
434
+ )
435
+ expect(res).to eq [7,"Ga"]
436
+ end
437
+
438
+ it "should handle DataGrid typed data grids -- non mcfly" do
439
+ ca = Gemini::State.find_by_name("CA")
440
+
441
+ res = lookup_grid_helper('infinity',
442
+ "Gb",
443
+ {"property_state" => ca,
444
+ },
445
+ )
446
+ expect(res).to eq [70,"Gb"]
447
+
448
+ # should be able to lookup bu name as well
449
+ res = lookup_grid_helper('infinity',
450
+ "Gb",
451
+ {"property_state" => "CA",
452
+ },
453
+ )
454
+ expect(res).to eq [70,"Gb"]
455
+ end
456
+
457
+ it "should return grid data and metadata simple" do
458
+ pt = 'infinity'
459
+ expected_data = [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [1.2, 2.3, 3.4],
460
+ [4.5, 5.6, 6.7]]
461
+ expected_metadata = [{"dir"=>"v",
462
+ "attr"=>"units",
463
+ "keys"=>[[1, 2], [1, 2], [3, 4], [3, 4]],
464
+ "type"=>"integer"},
465
+ {"dir"=>"v",
466
+ "attr"=>"ltv",
467
+ "keys"=>["[,80]", "(80,105]", "[,80]", "(80,105]"],
468
+ "type"=>"numrange"},
469
+ {"dir"=>"h",
470
+ "attr"=>"cltv",
471
+ "keys"=>["[100,110)", "[110,120)", "[120,]"],
472
+ "type"=>"numrange"},
473
+ {"dir"=>"h",
474
+ "attr"=>"fico",
475
+ "keys"=>["[600,700)", "[700,750)", "[750,]"],
476
+ "type"=>"numrange"}]
477
+
478
+ dg = Marty::DataGrid.lookup(pt, 'G2')
479
+ res = dg.lookup_grid_distinct_entry(pt, {}, nil, true, true)
480
+ expect(res["data"]).to eq (expected_data)
481
+ expect(res["metadata"]).to eq (expected_metadata)
482
+ end
483
+
484
+ it "should return grid data and metadata multi (following)" do
485
+ pt = 'infinity'
486
+ expected_data = [[1.1, 2.2, 3.3],[4.4, 5.5, 6.6],[1.2, 2.3, 3.4],
487
+ [4.5, 5.6, nil],[11.0, 22.0, 33.0]]
488
+ expected_metadata = [{"dir"=>"v",
489
+ "attr"=>"state",
490
+ "keys"=>[["CA"], ["HI", "TX"], ["NM"], ["MA"], nil],
491
+ "type"=>"string"},
492
+ {"dir"=>"v",
493
+ "attr"=>"ltv",
494
+ "keys"=>["[,80]", "(80,105]", "[,80]", "(80,105]",
495
+ "[,80]"],
496
+ "type"=>"numrange"},
497
+ {"dir"=>"h",
498
+ "attr"=>"fico",
499
+ "keys"=>["[600,700)", "[700,750)", "[750,]"],
500
+ "type"=>"numrange"}]
501
+ dg = Marty::DataGrid.lookup(pt, 'G8')
502
+ res = dg.lookup_grid_distinct_entry(pt, { "ltv" => 10,
503
+ "state" => "RI" }, nil, true,
504
+ true)
505
+ expect(res["data"]).to eq (expected_data)
506
+ expect(res["metadata"]).to eq (expected_metadata)
507
+ end
508
+
509
+ it "should return grid data and metadata multi (not following)" do
510
+ pt = 'infinity'
511
+ expected_data = [["G1"], ["G2"], ["G3"]]
512
+ expected_metadata = [{"dir"=>"v",
513
+ "attr"=>"ltv",
514
+ "keys"=>["[,115]", "(115,135]", "(135,140]"],
515
+ "type"=>"numrange"}]
516
+ dg = Marty::DataGrid.lookup(pt, 'G8')
517
+ res = dg.lookup_grid_distinct_entry(pt, { "ltv" => 10,
518
+ "state" => "RI" }, nil, false,
519
+ true)
520
+ expect(res["data"]).to eq (expected_data)
521
+ expect(res["metadata"]).to eq (expected_metadata)
522
+ end
523
+ end
524
+
525
+ describe "updates" do
526
+ it "should be possible to modify a grid referenced from a multi-grid" do
527
+ dgb = dg_from_import("Gb", Gb, '1/1/2014')
528
+ dgc = dg_from_import("Gc", Gc, '2/2/2014')
529
+
530
+ dgb.update_from_import("Gb", Gb.sub(/70/, "333"), '1/1/2015')
531
+ dgb.update_from_import("Gb", Gb.sub(/70/, "444"), '1/1/2016')
532
+
533
+ res = dgc.lookup_grid_distinct_entry('2/2/2014',
534
+ {"property_state" => "CA"})
535
+
536
+ expect(res["result"]).to eq(70)
537
+
538
+ res = dgc.lookup_grid_distinct_entry('2/2/2015',
539
+ {"property_state" => "CA"})
540
+
541
+ expect(res["result"]).to eq(333)
542
+
543
+ res = dgc.lookup_grid_distinct_entry('2/2/2016',
544
+ {"property_state" => "CA"})
545
+
546
+ expect(res["result"]).to eq(444)
547
+ end
548
+
549
+ it "should not create a new version if no change has been made" do
550
+ dg = dg_from_import("G4", G1)
551
+ dg.update_from_import("G4", G1)
552
+ expect(Marty::DataGrid.unscoped.where(group_id: dg.group_id).count).to eq 1
553
+ end
554
+
555
+ it "should be able to export and import back grids" do
556
+ [G1, G2, G3, G4, G5, G6, G7, G8, G9, Ga, Gb].each_with_index do
557
+ |grid, i|
558
+ dg = dg_from_import("G#{i}", grid)
559
+ g1 = dg.export
560
+ dg = dg_from_import("Gx#{i}", g1)
561
+ g2 = dg.export
562
+ expect(g1).to eq g2
563
+ end
564
+ end
565
+
566
+ it "should be able to externally export/import grids" do
567
+ load_scripts(nil, Date.today)
568
+
569
+ dg = dg_from_import("G1", G1)
570
+
571
+ p = posting("BASE", DateTime.tomorrow, '?')
572
+
573
+ engine = Marty::ScriptSet.new.get_engine("DataReport")
574
+ res = engine.evaluate("TableReport",
575
+ "result",
576
+ {
577
+ "pt_name" => p.name,
578
+ "class_name" => "Marty::DataGrid",
579
+ },
580
+ )
581
+
582
+ # FIXME: really hacky removing "" (data_grid) -- This is a bug
583
+ # in TableReport/CSV generation.
584
+ res.gsub!(/\"\"/, '')
585
+ sum = do_import_summary(Marty::DataGrid,
586
+ res,
587
+ 'infinity',
588
+ nil,
589
+ nil,
590
+ ",",
591
+ )
592
+
593
+ expect(sum).to eq({same: 1})
594
+
595
+ res11 = res.sub(/G1/, "G11")
596
+
597
+ sum = do_import_summary(Marty::DataGrid,
598
+ res11,
599
+ 'infinity',
600
+ nil,
601
+ nil,
602
+ ",",
603
+ )
604
+
605
+ expect(sum).to eq({create: 1})
606
+
607
+ g1 = Marty::DataGrid.lookup('infinity', "G1")
608
+ g11 = Marty::DataGrid.lookup('infinity', "G11")
609
+
610
+ expect(g1.export).to eq g11.export
611
+ end
612
+ end
613
+ end
614
+ end