secure-redact 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advanced.worker-DSVrF0gl.js","sources":["../src/detection/spatialMapper.ts","../src/detection/documentRouter.ts","../src/detection/fusionEngine.ts","../src/detection/regex.ts","../src/detection/checksums.ts","../src/detection/layer1.ts","../src/workers/advanced.worker.ts"],"sourcesContent":["// ─── Spatial Context Mapper: The Game Changer ───────────────────────────────\r\n// Groups OCR words into Lines and Blocks using bounding box spatial relationships\r\n// Detects Key-Value pairs (e.g., \"Name:\" → \"John Doe\") with high confidence\r\n// Implements table structure detection for invoices and bank statements\r\n\r\nimport type { OCRWord, DetectedEntity, PIIType } from '../types';\r\n\r\nexport interface TextLine {\r\n words: OCRWord[];\r\n y: number; // Average Y position\r\n x: number; // Leftmost X\r\n width: number; // Span width\r\n height: number; // Line height\r\n text: string; // Concatenated text\r\n}\r\n\r\nexport interface TextBlock {\r\n lines: TextLine[];\r\n bbox: { x: number; y: number; w: number; h: number; pageIndex: number };\r\n text: string;\r\n}\r\n\r\nexport interface KeyValuePair {\r\n key: OCRWord[];\r\n value: OCRWord[];\r\n keyText: string;\r\n valueText: string;\r\n confidence: number;\r\n type: PIIType;\r\n bbox: { x: number; y: number; w: number; h: number; pageIndex: number };\r\n}\r\n\r\n// ─── Spatial Grouping ────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Groups OCR words into lines based on Y-coordinate proximity.\r\n * Tolerance is adaptive based on median word height.\r\n */\r\nexport function groupWordsIntoLines(words: OCRWord[]): TextLine[] {\r\n if (words.length === 0) return [];\r\n\r\n // Calculate median word height for adaptive tolerance\r\n const heights = words.map(w => w.bbox.h).sort((a, b) => a - b);\r\n const medianHeight = heights[Math.floor(heights.length / 2)] || 20;\r\n const yTolerance = medianHeight * 0.5; // 50% of median height\r\n\r\n // Sort words by Y first, then X\r\n const sorted = [...words].sort((a, b) => {\r\n const yDiff = a.bbox.y - b.bbox.y;\r\n if (Math.abs(yDiff) < yTolerance) {\r\n return a.bbox.x - b.bbox.x; // Same line, sort by X\r\n }\r\n return yDiff;\r\n });\r\n\r\n const lines: TextLine[] = [];\r\n let currentLine: OCRWord[] = [sorted[0]];\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prevWord = sorted[i - 1];\r\n const currWord = sorted[i];\r\n\r\n // Check if on same line (similar Y coordinate)\r\n if (Math.abs(currWord.bbox.y - prevWord.bbox.y) < yTolerance) {\r\n currentLine.push(currWord);\r\n } else {\r\n // Save current line and start new one\r\n lines.push(createTextLine(currentLine));\r\n currentLine = [currWord];\r\n }\r\n }\r\n\r\n // Push last line\r\n if (currentLine.length > 0) {\r\n lines.push(createTextLine(currentLine));\r\n }\r\n\r\n return lines;\r\n}\r\n\r\nfunction createTextLine(words: OCRWord[]): TextLine {\r\n const xs = words.map(w => w.bbox.x);\r\n const ys = words.map(w => w.bbox.y);\r\n const heights = words.map(w => w.bbox.h);\r\n const rightEdges = words.map(w => w.bbox.x + w.bbox.w);\r\n\r\n return {\r\n words,\r\n y: ys.reduce((a, b) => a + b, 0) / ys.length,\r\n x: Math.min(...xs),\r\n width: Math.max(...rightEdges) - Math.min(...xs),\r\n height: Math.max(...heights),\r\n text: words.map(w => w.text).join(' '),\r\n };\r\n}\r\n\r\n/**\r\n * Groups lines into blocks based on vertical spacing.\r\n * Large gaps indicate block boundaries.\r\n */\r\nexport function groupLinesIntoBlocks(lines: TextLine[], pageIndex: number): TextBlock[] {\r\n if (lines.length === 0) return [];\r\n\r\n // Sort lines by Y\r\n const sorted = [...lines].sort((a, b) => a.y - b.y);\r\n\r\n // Calculate average line spacing\r\n const spacings: number[] = [];\r\n for (let i = 1; i < sorted.length; i++) {\r\n spacings.push(sorted[i].y - (sorted[i - 1].y + sorted[i - 1].height));\r\n }\r\n const avgSpacing = spacings.reduce((a, b) => a + b, 0) / spacings.length || 20;\r\n const blockThreshold = avgSpacing * 2.5; // 2.5x average = new block\r\n\r\n const blocks: TextBlock[] = [];\r\n let currentBlock: TextLine[] = [sorted[0]];\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prevLine = sorted[i - 1];\r\n const currLine = sorted[i];\r\n const gap = currLine.y - (prevLine.y + prevLine.height);\r\n\r\n if (gap > blockThreshold) {\r\n blocks.push(createTextBlock(currentBlock, pageIndex));\r\n currentBlock = [currLine];\r\n } else {\r\n currentBlock.push(currLine);\r\n }\r\n }\r\n\r\n if (currentBlock.length > 0) {\r\n blocks.push(createTextBlock(currentBlock, pageIndex));\r\n }\r\n\r\n return blocks;\r\n}\r\n\r\nfunction createTextBlock(lines: TextLine[], pageIndex: number): TextBlock {\r\n const xs = lines.map(l => l.x);\r\n const ys = lines.map(l => l.y);\r\n const rightEdges = lines.map(l => l.x + l.width);\r\n const bottomEdges = lines.map(l => l.y + l.height);\r\n\r\n return {\r\n lines,\r\n bbox: {\r\n x: Math.min(...xs),\r\n y: Math.min(...ys),\r\n w: Math.max(...rightEdges) - Math.min(...xs),\r\n h: Math.max(...bottomEdges) - Math.min(...ys),\r\n pageIndex,\r\n },\r\n text: lines.map(l => l.text).join('\\n'),\r\n };\r\n}\r\n\r\n// ─── Key-Value Pair Detection ────────────────────────────────────────────────\r\n\r\nconst KEY_PATTERNS: Array<{ pattern: RegExp; type: PIIType; confidence: number }> = [\r\n // Names\r\n { pattern: /^(name|patient name|customer name|account holder|holder name|full name|cardholder|person name):?$/i, type: 'NAME', confidence: 0.99 },\r\n { pattern: /^(father'?s? name|father name|spouse name):?$/i, type: 'NAME', confidence: 0.95 },\r\n \r\n // Addresses\r\n { pattern: /^(address|residence|location|billing address|shipping address|permanent address|correspondence address):?$/i, type: 'ADDRESS', confidence: 0.98 },\r\n \r\n // Phone\r\n { pattern: /^(phone|mobile|tel|telephone|contact|ph\\.?|mob\\.?|contact no\\.?|phone no\\.?|mobile no\\.?):?$/i, type: 'PHONE', confidence: 0.97 },\r\n \r\n // Email\r\n { pattern: /^(email|e-mail|email id|email address):?$/i, type: 'EMAIL', confidence: 0.98 },\r\n \r\n // Aadhaar\r\n { pattern: /^(aadhaar|aadhar|uid|aadhaar no\\.?|aadhar no\\.?|aadhaar number|enrollment no\\.?):?$/i, type: 'AADHAAR', confidence: 0.99 },\r\n \r\n // PAN\r\n { pattern: /^(pan|pan no\\.?|pan number|permanent account number):?$/i, type: 'PAN', confidence: 0.99 },\r\n \r\n // Bank Account\r\n { pattern: /^(account no\\.?|account number|a\\/c no\\.?|acc no\\.?|acct no\\.?):?$/i, type: 'ACCOUNT_NUMBER', confidence: 0.99 },\r\n { pattern: /^(ifsc|ifsc code|branch code|micr|micr code):?$/i, type: 'IFSC', confidence: 0.95 },\r\n \r\n // DOB\r\n { pattern: /^(dob|date of birth|birth date|d\\.o\\.b\\.?):?$/i, type: 'DOB', confidence: 0.97 },\r\n \r\n // Medical\r\n { pattern: /^(diagnosis|condition|disease|medication|prescription|blood group|blood type):?$/i, type: 'MEDICAL', confidence: 0.90 },\r\n \r\n // Invoice/Tax\r\n { pattern: /^(invoice no\\.?|bill no\\.?|invoice number|bill number):?$/i, type: 'INVOICE_NO', confidence: 0.85 },\r\n { pattern: /^(gst no\\.?|gstin|gst number|tax id):?$/i, type: 'GST', confidence: 0.85 },\r\n];\r\n\r\n/**\r\n * Detects key-value pairs by analyzing spatial relationships within lines.\r\n * Strategy: If a word matches a key pattern, look for value to its right.\r\n */\r\nexport function detectKeyValuePairs(lines: TextLine[], pageIndex: number): KeyValuePair[] {\r\n const pairs: KeyValuePair[] = [];\r\n\r\n for (const line of lines) {\r\n // Try to find key pattern in this line\r\n for (let i = 0; i < line.words.length; i++) {\r\n const word = line.words[i];\r\n const keyMatch = matchKeyPattern(word.text);\r\n\r\n if (keyMatch) {\r\n // Found key! Look for value to the right\r\n const valueWords = extractValueToRight(line.words, i);\r\n\r\n if (valueWords.length > 0) {\r\n const keyWords = [word];\r\n const valueText = valueWords.map(w => w.text).join(' ');\r\n\r\n // Calculate combined bbox\r\n const allWords = [...keyWords, ...valueWords];\r\n const bbox = calculateBBox(allWords, pageIndex);\r\n\r\n pairs.push({\r\n key: keyWords,\r\n value: valueWords,\r\n keyText: word.text,\r\n valueText,\r\n confidence: keyMatch.confidence,\r\n type: keyMatch.type,\r\n bbox,\r\n });\r\n\r\n // Skip processed words\r\n i += valueWords.length;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return pairs;\r\n}\r\n\r\nfunction matchKeyPattern(text: string): { type: PIIType; confidence: number } | null {\r\n const cleaned = text.trim();\r\n for (const { pattern, type, confidence } of KEY_PATTERNS) {\r\n if (pattern.test(cleaned)) {\r\n return { type, confidence };\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Extracts value words to the right of a key word.\r\n * Stops at punctuation or significant gap.\r\n */\r\nfunction extractValueToRight(words: OCRWord[], keyIndex: number): OCRWord[] {\r\n const valueWords: OCRWord[] = [];\r\n\r\n for (let i = keyIndex + 1; i < words.length; i++) {\r\n const word = words[i];\r\n\r\n // Stop if word is another key\r\n if (matchKeyPattern(word.text)) break;\r\n\r\n // Stop at line-ending punctuation\r\n if (/^[.,:;!?]+$/.test(word.text.trim())) {\r\n if (valueWords.length > 0) break; // Only stop if we already have value\r\n continue; // Skip standalone punctuation\r\n }\r\n\r\n // Check horizontal gap (new column)\r\n if (valueWords.length > 0) {\r\n const prevWord = valueWords[valueWords.length - 1];\r\n const gap = word.bbox.x - (prevWord.bbox.x + prevWord.bbox.w);\r\n const avgWordWidth = prevWord.bbox.w;\r\n\r\n // Large gap = new column, stop\r\n if (gap > avgWordWidth * 2) break;\r\n }\r\n\r\n valueWords.push(word);\r\n\r\n // Stop after reasonable length (prevent runaway)\r\n if (valueWords.length >= 15) break;\r\n }\r\n\r\n return valueWords;\r\n}\r\n\r\nfunction calculateBBox(words: OCRWord[], pageIndex: number): {\r\n x: number; y: number; w: number; h: number; pageIndex: number;\r\n} {\r\n if (words.length === 0) {\r\n return { x: 0, y: 0, w: 0, h: 0, pageIndex };\r\n }\r\n\r\n const xs = words.map(w => w.bbox.x);\r\n const ys = words.map(w => w.bbox.y);\r\n const rightEdges = words.map(w => w.bbox.x + w.bbox.w);\r\n const bottomEdges = words.map(w => w.bbox.y + w.bbox.h);\r\n\r\n return {\r\n x: Math.min(...xs),\r\n y: Math.min(...ys),\r\n w: Math.max(...rightEdges) - Math.min(...xs),\r\n h: Math.max(...bottomEdges) - Math.min(...ys),\r\n pageIndex,\r\n };\r\n}\r\n\r\n/**\r\n * Converts KeyValuePairs to DetectedEntities for the main pipeline.\r\n */\r\nexport function keyValuePairsToEntities(pairs: KeyValuePair[]): DetectedEntity[] {\r\n return pairs.map(pair => ({\r\n id: 'kv_' + crypto.randomUUID().substring(0, 8),\r\n type: pair.type,\r\n value: pair.valueText,\r\n confidence: pair.confidence,\r\n bbox: pair.bbox,\r\n masked: true,\r\n layer: 3, // Spatial layer (higher priority than NLP)\r\n }));\r\n}\r\n\r\n/**\r\n * Detects table structures by analyzing alignment and spacing patterns.\r\n * Returns column groups for structured data extraction.\r\n */\r\nexport function detectTableStructure(lines: TextLine[]): Array<{ columnIndex: number; words: OCRWord[] }> {\r\n // Simplified table detection: group words by X-coordinate alignment\r\n const columns = new Map<number, OCRWord[]>();\r\n const xTolerance = 15; // pixels\r\n\r\n for (const line of lines) {\r\n for (const word of line.words) {\r\n const x = word.bbox.x;\r\n\r\n // Find existing column\r\n let found = false;\r\n for (const [colX, colWords] of columns) {\r\n if (Math.abs(x - colX) < xTolerance) {\r\n colWords.push(word);\r\n found = true;\r\n break;\r\n }\r\n }\r\n\r\n if (!found) {\r\n columns.set(x, [word]);\r\n }\r\n }\r\n }\r\n\r\n // Convert to array and sort by X position\r\n const columnArray = Array.from(columns.entries())\r\n .sort((a, b) => a[0] - b[0])\r\n .map((entry, index) => ({\r\n columnIndex: index,\r\n words: entry[1],\r\n }));\r\n\r\n return columnArray;\r\n}\r\n","// ─── Document Router: Heuristic Document Classification ────────────────────\r\n// Classifies documents BEFORE heavy ML to apply context-specific rulesets\r\n// Supports: Invoice, Bank Statement, Medical Report, Tax Form, ID Card\r\n\r\nexport type DocumentType =\r\n | 'invoice'\r\n | 'bank_statement'\r\n | 'medical_report'\r\n | 'tax_return'\r\n | 'id_card_aadhaar'\r\n | 'id_card_pan'\r\n | 'health_report'\r\n | 'generic';\r\n\r\nexport interface DocumentClassification {\r\n primaryType: DocumentType;\r\n confidence: number;\r\n secondaryTypes: Array<{ type: DocumentType; confidence: number }>;\r\n detectedKeywords: string[];\r\n}\r\n\r\n// ─── Keyword Dictionaries ───────────────────────────────────────────────────\r\n\r\nconst INVOICE_KEYWORDS = [\r\n 'invoice', 'bill', 'bill no', 'invoice no', 'inv no', 'invoice number',\r\n 'bill to', 'ship to', 'customer', 'vendor', 'supplier', 'po number',\r\n 'purchase order', 'payment terms', 'due date', 'subtotal', 'tax', 'gst',\r\n 'igst', 'cgst', 'sgst', 'total amount', 'amount due', 'line item',\r\n 'qty', 'quantity', 'price', 'item description', 'hsn', 'sac code',\r\n];\r\n\r\nconst BANK_STATEMENT_KEYWORDS = [\r\n 'bank statement', 'account statement', 'statement of account',\r\n 'account number', 'account no', 'ifsc', 'ifsc code', 'branch',\r\n 'transaction', 'transaction date', 'credit', 'debit', 'balance',\r\n 'opening balance', 'closing balance', 'withdrawal', 'deposit',\r\n 'cheque', 'check', 'rtgs', 'neft', 'imps', 'upi', 'iban', 'swift',\r\n 'account holder', 'from date', 'to date', 'statement period',\r\n];\r\n\r\nconst MEDICAL_KEYWORDS = [\r\n 'patient', 'patient name', 'doctor', 'physician', 'hospital', 'clinic',\r\n 'medical report', 'lab report', 'pathology', 'radiology', 'diagnosis',\r\n 'prescription', 'medication', 'blood test', 'urine test', 'mri', 'ct scan',\r\n 'x-ray', 'ultrasound', 'ecg', 'ekg', 'test results', 'normal range',\r\n 'abnormal', 'positive', 'negative', 'hemoglobin', 'glucose', 'cholesterol',\r\n 'blood pressure', 'heart rate', 'pulse', 'temperature', 'weight', 'bmi',\r\n];\r\n\r\nconst TAX_KEYWORDS = [\r\n 'income tax', 'tax return', 'itr', 'assessment year', 'financial year',\r\n 'pan', 'permanent account number', 'tax deducted', 'tds', 'form 16',\r\n 'form 26as', 'gross income', 'taxable income', 'deductions', 'exemptions',\r\n 'tax payable', 'tax refund', 'acknowledgement', 'ack no', 'return filed',\r\n 'tan', 'employer', 'salary', 'wages', 'capital gains',\r\n];\r\n\r\nconst AADHAAR_KEYWORDS = [\r\n 'aadhaar', 'aadhar', 'uid', 'unique identification', 'uidai',\r\n 'government of india', 'bharatiya prachnya patr', 'enrollment no',\r\n 'vid', 'virtual id', 'date of birth', 'dob', 'gender', 'male', 'female',\r\n 'address', 'yob', 'year of birth',\r\n];\r\n\r\nconst PAN_KEYWORDS = [\r\n 'pan', 'permanent account number', 'income tax department',\r\n 'father name', 'fathers name', 'date of birth', 'dob',\r\n 'signature', 'photograph',\r\n];\r\n\r\nconst HEALTH_REPORT_KEYWORDS = [\r\n 'health report', 'medical certificate', 'fitness certificate',\r\n 'blood group', 'allergies', 'past medical history', 'current medications',\r\n 'vital signs', 'examination', 'clinical findings',\r\n];\r\n\r\n// ─── Scoring Functions ──────────────────────────────────────────────────────\r\n\r\nfunction scoreDocumentType(text: string, keywords: string[]): {\r\n score: number;\r\n matched: string[];\r\n} {\r\n const lowerText = text.toLowerCase();\r\n let score = 0;\r\n const matched: string[] = [];\r\n\r\n for (const keyword of keywords) {\r\n if (lowerText.includes(keyword)) {\r\n // Weight longer keywords higher (more specific)\r\n const weight = keyword.split(' ').length;\r\n score += weight;\r\n matched.push(keyword);\r\n }\r\n }\r\n\r\n return { score, matched };\r\n}\r\n\r\n/**\r\n * Classifies document type based on OCR text content using keyword matching.\r\n * Returns primary type and secondary candidates with confidence scores.\r\n */\r\nexport function classifyDocument(fullText: string): DocumentClassification {\r\n const invoiceResult = scoreDocumentType(fullText, INVOICE_KEYWORDS);\r\n const bankResult = scoreDocumentType(fullText, BANK_STATEMENT_KEYWORDS);\r\n const medicalResult = scoreDocumentType(fullText, MEDICAL_KEYWORDS);\r\n const taxResult = scoreDocumentType(fullText, TAX_KEYWORDS);\r\n const aadhaarResult = scoreDocumentType(fullText, AADHAAR_KEYWORDS);\r\n const panResult = scoreDocumentType(fullText, PAN_KEYWORDS);\r\n const healthResult = scoreDocumentType(fullText, HEALTH_REPORT_KEYWORDS);\r\n\r\n const candidates = [\r\n { type: 'invoice' as DocumentType, score: invoiceResult.score, matched: invoiceResult.matched },\r\n { type: 'bank_statement' as DocumentType, score: bankResult.score, matched: bankResult.matched },\r\n { type: 'medical_report' as DocumentType, score: medicalResult.score, matched: medicalResult.matched },\r\n { type: 'tax_return' as DocumentType, score: taxResult.score, matched: taxResult.matched },\r\n { type: 'id_card_aadhaar' as DocumentType, score: aadhaarResult.score, matched: aadhaarResult.matched },\r\n { type: 'id_card_pan' as DocumentType, score: panResult.score, matched: panResult.matched },\r\n { type: 'health_report' as DocumentType, score: healthResult.score, matched: healthResult.matched },\r\n ];\r\n\r\n // Sort by score descending\r\n candidates.sort((a, b) => b.score - a.score);\r\n\r\n const topCandidate = candidates[0];\r\n const totalScore = candidates.reduce((sum, c) => sum + c.score, 0);\r\n\r\n // If no clear winner, classify as generic\r\n if (topCandidate.score === 0) {\r\n return {\r\n primaryType: 'generic',\r\n confidence: 0,\r\n secondaryTypes: [],\r\n detectedKeywords: [],\r\n };\r\n }\r\n\r\n // Calculate confidence as ratio of top score to total\r\n const confidence = totalScore > 0 ? topCandidate.score / totalScore : 0;\r\n\r\n // Get secondary types (score > 0)\r\n const secondaryTypes = candidates\r\n .slice(1)\r\n .filter(c => c.score > 0)\r\n .map(c => ({\r\n type: c.type,\r\n confidence: totalScore > 0 ? c.score / totalScore : 0,\r\n }));\r\n\r\n return {\r\n primaryType: topCandidate.type,\r\n confidence: Math.min(confidence, 0.95), // Cap at 95%\r\n secondaryTypes,\r\n detectedKeywords: topCandidate.matched,\r\n };\r\n}\r\n\r\n/**\r\n * Returns context-specific ruleset configuration based on document type.\r\n * Defines which detection strategies to prioritize.\r\n */\r\nexport interface DetectionRuleset {\r\n prioritizeSpatialContext: boolean; // Use spatial key-value detection\r\n enableTableDetection: boolean; // Look for tabular structures\r\n aggressiveRegex: boolean; // Apply strict regex patterns\r\n skipParagraphNER: boolean; // Skip paragraph-style NER (for forms)\r\n confidenceBoost: number; // Multiplier for this doc type\r\n}\r\n\r\nexport function getRulesetForDocType(docType: DocumentType): DetectionRuleset {\r\n switch (docType) {\r\n case 'invoice':\r\n return {\r\n prioritizeSpatialContext: true,\r\n enableTableDetection: true,\r\n aggressiveRegex: true,\r\n skipParagraphNER: true,\r\n confidenceBoost: 1.2,\r\n };\r\n case 'bank_statement':\r\n return {\r\n prioritizeSpatialContext: true,\r\n enableTableDetection: true,\r\n aggressiveRegex: true,\r\n skipParagraphNER: true,\r\n confidenceBoost: 1.3,\r\n };\r\n case 'medical_report':\r\n case 'health_report':\r\n return {\r\n prioritizeSpatialContext: false,\r\n enableTableDetection: false,\r\n aggressiveRegex: false,\r\n skipParagraphNER: false,\r\n confidenceBoost: 1.0,\r\n };\r\n case 'tax_return':\r\n return {\r\n prioritizeSpatialContext: true,\r\n enableTableDetection: true,\r\n aggressiveRegex: true,\r\n skipParagraphNER: true,\r\n confidenceBoost: 1.15,\r\n };\r\n case 'id_card_aadhaar':\r\n case 'id_card_pan':\r\n return {\r\n prioritizeSpatialContext: true,\r\n enableTableDetection: false,\r\n aggressiveRegex: true,\r\n skipParagraphNER: true,\r\n confidenceBoost: 1.25,\r\n };\r\n default:\r\n return {\r\n prioritizeSpatialContext: false,\r\n enableTableDetection: false,\r\n aggressiveRegex: false,\r\n skipParagraphNER: false,\r\n confidenceBoost: 1.0,\r\n };\r\n }\r\n}\r\n","// ─── Confidence Fusion Engine ────────────────────────────────────────────────\r\n// Merges detections from multiple sources with intelligent scoring:\r\n// - Layer 0 (Deterministic Regex): 100% confidence (Emails, PAN, Aadhaar, SSN, Credit Cards)\r\n// - Layer 1 (Spatial Key-Value): 95-99% confidence (context-based detection)\r\n// - Layer 2 (NLP Heuristic): 60-90% confidence (dictionary matching)\r\n// - Layer 3 (ML/Gemini): 50-95% confidence (probabilistic)\r\n//\r\n// Resolves conflicts, deduplicates overlapping detections, and outputs final entities\r\n\r\nimport type { DetectedEntity } from '../types';\r\nimport type { DocumentType } from './documentRouter';\r\n\r\nexport interface FusionConfig {\r\n documentType: DocumentType;\r\n confidenceThreshold: number;\r\n preferSpatialOverNLP: boolean;\r\n deduplicationOverlapThreshold: number; // 0-1, IoU threshold\r\n}\r\n\r\nexport interface FusionResult {\r\n entities: DetectedEntity[];\r\n stats: {\r\n total: number;\r\n byLayer: Record<number, number>;\r\n byConfidenceRange: { low: number; medium: number; high: number };\r\n deduplicated: number;\r\n };\r\n}\r\n\r\n// ─── Scoring System ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Assigns layer-based confidence scores to entities.\r\n * Layer 0 (Regex): 100%\r\n * Layer 1 (Spatial): 95-99%\r\n * Layer 2 (NLP): 60-90%\r\n * Layer 3 (ML): 50-95%\r\n */\r\nexport function normalizeConfidenceByLayer(entity: DetectedEntity): DetectedEntity {\r\n let baseConfidence = entity.confidence;\r\n\r\n switch (entity.layer) {\r\n case 0: // Deterministic regex\r\n baseConfidence = 1.0;\r\n break;\r\n case 1: // Enhanced regex with context\r\n baseConfidence = Math.max(baseConfidence, 0.95);\r\n break;\r\n case 2: // NLP heuristic\r\n baseConfidence = Math.min(Math.max(baseConfidence, 0.60), 0.90);\r\n break;\r\n case 3: // Spatial key-value\r\n baseConfidence = Math.min(Math.max(baseConfidence, 0.95), 0.99);\r\n break;\r\n case 4: // ML/Gemini\r\n baseConfidence = Math.min(Math.max(baseConfidence, 0.50), 0.95);\r\n break;\r\n default:\r\n baseConfidence = Math.max(baseConfidence, 0.50);\r\n }\r\n\r\n return {\r\n ...entity,\r\n confidence: baseConfidence,\r\n };\r\n}\r\n\r\n// ─── Deduplication ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Calculates Intersection over Union (IoU) for two bounding boxes.\r\n */\r\nfunction calculateIoU(\r\n a: DetectedEntity['bbox'],\r\n b: DetectedEntity['bbox']\r\n): number {\r\n if (a.pageIndex !== b.pageIndex) return 0;\r\n\r\n const xOverlap = Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x));\r\n const yOverlap = Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));\r\n const intersection = xOverlap * yOverlap;\r\n\r\n const areaA = a.w * a.h;\r\n const areaB = b.w * b.h;\r\n const union = areaA + areaB - intersection;\r\n\r\n return union > 0 ? intersection / union : 0;\r\n}\r\n\r\n/**\r\n * Deduplicates overlapping entities using Non-Maximum Suppression (NMS).\r\n * Priority:\r\n * 1. Higher layer (spatial > regex > NLP)\r\n * 2. Higher confidence\r\n * 3. Longer text (more specific)\r\n */\r\nexport function deduplicateEntities(\r\n entities: DetectedEntity[],\r\n iouThreshold: number = 0.5\r\n): { deduplicated: DetectedEntity[]; removedCount: number } {\r\n // Sort by priority: layer DESC, confidence DESC, value length DESC\r\n const sorted = [...entities].sort((a, b) => {\r\n if (a.layer !== b.layer) return b.layer - a.layer; // Higher layer first\r\n if (Math.abs(a.confidence - b.confidence) > 0.01) {\r\n return b.confidence - a.confidence; // Higher confidence first\r\n }\r\n return b.value.length - a.value.length; // Longer text first\r\n });\r\n\r\n const kept: DetectedEntity[] = [];\r\n const removed: DetectedEntity[] = [];\r\n\r\n for (const entity of sorted) {\r\n let shouldKeep = true;\r\n\r\n for (const existing of kept) {\r\n const iou = calculateIoU(entity.bbox, existing.bbox);\r\n\r\n if (iou > iouThreshold) {\r\n // Overlaps with higher-priority entity\r\n shouldKeep = false;\r\n removed.push(entity);\r\n break;\r\n }\r\n }\r\n\r\n if (shouldKeep) {\r\n kept.push(entity);\r\n }\r\n }\r\n\r\n return {\r\n deduplicated: kept,\r\n removedCount: removed.length,\r\n };\r\n}\r\n\r\n// ─── Fusion Engine ──────────────────────────────────────────────────────────\r\n\r\nexport class FusionEngine {\r\n private config: FusionConfig;\r\n\r\n constructor(config: FusionConfig) {\r\n this.config = config;\r\n }\r\n\r\n /**\r\n * Main fusion method: combines detections from all sources.\r\n */\r\n fuse(\r\n regexEntities: DetectedEntity[],\r\n spatialEntities: DetectedEntity[],\r\n nlpEntities: DetectedEntity[],\r\n mlEntities: DetectedEntity[]\r\n ): FusionResult {\r\n // Step 1: Normalize confidence scores by layer\r\n const normalizedRegex = regexEntities.map(normalizeConfidenceByLayer);\r\n const normalizedSpatial = spatialEntities.map(normalizeConfidenceByLayer);\r\n const normalizedNLP = nlpEntities.map(normalizeConfidenceByLayer);\r\n const normalizedML = mlEntities.map(normalizeConfidenceByLayer);\r\n\r\n // Step 2: Combine all entities\r\n let combined = [\r\n ...normalizedRegex,\r\n ...normalizedSpatial,\r\n ...normalizedNLP,\r\n ...normalizedML,\r\n ];\r\n\r\n console.log(`[FusionEngine] Combined ${combined.length} entities from all sources`);\r\n console.log(`[FusionEngine] - Regex: ${normalizedRegex.length}`);\r\n console.log(`[FusionEngine] - Spatial: ${normalizedSpatial.length}`);\r\n console.log(`[FusionEngine] - NLP: ${normalizedNLP.length}`);\r\n console.log(`[FusionEngine] - ML/Gemini: ${normalizedML.length}`);\r\n\r\n // Step 3: Deduplicate overlapping detections\r\n const { deduplicated, removedCount } = deduplicateEntities(\r\n combined,\r\n this.config.deduplicationOverlapThreshold\r\n );\r\n\r\n console.log(`[FusionEngine] Deduplicated: removed ${removedCount}, kept ${deduplicated.length}`);\r\n\r\n // Step 4: Filter by confidence threshold\r\n const filtered = deduplicated.filter(\r\n e => e.confidence >= this.config.confidenceThreshold\r\n );\r\n\r\n console.log(`[FusionEngine] After confidence filter (>=${this.config.confidenceThreshold}): ${filtered.length}`);\r\n\r\n // Step 5: Generate statistics\r\n const stats = this.generateStats(filtered, removedCount);\r\n\r\n return {\r\n entities: filtered,\r\n stats,\r\n };\r\n }\r\n\r\n /**\r\n * Generates statistics about the fused entities.\r\n */\r\n private generateStats(\r\n entities: DetectedEntity[],\r\n removedCount: number\r\n ): FusionResult['stats'] {\r\n const byLayer: Record<number, number> = {};\r\n let low = 0;\r\n let medium = 0;\r\n let high = 0;\r\n\r\n for (const entity of entities) {\r\n // By layer\r\n byLayer[entity.layer] = (byLayer[entity.layer] || 0) + 1;\r\n\r\n // By confidence range\r\n if (entity.confidence < 0.7) low++;\r\n else if (entity.confidence < 0.9) medium++;\r\n else high++;\r\n }\r\n\r\n return {\r\n total: entities.length,\r\n byLayer,\r\n byConfidenceRange: { low, medium, high },\r\n deduplicated: removedCount,\r\n };\r\n }\r\n\r\n /**\r\n * Applies document-specific confidence boost.\r\n */\r\n applyDocumentTypeBoost(entities: DetectedEntity[]): DetectedEntity[] {\r\n const boost = this.getConfidenceBoost();\r\n if (boost === 1.0) return entities;\r\n\r\n return entities.map(e => ({\r\n ...e,\r\n confidence: Math.min(e.confidence * boost, 1.0),\r\n }));\r\n }\r\n\r\n private getConfidenceBoost(): number {\r\n switch (this.config.documentType) {\r\n case 'invoice':\r\n return 1.2;\r\n case 'bank_statement':\r\n return 1.3;\r\n case 'id_card_aadhaar':\r\n case 'id_card_pan':\r\n return 1.25;\r\n case 'tax_return':\r\n return 1.15;\r\n default:\r\n return 1.0;\r\n }\r\n }\r\n}\r\n\r\n// ─── Conflict Resolution ────────────────────────────────────────────────────\r\n\r\n/**\r\n * Resolves type conflicts when same region has multiple PII type candidates.\r\n * Strategy: Prefer deterministic > spatial > ML > NLP\r\n */\r\nexport function resolveTypeConflicts(entities: DetectedEntity[]): DetectedEntity[] {\r\n const groups = new Map<string, DetectedEntity[]>();\r\n\r\n // Group by bounding box hash\r\n for (const entity of entities) {\r\n const key = `${entity.bbox.pageIndex}_${entity.bbox.x}_${entity.bbox.y}_${entity.bbox.w}_${entity.bbox.h}`;\r\n if (!groups.has(key)) {\r\n groups.set(key, []);\r\n }\r\n groups.get(key)!.push(entity);\r\n }\r\n\r\n const resolved: DetectedEntity[] = [];\r\n\r\n for (const group of groups.values()) {\r\n if (group.length === 1) {\r\n resolved.push(group[0]);\r\n continue;\r\n }\r\n\r\n // Multiple entities for same bbox - pick best one\r\n const sorted = group.sort((a, b) => {\r\n // Prefer higher layer\r\n if (a.layer !== b.layer) return b.layer - a.layer;\r\n // Then higher confidence\r\n return b.confidence - a.confidence;\r\n });\r\n\r\n resolved.push(sorted[0]);\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\n// ─── Memory Management ──────────────────────────────────────────────────────\r\n\r\n/**\r\n * Cleans up large intermediate data structures to prevent memory leaks.\r\n * Call this after fusion is complete.\r\n */\r\nexport function cleanupIntermediateData(\r\n ...arrays: Array<DetectedEntity[] | null | undefined>\r\n): void {\r\n for (const arr of arrays) {\r\n if (arr) {\r\n arr.length = 0; // Clear array without deallocating\r\n }\r\n }\r\n}\r\n","import type { PIIType } from '../types';\r\n\r\nexport interface RegexMatch {\r\n type: PIIType;\r\n value: string;\r\n startIndex: number;\r\n endIndex: number;\r\n raw: string;\r\n}\r\n\r\n// ─── Aadhaar: 4-4-4 digit format ───────────────────────────────────────────\r\n\r\nconst AADHAAR_REGEX = /\\b(\\d{4}[\\s\\-]?\\d{4}[\\s\\-]?\\d{4})\\b/g;\r\n\r\nexport function findAadhaar(text: string): RegexMatch[] {\r\n const matches: RegexMatch[] = [];\r\n let match;\r\n\r\n AADHAAR_REGEX.lastIndex = 0;\r\n while ((match = AADHAAR_REGEX.exec(text)) !== null) {\r\n const raw = match[1];\r\n const digits = raw.replace(/\\D/g, '');\r\n if (digits.length === 12 && !/^0/.test(digits) && !/^1/.test(digits)) {\r\n matches.push({\r\n type: 'AADHAAR',\r\n value: digits,\r\n startIndex: match.index,\r\n endIndex: match.index + match[0].length,\r\n raw,\r\n });\r\n }\r\n }\r\n\r\n return matches;\r\n}\r\n\r\n// ─── PAN: ABCDE1234F ───────────────────────────────────────────────────────\r\n\r\nconst PAN_REGEX = /\\b([A-Z]{5}[0-9]{4}[A-Z])\\b/g;\r\n\r\nexport function findPAN(text: string): RegexMatch[] {\r\n const matches: RegexMatch[] = [];\r\n let match;\r\n\r\n PAN_REGEX.lastIndex = 0;\r\n while ((match = PAN_REGEX.exec(text)) !== null) {\r\n matches.push({\r\n type: 'PAN',\r\n value: match[1],\r\n startIndex: match.index,\r\n endIndex: match.index + match[0].length,\r\n raw: match[1],\r\n });\r\n }\r\n\r\n return matches;\r\n}\r\n\r\n// ─── Credit Card: 13-19 digits ──────────────────────────────────────────────\r\n\r\nconst CREDIT_CARD_REGEX = /\\b(\\d{4}[\\s\\-]?\\d{4}[\\s\\-]?\\d{4}[\\s\\-]?\\d{1,7})\\b/g;\r\n\r\nexport function findCreditCards(text: string): RegexMatch[] {\r\n const matches: RegexMatch[] = [];\r\n let match;\r\n\r\n CREDIT_CARD_REGEX.lastIndex = 0;\r\n while ((match = CREDIT_CARD_REGEX.exec(text)) !== null) {\r\n const raw = match[1];\r\n const digits = raw.replace(/\\D/g, '');\r\n if (digits.length >= 13 && digits.length <= 19) {\r\n matches.push({\r\n type: 'CREDIT_CARD',\r\n value: digits,\r\n startIndex: match.index,\r\n endIndex: match.index + match[0].length,\r\n raw,\r\n });\r\n }\r\n }\r\n\r\n return matches;\r\n}\r\n\r\n// ─── Indian Phone Numbers ───────────────────────────────────────────────────\r\n\r\nconst PHONE_REGEX = /(?:\\+91[\\s\\-]?|0)?([6-9]\\d{4}[\\s\\-]?\\d{5})\\b/g;\r\n\r\nexport function findPhoneNumbers(text: string): RegexMatch[] {\r\n const matches: RegexMatch[] = [];\r\n let match;\r\n\r\n PHONE_REGEX.lastIndex = 0;\r\n while ((match = PHONE_REGEX.exec(text)) !== null) {\r\n const raw = match[0];\r\n const digits = raw.replace(/\\D/g, '');\r\n // Indian mobile numbers are 10 digits (or 12 with +91)\r\n if (digits.length >= 10 && digits.length <= 12) {\r\n matches.push({\r\n type: 'PHONE',\r\n value: digits,\r\n startIndex: match.index,\r\n endIndex: match.index + match[0].length,\r\n raw,\r\n });\r\n }\r\n }\r\n\r\n return matches;\r\n}\r\n\r\n// ─── Run All Regex Matchers ─────────────────────────────────────────────────\r\n\r\nexport function findAllRegexMatches(text: string): RegexMatch[] {\r\n return [\r\n ...findAadhaar(text),\r\n ...findPAN(text),\r\n ...findCreditCards(text),\r\n ...findPhoneNumbers(text),\r\n ];\r\n}\r\n","// ─── Verhoeff Algorithm for Aadhaar Validation ──────────────────────────────\r\n\r\nconst verhoeffMultiplication: number[][] = [\r\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\r\n [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],\r\n [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],\r\n [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],\r\n [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],\r\n [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],\r\n [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],\r\n [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],\r\n [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],\r\n [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],\r\n];\r\n\r\nconst verhoeffPermutation: number[][] = [\r\n [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\r\n [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],\r\n [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],\r\n [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],\r\n [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],\r\n [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],\r\n [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],\r\n [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],\r\n];\r\n\r\nexport function verhoeffValidate(num: string): boolean {\r\n const digits = num.replace(/\\D/g, '');\r\n if (digits.length !== 12) return false;\r\n\r\n let c = 0;\r\n const len = digits.length;\r\n for (let i = 0; i < len; i++) {\r\n const digit = parseInt(digits[len - i - 1]);\r\n const perm = verhoeffPermutation[i % 8][digit];\r\n c = verhoeffMultiplication[c][perm];\r\n }\r\n return c === 0;\r\n}\r\n\r\n// ─── Luhn Algorithm for Credit Card Validation ──────────────────────────────\r\n\r\nexport function luhnValidate(num: string): boolean {\r\n const digits = num.replace(/\\D/g, '');\r\n if (digits.length < 13 || digits.length > 19) return false;\r\n\r\n let sum = 0;\r\n let alternate = false;\r\n\r\n for (let i = digits.length - 1; i >= 0; i--) {\r\n let n = parseInt(digits[i]);\r\n if (alternate) {\r\n n *= 2;\r\n if (n > 9) n -= 9;\r\n }\r\n sum += n;\r\n alternate = !alternate;\r\n }\r\n\r\n return sum % 10 === 0;\r\n}\r\n\r\n// ─── PAN Validation ─────────────────────────────────────────────────────────\r\n\r\nexport function panValidate(pan: string): boolean {\r\n // PAN format: ABCDE1234F\r\n // 5 uppercase letters + 4 digits + 1 uppercase letter\r\n const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]$/;\r\n if (!panRegex.test(pan)) return false;\r\n\r\n // 4th character indicates holder type\r\n const fourthChar = pan[3];\r\n const validTypes = 'ABCFGHLJPT';\r\n return validTypes.includes(fourthChar);\r\n}\r\n","import type { DetectedEntity, OCRWord } from '../types';\r\nimport { findAllRegexMatches } from './regex';\r\nimport { verhoeffValidate, luhnValidate, panValidate } from './checksums';\r\n\r\n/**\r\n * Layer 1: Deterministic PII Detection\r\n * Runs regex matchers and validates with cryptographic checksums.\r\n * All matches are high-confidence (1.0 for checksum-validated).\r\n */\r\nexport function runLayer1Detection(\r\n fullText: string,\r\n words: OCRWord[],\r\n pageIndex: number = 0\r\n): DetectedEntity[] {\r\n const regexMatches = findAllRegexMatches(fullText);\r\n const entities: DetectedEntity[] = [];\r\n\r\n for (const match of regexMatches) {\r\n let confidence = 0.85;\r\n let valid = true;\r\n\r\n switch (match.type) {\r\n case 'AADHAAR':\r\n valid = verhoeffValidate(match.value);\r\n confidence = valid ? 1.0 : 0.6;\r\n break;\r\n case 'CREDIT_CARD':\r\n valid = luhnValidate(match.value);\r\n confidence = valid ? 1.0 : 0.5;\r\n break;\r\n case 'PAN':\r\n valid = panValidate(match.value);\r\n confidence = valid ? 1.0 : 0.7;\r\n break;\r\n case 'PHONE':\r\n confidence = 0.9;\r\n break;\r\n }\r\n\r\n if (!valid && confidence < 0.5) continue;\r\n\r\n // Map text position to bounding boxes\r\n const bbox = mapTextToBBox(match.startIndex, match.endIndex, fullText, words, pageIndex);\r\n\r\n entities.push({\r\n id: 'l1_' + crypto.randomUUID().substring(0, 8),\r\n type: match.type,\r\n value: match.raw,\r\n confidence,\r\n bbox,\r\n masked: true,\r\n layer: 1,\r\n });\r\n }\r\n\r\n return entities;\r\n}\r\n\r\n/**\r\n * Merges bounding boxes from a list of OCR words into a single enclosing bbox.\r\n */\r\nfunction mergeWordBBoxes(matchingWords: OCRWord[], pageIndex: number): DetectedEntity['bbox'] {\r\n const minX = Math.min(...matchingWords.map(w => w.bbox.x));\r\n const minY = Math.min(...matchingWords.map(w => w.bbox.y));\r\n const maxX = Math.max(...matchingWords.map(w => w.bbox.x + w.bbox.w));\r\n const maxY = Math.max(...matchingWords.map(w => w.bbox.y + w.bbox.h));\r\n return { x: minX, y: minY, w: maxX - minX, h: maxY - minY, pageIndex };\r\n}\r\n\r\n/**\r\n * Maps a text range to a bounding box from OCR words.\r\n *\r\n * Strategy 1 (Primary): Direct token matching — split the matched text into\r\n * tokens and find the same sequence of tokens among the OCR words by content.\r\n * This is immune to character-position drift caused by Hindi/Unicode text.\r\n *\r\n * Strategy 2 (Fallback): Character-position mapping with indexOf, guarded\r\n * against indexOf returning -1 so that position tracking is never corrupted.\r\n */\r\nfunction mapTextToBBox(\r\n startIdx: number,\r\n endIdx: number,\r\n fullText: string,\r\n words: OCRWord[],\r\n pageIndex: number\r\n): DetectedEntity['bbox'] {\r\n const matchedText = fullText.substring(startIdx, endIdx).trim();\r\n\r\n // --- Strategy 1: Direct token matching (most reliable) ---\r\n const tokens = matchedText.split(/\\s+/).filter(t => t.length > 0);\r\n if (tokens.length > 0 && words.length > 0) {\r\n const clean = (s: string) => s.replace(/[^a-zA-Z0-9]/g, '');\r\n const cleanedTokens = tokens.map(clean).filter(t => t.length > 0);\r\n\r\n if (cleanedTokens.length > 0) {\r\n // Sequential window match: find first contiguous run of OCR words\r\n // whose cleaned text matches our cleaned tokens in order.\r\n for (let i = 0; i <= words.length - cleanedTokens.length; i++) {\r\n let allMatch = true;\r\n for (let j = 0; j < cleanedTokens.length; j++) {\r\n if (clean(words[i + j].text) !== cleanedTokens[j]) {\r\n allMatch = false;\r\n break;\r\n }\r\n }\r\n if (allMatch) {\r\n const result = mergeWordBBoxes(\r\n words.slice(i, i + cleanedTokens.length),\r\n pageIndex\r\n );\r\n return result;\r\n }\r\n }\r\n\r\n // Partial / fuzzy fallback: find the first token, then greedily\r\n // match as many subsequent tokens as possible.\r\n const firstTarget = cleanedTokens[0];\r\n for (let i = 0; i < words.length; i++) {\r\n if (clean(words[i].text) !== firstTarget) continue;\r\n const matched: OCRWord[] = [words[i]];\r\n let next = 1;\r\n for (let j = i + 1; j < words.length && next < cleanedTokens.length; j++) {\r\n if (clean(words[j].text) === cleanedTokens[next]) {\r\n matched.push(words[j]);\r\n next++;\r\n }\r\n }\r\n if (matched.length >= Math.ceil(cleanedTokens.length / 2)) {\r\n const result = mergeWordBBoxes(matched, pageIndex);\r\n return result;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // --- Strategy 2: Position-based indexOf mapping (with -1 guard) ---\r\n let currentPos = 0;\r\n const matchingWords: OCRWord[] = [];\r\n\r\n for (const word of words) {\r\n const wordStart = fullText.indexOf(word.text, currentPos);\r\n if (wordStart === -1) continue; // ← key fix: skip, keep currentPos intact\r\n const wordEnd = wordStart + word.text.length;\r\n\r\n if (wordEnd > startIdx && wordStart < endIdx) {\r\n matchingWords.push(word);\r\n }\r\n\r\n currentPos = wordEnd;\r\n if (wordStart > endIdx) break;\r\n }\r\n\r\n if (matchingWords.length > 0) {\r\n const result = mergeWordBBoxes(matchingWords, pageIndex);\r\n return result;\r\n }\r\n\r\n // Ultimate fallback\r\n return { x: 0, y: 0, w: 100, h: 20, pageIndex };\r\n}\r\n","// ─── Advanced Detection Worker ──────────────────────────────────────────────\r\n// Implements multi-stage detection pipeline with spatial awareness\r\n// Integrates: Document Router → Spatial Mapper → Fusion Engine\r\n\r\nimport type { OCRWord, DetectedEntity, PIIType } from '../types';\r\nimport {\r\n groupWordsIntoLines,\r\n groupLinesIntoBlocks,\r\n detectKeyValuePairs,\r\n keyValuePairsToEntities,\r\n detectTableStructure,\r\n} from '../detection/spatialMapper';\r\nimport { classifyDocument, getRulesetForDocType } from '../detection/documentRouter';\r\nimport { FusionEngine } from '../detection/fusionEngine';\r\nimport { runLayer1Detection } from '../detection/layer1';\r\n\r\ninterface AdvancedDetectionRequest {\r\n type: 'ADVANCED_DETECT';\r\n fullText: string;\r\n words: OCRWord[];\r\n pageIndex: number;\r\n confidenceThreshold: number;\r\n}\r\n\r\ninterface AdvancedDetectionResponse {\r\n type: 'DETECTION_RESULT' | 'DETECTION_ERROR';\r\n entities?: DetectedEntity[];\r\n documentType?: string;\r\n stats?: any;\r\n error?: string;\r\n}\r\n\r\n// ─── NLP Heuristic Detection (Inline for Worker) ────────────────────────────\r\n\r\nconst COMMON_FIRST_NAMES = new Set([\r\n 'aarav', 'aditi', 'aditya', 'akash', 'amit', 'amita', 'ananya', 'anil', 'anita', 'anjali',\r\n 'ankita', 'arjun', 'arun', 'aruna', 'ashok', 'bhavna', 'chandra', 'deepak', 'deepika',\r\n 'rahul', 'rajesh', 'priya', 'neha', 'vikram', 'sneha', 'pooja', 'rohit', 'john', 'james',\r\n 'mary', 'patricia', 'jennifer', 'michael', 'william', 'david', 'sarah', 'karen',\r\n]);\r\n\r\nconst COMMON_LAST_NAMES = new Set([\r\n 'sharma', 'verma', 'gupta', 'singh', 'kumar', 'patel', 'joshi', 'mishra', 'agarwal', 'mehta',\r\n 'reddy', 'rao', 'nair', 'menon', 'iyer', 'mukherjee', 'chatterjee', 'das', 'roy', 'shah',\r\n 'smith', 'johnson', 'williams', 'brown', 'jones', 'davis', 'miller', 'wilson', 'moore',\r\n]);\r\n\r\nfunction detectNamesHeuristic(words: OCRWord[]): DetectedEntity[] {\r\n const entities: DetectedEntity[] = [];\r\n\r\n for (let i = 0; i < words.length; i++) {\r\n const word = words[i].text.replace(/[^a-zA-Z]/g, '').toLowerCase();\r\n if (word.length < 2) continue;\r\n\r\n if (COMMON_FIRST_NAMES.has(word) || COMMON_LAST_NAMES.has(word)) {\r\n let fullName = words[i].text;\r\n let bbox = { ...words[i].bbox };\r\n let confidence = 0.65;\r\n\r\n // Look for multi-word names\r\n if (i + 1 < words.length) {\r\n const nextWord = words[i + 1].text.replace(/[^a-zA-Z]/g, '').toLowerCase();\r\n if (COMMON_FIRST_NAMES.has(nextWord) || COMMON_LAST_NAMES.has(nextWord)) {\r\n fullName += ' ' + words[i + 1].text;\r\n bbox.w = (words[i + 1].bbox.x + words[i + 1].bbox.w) - bbox.x;\r\n confidence = 0.78;\r\n i++;\r\n }\r\n }\r\n\r\n entities.push({\r\n id: 'nlp_' + crypto.randomUUID().substring(0, 8),\r\n type: 'NAME',\r\n value: fullName,\r\n confidence,\r\n bbox,\r\n masked: true,\r\n layer: 2, // NLP heuristic layer\r\n });\r\n }\r\n }\r\n\r\n return entities;\r\n}\r\n\r\n// ─── Main Worker Handler ────────────────────────────────────────────────────\r\n\r\nself.onmessage = async (e: MessageEvent<AdvancedDetectionRequest>) => {\r\n const { type, fullText, words, pageIndex, confidenceThreshold } = e.data;\r\n\r\n if (type !== 'ADVANCED_DETECT') return;\r\n\r\n try {\r\n console.log(`[Advanced Detection Worker] Starting multi-stage detection...`);\r\n console.log(`[Advanced Detection Worker] Input: ${words.length} words, ${fullText.length} chars`);\r\n\r\n // ────────────────────────────────────────────────────────────────────\r\n // STAGE 1: Document Classification (Router)\r\n // ────────────────────────────────────────────────────────────────────\r\n\r\n const classification = classifyDocument(fullText);\r\n const ruleset = getRulesetForDocType(classification.primaryType);\r\n\r\n console.log(`[Advanced Detection Worker] Document Type: ${classification.primaryType} (${Math.round(classification.confidence * 100)}%)`);\r\n console.log(`[Advanced Detection Worker] Ruleset:`, ruleset);\r\n\r\n // ────────────────────────────────────────────────────────────────────\r\n // STAGE 2: Spatial Context Mapping\r\n // ────────────────────────────────────────────────────────────────────\r\n\r\n let spatialEntities: DetectedEntity[] = [];\r\n\r\n if (ruleset.prioritizeSpatialContext) {\r\n console.log(`[Advanced Detection Worker] Running spatial analysis...`);\r\n\r\n const lines = groupWordsIntoLines(words);\r\n console.log(`[Advanced Detection Worker] Grouped into ${lines.length} lines`);\r\n\r\n const blocks = groupLinesIntoBlocks(lines, pageIndex);\r\n console.log(`[Advanced Detection Worker] Grouped into ${blocks.length} blocks`);\r\n\r\n // Detect key-value pairs\r\n const kvPairs = detectKeyValuePairs(lines, pageIndex);\r\n console.log(`[Advanced Detection Worker] Detected ${kvPairs.length} key-value pairs`);\r\n\r\n spatialEntities = keyValuePairsToEntities(kvPairs);\r\n\r\n // Table detection (for invoices and bank statements)\r\n if (ruleset.enableTableDetection) {\r\n const tableColumns = detectTableStructure(lines);\r\n console.log(`[Advanced Detection Worker] Detected ${tableColumns.length} table columns`);\r\n }\r\n }\r\n\r\n // ────────────────────────────────────────────────────────────────────\r\n // STAGE 3: Deterministic Regex Detection (Layer 1)\r\n // ────────────────────────────────────────────────────────────────────\r\n\r\n console.log(`[Advanced Detection Worker] Running regex detection...`);\r\n const regexEntities = runLayer1Detection(fullText, words, pageIndex);\r\n console.log(`[Advanced Detection Worker] Regex detected: ${regexEntities.length}`);\r\n\r\n // ────────────────────────────────────────────────────────────────────\r\n // STAGE 4: NLP Heuristic Detection (Layer 2)\r\n // ────────────────────────────────────────────────────────────────────\r\n\r\n let nlpEntities: DetectedEntity[] = [];\r\n\r\n if (!ruleset.skipParagraphNER) {\r\n console.log(`[Advanced Detection Worker] Running NLP heuristics...`);\r\n nlpEntities = detectNamesHeuristic(words);\r\n console.log(`[Advanced Detection Worker] NLP detected: ${nlpEntities.length}`);\r\n }\r\n\r\n // ────────────────────────────────────────────────────────────────────\r\n // STAGE 5: Confidence Fusion\r\n // ────────────────────────────────────────────────────────────────────\r\n\r\n console.log(`[Advanced Detection Worker] Fusing detections...`);\r\n\r\n const fusionEngine = new FusionEngine({\r\n documentType: classification.primaryType,\r\n confidenceThreshold,\r\n preferSpatialOverNLP: ruleset.prioritizeSpatialContext,\r\n deduplicationOverlapThreshold: 0.5,\r\n });\r\n\r\n const fusionResult = fusionEngine.fuse(\r\n regexEntities,\r\n spatialEntities,\r\n nlpEntities,\r\n [] // ML/Gemini entities come from main pipeline\r\n );\r\n\r\n console.log(`[Advanced Detection Worker] Fusion complete: ${fusionResult.entities.length} entities`);\r\n console.log(`[Advanced Detection Worker] Stats:`, fusionResult.stats);\r\n\r\n // Apply document-type-specific confidence boost\r\n const boostedEntities = fusionEngine.applyDocumentTypeBoost(fusionResult.entities);\r\n\r\n // ────────────────────────────────────────────────────────────────────\r\n // STAGE 6: Return Results\r\n // ────────────────────────────────────────────────────────────────────\r\n\r\n const response: AdvancedDetectionResponse = {\r\n type: 'DETECTION_RESULT',\r\n entities: boostedEntities,\r\n documentType: classification.primaryType,\r\n stats: {\r\n ...fusionResult.stats,\r\n documentConfidence: Math.round(classification.confidence * 100),\r\n detectedKeywords: classification.detectedKeywords.slice(0, 5),\r\n },\r\n };\r\n\r\n self.postMessage(response);\r\n\r\n // Cleanup to prevent memory leaks\r\n regexEntities.length = 0;\r\n spatialEntities.length = 0;\r\n nlpEntities.length = 0;\r\n\r\n } catch (error) {\r\n console.error('[Advanced Detection Worker] Error:', error);\r\n self.postMessage({\r\n type: 'DETECTION_ERROR',\r\n error: error instanceof Error ? error.message : 'Detection failed',\r\n });\r\n }\r\n};\r\n\r\n// Worker ready signal\r\nself.postMessage({ type: 'WORKER_READY' });\r\n"],"names":["groupWordsIntoLines","words","heights","w","a","b","yTolerance","sorted","yDiff","lines","currentLine","prevWord","currWord","createTextLine","xs","ys","rightEdges","groupLinesIntoBlocks","pageIndex","spacings","i","blockThreshold","blocks","currentBlock","prevLine","currLine","createTextBlock","l","bottomEdges","KEY_PATTERNS","detectKeyValuePairs","pairs","line","word","keyMatch","matchKeyPattern","valueWords","extractValueToRight","keyWords","valueText","allWords","bbox","calculateBBox","text","cleaned","pattern","type","confidence","keyIndex","gap","avgWordWidth","keyValuePairsToEntities","pair","detectTableStructure","columns","xTolerance","x","found","colX","colWords","entry","index","INVOICE_KEYWORDS","BANK_STATEMENT_KEYWORDS","MEDICAL_KEYWORDS","TAX_KEYWORDS","AADHAAR_KEYWORDS","PAN_KEYWORDS","HEALTH_REPORT_KEYWORDS","scoreDocumentType","keywords","lowerText","score","matched","keyword","weight","classifyDocument","fullText","invoiceResult","bankResult","medicalResult","taxResult","aadhaarResult","panResult","healthResult","candidates","topCandidate","totalScore","sum","c","secondaryTypes","getRulesetForDocType","docType","normalizeConfidenceByLayer","entity","baseConfidence","calculateIoU","xOverlap","yOverlap","intersection","areaA","areaB","union","deduplicateEntities","entities","iouThreshold","kept","removed","shouldKeep","existing","FusionEngine","config","regexEntities","spatialEntities","nlpEntities","mlEntities","normalizedRegex","normalizedSpatial","normalizedNLP","normalizedML","combined","deduplicated","removedCount","filtered","e","stats","byLayer","low","medium","high","boost","AADHAAR_REGEX","findAadhaar","matches","match","raw","digits","PAN_REGEX","findPAN","CREDIT_CARD_REGEX","findCreditCards","PHONE_REGEX","findPhoneNumbers","findAllRegexMatches","verhoeffMultiplication","verhoeffPermutation","verhoeffValidate","num","len","digit","perm","luhnValidate","alternate","n","panValidate","pan","fourthChar","runLayer1Detection","regexMatches","valid","mapTextToBBox","mergeWordBBoxes","matchingWords","minX","minY","maxX","maxY","startIdx","endIdx","tokens","t","clean","s","cleanedTokens","allMatch","j","firstTarget","next","currentPos","wordStart","wordEnd","COMMON_FIRST_NAMES","COMMON_LAST_NAMES","detectNamesHeuristic","fullName","nextWord","confidenceThreshold","classification","ruleset","kvPairs","tableColumns","fusionEngine","fusionResult","response","error"],"mappings":"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"}