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.
Files changed (43) hide show
  1. package/__tests__/__utils__/load_payload.js +16 -0
  2. package/__tests__/__utils__/payloads/minimal_change_base.json +574 -0
  3. package/__tests__/__utils__/payloads/minimal_change_base2.json +574 -0
  4. package/__tests__/__utils__/payloads/minimal_change_resulting_socotra.json +17 -0
  5. package/__tests__/__utils__/payloads/minimal_change_resulting_socotra2.json +27 -0
  6. package/__tests__/__utils__/payloads/minimal_change_retool.json +37 -0
  7. package/__tests__/__utils__/payloads/minimal_change_retool2.json +27 -0
  8. package/__tests__/__utils__/payloads/sample_minimal_retool.json +33 -0
  9. package/__tests__/__utils__/payloads/sample_minimal_socotra_payload.json +116 -0
  10. package/__tests__/__utils__/payloads/sample_new_policy_holder.json +17 -0
  11. package/__tests__/__utils__/payloads/sample_new_quote.json +210 -0
  12. package/__tests__/__utils__/payloads/sample_new_quote_retool.json +93 -0
  13. package/__tests__/__utils__/payloads/sample_retool.json +160 -0
  14. package/__tests__/__utils__/payloads/sample_retool_converted_quote.json +54 -0
  15. package/__tests__/__utils__/payloads/sample_retool_socotra_subset.json +127 -0
  16. package/__tests__/__utils__/payloads/sample_socotra_quote.json +773 -0
  17. package/__tests__/__utils__/payloads/sample_update_quote.json +18 -0
  18. package/__tests__/basic_knockout.test.js +113 -0
  19. package/__tests__/claims_history_knockout.test.js +56 -0
  20. package/__tests__/exterior_knockout.test.js +192 -0
  21. package/__tests__/helpers/index.js +10 -0
  22. package/__tests__/home_owner_knockouts.js +260 -0
  23. package/__tests__/interior_knockout.test.js +321 -0
  24. package/__tests__/rate_call_knockouts.test.js +347 -0
  25. package/__tests__/retool_utils/socotra_payload.test.js +168 -0
  26. package/__tests__/retool_utils/socotra_structure_helper.test.js +129 -0
  27. package/__tests__/underwriter.test.js +169 -0
  28. package/__tests__/wf_knockout.test.js +124 -0
  29. package/package.json +17 -0
  30. package/src/index.js +3 -0
  31. package/src/knockouts/basic_knockouts.js +66 -0
  32. package/src/knockouts/claims_history_knockout.js +24 -0
  33. package/src/knockouts/exterior_knockouts.js +97 -0
  34. package/src/knockouts/home_owner_knockouts.js +118 -0
  35. package/src/knockouts/index.js +83 -0
  36. package/src/knockouts/interior_knockouts.js +149 -0
  37. package/src/knockouts/rate_call_knockouts.js +155 -0
  38. package/src/knockouts/wf_knockouts.js +66 -0
  39. package/src/retool_to_socotra.js +18 -0
  40. package/src/retool_utils/socotra_payloads.js +316 -0
  41. package/src/retool_utils/socotra_structure_helper.js +223 -0
  42. package/src/underwriter.js +86 -0
  43. package/webpack.config.js +25 -0
