@archerjessop/utilities 7.18.0 → 7.20.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/dist/browser/data/extractors.js +1 -1
- package/dist/browser/data/extractors.js.map +1 -1
- package/dist/financial/equity-carry.js +2 -0
- package/dist/financial/equity-carry.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/debt.js +1 -1
- package/dist/services/debt.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function extractPhoneNumber(){const t
|
|
1
|
+
function extractPhoneNumber(){const t=/(\+?1?\s*\(?[0-9]{3}\)?[\s.-]*[0-9]{3}[\s.-]*[0-9]{4})/,e=document.querySelector("a[href^='tel:']");if(e&&e.href){const o=decodeURIComponent(e.href.replace(/^tel:/,"")).trim();if(t.test(o))return o}for(const e of[".phone-number",".number","[class*='phone']"]){const o=document.querySelector(e);if(!o)continue;const r=(o.textContent||"").match(t);if(r)return r[1].trim();if(o.href){const e=decodeURIComponent(o.href.replace(/^tel:/,"")).trim();if(t.test(e))return e}}const o=(document.body&&document.body.textContent||"").match(t);return o?o[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
2
|
//# sourceMappingURL=extractors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractors.js","sources":["../../../src/browser/data/extractors.js"],"sourcesContent":["export function extractPhoneNumber() {\r\n const
|
|
1
|
+
{"version":3,"file":"extractors.js","sources":["../../../src/browser/data/extractors.js"],"sourcesContent":["export function extractPhoneNumber() {\r\n const PHONE_RE = /(\\+?1?\\s*\\(?[0-9]{3}\\)?[\\s.-]*[0-9]{3}[\\s.-]*[0-9]{4})/;\r\n\r\n // A tel: link is unambiguous — prefer it. Its href holds the real number even when the\r\n // visible text is a label (\"Call\") or, on LoopNet, a lead-form validation message.\r\n const telLink = document.querySelector(\"a[href^='tel:']\");\r\n if (telLink && telLink.href) {\r\n const num = decodeURIComponent(telLink.href.replace(/^tel:/, \"\")).trim();\r\n if (PHONE_RE.test(num)) return num;\r\n }\r\n\r\n // Other phone-ish elements, but accept their TEXT only if it actually looks like a phone —\r\n // guards against lead-capture form fields (class*=\"phone\") whose text is a label/validation\r\n // message (\"Phone* Valid phone number is required\"), not a number.\r\n for (const sel of [\".phone-number\", \".number\", \"[class*='phone']\"]) {\r\n const el = document.querySelector(sel);\r\n if (!el) continue;\r\n const m = (el.textContent || \"\").match(PHONE_RE);\r\n if (m) return m[1].trim();\r\n if (el.href) {\r\n const num = decodeURIComponent(el.href.replace(/^tel:/, \"\")).trim();\r\n if (PHONE_RE.test(num)) return num;\r\n }\r\n }\r\n\r\n // Fallback to a body-text scan.\r\n const pageText = document.body ? document.body.textContent || \"\" : \"\";\r\n const phoneMatch = pageText.match(PHONE_RE);\r\n if (phoneMatch) {\r\n return phoneMatch[1].trim();\r\n }\r\n\r\n return \"Not found\";\r\n}\r\n\r\nexport function extractBedrooms() {\r\n try {\r\n // Look for bedroom information in various places\r\n const bodyText = document.body?.textContent || \"\";\r\n\r\n // Common patterns for bedroom information\r\n const bedroomPatterns = [\r\n /(\\d+)\\s*bed/i,\r\n /(\\d+)\\s*bedroom/i,\r\n /beds?\\s*:\\s*(\\d+)/i,\r\n /bedrooms?\\s*:\\s*(\\d+)/i,\r\n /(\\d+)\\s*BR/i,\r\n /(\\d+)br/i\r\n ];\r\n\r\n for (const pattern of bedroomPatterns) {\r\n const match = bodyText.match(pattern);\r\n if (match) {\r\n const bedrooms = parseInt(match[1]);\r\n if (bedrooms > 0 && bedrooms < 100) { // Sanity check\r\n return bedrooms;\r\n }\r\n }\r\n }\r\n\r\n // Look in property details section specifically\r\n const propertyDetails = document.querySelector(\".property-details\") ||\r\n document.querySelector(\"#PropertyDetails\") ||\r\n document.querySelector(\".details\");\r\n\r\n if (propertyDetails) {\r\n const detailsText = propertyDetails.textContent || \"\";\r\n for (const pattern of bedroomPatterns) {\r\n const match = detailsText.match(pattern);\r\n if (match) {\r\n const bedrooms = parseInt(match[1]);\r\n if (bedrooms > 0 && bedrooms < 100) {\r\n return bedrooms;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Default fallback\r\n return 10; // Default assumption for assisted living\r\n } catch (error) {\r\n return 10; // Default fallback\r\n }\r\n}\r\n"],"names":["extractPhoneNumber","PHONE_RE","telLink","document","querySelector","href","num","decodeURIComponent","replace","trim","test","sel","el","m","textContent","match","phoneMatch","body","extractBedrooms","bodyText","bedroomPatterns","pattern","bedrooms","parseInt","propertyDetails","detailsText","error"],"mappings":"AAAO,SAASA,qBACd,MAAMC,EAAW,yDAIXC,EAAUC,SAASC,cAAc,mBACvC,GAAIF,GAAWA,EAAQG,KAAM,CAC3B,MAAMC,EAAMC,mBAAmBL,EAAQG,KAAKG,QAAQ,QAAS,KAAKC,OAClE,GAAIR,EAASS,KAAKJ,GAAM,OAAOA,CACjC,CAKA,IAAK,MAAMK,IAAO,CAAC,gBAAiB,UAAW,oBAAqB,CAClE,MAAMC,EAAKT,SAASC,cAAcO,GAClC,IAAKC,EAAI,SACT,MAAMC,GAAKD,EAAGE,aAAe,IAAIC,MAAMd,GACvC,GAAIY,EAAG,OAAOA,EAAE,GAAGJ,OACnB,GAAIG,EAAGP,KAAM,CACX,MAAMC,EAAMC,mBAAmBK,EAAGP,KAAKG,QAAQ,QAAS,KAAKC,OAC7D,GAAIR,EAASS,KAAKJ,GAAM,OAAOA,CACjC,CACF,CAGA,MACMU,GADWb,SAASc,MAAOd,SAASc,KAAKH,aAAoB,IACvCC,MAAMd,GAClC,OAAIe,EACKA,EAAW,GAAGP,OAGhB,WACT,CAEO,SAASS,kBACd,IAEE,MAAMC,EAAWhB,SAASc,MAAMH,aAAe,GAGzCM,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,EAAkBrB,SAASC,cAAc,sBACxBD,SAASC,cAAc,qBACvBD,SAASC,cAAc,YAE9C,GAAIoB,EAAiB,CACnB,MAAMC,EAAcD,EAAgBV,aAAe,GACnD,IAAK,MAAMO,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
|
+
import{INTEREST_RATE_TIERS as e,DEFAULT_CAP_RATE as r,determineInterestRateType as n,SELLER_FI_AMORTIZATION as t,SELLER_FI_INTEREST_RATE as c}from"../config/financial.js";import{resolveListingFinancials as o,calculateAssignmentFee as i,calculatePMT as l}from"./calculations.js";const s=[{downPercent:60,dscrPercent:70,sellerPercent:40},{downPercent:50,dscrPercent:60,sellerPercent:50},{downPercent:40,dscrPercent:50,sellerPercent:60},{downPercent:30,dscrPercent:40,sellerPercent:70},{downPercent:20,dscrPercent:30,sellerPercent:80}],u=s[s.length-1];function tierAnnualDebtService(e,r,n,o){return 12*l(r*(e.dscrPercent/100),n,o)+12*l(r*(e.sellerPercent/100),c,t)}function sweepTiers(e,r,n,t,c){for(const o of s){const i=e-tierAnnualDebtService(o,r,n,t),l=o.downPercent/100*r;if(i>0&&l>=c)return{cashFlow:i,tier:o}}return null}function calculateEquityCarryScore({bedroomCount:t=null,capRate:c,price:l,propertyType:s="mfr",units:a=null}={}){const d=Number(l);if(!Number.isFinite(d)||d<=0)return null;const p=null==c||""===c?NaN:Number(c),m=Number.isFinite(p)?p/100:null,P=Number(a),f=n(s,Number.isFinite(P)?P:void 0),{amortization:w,rate:b}=e[f],{noi:y}=o({bedroomCount:t,estimatedCapRate:r,price:d,propertyType:s,reportedCapRate:m});let N,_,h=sweepTiers(y,d,b,w,0);if(h)N="prospect",_=d;else{const e=.85*d;h=sweepTiers(y,e,b,w,0),h?(N="discount",_=e):(N="dead",_=d)}const C=y-tierAnnualDebtService(u,_,b,w),F=_>0?C/_:0;let S;return S="dead"===N?"none":F>.08?"high":F>=.04?"medium":"low",{assignment:i(_),cap_source:null===m?"estimate":"reported",cash_flow:h?h.cashFlow:null,deal_pool:N,downpayment_percent:h?h.tier.downPercent:-1,equity_tier:h?{...h.tier}:null,offer_price:_,raw_yield:F,yield_band:S}}export{s as EQUITY_CARRY_TIERS,calculateEquityCarryScore};
|
|
2
|
+
//# sourceMappingURL=equity-carry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equity-carry.js","sources":["../../src/financial/equity-carry.js"],"sourcesContent":["// src/financial/equity-carry.js\n\nimport { DEFAULT_CAP_RATE, INTEREST_RATE_TIERS, SELLER_FI_AMORTIZATION, SELLER_FI_INTEREST_RATE, determineInterestRateType } from \"../config/financial.js\";\nimport { calculateAssignmentFee, calculatePMT, resolveListingFinancials } from \"./calculations.js\";\n\n/**\n * Equity Carry financing tiers (constant 110% leverage: DSCR % + seller carry % = 110).\n * Ordered highest down-payment first so a sweep stops at the strongest qualifying tier:\n * cash flow falls as the down payment rises (more of the stack moves to the market-rate\n * DSCR leg and off the 0% seller note), so the first tier that clears in a 60->20 sweep is\n * the highest down payment that still cash-flows.\n */\nexport const EQUITY_CARRY_TIERS = [\n { downPercent: 60, dscrPercent: 70, sellerPercent: 40 },\n { downPercent: 50, dscrPercent: 60, sellerPercent: 50 },\n { downPercent: 40, dscrPercent: 50, sellerPercent: 60 },\n { downPercent: 30, dscrPercent: 40, sellerPercent: 70 },\n { downPercent: 20, dscrPercent: 30, sellerPercent: 80 },\n];\n\nconst DISCOUNT_BASIS = 0.85;\nconst FIXED_YIELD_TIER = EQUITY_CARRY_TIERS[EQUITY_CARRY_TIERS.length - 1]; // 20% down\nconst YIELD_BAND_HIGH = 0.08;\nconst YIELD_BAND_MEDIUM = 0.04;\n\n/**\n * Annual debt service for one tier at a given price basis, from the suite primitives.\n * DSCR leg amortizes at the property's market rate/term; the seller carry leg is 0% / 30yr.\n * @param {{dscrPercent:number, sellerPercent:number}} tier\n * @param {number} basis - Price the financing is sized against (asking or discounted)\n * @param {number} dscrRate - DSCR annual interest rate (decimal)\n * @param {number} dscrAmortization - DSCR amortization (years)\n * @returns {number} Combined annual debt service (DSCR + seller carry)\n */\nfunction tierAnnualDebtService(tier, basis, dscrRate, dscrAmortization) {\n const dscrAnnual = calculatePMT(basis * (tier.dscrPercent / 100), dscrRate, dscrAmortization) * 12;\n const sellerAnnual = calculatePMT(basis * (tier.sellerPercent / 100), SELLER_FI_INTEREST_RATE, SELLER_FI_AMORTIZATION) * 12;\n return dscrAnnual + sellerAnnual;\n}\n\n/**\n * Sweep the tiers (60 -> 20) at one price basis; return the first (highest down payment)\n * tier whose annual cash flow is positive AND whose down payment covers any known debt.\n * @returns {{tier:object, cashFlow:number}|null} Winning tier + its annual cash flow, or null\n */\nfunction sweepTiers(noi, basis, dscrRate, dscrAmortization, estimatedDebt) {\n for (const tier of EQUITY_CARRY_TIERS) {\n const cashFlow = noi - tierAnnualDebtService(tier, basis, dscrRate, dscrAmortization);\n const downPayment = (tier.downPercent / 100) * basis;\n if (cashFlow > 0 && downPayment >= estimatedDebt) {\n return { cashFlow, tier };\n }\n }\n return null;\n}\n\n/**\n * Score a scraped listing with the Equity Carry Method and route it to a deal pool.\n *\n * Pure, no IO. Accepts inputs in the raw shape Postgres returns: `price` and `capRate` may\n * be NUMERIC strings, and `capRate` is a PERCENT (e.g. \"6.5\", not 0.065) — both are coerced\n * here, so the trap where a clean-decimal fixture passes while production data fails silently\n * cannot occur. A null/non-finite reported cap falls back to DEFAULT_CAP_RATE for multifamily.\n *\n * At score time the property is assumed 100% equity (estimatedDebt = 0), so debt coverage\n * always passes and the routing is driven purely by cash flow; the real debt figure is tested\n * later by the debt drain, which demotes uncovered prospect/discount rows to `shadow`.\n *\n * Run A (routing): sweep 60->20 at the asking price; the first qualifying tier => prospect.\n * If none qualify, re-sweep at 85% of asking; the first qualifying tier => discount. If none\n * qualify in either pass => dead.\n *\n * Run B (comparable yield): independent of Run A, the cash flow at a fixed 20%-down stack on\n * the chosen offer price, divided by that offer price. Banded high/medium/low; dead rows are\n * forced to band `none` (the raw yield is still returned).\n *\n * @param {Object} input\n * @param {number|null} [input.bedroomCount] - Bedroom count (assisted living NOI only)\n * @param {number|string|null} input.capRate - Reported cap rate as a PERCENT; null => estimate\n * @param {number|string} input.price - Asking price (positive); null/0 returns null\n * @param {string} [input.propertyType] - DB property type (mfr/str/assisted/other)\n * @param {number|string|null} [input.units] - Unit count (selects residential vs commercial DSCR tier)\n * @returns {{assignment:number, cap_source:string, cash_flow:(number|null), deal_pool:string, downpayment_percent:number, equity_tier:(object|null), offer_price:number, raw_yield:number, yield_band:string}|null}\n * `cap_source` is \"reported\" when a usable cap was supplied, \"estimate\" when the row was\n * scored on DEFAULT_CAP_RATE because none was — the UI flags \"estimate\" rows as\n * needs-confirmation and offers a manual cap-rate input that re-scores the row. Most\n * meaningful for cap-driven types (mfr/other); STR/assisted derive NOI from other models.\n * Null when price is not a positive number (caller should skip + log; the SQL gate excludes these).\n */\nexport function calculateEquityCarryScore({\n bedroomCount = null,\n capRate,\n price,\n propertyType = \"mfr\",\n units = null,\n} = {}) {\n const priceNum = Number(price);\n if (!Number.isFinite(priceNum) || priceNum <= 0) return null;\n\n const capPercent = capRate === null || capRate === undefined || capRate === \"\" ? NaN : Number(capRate);\n const reportedCapRate = Number.isFinite(capPercent) ? capPercent / 100 : null;\n\n const unitsNum = Number(units);\n const rateType = determineInterestRateType(propertyType, Number.isFinite(unitsNum) ? unitsNum : undefined);\n const { amortization: dscrAmortization, rate: dscrRate } = INTEREST_RATE_TIERS[rateType];\n\n const { noi } = resolveListingFinancials({\n bedroomCount,\n estimatedCapRate: DEFAULT_CAP_RATE,\n price: priceNum,\n propertyType,\n reportedCapRate,\n });\n\n const estimatedDebt = 0;\n\n let dealPool;\n let offerPrice;\n let winner = sweepTiers(noi, priceNum, dscrRate, dscrAmortization, estimatedDebt);\n if (winner) {\n dealPool = \"prospect\";\n offerPrice = priceNum;\n } else {\n const discountBasis = priceNum * DISCOUNT_BASIS;\n winner = sweepTiers(noi, discountBasis, dscrRate, dscrAmortization, estimatedDebt);\n if (winner) {\n dealPool = \"discount\";\n offerPrice = discountBasis;\n } else {\n dealPool = \"dead\";\n offerPrice = priceNum;\n }\n }\n\n const cashFlow20 = noi - tierAnnualDebtService(FIXED_YIELD_TIER, offerPrice, dscrRate, dscrAmortization);\n const rawYield = offerPrice > 0 ? cashFlow20 / offerPrice : 0;\n\n let yieldBand;\n if (dealPool === \"dead\") {\n yieldBand = \"none\";\n } else if (rawYield > YIELD_BAND_HIGH) {\n yieldBand = \"high\";\n } else if (rawYield >= YIELD_BAND_MEDIUM) {\n yieldBand = \"medium\";\n } else {\n yieldBand = \"low\";\n }\n\n return {\n assignment: calculateAssignmentFee(offerPrice),\n cap_source: reportedCapRate === null ? \"estimate\" : \"reported\",\n cash_flow: winner ? winner.cashFlow : null,\n deal_pool: dealPool,\n downpayment_percent: winner ? winner.tier.downPercent : -1,\n equity_tier: winner ? { ...winner.tier } : null,\n offer_price: offerPrice,\n raw_yield: rawYield,\n yield_band: yieldBand,\n };\n}\n"],"names":["EQUITY_CARRY_TIERS","downPercent","dscrPercent","sellerPercent","FIXED_YIELD_TIER","length","tierAnnualDebtService","tier","basis","dscrRate","dscrAmortization","calculatePMT","SELLER_FI_INTEREST_RATE","SELLER_FI_AMORTIZATION","sweepTiers","noi","estimatedDebt","cashFlow","downPayment","calculateEquityCarryScore","bedroomCount","capRate","price","propertyType","units","priceNum","Number","isFinite","capPercent","NaN","reportedCapRate","unitsNum","rateType","determineInterestRateType","undefined","amortization","rate","INTEREST_RATE_TIERS","resolveListingFinancials","estimatedCapRate","DEFAULT_CAP_RATE","dealPool","offerPrice","winner","discountBasis","cashFlow20","rawYield","yieldBand","assignment","calculateAssignmentFee","cap_source","cash_flow","deal_pool","downpayment_percent","equity_tier","offer_price","raw_yield","yield_band"],"mappings":"sRAYY,MAACA,EAAqB,CAChC,CAAEC,YAAa,GAAIC,YAAa,GAAIC,cAAe,IACnD,CAAEF,YAAa,GAAIC,YAAa,GAAIC,cAAe,IACnD,CAAEF,YAAa,GAAIC,YAAa,GAAIC,cAAe,IACnD,CAAEF,YAAa,GAAIC,YAAa,GAAIC,cAAe,IACnD,CAAEF,YAAa,GAAIC,YAAa,GAAIC,cAAe,KAI/CC,EAAmBJ,EAAmBA,EAAmBK,OAAS,GAaxE,SAASC,sBAAsBC,EAAMC,EAAOC,EAAUC,GAGpD,OAFgG,GAA7EC,EAAaH,GAASD,EAAKL,YAAc,KAAMO,EAAUC,GAC6C,GAApGC,EAAaH,GAASD,EAAKJ,cAAgB,KAAMS,EAAyBC,EAEjG,CAOA,SAASC,WAAWC,EAAKP,EAAOC,EAAUC,EAAkBM,GAC1D,IAAK,MAAMT,KAAQP,EAAoB,CACrC,MAAMiB,EAAWF,EAAMT,sBAAsBC,EAAMC,EAAOC,EAAUC,GAC9DQ,EAAeX,EAAKN,YAAc,IAAOO,EAC/C,GAAIS,EAAW,GAAKC,GAAeF,EACjC,MAAO,CAAEC,WAAUV,OAEvB,CACA,OAAO,IACT,CAmCO,SAASY,2BAA0BC,aACxCA,EAAe,KAAIC,QACnBA,EAAOC,MACPA,EAAKC,aACLA,EAAe,MAAKC,MACpBA,EAAQ,MACN,IACF,MAAMC,EAAWC,OAAOJ,GACxB,IAAKI,OAAOC,SAASF,IAAaA,GAAY,EAAG,OAAO,KAExD,MAAMG,EAAaP,SAAyD,KAAZA,EAAiBQ,IAAMH,OAAOL,GACxFS,EAAkBJ,OAAOC,SAASC,GAAcA,EAAa,IAAM,KAEnEG,EAAWL,OAAOF,GAClBQ,EAAWC,EAA0BV,EAAcG,OAAOC,SAASI,GAAYA,OAAWG,IACxFC,aAAczB,EAAkB0B,KAAM3B,GAAa4B,EAAoBL,IAEzEjB,IAAEA,GAAQuB,EAAyB,CACvClB,eACAmB,iBAAkBC,EAClBlB,MAAOG,EACPF,eACAO,oBAKF,IAAIW,EACAC,EACAC,EAAS7B,WAAWC,EAAKU,EAAUhB,EAAUC,EAJ3B,GAKtB,GAAIiC,EACFF,EAAW,WACXC,EAAajB,MACR,CACL,MAAMmB,EAvGa,IAuGGnB,EACtBkB,EAAS7B,WAAWC,EAAK6B,EAAenC,EAAUC,EAV9B,GAWhBiC,GACFF,EAAW,WACXC,EAAaE,IAEbH,EAAW,OACXC,EAAajB,EAEjB,CAEA,MAAMoB,EAAa9B,EAAMT,sBAAsBF,EAAkBsC,EAAYjC,EAAUC,GACjFoC,EAAWJ,EAAa,EAAIG,EAAaH,EAAa,EAE5D,IAAIK,EAWJ,OATEA,EADe,SAAbN,EACU,OACHK,EAtHW,IAuHR,OACHA,GAvHa,IAwHV,SAEA,MAGP,CACLE,WAAYC,EAAuBP,GACnCQ,WAAgC,OAApBpB,EAA2B,WAAa,WACpDqB,UAAWR,EAASA,EAAO1B,SAAW,KACtCmC,UAAWX,EACXY,oBAAqBV,EAASA,EAAOpC,KAAKN,aAAc,EACxDqD,YAAaX,EAAS,IAAKA,EAAOpC,MAAS,KAC3CgD,YAAab,EACbc,UAAWV,EACXW,WAAYV,EAEhB"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSTRNOI,equityPercentFromDebt,resolveListingFinancials,safePercentage}from"./financial/calculations.js";export{fetchDebt}from"./services/debt.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{calculateOriginalPrice,convertCapRateToDecimal,createExportObjectCore,formatDownPaymentPercent,mapPropertyType}from"./export/export-logic.js";export{calculateDOM,formatDate}from"./date/utilities.js";export{calculateCursorPosition,extractNumericValue,filterNumericInput,formatInputDisplay,formatLiveInput,formatLiveNumber,parseNumericInput}from"./formatting/financial-formatting.js";export{normalizeWhitespace}from"./formatting/text.js";export{CALCULATION_TOLERANCE,DEFAULT_CAP_RATE,DEFAULT_DOWN_PAYMENT,DEFAULT_DSCR_PERCENTAGE,DEFAULT_EQUITY_ESTIMATE,DEFAULT_INTEREST_RATE_TYPE,FINANCIAL_CONSTANTS,INTEREST_RATE_TIERS,MAX_ITERATIONS,SELLER_FI_AMORTIZATION,SELLER_FI_CARRY,SELLER_FI_DOWN_PAYMENT,SELLER_FI_INTEREST_RATE,determineInterestRateType}from"./config/financial.js";export{ASSISTED_LIVING,MULTIFAMILY,PROPERTY_TYPES,PROPERTY_TYPE_CONSTANTS,STR}from"./config/property-types.js";export{ASSIGNMENT_FEE_PERCENTAGE,BUSINESS_CONSTANTS,BUYER_AGENT_COMMISSION,CLOSING_COSTS_PERCENTAGE,CONSERVATIVE_COCR15_PRICE_MULTIPLIER,HARD_MONEY_RATE,MAX_COCR15_PRICE_MULTIPLIER,MINIMUM_COCR15_PRICE,NET_TO_BUYER_PERCENTAGE,REHAB_RATE,SELLER_AGENT_COMMISSION}from"./config/business.js";export{lookupLOI}from"./services/loi-lookup.js";export{LOI_LOOKUP_CONFIG,LOI_SENT_STATUS,MATCH_TYPES}from"./config/loi-lookup.js";export{getEnvVar,isBrowserEnvironment,isNodeEnvironment}from"./environment/utilities.js";const e="./dist/styles/base.css";export{e as STYLES_PATH};
|
|
1
|
+
export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSTRNOI,equityPercentFromDebt,resolveListingFinancials,safePercentage}from"./financial/calculations.js";export{EQUITY_CARRY_TIERS,calculateEquityCarryScore}from"./financial/equity-carry.js";export{fetchDebt}from"./services/debt.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{calculateOriginalPrice,convertCapRateToDecimal,createExportObjectCore,formatDownPaymentPercent,mapPropertyType}from"./export/export-logic.js";export{calculateDOM,formatDate}from"./date/utilities.js";export{calculateCursorPosition,extractNumericValue,filterNumericInput,formatInputDisplay,formatLiveInput,formatLiveNumber,parseNumericInput}from"./formatting/financial-formatting.js";export{normalizeWhitespace}from"./formatting/text.js";export{CALCULATION_TOLERANCE,DEFAULT_CAP_RATE,DEFAULT_DOWN_PAYMENT,DEFAULT_DSCR_PERCENTAGE,DEFAULT_EQUITY_ESTIMATE,DEFAULT_INTEREST_RATE_TYPE,FINANCIAL_CONSTANTS,INTEREST_RATE_TIERS,MAX_ITERATIONS,SELLER_FI_AMORTIZATION,SELLER_FI_CARRY,SELLER_FI_DOWN_PAYMENT,SELLER_FI_INTEREST_RATE,determineInterestRateType}from"./config/financial.js";export{ASSISTED_LIVING,MULTIFAMILY,PROPERTY_TYPES,PROPERTY_TYPE_CONSTANTS,STR}from"./config/property-types.js";export{ASSIGNMENT_FEE_PERCENTAGE,BUSINESS_CONSTANTS,BUYER_AGENT_COMMISSION,CLOSING_COSTS_PERCENTAGE,CONSERVATIVE_COCR15_PRICE_MULTIPLIER,HARD_MONEY_RATE,MAX_COCR15_PRICE_MULTIPLIER,MINIMUM_COCR15_PRICE,NET_TO_BUYER_PERCENTAGE,REHAB_RATE,SELLER_AGENT_COMMISSION}from"./config/business.js";export{lookupLOI}from"./services/loi-lookup.js";export{LOI_LOOKUP_CONFIG,LOI_SENT_STATUS,MATCH_TYPES}from"./config/loi-lookup.js";export{getEnvVar,isBrowserEnvironment,isNodeEnvironment}from"./environment/utilities.js";const e="./dist/styles/base.css";export{e as STYLES_PATH};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["/**\r\n * @archerjessop/utilities\r\n * Shared utilities for ArcherJessop property analysis tools\r\n */\r\n\r\n// Financial calculations\r\nexport { \r\n calculateAppreciatedValue,\r\n calculateAssignmentFee,\r\n calculateBalloonBalance,\r\n calculateCashFlow,\r\n calculateCashFlowYield,\r\n calculateCashOutAfterRefi,\r\n calculateCOCR30, \r\n calculateCOCRAtPercent,\r\n calculateDiscountFromPrice,\r\n calculateNetToBuyer,\r\n calculateNOIByType,\r\n calculatePMT,\r\n calculatePriceForCOCR,\r\n calculatePriceFromDiscount,\r\n calculateSTRNOI,\r\n equityPercentFromDebt,\r\n resolveListingFinancials,\r\n safePercentage,\r\n} from \"./financial/calculations.js\";\r\n\r\n// Agnostic debt service (pure IO; Node + browser)\r\nexport { fetchDebt } from \"./services/debt.js\";\r\n\r\n// Financial formatters\r\nexport { formatCurrency, formatPriceValue, formatPercentage } from \"./financial/formatters.js\";\r\n\r\n// Export logic (pure export-object creation)\r\nexport {\r\n calculateOriginalPrice,\r\n convertCapRateToDecimal,\r\n createExportObjectCore,\r\n formatDownPaymentPercent,\r\n mapPropertyType,\r\n} from \"./export/export-logic.js\";\r\n\r\n// Date utilities\r\nexport { calculateDOM, formatDate } from \"./date/utilities.js\";\r\n\r\n// Formatting utilities\r\nexport { \r\n calculateCursorPosition,\r\n extractNumericValue,\r\n filterNumericInput,\r\n formatInputDisplay,\r\n formatLiveInput,\r\n formatLiveNumber,\r\n parseNumericInput\r\n} from \"./formatting/financial-formatting.js\";\r\n\r\n// Text formatting utilities\r\nexport { normalizeWhitespace } from \"./formatting/text.js\";\r\n\r\n// Configuration constants\r\nexport * from \"./config/financial.js\";\r\nexport * from \"./config/property-types.js\";\r\nexport * from \"./config/business.js\";\r\n\r\nexport const STYLES_PATH = \"./dist/styles/base.css\";\r\n\r\n// LOI Lookup service and config\r\nexport { lookupLOI } from \"./services/loi-lookup.js\";\r\nexport { LOI_LOOKUP_CONFIG, MATCH_TYPES, LOI_SENT_STATUS } from \"./config/loi-lookup.js\";\r\n\r\n// Environment utilities\r\nexport { \r\n getEnvVar, \r\n isNodeEnvironment, \r\n isBrowserEnvironment \r\n} from \"./environment/utilities.js\";"],"names":["STYLES_PATH"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["/**\r\n * @archerjessop/utilities\r\n * Shared utilities for ArcherJessop property analysis tools\r\n */\r\n\r\n// Financial calculations\r\nexport { \r\n calculateAppreciatedValue,\r\n calculateAssignmentFee,\r\n calculateBalloonBalance,\r\n calculateCashFlow,\r\n calculateCashFlowYield,\r\n calculateCashOutAfterRefi,\r\n calculateCOCR30, \r\n calculateCOCRAtPercent,\r\n calculateDiscountFromPrice,\r\n calculateNetToBuyer,\r\n calculateNOIByType,\r\n calculatePMT,\r\n calculatePriceForCOCR,\r\n calculatePriceFromDiscount,\r\n calculateSTRNOI,\r\n equityPercentFromDebt,\r\n resolveListingFinancials,\r\n safePercentage,\r\n} from \"./financial/calculations.js\";\r\n\r\n// Equity Carry scoring engine (pure; scores scraped listings into deal pools)\r\nexport { EQUITY_CARRY_TIERS, calculateEquityCarryScore } from \"./financial/equity-carry.js\";\r\n\r\n// Agnostic debt service (pure IO; Node + browser)\r\nexport { fetchDebt } from \"./services/debt.js\";\r\n\r\n// Financial formatters\r\nexport { formatCurrency, formatPriceValue, formatPercentage } from \"./financial/formatters.js\";\r\n\r\n// Export logic (pure export-object creation)\r\nexport {\r\n calculateOriginalPrice,\r\n convertCapRateToDecimal,\r\n createExportObjectCore,\r\n formatDownPaymentPercent,\r\n mapPropertyType,\r\n} from \"./export/export-logic.js\";\r\n\r\n// Date utilities\r\nexport { calculateDOM, formatDate } from \"./date/utilities.js\";\r\n\r\n// Formatting utilities\r\nexport { \r\n calculateCursorPosition,\r\n extractNumericValue,\r\n filterNumericInput,\r\n formatInputDisplay,\r\n formatLiveInput,\r\n formatLiveNumber,\r\n parseNumericInput\r\n} from \"./formatting/financial-formatting.js\";\r\n\r\n// Text formatting utilities\r\nexport { normalizeWhitespace } from \"./formatting/text.js\";\r\n\r\n// Configuration constants\r\nexport * from \"./config/financial.js\";\r\nexport * from \"./config/property-types.js\";\r\nexport * from \"./config/business.js\";\r\n\r\nexport const STYLES_PATH = \"./dist/styles/base.css\";\r\n\r\n// LOI Lookup service and config\r\nexport { lookupLOI } from \"./services/loi-lookup.js\";\r\nexport { LOI_LOOKUP_CONFIG, MATCH_TYPES, LOI_SENT_STATUS } from \"./config/loi-lookup.js\";\r\n\r\n// Environment utilities\r\nexport { \r\n getEnvVar, \r\n isNodeEnvironment, \r\n isBrowserEnvironment \r\n} from \"./environment/utilities.js\";"],"names":["STYLES_PATH"],"mappings":"w/DAmEY,MAACA,EAAc"}
|
package/dist/services/debt.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
async function fetchDebt(e,{baseUrl:t="https://api.archerjessop.com"}={}){const a=await fetch(`${t}/debt?address=${encodeURIComponent(e)}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!a.ok)throw new Error(`HTTP error! status: ${a.status}`);const r=await a.json(),s="number"==typeof r.estimatedMortgageBalance&&Number.isFinite(r.estimatedMortgageBalance)?r.estimatedMortgageBalance:null;return{address:"string"==typeof r.address?r.address:e,currentMortgages:Array.isArray(r.currentMortgages)?r.currentMortgages:[],estimatedMortgageBalance:s,source:null===s?"estimated":"api"}}export{fetchDebt};
|
|
1
|
+
async function fetchDebt(e,{baseUrl:t="https://api.archerjessop.com"}={}){const a=await fetch(`${t}/debt?address=${encodeURIComponent(e)}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(404===a.status)return{address:e,currentMortgages:[],estimatedMortgageBalance:null,source:"estimated"};if(!a.ok)throw new Error(`HTTP error! status: ${a.status}`);const r=await a.json(),s="number"==typeof r.estimatedMortgageBalance&&Number.isFinite(r.estimatedMortgageBalance)?r.estimatedMortgageBalance:null;return{address:"string"==typeof r.address?r.address:e,currentMortgages:Array.isArray(r.currentMortgages)?r.currentMortgages:[],estimatedMortgageBalance:s,source:null===s?"estimated":"api"}}export{fetchDebt};
|
|
2
2
|
//# sourceMappingURL=debt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debt.js","sources":["../../src/services/debt.js"],"sourcesContent":["// Agnostic debt fetcher: receives an address, returns the property's outstanding debt.\n//\n// PURE IO — no per-repo state, no panel DOM, no caching. Works in both Node (the dashboard\n// add-by-address flow) and the browser (the analyzer engine). The caller owns caching, the\n// loading indicator, the equity computation, and the \"estimated = 100%\" fallback.\n//\n// Returns { address, estimatedMortgageBalance, currentMortgages, source }:\n// - estimatedMortgageBalance: number (debt owing) or null when the service has no figure\n// - currentMortgages: array of lien objects (amount, position, lenderName, loanType, ...)\n// - source: \"api\" when a numeric balance came back, \"estimated\" when it did not\n// Throws on a network / non-OK HTTP error so the caller can
|
|
1
|
+
{"version":3,"file":"debt.js","sources":["../../src/services/debt.js"],"sourcesContent":["// Agnostic debt fetcher: receives an address, returns the property's outstanding debt.\n//\n// PURE IO — no per-repo state, no panel DOM, no caching. Works in both Node (the dashboard\n// add-by-address flow) and the browser (the analyzer engine). The caller owns caching, the\n// loading indicator, the equity computation, and the \"estimated = 100%\" fallback.\n//\n// Returns { address, estimatedMortgageBalance, currentMortgages, source }:\n// - estimatedMortgageBalance: number (debt owing) or null when the service has no figure\n// - currentMortgages: array of lien objects (amount, position, lenderName, loanType, ...)\n// - source: \"api\" when a numeric balance came back, \"estimated\" when it did not\n// A 404 means the service definitively has no debt record for the address — returned as the\n// estimated case (not thrown). Throws on a genuine network / non-OK HTTP error (e.g. 500) so\n// the caller can log it and still fall back to the estimated case.\nexport async function fetchDebt(address, { baseUrl = \"https://api.archerjessop.com\" } = {}) {\n const response = await fetch(\n `${baseUrl}/debt?address=${encodeURIComponent(address)}`,\n { method: \"GET\", headers: { \"Content-Type\": \"application/json\" } }\n );\n\n if (response.status === 404) {\n return {\n address,\n currentMortgages: [],\n estimatedMortgageBalance: null,\n source: \"estimated\",\n };\n }\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const data = await response.json();\n\n const balance = typeof data.estimatedMortgageBalance === \"number\" && Number.isFinite(data.estimatedMortgageBalance)\n ? data.estimatedMortgageBalance\n : null;\n\n return {\n address: typeof data.address === \"string\" ? data.address : address,\n currentMortgages: Array.isArray(data.currentMortgages) ? data.currentMortgages : [],\n estimatedMortgageBalance: balance,\n source: balance === null ? \"estimated\" : \"api\",\n };\n}\n"],"names":["async","fetchDebt","address","baseUrl","response","fetch","encodeURIComponent","method","headers","status","currentMortgages","estimatedMortgageBalance","source","ok","Error","data","json","balance","Number","isFinite","Array","isArray"],"mappings":"AAaOA,eAAeC,UAAUC,GAASC,QAAEA,EAAU,gCAAmC,CAAA,GACtF,MAAMC,QAAiBC,MACrB,GAAGF,kBAAwBG,mBAAmBJ,KAC9C,CAAEK,OAAQ,MAAOC,QAAS,CAAE,eAAgB,sBAG9C,GAAwB,MAApBJ,EAASK,OACX,MAAO,CACLP,UACAQ,iBAAkB,GAClBC,yBAA0B,KAC1BC,OAAQ,aAIZ,IAAKR,EAASS,GACZ,MAAM,IAAIC,MAAM,uBAAuBV,EAASK,UAGlD,MAAMM,QAAaX,EAASY,OAEtBC,EAAmD,iBAAlCF,EAAKJ,0BAAyCO,OAAOC,SAASJ,EAAKJ,0BACtFI,EAAKJ,yBACL,KAEJ,MAAO,CACLT,QAAiC,iBAAjBa,EAAKb,QAAuBa,EAAKb,QAAUA,EAC3DQ,iBAAkBU,MAAMC,QAAQN,EAAKL,kBAAoBK,EAAKL,iBAAmB,GACjFC,yBAA0BM,EAC1BL,OAAoB,OAAZK,EAAmB,YAAc,MAE7C"}
|