@archerjessop/utilities 1.0.0 → 2.0.0

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.
package/README.md CHANGED
@@ -4,7 +4,14 @@ Comprehensive set of DOM manipulation, data extraction utilities, and various ca
4
4
 
5
5
  ## Updating the package
6
6
 
7
- Make your changes then, depending on the changes:
7
+ Make your changes then:
8
+
9
+ ```bash
10
+ npm run build
11
+ npm test
12
+ ```
13
+
14
+ and then, depending on the changes:
8
15
 
9
16
  For bug fixes (backward compatible):
10
17
 
@@ -0,0 +1,2 @@
1
+ function extractPhoneNumber(){const t=document.querySelector(".phone-number")||document.querySelector("a[href^='tel:']")||document.querySelector(".number")||document.querySelector("[class*='phone']");if(t){if(t.textContent&&"Call"!==t.textContent.trim())return t.textContent.trim();if(t.href){const e=t.href.match(/tel:(.+)/);if(e)return e[1]}}const e=(document.body&&document.body.textContent||"").match(/(\+?1?\s*\(?[0-9]{3}\)?[\s.-]*[0-9]{3}[\s.-]*[0-9]{4})/);return e?e[1].trim():"Not found"}function extractBedrooms(){try{const t=document.body?.textContent||"",e=[/(\d+)\s*bed/i,/(\d+)\s*bedroom/i,/beds?\s*:\s*(\d+)/i,/bedrooms?\s*:\s*(\d+)/i,/(\d+)\s*BR/i,/(\d+)br/i];for(const o of e){const e=t.match(o);if(e){const t=parseInt(e[1]);if(t>0&&t<100)return t}}const o=document.querySelector(".property-details")||document.querySelector("#PropertyDetails")||document.querySelector(".details");if(o){const t=o.textContent||"";for(const o of e){const e=t.match(o);if(e){const t=parseInt(e[1]);if(t>0&&t<100)return t}}}return 10}catch(t){return 10}}export{extractBedrooms,extractPhoneNumber};
2
+ //# sourceMappingURL=extractors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractors.js","sources":["../../src/data/extractors.js"],"sourcesContent":["export function extractPhoneNumber() {\n const phoneElement = document.querySelector(\".phone-number\") ||\n document.querySelector(\"a[href^='tel:']\") ||\n document.querySelector(\".number\") ||\n document.querySelector(\"[class*='phone']\");\n \n if (phoneElement) {\n if (phoneElement.textContent && phoneElement.textContent.trim() !== \"Call\") {\n return phoneElement.textContent.trim();\n } else if (phoneElement.href) {\n // Extract from tel: link\n const telMatch = phoneElement.href.match(/tel:(.+)/);\n if (telMatch) {\n return telMatch[1];\n }\n }\n }\n \n // Fallback to text search with multiple patterns\n const pageText = document.body ? document.body.textContent || \"\" : \"\";\n const phoneMatch = pageText.match(/(\\+?1?\\s*\\(?[0-9]{3}\\)?[\\s.-]*[0-9]{3}[\\s.-]*[0-9]{4})/);\n if (phoneMatch) {\n return phoneMatch[1].trim();\n }\n \n return \"Not found\";\n}\n\nexport function extractBedrooms() {\n try {\n // Look for bedroom information in various places\n const bodyText = document.body?.textContent || \"\";\n \n // Common patterns for bedroom information\n const bedroomPatterns = [\n /(\\d+)\\s*bed/i,\n /(\\d+)\\s*bedroom/i,\n /beds?\\s*:\\s*(\\d+)/i,\n /bedrooms?\\s*:\\s*(\\d+)/i,\n /(\\d+)\\s*BR/i,\n /(\\d+)br/i\n ];\n \n for (const pattern of bedroomPatterns) {\n const match = bodyText.match(pattern);\n if (match) {\n const bedrooms = parseInt(match[1]);\n if (bedrooms > 0 && bedrooms < 100) { // Sanity check\n return bedrooms;\n }\n }\n }\n \n // Look in property details section specifically\n const propertyDetails = document.querySelector(\".property-details\") || \n document.querySelector(\"#PropertyDetails\") ||\n document.querySelector(\".details\");\n \n if (propertyDetails) {\n const detailsText = propertyDetails.textContent || \"\";\n for (const pattern of bedroomPatterns) {\n const match = detailsText.match(pattern);\n if (match) {\n const bedrooms = parseInt(match[1]);\n if (bedrooms > 0 && bedrooms < 100) {\n return bedrooms;\n }\n }\n }\n }\n \n // Default fallback\n return 10; // Default assumption for assisted living\n } catch (error) {\n return 10; // Default fallback\n }\n}\n\n"],"names":["extractPhoneNumber","phoneElement","document","querySelector","textContent","trim","href","telMatch","match","phoneMatch","body","extractBedrooms","bodyText","bedroomPatterns","pattern","bedrooms","parseInt","propertyDetails","detailsText","error"],"mappings":"AAAO,SAASA,qBACd,MAAMC,EAAeC,SAASC,cAAc,kBACxBD,SAASC,cAAc,oBACvBD,SAASC,cAAc,YACvBD,SAASC,cAAc,oBAE3C,GAAIF,EAAc,CAChB,GAAIA,EAAaG,aAAmD,SAApCH,EAAaG,YAAYC,OACvD,OAAOJ,EAAaG,YAAYC,OAC3B,GAAIJ,EAAaK,KAAM,CAE5B,MAAMC,EAAWN,EAAaK,KAAKE,MAAM,YACzC,GAAID,EACF,OAAOA,EAAS,EAEpB,CACF,CAGA,MACME,GADWP,SAASQ,MAAOR,SAASQ,KAAKN,aAAoB,IACvCI,MAAM,0DAClC,OAAIC,EACKA,EAAW,GAAGJ,OAGhB,WACT,CAEO,SAASM,kBACd,IAEE,MAAMC,EAAWV,SAASQ,MAAMN,aAAe,GAGzCS,EAAkB,CACtB,eACA,mBACA,qBACA,yBACA,cACA,YAGF,IAAK,MAAMC,KAAWD,EAAiB,CACrC,MAAML,EAAQI,EAASJ,MAAMM,GAC7B,GAAIN,EAAO,CACT,MAAMO,EAAWC,SAASR,EAAM,IAChC,GAAIO,EAAW,GAAKA,EAAW,IAC7B,OAAOA,CAEX,CACF,CAGA,MAAME,EAAkBf,SAASC,cAAc,sBACxBD,SAASC,cAAc,qBACvBD,SAASC,cAAc,YAE9C,GAAIc,EAAiB,CACnB,MAAMC,EAAcD,EAAgBb,aAAe,GACnD,IAAK,MAAMU,KAAWD,EAAiB,CACrC,MAAML,EAAQU,EAAYV,MAAMM,GAChC,GAAIN,EAAO,CACT,MAAMO,EAAWC,SAASR,EAAM,IAChC,GAAIO,EAAW,GAAKA,EAAW,IAC7B,OAAOA,CAEX,CACF,CACF,CAGA,OAAO,EACT,CAAE,MAAOI,GACP,OAAO,EACT,CACF"}
@@ -0,0 +1,2 @@
1
+ function calculateDOM(t){if(!t||"Not found"===t)return"Not found";try{let e;const a=t.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);if(a){const[,t,n,r]=a;e=new Date(r,t-1,n)}else e=new Date(t);if(isNaN(e.getTime()))return"Invalid date";const n=new Date-e,r=Math.floor(n/864e5);return`${r} (${(t=>`${(t.getMonth()+1).toString().padStart(2,"0")}/${t.getDate().toString().padStart(2,"0")}/${t.getFullYear()}`)(e)})`}catch(t){return"Calculation error"}}export{calculateDOM};
2
+ //# sourceMappingURL=utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.js","sources":["../../src/date/utilities.js"],"sourcesContent":["/**\n * DATE utilities\n */\n\nexport function calculateDOM(listingDateText) {\n if (!listingDateText || listingDateText === \"Not found\") {\n return \"Not found\";\n }\n\n try {\n // Parse various date formats\n let listingDate;\n \n // Try parsing MM/DD/YYYY format\n const mmddyyyyMatch = listingDateText.match(/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})/);\n if (mmddyyyyMatch) {\n const [, month, day, year] = mmddyyyyMatch;\n listingDate = new Date(year, month - 1, day);\n } else {\n // Try parsing other common date formats\n listingDate = new Date(listingDateText);\n }\n\n if (isNaN(listingDate.getTime())) {\n return \"Invalid date\";\n }\n\n // Calculate days difference\n const today = new Date();\n const diffTime = today - listingDate;\n const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));\n\n // Format the original date as MM/DD/YYYY\n const formatDate = (date) => {\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getDate().toString().padStart(2, \"0\");\n const year = date.getFullYear();\n return `${month}/${day}/${year}`;\n };\n\n return `${diffDays} (${formatDate(listingDate)})`;\n } catch (error) {\n return \"Calculation error\";\n }\n}\n\n"],"names":["calculateDOM","listingDateText","listingDate","mmddyyyyMatch","match","month","day","year","Date","isNaN","getTime","diffTime","diffDays","Math","floor","date","getMonth","toString","padStart","getDate","getFullYear","formatDate","error"],"mappings":"AAIO,SAASA,aAAaC,GAC3B,IAAKA,GAAuC,cAApBA,EACtB,MAAO,YAGT,IAEE,IAAIC,EAGJ,MAAMC,EAAgBF,EAAgBG,MAAM,iCAC5C,GAAID,EAAe,CACjB,OAASE,EAAOC,EAAKC,GAAQJ,EAC7BD,EAAc,IAAIM,KAAKD,EAAMF,EAAQ,EAAGC,EAC1C,MAEEJ,EAAc,IAAIM,KAAKP,GAGzB,GAAIQ,MAAMP,EAAYQ,WACpB,MAAO,eAIT,MACMC,EADQ,IAAIH,KACON,EACnBU,EAAWC,KAAKC,MAAMH,EAAQ,OAUpC,MAAO,GAAGC,MAPS,CAACG,GAIX,IAHQA,EAAKC,WAAa,GAAGC,WAAWC,SAAS,EAAG,QAC/CH,EAAKI,UAAUF,WAAWC,SAAS,EAAG,QACrCH,EAAKK,gBAIGC,CAAWnB,KACpC,CAAE,MAAOoB,GACP,MAAO,mBACT,CACF"}
@@ -1,2 +1,2 @@
1
- function calculatePMT(t,a,c){if(0===a)return t/(12*c);const e=a/12,n=12*c;return t*(e*Math.pow(1+e,n))/(Math.pow(1+e,n)-1)}export{calculatePMT};
1
+ function calculatePMT(t,c,a){if(0===c)return t/(12*a);const l=c/12,e=12*a;return t*(l*Math.pow(1+l,e))/(Math.pow(1+l,e)-1)}function calculateCOCR30(t,c){try{const a=.3*t,l=12*calculatePMT(.7*t,.075,30);return(c-l)/a*100}catch(t){return 0}}function calculateCashFlowYield(t,c){if(!c||c<=0)return 0;return 12*t/c*100}export{calculateCOCR30,calculateCashFlowYield,calculatePMT};
2
2
  //# sourceMappingURL=calculations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"calculations.js","sources":["../../src/financial/calculations.js"],"sourcesContent":["// src/financial/calculations.js\r\n\r\n/**\r\n * PMT function for loan payment calculation\r\n * @param {number} principal - Loan principal amount \r\n * @param {number} annualRate - Annual interest rate (as decimal, e.g., 0.075 for 7.5%)\r\n * @param {number} years - Loan term in years\r\n * @returns {number} Monthly payment amount\r\n */\r\nexport function calculatePMT(principal, annualRate, years) {\r\n if (annualRate === 0) {\r\n return principal / (years * 12);\r\n }\r\n \r\n const monthlyRate = annualRate / 12;\r\n const numPayments = years * 12;\r\n const pmt = principal * (monthlyRate * Math.pow(1 + monthlyRate, numPayments)) / \r\n (Math.pow(1 + monthlyRate, numPayments) - 1);\r\n return pmt;\r\n}"],"names":["calculatePMT","principal","annualRate","years","monthlyRate","numPayments","Math","pow"],"mappings":"AASO,SAASA,aAAaC,EAAWC,EAAYC,GAClD,GAAmB,IAAfD,EACF,OAAOD,GAAqB,GAARE,GAGtB,MAAMC,EAAcF,EAAa,GAC3BG,EAAsB,GAARF,EAGpB,OAFYF,GAAaG,EAAcE,KAAKC,IAAI,EAAIH,EAAaC,KACpDC,KAAKC,IAAI,EAAIH,EAAaC,GAAe,EAExD"}
