@archerjessop/utilities 4.6.27 → 4.6.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- function formatDate(t){return`${(t.getMonth()+1).toString().padStart(2,"0")}/${t.getDate().toString().padStart(2,"0")}/${t.getFullYear()}`}function calculateDOM(t,e=!0){if(!t||"Not found"===t)return"Not found";try{let a;const r=t.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);if(r){const[,t,e,n]=r;a=new Date(n,t-1,e)}else a=new Date(t);if(isNaN(a.getTime()))return"Invalid date";const n=new Date-a,o=Math.floor(n/864e5);return e?`${o} (${formatDate(a)})`:o}catch(t){return"Calculation error"}}export{calculateDOM,formatDate};
1
+ function formatDate(t,e=!0){if(!t)return"";let n;if(/[zZ]|([+\-]\d{2}:?\d{2})/.test(t))n=new Date(t);else{const[e,r,a]=t.split("T")[0].split("-").map(Number);n=new Date(e,r-1,a)}if(isNaN(n))return"";const r=e?{month:"short",day:"numeric",year:"numeric"}:{month:"short",day:"numeric"};return n.toLocaleDateString("en-US",r)}function calculateDOM(t,e=!0){if(!t||"Not found"===t)return"Not found";try{let n;const r=t.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);if(r){const[,t,e,a]=r;n=new Date(a,t-1,e)}else n=new Date(t);if(isNaN(n.getTime()))return"Invalid date";const a=new Date-n,o=Math.floor(a/864e5);return e?`${o} (${formatDate(n)})`:o}catch(t){return"Calculation error"}}export{calculateDOM,formatDate};
2
2
  //# sourceMappingURL=utilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.js","sources":["../../src/date/utilities.js"],"sourcesContent":["/**\r\n * DATE utilities\r\n */\r\n\r\nexport function formatDate(date) {\r\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\r\n const day = date.getDate().toString().padStart(2, \"0\");\r\n const year = date.getFullYear();\r\n return `${month}/${day}/${year}`;\r\n}\r\n\r\nexport function calculateDOM(listingDateText, returnFormattedDate = true) {\r\n if (!listingDateText || listingDateText === \"Not found\") {\r\n return \"Not found\";\r\n }\r\n\r\n try {\r\n // Parse various date formats\r\n let listingDate;\r\n \r\n // Try parsing MM/DD/YYYY format\r\n const mmddyyyyMatch = listingDateText.match(/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})/);\r\n if (mmddyyyyMatch) {\r\n const [, month, day, year] = mmddyyyyMatch;\r\n listingDate = new Date(year, month - 1, day);\r\n } else {\r\n // Try parsing other common date formats\r\n listingDate = new Date(listingDateText);\r\n }\r\n\r\n if (isNaN(listingDate.getTime())) {\r\n return \"Invalid date\";\r\n }\r\n\r\n // Calculate days difference\r\n const today = new Date();\r\n const diffTime = today - listingDate;\r\n const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));\r\n\r\n if(returnFormattedDate) {\r\n return `${diffDays} (${formatDate(listingDate)})`;\r\n }\r\n else {\r\n return diffDays;\r\n }\r\n } catch (error) {\r\n return \"Calculation error\";\r\n }\r\n}\r\n\r\n"],"names":["formatDate","date","getMonth","toString","padStart","getDate","getFullYear","calculateDOM","listingDateText","returnFormattedDate","listingDate","mmddyyyyMatch","match","month","day","year","Date","isNaN","getTime","diffTime","diffDays","Math","floor","error"],"mappings":"AAIO,SAASA,WAAWC,GAIzB,MAAO,IAHQA,EAAKC,WAAa,GAAGC,WAAWC,SAAS,EAAG,QAC/CH,EAAKI,UAAUF,WAAWC,SAAS,EAAG,QACrCH,EAAKK,eAEpB,CAEO,SAASC,aAAaC,EAAiBC,GAAsB,GAClE,IAAKD,GAAuC,cAApBA,EACtB,MAAO,YAGT,IAEE,IAAIE,EAGJ,MAAMC,EAAgBH,EAAgBI,MAAM,iCAC5C,GAAID,EAAe,CACjB,MAAM,CAAGE,EAAOC,EAAKC,GAAQJ,EAC7BD,EAAc,IAAIM,KAAKD,EAAMF,EAAQ,EAAGC,EAC1C,MAEEJ,EAAc,IAAIM,KAAKR,GAGzB,GAAIS,MAAMP,EAAYQ,WACpB,MAAO,eAIT,MACMC,EADQ,IAAIH,KACON,EACnBU,EAAWC,KAAKC,MAAMH,EAAQ,OAEpC,OAAGV,EACM,GAAGW,MAAapB,WAAWU,MAG3BU,CAEX,CAAE,MAAOG,GACP,MAAO,mBACT,CACF"}
