Preuve · code, extrait

backend/leadbyte-event-inspector.mjs

Dépôt jeremy-flexylead/flexylead-crm · commit ccbbaa07870e0f786729003dd6f8862e4bcc8256 · dossier de production synchronisé le 14 septembre 2026 à 08 h 46

Extrait tel qu'à ce commit : lignes 313–388, sur 1 307 (numérotation d'origine, 30 lignes de contexte autour de chaque citation) · fichier intégral sur GitHub (dépôt privé, accès requis)

Lignes citées par l'audit : 343–358

76 lignes reproduites · ← retour à l'audit · catalogue des preuves

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