stand_socotra_policy_transformer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/__utils__/load_payload.js +16 -0
- package/__tests__/__utils__/payloads/minimal_change_base.json +574 -0
- package/__tests__/__utils__/payloads/minimal_change_base2.json +574 -0
- package/__tests__/__utils__/payloads/minimal_change_resulting_socotra.json +17 -0
- package/__tests__/__utils__/payloads/minimal_change_resulting_socotra2.json +27 -0
- package/__tests__/__utils__/payloads/minimal_change_retool.json +37 -0
- package/__tests__/__utils__/payloads/minimal_change_retool2.json +27 -0
- package/__tests__/__utils__/payloads/sample_minimal_retool.json +33 -0
- package/__tests__/__utils__/payloads/sample_minimal_socotra_payload.json +116 -0
- package/__tests__/__utils__/payloads/sample_new_policy_holder.json +17 -0
- package/__tests__/__utils__/payloads/sample_new_quote.json +210 -0
- package/__tests__/__utils__/payloads/sample_new_quote_retool.json +93 -0
- package/__tests__/__utils__/payloads/sample_retool.json +160 -0
- package/__tests__/__utils__/payloads/sample_retool_converted_quote.json +54 -0
- package/__tests__/__utils__/payloads/sample_retool_socotra_subset.json +127 -0
- package/__tests__/__utils__/payloads/sample_socotra_quote.json +773 -0
- package/__tests__/__utils__/payloads/sample_update_quote.json +18 -0
- package/__tests__/basic_knockout.test.js +113 -0
- package/__tests__/claims_history_knockout.test.js +56 -0
- package/__tests__/exterior_knockout.test.js +192 -0
- package/__tests__/helpers/index.js +10 -0
- package/__tests__/home_owner_knockouts.js +260 -0
- package/__tests__/interior_knockout.test.js +321 -0
- package/__tests__/rate_call_knockouts.test.js +347 -0
- package/__tests__/retool_utils/socotra_payload.test.js +168 -0
- package/__tests__/retool_utils/socotra_structure_helper.test.js +129 -0
- package/__tests__/underwriter.test.js +169 -0
- package/__tests__/wf_knockout.test.js +124 -0
- package/package.json +17 -0
- package/src/index.js +3 -0
- package/src/knockouts/basic_knockouts.js +66 -0
- package/src/knockouts/claims_history_knockout.js +24 -0
- package/src/knockouts/exterior_knockouts.js +97 -0
- package/src/knockouts/home_owner_knockouts.js +118 -0
- package/src/knockouts/index.js +83 -0
- package/src/knockouts/interior_knockouts.js +149 -0
- package/src/knockouts/rate_call_knockouts.js +155 -0
- package/src/knockouts/wf_knockouts.js +66 -0
- package/src/retool_to_socotra.js +18 -0
- package/src/retool_utils/socotra_payloads.js +316 -0
- package/src/retool_utils/socotra_structure_helper.js +223 -0
- package/src/underwriter.js +86 -0
- package/webpack.config.js +25 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const {load_payload} = require("../__utils__/load_payload");
|
|
2
|
+
const {new_policy_payload, policy_holder_payload, quote_to_retool_payload, retool_to_quote_updated } = require("../../src/retool_utils/socotra_payloads");
|
|
3
|
+
|
|
4
|
+
describe("Policy Holder Payload", () => {
|
|
5
|
+
test('fails with descriptive error if just one field is missing', () => {
|
|
6
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool.json')
|
|
7
|
+
delete retool_payload['owner_first_name']
|
|
8
|
+
|
|
9
|
+
let res = policy_holder_payload(retool_payload);
|
|
10
|
+
expect(res.payload).toEqual({});
|
|
11
|
+
expect(res.error_message).toBe("must include the following fields [owner_first_name]")
|
|
12
|
+
expect(res.status).toBe("error")
|
|
13
|
+
});
|
|
14
|
+
test('fails with descriptive error if just more than one required field is missing', () => {
|
|
15
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool.json')
|
|
16
|
+
delete retool_payload['owner_first_name']
|
|
17
|
+
delete retool_payload['owner_last_name']
|
|
18
|
+
delete retool_payload['owner_email']
|
|
19
|
+
delete retool_payload['owner_phone_number']
|
|
20
|
+
|
|
21
|
+
let res = policy_holder_payload(retool_payload);
|
|
22
|
+
expect(res.payload).toEqual({});
|
|
23
|
+
expect(res.error_message).toBe("must include the following fields [owner_first_name,owner_last_name,owner_email]")
|
|
24
|
+
expect(res.status).toBe("error")
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('succeeds if all required fields are there', () => {
|
|
28
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool.json')
|
|
29
|
+
let res = policy_holder_payload(retool_payload);
|
|
30
|
+
|
|
31
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/sample_new_policy_holder.json'));
|
|
32
|
+
expect(res.error_message).toBe("")
|
|
33
|
+
expect(res.status).toBe("success")
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("New Policy Payload", () => {
|
|
38
|
+
test("Happy Path works", () => {
|
|
39
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool.json')
|
|
40
|
+
let res = new_policy_payload(retool_payload, '9bdadf0c-5ce0-4ea7-8f9d-7e7d235522d0');
|
|
41
|
+
|
|
42
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/sample_new_quote.json'));
|
|
43
|
+
expect(res.error_message).toBe("")
|
|
44
|
+
expect(res.status).toBe("success")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("Works when number set to empty string", () => {
|
|
48
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool.json')
|
|
49
|
+
retool_payload['home_heating_update_year'] = ""
|
|
50
|
+
let res = new_policy_payload(retool_payload, '9bdadf0c-5ce0-4ea7-8f9d-7e7d235522d0');
|
|
51
|
+
|
|
52
|
+
let expected_payload = load_payload('__tests__/__utils__/payloads/sample_new_quote.json')
|
|
53
|
+
delete expected_payload['fieldValues']['home_heater_last_updated']
|
|
54
|
+
expect(res.payload).toEqual(expected_payload);
|
|
55
|
+
expect(res.error_message).toBe("")
|
|
56
|
+
expect(res.status).toBe("success")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("Works with payment schedule override", () => {
|
|
60
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool.json')
|
|
61
|
+
retool_payload['payment_schedule'] = 'quarterly'
|
|
62
|
+
let res = new_policy_payload(retool_payload, '9bdadf0c-5ce0-4ea7-8f9d-7e7d235522d0');
|
|
63
|
+
|
|
64
|
+
let socotra_quote_payload = load_payload('__tests__/__utils__/payloads/sample_new_quote.json')
|
|
65
|
+
socotra_quote_payload['paymentScheduleName'] = 'quarterly'
|
|
66
|
+
|
|
67
|
+
expect(res.payload).toEqual(socotra_quote_payload);
|
|
68
|
+
expect(res.error_message).toBe("")
|
|
69
|
+
expect(res.status).toBe("success")
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("Works with minimal payload with no unneeded fields", () => {
|
|
73
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_minimal_retool.json')
|
|
74
|
+
let res = new_policy_payload(retool_payload, 'f0d09c62-3ec1-4830-b4b3-17e7bed46560');
|
|
75
|
+
|
|
76
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/sample_minimal_socotra_payload.json'));
|
|
77
|
+
expect(res.error_message).toBe("")
|
|
78
|
+
expect(res.status).toBe("success")
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe("Retool Payload", () => {
|
|
83
|
+
test('Happy path converts to retool payload', () => {
|
|
84
|
+
let socotra_payload = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
85
|
+
let res = quote_to_retool_payload(socotra_payload)
|
|
86
|
+
|
|
87
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/sample_retool_socotra_subset.json'));
|
|
88
|
+
expect(res.error_message).toBe("")
|
|
89
|
+
expect(res.status).toBe("success")
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe("Update Socotra Quote", () => {
|
|
94
|
+
test('Happy path converts to retool payload', () => {
|
|
95
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool_socotra_subset.json')
|
|
96
|
+
retool_payload["protection_class"] = "3"
|
|
97
|
+
retool_payload["deadbolt"] = "No"
|
|
98
|
+
retool_payload["coverage_b"] = 300000
|
|
99
|
+
retool_payload["zip"] = "94655"
|
|
100
|
+
retool_payload["payment_schedule"] = "upfront"
|
|
101
|
+
|
|
102
|
+
let old_quote = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
103
|
+
delete old_quote['name']
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
let res = retool_to_quote_updated(retool_payload, old_quote)
|
|
107
|
+
|
|
108
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/sample_update_quote.json'));
|
|
109
|
+
expect(res.error_message).toBe("")
|
|
110
|
+
expect(res.status).toBe("success")
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('Minimal Change Example', () => {
|
|
114
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/minimal_change_retool.json')
|
|
115
|
+
let old_quote = load_payload('__tests__/__utils__/payloads/minimal_change_base.json')
|
|
116
|
+
delete old_quote['name']
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
let res = retool_to_quote_updated(retool_payload, old_quote)
|
|
120
|
+
|
|
121
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/minimal_change_resulting_socotra.json'));
|
|
122
|
+
expect(res.error_message).toBe("")
|
|
123
|
+
expect(res.status).toBe("success")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('Minimal Change Example 2 Nulls from socotra', () => {
|
|
127
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/minimal_change_retool2.json')
|
|
128
|
+
let old_quote = load_payload('__tests__/__utils__/payloads/minimal_change_base2.json')
|
|
129
|
+
delete old_quote['name']
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
let res = retool_to_quote_updated(retool_payload, old_quote)
|
|
133
|
+
|
|
134
|
+
expect(res.payload).toEqual(load_payload('__tests__/__utils__/payloads/minimal_change_resulting_socotra2.json'));
|
|
135
|
+
expect(res.error_message).toBe("")
|
|
136
|
+
expect(res.status).toBe("success")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('deletes exposure if not needed', () => {
|
|
140
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool_socotra_subset.json')
|
|
141
|
+
retool_payload["coverage_b"] = 300000
|
|
142
|
+
|
|
143
|
+
let old_quote = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
let res = retool_to_quote_updated(retool_payload, old_quote)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
expect(res.payload).not.toHaveProperty("updateExposures")
|
|
150
|
+
expect(res.error_message).toBe("")
|
|
151
|
+
expect(res.status).toBe("success")
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('deletes field_values if not needed', () => {
|
|
155
|
+
let retool_payload = load_payload('__tests__/__utils__/payloads/sample_retool_socotra_subset.json')
|
|
156
|
+
retool_payload["deadbolt"] = "Yes"
|
|
157
|
+
|
|
158
|
+
let old_quote = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
let res = retool_to_quote_updated(retool_payload, old_quote)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
expect(res.payload).not.toHaveProperty("fieldValues")
|
|
165
|
+
expect(res.error_message).toBe("")
|
|
166
|
+
expect(res.status).toBe("success")
|
|
167
|
+
})
|
|
168
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const {SocotraEntry } = require("../../src/retool_utils/socotra_structure_helper");
|
|
2
|
+
const {load_payload} = require("../__utils__/load_payload");
|
|
3
|
+
describe("SocotraEntry", () => {
|
|
4
|
+
test('fails with descriptive error if you pick a location that is not supported', () => {
|
|
5
|
+
expect(() => new SocotraEntry("some id", "quote locator", "bad.location"))
|
|
6
|
+
.toThrow('Unsupported Socotra Location: bad.location');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('happy path works', () => {
|
|
10
|
+
let se = new SocotraEntry("some id", "field_name", "policy.fields")
|
|
11
|
+
expect(se.retool_id).toEqual("some id")
|
|
12
|
+
expect(se.socotra_id).toEqual("field_name")
|
|
13
|
+
expect(se.socotra_location).toEqual("policy.fields")
|
|
14
|
+
expect(se.to_retool_func(5)).toEqual(5)
|
|
15
|
+
expect(se.to_scotra_func(5)).toEqual(5)
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("socotra_create_policy_template", () => {
|
|
20
|
+
test('gives back a payload with the required attributes empty', () => {
|
|
21
|
+
let template = SocotraEntry.socotra_create_policy_template('some policy_holder_locator')
|
|
22
|
+
expect(template.policyholderLocator).toEqual("some policy_holder_locator")
|
|
23
|
+
expect(template.fieldValues).toEqual({})
|
|
24
|
+
expect(template.fieldGroups).toEqual([])
|
|
25
|
+
expect(template.exposures[0].exposureName).toEqual("dwelling")
|
|
26
|
+
expect(template.exposures[0].fieldGroups).toEqual([])
|
|
27
|
+
expect(template.exposures[0].fieldValues).toEqual({})
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("socotra_create_response", () => {
|
|
32
|
+
test('works for policy values', () => {
|
|
33
|
+
let template = SocotraEntry.socotra_create_policy_template("locator")
|
|
34
|
+
let retool_payload = {"some_id": 42}
|
|
35
|
+
let se = new SocotraEntry("some_id", "example_field", "policy.fields")
|
|
36
|
+
|
|
37
|
+
se.socotra_create_response(retool_payload, template)
|
|
38
|
+
expect(template.fieldValues.example_field).toEqual(retool_payload["some_id"])
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('works for exposure_values', () => {
|
|
42
|
+
let template = SocotraEntry.socotra_create_policy_template("locator")
|
|
43
|
+
let retool_payload = {"some_id": 42}
|
|
44
|
+
let se = new SocotraEntry("some_id", "example_field", "exposure.dwelling.fields")
|
|
45
|
+
|
|
46
|
+
se.socotra_create_response(retool_payload, template)
|
|
47
|
+
expect(template.exposures[0].fieldValues.example_field).toEqual(retool_payload["some_id"])
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('works for the effective_date', () => {
|
|
51
|
+
let template = SocotraEntry.socotra_create_policy_template("locator")
|
|
52
|
+
let retool_payload = {"start_date": "2024-01-01"}
|
|
53
|
+
let se = new SocotraEntry("start_date", "startTimestamp", "effective_date")
|
|
54
|
+
|
|
55
|
+
se.socotra_create_response(retool_payload, template)
|
|
56
|
+
expect(template.policyStartTimestamp).toEqual(1704096000000)
|
|
57
|
+
expect(template.policyEndTimestamp).toEqual(1735718400000)
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('applies transformation if one is given', () => {
|
|
61
|
+
let template = SocotraEntry.socotra_create_policy_template("locator")
|
|
62
|
+
let retool_payload = {"some_id": 42}
|
|
63
|
+
let se = new SocotraEntry("some_id", "example_field", "exposure.dwelling.fields", undefined,(x) => x+1)
|
|
64
|
+
|
|
65
|
+
se.socotra_create_response(retool_payload, template)
|
|
66
|
+
expect(template.exposures[0].fieldValues.example_field).toEqual(retool_payload["some_id"] + 1)
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe("socotra_create_quote from other quote", () => {
|
|
70
|
+
test('adds selected values to the right place', () => {
|
|
71
|
+
let template = SocotraEntry.socotra_create_update_template("exposure_locator")
|
|
72
|
+
let retool_payload = {"some_policy_id": 42, "some_exposure_field": 43}
|
|
73
|
+
let se = new SocotraEntry("some_policy_id", "example_field", "policy.fields")
|
|
74
|
+
let se1 = new SocotraEntry("some_exposure_field", "example_field1", "exposure.dwelling.fields")
|
|
75
|
+
|
|
76
|
+
se.socotra_update(retool_payload, template)
|
|
77
|
+
se1.socotra_update(retool_payload, template)
|
|
78
|
+
expect(template.fieldValues.example_field).toEqual(retool_payload["some_policy_id"])
|
|
79
|
+
expect(template.updateExposures[0].fieldValues.example_field1).toEqual(retool_payload["some_exposure_field"])
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('works for effective_date', () => {
|
|
83
|
+
let template = SocotraEntry.socotra_create_update_template("exposure_locator")
|
|
84
|
+
let retool_payload = {"some_policy_id": 42, "some_exposure_field": 43, "start_date": "2024-01-01"}
|
|
85
|
+
let se = new SocotraEntry("start_date", "startTimestamp", "effective_date")
|
|
86
|
+
|
|
87
|
+
se.socotra_update(retool_payload, template)
|
|
88
|
+
expect(template.policyStartTimestamp).toEqual(1704096000000)
|
|
89
|
+
expect(template.policyEndTimestamp).toEqual(1735718400000)
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("retool_response", () => {
|
|
94
|
+
test('works for policy values', () => {
|
|
95
|
+
let template = {}
|
|
96
|
+
let socotra_payload = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
97
|
+
let se = new SocotraEntry("some_id", "gre_wf_score", "policy.fields")
|
|
98
|
+
|
|
99
|
+
se.retool_response(socotra_payload, template)
|
|
100
|
+
expect(template.some_id).toEqual("2.38")
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('works for exposure_values', () => {
|
|
104
|
+
let template = {}
|
|
105
|
+
let socotra_payload = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
106
|
+
let se = new SocotraEntry("some_id", "protection_class", "exposure.dwelling.fields")
|
|
107
|
+
|
|
108
|
+
se.retool_response(socotra_payload, template)
|
|
109
|
+
expect(template.some_id).toEqual("2")
|
|
110
|
+
});
|
|
111
|
+
test('works for effective_date', () => {
|
|
112
|
+
let template = {}
|
|
113
|
+
let socotra_payload = load_payload('__tests__/__utils__/payloads/sample_socotra_quote.json')
|
|
114
|
+
let se = new SocotraEntry("start_date", "startTimestamp", "effective_date")
|
|
115
|
+
|
|
116
|
+
se.retool_response(socotra_payload, template)
|
|
117
|
+
expect(template.start_date).toEqual("2024-09-06")
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// test('applies transformation if one is given', () => {
|
|
121
|
+
// let template = SocotraEntry.socotra_create_policy_template("locator")
|
|
122
|
+
// let retool_payload = {"some_id": 42}
|
|
123
|
+
// let se = new SocotraEntry("some_id", "example_field", "exposure.dwelling.fields", (x) => x+1)
|
|
124
|
+
//
|
|
125
|
+
// se.retool_response(retool_payload, template)
|
|
126
|
+
// expect(template.exposures[0].fieldValues.example_field).toEqual(retool_payload["some_id"] + 1)
|
|
127
|
+
// });
|
|
128
|
+
});
|
|
129
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const {underwrite} = require('../src')
|
|
2
|
+
const {claims_history_helper} = require("./helpers")
|
|
3
|
+
|
|
4
|
+
beforeAll(() => {
|
|
5
|
+
jest
|
|
6
|
+
.useFakeTimers()
|
|
7
|
+
.setSystemTime(new Date('2024-07-04').getTime());
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
jest.useRealTimers();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('Underwriter', () => {
|
|
15
|
+
describe('with_full_check', () =>{
|
|
16
|
+
test('Accepts with passing payload', () => {
|
|
17
|
+
let decision = underwrite(payload_helper())
|
|
18
|
+
expect(decision.decision).toBe('accept')
|
|
19
|
+
expect(decision.notes).toEqual([])
|
|
20
|
+
expect(decision.refers).toEqual([])
|
|
21
|
+
|
|
22
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(43)
|
|
23
|
+
expect(decision.all_decisions['full_replacement_value']).toBe('accept')
|
|
24
|
+
|
|
25
|
+
expect(Object.keys(decision.notes_dict).length).toEqual(43)
|
|
26
|
+
expect(decision.notes_dict['full_replacement_value']).toBe(null)
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('Rejects if something in payload rejects', () => {
|
|
30
|
+
let decision = underwrite(payload_helper({flood_score: 10, is_vacant: "Yes"}))
|
|
31
|
+
expect(decision.decision).toBe('reject')
|
|
32
|
+
expect(decision.notes.length).toEqual(2)
|
|
33
|
+
expect(decision.refers).toEqual([])
|
|
34
|
+
|
|
35
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(43)
|
|
36
|
+
expect(decision.all_decisions['flood_score']).toBe('reject')
|
|
37
|
+
expect(decision.all_decisions['is_vacant']).toBe('reject')
|
|
38
|
+
|
|
39
|
+
expect(Object.keys(decision.notes_dict).length).toEqual(43)
|
|
40
|
+
expect(decision.notes_dict['flood_score']).not.toBe(null)
|
|
41
|
+
expect(decision.all_decisions['is_vacant']).not.toBe(null)
|
|
42
|
+
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('Refer if something in payload refers', () => {
|
|
46
|
+
let decision = underwrite(payload_helper({"full_replacement_value": 6_500_000}))
|
|
47
|
+
expect(decision.decision).toBe('refer')
|
|
48
|
+
expect(decision.notes.length).toEqual(0)
|
|
49
|
+
expect(decision.refers).toEqual(["full_replacement_value"])
|
|
50
|
+
|
|
51
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(43)
|
|
52
|
+
expect(decision.all_decisions['full_replacement_value']).toBe('refer')
|
|
53
|
+
|
|
54
|
+
expect(Object.keys(decision.notes_dict).length).toEqual(43)
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('Reject if something in payload refers and something else rejects', () => {
|
|
58
|
+
let decision = underwrite(payload_helper({full_replacement_value: 6_500_000, flood_score: 10}))
|
|
59
|
+
expect(decision.decision).toBe('reject')
|
|
60
|
+
expect(decision.notes.length).toEqual(1)
|
|
61
|
+
expect(decision.refers).toEqual(["full_replacement_value"])
|
|
62
|
+
|
|
63
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(43)
|
|
64
|
+
expect(decision.all_decisions['full_replacement_value']).toBe('refer')
|
|
65
|
+
expect(decision.all_decisions['flood_score']).toBe('reject')
|
|
66
|
+
|
|
67
|
+
expect(Object.keys(decision.notes_dict).length).toEqual(43)
|
|
68
|
+
expect(decision.notes_dict['full_replacement_value']).toBeNull()
|
|
69
|
+
expect(decision.notes_dict['flood_score']).not.toBeNull()
|
|
70
|
+
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('Reject if something in payload is missing', () => {
|
|
74
|
+
payload = payload_helper()
|
|
75
|
+
delete payload.full_replacement_value
|
|
76
|
+
delete payload.arf
|
|
77
|
+
let decision = underwrite(payload)
|
|
78
|
+
expect(decision.decision).toBe('reject')
|
|
79
|
+
expect(decision.notes.length).toEqual(2)
|
|
80
|
+
expect(decision.refers).toEqual([])
|
|
81
|
+
|
|
82
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(43)
|
|
83
|
+
expect(decision.all_decisions['full_replacement_value']).toBe('reject')
|
|
84
|
+
expect(decision.all_decisions['wf_score_mean']).toBe('reject')
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("partial_check", () => {
|
|
88
|
+
test("doesn't reject for missing fields", ()=> {
|
|
89
|
+
let decision = underwrite({
|
|
90
|
+
multi_family_unit_count: 1, primary_dwelling_insured: 'Yes',
|
|
91
|
+
full_replacement_value: 6_000_000, prior_carrier: "Yes"}, false)
|
|
92
|
+
expect(decision.decision).toBe('accept')
|
|
93
|
+
expect(decision.notes).toEqual([])
|
|
94
|
+
expect(decision.refers).toEqual([])
|
|
95
|
+
|
|
96
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(4)
|
|
97
|
+
expect(decision.all_decisions['full_replacement_value']).toBe('accept')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("rejects if it should", ()=> {
|
|
101
|
+
let decision = underwrite({
|
|
102
|
+
multi_family_unit_count: 1, primary_dwelling_insured: 'Yes',
|
|
103
|
+
full_replacement_value: 6_000_000, prior_carrier: "Yes",
|
|
104
|
+
number_of_bankruptcies_judgements_or_liens: 2}, false)
|
|
105
|
+
expect(decision.decision).toBe('reject')
|
|
106
|
+
expect(decision.notes).toEqual(["Has bankruptcies, judgements, or liens"])
|
|
107
|
+
expect(decision.refers).toEqual([])
|
|
108
|
+
|
|
109
|
+
expect(Object.keys(decision.all_decisions).length).toEqual(5)
|
|
110
|
+
expect(decision.all_decisions['full_replacement_value']).toBe('accept')
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
function payload_helper(override){
|
|
116
|
+
let sample_payload = {
|
|
117
|
+
address: '1234 main street',
|
|
118
|
+
year_built: '2005',
|
|
119
|
+
co_applicant_address: '1234 main street',
|
|
120
|
+
multi_family_unit_count: 1,
|
|
121
|
+
primary_dwelling_insured: 'Yes',
|
|
122
|
+
full_replacement_value: 6_000_000,
|
|
123
|
+
prior_carrier: "Yes",
|
|
124
|
+
wf_variance: .001,
|
|
125
|
+
wf_score_mean: .3,
|
|
126
|
+
arf: 1.9,
|
|
127
|
+
flood_score: 5,
|
|
128
|
+
distance_to_coast: 1.4,
|
|
129
|
+
pool_features: ['Inground', "Fenced with self-locking gate"],
|
|
130
|
+
number_of_dogs: 1,
|
|
131
|
+
dog_history: 'No',
|
|
132
|
+
dog_breeds: ['non-guard dog of other breeds'],
|
|
133
|
+
exotic_pets: "No",
|
|
134
|
+
farming_type: "None",
|
|
135
|
+
number_of_mortgages: 0,
|
|
136
|
+
number_of_bankruptcies_judgements_or_liens: 0,
|
|
137
|
+
is_vacant: "No",
|
|
138
|
+
under_renovation: "No",
|
|
139
|
+
plumbing_last_update_year: 2022,
|
|
140
|
+
water_heater_last_update_year: 2022,
|
|
141
|
+
electrical_last_update_year: 2022,
|
|
142
|
+
home_heating_last_update_year: 2022,
|
|
143
|
+
heating_source: 'central gas heat',
|
|
144
|
+
has_underground_fuel_tank: "No",
|
|
145
|
+
has_steel_braided_hose: "Yes",
|
|
146
|
+
has_water_shutoff: "Yes",
|
|
147
|
+
circuit_breaker: "No",
|
|
148
|
+
home_business_type: "no",
|
|
149
|
+
has_attractive_nuisance: "No",
|
|
150
|
+
home_rental_type: "no",
|
|
151
|
+
degree_of_slope: 15,
|
|
152
|
+
construction_type: "Concrete",
|
|
153
|
+
siding_material: "Adobe",
|
|
154
|
+
protection_class: "4",
|
|
155
|
+
roof_material: "Corrugated Steel - Metal",
|
|
156
|
+
class_a_roof: "Yes",
|
|
157
|
+
foundation_type: "Crawlspace",
|
|
158
|
+
has_slab_foundation_plumbing: "No",
|
|
159
|
+
fire_protective_devices: ["Central"],
|
|
160
|
+
theft_protective_devices: ["Dead Bolt", "Direct"],
|
|
161
|
+
occupation: "writer",
|
|
162
|
+
claims: [
|
|
163
|
+
claims_history_helper('06/23/2022', 'Hurricane', 100),
|
|
164
|
+
claims_history_helper('11/1/2020', 'SlipFall', 100)
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {...sample_payload, ...override}
|
|
169
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const { knockout } = require('../src/knockouts/index.js');
|
|
2
|
+
const { stand_wf_knockout } = require('../src/knockouts/wf_knockouts')
|
|
3
|
+
describe("WF Knockouts", () =>
|
|
4
|
+
{
|
|
5
|
+
describe("wf_score_mean", () => {
|
|
6
|
+
|
|
7
|
+
test('rejects if mean > 0.7', () => {
|
|
8
|
+
let result = stand_wf_knockout(.7, .2);
|
|
9
|
+
let decision = result.underwriter_result;
|
|
10
|
+
|
|
11
|
+
expect(result.category).toBe("D");
|
|
12
|
+
expect(decision.decision).toBe("reject");
|
|
13
|
+
expect(decision.note).toBe("Stand WF mean must be less than or equal to 0.7");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('set as c and refer if arf >= 8', () => {
|
|
17
|
+
let result = stand_wf_knockout(.1, 8.0);
|
|
18
|
+
let decision = result.underwriter_result;
|
|
19
|
+
|
|
20
|
+
expect(result.category).toBe("C");
|
|
21
|
+
expect(decision.decision).toBe("refer");
|
|
22
|
+
expect(decision.note).toBe(null);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('set as c and acept if 5 < arf < 8', () => {
|
|
26
|
+
let result = stand_wf_knockout(.1, 5.0);
|
|
27
|
+
let decision = result.underwriter_result;
|
|
28
|
+
|
|
29
|
+
expect(result.category).toBe("C");
|
|
30
|
+
expect(decision.decision).toBe("accept");
|
|
31
|
+
expect(decision.note).toBe(null);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('set as c and accept if arf <8 and .2 < mean < .7', () => {
|
|
35
|
+
let result = stand_wf_knockout(.34, 4.2);
|
|
36
|
+
let decision = result.underwriter_result;
|
|
37
|
+
|
|
38
|
+
expect(result.category).toBe("C");
|
|
39
|
+
expect(decision.decision).toBe("accept");
|
|
40
|
+
expect(decision.note).toBe(null);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('set as B and accept if arf <5 and .1 < mean < .2', () => {
|
|
44
|
+
let result = stand_wf_knockout(.14, 4.3);
|
|
45
|
+
let decision = result.underwriter_result;
|
|
46
|
+
|
|
47
|
+
expect(result.category).toBe("B");
|
|
48
|
+
expect(decision.decision).toBe("accept");
|
|
49
|
+
expect(decision.note).toBe(null);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('set as A and accept if arf <5 and 0 < mean < .1', () => {
|
|
53
|
+
let result = stand_wf_knockout(.04, 4.3);
|
|
54
|
+
let decision = result.underwriter_result;
|
|
55
|
+
|
|
56
|
+
expect(result.category).toBe("A");
|
|
57
|
+
expect(decision.decision).toBe("accept");
|
|
58
|
+
expect(decision.note).toBe(null);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('negative mean used reject', () => {
|
|
62
|
+
let result = stand_wf_knockout(-.04, 4.3);
|
|
63
|
+
let decision = result.underwriter_result;
|
|
64
|
+
|
|
65
|
+
expect(result.category).toBe("");
|
|
66
|
+
expect(decision.decision).toBe("reject");
|
|
67
|
+
expect(decision.note).toBe("Mean and ARF must be positive numbers");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('negative arf used reject', () => {
|
|
71
|
+
let result = stand_wf_knockout(.04, -4.3);
|
|
72
|
+
let decision = result.underwriter_result;
|
|
73
|
+
|
|
74
|
+
expect(result.category).toBe("");
|
|
75
|
+
expect(decision.decision).toBe("reject");
|
|
76
|
+
expect(decision.note).toBe("Mean and ARF must be positive numbers");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('non number arf used reject', () => {
|
|
80
|
+
let result = stand_wf_knockout(.04, "hi");
|
|
81
|
+
let decision = result.underwriter_result;
|
|
82
|
+
|
|
83
|
+
expect(result.category).toBe("");
|
|
84
|
+
expect(decision.decision).toBe("reject");
|
|
85
|
+
expect(decision.note).toBe("ARF must be a number");
|
|
86
|
+
});
|
|
87
|
+
test('non number arf used reject', () => {
|
|
88
|
+
let result = stand_wf_knockout("hi", 4);
|
|
89
|
+
let decision = result.underwriter_result;
|
|
90
|
+
|
|
91
|
+
expect(result.category).toBe("");
|
|
92
|
+
expect(decision.decision).toBe("reject");
|
|
93
|
+
expect(decision.note).toBe("ARF must be a number");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("WF Variance Knockout", () => {
|
|
99
|
+
|
|
100
|
+
test('accept if variance <= 0.09', () => {
|
|
101
|
+
let decision = knockout('wf_variance', 0.08);
|
|
102
|
+
expect(decision.decision).toBe("accept");
|
|
103
|
+
expect(decision.note).toBe(null)
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('refer if variance > .09', () => {
|
|
107
|
+
let decision = knockout('wf_variance', 0.1);
|
|
108
|
+
expect(decision.decision).toBe("refer");
|
|
109
|
+
expect(decision.note).toBe(null)
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('rejects if value is not an number', () => {
|
|
113
|
+
let decision = knockout('wf_variance', "hi");
|
|
114
|
+
expect(decision.decision).toBe("reject");
|
|
115
|
+
expect(decision.note).toBe("Variance must be a number.")
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('rejects if value is negative', () => {
|
|
119
|
+
let decision = knockout('wf_variance', -3);
|
|
120
|
+
expect(decision.decision).toBe("reject");
|
|
121
|
+
expect(decision.note).toBe("Variance must be a positive number.")
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stand_socotra_policy_transformer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stands internal javscipt module for executing underwritting",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"build": "webpack"
|
|
9
|
+
},
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"jest": "^29.7.0",
|
|
14
|
+
"webpack": "^5.92.1",
|
|
15
|
+
"webpack-cli": "^5.1.4"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const {underwrite, knockout_names} = require('./underwriter')
|
|
2
|
+
const {policy_holder_payload, new_policy_payload, quote_to_retool_payload, retool_to_quote_updated} = require('./retool_utils/socotra_payloads')
|
|
3
|
+
module.exports = {underwrite, knockout_names, policy_holder_payload, new_policy_payload, quote_to_retool_payload, retool_to_quote_updated}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
function multi_family_unit_count(num_units){
|
|
2
|
+
|
|
3
|
+
if(!Number.isInteger(num_units)){
|
|
4
|
+
return {decision: "reject", note: "Number of units must be an integer"}
|
|
5
|
+
}
|
|
6
|
+
else if(num_units > 2){
|
|
7
|
+
return {decision: "reject", note: "We only allow homes with two units or less."}
|
|
8
|
+
} else if (num_units <= 0){
|
|
9
|
+
return {decision: "reject", note: "Number of units must be greater than 0"}
|
|
10
|
+
}
|
|
11
|
+
else{
|
|
12
|
+
return {decision: "accept", note: null}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function primary_dwelling_is_insured(is_insured_string){
|
|
17
|
+
|
|
18
|
+
if(is_insured_string == 'No'){
|
|
19
|
+
return {decision: "refer", note: null}
|
|
20
|
+
} else if(is_insured_string == 'Yes'){
|
|
21
|
+
return {decision: "accept", note: null}
|
|
22
|
+
} else {
|
|
23
|
+
return {decision: "reject", note: "Primary dwelling insured value must be in ['yes', 'no']"}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function full_replacement_value(replacement_value){
|
|
28
|
+
if(!Number.isInteger(replacement_value)){
|
|
29
|
+
return {decision: "reject", note: "Full replacement value must be an integer"}
|
|
30
|
+
}
|
|
31
|
+
else if(replacement_value > 6_000_000){
|
|
32
|
+
return {decision: "refer", note: null}
|
|
33
|
+
} else if (replacement_value < 2_000_000){
|
|
34
|
+
return {decision: "reject", note: "We only insure homes greater than $1M"}
|
|
35
|
+
}
|
|
36
|
+
else{
|
|
37
|
+
return {decision: "accept", note: null}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function prior_carrier(carrier_string){
|
|
42
|
+
|
|
43
|
+
if(carrier_string === ""){
|
|
44
|
+
return {decision: "refer", note: null}
|
|
45
|
+
} else if(typeof carrier_string == 'string'){
|
|
46
|
+
return {decision: "accept", note: null}
|
|
47
|
+
} else {
|
|
48
|
+
return {decision: "reject", note: "Prior Carrier Must be a string"}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function occupation(occ){
|
|
53
|
+
if(typeof occ != 'string'){
|
|
54
|
+
return {decision: "reject", note: "Occupation must be a string"}
|
|
55
|
+
} else if (['Professional Athlete', 'Entertainer', 'Journalist',' Politician'].includes(occ)){
|
|
56
|
+
return {decision: "refer", note: null}
|
|
57
|
+
} else {
|
|
58
|
+
return {decision: "accept", note: null}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
multi_family_unit_count, primary_dwelling_is_insured, full_replacement_value, prior_carrier,
|
|
65
|
+
occupation
|
|
66
|
+
}
|