/**
* 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;
}
}
}