// WhatsApp message parser for FreightDesk // Parses natural language freight messages into structured data const { CITIES } = require('../config/constants'); // Known shipper names (from existing data) const KNOWN_SHIPPERS = [ 'Kahn Transport', 'Agarwal Packers and Movers', 'Agarwal', 'Sahara Packers', 'Ambika Packers', 'Century Polymers', 'DRS', 'Superstar', 'Superstar Packers', 'Chips', 'Chipps', 'Indian CBE', 'Indian CBE Shipper', 'KTC', 'ATC', 'TCI', 'Filatex', 'Dryfish John', 'Sun Packers', 'Thangavel', 'Nafees Alappuzha', 'Shivaprasad', 'Jinu Coin', 'Balmer Thuni', 'Pasupathy', 'Sulphi Baddest', 'KRS', 'Hirosh', 'Hirosh Roadways', 'Aero Rubber', 'Silverstar', 'DRS Agarwal', 'Gem', 'E20 Packers and Movers', 'CRT Transport', 'Mohamed Anas', 'Nair', 'Badadosth', ]; // Status keywords mapping const STATUS_KEYWORDS = { 'pending lead': ['pending lead', 'lead', 'enquiry', 'enquiry'], 'assigned vehicle': ['assigned vehicle', 'vehicle assigned'], 'assigned': ['assigned', 'allotted'], 'loaded / in transit': ['loaded', 'in transit', 'on the way', 'dispatched', 'started'], 'delivered / pending collection': ['delivered', 'delivery done'], 'pending collection': ['pending collection', 'collection pending', 'to collect'], 'partially pending': ['partially pending', 'partial pending'], 'fully pending from shipper': ['fully pending', 'no payment'], 'settled': ['settled', 'complete', 'completed', 'closed'], 'commission received': ['commission received', 'comm received'], 'commission adjusted': ['commission adjusted', 'comm adjusted'], 'commission due': ['commission due', 'comm due'], 'reconciled': ['reconciled'], 'completed': ['completed', 'done'], 'handled directly by shipper': ['directly by shipper', 'handled directly'], 'available vehicle': ['available', 'vehicle available'], 'partial': ['partial'], }; function parseWhatsAppMessage(text) { const result = { shipper: null, vehicle: null, from_city: null, to_city: null, via: null, status: null, freight_charged: null, advance_received: null, paid_to_driver: null, commission: null, driver_freight: null, pending_from_shipper: null, pending_to_driver: null, notes: text, confidence: 'low', parsed_fields: [], }; const lower = text.toLowerCase(); // 1. Parse shipper for (const shipper of KNOWN_SHIPPERS) { if (lower.includes(shipper.toLowerCase())) { result.shipper = shipper; result.parsed_fields.push('shipper'); break; } } // 2. Parse vehicle number (Indian format: XX00XX0000) const vehicleMatch = text.match(/\b([A-Z]{2}\s*\d{1,2}\s*[A-Z]{1,3}\s*\d{4})\b/i); if (vehicleMatch) { result.vehicle = vehicleMatch[1].replace(/\s/g, '').toUpperCase(); result.parsed_fields.push('vehicle'); } // 3. Parse cities (from → to pattern) const cityPattern = CITIES.map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'); const routeMatch = text.match(new RegExp(`(${cityPattern})\\s*(?:to|→|-|via)\\s*(${cityPattern})`, 'i')); if (routeMatch) { result.from_city = routeMatch[1]; result.to_city = routeMatch[2]; result.parsed_fields.push('from_city', 'to_city'); } else { // Try to find any known city for (const city of CITIES) { if (lower.includes(city.toLowerCase())) { if (!result.to_city) { result.to_city = city; result.parsed_fields.push('to_city'); } else if (!result.from_city) { result.from_city = city; result.parsed_fields.push('from_city'); } } } } // 4. Parse via const viaMatch = text.match(/via\s+([A-Za-z\s,]+?)(?:\s*(?:to|→|-|loaded|freight|₹|\d{4,}))/i); if (viaMatch) { result.via = viaMatch[1].trim(); result.parsed_fields.push('via'); } // 5. Parse status for (const [status, keywords] of Object.entries(STATUS_KEYWORDS)) { for (const kw of keywords) { if (lower.includes(kw)) { result.status = status; result.parsed_fields.push('status'); break; } } if (result.status) break; } // 6. Parse amounts // Freight: look for "freight", "charged", "total" followed by number const freightMatch = text.match(/(?:freight|charged|total|amount|bill)\s*[:\-]?\s*₹?\s*(\d[\d,]*)/i); if (freightMatch) { result.freight_charged = parseInt(freightMatch[1].replace(/,/g, '')); result.parsed_fields.push('freight_charged'); } else { // Try standalone large numbers (4-6 digits) that could be freight const amountMatches = text.match(/₹?\s*(\d{4,6})\b/g); if (amountMatches) { const amounts = amountMatches.map(m => parseInt(m.replace(/[₹,\s]/g, ''))); if (amounts.length > 0) { result.freight_charged = Math.max(...amounts); result.parsed_fields.push('freight_charged'); } } } // Advance received const advanceMatch = text.match(/(?:advance|received|paid by shipper)\s*[:\-]?\s*₹?\s*(\d[\d,]*)/i); if (advanceMatch) { result.advance_received = parseInt(advanceMatch[1].replace(/,/g, '')); result.parsed_fields.push('advance_received'); } // Paid to driver const driverPaidMatch = text.match(/(?:paid to driver|driver advance|driver paid|to driver)\s*[:\-]?\s*₹?\s*(\d[\d,]*)/i); if (driverPaidMatch) { result.paid_to_driver = parseInt(driverPaidMatch[1].replace(/,/g, '')); result.parsed_fields.push('paid_to_driver'); } // Commission const commissionMatch = text.match(/(?:commission|comm)\s*[:\-]?\s*₹?\s*(\d[\d,]*)/i); if (commissionMatch) { result.commission = parseInt(commissionMatch[1].replace(/,/g, '')); result.parsed_fields.push('commission'); } // Driver freight const driverFreightMatch = text.match(/(?:driver freight|driver rate|driver amount)\s*[:\-]?\s*₹?\s*(\d[\d,]*)/i); if (driverFreightMatch) { result.driver_freight = parseInt(driverFreightMatch[1].replace(/,/g, '')); result.parsed_fields.push('driver_freight'); } // Auto-calculate commission if not parsed if (!result.commission && result.freight_charged && result.paid_to_driver) { result.commission = result.freight_charged - result.paid_to_driver; result.parsed_fields.push('commission (auto)'); } // Auto-calculate pending from shipper if (!result.pending_from_shipper && result.freight_charged) { result.pending_from_shipper = result.freight_charged - (result.advance_received || 0); if (result.pending_from_shipper > 0) result.parsed_fields.push('pending_from_shipper (auto)'); } // Auto-calculate pending to driver if (!result.pending_to_driver && result.driver_freight) { result.pending_to_driver = result.driver_freight - (result.paid_to_driver || 0); if (result.pending_to_driver > 0) result.parsed_fields.push('pending_to_driver (auto)'); } // Confidence based on how many fields were parsed const fieldCount = result.parsed_fields.length; if (fieldCount >= 6) result.confidence = 'high'; else if (fieldCount >= 3) result.confidence = 'medium'; return result; } module.exports = { parseWhatsAppMessage, KNOWN_SHIPPERS };