catalist-support-agent 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/dist/admin-portal.d.ts +43 -0
- package/dist/admin-portal.d.ts.map +1 -0
- package/dist/admin-portal.js +166 -0
- package/dist/admin-portal.js.map +1 -0
- package/dist/analysis/entities.d.ts +73 -0
- package/dist/analysis/entities.d.ts.map +1 -0
- package/dist/analysis/entities.js +378 -0
- package/dist/analysis/entities.js.map +1 -0
- package/dist/analysis/index.d.ts +44 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +243 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/intent.d.ts +49 -0
- package/dist/analysis/intent.d.ts.map +1 -0
- package/dist/analysis/intent.js +320 -0
- package/dist/analysis/intent.js.map +1 -0
- package/dist/analysis/sentiment.d.ts +57 -0
- package/dist/analysis/sentiment.d.ts.map +1 -0
- package/dist/analysis/sentiment.js +351 -0
- package/dist/analysis/sentiment.js.map +1 -0
- package/dist/brand/compliance.d.ts +122 -0
- package/dist/brand/compliance.d.ts.map +1 -0
- package/dist/brand/compliance.js +378 -0
- package/dist/brand/compliance.js.map +1 -0
- package/dist/brand/forbidden-terms.d.ts +99 -0
- package/dist/brand/forbidden-terms.d.ts.map +1 -0
- package/dist/brand/forbidden-terms.js +265 -0
- package/dist/brand/forbidden-terms.js.map +1 -0
- package/dist/brand/index.d.ts +10 -0
- package/dist/brand/index.d.ts.map +1 -0
- package/dist/brand/index.js +12 -0
- package/dist/brand/index.js.map +1 -0
- package/dist/config.d.ts +325 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +492 -0
- package/dist/config.js.map +1 -0
- package/dist/delivery/index.d.ts +84 -0
- package/dist/delivery/index.d.ts.map +1 -0
- package/dist/delivery/index.js +435 -0
- package/dist/delivery/index.js.map +1 -0
- package/dist/embeddings/cache.d.ts +96 -0
- package/dist/embeddings/cache.d.ts.map +1 -0
- package/dist/embeddings/cache.js +193 -0
- package/dist/embeddings/cache.js.map +1 -0
- package/dist/embeddings/index.d.ts +152 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +337 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/openai-client.d.ts +67 -0
- package/dist/embeddings/openai-client.d.ts.map +1 -0
- package/dist/embeddings/openai-client.js +190 -0
- package/dist/embeddings/openai-client.js.map +1 -0
- package/dist/errors.d.ts +302 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +508 -0
- package/dist/errors.js.map +1 -0
- package/dist/escalation/index.d.ts +93 -0
- package/dist/escalation/index.d.ts.map +1 -0
- package/dist/escalation/index.js +436 -0
- package/dist/escalation/index.js.map +1 -0
- package/dist/extraction/deduplication.d.ts +97 -0
- package/dist/extraction/deduplication.d.ts.map +1 -0
- package/dist/extraction/deduplication.js +271 -0
- package/dist/extraction/deduplication.js.map +1 -0
- package/dist/extraction/gmail-extractor.d.ts +160 -0
- package/dist/extraction/gmail-extractor.d.ts.map +1 -0
- package/dist/extraction/gmail-extractor.js +396 -0
- package/dist/extraction/gmail-extractor.js.map +1 -0
- package/dist/extraction/gmail-token-manager.d.ts +36 -0
- package/dist/extraction/gmail-token-manager.d.ts.map +1 -0
- package/dist/extraction/gmail-token-manager.js +146 -0
- package/dist/extraction/gmail-token-manager.js.map +1 -0
- package/dist/extraction/index.d.ts +13 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +20 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/extraction/pii-handler.d.ts +100 -0
- package/dist/extraction/pii-handler.d.ts.map +1 -0
- package/dist/extraction/pii-handler.js +295 -0
- package/dist/extraction/pii-handler.js.map +1 -0
- package/dist/extraction/pipeline.d.ts +94 -0
- package/dist/extraction/pipeline.d.ts.map +1 -0
- package/dist/extraction/pipeline.js +380 -0
- package/dist/extraction/pipeline.js.map +1 -0
- package/dist/extraction/quality-filter.d.ts +99 -0
- package/dist/extraction/quality-filter.d.ts.map +1 -0
- package/dist/extraction/quality-filter.js +370 -0
- package/dist/extraction/quality-filter.js.map +1 -0
- package/dist/extraction/rate-limiter.d.ts +90 -0
- package/dist/extraction/rate-limiter.d.ts.map +1 -0
- package/dist/extraction/rate-limiter.js +242 -0
- package/dist/extraction/rate-limiter.js.map +1 -0
- package/dist/extraction/state-manager.d.ts +126 -0
- package/dist/extraction/state-manager.d.ts.map +1 -0
- package/dist/extraction/state-manager.js +344 -0
- package/dist/extraction/state-manager.js.map +1 -0
- package/dist/generation/index.d.ts +75 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +641 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/intake/index.d.ts +15 -0
- package/dist/intake/index.d.ts.map +1 -0
- package/dist/intake/index.js +19 -0
- package/dist/intake/index.js.map +1 -0
- package/dist/intake/normalizer.d.ts +163 -0
- package/dist/intake/normalizer.d.ts.map +1 -0
- package/dist/intake/normalizer.js +309 -0
- package/dist/intake/normalizer.js.map +1 -0
- package/dist/intake/postmark.d.ts +72 -0
- package/dist/intake/postmark.d.ts.map +1 -0
- package/dist/intake/postmark.js +276 -0
- package/dist/intake/postmark.js.map +1 -0
- package/dist/intake/slack.d.ts +106 -0
- package/dist/intake/slack.d.ts.map +1 -0
- package/dist/intake/slack.js +378 -0
- package/dist/intake/slack.js.map +1 -0
- package/dist/intake/twilio.d.ts +86 -0
- package/dist/intake/twilio.d.ts.map +1 -0
- package/dist/intake/twilio.js +283 -0
- package/dist/intake/twilio.js.map +1 -0
- package/dist/knowledge/index.d.ts +100 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +516 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/invoice-resolver.d.ts +62 -0
- package/dist/knowledge/invoice-resolver.d.ts.map +1 -0
- package/dist/knowledge/invoice-resolver.js +267 -0
- package/dist/knowledge/invoice-resolver.js.map +1 -0
- package/dist/types.d.ts +535 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +48 -0
- package/dist/types.js.map +1 -0
- package/ga-service-account.json +13 -0
- package/gmail-knowledge-migration.sql +149 -0
- package/nul +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified entry point for Admin Portal integration
|
|
3
|
+
*
|
|
4
|
+
* Provides a focused API for the Gmail webhook without exposing
|
|
5
|
+
* the full library complexity.
|
|
6
|
+
*/
|
|
7
|
+
export interface EmailProcessingInput {
|
|
8
|
+
fromEmail: string;
|
|
9
|
+
fromName?: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
body: string;
|
|
12
|
+
threadId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface EmailProcessingResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
response?: {
|
|
17
|
+
text: string;
|
|
18
|
+
qualityScore: number;
|
|
19
|
+
};
|
|
20
|
+
analysis?: {
|
|
21
|
+
intent: string;
|
|
22
|
+
sentiment: string;
|
|
23
|
+
urgency: string;
|
|
24
|
+
entities: {
|
|
25
|
+
orderNumbers: string[];
|
|
26
|
+
productNames: string[];
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
customer?: {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
company?: string;
|
|
33
|
+
};
|
|
34
|
+
escalated: boolean;
|
|
35
|
+
escalationReason?: string;
|
|
36
|
+
noResponseNeeded: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Process an email through the full support pipeline
|
|
40
|
+
*/
|
|
41
|
+
export declare function processEmail(input: EmailProcessingInput): Promise<EmailProcessingResult>;
|
|
42
|
+
export { humanizeResponse } from './generation/index.js';
|
|
43
|
+
//# sourceMappingURL=admin-portal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-portal.d.ts","sourceRoot":"","sources":["../src/admin-portal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE;YACR,YAAY,EAAE,MAAM,EAAE,CAAC;YACvB,YAAY,EAAE,MAAM,EAAE,CAAC;SACxB,CAAC;KACH,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CA8HhC;AAmCD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified entry point for Admin Portal integration
|
|
3
|
+
*
|
|
4
|
+
* Provides a focused API for the Gmail webhook without exposing
|
|
5
|
+
* the full library complexity.
|
|
6
|
+
*/
|
|
7
|
+
import { analyzeMessage, triageMessage } from './analysis/index.js';
|
|
8
|
+
import { getKnowledgeService } from './knowledge/index.js';
|
|
9
|
+
import { humanizeResponse, generateResponse } from './generation/index.js';
|
|
10
|
+
import { checkBrandCompliance } from './brand/index.js';
|
|
11
|
+
import { createConversationId, createMessageId } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Process an email through the full support pipeline
|
|
14
|
+
*/
|
|
15
|
+
export async function processEmail(input) {
|
|
16
|
+
// Create normalized message structure
|
|
17
|
+
const message = {
|
|
18
|
+
id: createMessageId(crypto.randomUUID()),
|
|
19
|
+
conversationId: createConversationId(crypto.randomUUID()),
|
|
20
|
+
channel: 'email',
|
|
21
|
+
direction: 'inbound',
|
|
22
|
+
sender: {
|
|
23
|
+
email: input.fromEmail,
|
|
24
|
+
name: input.fromName,
|
|
25
|
+
},
|
|
26
|
+
content: {
|
|
27
|
+
text: input.body,
|
|
28
|
+
subject: input.subject,
|
|
29
|
+
},
|
|
30
|
+
metadata: {
|
|
31
|
+
type: 'email',
|
|
32
|
+
messageId: input.threadId || crypto.randomUUID(),
|
|
33
|
+
fromAddress: input.fromEmail,
|
|
34
|
+
toAddresses: ['sales@catalistgroup.co'],
|
|
35
|
+
},
|
|
36
|
+
receivedAt: new Date().toISOString(),
|
|
37
|
+
};
|
|
38
|
+
// 1. Quick triage for forbidden terms
|
|
39
|
+
const triage = triageMessage(message);
|
|
40
|
+
if (triage.requiresEscalation && triage.quickAssessment.includes('forbidden terms')) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
escalated: true,
|
|
44
|
+
escalationReason: triage.quickAssessment,
|
|
45
|
+
noResponseNeeded: false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// 2. Check for automated senders that don't need response
|
|
49
|
+
if (shouldSkipResponse(input.fromEmail)) {
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
escalated: false,
|
|
53
|
+
noResponseNeeded: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 3. Full analysis
|
|
57
|
+
const analysis = await analyzeMessage(message);
|
|
58
|
+
// Check if analysis indicates escalation required
|
|
59
|
+
if (analysis.escalationRequired) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
escalated: true,
|
|
63
|
+
escalationReason: analysis.escalationReasons?.join('; ') || 'Analysis requires escalation',
|
|
64
|
+
noResponseNeeded: false,
|
|
65
|
+
analysis: {
|
|
66
|
+
intent: analysis.intent.primary,
|
|
67
|
+
sentiment: analysis.sentiment.category,
|
|
68
|
+
urgency: analysis.sentiment.urgency,
|
|
69
|
+
entities: {
|
|
70
|
+
orderNumbers: analysis.entities.orderNumbers ?? [],
|
|
71
|
+
productNames: analysis.entities.productNames ?? [],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// 4. Retrieve knowledge context (customer data, orders)
|
|
77
|
+
let knowledge;
|
|
78
|
+
try {
|
|
79
|
+
knowledge = await getKnowledgeService().retrieveContext(message, analysis);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.warn('[AdminPortal] Knowledge retrieval failed, continuing with empty context:', error);
|
|
83
|
+
knowledge = createEmptyKnowledgeContext();
|
|
84
|
+
}
|
|
85
|
+
// 5. Generate response
|
|
86
|
+
const rawResponse = await generateResponse(message, analysis, knowledge);
|
|
87
|
+
// 6. Humanize the response (remove AI patterns)
|
|
88
|
+
const humanizedText = humanizeResponse(rawResponse.content.text);
|
|
89
|
+
// 7. Validate brand compliance of generated response
|
|
90
|
+
const brandCheck = checkBrandCompliance(humanizedText);
|
|
91
|
+
if (!brandCheck.passed) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
escalated: true,
|
|
95
|
+
escalationReason: `Brand compliance failed: ${brandCheck.toneIssues?.join(', ') || 'Unknown issues'}`,
|
|
96
|
+
noResponseNeeded: false,
|
|
97
|
+
analysis: {
|
|
98
|
+
intent: analysis.intent.primary,
|
|
99
|
+
sentiment: analysis.sentiment.category,
|
|
100
|
+
urgency: analysis.sentiment.urgency,
|
|
101
|
+
entities: {
|
|
102
|
+
orderNumbers: analysis.entities.orderNumbers ?? [],
|
|
103
|
+
productNames: analysis.entities.productNames ?? [],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
response: {
|
|
111
|
+
text: humanizedText,
|
|
112
|
+
qualityScore: rawResponse.qualityScore ?? 0.9,
|
|
113
|
+
},
|
|
114
|
+
analysis: {
|
|
115
|
+
intent: analysis.intent.primary,
|
|
116
|
+
sentiment: analysis.sentiment.category,
|
|
117
|
+
urgency: analysis.sentiment.urgency,
|
|
118
|
+
entities: {
|
|
119
|
+
orderNumbers: analysis.entities.orderNumbers ?? [],
|
|
120
|
+
productNames: analysis.entities.productNames ?? [],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
customer: knowledge.customer
|
|
124
|
+
? {
|
|
125
|
+
id: String(knowledge.customer.id),
|
|
126
|
+
name: knowledge.customer.contactName ?? knowledge.customer.companyName,
|
|
127
|
+
company: knowledge.customer.companyName,
|
|
128
|
+
}
|
|
129
|
+
: undefined,
|
|
130
|
+
escalated: false,
|
|
131
|
+
noResponseNeeded: false,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function shouldSkipResponse(email) {
|
|
135
|
+
const automatedPatterns = [
|
|
136
|
+
/^noreply@/i,
|
|
137
|
+
/^no-reply@/i,
|
|
138
|
+
/^donotreply@/i,
|
|
139
|
+
/^notifications?@/i,
|
|
140
|
+
/^alerts?@/i,
|
|
141
|
+
/@.*\.apple\.com$/i,
|
|
142
|
+
/@.*\.google\.com$/i,
|
|
143
|
+
/@.*\.amazon\.com$/i,
|
|
144
|
+
];
|
|
145
|
+
return automatedPatterns.some((p) => p.test(email));
|
|
146
|
+
}
|
|
147
|
+
function createEmptyKnowledgeContext() {
|
|
148
|
+
return {
|
|
149
|
+
customer: undefined,
|
|
150
|
+
products: undefined,
|
|
151
|
+
orders: undefined,
|
|
152
|
+
policies: undefined,
|
|
153
|
+
similarConversations: undefined,
|
|
154
|
+
templates: undefined,
|
|
155
|
+
assembledAt: new Date().toISOString(),
|
|
156
|
+
confidenceScores: {
|
|
157
|
+
customerData: 0,
|
|
158
|
+
productData: 0,
|
|
159
|
+
policyData: 0,
|
|
160
|
+
similarConversations: 0,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Re-export humanizeResponse for direct use
|
|
165
|
+
export { humanizeResponse } from './generation/index.js';
|
|
166
|
+
//# sourceMappingURL=admin-portal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-portal.js","sourceRoot":"","sources":["../src/admin-portal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAgC,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAoCnE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAA2B;IAE3B,sCAAsC;IACtC,MAAM,OAAO,GAAmB;QAC9B,EAAE,EAAE,eAAe,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACxC,cAAc,EAAE,oBAAoB,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACzD,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,SAAS;QACpB,MAAM,EAAE;YACN,KAAK,EAAE,KAAK,CAAC,SAAS;YACtB,IAAI,EAAE,KAAK,CAAC,QAAQ;SACrB;QACD,OAAO,EAAE;YACP,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB;QACD,QAAQ,EAAE;YACR,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC,UAAU,EAAE;YAChD,WAAW,EAAE,KAAK,CAAC,SAAS;YAC5B,WAAW,EAAE,CAAC,wBAAwB,CAAC;SACxC;QACD,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;IAEF,sCAAsC;IACtC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,kBAAkB,IAAI,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACpF,OAAO;YACL,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,MAAM,CAAC,eAAe;YACxC,gBAAgB,EAAE,KAAK;SACxB,CAAC;IACJ,CAAC;IAED,0DAA0D;IAC1D,IAAI,kBAAkB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACxC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,KAAK;YAChB,gBAAgB,EAAE,IAAI;SACvB,CAAC;IACJ,CAAC;IAED,mBAAmB;IACnB,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAE/C,kDAAkD;IAClD,IAAI,QAAQ,CAAC,kBAAkB,EAAE,CAAC;QAChC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,QAAQ,CAAC,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,8BAA8B;YAC1F,gBAAgB,EAAE,KAAK;YACvB,QAAQ,EAAE;gBACR,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO;gBAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,QAAQ;gBACtC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,OAAO;gBACnC,QAAQ,EAAE;oBACR,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE;oBAClD,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE;iBACnD;aACF;SACF,CAAC;IACJ,CAAC;IAED,wDAAwD;IACxD,IAAI,SAA2B,CAAC;IAChC,IAAI,CAAC;QACH,SAAS,GAAG,MAAM,mBAAmB,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,0EAA0E,EAAE,KAAK,CAAC,CAAC;QAChG,SAAS,GAAG,2BAA2B,EAAE,CAAC;IAC5C,CAAC;IAED,uBAAuB;IACvB,MAAM,WAAW,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IAEzE,gDAAgD;IAChD,MAAM,aAAa,GAAG,gBAAgB,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,qDAAqD;IACrD,MAAM,UAAU,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,4BAA4B,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,gBAAgB,EAAE;YACrG,gBAAgB,EAAE,KAAK;YACvB,QAAQ,EAAE;gBACR,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO;gBAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,QAAQ;gBACtC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,OAAO;gBACnC,QAAQ,EAAE;oBACR,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE;oBAClD,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE;iBACnD;aACF;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE;YACR,IAAI,EAAE,aAAa;YACnB,YAAY,EAAE,WAAW,CAAC,YAAY,IAAI,GAAG;SAC9C;QACD,QAAQ,EAAE;YACR,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO;YAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,QAAQ;YACtC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,OAAO;YACnC,QAAQ,EAAE;gBACR,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE;gBAClD,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE;aACnD;SACF;QACD,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC1B,CAAC,CAAC;gBACE,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,SAAS,CAAC,QAAQ,CAAC,WAAW;gBACtE,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,WAAW;aACxC;YACH,CAAC,CAAC,SAAS;QACb,SAAS,EAAE,KAAK;QAChB,gBAAgB,EAAE,KAAK;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,MAAM,iBAAiB,GAAG;QACxB,YAAY;QACZ,aAAa;QACb,eAAe;QACf,mBAAmB;QACnB,YAAY;QACZ,mBAAmB;QACnB,oBAAoB;QACpB,oBAAoB;KACrB,CAAC;IACF,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,2BAA2B;IAClC,OAAO;QACL,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,SAAS;QACjB,QAAQ,EAAE,SAAS;QACnB,oBAAoB,EAAE,SAAS;QAC/B,SAAS,EAAE,SAAS;QACpB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,gBAAgB,EAAE;YAChB,YAAY,EAAE,CAAC;YACf,WAAW,EAAE,CAAC;YACd,UAAU,EAAE,CAAC;YACb,oBAAoB,EAAE,CAAC;SACxB;KACF,CAAC;AACJ,CAAC;AAED,4CAA4C;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Extraction Service
|
|
3
|
+
*
|
|
4
|
+
* Extracts structured entities from support messages using Claude.
|
|
5
|
+
* Identifies order numbers, product references, dates, amounts, etc.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtractedEntities, InboundMessage } from '../types.js';
|
|
8
|
+
export declare class EntityExtractionService {
|
|
9
|
+
private client;
|
|
10
|
+
private getClient;
|
|
11
|
+
/**
|
|
12
|
+
* Extract entities from a message using Claude
|
|
13
|
+
*/
|
|
14
|
+
extract(message: InboundMessage): Promise<ExtractedEntities>;
|
|
15
|
+
/**
|
|
16
|
+
* Parse the entity extraction response
|
|
17
|
+
*/
|
|
18
|
+
private parseEntityResponse;
|
|
19
|
+
/**
|
|
20
|
+
* Filter empty arrays to undefined
|
|
21
|
+
*/
|
|
22
|
+
private filterEmpty;
|
|
23
|
+
/**
|
|
24
|
+
* Parse date entities
|
|
25
|
+
*/
|
|
26
|
+
private parseDates;
|
|
27
|
+
/**
|
|
28
|
+
* Try to parse a date string
|
|
29
|
+
*/
|
|
30
|
+
private tryParseDate;
|
|
31
|
+
/**
|
|
32
|
+
* Validate date type
|
|
33
|
+
*/
|
|
34
|
+
private validateDateType;
|
|
35
|
+
/**
|
|
36
|
+
* Parse monetary amounts
|
|
37
|
+
*/
|
|
38
|
+
private parseMonetaryAmounts;
|
|
39
|
+
/**
|
|
40
|
+
* Extract entities using regex patterns (fallback)
|
|
41
|
+
*/
|
|
42
|
+
extractWithRegex(message: InboundMessage): ExtractedEntities;
|
|
43
|
+
}
|
|
44
|
+
export declare function getEntityExtractionService(): EntityExtractionService;
|
|
45
|
+
/**
|
|
46
|
+
* Extract entities from message
|
|
47
|
+
*/
|
|
48
|
+
export declare function extractEntities(message: InboundMessage): Promise<ExtractedEntities>;
|
|
49
|
+
/**
|
|
50
|
+
* Extract entities using regex (fallback)
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractEntitiesRegex(message: InboundMessage): ExtractedEntities;
|
|
53
|
+
/**
|
|
54
|
+
* Merge extracted entities from multiple sources
|
|
55
|
+
*/
|
|
56
|
+
export declare function mergeEntities(...entities: ExtractedEntities[]): ExtractedEntities;
|
|
57
|
+
/**
|
|
58
|
+
* Check if entities contain order reference
|
|
59
|
+
*/
|
|
60
|
+
export declare function hasOrderReference(entities: ExtractedEntities): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Check if entities contain product reference
|
|
63
|
+
*/
|
|
64
|
+
export declare function hasProductReference(entities: ExtractedEntities): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Get primary order number from entities
|
|
67
|
+
*/
|
|
68
|
+
export declare function getPrimaryOrderNumber(entities: ExtractedEntities): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Get total monetary amount mentioned
|
|
71
|
+
*/
|
|
72
|
+
export declare function getTotalAmount(entities: ExtractedEntities): number | undefined;
|
|
73
|
+
//# sourceMappingURL=entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/analysis/entities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAiC,cAAc,EAAE,MAAM,aAAa,CAAC;AAyEpG,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAA0B;IAExC,OAAO,CAAC,SAAS;IAejB;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA2ClE;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA8C3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,OAAO,CAAC,UAAU;IAYlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB;CAkE7D;AAQD,wBAAgB,0BAA0B,IAAI,uBAAuB,CAKpE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEzF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB,CAE/E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,QAAQ,EAAE,iBAAiB,EAAE,GAAG,iBAAiB,CA6CjF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAEtE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAKxE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAErF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAY9E"}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Extraction Service
|
|
3
|
+
*
|
|
4
|
+
* Extracts structured entities from support messages using Claude.
|
|
5
|
+
* Identifies order numbers, product references, dates, amounts, etc.
|
|
6
|
+
*/
|
|
7
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
8
|
+
import { config } from '../config.js';
|
|
9
|
+
import { AnalysisError } from '../errors.js';
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Entity Patterns (Regex-based extraction)
|
|
12
|
+
// =============================================================================
|
|
13
|
+
const PATTERNS = {
|
|
14
|
+
// Order numbers: Various formats like ORD-12345, #12345, SO-12345
|
|
15
|
+
orderNumber: /(?:order|ord|so|po|invoice)[\s#:-]*([A-Z0-9-]{4,20})/gi,
|
|
16
|
+
// Catalist invoice numbers: 7-digit numbers starting with 91 (e.g., 9107893)
|
|
17
|
+
catalistInvoice: /\b(91\d{5})\b/g,
|
|
18
|
+
// Amazon ASIN: 10-character alphanumeric, typically starts with B0
|
|
19
|
+
asin: /\b(B0[A-Z0-9]{8}|[A-Z0-9]{10})\b/g,
|
|
20
|
+
// SKU patterns: Common formats like ABC-12345, 12345-ABC
|
|
21
|
+
sku: /\b([A-Z]{2,5}[-\s]?[0-9]{3,10}|[0-9]{3,10}[-\s]?[A-Z]{2,5})\b/gi,
|
|
22
|
+
// Monetary amounts: $100, $1,234.56, USD 100
|
|
23
|
+
money: /(?:\$|USD|EUR|GBP)\s*([0-9,]+(?:\.[0-9]{2})?)|([0-9,]+(?:\.[0-9]{2})?)\s*(?:dollars?|USD|EUR|GBP)/gi,
|
|
24
|
+
// Phone numbers: Various formats
|
|
25
|
+
phone: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
|
|
26
|
+
// Email addresses
|
|
27
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
28
|
+
// Dates: Various formats
|
|
29
|
+
date: /(?:(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+\d{1,2}(?:st|nd|rd|th)?,?\s*\d{4}|\d{1,2}[-/]\d{1,2}[-/]\d{2,4}|\d{4}[-/]\d{1,2}[-/]\d{1,2})/gi,
|
|
30
|
+
};
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Entity Extraction Prompt
|
|
33
|
+
// =============================================================================
|
|
34
|
+
const ENTITY_EXTRACTION_PROMPT = `You are an entity extraction system for B2B marketplace support messages.
|
|
35
|
+
|
|
36
|
+
Extract the following entities if present:
|
|
37
|
+
- **Order Numbers**: Order IDs, invoice numbers, PO numbers
|
|
38
|
+
- **Product Names**: Product names, brand names mentioned
|
|
39
|
+
- **Product SKUs**: Product codes, SKU numbers, Amazon ASINs (10-character codes like B00V3HW8E0)
|
|
40
|
+
- **Dates**: Any dates mentioned (order dates, delivery dates, etc.)
|
|
41
|
+
- **Monetary Amounts**: Prices, totals, refund amounts
|
|
42
|
+
- **Customer References**: Customer account numbers, company names
|
|
43
|
+
- **Company Names**: Business names mentioned
|
|
44
|
+
- **Person Names**: Contact names mentioned
|
|
45
|
+
|
|
46
|
+
## Response Format
|
|
47
|
+
|
|
48
|
+
Respond with a JSON object:
|
|
49
|
+
{
|
|
50
|
+
"orderNumbers": ["ORD-12345"],
|
|
51
|
+
"productNames": ["Product Name"],
|
|
52
|
+
"productSkus": ["SKU-123"],
|
|
53
|
+
"dates": [
|
|
54
|
+
{"raw": "January 15, 2024", "type": "order_date|delivery_date|request_date|general"}
|
|
55
|
+
],
|
|
56
|
+
"monetaryAmounts": [
|
|
57
|
+
{"raw": "$150.00", "amount": 150.00, "currency": "USD", "context": "refund amount"}
|
|
58
|
+
],
|
|
59
|
+
"customerReferences": ["ACC-123"],
|
|
60
|
+
"companyNames": ["Company Inc"],
|
|
61
|
+
"personNames": ["John Smith"]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Include only entities that are clearly present. Use null for arrays with no matches.
|
|
65
|
+
`;
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Entity Extraction Service
|
|
68
|
+
// =============================================================================
|
|
69
|
+
export class EntityExtractionService {
|
|
70
|
+
client = null;
|
|
71
|
+
getClient() {
|
|
72
|
+
if (!this.client) {
|
|
73
|
+
if (config.claude.apiKey) {
|
|
74
|
+
this.client = new Anthropic({
|
|
75
|
+
apiKey: config.claude.apiKey,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw new AnalysisError('Claude API key not configured', 'entities', {
|
|
80
|
+
context: { reason: 'Missing ANTHROPIC_API_KEY' },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return this.client;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract entities from a message using Claude
|
|
88
|
+
*/
|
|
89
|
+
async extract(message) {
|
|
90
|
+
try {
|
|
91
|
+
const client = this.getClient();
|
|
92
|
+
let content = message.content.text;
|
|
93
|
+
if (message.content.subject) {
|
|
94
|
+
content = `Subject: ${message.content.subject}\n\n${content}`;
|
|
95
|
+
}
|
|
96
|
+
const response = await client.messages.create({
|
|
97
|
+
model: config.claude.model,
|
|
98
|
+
max_tokens: 512,
|
|
99
|
+
system: ENTITY_EXTRACTION_PROMPT,
|
|
100
|
+
messages: [
|
|
101
|
+
{
|
|
102
|
+
role: 'user',
|
|
103
|
+
content: `Extract entities from this customer message:\n\n${content}`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
const textContent = response.content.find((c) => c.type === 'text');
|
|
108
|
+
if (!textContent || textContent.type !== 'text') {
|
|
109
|
+
throw new AnalysisError('No text response from Claude', 'entities');
|
|
110
|
+
}
|
|
111
|
+
return this.parseEntityResponse(textContent.text);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof AnalysisError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
throw new AnalysisError(`Entity extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'entities', {
|
|
118
|
+
cause: error instanceof Error ? error : undefined,
|
|
119
|
+
messageId: message.id,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse the entity extraction response
|
|
125
|
+
*/
|
|
126
|
+
parseEntityResponse(text) {
|
|
127
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
128
|
+
if (!jsonMatch) {
|
|
129
|
+
throw new AnalysisError('Could not parse entity extraction JSON', 'entities', {
|
|
130
|
+
context: { response: text.substring(0, 200) },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
135
|
+
return {
|
|
136
|
+
orderNumbers: this.filterEmpty(parsed.orderNumbers),
|
|
137
|
+
productNames: this.filterEmpty(parsed.productNames),
|
|
138
|
+
productSkus: this.filterEmpty(parsed.productSkus),
|
|
139
|
+
dates: this.parseDates(parsed.dates),
|
|
140
|
+
monetaryAmounts: this.parseMonetaryAmounts(parsed.monetaryAmounts),
|
|
141
|
+
customerReferences: this.filterEmpty(parsed.customerReferences),
|
|
142
|
+
companyNames: this.filterEmpty(parsed.companyNames),
|
|
143
|
+
personNames: this.filterEmpty(parsed.personNames),
|
|
144
|
+
phoneNumbers: this.filterEmpty(parsed.phoneNumbers),
|
|
145
|
+
emails: this.filterEmpty(parsed.emails),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new AnalysisError('Invalid JSON in entity extraction response', 'entities', {
|
|
150
|
+
context: { response: text.substring(0, 200) },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Filter empty arrays to undefined
|
|
156
|
+
*/
|
|
157
|
+
filterEmpty(arr) {
|
|
158
|
+
if (!arr || arr.length === 0)
|
|
159
|
+
return undefined;
|
|
160
|
+
return arr.filter((s) => s && s.trim());
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Parse date entities
|
|
164
|
+
*/
|
|
165
|
+
parseDates(dates) {
|
|
166
|
+
if (!dates || dates.length === 0)
|
|
167
|
+
return undefined;
|
|
168
|
+
return dates.map((d) => ({
|
|
169
|
+
raw: d.raw,
|
|
170
|
+
parsed: this.tryParseDate(d.raw),
|
|
171
|
+
type: this.validateDateType(d.type),
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Try to parse a date string
|
|
176
|
+
*/
|
|
177
|
+
tryParseDate(raw) {
|
|
178
|
+
try {
|
|
179
|
+
const parsed = new Date(raw);
|
|
180
|
+
if (!isNaN(parsed.getTime())) {
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Ignore parse errors
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Validate date type
|
|
191
|
+
*/
|
|
192
|
+
validateDateType(type) {
|
|
193
|
+
if (type === 'order_date' ||
|
|
194
|
+
type === 'delivery_date' ||
|
|
195
|
+
type === 'request_date' ||
|
|
196
|
+
type === 'general') {
|
|
197
|
+
return type;
|
|
198
|
+
}
|
|
199
|
+
return 'general';
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Parse monetary amounts
|
|
203
|
+
*/
|
|
204
|
+
parseMonetaryAmounts(amounts) {
|
|
205
|
+
if (!amounts || amounts.length === 0)
|
|
206
|
+
return undefined;
|
|
207
|
+
return amounts.map((a) => ({
|
|
208
|
+
raw: a.raw,
|
|
209
|
+
amount: a.amount,
|
|
210
|
+
currency: a.currency || 'USD',
|
|
211
|
+
context: a.context,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Extract entities using regex patterns (fallback)
|
|
216
|
+
*/
|
|
217
|
+
extractWithRegex(message) {
|
|
218
|
+
const text = `${message.content.subject ?? ''}\n${message.content.text}`;
|
|
219
|
+
// Extract order numbers
|
|
220
|
+
const orderMatches = [...text.matchAll(PATTERNS.orderNumber)];
|
|
221
|
+
const orderNumbers = orderMatches.map((m) => m[1]).filter(Boolean);
|
|
222
|
+
// Extract Catalist invoice numbers (7-digit starting with 91)
|
|
223
|
+
const invoiceMatches = [...text.matchAll(PATTERNS.catalistInvoice)];
|
|
224
|
+
const invoiceNumbers = invoiceMatches.map((m) => m[1]).filter(Boolean);
|
|
225
|
+
// Merge invoice numbers into order numbers (they're often used interchangeably)
|
|
226
|
+
orderNumbers.push(...invoiceNumbers);
|
|
227
|
+
// Extract ASINs (Amazon Standard Identification Number)
|
|
228
|
+
const asinMatches = [...text.matchAll(PATTERNS.asin)];
|
|
229
|
+
const asins = asinMatches.map((m) => m[1]).filter(Boolean);
|
|
230
|
+
// Extract SKUs
|
|
231
|
+
const skuMatches = [...text.matchAll(PATTERNS.sku)];
|
|
232
|
+
const productSkus = [
|
|
233
|
+
...asins, // ASINs are treated as product identifiers
|
|
234
|
+
...skuMatches.map((m) => m[1]).filter(Boolean),
|
|
235
|
+
];
|
|
236
|
+
// Extract monetary amounts
|
|
237
|
+
const moneyMatches = [...text.matchAll(PATTERNS.money)];
|
|
238
|
+
const monetaryAmounts = moneyMatches
|
|
239
|
+
.map((m) => {
|
|
240
|
+
const amountStr = m[1] || m[2];
|
|
241
|
+
if (!amountStr)
|
|
242
|
+
return null;
|
|
243
|
+
const amount = parseFloat(amountStr.replace(/,/g, ''));
|
|
244
|
+
if (isNaN(amount))
|
|
245
|
+
return null;
|
|
246
|
+
return {
|
|
247
|
+
raw: m[0],
|
|
248
|
+
amount,
|
|
249
|
+
currency: 'USD',
|
|
250
|
+
};
|
|
251
|
+
})
|
|
252
|
+
.filter((a) => a !== null);
|
|
253
|
+
// Extract dates
|
|
254
|
+
const dateMatches = [...text.matchAll(PATTERNS.date)];
|
|
255
|
+
const dates = dateMatches.map((m) => ({
|
|
256
|
+
raw: m[0],
|
|
257
|
+
parsed: this.tryParseDate(m[0]),
|
|
258
|
+
type: 'general',
|
|
259
|
+
}));
|
|
260
|
+
// Extract emails
|
|
261
|
+
const emailMatches = [...text.matchAll(PATTERNS.email)];
|
|
262
|
+
const emails = emailMatches.map((m) => m[0]).filter(Boolean);
|
|
263
|
+
// Extract phone numbers
|
|
264
|
+
const phoneMatches = [...text.matchAll(PATTERNS.phone)];
|
|
265
|
+
const phoneNumbers = phoneMatches.map((m) => m[0]).filter(Boolean);
|
|
266
|
+
return {
|
|
267
|
+
orderNumbers: orderNumbers.length > 0 ? [...new Set(orderNumbers)] : undefined,
|
|
268
|
+
productSkus: productSkus.length > 0 ? [...new Set(productSkus)] : undefined,
|
|
269
|
+
monetaryAmounts: monetaryAmounts.length > 0 ? monetaryAmounts : undefined,
|
|
270
|
+
dates: dates.length > 0 ? dates : undefined,
|
|
271
|
+
emails: emails.length > 0 ? [...new Set(emails)] : undefined,
|
|
272
|
+
phoneNumbers: phoneNumbers.length > 0 ? [...new Set(phoneNumbers)] : undefined,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// =============================================================================
|
|
277
|
+
// Singleton and Utility Functions
|
|
278
|
+
// =============================================================================
|
|
279
|
+
let entityService = null;
|
|
280
|
+
export function getEntityExtractionService() {
|
|
281
|
+
if (!entityService) {
|
|
282
|
+
entityService = new EntityExtractionService();
|
|
283
|
+
}
|
|
284
|
+
return entityService;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Extract entities from message
|
|
288
|
+
*/
|
|
289
|
+
export async function extractEntities(message) {
|
|
290
|
+
return getEntityExtractionService().extract(message);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Extract entities using regex (fallback)
|
|
294
|
+
*/
|
|
295
|
+
export function extractEntitiesRegex(message) {
|
|
296
|
+
return getEntityExtractionService().extractWithRegex(message);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Merge extracted entities from multiple sources
|
|
300
|
+
*/
|
|
301
|
+
export function mergeEntities(...entities) {
|
|
302
|
+
const merged = {};
|
|
303
|
+
for (const e of entities) {
|
|
304
|
+
if (e.orderNumbers) {
|
|
305
|
+
merged.orderNumbers = [
|
|
306
|
+
...new Set([...(merged.orderNumbers ?? []), ...e.orderNumbers]),
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
if (e.productNames) {
|
|
310
|
+
merged.productNames = [
|
|
311
|
+
...new Set([...(merged.productNames ?? []), ...e.productNames]),
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
if (e.productSkus) {
|
|
315
|
+
merged.productSkus = [...new Set([...(merged.productSkus ?? []), ...e.productSkus])];
|
|
316
|
+
}
|
|
317
|
+
if (e.dates) {
|
|
318
|
+
merged.dates = [...(merged.dates ?? []), ...e.dates];
|
|
319
|
+
}
|
|
320
|
+
if (e.monetaryAmounts) {
|
|
321
|
+
merged.monetaryAmounts = [...(merged.monetaryAmounts ?? []), ...e.monetaryAmounts];
|
|
322
|
+
}
|
|
323
|
+
if (e.customerReferences) {
|
|
324
|
+
merged.customerReferences = [
|
|
325
|
+
...new Set([...(merged.customerReferences ?? []), ...e.customerReferences]),
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
if (e.companyNames) {
|
|
329
|
+
merged.companyNames = [
|
|
330
|
+
...new Set([...(merged.companyNames ?? []), ...e.companyNames]),
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
if (e.personNames) {
|
|
334
|
+
merged.personNames = [...new Set([...(merged.personNames ?? []), ...e.personNames])];
|
|
335
|
+
}
|
|
336
|
+
if (e.phoneNumbers) {
|
|
337
|
+
merged.phoneNumbers = [...new Set([...(merged.phoneNumbers ?? []), ...e.phoneNumbers])];
|
|
338
|
+
}
|
|
339
|
+
if (e.emails) {
|
|
340
|
+
merged.emails = [...new Set([...(merged.emails ?? []), ...e.emails])];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return merged;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Check if entities contain order reference
|
|
347
|
+
*/
|
|
348
|
+
export function hasOrderReference(entities) {
|
|
349
|
+
return !!(entities.orderNumbers && entities.orderNumbers.length > 0);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Check if entities contain product reference
|
|
353
|
+
*/
|
|
354
|
+
export function hasProductReference(entities) {
|
|
355
|
+
return !!((entities.productNames && entities.productNames.length > 0) ||
|
|
356
|
+
(entities.productSkus && entities.productSkus.length > 0));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get primary order number from entities
|
|
360
|
+
*/
|
|
361
|
+
export function getPrimaryOrderNumber(entities) {
|
|
362
|
+
return entities.orderNumbers?.[0];
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get total monetary amount mentioned
|
|
366
|
+
*/
|
|
367
|
+
export function getTotalAmount(entities) {
|
|
368
|
+
if (!entities.monetaryAmounts || entities.monetaryAmounts.length === 0) {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
// If only one amount, return it
|
|
372
|
+
if (entities.monetaryAmounts.length === 1) {
|
|
373
|
+
return entities.monetaryAmounts[0]?.amount;
|
|
374
|
+
}
|
|
375
|
+
// If multiple amounts, return the largest (likely the total)
|
|
376
|
+
return Math.max(...entities.monetaryAmounts.map((a) => a.amount));
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=entities.js.map
|