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.
Files changed (140) hide show
  1. package/dist/admin-portal.d.ts +43 -0
  2. package/dist/admin-portal.d.ts.map +1 -0
  3. package/dist/admin-portal.js +166 -0
  4. package/dist/admin-portal.js.map +1 -0
  5. package/dist/analysis/entities.d.ts +73 -0
  6. package/dist/analysis/entities.d.ts.map +1 -0
  7. package/dist/analysis/entities.js +378 -0
  8. package/dist/analysis/entities.js.map +1 -0
  9. package/dist/analysis/index.d.ts +44 -0
  10. package/dist/analysis/index.d.ts.map +1 -0
  11. package/dist/analysis/index.js +243 -0
  12. package/dist/analysis/index.js.map +1 -0
  13. package/dist/analysis/intent.d.ts +49 -0
  14. package/dist/analysis/intent.d.ts.map +1 -0
  15. package/dist/analysis/intent.js +320 -0
  16. package/dist/analysis/intent.js.map +1 -0
  17. package/dist/analysis/sentiment.d.ts +57 -0
  18. package/dist/analysis/sentiment.d.ts.map +1 -0
  19. package/dist/analysis/sentiment.js +351 -0
  20. package/dist/analysis/sentiment.js.map +1 -0
  21. package/dist/brand/compliance.d.ts +122 -0
  22. package/dist/brand/compliance.d.ts.map +1 -0
  23. package/dist/brand/compliance.js +378 -0
  24. package/dist/brand/compliance.js.map +1 -0
  25. package/dist/brand/forbidden-terms.d.ts +99 -0
  26. package/dist/brand/forbidden-terms.d.ts.map +1 -0
  27. package/dist/brand/forbidden-terms.js +265 -0
  28. package/dist/brand/forbidden-terms.js.map +1 -0
  29. package/dist/brand/index.d.ts +10 -0
  30. package/dist/brand/index.d.ts.map +1 -0
  31. package/dist/brand/index.js +12 -0
  32. package/dist/brand/index.js.map +1 -0
  33. package/dist/config.d.ts +325 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +492 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/delivery/index.d.ts +84 -0
  38. package/dist/delivery/index.d.ts.map +1 -0
  39. package/dist/delivery/index.js +435 -0
  40. package/dist/delivery/index.js.map +1 -0
  41. package/dist/embeddings/cache.d.ts +96 -0
  42. package/dist/embeddings/cache.d.ts.map +1 -0
  43. package/dist/embeddings/cache.js +193 -0
  44. package/dist/embeddings/cache.js.map +1 -0
  45. package/dist/embeddings/index.d.ts +152 -0
  46. package/dist/embeddings/index.d.ts.map +1 -0
  47. package/dist/embeddings/index.js +337 -0
  48. package/dist/embeddings/index.js.map +1 -0
  49. package/dist/embeddings/openai-client.d.ts +67 -0
  50. package/dist/embeddings/openai-client.d.ts.map +1 -0
  51. package/dist/embeddings/openai-client.js +190 -0
  52. package/dist/embeddings/openai-client.js.map +1 -0
  53. package/dist/errors.d.ts +302 -0
  54. package/dist/errors.d.ts.map +1 -0
  55. package/dist/errors.js +508 -0
  56. package/dist/errors.js.map +1 -0
  57. package/dist/escalation/index.d.ts +93 -0
  58. package/dist/escalation/index.d.ts.map +1 -0
  59. package/dist/escalation/index.js +436 -0
  60. package/dist/escalation/index.js.map +1 -0
  61. package/dist/extraction/deduplication.d.ts +97 -0
  62. package/dist/extraction/deduplication.d.ts.map +1 -0
  63. package/dist/extraction/deduplication.js +271 -0
  64. package/dist/extraction/deduplication.js.map +1 -0
  65. package/dist/extraction/gmail-extractor.d.ts +160 -0
  66. package/dist/extraction/gmail-extractor.d.ts.map +1 -0
  67. package/dist/extraction/gmail-extractor.js +396 -0
  68. package/dist/extraction/gmail-extractor.js.map +1 -0
  69. package/dist/extraction/gmail-token-manager.d.ts +36 -0
  70. package/dist/extraction/gmail-token-manager.d.ts.map +1 -0
  71. package/dist/extraction/gmail-token-manager.js +146 -0
  72. package/dist/extraction/gmail-token-manager.js.map +1 -0
  73. package/dist/extraction/index.d.ts +13 -0
  74. package/dist/extraction/index.d.ts.map +1 -0
  75. package/dist/extraction/index.js +20 -0
  76. package/dist/extraction/index.js.map +1 -0
  77. package/dist/extraction/pii-handler.d.ts +100 -0
  78. package/dist/extraction/pii-handler.d.ts.map +1 -0
  79. package/dist/extraction/pii-handler.js +295 -0
  80. package/dist/extraction/pii-handler.js.map +1 -0
  81. package/dist/extraction/pipeline.d.ts +94 -0
  82. package/dist/extraction/pipeline.d.ts.map +1 -0
  83. package/dist/extraction/pipeline.js +380 -0
  84. package/dist/extraction/pipeline.js.map +1 -0
  85. package/dist/extraction/quality-filter.d.ts +99 -0
  86. package/dist/extraction/quality-filter.d.ts.map +1 -0
  87. package/dist/extraction/quality-filter.js +370 -0
  88. package/dist/extraction/quality-filter.js.map +1 -0
  89. package/dist/extraction/rate-limiter.d.ts +90 -0
  90. package/dist/extraction/rate-limiter.d.ts.map +1 -0
  91. package/dist/extraction/rate-limiter.js +242 -0
  92. package/dist/extraction/rate-limiter.js.map +1 -0
  93. package/dist/extraction/state-manager.d.ts +126 -0
  94. package/dist/extraction/state-manager.d.ts.map +1 -0
  95. package/dist/extraction/state-manager.js +344 -0
  96. package/dist/extraction/state-manager.js.map +1 -0
  97. package/dist/generation/index.d.ts +75 -0
  98. package/dist/generation/index.d.ts.map +1 -0
  99. package/dist/generation/index.js +641 -0
  100. package/dist/generation/index.js.map +1 -0
  101. package/dist/index.d.ts +96 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +233 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/intake/index.d.ts +15 -0
  106. package/dist/intake/index.d.ts.map +1 -0
  107. package/dist/intake/index.js +19 -0
  108. package/dist/intake/index.js.map +1 -0
  109. package/dist/intake/normalizer.d.ts +163 -0
  110. package/dist/intake/normalizer.d.ts.map +1 -0
  111. package/dist/intake/normalizer.js +309 -0
  112. package/dist/intake/normalizer.js.map +1 -0
  113. package/dist/intake/postmark.d.ts +72 -0
  114. package/dist/intake/postmark.d.ts.map +1 -0
  115. package/dist/intake/postmark.js +276 -0
  116. package/dist/intake/postmark.js.map +1 -0
  117. package/dist/intake/slack.d.ts +106 -0
  118. package/dist/intake/slack.d.ts.map +1 -0
  119. package/dist/intake/slack.js +378 -0
  120. package/dist/intake/slack.js.map +1 -0
  121. package/dist/intake/twilio.d.ts +86 -0
  122. package/dist/intake/twilio.d.ts.map +1 -0
  123. package/dist/intake/twilio.js +283 -0
  124. package/dist/intake/twilio.js.map +1 -0
  125. package/dist/knowledge/index.d.ts +100 -0
  126. package/dist/knowledge/index.d.ts.map +1 -0
  127. package/dist/knowledge/index.js +516 -0
  128. package/dist/knowledge/index.js.map +1 -0
  129. package/dist/knowledge/invoice-resolver.d.ts +62 -0
  130. package/dist/knowledge/invoice-resolver.d.ts.map +1 -0
  131. package/dist/knowledge/invoice-resolver.js +267 -0
  132. package/dist/knowledge/invoice-resolver.js.map +1 -0
  133. package/dist/types.d.ts +535 -0
  134. package/dist/types.d.ts.map +1 -0
  135. package/dist/types.js +48 -0
  136. package/dist/types.js.map +1 -0
  137. package/ga-service-account.json +13 -0
  138. package/gmail-knowledge-migration.sql +149 -0
  139. package/nul +1 -0
  140. 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":"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"}
@@ -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