/ src / utils / compact-contacts.js
compact-contacts.js
 1  /**
 2   * Compact and deduplicate contacts_json objects.
 3   *
 4   * - Deduplicates email_addresses (by email value, case-insensitive)
 5   * - Deduplicates phone_numbers (by number value after stripping whitespace)
 6   * - Deduplicates social_profiles (by URL)
 7   * - Removes keys with null values, empty arrays, or empty strings
 8   *
 9   * Reusable utility: imported by the one-off compaction script AND
10   * wired into the enrich stage so new contacts are always compact.
11   */
12  
13  /**
14   * Compact a contacts_json object: dedup arrays, strip empties/nulls.
15   * @param {Object} contacts - Parsed contacts_json object
16   * @returns {Object} Compacted contacts object (new object, original not mutated)
17   */
18  export function compactContacts(contacts) {
19    if (!contacts || typeof contacts !== 'object' || Array.isArray(contacts)) {
20      return contacts;
21    }
22  
23    const result = { ...contacts };
24  
25    // Deduplicate email_addresses by email value (case-insensitive)
26    if (Array.isArray(result.email_addresses)) {
27      const seen = new Set();
28      result.email_addresses = result.email_addresses.filter(entry => {
29        if (!entry || typeof entry !== 'object') return false;
30        const key = (entry.email || entry.address || '').toLowerCase().trim();
31        if (!key || seen.has(key)) return false;
32        seen.add(key);
33        return true;
34      });
35    }
36  
37    // Deduplicate phone_numbers by number value (strip all whitespace)
38    if (Array.isArray(result.phone_numbers)) {
39      const seen = new Set();
40      result.phone_numbers = result.phone_numbers.filter(entry => {
41        if (!entry || typeof entry !== 'object') return false;
42        const key = (entry.number || '').replace(/\s+/g, '');
43        if (!key || seen.has(key)) return false;
44        seen.add(key);
45        return true;
46      });
47    }
48  
49    // Deduplicate social_profiles by URL
50    if (Array.isArray(result.social_profiles)) {
51      const seen = new Set();
52      result.social_profiles = result.social_profiles.filter(entry => {
53        if (!entry || typeof entry !== 'object') return false;
54        const key = (entry.url || '').trim();
55        if (!key || seen.has(key)) return false;
56        seen.add(key);
57        return true;
58      });
59    }
60  
61    // Remove keys with null values, empty arrays, or empty strings
62    for (const key of Object.keys(result)) {
63      const val = result[key];
64      if (val === null || val === '' || (Array.isArray(val) && val.length === 0)) {
65        delete result[key];
66      }
67    }
68  
69    return result;
70  }