@@ -0,0 +1,24 @@
1
+ function check_claims_history(history){
2
+ if (!history){
3
+ return {decision: 'reject', note: "For Claims use empty instead of null"}
4
+ }
5
+
6
+ let current_date = new Date();
7
+ let date_five_years_ago = new Date();
8
+ let date_three_years_ago = new Date();
9
+ date_five_years_ago.setFullYear(current_date.getFullYear() - 5);
10
+ date_three_years_ago.setFullYear(current_date.getFullYear() - 3);
11
+
12
+ let claims_in_last_five_years = history.filter(claim => claim['date'] > date_five_years_ago)
13
+ let claims_in_last_three_years = history.filter(claim => claim['date'] > date_three_years_ago)
14
+
15
+ if(claims_in_last_five_years.length > 2){
16
+ return {decision: 'reject', note: "To many claims in the past 5 years"}
17
+ } else if (claims_in_last_three_years.length > 1){
18
+ return {decision: 'reject', note: "To many claims in the past 3 years"}
19
+ } else {
20
+ return {decision: "accept", note: null}
21
+ }
22
+ }
23
+
24
+ module.exports = {check_claims_history}
@@ -0,0 +1,97 @@
1
+ function flood_score(score){
2
+ if(!Number.isFinite(score) ) {
3
+ return {decision: "reject", note: "Flood score must be a number."}
4
+ } else if(score < 0){
5
+ return {decision: "reject", note: "Flood score must be a positive number."}
6
+ }
7
+
8
+ if(score < 9){
9
+ return {decision: "accept", note: null}
10
+ } else {
11
+ return {decision: "reject", note: "Flood risk is too high"}
12
+ }
13
+ }
14
+
15
+ function distance_to_coast(distance){
16
+ if(!Number.isFinite(distance) ) {
17
+ return {decision: "reject", note: "Distance to coast must be a number."}
18
+ } else if(distance < 0){
19
+ return {decision: "reject", note: "Distance to coast must be a positive number."}
20
+ }
21
+
22
+ if(distance > 0.19){
23
+ return {decision: "accept", note: null}
24
+ } else {
25
+ return {decision: "refer", note: null}
26
+ }
27
+ }
28
+
29
+ function pool_features(feature_list){
30
+ if(!Array.isArray(feature_list)){
31
+ return {decision: "reject", note: "Pool features must be a list."}
32
+ }
33
+
34
+ let has_drowning_mitigation = feature_list.includes("Inground") && (
35
+ feature_list.includes("Electric retractable safety cover")
36
+ || feature_list.includes("Fenced with self-locking gate")
37
+ )
38
+
39
+ has_drowning_mitigation = has_drowning_mitigation || (
40
+ feature_list.includes("Above Ground") && feature_list.includes("Retractable/removable ladder"))
41
+
42
+
43
+ if(feature_list.includes('Diving Board') || feature_list.includes('Slide')){
44
+ return {decision: "refer", note: null}
45
+ } else if(has_drowning_mitigation) {
46
+ return {decision: "accept", note: null}
47
+ } else {
48
+ return {decision: "refer", note: null}
49
+ }
50
+ }
51
+
52
+ function farming_type(type){
53
+ let type_enum = ["Farming activities produce income",
54
+ "Incidental or hobby farming", "Vineyard", "None"]
55
+ if(typeof type != 'string') {
56
+ return {decision: "reject", note: "Farming type must be a string"}
57
+ } else if(!type_enum.includes(type)){
58
+ return {decision: "reject", note: `Farming type must be in [${type_enum}]`}
59
+ }
60
+
61
+ if(type == 'Vineyard'){
62
+ return {decision: "refer", note: null}
63
+ } else if (type == 'Farming activities produce income'){
64
+ return {decision: "reject", note: "Cannot insure commercial farms"}
65
+ } else {
66
+ return {decision: "accept", note: null}
67
+ }
68
+ }
69
+
70
+ function has_attractive_nuisance(has_nuisance){
71
+ if(has_nuisance == "Yes"){
72
+ return {decision: "refer", note: null}
73
+ } else if(has_nuisance == "No"){
74
+ return {decision: "accept", note: null}
75
+ }
76
+ return {decision: "reject", note: "Has attractive nuisance must be Yes or No."}
77
+ }
78
+
79
+ function degree_of_slope(slope_angle){
80
+ if(!Number.isFinite(slope_angle) ) {
81
+ return {decision: "reject", note: "Degree of slope must be a number."}
82
+ } else if(slope_angle < 0){
83
+ return {decision: "reject", note: "Degree of slope must be a positive number."}
84
+ }
85
+
86
+ if(slope_angle > 30){
87
+ return {decision: "reject", note: "Slope greater than 30 degrees"}
88
+ } else {
89
+ return {decision: "accept", note: null}
90
+ }
91
+ }
92
+
93
+
94
+
95
+ module.exports = {
96
+ distance_to_coast, pool_features, farming_type, has_attractive_nuisance, degree_of_slope, flood_score
97
+ }
@@ -0,0 +1,118 @@
1
+ function number_of_dogs(num_dog){
2
+ if(!Number.isFinite(num_dog) ) {
3
+ return {decision: "reject", note: "Number of dogs must be a number."}
4
+ } else if(num_dog < 0){
5
+ return {decision: "reject", note: "Number of dogs must be a positive number."}
6
+ }
7
+
8
+ if(num_dog < 4){
9
+ return {decision: "accept", note: null}
10
+ } else {
11
+ return {decision: "reject", note: "Owner has 4 or more dogs"}
12
+ }
13
+ }
14
+
15
+ function dog_history(has_history){
16
+ if(has_history == "Yes"){
17
+ return {decision: "reject", note: "Has a dog with a history of aggressive behavior"}
18
+ } else if(has_history == "No"){
19
+ return {decision: "accept", note: null}
20
+ }
21
+ return {decision: "reject", note: "Dog has history must be Yes or No."}
22
+ }
23
+
24
+ function dog_breeds(dogs){
25
+ if(!Array.isArray(dogs)){
26
+ return {decision: "reject", note: "Dog breed must be a list"}
27
+ }
28
+
29
+ if(dogs.every(value => value === "non-guard dog of other breeds")){
30
+ return {decision: "accept", note: null}
31
+ } else {
32
+ return {decision: "reject", note: "Dog of given breed not allowed"}
33
+ }
34
+ }
35
+
36
+ function exotic_pets(has_exotic_pets){
37
+ if(has_exotic_pets == "Yes"){
38
+ return {decision: "reject", note: "Has an exotic pet"}
39
+ } else if(has_exotic_pets == "No"){
40
+ return {decision: "accept", note: null}
41
+ }
42
+ return {decision: "reject", note: "Has exotic pet must be Yes or No."}
43
+ }
44
+
45
+ function number_of_mortgages(num_mortgages){
46
+ if(!Number.isFinite(num_mortgages) ) {
47
+ return {decision: "reject", note: "Number of mortgages must be a number."}
48
+ } else if(num_mortgages < 0){
49
+ return {decision: "reject", note: "Number of mortgages must be a positive number."}
50
+ }
51
+
52
+ if(num_mortgages < 2){
53
+ return {decision: "accept", note: null}
54
+ } else {
55
+ return {decision: "refer", note: null}
56
+ }
57
+ }
58
+
59
+ function number_of_bankruptcies_judgements_or_liens(num_bankruptcies){
60
+ if(!Number.isFinite(num_bankruptcies) ) {
61
+ return {decision: "reject", note: "Number of bankruptcies, judgements, or liens must be a number"}
62
+ } else if(num_bankruptcies < 0){
63
+ return {decision: "reject", note: "Number of bankruptcies, judgements, or liens must be a positive number"}
64
+ }
65
+
66
+ if(num_bankruptcies === 0){
67
+ return {decision: "accept", note: null}
68
+ } else {
69
+ return {decision: "reject", note: "Has bankruptcies, judgements, or liens"}
70
+ }
71
+ }
72
+
73
+ function home_business_type(type) {
74
+ let type_enum = ["yes - day care", "yes - other", "no"]
75
+ if (typeof type != 'string') {
76
+ return {decision: "reject", note: "Home business type must be a string"}
77
+ } else if (!type_enum.includes(type)) {
78
+ return {decision: "reject", note: "Home business type must be in [yes - day care, yes - other, no]"}
79
+ }
80
+
81
+ if (type == "no") {
82
+ return {decision: "accept", note: null}
83
+ } else if (type == 'yes - day care') {
84
+ return {decision: "reject", note: "Cannot insure daycare"}
85
+ } else {
86
+ return {decision: "refer", note: null}
87
+ }
88
+ }
89
+
90
+ function home_rental_type(type){
91
+ let type_enum = ["short-term rentals less than 6 months of the year", "short term rentals more than 6 months of the year", "timeshare", "no"]
92
+ if (typeof type != 'string') {
93
+ return {decision: "reject", note: "Home rental type must be a string"}
94
+ } else if (!type_enum.includes(type)) {
95
+ return {decision: "reject", note: "Home rental type must be in [short-term rentals less than 6 months of the year, short term rentals more than 6 months of the year, timeshare, no]"}
96
+ }
97
+
98
+ if (type == "no") {
99
+ return {decision: "accept", note: null}
100
+ } else if (["short-term rentals less than 6 months of the year", "timeshare"].includes(type)) {
101
+ return {decision: "refer", note: null}
102
+ } else {
103
+ return {decision: "reject", note: "Cannot insure rentals longer than 6 months"}
104
+ }
105
+ }
106
+
107
+ function same_address(applicant_address, co_applicant_address){
108
+ if (applicant_address === co_applicant_address){
109
+ return {decision: "accept", note: null}
110
+ } else {
111
+ return {decision: "reject", note: "Co-Applicant must have same address as Applicant."}
112
+ }
113
+ }
114
+
115
+ module.exports = {
116
+ number_of_dogs, dog_history, dog_breeds, exotic_pets, number_of_mortgages,
117
+ number_of_bankruptcies_judgements_or_liens, home_business_type, home_rental_type, same_address
118
+ }
@@ -0,0 +1,83 @@
1
+ const {
2
+ multi_family_unit_count,
3
+ primary_dwelling_is_insured,
4
+ full_replacement_value,
5
+ prior_carrier, occupation
6
+ } = require('./basic_knockouts')
7
+
8
+ const { wf_variance, stand_wf_knockout } = require('./wf_knockouts');
9
+ const { distance_to_coast, pool_features, farming_type, has_attractive_nuisance,
10
+ degree_of_slope, flood_score} = require('./exterior_knockouts')
11
+ const {number_of_dogs, dog_history, dog_breeds, exotic_pets, number_of_mortgages,
12
+ number_of_bankruptcies_judgements_or_liens, home_business_type, home_rental_type, same_address
13
+ } = require('./home_owner_knockouts')
14
+ const {is_vacant, under_renovation, plumbing_last_update_year,
15
+ water_heater_last_update_year, electrical_last_update_year, home_heating_last_update_year,
16
+ heating_source, has_underground_fuel_tank, has_steel_braided_hose, has_water_shutoff, has_circuit_breaker
17
+ } = require('./interior_knockouts')
18
+
19
+ const {
20
+ construction_type, siding_material, protection_class, roof_material, foundation_type,
21
+ has_slab_foundation_plumbing, fire_protective_devices, theft_protective_devices, has_class_a_roof
22
+ } = require('./rate_call_knockouts')
23
+
24
+ const {check_claims_history} = require('./claims_history_knockout')
25
+
26
+ const questions = {
27
+ "multi_family_unit_count": multi_family_unit_count,
28
+ "primary_dwelling_insured": primary_dwelling_is_insured,
29
+ "full_replacement_value": full_replacement_value,
30
+ "prior_carrier": prior_carrier,
31
+ "wf_variance": wf_variance,
32
+ "flood_score": flood_score,
33
+ "distance_to_coast": distance_to_coast,
34
+ "pool_features": pool_features,
35
+ "number_of_dogs": number_of_dogs,
36
+ "dog_history": dog_history,
37
+ "dog_breeds": dog_breeds,
38
+ "exotic_pets": exotic_pets,
39
+ "farming_type": farming_type,
40
+ "number_of_mortgages": number_of_mortgages,
41
+ "number_of_bankruptcies_judgements_or_liens": number_of_bankruptcies_judgements_or_liens,
42
+ "is_vacant": is_vacant,
43
+ "under_renovation": under_renovation,
44
+ "plumbing_last_update_year": plumbing_last_update_year,
45
+ "water_heater_last_update_year": water_heater_last_update_year,
46
+ "electrical_last_update_year": electrical_last_update_year,
47
+ "home_heating_last_update_year": home_heating_last_update_year,
48
+ "heating_source": heating_source,
49
+ "has_underground_fuel_tank": has_underground_fuel_tank,
50
+ "has_steel_braided_hose": has_steel_braided_hose,
51
+ "has_water_shutoff": has_water_shutoff,
52
+ "home_business_type": home_business_type,
53
+ "has_attractive_nuisance": has_attractive_nuisance,
54
+ "home_rental_type": home_rental_type,
55
+ "degree_of_slope": degree_of_slope,
56
+ "construction_type": construction_type,
57
+ "siding_material": siding_material,
58
+ "protection_class": protection_class,
59
+ "roof_material": roof_material,
60
+ "foundation_type": foundation_type,
61
+ "has_slab_foundation_plumbing": has_slab_foundation_plumbing,
62
+ "fire_protective_devices": fire_protective_devices,
63
+ "theft_protective_devices": theft_protective_devices,
64
+ "occupation": occupation
65
+
66
+ }
67
+
68
+ function knockout(question_name, answer){
69
+ if (questions.hasOwnProperty(question_name)){
70
+ return questions[question_name](answer)
71
+ } else {
72
+ return {decision: "question_not_found", note: null}
73
+ }
74
+ }
75
+
76
+ function knockout_names() {
77
+ return Object.keys(questions)
78
+ }
79
+
80
+ module.exports = {
81
+ knockout, has_circuit_breaker, same_address, knockout_names, stand_wf_knockout, has_class_a_roof,
82
+ check_claims_history
83
+ }
@@ -0,0 +1,149 @@
1
+ function is_vacant(vacant){
2
+ if(vacant === 'No'){
3
+ return {decision: "accept", note: null}
4
+ } else if (vacant === 'Yes'){
5
+ return {decision: "reject", note: "Home is vacant"}
6
+ } else {
7
+ return {decision: "reject", note: "Vacant must be yes or no"}
8
+ }
9
+ }
10
+
11
+ function under_renovation(renovation){
12
+ if(renovation === 'No'){
13
+ return {decision: "accept", note: null}
14
+ } else if (renovation === 'Yes'){
15
+ return {decision: "reject", note: "Home is under renovation"}
16
+ } else {
17
+ return {decision: "reject", note: "Under renovation must be yes or no"}
18
+ }
19
+ }
20
+
21
+ function plumbing_last_update_year(year_updated){
22
+ if(!Number.isInteger(year_updated)){
23
+ return {decision: "reject", note: "Plumbing year must be an integer"}
24
+ }
25
+ if(year_updated < 1600 || year_updated > 3000){
26
+ return {decision: "reject", note: "Plumbing input should be the year it was last updated not the age"}
27
+ }
28
+
29
+ year_threshold = new Date().getFullYear() - 30
30
+ if(year_updated >= year_threshold){
31
+ return {decision: "accept", note: null}
32
+ } else {
33
+ return {decision: "reject", note: "Plumbing must have been updated in the last 30 years"}
34
+ }
35
+ }
36
+
37
+ function water_heater_last_update_year(year_updated){
38
+ if(!Number.isInteger(year_updated)){
39
+ return {decision: "reject", note: "Water heater year must be an integer"}
40
+ }
41
+ if(year_updated < 1600 || year_updated > 3000){
42
+ return {decision: "reject", note: "Water heater input should be the year it was last updated not the age"}
43
+ }
44
+
45
+ year_threshold = new Date().getFullYear() - 10
46
+ if(year_updated >= year_threshold){
47
+ return {decision: "accept", note: null}
48
+ } else {
49
+ return {decision: "reject", note: "Water heater must have been updated in the last 10 years"}
50
+ }
51
+ }
52
+
53
+ function electrical_last_update_year(year_updated){
54
+ if(!Number.isInteger(year_updated)){
55
+ return {decision: "reject", note: "Electrical year must be an integer"}
56
+ }
57
+ if(year_updated < 1600 || year_updated > 3000){
58
+ return {decision: "reject", note: "Electrical input should be the year it was last updated not the age"}
59
+ }
60
+
61
+ year_threshold = new Date().getFullYear() - 30
62
+ if(year_updated >= year_threshold){
63
+ return {decision: "accept", note: null}
64
+ } else {
65
+ return {decision: "reject", note: "Electrical must have been updated in the last 30 years"}
66
+ }
67
+ }
68
+
69
+ function home_heating_last_update_year(year_updated){
70
+ if(!Number.isInteger(year_updated)){
71
+ return {decision: "reject", note: "Home heating year must be an integer"}
72
+ }
73
+ if(year_updated < 1600 || year_updated > 3000){
74
+ return {decision: "reject", note: "Home heating input should be the year it was last updated not the age"}
75
+ }
76
+
77
+ year_threshold = new Date().getFullYear() - 30
78
+ if(year_updated >= year_threshold){
79
+ return {decision: "accept", note: null}
80
+ } else {
81
+ return {decision: "reject", note: "Home heating must have been updated in the last 30 years"}
82
+ }
83
+ }
84
+
85
+ function heating_source(heating_type) {
86
+ heating_type_enum = [
87
+ "central gas heat", "thermostatically controlled electrical heat",
88
+ "wood", "coal", "pellet stove"
89
+ ]
90
+
91
+ if(typeof heating_type !== 'string'){
92
+ return {decision: "reject", note: "Home heating year must be a string"}
93
+
94
+ } else if(!heating_type_enum.includes(heating_type)){
95
+ return {decision: "reject", note: "Heat source must be in [central gas heat, thermostatically controlled electrical heat, wood, coal, pellet stove]"}
96
+ }
97
+
98
+ if(!["wood", "coal", "pellet stove"].includes(heating_type)){
99
+ return {decision: "accept", note: null}
100
+ } else {
101
+ return {decision: "refer", note: null}
102
+ }
103
+ }
104
+
105
+ function has_underground_fuel_tank(has_tank){
106
+ if(has_tank === 'No'){
107
+ return {decision: "accept", note: null}
108
+ } else if (has_tank === 'Yes'){
109
+ return {decision: "reject", note: "Home has underground fuel tank"}
110
+ } else {
111
+ return {decision: "reject", note: "Underground fuel tank must be yes or no"}
112
+ }
113
+ }
114
+
115
+ function has_steel_braided_hose(has_steel){
116
+ if(has_steel === 'Yes'){
117
+ return {decision: "accept", note: null}
118
+ } else if (has_steel === 'No'){
119
+ return {decision: "refer", note: null}
120
+ } else {
121
+ return {decision: "reject", note: "Steel braided hose must be yes or no"}
122
+ }
123
+ }
124
+
125
+ function has_water_shutoff(has_shutoff){
126
+ if(has_shutoff === 'Yes'){
127
+ return {decision: "accept", note: null}
128
+ } else if (has_shutoff === 'No'){
129
+ return {decision: "refer", note: null}
130
+ } else {
131
+ return {decision: "reject", note: "Water shutoff must be yes or no"}
132
+ }
133
+ }
134
+
135
+ function has_circuit_breaker(has_breaker, year_built){
136
+ if ((has_breaker === 'No') || (year_built >= 1980)){
137
+ return {decision: "accept", note: null}
138
+ } else if(has_breaker === 'Yes'){
139
+ return {decision: "reject", note: "Home built before 1980 cannot have circuit breakers"}
140
+ } else {
141
+ return {decision: "reject", note: "Circuit breaker must be yes or no"}
142
+ }
143
+ }
144
+
145
+ module.exports = {
146
+ is_vacant, under_renovation, plumbing_last_update_year, water_heater_last_update_year,
147
+ electrical_last_update_year, home_heating_last_update_year, heating_source, has_underground_fuel_tank,
148
+ has_steel_braided_hose, has_water_shutoff, has_circuit_breaker
149
+ }
@@ -0,0 +1,155 @@
1
+ function construction_type(type){
2
+ let type_enum = ["Concrete", "Frame", "Log", "Masonry", "Mobile / Manufactured", "Steel", "Modular"]
3
+ if(typeof type != 'string') {
4
+ return {decision: "reject", note: "Construction type must be a string"}
5
+ } else if(!type_enum.includes(type)){
6
+ return {decision: "reject", note: "Construction type must be one of [Concrete, Frame, Log, Masonry, Mobile / Manufactured, Steel, Modular]"}
7
+ }
8
+
9
+ if(["Concrete", "Frame","Masonry", "Steel"].includes(type)){
10
+ return {decision: "accept", note: null}
11
+ } else {
12
+ return {decision: "reject", note: "Construction type not supported"}
13
+ }
14
+ }
15
+
16
+ function siding_material(type){
17
+ let material_enum = ["Adobe", "Aluminum / Steel", "Asbestos", "Brick / Masonry Veneer",
18
+ "Brick / Stone - Solid", "Cement Fiber", "Clapboard", "Exterior Insulation Finishing System (EIFS)", "Log",
19
+ "Stone Veneer", "Stucco", "Vinyl", "Wood", "Other - above descriptions do not apply"]
20
+ if(typeof type != 'string') {
21
+ return {decision: "reject", note: "Siding material must be a string"}
22
+ } else if(!material_enum.includes(type)){
23
+ return {decision: "reject", note: "Siding material must be one of [Adobe, Aluminum / Steel, Asbestos, Brick / Masonry Veneer, Brick / Stone - Solid, Cement Fiber, Clapboard, Exterior Insulation Finishing System (EIFS), Log, Stone Veneer, Stucco, Vinyl, Wood, Other - above descriptions do not apply]"}
24
+ }
25
+
26
+ if(["Log", "Asbestos", "Exterior Insulation Finishing System (EIFS)"].includes(type)){
27
+ return {decision: "reject", note: "Siding material not supported"}
28
+ } else if(type === 'Wood'){
29
+
30
+ } else {
31
+ return {decision: "accept", note: null}
32
+ }
33
+ }
34
+
35
+ function protection_class(protection){
36
+ let protection_types = ["1","2","3","4","5","6", "7", "8", "8B", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9", "1X", "2X", "3X", "4X", "5X", "6X", "7X", "8X", "9", "10", "10W"]
37
+
38
+ if(typeof protection != 'string') {
39
+ return {decision: "reject", note: "Protection class must be a string"}
40
+ } else if(!protection_types.includes(protection)){
41
+ return {decision: "reject", note: `Protection class must be one of ${protection_types}`}
42
+ }
43
+
44
+ if(["9", "10"].includes(protection)){
45
+ return {decision: "reject", note: "Protection class not supported"}
46
+ }
47
+
48
+ return {decision: "accept", note: null}
49
+ }
50
+
51
+ function roof_material(material){
52
+ let material_enum = ["Architecture Shingles", "Asphalt Fiberglass Composite", "Clay-Tile-Slate",
53
+ "Concrete Tile", "Corrugated Steel - Metal", "Flat Foam Composite"," Flat Membrane",
54
+ "Flat Tar Gravel", "Other", "Wood Shake", "Wood Shingle"]
55
+
56
+ if(typeof material != 'string') {
57
+ return {decision: "reject", note: "Roof material must be a string"}
58
+ } else if(!material_enum.includes(material)){
59
+ return {decision: "reject", note: `Roof material must be one of ${material_enum}`}
60
+ }
61
+
62
+ if(["Wood Shake", "Wood Shingle",].includes(material)){
63
+ return {decision: "reject", note: "Roof material not supported"}
64
+ } else {
65
+ return {decision: "accept", note: null}
66
+ }
67
+ }
68
+
69
+ function has_class_a_roof(is_class_a, wildfire_category){
70
+ if(typeof is_class_a != 'string') {
71
+ return {decision: "reject", note: "Has class a roof must be a string"}
72
+ } else if (!["Yes", "No"].includes(is_class_a)){
73
+ return{decision: "reject", note: "Has class a roof must be Yes or No"}
74
+ }
75
+
76
+ if(is_class_a === "Yes" || ['A', 'B'].includes(wildfire_category)){
77
+ return {decision: "accept", note: null}
78
+ } else{
79
+ return {decision: "reject", note: "Homes in this wildfire category must have class a roofs"}
80
+ }
81
+ }
82
+
83
+ function foundation_type(type){
84
+ let type_enum = ["Basement Finished", "Basement Partially Finished", "Basement Unfinished",
85
+ "Crawlspace", "Other", "Piers", "Pilings", "Stilts", "Slab"]
86
+ if(typeof type != 'string') {
87
+ return {decision: "reject", note: "Foundation type must be a string"}
88
+ } else if(!type_enum.includes(type)){
89
+ return {decision: "reject", note: `Foundation type must be one of ${type_enum}`}
90
+ }
91
+
92
+ if(type == 'Other'){
93
+ return {decision: "refer", note: null}
94
+ } else if(["Piers", "Pilings", "Stilts"].includes(type)){
95
+ return {decision: "reject", note: "Foundation type not supported"}
96
+ } else {
97
+ return {decision: "accept", note: null}
98
+ }
99
+
100
+
101
+ }
102
+
103
+ function has_slab_foundation_plumbing(has_bad_plumbing){
104
+ if(typeof has_bad_plumbing != 'string') {
105
+ return {decision: "reject", note: "Has slab foundation plumbing must be a string"}
106
+ } else if (!["Yes", "No"].includes(has_bad_plumbing)){
107
+ return{decision: "reject", note: "Has slab foundation plumbing must be Yes or No"}
108
+ }
109
+
110
+ if(has_bad_plumbing === "Yes"){
111
+ return {decision: "reject", note: "Homes with slab foundation plumbing are not allowed"}
112
+ } else{
113
+ return {decision: "accept", note: null}
114
+ }
115
+ }
116
+
117
+ function fire_protective_devices(devices_list){
118
+ if(!Array.isArray(devices_list)){
119
+ return {decision: "reject", note: "Fire protective devices must be a list"}
120
+ }
121
+
122
+ let device_types = ['Local', 'Central', 'Direct', "Sprinkler", "Fire Ext", "Smoke", "No"]
123
+ if(!devices_list.every(device => device_types.includes(device))){
124
+ return {decision: "reject", note: `Unrecognized fire device type. Valid devices: ${device_types}`}
125
+ }
126
+
127
+ if(devices_list.includes('Direct') || devices_list.includes('Central')){
128
+ return {decision: "accept", note: null}
129
+ } else {
130
+ return {decision: "reject", note: "Insufficient fire devices"}
131
+ }
132
+ }
133
+
134
+ function theft_protective_devices(devices_list) {
135
+ if(!Array.isArray(devices_list)){
136
+ return {decision: "reject", note: "Theft protective devices must be a list"}
137
+ }
138
+
139
+ device_types = ["Dead Bolt", "Local", "Central", "Direct", "24 Hour Security Guard", "No"]
140
+ if(!devices_list.every(device => device_types.includes(device))){
141
+ return {decision: "reject", note: `Unrecognized theft device type. Valid devices: ${device_types}`}
142
+ }
143
+
144
+ if(devices_list.includes('Central') || devices_list.includes('Direct') ||
145
+ devices_list.includes("24 Hour Security Guard")){
146
+ return {decision: "accept", note: null}
147
+ } else {
148
+ return {decision: "reject", note: "Insufficient theft device"}
149
+ }
150
+ }
151
+
152
+ module.exports = {
153
+ construction_type, siding_material, protection_class, roof_material, has_class_a_roof, foundation_type,
154
+ has_slab_foundation_plumbing, fire_protective_devices, theft_protective_devices
155
+ }