1
+ {"version":3,"file":"calculations.js","sources":["../../src/financial/calculations.js"],"sourcesContent":["// src/financial/calculations.js\n\n/**\n * PMT function for loan payment calculation\n * @param {number} principal - Loan principal amount \n * @param {number} annualRate - Annual interest rate (as decimal, e.g., 0.075 for 7.5%)\n * @param {number} years - Loan term in years\n * @returns {number} Monthly payment amount\n */\nexport function calculatePMT(principal, annualRate, years) {\n if (annualRate === 0) {\n return principal / (years * 12);\n }\n \n const monthlyRate = annualRate / 12;\n const numPayments = years * 12;\n const pmt = principal * (monthlyRate * Math.pow(1 + monthlyRate, numPayments)) / \n (Math.pow(1 + monthlyRate, numPayments) - 1);\n return pmt;\n}\n\nexport function calculateCOCR30(askingPrice, noi) {\n try {\n const cashInvested = askingPrice * 0.30; // 30% down payment\n const dscrLoanAmount = askingPrice * 0.70; // Fixed 70% DSCR loan\n const dscrPayment = calculatePMT(dscrLoanAmount, 0.075, 30) * 12; // Annual DSCR payment\n const annualCashFlow = noi - dscrPayment;\n const cocr = (annualCashFlow / cashInvested) * 100;\n \n return cocr;\n } catch (error) {\n return 0;\n }\n}\n\nexport function calculateCashFlowYield(monthlyCashFlow, purchasePrice) {\n if (!purchasePrice || purchasePrice <= 0) return 0;\n const annualCashFlow = monthlyCashFlow * 12;\n return (annualCashFlow / purchasePrice) * 100;\n}\n\n"],"names":["calculatePMT","principal","annualRate","years","monthlyRate","numPayments","Math","pow","calculateCOCR30","askingPrice","noi","cashInvested","dscrPayment","error","calculateCashFlowYield","monthlyCashFlow","purchasePrice"],"mappings":"AASO,SAASA,aAAaC,EAAWC,EAAYC,GAClD,GAAmB,IAAfD,EACF,OAAOD,GAAqB,GAARE,GAGtB,MAAMC,EAAcF,EAAa,GAC3BG,EAAsB,GAARF,EAGpB,OAFYF,GAAaG,EAAcE,KAAKC,IAAI,EAAIH,EAAaC,KACpDC,KAAKC,IAAI,EAAIH,EAAaC,GAAe,EAExD,CAEO,SAASG,gBAAgBC,EAAaC,GAC3C,IACE,MAAMC,EAA6B,GAAdF,EAEfG,EAAwD,GAA1CZ,aADiB,GAAdS,EAC0B,KAAO,IAIxD,OAHuBC,EAAME,GACED,EAAgB,GAGjD,CAAE,MAAOE,GACP,OAAO,CACT,CACF,CAEO,SAASC,uBAAuBC,EAAiBC,GACtD,IAAKA,GAAiBA,GAAiB,EAAG,OAAO,EAEjD,OADyC,GAAlBD,EACEC,EAAiB,GAC5C"}
@@ -0,0 +1,2 @@
1
+ function formatCurrency(e,t=!1){if(isNaN(e)||!isFinite(e))return"N/A";const i=Math.abs(e),r=e<0?"-$":"$";if(t)return r+i.toLocaleString("en-US",{maximumFractionDigits:0});if(i>=1e6){return r+(i/1e6).toFixed(3).replace(/\.?0+$/,"")+"M"}if(i>=1e3){return r+(i/1e3).toFixed(3).replace(/\.?0+$/,"")+"K"}return r+i.toLocaleString("en-US",{maximumFractionDigits:0})}function formatPriceValue(e){if(isNaN(e)||!isFinite(e))return"N/A";const t=Math.abs(e),i=e<0?"-$":"$";return t>=1e6?i+(t/1e6).toFixed(1)+"M":t>=1e3?i+(t/1e3).toFixed(0)+"K":i+t.toLocaleString("en-US",{maximumFractionDigits:0})}function formatPercentage(e){return isNaN(e)||!isFinite(e)?"N/A":e.toFixed(1)+"%"}export{formatCurrency,formatPercentage,formatPriceValue};
2
+ //# sourceMappingURL=formatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.js","sources":["../../src/financial/formatters.js"],"sourcesContent":["export function formatCurrency(amount, isMonthly = false) {\n if (isNaN(amount) || !isFinite(amount)) return \"N/A\";\n \n const absAmount = Math.abs(amount);\n const isNegative = amount < 0;\n const prefix = isNegative ? \"-$\" : \"$\";\n \n if (isMonthly) {\n // For monthly payments, show full amount with commas\n return prefix + absAmount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n } else {\n // For larger amounts, use K/M notation with 2-3 decimal places as needed\n if (absAmount >= 1000000) {\n const millions = absAmount / 1000000;\n // Use 2-3 decimal places but remove unnecessary trailing zeros\n const formatted = millions.toFixed(3).replace(/\\.?0+$/, \"\");\n return prefix + formatted + \"M\";\n } else if (absAmount >= 1000) {\n const thousands = absAmount / 1000;\n // Use 2-3 decimal places but remove unnecessary trailing zeros \n const formatted = thousands.toFixed(3).replace(/\\.?0+$/, \"\");\n return prefix + formatted + \"K\";\n } else {\n return prefix + absAmount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n }\n }\n}\n\nexport function formatPriceValue(amount) {\n if (isNaN(amount) || !isFinite(amount)) return \"N/A\";\n \n const absAmount = Math.abs(amount);\n const isNegative = amount < 0;\n const prefix = isNegative ? \"-$\" : \"$\";\n \n if (absAmount >= 1000000) {\n return prefix + (absAmount / 1000000).toFixed(1) + \"M\";\n } else if (absAmount >= 1000) {\n return prefix + (absAmount / 1000).toFixed(0) + \"K\";\n } else {\n return prefix + absAmount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n }\n}\n\nexport function formatPercentage(percentage) {\n if (isNaN(percentage) || !isFinite(percentage)) return \"N/A\";\n return percentage.toFixed(1) + \"%\";\n}\n\n"],"names":["formatCurrency","amount","isMonthly","isNaN","isFinite","absAmount","Math","abs","prefix","toLocaleString","maximumFractionDigits","toFixed","replace","formatPriceValue","formatPercentage","percentage"],"mappings":"AAAO,SAASA,eAAeC,EAAQC,GAAY,GACjD,GAAIC,MAAMF,KAAYG,SAASH,GAAS,MAAO,MAE/C,MAAMI,EAAYC,KAAKC,IAAIN,GAErBO,EADaP,EAAS,EACA,KAAO,IAEnC,GAAIC,EAEF,OAAOM,EAASH,EAAUI,eAAe,QAAS,CAAEC,sBAAuB,IAG3E,GAAIL,GAAa,IAAS,CAIxB,OAAOG,GAHUH,EAAY,KAEFM,QAAQ,GAAGC,QAAQ,SAAU,IAC5B,GAC9B,CAAO,GAAIP,GAAa,IAAM,CAI5B,OAAOG,GAHWH,EAAY,KAEFM,QAAQ,GAAGC,QAAQ,SAAU,IAC7B,GAC9B,CACE,OAAOJ,EAASH,EAAUI,eAAe,QAAS,CAAEC,sBAAuB,GAGjF,CAEO,SAASG,iBAAiBZ,GAC/B,GAAIE,MAAMF,KAAYG,SAASH,GAAS,MAAO,MAE/C,MAAMI,EAAYC,KAAKC,IAAIN,GAErBO,EADaP,EAAS,EACA,KAAO,IAEnC,OAAII,GAAa,IACRG,GAAUH,EAAY,KAASM,QAAQ,GAAK,IAC1CN,GAAa,IACfG,GAAUH,EAAY,KAAMM,QAAQ,GAAK,IAEzCH,EAASH,EAAUI,eAAe,QAAS,CAAEC,sBAAuB,GAE/E,CAEO,SAASI,iBAAiBC,GAC/B,OAAIZ,MAAMY,KAAgBX,SAASW,GAAoB,MAChDA,EAAWJ,QAAQ,GAAK,GACjC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export{calculatePMT}from"./financial/calculations.js";
1
+ export{calculateCOCR30,calculateCashFlowYield,calculatePMT}from"./financial/calculations.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{extractBedrooms,extractPhoneNumber}from"./data/extractors.js";export{calculateDOM}from"./date/utilities.js";
2
2
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archerjessop/utilities",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Shared utilities for ArcherJessop property analysis tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,7 +24,10 @@
24
24
  "clean": "rm -rf dist",
25
25
  "prepublishOnly": "npm run clean && npm run build && npm run test",
26
26
  "lint": "eslint src tests",
27
- "lint:fix": "eslint src tests --fix"
27
+ "lint:fix": "eslint src tests --fix",
28
+ "release:patch": "npm version patch && git push && git push --tags && npm publish --access public",
29
+ "release:minor": "npm version minor && git push && git push --tags && npm publish --access public",
30
+ "release:major": "npm version major && git push && git push --tags && npm publish --access public"
28
31
  },
29
32
  "repository": {
30
33
  "type": "git",