1
+ {"version":3,"file":"utilities.js","sources":["../../src/date/utilities.js"],"sourcesContent":["/**\r\n * DATE utilities\r\n */\r\n\r\nexport function formatDate(dateString, includeYear = true) {\r\n if (!dateString) return \"\";\r\n\r\n // Detect if the string includes a timezone (Z or ±HH:MM)\r\n const hasTimezone = /[zZ]|([+\\-]\\d{2}:?\\d{2})/.test(dateString);\r\n\r\n let date;\r\n if (hasTimezone) {\r\n date = new Date(dateString);\r\n } else {\r\n const [year, month, day] = dateString.split(\"T\")[0].split(\"-\").map(Number);\r\n date = new Date(year, month - 1, day);\r\n }\r\n\r\n if (isNaN(date)) return \"\";\r\n\r\n const options = includeYear\r\n ? { month: \"short\", day: \"numeric\", year: \"numeric\" }\r\n : { month: \"short\", day: \"numeric\" };\r\n\r\n return date.toLocaleDateString(\"en-US\", options);\r\n}\r\n\r\n\r\nexport function calculateDOM(listingDateText, returnFormattedDate = true) {\r\n if (!listingDateText || listingDateText === \"Not found\") {\r\n return \"Not found\";\r\n }\r\n\r\n try {\r\n // Parse various date formats\r\n let listingDate;\r\n \r\n // Try parsing MM/DD/YYYY format\r\n const mmddyyyyMatch = listingDateText.match(/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})/);\r\n if (mmddyyyyMatch) {\r\n const [, month, day, year] = mmddyyyyMatch;\r\n listingDate = new Date(year, month - 1, day);\r\n } else {\r\n // Try parsing other common date formats\r\n listingDate = new Date(listingDateText);\r\n }\r\n\r\n if (isNaN(listingDate.getTime())) {\r\n return \"Invalid date\";\r\n }\r\n\r\n // Calculate days difference\r\n const today = new Date();\r\n const diffTime = today - listingDate;\r\n const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));\r\n\r\n if(returnFormattedDate) {\r\n return `${diffDays} (${formatDate(listingDate)})`;\r\n }\r\n else {\r\n return diffDays;\r\n }\r\n } catch (error) {\r\n return \"Calculation error\";\r\n }\r\n}\r\n\r\n"],"names":["formatDate","dateString","includeYear","date","test","Date","year","month","day","split","map","Number","isNaN","options","toLocaleDateString","calculateDOM","listingDateText","returnFormattedDate","listingDate","mmddyyyyMatch","match","getTime","diffTime","diffDays","Math","floor","error"],"mappings":"AAIO,SAASA,WAAWC,EAAYC,GAAc,GACnD,IAAKD,EAAY,MAAO,GAKxB,IAAIE,EACJ,GAHoB,2BAA2BC,KAAKH,GAIlDE,EAAO,IAAIE,KAAKJ,OACX,CACL,MAAOK,EAAMC,EAAOC,GAAOP,EAAWQ,MAAM,KAAK,GAAGA,MAAM,KAAKC,IAAIC,QACnER,EAAO,IAAIE,KAAKC,EAAMC,EAAQ,EAAGC,EACnC,CAEA,GAAII,MAAMT,GAAO,MAAO,GAExB,MAAMU,EAAUX,EACZ,CAAEK,MAAO,QAASC,IAAK,UAAWF,KAAM,WACxC,CAAEC,MAAO,QAASC,IAAK,WAE3B,OAAOL,EAAKW,mBAAmB,QAASD,EAC1C,CAGO,SAASE,aAAaC,EAAiBC,GAAsB,GAClE,IAAKD,GAAuC,cAApBA,EACtB,MAAO,YAGT,IAEE,IAAIE,EAGJ,MAAMC,EAAgBH,EAAgBI,MAAM,iCAC5C,GAAID,EAAe,CACjB,MAAM,CAAGZ,EAAOC,EAAKF,GAAQa,EAC7BD,EAAc,IAAIb,KAAKC,EAAMC,EAAQ,EAAGC,EAC1C,MAEEU,EAAc,IAAIb,KAAKW,GAGzB,GAAIJ,MAAMM,EAAYG,WACpB,MAAO,eAIT,MACMC,EADQ,IAAIjB,KACOa,EACnBK,EAAWC,KAAKC,MAAMH,EAAQ,OAEpC,OAAGL,EACM,GAAGM,MAAavB,WAAWkB,MAG3BK,CAEX,CAAE,MAAOG,GACP,MAAO,mBACT,CACF"}
@@ -1,2 +1,2 @@
1
- import{MATCH_TYPES as t,LOI_LOOKUP_CONFIG as e}from"../config/loi-lookup.js";function normalizeAddress(t){return t?t.toLowerCase().replace(/[^\w\s]/g," ").replace(/\s+/g," ").trim():""}function parseAddress(t){const e=normalizeAddress(t),r=e.match(/\b\d{5}\b/),a=r?r[0]:null,s=e.match(/\b([a-z]{2})\s+\d{5}\b/),c=s?s[1]:null,n=e.match(/([a-z\s]+)\s+[a-z]{2}\s+\d{5}/),o=n?n[1].trim():null,u=e.match(/^(.+?)\s+[a-z\s]+\s+[a-z]{2}\s+\d{5}/);return{city:o,full:e,state:c,street:u?u[1].trim():e,zip:a}}function matchAddresses(e,r){const a=parseAddress(e),s=parseAddress(r);if(a.city&&s.city&&a.city!==s.city)return{matchType:t.NO_MATCH,score:0};if(a.zip&&s.zip&&a.zip!==s.zip)return{matchType:t.NO_MATCH,score:0};if(a.state&&s.state&&a.state!==s.state)return{matchType:t.NO_MATCH,score:0};const c=function(t,e){if(!t||!e)return 0;const r=normalizeAddress(t),a=normalizeAddress(e);if(r===a)return 1;if(r.includes(a)||a.includes(r))return.8;const s=r.split(" ").filter(t=>t.length>2),c=a.split(" ").filter(t=>t.length>2);return 0===s.length||0===c.length?0:2*s.filter(t=>c.includes(t)).length/(s.length+c.length)}(e,r);return c>=.95?{matchType:t.EXACT,score:c}:c>=.6?{matchType:t.FUZZY,score:c}:{matchType:t.NO_MATCH,score:c}}async function lookupLOI(r){if(!r)return{data:null,matchType:t.NO_RESPONSE,searchQuery:r};const a=await async function(r){try{const a=await fetch(e.SPREADSHEET_URL,{headers:{Accept:"text/csv"},method:"GET"});if(console.log("lookupFromSpreadsheet response",a),!a.ok)return{data:null,matchType:t.NO_RESPONSE,searchQuery:r};const s=function(t){const e=t.split("\n").filter(t=>t.trim());if(0===e.length)return[];const r=[];for(let t=1;t<e.length;t++){const a=e[t],s=[];let c="",n=!1;for(let t=0;t<a.length;t++){const e=a[t];'"'===e?n=!n:","!==e||n?c+=e:(s.push(c.trim()),c="")}s.push(c.trim()),s.length>=4&&s[1]&&r.push({address:s[1],contactName:s[3]||null,date:s[2]||null})}return r}(await a.text());let c=null,n=0;for(const a of s){const s=matchAddresses(r,a.address);s.matchType!==t.NO_MATCH&&s.score>n&&(n=s.score,c={data:{contactName:a.contactName,createdAt:a.date,opportunityAddress:a.address,opportunityName:a.address,statusOrStage:e.LOI_SENT_STATUS,updatedAt:a.date,foundIn:"spreadsheet"},matchType:s.matchType,score:s.score,searchQuery:r})}return c||{data:null,matchType:t.NO_MATCH,searchQuery:r}}catch(e){return{data:null,error:e.message,matchType:t.NO_RESPONSE,searchQuery:r}}}(r);return a.matchType!==t.NO_MATCH&&a.matchType!==t.NO_RESPONSE?a:await async function(r){try{const a=new URL(e.API_BASE_URL);a.pathname=e.WEBHOOK_PATH,a.searchParams.set("location_id",e.LOCATION_ID),a.searchParams.set("q",r);const s=await fetch(a.toString(),{headers:{Accept:"application/json"},method:"GET"});if(console.log("lookupFromAPI response",s),!s.ok)return{data:null,error:`HTTP ${s.status}`,matchType:t.NO_RESPONSE,searchQuery:r};const c=await s.json();if(!c||!c.opportunityName)return{data:null,matchType:t.NO_RESPONSE,searchQuery:r};const n=c.opportunityName.split("+")[0].trim(),o=matchAddresses(r,n);return{data:{contactName:c.contactName,createdAt:c.createdAt,opportunityAddress:n,opportunityName:c.opportunityName,statusOrStage:c.statusOrStage,updatedAt:c.updatedAt,foundIn:"api"},matchType:o.matchType,score:o.score,searchQuery:r}}catch(e){return{data:null,error:e.message,matchType:t.NO_RESPONSE,searchQuery:r}}}(r)}export{lookupLOI};
1
+ import{MATCH_TYPES as t,LOI_LOOKUP_CONFIG as e}from"../config/loi-lookup.js";function normalizeAddress(t){return t?t.toLowerCase().replace(/[^\w\s]/g," ").replace(/\s+/g," ").trim():""}function parseAddress(t){const e=normalizeAddress(t),r=e.match(/\b\d{5}\b/),a=r?r[0]:null,s=e.match(/\b([a-z]{2})\s+\d{5}\b/),n=s?s[1]:null,c=e.match(/([a-z\s]+)\s+[a-z]{2}\s+\d{5}/),o=c?c[1].trim():null,i=e.match(/^(.+?)\s+[a-z\s]+\s+[a-z]{2}\s+\d{5}/);return{city:o,full:e,state:n,street:i?i[1].trim():e,zip:a}}function matchAddresses(e,r){const a=parseAddress(e),s=parseAddress(r);if(a.city&&s.city&&a.city!==s.city)return{matchType:t.NO_MATCH,score:0};if(a.zip&&s.zip&&a.zip!==s.zip)return{matchType:t.NO_MATCH,score:0};if(a.state&&s.state&&a.state!==s.state)return{matchType:t.NO_MATCH,score:0};const n=function(t,e){if(!t||!e)return 0;const r=normalizeAddress(t),a=normalizeAddress(e);if(r===a)return 1;if(r.includes(a)||a.includes(r))return.8;const s=r.split(" ").filter(t=>t.length>2),n=a.split(" ").filter(t=>t.length>2);return 0===s.length||0===n.length?0:2*s.filter(t=>n.includes(t)).length/(s.length+n.length)}(e,r);return n>=.95?{matchType:t.EXACT,score:n}:n>=.6?{matchType:t.FUZZY,score:n}:{matchType:t.NO_MATCH,score:n}}async function lookupLOI(r){return r?await async function(r){try{const a=new URL(e.API_BASE_URL);a.pathname=e.WEBHOOK_PATH,a.searchParams.set("location_id",e.LOCATION_ID),a.searchParams.set("q",r);const s=await fetch(a.toString(),{headers:{Accept:"application/json"},method:"GET"});if(console.log("lookupFromAPI response",s),!s.ok)return{data:null,error:`HTTP ${s.status}`,matchType:t.NO_RESPONSE,searchQuery:r};const n=await s.json();if(!n||!n.opportunityName)return{data:null,matchType:t.NO_RESPONSE,searchQuery:r};const c=n.opportunityName.split("+")[0].trim(),o=matchAddresses(r,c);return{data:{contactName:n.contactName,createdAt:n.createdAt,opportunityAddress:c,opportunityName:n.opportunityName,statusOrStage:n.statusOrStage,updatedAt:n.updatedAt,foundIn:"api"},matchType:o.matchType,score:o.score,searchQuery:r}}catch(e){return{data:null,error:e.message,matchType:t.NO_RESPONSE,searchQuery:r}}}(r):{data:null,matchType:t.NO_RESPONSE,searchQuery:r}}export{lookupLOI};
2
2
  //# sourceMappingURL=loi-lookup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loi-lookup.js","sources":["../../src/services/loi-lookup.js"],"sourcesContent":["/**\r\n * LOI Lookup Service\r\n * Checks if a property address has an existing LOI in the CRM system\r\n */\r\n\r\nimport { LOI_LOOKUP_CONFIG, MATCH_TYPES } from \"../config/loi-lookup.js\";\r\n\r\n/**\r\n * Normalize an address string for comparison\r\n * @param {string} address - Address string to normalize\r\n * @returns {string} Normalized address\r\n */\r\nfunction normalizeAddress(address) {\r\n if (!address) return \"\";\r\n \r\n return address\r\n .toLowerCase()\r\n .replace(/[^\\w\\s]/g, \" \")\r\n .replace(/\\s+/g, \" \")\r\n .trim();\r\n}\r\n\r\n/**\r\n * Extract components from an address string\r\n * @param {string} address - Address to parse\r\n * @returns {Object} Address components\r\n */\r\nfunction parseAddress(address) {\r\n const normalized = normalizeAddress(address);\r\n \r\n const zipMatch = normalized.match(/\\b\\d{5}\\b/);\r\n const zip = zipMatch ? zipMatch[0] : null;\r\n \r\n const stateMatch = normalized.match(/\\b([a-z]{2})\\s+\\d{5}\\b/);\r\n const state = stateMatch ? stateMatch[1] : null;\r\n \r\n const cityMatch = normalized.match(/([a-z\\s]+)\\s+[a-z]{2}\\s+\\d{5}/);\r\n const city = cityMatch ? cityMatch[1].trim() : null;\r\n \r\n const streetMatch = normalized.match(/^(.+?)\\s+[a-z\\s]+\\s+[a-z]{2}\\s+\\d{5}/);\r\n const street = streetMatch ? streetMatch[1].trim() : normalized;\r\n \r\n return {\r\n city,\r\n full: normalized,\r\n state,\r\n street,\r\n zip,\r\n };\r\n}\r\n\r\n/**\r\n * Calculate similarity score between two strings (0-1)\r\n * @param {string} str1 - First string\r\n * @param {string} str2 - Second string\r\n * @returns {number} Similarity score\r\n */\r\nfunction calculateSimilarity(str1, str2) {\r\n if (!str1 || !str2) return 0;\r\n \r\n const s1 = normalizeAddress(str1);\r\n const s2 = normalizeAddress(str2);\r\n \r\n if (s1 === s2) return 1.0;\r\n \r\n if (s1.includes(s2) || s2.includes(s1)) return 0.8;\r\n \r\n const words1 = s1.split(\" \").filter(w => w.length > 2);\r\n const words2 = s2.split(\" \").filter(w => w.length > 2);\r\n \r\n if (words1.length === 0 || words2.length === 0) return 0;\r\n \r\n const commonWords = words1.filter(w => words2.includes(w));\r\n const overlapRatio = (commonWords.length * 2) / (words1.length + words2.length);\r\n \r\n return overlapRatio;\r\n}\r\n\r\n/**\r\n * Match two addresses using fuzzy matching\r\n * @param {string} searchQuery - The address being searched\r\n * @param {string} opportunityAddress - Address from the CRM opportunity\r\n * @returns {Object} Match result with type and score\r\n */\r\nfunction matchAddresses(searchQuery, opportunityAddress) {\r\n const search = parseAddress(searchQuery);\r\n const opportunity = parseAddress(opportunityAddress);\r\n \r\n if (search.city && opportunity.city) {\r\n if (search.city !== opportunity.city) {\r\n return { matchType: MATCH_TYPES.NO_MATCH, score: 0 };\r\n }\r\n }\r\n \r\n if (search.zip && opportunity.zip) {\r\n if (search.zip !== opportunity.zip) {\r\n return { matchType: MATCH_TYPES.NO_MATCH, score: 0 };\r\n }\r\n }\r\n \r\n if (search.state && opportunity.state) {\r\n if (search.state !== opportunity.state) {\r\n return { matchType: MATCH_TYPES.NO_MATCH, score: 0 };\r\n }\r\n }\r\n \r\n const similarity = calculateSimilarity(searchQuery, opportunityAddress);\r\n \r\n if (similarity >= 0.95) {\r\n return { matchType: MATCH_TYPES.EXACT, score: similarity };\r\n }\r\n \r\n if (similarity >= 0.6) {\r\n return { matchType: MATCH_TYPES.FUZZY, score: similarity };\r\n }\r\n \r\n return { matchType: MATCH_TYPES.NO_MATCH, score: similarity };\r\n}\r\n\r\n/**\r\n * Parse CSV text into array of objects\r\n * Handles quoted fields with commas correctly\r\n * @param {string} csvText - CSV content\r\n * @returns {Array<Object>} Parsed rows\r\n */\r\nfunction parseCSV(csvText) {\r\n const lines = csvText.split(\"\\n\").filter(line => line.trim());\r\n if (lines.length === 0) return [];\r\n \r\n const rows = [];\r\n \r\n for (let i = 1; i < lines.length; i++) {\r\n const line = lines[i];\r\n const cols = [];\r\n let current = \"\";\r\n let inQuotes = false;\r\n \r\n for (let j = 0; j < line.length; j++) {\r\n const char = line[j];\r\n \r\n if (char === '\"') {\r\n inQuotes = !inQuotes;\r\n } else if (char === \",\" && !inQuotes) {\r\n cols.push(current.trim());\r\n current = \"\";\r\n } else {\r\n current += char;\r\n }\r\n }\r\n cols.push(current.trim());\r\n \r\n if (cols.length >= 4 && cols[1]) {\r\n rows.push({\r\n address: cols[1],\r\n contactName: cols[3] || null,\r\n date: cols[2] || null,\r\n });\r\n }\r\n }\r\n \r\n return rows;\r\n}\r\n\r\n/**\r\n * Lookup LOI from Google Spreadsheet\r\n * DELETE THIS FUNCTION when phasing out spreadsheet\r\n * @param {string} searchQuery - Property address to search\r\n * @returns {Promise<Object>} Lookup result\r\n */\r\nasync function lookupFromSpreadsheet(searchQuery) {\r\n try {\r\n const response = await fetch(LOI_LOOKUP_CONFIG.SPREADSHEET_URL, {\r\n headers: {\r\n \"Accept\": \"text/csv\",\r\n },\r\n method: \"GET\",\r\n });\r\n\r\n console.log('lookupFromSpreadsheet response', response)\r\n \r\n if (!response.ok) {\r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const csvText = await response.text();\r\n const rows = parseCSV(csvText);\r\n \r\n let bestMatch = null;\r\n let bestScore = 0;\r\n \r\n for (const row of rows) {\r\n const matchResult = matchAddresses(searchQuery, row.address);\r\n \r\n if (matchResult.matchType !== MATCH_TYPES.NO_MATCH && matchResult.score > bestScore) {\r\n bestScore = matchResult.score;\r\n bestMatch = {\r\n data: {\r\n contactName: row.contactName,\r\n createdAt: row.date,\r\n opportunityAddress: row.address,\r\n opportunityName: row.address,\r\n statusOrStage: LOI_LOOKUP_CONFIG.LOI_SENT_STATUS,\r\n updatedAt: row.date,\r\n foundIn: \"spreadsheet\",\r\n },\r\n matchType: matchResult.matchType,\r\n score: matchResult.score,\r\n searchQuery,\r\n };\r\n }\r\n }\r\n \r\n if (bestMatch) {\r\n return bestMatch;\r\n }\r\n \r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_MATCH,\r\n searchQuery,\r\n };\r\n \r\n } catch (error) {\r\n return {\r\n data: null,\r\n error: error.message,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Lookup LOI from API\r\n * This function will remain after spreadsheet is phased out\r\n * @param {string} searchQuery - Property address to search\r\n * @returns {Promise<Object>} Lookup result\r\n */\r\nasync function lookupFromAPI(searchQuery) {\r\n try {\r\n const url = new URL(LOI_LOOKUP_CONFIG.API_BASE_URL);\r\n url.pathname = LOI_LOOKUP_CONFIG.WEBHOOK_PATH;\r\n url.searchParams.set(\"location_id\", LOI_LOOKUP_CONFIG.LOCATION_ID);\r\n url.searchParams.set(\"q\", searchQuery);\r\n \r\n const response = await fetch(url.toString(), {\r\n headers: {\r\n \"Accept\": \"application/json\",\r\n },\r\n method: \"GET\",\r\n });\r\n \r\n console.log('lookupFromAPI response', response)\r\n\r\n if (!response.ok) {\r\n return {\r\n data: null,\r\n error: `HTTP ${response.status}`,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const data = await response.json();\r\n \r\n if (!data || !data.opportunityName) {\r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const opportunityAddress = data.opportunityName.split(\"+\")[0].trim();\r\n \r\n const matchResult = matchAddresses(searchQuery, opportunityAddress);\r\n \r\n return {\r\n data: {\r\n contactName: data.contactName,\r\n createdAt: data.createdAt,\r\n opportunityAddress,\r\n opportunityName: data.opportunityName,\r\n statusOrStage: data.statusOrStage,\r\n updatedAt: data.updatedAt,\r\n foundIn: \"api\",\r\n },\r\n matchType: matchResult.matchType,\r\n score: matchResult.score,\r\n searchQuery,\r\n };\r\n \r\n } catch (error) {\r\n return {\r\n data: null,\r\n error: error.message,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Lookup LOI status for a property address\r\n * @param {string} searchQuery - Property address to search\r\n * @returns {Promise<Object>} Lookup result\r\n */\r\nexport async function lookupLOI(searchQuery) {\r\n if (!searchQuery) {\r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const spreadsheetResult = await lookupFromSpreadsheet(searchQuery);\r\n if (spreadsheetResult.matchType !== MATCH_TYPES.NO_MATCH && spreadsheetResult.matchType !== MATCH_TYPES.NO_RESPONSE) {\r\n return spreadsheetResult;\r\n }\r\n \r\n return await lookupFromAPI(searchQuery);\r\n}"],"names":["normalizeAddress","address","toLowerCase","replace","trim","parseAddress","normalized","zipMatch","match","zip","stateMatch","state","cityMatch","city","streetMatch","full","street","matchAddresses","searchQuery","opportunityAddress","search","opportunity","matchType","MATCH_TYPES","NO_MATCH","score","similarity","str1","str2","s1","s2","includes","words1","split","filter","w","length","words2","calculateSimilarity","EXACT","FUZZY","async","lookupLOI","data","NO_RESPONSE","spreadsheetResult","response","fetch","LOI_LOOKUP_CONFIG","SPREADSHEET_URL","headers","Accept","method","console","log","ok","rows","csvText","lines","line","i","cols","current","inQuotes","j","char","push","contactName","date","parseCSV","text","bestMatch","bestScore","row","matchResult","createdAt","opportunityName","statusOrStage","LOI_SENT_STATUS","updatedAt","foundIn","error","message","lookupFromSpreadsheet","url","URL","API_BASE_URL","pathname","WEBHOOK_PATH","searchParams","set","LOCATION_ID","toString","status","json","lookupFromAPI"],"mappings":"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"}
