Skip to main content
Glama
improved-customer-detection.ts12 kB
/** * Improved customer detection logic that validates divisions have actual data * This fixes the issue where Bank Leumi division 1 returns wrong account or massive incorrect costs */ export interface DivisionInfo { accountKey: string; divisionId: string; accountId: string; accountName?: string; cloudTypeId?: number; } export interface CustomerDetectionResult { accountKey: string; divisionId: string; accountId?: string; isValidated?: boolean; } export class ImprovedCustomerDetection { /** * Validates that a division returns data for the correct account * Prevents issues where API returns wrong account ID or unrealistic costs */ private async validateDivision( apiClient: any, division: DivisionInfo, userKey: string, testDateRange: { startDate: string; endDate: string } ): Promise<boolean> { try { // Build API key for this division const apiKey = `${userKey}:${division.accountKey}:${division.divisionId}`; // Make a test request to validate the division const testParams = { startDate: testDateRange.startDate, endDate: testDateRange.endDate, groupBy: 'none', periodGranLevel: 'month', costType: ['cost', 'discount'], excludeFilters: { chargetype: 'Tax' } }; console.error(`[DIVISION-VALIDATION] Testing division ${division.divisionId} with account ${division.accountId}`); // Save current auth headers const originalAuth = apiClient._authToken; // Temporarily set division-specific API key apiClient.setAuthHeaders({ ...originalAuth, apikey: apiKey }); // Make test request with specific apikey const response = await apiClient.makeRequest('/invoices/caui', { ...testParams, customer_account_key: division.accountKey, customer_division_id: division.divisionId }); // Restore original auth apiClient.setAuthHeaders(originalAuth); if (!response.success || !response.data) { console.error(`[DIVISION-VALIDATION] Division ${division.divisionId} returned no data`); return false; } const data = Array.isArray(response.data) ? response.data : []; // Check 1: Verify the returned account ID matches expected if (data.length > 0) { const returnedAccountId = data[0].account_id || data[0].accountId; if (returnedAccountId && returnedAccountId !== division.accountId) { console.error(`[DIVISION-VALIDATION] ❌ Division ${division.divisionId} returned wrong account! Expected: ${division.accountId}, Got: ${returnedAccountId}`); return false; } } // Check 2: Validate costs are reasonable (not in millions for small accounts) const totalCost = data.reduce((sum: number, row: any) => { const cost = row.total_cost || row.cost || 0; return sum + parseFloat(cost); }, 0); // If costs are over $1M per month average, flag for review const months = data.length || 1; const avgMonthlyCost = totalCost / months; if (avgMonthlyCost > 1000000) { console.error(`[DIVISION-VALIDATION] ⚠️ Division ${division.divisionId} has unusually high costs: $${avgMonthlyCost.toFixed(2)}/month average`); // Don't automatically reject, but log warning } // Check 3: Ensure we have actual cost data (not just $0.00 or $0.01) if (totalCost < 0.10 && data.length > 0) { console.error(`[DIVISION-VALIDATION] Division ${division.divisionId} has negligible costs: $${totalCost.toFixed(2)} total`); // This might be valid for inactive accounts, don't reject } console.error(`[DIVISION-VALIDATION] ✅ Division ${division.divisionId} validated: ${data.length} records, $${totalCost.toFixed(2)} total`); return true; } catch (error: any) { console.error(`[DIVISION-VALIDATION] Error validating division ${division.divisionId}: ${error.message}`); return false; } } /** * Improved detection that validates divisions and chooses the correct one */ public async detectCustomerWithValidation( userQuery: string, apiClient: any, session: any, currentParams: any ): Promise<CustomerDetectionResult | null> { try { // Get user key from session auth const userKey = session.auth?.getUserKey(); if (!userKey) { console.error(`[IMPROVED-DETECTION] No user key available in session`); return null; } console.error(`[IMPROVED-DETECTION] Got user key: ${userKey}`); // Get customer divisions const divisionsResponse = await apiClient.makeRequest('/users/plain-sub-users'); if (!divisionsResponse.success || !divisionsResponse.data?.customerDivisions) { console.error(`[IMPROVED-DETECTION] Failed to get customer divisions`); return null; } const customerDivisions = divisionsResponse.data.customerDivisions; // Find matching customer (same pattern matching as before) const queryLower = userQuery.toLowerCase(); let matchedCustomerName: string | null = null; let matchedDivisions: DivisionInfo[] = []; // Check for Bank Leumi specifically if (queryLower.includes('bank leumi') || queryLower.includes('leumi')) { matchedCustomerName = 'Bank Leumi'; if (customerDivisions['Bank Leumi']) { matchedDivisions = customerDivisions['Bank Leumi']; console.error(`[IMPROVED-DETECTION] Found ${matchedDivisions.length} divisions for Bank Leumi`); } } // Add other bank patterns as needed else if (queryLower.includes('bank hapoalim') || queryLower.includes('hapoalim')) { matchedCustomerName = 'Bank Hapoalim'; if (customerDivisions['Bank Hapoalim']) { matchedDivisions = customerDivisions['Bank Hapoalim']; } } if (!matchedCustomerName || matchedDivisions.length === 0) { console.error(`[IMPROVED-DETECTION] No matching customer found in query`); return null; } // If only one division, use it (but still validate) if (matchedDivisions.length === 1) { const division = matchedDivisions[0]; console.error(`[IMPROVED-DETECTION] Single division found, using ${division.accountKey}/${division.divisionId}`); // Validate even single division const testRange = { startDate: new Date(new Date().setMonth(new Date().getMonth() - 3)).toISOString().split('T')[0], endDate: new Date().toISOString().split('T')[0] }; const isValid = await this.validateDivision(apiClient, division, userKey, testRange); return { accountKey: String(division.accountKey), divisionId: String(division.divisionId), accountId: division.accountId, isValidated: isValid }; } // Multiple divisions - validate each and choose the best one console.error(`[IMPROVED-DETECTION] Multiple divisions found, validating each...`); const testRange = { startDate: '2025-01-01', // Test recent data endDate: '2025-12-31' }; const validationResults: Array<{ division: DivisionInfo; isValid: boolean; hasData: boolean; totalCost: number; }> = []; for (const division of matchedDivisions) { console.error(`[IMPROVED-DETECTION] Testing division ${division.divisionId} (${division.accountKey})`); // Quick cost check for each division const apiKey = `${userKey}:${division.accountKey}:${division.divisionId}`; try { // Save current auth headers const originalAuth = apiClient._authToken; // Temporarily set division-specific API key apiClient.setAuthHeaders({ ...originalAuth, apikey: apiKey }); const response = await apiClient.makeRequest('/invoices/caui', { startDate: testRange.startDate, endDate: testRange.endDate, groupBy: 'none', periodGranLevel: 'month', costType: ['cost', 'discount'], excludeFilters: { chargetype: 'Tax' }, customer_account_key: division.accountKey, customer_division_id: division.divisionId }); // Restore original auth apiClient.setAuthHeaders(originalAuth); if (response.success && response.data) { const data = Array.isArray(response.data) ? response.data : []; // Check account ID matches let accountMatches = true; if (data.length > 0) { const returnedAccountId = data[0].account_id || data[0].accountId; if (returnedAccountId && returnedAccountId !== division.accountId) { accountMatches = false; console.error(`[IMPROVED-DETECTION] Division ${division.divisionId} returns wrong account: ${returnedAccountId} vs ${division.accountId}`); } } // Calculate total cost const totalCost = data.reduce((sum: number, row: any) => { const cost = row.total_cost || row.cost || 0; return sum + parseFloat(cost); }, 0); validationResults.push({ division: division, isValid: accountMatches, hasData: data.length > 0 && totalCost > 0.10, // More than 10 cents totalCost: totalCost }); console.error(`[IMPROVED-DETECTION] Division ${division.divisionId}: Valid=${accountMatches}, HasData=${data.length > 0}, Cost=$${totalCost.toFixed(2)}`); } } catch (error) { console.error(`[IMPROVED-DETECTION] Error testing division ${division.divisionId}: ${error}`); validationResults.push({ division: division, isValid: false, hasData: false, totalCost: 0 }); } } // Choose the best division based on validation results // Priority: 1) Valid & has data, 2) Has reasonable costs, 3) Not returning wrong account const bestDivision = validationResults .filter(r => r.isValid) // Must return correct account .filter(r => r.hasData) // Must have actual data .filter(r => r.totalCost > 0.10 && r.totalCost < 10000000) // Reasonable cost range .sort((a, b) => { // Prefer divisions with moderate costs (likely real data) // Division 2 with ~$1800 is more likely correct than Division 1 with millions if (a.totalCost > 100 && a.totalCost < 100000) return -1; if (b.totalCost > 100 && b.totalCost < 100000) return 1; return b.totalCost - a.totalCost; })[0]; if (bestDivision) { console.error(`[IMPROVED-DETECTION] ✅ Selected best division: ${bestDivision.division.accountKey}/${bestDivision.division.divisionId} with $${bestDivision.totalCost.toFixed(2)}`); return { accountKey: String(bestDivision.division.accountKey), divisionId: String(bestDivision.division.divisionId), accountId: bestDivision.division.accountId, isValidated: true }; } // Fallback: use first division if none validated perfectly const fallback = matchedDivisions[0]; console.error(`[IMPROVED-DETECTION] ⚠️ No perfect match, using fallback division: ${fallback.accountKey}/${fallback.divisionId}`); return { accountKey: String(fallback.accountKey), divisionId: String(fallback.divisionId), accountId: fallback.accountId, isValidated: false }; } catch (error: any) { console.error(`[IMPROVED-DETECTION] Error in detection: ${error.message}`); return null; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/daviddraiumbrella/invoice-monitoring'

If you have feedback or need assistance with the MCP directory API, please join our Discord server