-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrisk.js
More file actions
185 lines (175 loc) · 12.1 KB
/
Copy pathrisk.js
File metadata and controls
185 lines (175 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
(function registerRiskAssessment(root, factory) {
const api = factory();
if (typeof module === "object" && module.exports) module.exports = api;
root.SimplinsuredRisk = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function createRiskAssessment() {
function assessGeneric(answers) {
const answerValues = Array.isArray(answers) ? answers : Object.values(answers || {});
const protectionAnswer = String(answerValues[2] || "").toLowerCase();
const allAnswers = answerValues
.join(" ")
.toLowerCase()
.replace(
/\b(?:no|never had|without any) (?:previous |recent )?(?:claim|claims|loss|losses|breach|breaches|fire|fires|accident|accidents|recall|recalls|bad debt|bad debts)\b/g,
"",
)
.replace(/\bno (?:ransomware|data breach|regulatory inquiry|known dispute|overdue accounts)\b/g, "");
const riskSignals = [
/\bprevious (?:claim|loss|breach|fire|accident|recall|bad debt)\b/,
/\brecent (?:claim|loss|breach|fire|accident|recall|bad debt)\b/,
/\b(?:ransomware|data breach|regulatory inquiry|known dispute|overdue accounts)\b/,
/\b(?:flammable|explosive|fireworks|hazardous chemicals?)\b/,
/\b(?:flood[- ]prone|theft[- ]prone|unlicensed|no approvals?)\b/,
];
const weakControls =
/^(?:none|nothing|not sure|no)$/i.test(protectionAnswer.trim()) ||
/\bno (?:backup|backups|mfa|security|alarm|alarms|sprinkler|sprinklers|maintenance|controls|training)\b/.test(
protectionAnswer,
);
const matchedSignals = riskSignals.filter((pattern) => pattern.test(allAnswers)).length;
return {
needsUnderwriter: weakControls || matchedSignals > 0,
scoreAdjustment: weakControls || matchedSignals > 0 ? 3 : 0,
reasons: weakControls
? ["The current safeguards need a closer review."]
: matchedSignals > 0
? ["One of the exposures mentioned needs specialist review."]
: [],
reason: weakControls
? "The current safeguards need a closer review."
: "One of the exposures you mentioned needs a specialist review.",
};
}
function id(answers, key) {
return answers?.[key]?.canonicalId || "";
}
function buildResult(rules) {
const reasons = [];
let scoreAdjustment = 0;
let needsUnderwriter = false;
rules.forEach((rule) => {
if (!rule.when) return;
scoreAdjustment += rule.points || 0;
if (rule.refer) needsUnderwriter = true;
if (rule.reason) reasons.push(rule.reason);
});
return {
scoreAdjustment,
needsUnderwriter,
reasons,
reason: reasons[0] || "",
};
}
const lineRules = {
cyber({ answers }) {
return buildResult([
{ when: id(answers, "cyberControls") === "weak", refer: true, points: 5, reason: "Cyber controls are not mature enough for automated binding." },
{ when: id(answers, "paymentData") === "stored" && ["limited", "weak"].includes(id(answers, "cyberControls")), refer: true, points: 4, reason: "Direct payment-data handling needs a final cyber check." },
{ when: id(answers, "digitalRecords") === "over_1m", points: 3, reason: "Very large digital-record volume increases breach-notification exposure." },
{ when: ["multiple", "known_issue"].includes(id(answers, "claimsHistory")), refer: true, points: 5, reason: "Prior or known cyber incidents require manual terms." },
]);
},
professional({ answers }) {
return buildResult([
{ when: id(answers, "piBasis") === "gap", refer: true, points: 5, reason: "A possible retroactive-cover gap must be reviewed before quoting." },
{ when: id(answers, "retroactiveCover") === "unknown", refer: true, points: 4, reason: "Unknown retroactive-date position can change whether prior acts are covered." },
{ when: id(answers, "contractSize") === "large" && id(answers, "geographicReach") === "global", points: 3, reason: "Large global contracts increase professional-liability severity." },
{ when: ["multiple", "known_issue"].includes(id(answers, "claimsHistory")), refer: true, points: 5, reason: "Prior complaints or known disputes need a final check." },
]);
},
event: ({ answers }) =>
buildResult([
{ when: id(answers, "hazardousFeatures") === "high", refer: true, points: 5, reason: "Fireworks, stunts, aviation, or hazardous features need specialist event terms." },
{ when: id(answers, "eventContinuity") === "complex", refer: true, points: 4, reason: "Complex cancellation or crowd-control exposure needs a final event check." },
{ when: id(answers, "eventSize") === "large" && id(answers, "eventContinuity") !== "low", points: 3, reason: "Large attendance combined with continuity exposure increases severity." },
]),
product: ({ answers, locationComparison }) =>
buildResult([
{ when: id(answers, "productRisk") === "medical_critical", refer: true, points: 5, reason: "Medical, automotive, or safety-critical products require manual underwriting." },
{ when: id(answers, "productTraceability") === "limited", refer: true, points: 5, reason: "Limited traceability or recall documentation can make automated terms unreliable." },
{ when: locationComparison?.crossBorder === true, points: 2, reason: "Manufacture and selling territories differ, so export liability has been considered." },
{ when: ["multiple", "known_issue"].includes(id(answers, "claimsHistory")), refer: true, points: 5, reason: "Prior recalls, complaints, or known product issues need review." },
]),
equipment: ({ answers }) =>
buildResult([
{ when: id(answers, "equipmentValue") === "over_50l", points: 4, reason: "Very high equipment value may need valuation support." },
{ when: id(answers, "securityLevel") === "minimal", refer: true, points: 5, reason: "Minimal theft protection needs specialist equipment terms." },
{ when: id(answers, "maintenancePlan") === "limited", points: 3, reason: "Limited maintenance records can affect breakdown and damage cover." },
{ when: id(answers, "transitExposure") === "frequent", points: 3, reason: "Frequent transit increases theft and accidental-damage exposure." },
]),
"trade-credit": ({ answers }) =>
buildResult([
{ when: id(answers, "creditConcentration") === "high", points: 4, reason: "High buyer concentration can change credit limits and premium." },
{ when: id(answers, "creditTerms") === "long", points: 4, reason: "Credit terms above 90 days need stronger buyer review." },
{ when: id(answers, "receivablesAging") === "serious_overdue", refer: true, points: 5, reason: "Material overdue receivables require manual credit underwriting." },
{ when: id(answers, "geographicReach") === "global", points: 2, reason: "Global buyer geography introduces country and political-risk exposure." },
]),
marine: ({ answers }) =>
buildResult([
{ when: id(answers, "cargoType") === "hazardous", refer: true, points: 5, reason: "Hazardous or unusually high-value cargo needs marine specialist review." },
{ when: id(answers, "marineRoute") === "high_risk", refer: true, points: 4, reason: "High-theft, catastrophe, or difficult routes require manual terms." },
{ when: id(answers, "incoterms") === "unclear", refer: true, points: 3, reason: "Unclear Incoterms can create gaps in who is responsible for insurance." },
]),
management: ({ answers }) =>
buildResult([
{ when: id(answers, "entityLifecycle") === "distress", refer: true, points: 5, reason: "Financial stress, layoffs, disputes, or regulatory attention need D&O review." },
{ when: id(answers, "governance") === "informal", refer: true, points: 4, reason: "Informal governance can materially affect management-liability terms." },
{ when: id(answers, "revenueBand") === "over_25cr" && id(answers, "entityLifecycle") !== "stable_private", points: 3, reason: "Larger organizations with change pressure need tighter underwriting." },
]),
"public-liability": ({ answers }) =>
buildResult([
{ when: id(answers, "publicPremises") === "high", points: 4, reason: "High footfall, hazardous work, or completed operations increases liability severity." },
{ when: id(answers, "controls") === "none", refer: true, points: 5, reason: "Liability cover cannot be automated without basic safety controls." },
{ when: ["multiple", "known_issue"].includes(id(answers, "claimsHistory")), refer: true, points: 5, reason: "Prior liability claims or known incidents require a final check." },
]),
drone: ({ answers }) =>
buildResult([
{ when: id(answers, "droneCompliance") === "unclear", refer: true, points: 5, reason: "Drone permissions, pilot credentials, or operating zones are unclear." },
{ when: id(answers, "droneUse") === "delivery", points: 4, reason: "Delivery or flights near people increase third-party liability exposure." },
{ when: id(answers, "droneUse") === "industrial" && id(answers, "geographicReach") !== "local", points: 3, reason: "Industrial drone use outside one local area needs tighter terms." },
]),
construction: ({ answers }) =>
buildResult([
{ when: id(answers, "projectValue") === "over_5cr", points: 4, reason: "Large project values may need engineering review and limit validation." },
{ when: id(answers, "siteProtection") === "weak", refer: true, points: 5, reason: "Weak fire, theft, permit, or site controls require manual construction terms." },
{ when: id(answers, "constructionStage") === "late", points: 2, reason: "Testing and commissioning can create higher severity exposure." },
]),
"employee-compensation": ({ answers }) =>
buildResult([
{ when: id(answers, "workforceRisk") === "manual", points: 4, reason: "Manual, driving, factory, or construction work increases employee-injury severity." },
{ when: id(answers, "statutoryCompliance") === "unclear", refer: true, points: 5, reason: "Unclear wage, contractor, or safety records require manual review." },
{ when: id(answers, "wageRoll") === "over_2cr" && id(answers, "workforceRisk") !== "office", points: 3, reason: "Large wage roll with non-office roles needs closer employee-comp terms." },
]),
"crime-fidelity": ({ answers }) =>
buildResult([
{ when: id(answers, "crimeControls") === "weak", refer: true, points: 5, reason: "Single-person approval or weak segregation needs a final crime-cover check." },
{ when: id(answers, "transactionExposure") === "high", points: 5, reason: "High transaction or treasury exposure affects crime and social-engineering limits." },
{ when: ["multiple", "known_issue"].includes(id(answers, "claimsHistory")), refer: true, points: 5, reason: "Prior fraud, theft, or known issues require manual review." },
]),
environmental: ({ answers }) =>
buildResult([
{ when: id(answers, "environmentalExposure") === "high", refer: true, points: 5, reason: "Chemical, waste, heavy-industry, or discharge exposure needs environmental underwriting." },
{ when: id(answers, "environmentalControls") === "weak", refer: true, points: 5, reason: "Incomplete spill, waste, storage, or discharge controls require manual review." },
{ when: ["multiple", "known_issue"].includes(id(answers, "claimsHistory")), refer: true, points: 5, reason: "Prior pollution or cleanup events need specialist review." },
]),
};
function assess(input) {
if (Array.isArray(input)) return assessGeneric(input);
const answers = input?.answers || {};
const generic = assessGeneric(Object.values(answers).map((answer) => answer.raw || answer.label || ""));
const categoryResult = lineRules[input?.categoryId]?.(input) || {
scoreAdjustment: 0,
needsUnderwriter: false,
reasons: [],
reason: "",
};
const reasons = [...categoryResult.reasons, ...generic.reasons];
return {
scoreAdjustment: categoryResult.scoreAdjustment + generic.scoreAdjustment,
needsUnderwriter: categoryResult.needsUnderwriter || generic.needsUnderwriter,
reasons,
reason: reasons[0] || "",
};
}
return { assess };
});