1
+ {"version":3,"file":"loi-lookup.js","sources":["../../src/services/loi-lookup.js"],"sourcesContent":["/**\r\n * LOI Lookup Service\r\n * Checks if a property address has an existing LOI in the CRM system\r\n */\r\n\r\nimport { LOI_LOOKUP_CONFIG, MATCH_TYPES } from \"../config/loi-lookup.js\";\r\n\r\n/**\r\n * Normalize an address string for comparison\r\n * @param {string} address - Address string to normalize\r\n * @returns {string} Normalized address\r\n */\r\nfunction normalizeAddress(address) {\r\n if (!address) return \"\";\r\n \r\n return address\r\n .toLowerCase()\r\n .replace(/[^\\w\\s]/g, \" \")\r\n .replace(/\\s+/g, \" \")\r\n .trim();\r\n}\r\n\r\n/**\r\n * Extract components from an address string\r\n * @param {string} address - Address to parse\r\n * @returns {Object} Address components\r\n */\r\nfunction parseAddress(address) {\r\n const normalized = normalizeAddress(address);\r\n \r\n const zipMatch = normalized.match(/\\b\\d{5}\\b/);\r\n const zip = zipMatch ? zipMatch[0] : null;\r\n \r\n const stateMatch = normalized.match(/\\b([a-z]{2})\\s+\\d{5}\\b/);\r\n const state = stateMatch ? stateMatch[1] : null;\r\n \r\n const cityMatch = normalized.match(/([a-z\\s]+)\\s+[a-z]{2}\\s+\\d{5}/);\r\n const city = cityMatch ? cityMatch[1].trim() : null;\r\n \r\n const streetMatch = normalized.match(/^(.+?)\\s+[a-z\\s]+\\s+[a-z]{2}\\s+\\d{5}/);\r\n const street = streetMatch ? streetMatch[1].trim() : normalized;\r\n \r\n return {\r\n city,\r\n full: normalized,\r\n state,\r\n street,\r\n zip,\r\n };\r\n}\r\n\r\n/**\r\n * Calculate similarity score between two strings (0-1)\r\n * @param {string} str1 - First string\r\n * @param {string} str2 - Second string\r\n * @returns {number} Similarity score\r\n */\r\nfunction calculateSimilarity(str1, str2) {\r\n if (!str1 || !str2) return 0;\r\n \r\n const s1 = normalizeAddress(str1);\r\n const s2 = normalizeAddress(str2);\r\n \r\n if (s1 === s2) return 1.0;\r\n \r\n if (s1.includes(s2) || s2.includes(s1)) return 0.8;\r\n \r\n const words1 = s1.split(\" \").filter(w => w.length > 2);\r\n const words2 = s2.split(\" \").filter(w => w.length > 2);\r\n \r\n if (words1.length === 0 || words2.length === 0) return 0;\r\n \r\n const commonWords = words1.filter(w => words2.includes(w));\r\n const overlapRatio = (commonWords.length * 2) / (words1.length + words2.length);\r\n \r\n return overlapRatio;\r\n}\r\n\r\n/**\r\n * Match two addresses using fuzzy matching\r\n * @param {string} searchQuery - The address being searched\r\n * @param {string} opportunityAddress - Address from the CRM opportunity\r\n * @returns {Object} Match result with type and score\r\n */\r\nfunction matchAddresses(searchQuery, opportunityAddress) {\r\n const search = parseAddress(searchQuery);\r\n const opportunity = parseAddress(opportunityAddress);\r\n \r\n if (search.city && opportunity.city) {\r\n if (search.city !== opportunity.city) {\r\n return { matchType: MATCH_TYPES.NO_MATCH, score: 0 };\r\n }\r\n }\r\n \r\n if (search.zip && opportunity.zip) {\r\n if (search.zip !== opportunity.zip) {\r\n return { matchType: MATCH_TYPES.NO_MATCH, score: 0 };\r\n }\r\n }\r\n \r\n if (search.state && opportunity.state) {\r\n if (search.state !== opportunity.state) {\r\n return { matchType: MATCH_TYPES.NO_MATCH, score: 0 };\r\n }\r\n }\r\n \r\n const similarity = calculateSimilarity(searchQuery, opportunityAddress);\r\n \r\n if (similarity >= 0.95) {\r\n return { matchType: MATCH_TYPES.EXACT, score: similarity };\r\n }\r\n \r\n if (similarity >= 0.6) {\r\n return { matchType: MATCH_TYPES.FUZZY, score: similarity };\r\n }\r\n \r\n return { matchType: MATCH_TYPES.NO_MATCH, score: similarity };\r\n}\r\n\r\n/**\r\n * Parse CSV text into array of objects\r\n * Handles quoted fields with commas correctly\r\n * @param {string} csvText - CSV content\r\n * @returns {Array<Object>} Parsed rows\r\n */\r\nfunction parseCSV(csvText) {\r\n const lines = csvText.split(\"\\n\").filter(line => line.trim());\r\n if (lines.length === 0) return [];\r\n \r\n const rows = [];\r\n \r\n for (let i = 1; i < lines.length; i++) {\r\n const line = lines[i];\r\n const cols = [];\r\n let current = \"\";\r\n let inQuotes = false;\r\n \r\n for (let j = 0; j < line.length; j++) {\r\n const char = line[j];\r\n \r\n if (char === '\"') {\r\n inQuotes = !inQuotes;\r\n } else if (char === \",\" && !inQuotes) {\r\n cols.push(current.trim());\r\n current = \"\";\r\n } else {\r\n current += char;\r\n }\r\n }\r\n cols.push(current.trim());\r\n \r\n if (cols.length >= 4 && cols[1]) {\r\n rows.push({\r\n address: cols[1],\r\n contactName: cols[3] || null,\r\n date: cols[2] || null,\r\n });\r\n }\r\n }\r\n \r\n return rows;\r\n}\r\n\r\n/**\r\n * Lookup LOI from Google Spreadsheet\r\n * DELETE THIS FUNCTION when phasing out spreadsheet\r\n * @param {string} searchQuery - Property address to search\r\n * @returns {Promise<Object>} Lookup result\r\n */\r\nasync function lookupFromSpreadsheet(searchQuery) {\r\n try {\r\n const response = await fetch(LOI_LOOKUP_CONFIG.SPREADSHEET_URL, {\r\n headers: {\r\n \"Accept\": \"text/csv\",\r\n },\r\n method: \"GET\",\r\n });\r\n\r\n console.log('lookupFromSpreadsheet response', response)\r\n \r\n if (!response.ok) {\r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const csvText = await response.text();\r\n const rows = parseCSV(csvText);\r\n \r\n let bestMatch = null;\r\n let bestScore = 0;\r\n \r\n for (const row of rows) {\r\n const matchResult = matchAddresses(searchQuery, row.address);\r\n \r\n if (matchResult.matchType !== MATCH_TYPES.NO_MATCH && matchResult.score > bestScore) {\r\n bestScore = matchResult.score;\r\n bestMatch = {\r\n data: {\r\n contactName: row.contactName,\r\n createdAt: row.date,\r\n opportunityAddress: row.address,\r\n opportunityName: row.address,\r\n statusOrStage: LOI_LOOKUP_CONFIG.LOI_SENT_STATUS,\r\n updatedAt: row.date,\r\n foundIn: \"spreadsheet\",\r\n },\r\n matchType: matchResult.matchType,\r\n score: matchResult.score,\r\n searchQuery,\r\n };\r\n }\r\n }\r\n \r\n if (bestMatch) {\r\n return bestMatch;\r\n }\r\n \r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_MATCH,\r\n searchQuery,\r\n };\r\n \r\n } catch (error) {\r\n return {\r\n data: null,\r\n error: error.message,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Lookup LOI from API\r\n * This function will remain after spreadsheet is phased out\r\n * @param {string} searchQuery - Property address to search\r\n * @returns {Promise<Object>} Lookup result\r\n */\r\nasync function lookupFromAPI(searchQuery) {\r\n try {\r\n const url = new URL(LOI_LOOKUP_CONFIG.API_BASE_URL);\r\n url.pathname = LOI_LOOKUP_CONFIG.WEBHOOK_PATH;\r\n url.searchParams.set(\"location_id\", LOI_LOOKUP_CONFIG.LOCATION_ID);\r\n url.searchParams.set(\"q\", searchQuery);\r\n \r\n const response = await fetch(url.toString(), {\r\n headers: {\r\n \"Accept\": \"application/json\",\r\n },\r\n method: \"GET\",\r\n });\r\n \r\n console.log('lookupFromAPI response', response)\r\n\r\n if (!response.ok) {\r\n return {\r\n data: null,\r\n error: `HTTP ${response.status}`,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const data = await response.json();\r\n \r\n if (!data || !data.opportunityName) {\r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n const opportunityAddress = data.opportunityName.split(\"+\")[0].trim();\r\n \r\n const matchResult = matchAddresses(searchQuery, opportunityAddress);\r\n \r\n return {\r\n data: {\r\n contactName: data.contactName,\r\n createdAt: data.createdAt,\r\n opportunityAddress,\r\n opportunityName: data.opportunityName,\r\n statusOrStage: data.statusOrStage,\r\n updatedAt: data.updatedAt,\r\n foundIn: \"api\",\r\n },\r\n matchType: matchResult.matchType,\r\n score: matchResult.score,\r\n searchQuery,\r\n };\r\n \r\n } catch (error) {\r\n return {\r\n data: null,\r\n error: error.message,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Lookup LOI status for a property address\r\n * @param {string} searchQuery - Property address to search\r\n * @returns {Promise<Object>} Lookup result\r\n */\r\nexport async function lookupLOI(searchQuery) {\r\n if (!searchQuery) {\r\n return {\r\n data: null,\r\n matchType: MATCH_TYPES.NO_RESPONSE,\r\n searchQuery,\r\n };\r\n }\r\n \r\n // const spreadsheetResult = await lookupFromSpreadsheet(searchQuery);\r\n // if (spreadsheetResult.matchType !== MATCH_TYPES.NO_MATCH && spreadsheetResult.matchType !== MATCH_TYPES.NO_RESPONSE) {\r\n // return spreadsheetResult;\r\n // }\r\n \r\n return await lookupFromAPI(searchQuery);\r\n}"],"names":["normalizeAddress","address","toLowerCase","replace","trim","parseAddress","normalized","zipMatch","match","zip","stateMatch","state","cityMatch","city","streetMatch","full","street","matchAddresses","searchQuery","opportunityAddress","search","opportunity","matchType","MATCH_TYPES","NO_MATCH","score","similarity","str1","str2","s1","s2","includes","words1","split","filter","w","length","words2","calculateSimilarity","EXACT","FUZZY","async","lookupLOI","url","URL","LOI_LOOKUP_CONFIG","API_BASE_URL","pathname","WEBHOOK_PATH","searchParams","set","LOCATION_ID","response","fetch","toString","headers","Accept","method","console","log","ok","data","error","status","NO_RESPONSE","json","opportunityName","matchResult","contactName","createdAt","statusOrStage","updatedAt","foundIn","message","lookupFromAPI"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archerjessop/utilities",
3
- "version": "4.6.27",
3
+ "version": "4.6.29",
4
4
  "description": "Shared utilities for ArcherJessop property analysis tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",