@tpmjs/official-risk-clause-highlight 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +282 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 TPMJS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as ai from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Risk Clause Highlight Tool for TPMJS
|
|
5
|
+
* Identifies and highlights potentially risky clauses in contracts
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Severity level of a risk
|
|
9
|
+
*/
|
|
10
|
+
type RiskSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
11
|
+
/**
|
|
12
|
+
* Category of risk
|
|
13
|
+
*/
|
|
14
|
+
type RiskCategory = 'liability' | 'indemnification' | 'auto_renewal' | 'termination' | 'limitation_of_liability' | 'warranty_disclaimer' | 'data_rights' | 'arbitration' | 'unilateral_modification' | 'venue_jurisdiction' | 'assignment' | 'confidentiality_scope' | 'payment_terms' | 'penalty_clause' | 'other';
|
|
15
|
+
/**
|
|
16
|
+
* Represents a risky clause in the contract
|
|
17
|
+
*/
|
|
18
|
+
interface RiskClause {
|
|
19
|
+
category: RiskCategory;
|
|
20
|
+
severity: RiskSeverity;
|
|
21
|
+
text: string;
|
|
22
|
+
location: {
|
|
23
|
+
startIndex: number;
|
|
24
|
+
endIndex: number;
|
|
25
|
+
paragraph?: number;
|
|
26
|
+
};
|
|
27
|
+
riskDescription: string;
|
|
28
|
+
mitigationSuggestion: string;
|
|
29
|
+
impact: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Input interface for risk clause analysis
|
|
33
|
+
*/
|
|
34
|
+
interface RiskClauseHighlightInput {
|
|
35
|
+
contractText: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Output interface for contract risk analysis
|
|
39
|
+
*/
|
|
40
|
+
interface ContractRisks {
|
|
41
|
+
risks: RiskClause[];
|
|
42
|
+
totalRisks: number;
|
|
43
|
+
risksBySeverity: Record<RiskSeverity, number>;
|
|
44
|
+
risksByCategory: Record<RiskCategory, number>;
|
|
45
|
+
overallRiskScore: number;
|
|
46
|
+
summary: string;
|
|
47
|
+
recommendations: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Risk Clause Highlight Tool
|
|
51
|
+
* Identifies and highlights potentially risky clauses in contracts
|
|
52
|
+
*/
|
|
53
|
+
declare const riskClauseHighlightTool: ai.Tool<RiskClauseHighlightInput, ContractRisks>;
|
|
54
|
+
|
|
55
|
+
export { type ContractRisks, type RiskClause, riskClauseHighlightTool as default, riskClauseHighlightTool };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { tool, jsonSchema } from 'ai';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var RISK_PATTERNS = {
|
|
5
|
+
liability: {
|
|
6
|
+
keywords: [
|
|
7
|
+
"unlimited liability",
|
|
8
|
+
"full liability",
|
|
9
|
+
"liable for all",
|
|
10
|
+
"responsible for any and all"
|
|
11
|
+
],
|
|
12
|
+
severity: "critical",
|
|
13
|
+
riskDescription: "Unlimited or broad liability exposure",
|
|
14
|
+
impact: "You may be held responsible for unlimited damages or losses",
|
|
15
|
+
mitigation: "Negotiate to cap liability at a reasonable amount (e.g., contract value or insurance coverage)"
|
|
16
|
+
},
|
|
17
|
+
indemnification: {
|
|
18
|
+
keywords: [
|
|
19
|
+
"shall indemnify",
|
|
20
|
+
"agree to defend",
|
|
21
|
+
"hold harmless",
|
|
22
|
+
"indemnification obligations"
|
|
23
|
+
],
|
|
24
|
+
severity: "high",
|
|
25
|
+
riskDescription: "Indemnification obligations may expose you to third-party claims",
|
|
26
|
+
impact: "You may be required to defend and pay for claims against the other party",
|
|
27
|
+
mitigation: "Limit indemnification to claims arising from your gross negligence or willful misconduct"
|
|
28
|
+
},
|
|
29
|
+
auto_renewal: {
|
|
30
|
+
keywords: ["automatically renew", "auto-renew", "renew automatically", "evergreen clause"],
|
|
31
|
+
severity: "medium",
|
|
32
|
+
riskDescription: "Contract automatically renews without explicit consent",
|
|
33
|
+
impact: "You may be locked into additional contract terms without realizing it",
|
|
34
|
+
mitigation: "Require explicit opt-in for renewals or set calendar reminders for termination notice"
|
|
35
|
+
},
|
|
36
|
+
termination: {
|
|
37
|
+
keywords: [
|
|
38
|
+
"terminate at will",
|
|
39
|
+
"terminate without cause",
|
|
40
|
+
"terminate immediately",
|
|
41
|
+
"no termination right"
|
|
42
|
+
],
|
|
43
|
+
severity: "high",
|
|
44
|
+
riskDescription: "Unfavorable termination rights or restrictions",
|
|
45
|
+
impact: "You may be unable to exit the contract or face immediate termination by the other party",
|
|
46
|
+
mitigation: "Negotiate mutual termination rights with reasonable notice periods"
|
|
47
|
+
},
|
|
48
|
+
limitation_of_liability: {
|
|
49
|
+
keywords: [
|
|
50
|
+
"exclude all liability",
|
|
51
|
+
"no liability for",
|
|
52
|
+
"limited to direct damages",
|
|
53
|
+
"liability is capped"
|
|
54
|
+
],
|
|
55
|
+
severity: "medium",
|
|
56
|
+
riskDescription: "Other party limits their liability exposure",
|
|
57
|
+
impact: "You may not be able to recover full damages if the other party breaches",
|
|
58
|
+
mitigation: "Ensure liability caps are reasonable and include exceptions for gross negligence"
|
|
59
|
+
},
|
|
60
|
+
warranty_disclaimer: {
|
|
61
|
+
keywords: [
|
|
62
|
+
"as is",
|
|
63
|
+
"without warranty",
|
|
64
|
+
"disclaims all warranties",
|
|
65
|
+
"no warranties of any kind"
|
|
66
|
+
],
|
|
67
|
+
severity: "medium",
|
|
68
|
+
riskDescription: "No warranties provided for products or services",
|
|
69
|
+
impact: "You have no recourse if products/services are defective or unsuitable",
|
|
70
|
+
mitigation: "Request specific warranties for fitness, merchantability, and non-infringement"
|
|
71
|
+
},
|
|
72
|
+
data_rights: {
|
|
73
|
+
keywords: [
|
|
74
|
+
"own all data",
|
|
75
|
+
"license to use your data",
|
|
76
|
+
"transfer of data rights",
|
|
77
|
+
"perpetual license"
|
|
78
|
+
],
|
|
79
|
+
severity: "high",
|
|
80
|
+
riskDescription: "Broad data rights granted to the other party",
|
|
81
|
+
impact: "You may lose ownership or control of your data",
|
|
82
|
+
mitigation: "Retain ownership of your data and grant only limited licenses necessary for service delivery"
|
|
83
|
+
},
|
|
84
|
+
arbitration: {
|
|
85
|
+
keywords: [
|
|
86
|
+
"mandatory arbitration",
|
|
87
|
+
"binding arbitration",
|
|
88
|
+
"waive right to jury",
|
|
89
|
+
"class action waiver"
|
|
90
|
+
],
|
|
91
|
+
severity: "medium",
|
|
92
|
+
riskDescription: "Disputes must be resolved through arbitration",
|
|
93
|
+
impact: "You may be unable to pursue litigation or join class action lawsuits",
|
|
94
|
+
mitigation: "Ensure arbitration terms are fair (e.g., shared costs, neutral venue)"
|
|
95
|
+
},
|
|
96
|
+
unilateral_modification: {
|
|
97
|
+
keywords: [
|
|
98
|
+
"modify at any time",
|
|
99
|
+
"change without notice",
|
|
100
|
+
"reserve the right to change",
|
|
101
|
+
"unilaterally modify"
|
|
102
|
+
],
|
|
103
|
+
severity: "high",
|
|
104
|
+
riskDescription: "Contract can be changed without your consent",
|
|
105
|
+
impact: "Terms can be changed unfavorably at any time",
|
|
106
|
+
mitigation: "Require advance notice of changes and the right to terminate if you disagree"
|
|
107
|
+
},
|
|
108
|
+
venue_jurisdiction: {
|
|
109
|
+
keywords: ["exclusive jurisdiction", "venue shall be", "submit to jurisdiction", "courts of"],
|
|
110
|
+
severity: "low",
|
|
111
|
+
riskDescription: "Disputes must be resolved in a specific jurisdiction",
|
|
112
|
+
impact: "You may need to litigate in an inconvenient or unfavorable location",
|
|
113
|
+
mitigation: "Negotiate for mutual jurisdiction or arbitration in a neutral location"
|
|
114
|
+
},
|
|
115
|
+
assignment: {
|
|
116
|
+
keywords: [
|
|
117
|
+
"may assign",
|
|
118
|
+
"freely assign",
|
|
119
|
+
"transfer this agreement",
|
|
120
|
+
"assignment without consent"
|
|
121
|
+
],
|
|
122
|
+
severity: "medium",
|
|
123
|
+
riskDescription: "Other party can assign contract to a third party",
|
|
124
|
+
impact: "You may end up contracting with an unknown or undesirable third party",
|
|
125
|
+
mitigation: "Require your written consent before any assignment"
|
|
126
|
+
},
|
|
127
|
+
confidentiality_scope: {
|
|
128
|
+
keywords: [
|
|
129
|
+
"all information is confidential",
|
|
130
|
+
"perpetual confidentiality",
|
|
131
|
+
"confidentiality survives forever"
|
|
132
|
+
],
|
|
133
|
+
severity: "low",
|
|
134
|
+
riskDescription: "Overly broad or perpetual confidentiality obligations",
|
|
135
|
+
impact: "You may be restricted from using general knowledge or industry practices",
|
|
136
|
+
mitigation: "Limit confidentiality to specific information and set a reasonable termination period"
|
|
137
|
+
},
|
|
138
|
+
payment_terms: {
|
|
139
|
+
keywords: ["non-refundable", "payment in advance", "no refunds", "prepaid fees"],
|
|
140
|
+
severity: "medium",
|
|
141
|
+
riskDescription: "Unfavorable payment terms or no refund policy",
|
|
142
|
+
impact: "You may lose money if you need to terminate early or if services are unsatisfactory",
|
|
143
|
+
mitigation: "Negotiate pro-rated refunds or performance-based payment terms"
|
|
144
|
+
},
|
|
145
|
+
penalty_clause: {
|
|
146
|
+
keywords: ["penalty", "liquidated damages", "late fee", "interest on overdue"],
|
|
147
|
+
severity: "medium",
|
|
148
|
+
riskDescription: "Financial penalties for breaches or late payments",
|
|
149
|
+
impact: "You may face significant penalties for minor violations",
|
|
150
|
+
mitigation: "Ensure penalties are reasonable and proportional to actual damages"
|
|
151
|
+
},
|
|
152
|
+
other: {
|
|
153
|
+
keywords: [],
|
|
154
|
+
severity: "low",
|
|
155
|
+
riskDescription: "Other potential risk identified",
|
|
156
|
+
impact: "Impact depends on specific clause",
|
|
157
|
+
mitigation: "Review carefully with legal counsel"
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
function analyzeContractRisks(contractText) {
|
|
161
|
+
if (!contractText || contractText.trim().length === 0) {
|
|
162
|
+
throw new Error("Contract text cannot be empty");
|
|
163
|
+
}
|
|
164
|
+
const paragraphs = contractText.split(/\n\s*\n/).filter((p) => p.trim().length > 0);
|
|
165
|
+
const risks = [];
|
|
166
|
+
const risksBySeverity = {
|
|
167
|
+
low: 0,
|
|
168
|
+
medium: 0,
|
|
169
|
+
high: 0,
|
|
170
|
+
critical: 0
|
|
171
|
+
};
|
|
172
|
+
const risksByCategory = {};
|
|
173
|
+
Object.keys(RISK_PATTERNS).forEach((category) => {
|
|
174
|
+
risksByCategory[category] = 0;
|
|
175
|
+
});
|
|
176
|
+
paragraphs.forEach((paragraph, paraIndex) => {
|
|
177
|
+
const paraText = paragraph.trim();
|
|
178
|
+
const normalizedPara = paraText.toLowerCase();
|
|
179
|
+
const startIndex = contractText.indexOf(paraText);
|
|
180
|
+
for (const [category, pattern] of Object.entries(RISK_PATTERNS)) {
|
|
181
|
+
if (category === "other") continue;
|
|
182
|
+
const keywordMatches = pattern.keywords.some(
|
|
183
|
+
(keyword) => normalizedPara.includes(keyword.toLowerCase())
|
|
184
|
+
);
|
|
185
|
+
if (keywordMatches) {
|
|
186
|
+
const risk = {
|
|
187
|
+
category,
|
|
188
|
+
severity: pattern.severity,
|
|
189
|
+
text: paraText,
|
|
190
|
+
location: {
|
|
191
|
+
startIndex,
|
|
192
|
+
endIndex: startIndex + paraText.length,
|
|
193
|
+
paragraph: paraIndex + 1
|
|
194
|
+
},
|
|
195
|
+
riskDescription: pattern.riskDescription,
|
|
196
|
+
mitigationSuggestion: pattern.mitigation,
|
|
197
|
+
impact: pattern.impact
|
|
198
|
+
};
|
|
199
|
+
risks.push(risk);
|
|
200
|
+
risksBySeverity[pattern.severity]++;
|
|
201
|
+
risksByCategory[category]++;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const severityWeights = { low: 10, medium: 25, high: 50, critical: 100 };
|
|
207
|
+
const totalWeightedRisks = risksBySeverity.low * severityWeights.low + risksBySeverity.medium * severityWeights.medium + risksBySeverity.high * severityWeights.high + risksBySeverity.critical * severityWeights.critical;
|
|
208
|
+
const maxPossibleScore = paragraphs.length * severityWeights.critical;
|
|
209
|
+
const overallRiskScore = Math.min(
|
|
210
|
+
100,
|
|
211
|
+
Math.round(totalWeightedRisks / Math.max(1, maxPossibleScore) * 100)
|
|
212
|
+
);
|
|
213
|
+
const recommendations = [];
|
|
214
|
+
if (risksBySeverity.critical > 0) {
|
|
215
|
+
recommendations.push(
|
|
216
|
+
`Address ${risksBySeverity.critical} critical risk${risksBySeverity.critical > 1 ? "s" : ""} immediately before signing`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (risksBySeverity.high > 0) {
|
|
220
|
+
recommendations.push(
|
|
221
|
+
`Negotiate or mitigate ${risksBySeverity.high} high-severity risk${risksBySeverity.high > 1 ? "s" : ""}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
if (risksBySeverity.medium > 3) {
|
|
225
|
+
recommendations.push("Consider having legal counsel review the multiple medium-risk clauses");
|
|
226
|
+
}
|
|
227
|
+
if (risks.length === 0) {
|
|
228
|
+
recommendations.push(
|
|
229
|
+
"No standard risk patterns detected, but still review the contract carefully"
|
|
230
|
+
);
|
|
231
|
+
} else {
|
|
232
|
+
recommendations.push(
|
|
233
|
+
"Review each identified risk clause with the mitigation suggestions provided"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const riskLevel = overallRiskScore >= 75 ? "very high" : overallRiskScore >= 50 ? "high" : overallRiskScore >= 25 ? "moderate" : "low";
|
|
237
|
+
const summary = `Identified ${risks.length} potentially risky clause${risks.length !== 1 ? "s" : ""} with an overall risk score of ${overallRiskScore}/100 (${riskLevel} risk). ${risksBySeverity.critical} critical, ${risksBySeverity.high} high, ${risksBySeverity.medium} medium, and ${risksBySeverity.low} low severity risks detected.`;
|
|
238
|
+
return {
|
|
239
|
+
risks: risks.sort((a, b) => {
|
|
240
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
241
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
242
|
+
}),
|
|
243
|
+
totalRisks: risks.length,
|
|
244
|
+
risksBySeverity,
|
|
245
|
+
risksByCategory,
|
|
246
|
+
overallRiskScore,
|
|
247
|
+
summary,
|
|
248
|
+
recommendations
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
var riskClauseHighlightTool = tool({
|
|
252
|
+
description: "Identifies and highlights potentially risky clauses in contracts such as unlimited liability, broad indemnification, auto-renewal terms, unfavorable termination rights, warranty disclaimers, data rights transfers, mandatory arbitration, and unilateral modification clauses. Returns detailed risk analysis with severity ratings, impact assessments, and mitigation suggestions for each identified risk.",
|
|
253
|
+
inputSchema: jsonSchema({
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
contractText: {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "The full contract text to analyze for risky clauses"
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
required: ["contractText"],
|
|
262
|
+
additionalProperties: false
|
|
263
|
+
}),
|
|
264
|
+
execute: async ({ contractText }) => {
|
|
265
|
+
if (typeof contractText !== "string") {
|
|
266
|
+
throw new Error("Contract text must be a string");
|
|
267
|
+
}
|
|
268
|
+
if (contractText.trim().length === 0) {
|
|
269
|
+
throw new Error("Contract text cannot be empty");
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
return analyzeContractRisks(contractText);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Failed to analyze contract risks: ${error instanceof Error ? error.message : String(error)}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
var index_default = riskClauseHighlightTool;
|
|
281
|
+
|
|
282
|
+
export { index_default as default, riskClauseHighlightTool };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tpmjs/official-risk-clause-highlight",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Identifies and highlights potentially risky clauses in contracts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"tpmjs",
|
|
8
|
+
"legal",
|
|
9
|
+
"contract",
|
|
10
|
+
"risk",
|
|
11
|
+
"analysis"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.3.5",
|
|
24
|
+
"typescript": "^5.9.3",
|
|
25
|
+
"@tpmjs/tsconfig": "0.0.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/anthropics/tpmjs.git",
|
|
33
|
+
"directory": "packages/tools/official/risk-clause-highlight"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://tpmjs.com",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"tpmjs": {
|
|
38
|
+
"category": "legal",
|
|
39
|
+
"frameworks": [
|
|
40
|
+
"vercel-ai"
|
|
41
|
+
],
|
|
42
|
+
"tools": [
|
|
43
|
+
{
|
|
44
|
+
"name": "riskClauseHighlightTool",
|
|
45
|
+
"description": "Identifies and highlights potentially risky clauses in contracts",
|
|
46
|
+
"parameters": [
|
|
47
|
+
{
|
|
48
|
+
"name": "contractText",
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Contract text to analyze",
|
|
51
|
+
"required": true
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"returns": {
|
|
55
|
+
"type": "ContractRisks",
|
|
56
|
+
"description": "Identified risks with severity ratings and mitigation suggestions"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"ai": "6.0.0-beta.124"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsup",
|
|
66
|
+
"dev": "tsup --watch",
|
|
67
|
+
"type-check": "tsc --noEmit",
|
|
68
|
+
"clean": "rm -rf dist .turbo"
|
|
69
|
+
}
|
|
70
|
+
}
|