313 lead: { 314 ...sorted, 315 leadId, 316 sid, 317 ssid, 318 campid: explicitCampaignId || campaignReference, 319 phone1: phone, 320 email, 321 firstname: firstName, 322 lastname: lastName, 323 street1: firstClean(body.street1, body.street, addressText, body.adresse, 324 address.street1, address.street, address.address, address.adresse), 325 postcode: firstClean(body.postcode, body.postal_code, body.zip, body.code_postal, 326 address.postcode, address.postalCode, address.postal_code, address.zip, address.code_postal), 327 towncity: firstClean(body.towncity, body.city, body.ville, address.towncity, address.city, address.ville), 328 source: firstClean(body.source, body.vertical, body.category, category), 329 _delivery_fields: deliveryCustomFields(body), 330 }, 331 }; 332} 333 334const STANDARD_DELIVERY_FIELDS = new Set([ 335 "id", "leadid", "lead_id", "leadbyte_lead_id", "sid", "ssid", "supplier_id", "supplierid", "supplier_name", 336 "suppliername", "campaign", "campaign_id", "campaignid", "campid", "campaign_reference", "campaign_ref", 337 "campaign_code", "campaign_name", "campaignname", "phone1", "phone", "telephone", "tel", "mobile", "email", 338 "email_address", "mail", "firstname", "first_name", "prenom", "lastname", "last_name", "nom", "street1", "street", 339 "address", "adresse", "postcode", "postal_code", "zip", "code_postal", "towncity", "city", "ville", "source", 340 "vertical", "category", "contact", 341]); 342 343function deliveryCustomFields(body) { 344 const entries = []; 345 const visit = (value, prefix = "", depth = 0) => { 346 if (!value || typeof value !== "object" || Array.isArray(value) || depth > 2 || entries.length >= 40) return; 347 for (const [key, child] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) { 348 if (!prefix && STANDARD_DELIVERY_FIELDS.has(key.toLowerCase())) continue; 349 const path = prefix ? `${prefix}.${key}` : key; 350 if (isSensitiveKey(key) || isSensitiveKey(path)) continue; 351 if (["string", "number", "boolean"].includes(typeof child)) entries.push([path, child]); 352 else visit(child, path, depth + 1); 353 if (entries.length >= 40) break; 354 } 355 }; 356 visit(body); 357 return Object.fromEntries(entries); 358} 359 360function inferFlatDeliveryCategory(body) { 361 const explicit = [body.vertical, body.category, body.product, body.produit, body.campaign_reference, body.campaign_ref, 362 body.campaign_code, body.campaign_name, typeof body.campaign === "string" ? body.campaign : ""] 363 .map(clean).join(" ").toLowerCase(); 364 const customKeys = Object.keys(body).filter((key) => !STANDARD_DELIVERY_FIELDS.has(key.toLowerCase())).join(" ").toLowerCase(); 365 const customValues = Object.entries(body) 366 .filter(([key, value]) => !STANDARD_DELIVERY_FIELDS.has(key.toLowerCase()) && typeof value === "string") 367 .map(([, value]) => value).join(" ").toLowerCase(); 368 const haystack = `${explicit} ${customKeys} ${customValues}`; 369 if (/energie|energy|electric|électric|gaz|fournisseur|compteur|\bpdl\b|\bpce\b/.test(haystack)) return "energie"; 370 if (/telco|telecom|télécom|operateur|opérateur|internet|fibre|forfait|\bbox\b/.test(haystack)) return "telco"; 371 if (ENERGY_DELIVERY_SIDS.has(clean(body.sid))) return "energie"; 372 return ""; 373} 374 375function canonicalCampaignReference(category) { 376 if (category === "energie") return "FR-ENERGIE"; 377 if (category === "telco") return "FR--TELCO"; 378 return "LEADBYTE-UNCLASSIFIED"; 379} 380 381function firstClean(...values) { 382 for (const value of values) { 383 const cleaned = clean(value); 384 if (cleaned) return cleaned; 385 } 386 return ""; 387} 388