@open-mercato/core 0.4.5-develop-3f7d1d7925 → 0.4.5-develop-509cc99488
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import sanitizeHtml from "sanitize-html";
|
|
2
3
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
4
|
import { InboxEmail, InboxProposal, InboxProposalAction, InboxDiscrepancy, InboxSettings } from "../data/entities.js";
|
|
4
5
|
import { matchContacts } from "../lib/contactMatcher.js";
|
|
@@ -626,7 +627,10 @@ function enrichDraftReplyTargets(draftReplies, participantEmailMap) {
|
|
|
626
627
|
function buildFullTextForExtraction(email) {
|
|
627
628
|
let text = email.rawText || "";
|
|
628
629
|
if (!text && email.rawHtml) {
|
|
629
|
-
text = email.rawHtml
|
|
630
|
+
text = sanitizeHtml(email.rawHtml, {
|
|
631
|
+
allowedTags: [],
|
|
632
|
+
allowedAttributes: {}
|
|
633
|
+
}).replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
630
634
|
}
|
|
631
635
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\t/g, " ").replace(/ {2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
632
636
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/inbox_ops/subscribers/extractionWorker.ts"],
|
|
4
|
-
"sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityClass } from '@mikro-orm/core'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxEmail, InboxProposal, InboxProposalAction, InboxDiscrepancy, InboxSettings } from '../data/entities'\nimport type { ExtractedParticipant, InboxDiscrepancyType } from '../data/entities'\nimport { extractionOutputSchema } from '../data/validators'\nimport { matchContacts } from '../lib/contactMatcher'\nimport { buildExtractionSystemPrompt, buildExtractionUserPrompt } from '../lib/extractionPrompt'\nimport { REQUIRED_FEATURES_MAP } from '../lib/constants'\nimport { fetchCatalogProductsForExtraction } from '../lib/catalogLookup'\nimport { enrichOrderPayload } from '../lib/payloadEnrichment'\nimport { validatePrices } from '../lib/priceValidator'\nimport { extractParticipantsFromThread } from '../lib/emailParser'\nimport { runExtractionWithConfiguredProvider } from '../lib/llmProvider'\nimport { safeParsePayloadJson } from '../lib/validation'\nimport { emitInboxOpsEvent } from '../events'\n\nexport const metadata = {\n event: 'inbox_ops.email.received',\n persistent: true,\n id: 'inbox_ops:extraction-worker',\n}\n\ninterface EmailReceivedPayload {\n emailId: string\n tenantId: string\n organizationId: string\n forwardedByAddress: string\n subject: string\n}\n\ninterface ResolverContext {\n resolve: <T = unknown>(name: string) => T\n}\n\ninterface ExtractionEntityClasses {\n customerEntity?: EntityClass<{ id: string; kind: string; displayName: string; primaryEmail?: string | null }>\n catalogProduct?: EntityClass<{ id: string; name: string; sku?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>\n catalogProductPrice?: EntityClass<{ product?: unknown; unitPriceNet?: string | null; unitPriceGross?: string | null; currencyCode?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null; createdAt?: Date }>\n salesOrder?: EntityClass<{ id: string; orderNumber: string; customerReference?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>\n salesChannel?: EntityClass<{ id: string; name: string; currencyCode?: string; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>\n customerAddress?: EntityClass<{ id: string; isPrimary: boolean; tenantId?: string; organizationId?: string; entity?: { id: string } | string; createdAt?: Date }>\n}\n\ninterface DiscrepancyInput {\n actionIndex?: number\n type: InboxDiscrepancyType\n severity: 'warning' | 'error'\n description: string\n expectedValue?: string | null\n foundValue?: string | null\n}\n\nfunction tryResolve<T>(ctx: ResolverContext, name: string): T | undefined {\n try {\n return ctx.resolve<T>(name)\n } catch {\n console.debug(`[inbox_ops:extraction] optional dependency \"${name}\" not available`)\n return undefined\n }\n}\n\nfunction resolveEntityClasses(ctx: ResolverContext): ExtractionEntityClasses {\n return {\n customerEntity: tryResolve(ctx, 'CustomerEntity'),\n catalogProduct: tryResolve(ctx, 'CatalogProduct'),\n catalogProductPrice: tryResolve(ctx, 'CatalogProductPrice'),\n salesOrder: tryResolve(ctx, 'SalesOrder'),\n salesChannel: tryResolve(ctx, 'SalesChannel'),\n customerAddress: tryResolve(ctx, 'CustomerAddress'),\n }\n}\n\nfunction createDiscrepancy(\n em: EntityManager,\n proposalId: string,\n allActions: { id: string }[],\n input: DiscrepancyInput,\n scope: { organizationId: string; tenantId: string },\n) {\n return em.create(InboxDiscrepancy, {\n proposalId,\n actionId: input.actionIndex !== undefined && allActions[input.actionIndex]\n ? allActions[input.actionIndex].id\n : null,\n type: input.type,\n severity: input.severity,\n description: input.description,\n expectedValue: input.expectedValue || null,\n foundValue: input.foundValue || null,\n resolved: false,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n}\n\nexport default async function handle(payload: EmailReceivedPayload, ctx: ResolverContext) {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const entityClasses = resolveEntityClasses(ctx)\n\n // Optimistic lock: atomically claim the email for processing.\n // If another worker already claimed it, nativeUpdate returns 0 rows.\n const claimed = await em.nativeUpdate(\n InboxEmail,\n { id: payload.emailId, status: 'received' },\n { status: 'processing' },\n )\n if (claimed === 0) return\n\n const email = await findOneWithDecryption(\n em,\n InboxEmail,\n { id: payload.emailId },\n undefined,\n { tenantId: payload.tenantId, organizationId: payload.organizationId },\n )\n if (!email) {\n console.error(`[inbox_ops:extraction-worker] Email not found: ${payload.emailId}`)\n return\n }\n\n try {\n const scope = {\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n }\n\n // Load tenant settings for working language\n const settings = await findOneWithDecryption(em, InboxSettings, { organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null }, undefined, scope)\n const workingLanguage = settings?.workingLanguage || 'en'\n\n // Step 1: Build full text for LLM extraction.\n // Use rawText (or derive from rawHtml) instead of cleanedText because\n // cleanedText strips quoted replies \u2014 which contain the actual order content\n // in forwarded email threads.\n const fullText = buildFullTextForExtraction(email)\n if (!fullText.trim()) {\n email.status = 'failed'\n email.processingError = 'No text content found in email'\n await em.flush()\n return\n }\n\n // Step 2: Match contacts from thread participants\n const threadParticipants = extractParticipantsFromThread(email)\n const contactMatches = await matchContacts(em, threadParticipants, scope,\n entityClasses.customerEntity ? { customerEntityClass: entityClasses.customerEntity } : undefined,\n )\n\n // Step 2b: Fetch catalog products for LLM context\n const catalogProducts = await fetchCatalogProductsForExtraction(em, scope,\n entityClasses.catalogProduct && entityClasses.catalogProductPrice\n ? { catalogProductClass: entityClasses.catalogProduct, catalogProductPriceClass: entityClasses.catalogProductPrice }\n : undefined,\n )\n\n // Step 3: Call LLM for extraction\n const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || '204800', 10)\n const truncatedText = fullText.slice(0, maxTextSize)\n\n const systemPrompt = buildExtractionSystemPrompt(contactMatches, catalogProducts, undefined, workingLanguage)\n const userPrompt = buildExtractionUserPrompt(truncatedText)\n\n let extractionResult: ReturnType<typeof extractionOutputSchema.parse>\n let tokensUsed = 0\n let modelUsed = ''\n\n try {\n const timeoutMsRaw = Number.parseInt(process.env.INBOX_OPS_LLM_TIMEOUT_MS || '90000', 10)\n const timeoutMs = Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0 ? timeoutMsRaw : 90000\n const extraction = await runExtractionWithConfiguredProvider({\n systemPrompt,\n userPrompt,\n modelOverride: process.env.INBOX_OPS_LLM_MODEL,\n timeoutMs,\n })\n extractionResult = extraction.object\n tokensUsed = extraction.totalTokens\n modelUsed = extraction.modelWithProvider\n } catch (llmError) {\n email.status = 'failed'\n email.processingError = `LLM extraction failed: ${llmError instanceof Error ? llmError.message : String(llmError)}`\n await em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.email.failed', {\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n error: email.processingError,\n })\n } catch (eventError) {\n console.error('[inbox_ops:extraction-worker] Failed to emit email.failed event:', eventError)\n }\n\n return\n }\n\n const confidenceThresholdRaw = Number.parseFloat(process.env.INBOX_OPS_CONFIDENCE_THRESHOLD || '0.5')\n const confidenceThreshold = Number.isFinite(confidenceThresholdRaw)\n ? Math.min(Math.max(confidenceThresholdRaw, 0), 1)\n : 0.5\n const requiresReview = extractionResult.confidence < confidenceThreshold\n\n // Step 4: Validate prices for order/quote actions\n const orderActions = extractionResult.proposedActions\n .map((action, index) => ({\n ...action, payload: safeParsePayloadJson(action.payloadJson), index,\n }))\n .filter((a) => a.actionType === 'create_order' || a.actionType === 'create_quote')\n\n const priceDiscrepancies = await validatePrices(em, orderActions, scope,\n entityClasses.catalogProductPrice ? { catalogProductPriceClass: entityClasses.catalogProductPrice } : undefined,\n )\n\n // Step 4b: Check for duplicate orders by customerReference\n const duplicateOrderDiscrepancies = await detectDuplicateOrders(em, orderActions, scope, entityClasses.salesOrder)\n\n // Step 5: Match LLM-discovered participants not found in email headers.\n // Header-based matchContacts (step 2) only covers From/To/Cc addresses.\n // In forwarded threads, the original sender is in the body, not the headers.\n const headerEmails = new Set(contactMatches.map((m) => m.participant.email.toLowerCase()))\n const llmOnlyParticipants = extractionResult.participants\n .filter((p) => p.email && !headerEmails.has(p.email.toLowerCase()))\n .map((p) => ({ name: p.name, email: p.email, role: p.role || 'unknown' }))\n\n if (llmOnlyParticipants.length > 0) {\n const llmContactMatches = await matchContacts(em, llmOnlyParticipants, scope,\n entityClasses.customerEntity ? { customerEntityClass: entityClasses.customerEntity } : undefined,\n )\n contactMatches.push(...llmContactMatches)\n }\n\n // Step 5b: Merge contact match data into participants\n const enrichedParticipants: ExtractedParticipant[] = extractionResult.participants.map((p) => {\n const match = contactMatches.find(\n (m) => m.participant.email.toLowerCase() === p.email.toLowerCase(),\n )\n return {\n ...p,\n matchedContactId: match?.match?.contactId || null,\n matchedContactType: match?.match?.contactType || null,\n matchConfidence: match?.match?.confidence,\n }\n })\n\n // Step 6: Detect partial forward\n const possiblyIncomplete = extractionResult.possiblyIncomplete || detectPartialForward(email)\n\n // Step 6b: Normalize + enrich order/quote payloads\n const enrichmentDiscrepancies: DiscrepancyInput[] = []\n for (const [actionIndex, action] of extractionResult.proposedActions.entries()) {\n if (action.actionType === 'create_order' || action.actionType === 'create_quote') {\n const parsedPayload = safeParsePayloadJson(action.payloadJson)\n\n normalizeOrderPayloadFields(parsedPayload)\n\n const { payload: enriched, warnings } = await enrichOrderPayload(parsedPayload, {\n em,\n scope,\n contactMatches,\n catalogProducts,\n senderEmail: email.forwardedByAddress,\n salesChannelClass: entityClasses.salesChannel,\n customerAddressClass: entityClasses.customerAddress,\n })\n\n action.payloadJson = JSON.stringify(enriched)\n\n // Discrepancy descriptions are stored in the DB and rendered on the proposal review page.\n // Not i18n keys \u2014 the proposal UI displays them as-is for operator guidance.\n for (const warning of warnings) {\n if (warning === 'no_channel_resolved') {\n enrichmentDiscrepancies.push({\n actionIndex,\n type: 'other',\n severity: 'error',\n description: 'No sales channel available. Create a channel in Sales settings before accepting this order.',\n })\n } else if (warning === 'no_currency_resolved') {\n enrichmentDiscrepancies.push({\n actionIndex,\n type: 'currency_mismatch',\n severity: 'warning',\n description: 'No currency could be resolved for this order. Set a currency code or configure a sales channel with a default currency.',\n })\n }\n }\n }\n }\n\n // Step 6b-2: Enrich create_contact payloads with participant emails when the LLM omitted them,\n // and fix hallucinated draft_reply target emails using known participant data.\n const participantEmailMap = buildParticipantEmailMap(contactMatches, extractionResult.participants)\n enrichCreateContactEmails(extractionResult.proposedActions, participantEmailMap)\n enrichDraftReplyTargets(extractionResult.draftReplies, participantEmailMap)\n\n // Step 6c: Detect unresolved products and auto-generate create_product actions\n const productNotFoundDiscrepancies: DiscrepancyInput[] = []\n const autoProductActions: { actionType: 'create_product'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] = []\n const seenProductNames = new Set<string>()\n\n for (const [actionIndex, action] of extractionResult.proposedActions.entries()) {\n if (action.actionType !== 'create_order' && action.actionType !== 'create_quote') continue\n const parsedPayload = safeParsePayloadJson(action.payloadJson)\n const lineItems = Array.isArray(parsedPayload.lineItems)\n ? (parsedPayload.lineItems as Record<string, unknown>[])\n : []\n for (const item of lineItems) {\n if (!item.productId) {\n const productName = typeof item.productName === 'string'\n ? item.productName\n : (typeof item.description === 'string' ? item.description : 'Unknown')\n productNotFoundDiscrepancies.push({\n actionIndex,\n type: 'product_not_found',\n severity: 'error',\n description: `Product \"${productName}\" could not be matched to any catalog product`,\n foundValue: productName,\n })\n const nameKey = productName.toLowerCase().trim()\n if (nameKey && nameKey !== 'unknown' && !seenProductNames.has(nameKey)) {\n seenProductNames.add(nameKey)\n const sku = typeof item.sku === 'string' ? item.sku : undefined\n const unitPrice = typeof item.unitPrice === 'string' ? item.unitPrice : undefined\n const currencyCode = typeof parsedPayload.currencyCode === 'string' ? parsedPayload.currencyCode : undefined\n autoProductActions.push({\n actionType: 'create_product',\n description: `Create catalog product \"${productName}\"`,\n confidence: 0.9,\n requiredFeature: REQUIRED_FEATURES_MAP.create_product,\n payloadJson: JSON.stringify({\n title: productName,\n ...(sku && { sku }),\n ...(unitPrice && { unitPrice }),\n ...(currencyCode && { currencyCode }),\n kind: 'product',\n }),\n })\n }\n }\n }\n }\n\n // Step 7: Create proposal + actions + discrepancies atomically\n const proposalId = randomUUID()\n const proposal = em.create(InboxProposal, {\n id: proposalId,\n inboxEmailId: email.id,\n summary: extractionResult.summary,\n participants: enrichedParticipants,\n confidence: String(extractionResult.confidence.toFixed(2)),\n detectedLanguage: extractionResult.detectedLanguage || email.detectedLanguage,\n status: 'pending',\n possiblyIncomplete,\n llmModel: modelUsed,\n llmTokensUsed: tokensUsed,\n workingLanguage,\n organizationId: email.organizationId,\n tenantId: email.tenantId,\n })\n em.persist(proposal)\n\n // Step 6d: Auto-generate create_contact actions for unmatched participants (from headers)\n const autoContactActions = buildContactActionsForUnmatchedParticipants(\n contactMatches,\n extractionResult.proposedActions,\n email.toAddress,\n )\n\n // Step 6d-2: Also generate create_contact for LLM-discovered unmatched participants\n const llmContactActions = buildContactActionsForUnmatchedLlmParticipants(\n enrichedParticipants,\n contactMatches,\n extractionResult.proposedActions,\n autoContactActions,\n email.toAddress,\n )\n autoContactActions.push(...llmContactActions)\n\n // Step 6e: Auto-generate link_contact actions for matched participants\n const autoLinkActions = buildLinkContactActionsForMatchedParticipants(\n contactMatches,\n extractionResult.proposedActions,\n email.toAddress,\n )\n\n // Create actions \u2014 contact & product creation actions go first so they're executed before orders\n const combinedProposedActions = [...autoContactActions, ...autoLinkActions, ...autoProductActions, ...extractionResult.proposedActions]\n const allActions = [\n ...combinedProposedActions.map((action, index) => {\n const parsedPayload = safeParsePayloadJson(action.payloadJson)\n return em.create(InboxProposalAction, {\n id: randomUUID(),\n proposalId: proposalId,\n sortOrder: index,\n actionType: action.actionType,\n description: action.description,\n payload: parsedPayload,\n status: 'pending',\n confidence: String(action.confidence.toFixed(2)),\n requiredFeature: action.requiredFeature || REQUIRED_FEATURES_MAP[action.actionType] || null,\n organizationId: email.organizationId,\n tenantId: email.tenantId,\n })\n }),\n ...extractionResult.draftReplies.map((reply, index) =>\n em.create(InboxProposalAction, {\n id: randomUUID(),\n proposalId: proposalId,\n sortOrder: combinedProposedActions.length + index,\n actionType: 'draft_reply',\n description: `Draft reply to ${reply.toName || reply.to}: ${reply.subject}`,\n payload: {\n to: reply.to,\n toName: reply.toName,\n subject: reply.subject,\n body: reply.body,\n context: reply.context,\n replyTo: email.replyTo,\n inReplyToMessageId: email.messageId,\n references: email.emailReferences,\n },\n status: 'pending',\n confidence: String(extractionResult.confidence.toFixed(2)),\n requiredFeature: 'inbox_ops.replies.send',\n organizationId: email.organizationId,\n tenantId: email.tenantId,\n }),\n ),\n ]\n allActions.forEach((a) => em.persist(a))\n\n // Discrepancy actionIndex values reference extractionResult.proposedActions,\n // but allActions prepends auto-generated actions. Offset indices accordingly.\n const actionIndexOffset = autoContactActions.length + autoLinkActions.length + autoProductActions.length\n const offsetIndex = (d: DiscrepancyInput): DiscrepancyInput =>\n d.actionIndex !== undefined ? { ...d, actionIndex: d.actionIndex + actionIndexOffset } : d\n\n // Create discrepancies using factory\n const allDiscrepancies = [\n ...extractionResult.discrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...priceDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...duplicateOrderDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...productNotFoundDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...enrichmentDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ]\n\n // Flag unmatched contacts as discrepancies (from header-based matches + LLM-discovered participants)\n const contactDiscrepancyEmails = new Set<string>()\n for (const match of contactMatches) {\n if (!match.match && match.participant.email) {\n const emailLower = match.participant.email.toLowerCase()\n contactDiscrepancyEmails.add(emailLower)\n allDiscrepancies.push(\n createDiscrepancy(em, proposalId, allActions, {\n type: 'unknown_contact',\n severity: 'warning',\n description: `No matching contact found for ${match.participant.name} (${match.participant.email})`,\n foundValue: match.participant.email,\n }, scope),\n )\n }\n }\n for (const participant of enrichedParticipants) {\n if (participant.matchedContactId) continue\n const emailLower = (participant.email || '').toLowerCase()\n if (!emailLower || contactDiscrepancyEmails.has(emailLower)) continue\n contactDiscrepancyEmails.add(emailLower)\n allDiscrepancies.push(\n createDiscrepancy(em, proposalId, allActions, {\n type: 'unknown_contact',\n severity: 'warning',\n description: `No matching contact found for ${participant.name} (${participant.email})`,\n foundValue: participant.email,\n }, scope),\n )\n }\n\n // Flag draft_reply actions that target unmatched contacts (blocks accept)\n const matchedEmails = new Set(\n contactMatches\n .filter((m) => m.match?.contactId)\n .map((m) => m.participant.email.toLowerCase()),\n )\n for (const [actionIndex, action] of allActions.entries()) {\n if (action.actionType !== 'draft_reply') continue\n const payload = action.payload as Record<string, unknown> | null\n const toEmail = typeof payload?.to === 'string' ? payload.to.trim().toLowerCase() : ''\n if (toEmail && !matchedEmails.has(toEmail)) {\n allDiscrepancies.push(\n createDiscrepancy(em, proposalId, allActions, {\n actionIndex,\n type: 'unknown_contact',\n severity: 'error',\n description: `Draft reply target \"${toEmail}\" has no matching contact. Create the contact first.`,\n foundValue: toEmail,\n }, scope),\n )\n }\n }\n\n allDiscrepancies.forEach((d) => em.persist(d))\n\n // Step 8: Update email status\n email.status = requiresReview ? 'needs_review' : 'processed'\n email.detectedLanguage = extractionResult.detectedLanguage || email.detectedLanguage\n\n await em.flush()\n\n // Step 9: Emit events\n try {\n await emitInboxOpsEvent('inbox_ops.email.processed', {\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n })\n\n await emitInboxOpsEvent('inbox_ops.proposal.created', {\n proposalId: proposal.id,\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n actionCount: allActions.length,\n discrepancyCount: allDiscrepancies.length,\n confidence: proposal.confidence,\n summary: proposal.summary,\n })\n } catch (eventError) {\n console.error('[inbox_ops:extraction-worker] Failed to emit events:', eventError)\n }\n } catch (err) {\n email.status = 'failed'\n email.processingError = err instanceof Error ? err.message : String(err)\n await em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.email.failed', {\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n error: email.processingError,\n })\n } catch (eventError) {\n console.error('[inbox_ops:extraction-worker] Failed to emit email.failed event:', eventError)\n }\n\n console.error('[inbox_ops:extraction-worker] Extraction failed:', err)\n }\n}\n\nfunction normalizeOrderPayloadFields(payload: Record<string, unknown>): void {\n const lineItems = Array.isArray(payload.lineItems)\n ? (payload.lineItems as Record<string, unknown>[])\n : []\n for (const item of lineItems) {\n if (!item.productName && typeof item.description === 'string') {\n item.productName = item.description\n }\n if (typeof item.quantity === 'number') {\n item.quantity = String(item.quantity)\n }\n if (typeof item.unitPrice === 'number') {\n item.unitPrice = String(item.unitPrice)\n }\n }\n}\n\nfunction buildContactActionsForUnmatchedParticipants(\n contactMatches: { participant: { name: string; email: string }; match?: { contactId: string } | null }[],\n existingActions: { actionType: string; payloadJson: string }[],\n inboxAddress: string,\n): { actionType: 'create_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {\n const alreadyProposed = new Set(\n existingActions\n .filter((a) => a.actionType === 'create_contact')\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n return typeof p.email === 'string' ? p.email.toLowerCase() : ''\n })\n .filter(Boolean),\n )\n\n const inboxLower = (inboxAddress || '').toLowerCase()\n const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']\n\n return contactMatches\n .filter((m) => {\n if (m.match?.contactId) return false\n const emailLower = m.participant.email.toLowerCase()\n if (alreadyProposed.has(emailLower)) return false\n if (emailLower === inboxLower) return false\n return !systemPatterns.some((p) => emailLower.includes(p))\n })\n .map((m) => ({\n actionType: 'create_contact' as const,\n description: `Create contact for ${m.participant.name} (${m.participant.email})`,\n confidence: 0.9,\n requiredFeature: REQUIRED_FEATURES_MAP.create_contact,\n payloadJson: JSON.stringify({\n type: 'person',\n name: m.participant.name,\n email: m.participant.email,\n source: 'inbox_ops',\n }),\n }))\n}\n\nfunction buildLinkContactActionsForMatchedParticipants(\n contactMatches: { participant: { name: string; email: string }; match?: { contactId: string; contactType?: string; contactName?: string } | null }[],\n existingActions: { actionType: string; payloadJson: string }[],\n inboxAddress: string,\n): { actionType: 'link_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {\n const alreadyProposed = new Set(\n existingActions\n .filter((a) => a.actionType === 'link_contact')\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n const email = typeof p.emailAddress === 'string' ? p.emailAddress : (typeof p.email === 'string' ? p.email : '')\n return email.toLowerCase()\n })\n .filter(Boolean),\n )\n\n const inboxLower = (inboxAddress || '').toLowerCase()\n const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']\n\n return contactMatches\n .filter((m) => {\n if (!m.match?.contactId) return false\n const emailLower = m.participant.email.toLowerCase()\n if (alreadyProposed.has(emailLower)) return false\n if (emailLower === inboxLower) return false\n return !systemPatterns.some((p) => emailLower.includes(p))\n })\n .map((m) => ({\n actionType: 'link_contact' as const,\n description: `Link ${m.participant.name} (${m.participant.email}) to existing contact`,\n confidence: 0.95,\n requiredFeature: REQUIRED_FEATURES_MAP.link_contact,\n payloadJson: JSON.stringify({\n emailAddress: m.participant.email,\n contactId: m.match!.contactId,\n contactType: m.match!.contactType || 'person',\n contactName: m.participant.name,\n }),\n }))\n}\n\nfunction buildContactActionsForUnmatchedLlmParticipants(\n enrichedParticipants: { name: string; email: string; matchedContactId?: string | null }[],\n contactMatches: { participant: { email: string } }[],\n existingActions: { actionType: string; payloadJson: string }[],\n alreadyAutoCreated: { payloadJson: string }[],\n inboxAddress: string,\n): { actionType: 'create_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {\n const headerEmails = new Set(\n contactMatches.map((m) => m.participant.email.toLowerCase()),\n )\n\n const alreadyProposed = new Set([\n ...existingActions\n .filter((a) => a.actionType === 'create_contact')\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n return typeof p.email === 'string' ? p.email.toLowerCase() : ''\n })\n .filter(Boolean),\n ...alreadyAutoCreated\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n return typeof p.email === 'string' ? p.email.toLowerCase() : ''\n })\n .filter(Boolean),\n ])\n\n const inboxLower = (inboxAddress || '').toLowerCase()\n const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']\n\n return enrichedParticipants\n .filter((p) => {\n if (p.matchedContactId) return false\n const emailLower = (p.email || '').toLowerCase()\n if (!emailLower) return false\n if (headerEmails.has(emailLower)) return false\n if (alreadyProposed.has(emailLower)) return false\n if (emailLower === inboxLower) return false\n return !systemPatterns.some((pat) => emailLower.includes(pat))\n })\n .map((p) => ({\n actionType: 'create_contact' as const,\n description: `Create contact for ${p.name} (${p.email})`,\n confidence: 0.85,\n requiredFeature: REQUIRED_FEATURES_MAP.create_contact,\n payloadJson: JSON.stringify({\n type: 'person',\n name: p.name,\n email: p.email,\n source: 'inbox_ops',\n }),\n }))\n}\n\nasync function detectDuplicateOrders(\n em: EntityManager,\n orderActions: { actionType: string; payload: Record<string, unknown>; index: number }[],\n scope: { tenantId: string; organizationId: string },\n salesOrderClass?: EntityClass<{ id: string; orderNumber: string; customerReference?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>,\n): Promise<{ type: 'duplicate_order'; severity: 'error'; description: string; expectedValue: string | null; foundValue: string | null; actionIndex: number }[]> {\n if (!salesOrderClass) return []\n const discrepancies: { type: 'duplicate_order'; severity: 'error'; description: string; expectedValue: string | null; foundValue: string | null; actionIndex: number }[] = []\n\n for (const action of orderActions) {\n if (action.actionType !== 'create_order') continue\n\n const customerReference = typeof action.payload.customerReference === 'string'\n ? action.payload.customerReference.trim()\n : null\n\n if (!customerReference) continue\n\n try {\n const existing = await findOneWithDecryption(\n em,\n salesOrderClass,\n {\n customerReference,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (existing) {\n discrepancies.push({\n type: 'duplicate_order',\n severity: 'error',\n description: `An order with customer reference \"${customerReference}\" already exists (${existing.orderNumber || existing.id})`,\n expectedValue: null,\n foundValue: customerReference,\n actionIndex: action.index,\n })\n }\n } catch {\n // Skip duplicate detection if lookup fails\n }\n }\n\n return discrepancies\n}\n\nfunction detectPartialForward(email: InboxEmail): boolean {\n const subject = email.subject || ''\n const hasReOrFw = /^(RE|FW|Fwd):/i.test(subject)\n const messageCount = email.threadMessages?.length || 0\n return hasReOrFw && messageCount < 2\n}\n\nfunction buildParticipantEmailMap(\n contactMatches: { participant: { name: string; email: string } }[],\n llmParticipants: { name: string; email: string }[],\n): Map<string, string> {\n const nameToEmail = new Map<string, string>()\n // Header-based participants are the most reliable source\n for (const m of contactMatches) {\n if (m.participant.name && m.participant.email) {\n nameToEmail.set(m.participant.name.trim().toLowerCase(), m.participant.email.trim().toLowerCase())\n }\n }\n // LLM-extracted participants as fallback (don't overwrite header-based)\n for (const p of llmParticipants) {\n if (p.name && p.email) {\n const key = p.name.trim().toLowerCase()\n if (!nameToEmail.has(key)) {\n nameToEmail.set(key, p.email.trim().toLowerCase())\n }\n }\n }\n return nameToEmail\n}\n\nfunction enrichCreateContactEmails(\n actions: { actionType: string; payloadJson: string }[],\n participantEmailMap: Map<string, string>,\n): void {\n for (const action of actions) {\n if (action.actionType !== 'create_contact') continue\n const payload = safeParsePayloadJson(action.payloadJson)\n if (payload.email) continue\n const name = typeof payload.name === 'string' ? payload.name.trim() : ''\n if (!name) continue\n // Try exact name match first, then partial (first part before / or ,)\n const email = participantEmailMap.get(name.toLowerCase())\n ?? findPartialNameMatch(name, participantEmailMap)\n if (email) {\n payload.email = email\n action.payloadJson = JSON.stringify(payload)\n }\n }\n}\n\nfunction enrichDraftReplyTargets(\n draftReplies: { to: string; toName?: string; subject: string; body: string; context?: string }[],\n participantEmailMap: Map<string, string>,\n): void {\n const knownEmails = new Set(participantEmailMap.values())\n for (const reply of draftReplies) {\n const toEmail = reply.to.trim().toLowerCase()\n if (knownEmails.has(toEmail)) continue\n // The LLM hallucinated an email \u2014 try to resolve via toName\n const toName = (reply.toName || '').trim()\n if (!toName) continue\n const correctedEmail = participantEmailMap.get(toName.toLowerCase())\n ?? findPartialNameMatch(toName, participantEmailMap)\n if (correctedEmail) {\n reply.to = correctedEmail\n }\n }\n}\n\nfunction buildFullTextForExtraction(email: InboxEmail): string {\n let text = email.rawText || ''\n if (!text && email.rawHtml) {\n text = email.rawHtml\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<\\/p>/gi, '\\n\\n')\n .replace(/<\\/div>/gi, '\\n')\n .replace(/<\\/tr>/gi, '\\n')\n .replace(/<\\/li>/gi, '\\n')\n .replace(/<[^>]+>/g, '')\n .replace(/ /gi, ' ')\n .replace(/&/gi, '&')\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/"/gi, '\"')\n .replace(/'/gi, \"'\")\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n }\n return text\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\t/g, ' ')\n .replace(/ {2,}/g, ' ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\nfunction findPartialNameMatch(name: string, map: Map<string, string>): string | undefined {\n const lower = name.toLowerCase()\n // Split on common separators (e.g. \"Marco Rossi / Rossi Imports S.r.l.\")\n const parts = lower.split(/\\s*[\\/,]\\s*/).map((p) => p.trim()).filter(Boolean)\n for (const part of parts) {\n const match = map.get(part)\n if (match) return match\n }\n // Try matching first+last name against map keys\n for (const [mapName, mapEmail] of map) {\n if (lower.includes(mapName) || mapName.includes(lower)) {\n return mapEmail\n }\n for (const part of parts) {\n if (part.includes(mapName) || mapName.includes(part)) {\n return mapEmail\n }\n }\n }\n return undefined\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityClass } from '@mikro-orm/core'\nimport sanitizeHtml from 'sanitize-html'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxEmail, InboxProposal, InboxProposalAction, InboxDiscrepancy, InboxSettings } from '../data/entities'\nimport type { ExtractedParticipant, InboxDiscrepancyType } from '../data/entities'\nimport { extractionOutputSchema } from '../data/validators'\nimport { matchContacts } from '../lib/contactMatcher'\nimport { buildExtractionSystemPrompt, buildExtractionUserPrompt } from '../lib/extractionPrompt'\nimport { REQUIRED_FEATURES_MAP } from '../lib/constants'\nimport { fetchCatalogProductsForExtraction } from '../lib/catalogLookup'\nimport { enrichOrderPayload } from '../lib/payloadEnrichment'\nimport { validatePrices } from '../lib/priceValidator'\nimport { extractParticipantsFromThread } from '../lib/emailParser'\nimport { runExtractionWithConfiguredProvider } from '../lib/llmProvider'\nimport { safeParsePayloadJson } from '../lib/validation'\nimport { emitInboxOpsEvent } from '../events'\n\nexport const metadata = {\n event: 'inbox_ops.email.received',\n persistent: true,\n id: 'inbox_ops:extraction-worker',\n}\n\ninterface EmailReceivedPayload {\n emailId: string\n tenantId: string\n organizationId: string\n forwardedByAddress: string\n subject: string\n}\n\ninterface ResolverContext {\n resolve: <T = unknown>(name: string) => T\n}\n\ninterface ExtractionEntityClasses {\n customerEntity?: EntityClass<{ id: string; kind: string; displayName: string; primaryEmail?: string | null }>\n catalogProduct?: EntityClass<{ id: string; name: string; sku?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>\n catalogProductPrice?: EntityClass<{ product?: unknown; unitPriceNet?: string | null; unitPriceGross?: string | null; currencyCode?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null; createdAt?: Date }>\n salesOrder?: EntityClass<{ id: string; orderNumber: string; customerReference?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>\n salesChannel?: EntityClass<{ id: string; name: string; currencyCode?: string; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>\n customerAddress?: EntityClass<{ id: string; isPrimary: boolean; tenantId?: string; organizationId?: string; entity?: { id: string } | string; createdAt?: Date }>\n}\n\ninterface DiscrepancyInput {\n actionIndex?: number\n type: InboxDiscrepancyType\n severity: 'warning' | 'error'\n description: string\n expectedValue?: string | null\n foundValue?: string | null\n}\n\nfunction tryResolve<T>(ctx: ResolverContext, name: string): T | undefined {\n try {\n return ctx.resolve<T>(name)\n } catch {\n console.debug(`[inbox_ops:extraction] optional dependency \"${name}\" not available`)\n return undefined\n }\n}\n\nfunction resolveEntityClasses(ctx: ResolverContext): ExtractionEntityClasses {\n return {\n customerEntity: tryResolve(ctx, 'CustomerEntity'),\n catalogProduct: tryResolve(ctx, 'CatalogProduct'),\n catalogProductPrice: tryResolve(ctx, 'CatalogProductPrice'),\n salesOrder: tryResolve(ctx, 'SalesOrder'),\n salesChannel: tryResolve(ctx, 'SalesChannel'),\n customerAddress: tryResolve(ctx, 'CustomerAddress'),\n }\n}\n\nfunction createDiscrepancy(\n em: EntityManager,\n proposalId: string,\n allActions: { id: string }[],\n input: DiscrepancyInput,\n scope: { organizationId: string; tenantId: string },\n) {\n return em.create(InboxDiscrepancy, {\n proposalId,\n actionId: input.actionIndex !== undefined && allActions[input.actionIndex]\n ? allActions[input.actionIndex].id\n : null,\n type: input.type,\n severity: input.severity,\n description: input.description,\n expectedValue: input.expectedValue || null,\n foundValue: input.foundValue || null,\n resolved: false,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n}\n\nexport default async function handle(payload: EmailReceivedPayload, ctx: ResolverContext) {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const entityClasses = resolveEntityClasses(ctx)\n\n // Optimistic lock: atomically claim the email for processing.\n // If another worker already claimed it, nativeUpdate returns 0 rows.\n const claimed = await em.nativeUpdate(\n InboxEmail,\n { id: payload.emailId, status: 'received' },\n { status: 'processing' },\n )\n if (claimed === 0) return\n\n const email = await findOneWithDecryption(\n em,\n InboxEmail,\n { id: payload.emailId },\n undefined,\n { tenantId: payload.tenantId, organizationId: payload.organizationId },\n )\n if (!email) {\n console.error(`[inbox_ops:extraction-worker] Email not found: ${payload.emailId}`)\n return\n }\n\n try {\n const scope = {\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n }\n\n // Load tenant settings for working language\n const settings = await findOneWithDecryption(em, InboxSettings, { organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null }, undefined, scope)\n const workingLanguage = settings?.workingLanguage || 'en'\n\n // Step 1: Build full text for LLM extraction.\n // Use rawText (or derive from rawHtml) instead of cleanedText because\n // cleanedText strips quoted replies \u2014 which contain the actual order content\n // in forwarded email threads.\n const fullText = buildFullTextForExtraction(email)\n if (!fullText.trim()) {\n email.status = 'failed'\n email.processingError = 'No text content found in email'\n await em.flush()\n return\n }\n\n // Step 2: Match contacts from thread participants\n const threadParticipants = extractParticipantsFromThread(email)\n const contactMatches = await matchContacts(em, threadParticipants, scope,\n entityClasses.customerEntity ? { customerEntityClass: entityClasses.customerEntity } : undefined,\n )\n\n // Step 2b: Fetch catalog products for LLM context\n const catalogProducts = await fetchCatalogProductsForExtraction(em, scope,\n entityClasses.catalogProduct && entityClasses.catalogProductPrice\n ? { catalogProductClass: entityClasses.catalogProduct, catalogProductPriceClass: entityClasses.catalogProductPrice }\n : undefined,\n )\n\n // Step 3: Call LLM for extraction\n const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || '204800', 10)\n const truncatedText = fullText.slice(0, maxTextSize)\n\n const systemPrompt = buildExtractionSystemPrompt(contactMatches, catalogProducts, undefined, workingLanguage)\n const userPrompt = buildExtractionUserPrompt(truncatedText)\n\n let extractionResult: ReturnType<typeof extractionOutputSchema.parse>\n let tokensUsed = 0\n let modelUsed = ''\n\n try {\n const timeoutMsRaw = Number.parseInt(process.env.INBOX_OPS_LLM_TIMEOUT_MS || '90000', 10)\n const timeoutMs = Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0 ? timeoutMsRaw : 90000\n const extraction = await runExtractionWithConfiguredProvider({\n systemPrompt,\n userPrompt,\n modelOverride: process.env.INBOX_OPS_LLM_MODEL,\n timeoutMs,\n })\n extractionResult = extraction.object\n tokensUsed = extraction.totalTokens\n modelUsed = extraction.modelWithProvider\n } catch (llmError) {\n email.status = 'failed'\n email.processingError = `LLM extraction failed: ${llmError instanceof Error ? llmError.message : String(llmError)}`\n await em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.email.failed', {\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n error: email.processingError,\n })\n } catch (eventError) {\n console.error('[inbox_ops:extraction-worker] Failed to emit email.failed event:', eventError)\n }\n\n return\n }\n\n const confidenceThresholdRaw = Number.parseFloat(process.env.INBOX_OPS_CONFIDENCE_THRESHOLD || '0.5')\n const confidenceThreshold = Number.isFinite(confidenceThresholdRaw)\n ? Math.min(Math.max(confidenceThresholdRaw, 0), 1)\n : 0.5\n const requiresReview = extractionResult.confidence < confidenceThreshold\n\n // Step 4: Validate prices for order/quote actions\n const orderActions = extractionResult.proposedActions\n .map((action, index) => ({\n ...action, payload: safeParsePayloadJson(action.payloadJson), index,\n }))\n .filter((a) => a.actionType === 'create_order' || a.actionType === 'create_quote')\n\n const priceDiscrepancies = await validatePrices(em, orderActions, scope,\n entityClasses.catalogProductPrice ? { catalogProductPriceClass: entityClasses.catalogProductPrice } : undefined,\n )\n\n // Step 4b: Check for duplicate orders by customerReference\n const duplicateOrderDiscrepancies = await detectDuplicateOrders(em, orderActions, scope, entityClasses.salesOrder)\n\n // Step 5: Match LLM-discovered participants not found in email headers.\n // Header-based matchContacts (step 2) only covers From/To/Cc addresses.\n // In forwarded threads, the original sender is in the body, not the headers.\n const headerEmails = new Set(contactMatches.map((m) => m.participant.email.toLowerCase()))\n const llmOnlyParticipants = extractionResult.participants\n .filter((p) => p.email && !headerEmails.has(p.email.toLowerCase()))\n .map((p) => ({ name: p.name, email: p.email, role: p.role || 'unknown' }))\n\n if (llmOnlyParticipants.length > 0) {\n const llmContactMatches = await matchContacts(em, llmOnlyParticipants, scope,\n entityClasses.customerEntity ? { customerEntityClass: entityClasses.customerEntity } : undefined,\n )\n contactMatches.push(...llmContactMatches)\n }\n\n // Step 5b: Merge contact match data into participants\n const enrichedParticipants: ExtractedParticipant[] = extractionResult.participants.map((p) => {\n const match = contactMatches.find(\n (m) => m.participant.email.toLowerCase() === p.email.toLowerCase(),\n )\n return {\n ...p,\n matchedContactId: match?.match?.contactId || null,\n matchedContactType: match?.match?.contactType || null,\n matchConfidence: match?.match?.confidence,\n }\n })\n\n // Step 6: Detect partial forward\n const possiblyIncomplete = extractionResult.possiblyIncomplete || detectPartialForward(email)\n\n // Step 6b: Normalize + enrich order/quote payloads\n const enrichmentDiscrepancies: DiscrepancyInput[] = []\n for (const [actionIndex, action] of extractionResult.proposedActions.entries()) {\n if (action.actionType === 'create_order' || action.actionType === 'create_quote') {\n const parsedPayload = safeParsePayloadJson(action.payloadJson)\n\n normalizeOrderPayloadFields(parsedPayload)\n\n const { payload: enriched, warnings } = await enrichOrderPayload(parsedPayload, {\n em,\n scope,\n contactMatches,\n catalogProducts,\n senderEmail: email.forwardedByAddress,\n salesChannelClass: entityClasses.salesChannel,\n customerAddressClass: entityClasses.customerAddress,\n })\n\n action.payloadJson = JSON.stringify(enriched)\n\n // Discrepancy descriptions are stored in the DB and rendered on the proposal review page.\n // Not i18n keys \u2014 the proposal UI displays them as-is for operator guidance.\n for (const warning of warnings) {\n if (warning === 'no_channel_resolved') {\n enrichmentDiscrepancies.push({\n actionIndex,\n type: 'other',\n severity: 'error',\n description: 'No sales channel available. Create a channel in Sales settings before accepting this order.',\n })\n } else if (warning === 'no_currency_resolved') {\n enrichmentDiscrepancies.push({\n actionIndex,\n type: 'currency_mismatch',\n severity: 'warning',\n description: 'No currency could be resolved for this order. Set a currency code or configure a sales channel with a default currency.',\n })\n }\n }\n }\n }\n\n // Step 6b-2: Enrich create_contact payloads with participant emails when the LLM omitted them,\n // and fix hallucinated draft_reply target emails using known participant data.\n const participantEmailMap = buildParticipantEmailMap(contactMatches, extractionResult.participants)\n enrichCreateContactEmails(extractionResult.proposedActions, participantEmailMap)\n enrichDraftReplyTargets(extractionResult.draftReplies, participantEmailMap)\n\n // Step 6c: Detect unresolved products and auto-generate create_product actions\n const productNotFoundDiscrepancies: DiscrepancyInput[] = []\n const autoProductActions: { actionType: 'create_product'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] = []\n const seenProductNames = new Set<string>()\n\n for (const [actionIndex, action] of extractionResult.proposedActions.entries()) {\n if (action.actionType !== 'create_order' && action.actionType !== 'create_quote') continue\n const parsedPayload = safeParsePayloadJson(action.payloadJson)\n const lineItems = Array.isArray(parsedPayload.lineItems)\n ? (parsedPayload.lineItems as Record<string, unknown>[])\n : []\n for (const item of lineItems) {\n if (!item.productId) {\n const productName = typeof item.productName === 'string'\n ? item.productName\n : (typeof item.description === 'string' ? item.description : 'Unknown')\n productNotFoundDiscrepancies.push({\n actionIndex,\n type: 'product_not_found',\n severity: 'error',\n description: `Product \"${productName}\" could not be matched to any catalog product`,\n foundValue: productName,\n })\n const nameKey = productName.toLowerCase().trim()\n if (nameKey && nameKey !== 'unknown' && !seenProductNames.has(nameKey)) {\n seenProductNames.add(nameKey)\n const sku = typeof item.sku === 'string' ? item.sku : undefined\n const unitPrice = typeof item.unitPrice === 'string' ? item.unitPrice : undefined\n const currencyCode = typeof parsedPayload.currencyCode === 'string' ? parsedPayload.currencyCode : undefined\n autoProductActions.push({\n actionType: 'create_product',\n description: `Create catalog product \"${productName}\"`,\n confidence: 0.9,\n requiredFeature: REQUIRED_FEATURES_MAP.create_product,\n payloadJson: JSON.stringify({\n title: productName,\n ...(sku && { sku }),\n ...(unitPrice && { unitPrice }),\n ...(currencyCode && { currencyCode }),\n kind: 'product',\n }),\n })\n }\n }\n }\n }\n\n // Step 7: Create proposal + actions + discrepancies atomically\n const proposalId = randomUUID()\n const proposal = em.create(InboxProposal, {\n id: proposalId,\n inboxEmailId: email.id,\n summary: extractionResult.summary,\n participants: enrichedParticipants,\n confidence: String(extractionResult.confidence.toFixed(2)),\n detectedLanguage: extractionResult.detectedLanguage || email.detectedLanguage,\n status: 'pending',\n possiblyIncomplete,\n llmModel: modelUsed,\n llmTokensUsed: tokensUsed,\n workingLanguage,\n organizationId: email.organizationId,\n tenantId: email.tenantId,\n })\n em.persist(proposal)\n\n // Step 6d: Auto-generate create_contact actions for unmatched participants (from headers)\n const autoContactActions = buildContactActionsForUnmatchedParticipants(\n contactMatches,\n extractionResult.proposedActions,\n email.toAddress,\n )\n\n // Step 6d-2: Also generate create_contact for LLM-discovered unmatched participants\n const llmContactActions = buildContactActionsForUnmatchedLlmParticipants(\n enrichedParticipants,\n contactMatches,\n extractionResult.proposedActions,\n autoContactActions,\n email.toAddress,\n )\n autoContactActions.push(...llmContactActions)\n\n // Step 6e: Auto-generate link_contact actions for matched participants\n const autoLinkActions = buildLinkContactActionsForMatchedParticipants(\n contactMatches,\n extractionResult.proposedActions,\n email.toAddress,\n )\n\n // Create actions \u2014 contact & product creation actions go first so they're executed before orders\n const combinedProposedActions = [...autoContactActions, ...autoLinkActions, ...autoProductActions, ...extractionResult.proposedActions]\n const allActions = [\n ...combinedProposedActions.map((action, index) => {\n const parsedPayload = safeParsePayloadJson(action.payloadJson)\n return em.create(InboxProposalAction, {\n id: randomUUID(),\n proposalId: proposalId,\n sortOrder: index,\n actionType: action.actionType,\n description: action.description,\n payload: parsedPayload,\n status: 'pending',\n confidence: String(action.confidence.toFixed(2)),\n requiredFeature: action.requiredFeature || REQUIRED_FEATURES_MAP[action.actionType] || null,\n organizationId: email.organizationId,\n tenantId: email.tenantId,\n })\n }),\n ...extractionResult.draftReplies.map((reply, index) =>\n em.create(InboxProposalAction, {\n id: randomUUID(),\n proposalId: proposalId,\n sortOrder: combinedProposedActions.length + index,\n actionType: 'draft_reply',\n description: `Draft reply to ${reply.toName || reply.to}: ${reply.subject}`,\n payload: {\n to: reply.to,\n toName: reply.toName,\n subject: reply.subject,\n body: reply.body,\n context: reply.context,\n replyTo: email.replyTo,\n inReplyToMessageId: email.messageId,\n references: email.emailReferences,\n },\n status: 'pending',\n confidence: String(extractionResult.confidence.toFixed(2)),\n requiredFeature: 'inbox_ops.replies.send',\n organizationId: email.organizationId,\n tenantId: email.tenantId,\n }),\n ),\n ]\n allActions.forEach((a) => em.persist(a))\n\n // Discrepancy actionIndex values reference extractionResult.proposedActions,\n // but allActions prepends auto-generated actions. Offset indices accordingly.\n const actionIndexOffset = autoContactActions.length + autoLinkActions.length + autoProductActions.length\n const offsetIndex = (d: DiscrepancyInput): DiscrepancyInput =>\n d.actionIndex !== undefined ? { ...d, actionIndex: d.actionIndex + actionIndexOffset } : d\n\n // Create discrepancies using factory\n const allDiscrepancies = [\n ...extractionResult.discrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...priceDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...duplicateOrderDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...productNotFoundDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ...enrichmentDiscrepancies.map((d) =>\n createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope),\n ),\n ]\n\n // Flag unmatched contacts as discrepancies (from header-based matches + LLM-discovered participants)\n const contactDiscrepancyEmails = new Set<string>()\n for (const match of contactMatches) {\n if (!match.match && match.participant.email) {\n const emailLower = match.participant.email.toLowerCase()\n contactDiscrepancyEmails.add(emailLower)\n allDiscrepancies.push(\n createDiscrepancy(em, proposalId, allActions, {\n type: 'unknown_contact',\n severity: 'warning',\n description: `No matching contact found for ${match.participant.name} (${match.participant.email})`,\n foundValue: match.participant.email,\n }, scope),\n )\n }\n }\n for (const participant of enrichedParticipants) {\n if (participant.matchedContactId) continue\n const emailLower = (participant.email || '').toLowerCase()\n if (!emailLower || contactDiscrepancyEmails.has(emailLower)) continue\n contactDiscrepancyEmails.add(emailLower)\n allDiscrepancies.push(\n createDiscrepancy(em, proposalId, allActions, {\n type: 'unknown_contact',\n severity: 'warning',\n description: `No matching contact found for ${participant.name} (${participant.email})`,\n foundValue: participant.email,\n }, scope),\n )\n }\n\n // Flag draft_reply actions that target unmatched contacts (blocks accept)\n const matchedEmails = new Set(\n contactMatches\n .filter((m) => m.match?.contactId)\n .map((m) => m.participant.email.toLowerCase()),\n )\n for (const [actionIndex, action] of allActions.entries()) {\n if (action.actionType !== 'draft_reply') continue\n const payload = action.payload as Record<string, unknown> | null\n const toEmail = typeof payload?.to === 'string' ? payload.to.trim().toLowerCase() : ''\n if (toEmail && !matchedEmails.has(toEmail)) {\n allDiscrepancies.push(\n createDiscrepancy(em, proposalId, allActions, {\n actionIndex,\n type: 'unknown_contact',\n severity: 'error',\n description: `Draft reply target \"${toEmail}\" has no matching contact. Create the contact first.`,\n foundValue: toEmail,\n }, scope),\n )\n }\n }\n\n allDiscrepancies.forEach((d) => em.persist(d))\n\n // Step 8: Update email status\n email.status = requiresReview ? 'needs_review' : 'processed'\n email.detectedLanguage = extractionResult.detectedLanguage || email.detectedLanguage\n\n await em.flush()\n\n // Step 9: Emit events\n try {\n await emitInboxOpsEvent('inbox_ops.email.processed', {\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n })\n\n await emitInboxOpsEvent('inbox_ops.proposal.created', {\n proposalId: proposal.id,\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n actionCount: allActions.length,\n discrepancyCount: allDiscrepancies.length,\n confidence: proposal.confidence,\n summary: proposal.summary,\n })\n } catch (eventError) {\n console.error('[inbox_ops:extraction-worker] Failed to emit events:', eventError)\n }\n } catch (err) {\n email.status = 'failed'\n email.processingError = err instanceof Error ? err.message : String(err)\n await em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.email.failed', {\n emailId: email.id,\n tenantId: email.tenantId,\n organizationId: email.organizationId,\n error: email.processingError,\n })\n } catch (eventError) {\n console.error('[inbox_ops:extraction-worker] Failed to emit email.failed event:', eventError)\n }\n\n console.error('[inbox_ops:extraction-worker] Extraction failed:', err)\n }\n}\n\nfunction normalizeOrderPayloadFields(payload: Record<string, unknown>): void {\n const lineItems = Array.isArray(payload.lineItems)\n ? (payload.lineItems as Record<string, unknown>[])\n : []\n for (const item of lineItems) {\n if (!item.productName && typeof item.description === 'string') {\n item.productName = item.description\n }\n if (typeof item.quantity === 'number') {\n item.quantity = String(item.quantity)\n }\n if (typeof item.unitPrice === 'number') {\n item.unitPrice = String(item.unitPrice)\n }\n }\n}\n\nfunction buildContactActionsForUnmatchedParticipants(\n contactMatches: { participant: { name: string; email: string }; match?: { contactId: string } | null }[],\n existingActions: { actionType: string; payloadJson: string }[],\n inboxAddress: string,\n): { actionType: 'create_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {\n const alreadyProposed = new Set(\n existingActions\n .filter((a) => a.actionType === 'create_contact')\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n return typeof p.email === 'string' ? p.email.toLowerCase() : ''\n })\n .filter(Boolean),\n )\n\n const inboxLower = (inboxAddress || '').toLowerCase()\n const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']\n\n return contactMatches\n .filter((m) => {\n if (m.match?.contactId) return false\n const emailLower = m.participant.email.toLowerCase()\n if (alreadyProposed.has(emailLower)) return false\n if (emailLower === inboxLower) return false\n return !systemPatterns.some((p) => emailLower.includes(p))\n })\n .map((m) => ({\n actionType: 'create_contact' as const,\n description: `Create contact for ${m.participant.name} (${m.participant.email})`,\n confidence: 0.9,\n requiredFeature: REQUIRED_FEATURES_MAP.create_contact,\n payloadJson: JSON.stringify({\n type: 'person',\n name: m.participant.name,\n email: m.participant.email,\n source: 'inbox_ops',\n }),\n }))\n}\n\nfunction buildLinkContactActionsForMatchedParticipants(\n contactMatches: { participant: { name: string; email: string }; match?: { contactId: string; contactType?: string; contactName?: string } | null }[],\n existingActions: { actionType: string; payloadJson: string }[],\n inboxAddress: string,\n): { actionType: 'link_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {\n const alreadyProposed = new Set(\n existingActions\n .filter((a) => a.actionType === 'link_contact')\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n const email = typeof p.emailAddress === 'string' ? p.emailAddress : (typeof p.email === 'string' ? p.email : '')\n return email.toLowerCase()\n })\n .filter(Boolean),\n )\n\n const inboxLower = (inboxAddress || '').toLowerCase()\n const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']\n\n return contactMatches\n .filter((m) => {\n if (!m.match?.contactId) return false\n const emailLower = m.participant.email.toLowerCase()\n if (alreadyProposed.has(emailLower)) return false\n if (emailLower === inboxLower) return false\n return !systemPatterns.some((p) => emailLower.includes(p))\n })\n .map((m) => ({\n actionType: 'link_contact' as const,\n description: `Link ${m.participant.name} (${m.participant.email}) to existing contact`,\n confidence: 0.95,\n requiredFeature: REQUIRED_FEATURES_MAP.link_contact,\n payloadJson: JSON.stringify({\n emailAddress: m.participant.email,\n contactId: m.match!.contactId,\n contactType: m.match!.contactType || 'person',\n contactName: m.participant.name,\n }),\n }))\n}\n\nfunction buildContactActionsForUnmatchedLlmParticipants(\n enrichedParticipants: { name: string; email: string; matchedContactId?: string | null }[],\n contactMatches: { participant: { email: string } }[],\n existingActions: { actionType: string; payloadJson: string }[],\n alreadyAutoCreated: { payloadJson: string }[],\n inboxAddress: string,\n): { actionType: 'create_contact'; description: string; confidence: number; requiredFeature: string; payloadJson: string }[] {\n const headerEmails = new Set(\n contactMatches.map((m) => m.participant.email.toLowerCase()),\n )\n\n const alreadyProposed = new Set([\n ...existingActions\n .filter((a) => a.actionType === 'create_contact')\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n return typeof p.email === 'string' ? p.email.toLowerCase() : ''\n })\n .filter(Boolean),\n ...alreadyAutoCreated\n .map((a) => {\n const p = safeParsePayloadJson(a.payloadJson)\n return typeof p.email === 'string' ? p.email.toLowerCase() : ''\n })\n .filter(Boolean),\n ])\n\n const inboxLower = (inboxAddress || '').toLowerCase()\n const systemPatterns = ['noreply', 'no-reply', 'donotreply', 'mailer-daemon', 'postmaster']\n\n return enrichedParticipants\n .filter((p) => {\n if (p.matchedContactId) return false\n const emailLower = (p.email || '').toLowerCase()\n if (!emailLower) return false\n if (headerEmails.has(emailLower)) return false\n if (alreadyProposed.has(emailLower)) return false\n if (emailLower === inboxLower) return false\n return !systemPatterns.some((pat) => emailLower.includes(pat))\n })\n .map((p) => ({\n actionType: 'create_contact' as const,\n description: `Create contact for ${p.name} (${p.email})`,\n confidence: 0.85,\n requiredFeature: REQUIRED_FEATURES_MAP.create_contact,\n payloadJson: JSON.stringify({\n type: 'person',\n name: p.name,\n email: p.email,\n source: 'inbox_ops',\n }),\n }))\n}\n\nasync function detectDuplicateOrders(\n em: EntityManager,\n orderActions: { actionType: string; payload: Record<string, unknown>; index: number }[],\n scope: { tenantId: string; organizationId: string },\n salesOrderClass?: EntityClass<{ id: string; orderNumber: string; customerReference?: string | null; tenantId?: string; organizationId?: string; deletedAt?: Date | null }>,\n): Promise<{ type: 'duplicate_order'; severity: 'error'; description: string; expectedValue: string | null; foundValue: string | null; actionIndex: number }[]> {\n if (!salesOrderClass) return []\n const discrepancies: { type: 'duplicate_order'; severity: 'error'; description: string; expectedValue: string | null; foundValue: string | null; actionIndex: number }[] = []\n\n for (const action of orderActions) {\n if (action.actionType !== 'create_order') continue\n\n const customerReference = typeof action.payload.customerReference === 'string'\n ? action.payload.customerReference.trim()\n : null\n\n if (!customerReference) continue\n\n try {\n const existing = await findOneWithDecryption(\n em,\n salesOrderClass,\n {\n customerReference,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (existing) {\n discrepancies.push({\n type: 'duplicate_order',\n severity: 'error',\n description: `An order with customer reference \"${customerReference}\" already exists (${existing.orderNumber || existing.id})`,\n expectedValue: null,\n foundValue: customerReference,\n actionIndex: action.index,\n })\n }\n } catch {\n // Skip duplicate detection if lookup fails\n }\n }\n\n return discrepancies\n}\n\nfunction detectPartialForward(email: InboxEmail): boolean {\n const subject = email.subject || ''\n const hasReOrFw = /^(RE|FW|Fwd):/i.test(subject)\n const messageCount = email.threadMessages?.length || 0\n return hasReOrFw && messageCount < 2\n}\n\nfunction buildParticipantEmailMap(\n contactMatches: { participant: { name: string; email: string } }[],\n llmParticipants: { name: string; email: string }[],\n): Map<string, string> {\n const nameToEmail = new Map<string, string>()\n // Header-based participants are the most reliable source\n for (const m of contactMatches) {\n if (m.participant.name && m.participant.email) {\n nameToEmail.set(m.participant.name.trim().toLowerCase(), m.participant.email.trim().toLowerCase())\n }\n }\n // LLM-extracted participants as fallback (don't overwrite header-based)\n for (const p of llmParticipants) {\n if (p.name && p.email) {\n const key = p.name.trim().toLowerCase()\n if (!nameToEmail.has(key)) {\n nameToEmail.set(key, p.email.trim().toLowerCase())\n }\n }\n }\n return nameToEmail\n}\n\nfunction enrichCreateContactEmails(\n actions: { actionType: string; payloadJson: string }[],\n participantEmailMap: Map<string, string>,\n): void {\n for (const action of actions) {\n if (action.actionType !== 'create_contact') continue\n const payload = safeParsePayloadJson(action.payloadJson)\n if (payload.email) continue\n const name = typeof payload.name === 'string' ? payload.name.trim() : ''\n if (!name) continue\n // Try exact name match first, then partial (first part before / or ,)\n const email = participantEmailMap.get(name.toLowerCase())\n ?? findPartialNameMatch(name, participantEmailMap)\n if (email) {\n payload.email = email\n action.payloadJson = JSON.stringify(payload)\n }\n }\n}\n\nfunction enrichDraftReplyTargets(\n draftReplies: { to: string; toName?: string; subject: string; body: string; context?: string }[],\n participantEmailMap: Map<string, string>,\n): void {\n const knownEmails = new Set(participantEmailMap.values())\n for (const reply of draftReplies) {\n const toEmail = reply.to.trim().toLowerCase()\n if (knownEmails.has(toEmail)) continue\n // The LLM hallucinated an email \u2014 try to resolve via toName\n const toName = (reply.toName || '').trim()\n if (!toName) continue\n const correctedEmail = participantEmailMap.get(toName.toLowerCase())\n ?? findPartialNameMatch(toName, participantEmailMap)\n if (correctedEmail) {\n reply.to = correctedEmail\n }\n }\n}\n\nfunction buildFullTextForExtraction(email: InboxEmail): string {\n let text = email.rawText || ''\n if (!text && email.rawHtml) {\n text = sanitizeHtml(email.rawHtml, {\n allowedTags: [],\n allowedAttributes: {},\n })\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n }\n return text\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\t/g, ' ')\n .replace(/ {2,}/g, ' ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\nfunction findPartialNameMatch(name: string, map: Map<string, string>): string | undefined {\n const lower = name.toLowerCase()\n // Split on common separators (e.g. \"Marco Rossi / Rossi Imports S.r.l.\")\n const parts = lower.split(/\\s*[\\/,]\\s*/).map((p) => p.trim()).filter(Boolean)\n for (const part of parts) {\n const match = map.get(part)\n if (match) return match\n }\n // Try matching first+last name against map keys\n for (const [mapName, mapEmail] of map) {\n if (lower.includes(mapName) || mapName.includes(lower)) {\n return mapEmail\n }\n for (const part of parts) {\n if (part.includes(mapName) || mapName.includes(part)) {\n return mapEmail\n }\n }\n }\n return undefined\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["payload"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.5-develop-
|
|
3
|
+
"version": "0.4.5-develop-509cc99488",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.5-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.5-develop-509cc99488",
|
|
211
211
|
"@types/semver": "^7.5.8",
|
|
212
212
|
"@xyflow/react": "^12.6.0",
|
|
213
213
|
"ai": "^6.0.0",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
3
|
import type { EntityClass } from '@mikro-orm/core'
|
|
4
|
+
import sanitizeHtml from 'sanitize-html'
|
|
4
5
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
5
6
|
import { InboxEmail, InboxProposal, InboxProposalAction, InboxDiscrepancy, InboxSettings } from '../data/entities'
|
|
6
7
|
import type { ExtractedParticipant, InboxDiscrepancyType } from '../data/entities'
|
|
@@ -834,21 +835,12 @@ function enrichDraftReplyTargets(
|
|
|
834
835
|
function buildFullTextForExtraction(email: InboxEmail): string {
|
|
835
836
|
let text = email.rawText || ''
|
|
836
837
|
if (!text && email.rawHtml) {
|
|
837
|
-
text = email.rawHtml
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
.replace(
|
|
842
|
-
.replace(
|
|
843
|
-
.replace(/<\/tr>/gi, '\n')
|
|
844
|
-
.replace(/<\/li>/gi, '\n')
|
|
845
|
-
.replace(/<[^>]+>/g, '')
|
|
846
|
-
.replace(/ /gi, ' ')
|
|
847
|
-
.replace(/&/gi, '&')
|
|
848
|
-
.replace(/</gi, '<')
|
|
849
|
-
.replace(/>/gi, '>')
|
|
850
|
-
.replace(/"/gi, '"')
|
|
851
|
-
.replace(/'/gi, "'")
|
|
838
|
+
text = sanitizeHtml(email.rawHtml, {
|
|
839
|
+
allowedTags: [],
|
|
840
|
+
allowedAttributes: {},
|
|
841
|
+
})
|
|
842
|
+
.replace(/\r\n/g, '\n')
|
|
843
|
+
.replace(/\r/g, '\n')
|
|
852
844
|
.replace(/\n{3,}/g, '\n\n')
|
|
853
845
|
.trim()
|
|
854
846
|
}
|