@archerjessop/utilities 7.28.0 → 7.29.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.
@@ -1,2 +1,2 @@
1
- import{getEnvVar as E}from"../environment/utilities.js";const _={ASSIGNMENT_FEE_PERCENTAGE:.05,CASH_OFFER_ASSIGNMENT_PERCENTAGE:.07,NET_TO_BUYER_PERCENTAGE:.1,CLOSING_COSTS_PERCENTAGE:.0125,HARD_MONEY_RATE:.03,REHAB_RATE:0,SELLER_AGENT_COMMISSION:.025,BUYER_AGENT_COMMISSION:.025,MINIMUM_COCR15_PRICE:1e4,MAX_COCR15_PRICE_MULTIPLIER:50,CONSERVATIVE_COCR15_PRICE_MULTIPLIER:20,MAX_ITERATIONS:50,CALCULATION_TOLERANCE:.001,ADJUSTMENT_FACTOR:.5,EXPORT_URL_BASE:E("EXPORT_URL_BASE","https://app.archerjessop.com/property-dashboard/import"),EXCLUDED_EXPORT_VALUES:["Loading...","Not found","",null,void 0],DASHBOARD_URL_BASE:E("DASHBOARD_URL_BASE","https://app.archerjessop.com/property-dashboard/")},{ASSIGNMENT_FEE_PERCENTAGE:R,CASH_OFFER_ASSIGNMENT_PERCENTAGE:A,NET_TO_BUYER_PERCENTAGE:C,CLOSING_COSTS_PERCENTAGE:T,HARD_MONEY_RATE:N,SELLER_AGENT_COMMISSION:I,BUYER_AGENT_COMMISSION:S,MINIMUM_COCR15_PRICE:O,MAX_COCR15_PRICE_MULTIPLIER:M,CONSERVATIVE_COCR15_PRICE_MULTIPLIER:L,REHAB_RATE:P}=_;export{R as ASSIGNMENT_FEE_PERCENTAGE,_ as BUSINESS_CONSTANTS,S as BUYER_AGENT_COMMISSION,A as CASH_OFFER_ASSIGNMENT_PERCENTAGE,T as CLOSING_COSTS_PERCENTAGE,L as CONSERVATIVE_COCR15_PRICE_MULTIPLIER,N as HARD_MONEY_RATE,M as MAX_COCR15_PRICE_MULTIPLIER,O as MINIMUM_COCR15_PRICE,C as NET_TO_BUYER_PERCENTAGE,P as REHAB_RATE,I as SELLER_AGENT_COMMISSION};
1
+ import{getEnvVar as E}from"../environment/utilities.js";const _={ASSIGNMENT_FEE_PERCENTAGE:.05,CASH_OFFER_ASSIGNMENT_PERCENTAGE:.07,CASH_OFFER_ROUNDING:1e4,NET_TO_BUYER_PERCENTAGE:.1,CLOSING_COSTS_PERCENTAGE:.0125,HARD_MONEY_RATE:.03,REHAB_RATE:0,SELLER_AGENT_COMMISSION:.025,BUYER_AGENT_COMMISSION:.025,MINIMUM_COCR15_PRICE:1e4,MAX_COCR15_PRICE_MULTIPLIER:50,CONSERVATIVE_COCR15_PRICE_MULTIPLIER:20,MAX_ITERATIONS:50,CALCULATION_TOLERANCE:.001,ADJUSTMENT_FACTOR:.5,EXPORT_URL_BASE:E("EXPORT_URL_BASE","https://app.archerjessop.com/property-dashboard/import"),EXCLUDED_EXPORT_VALUES:["Loading...","Not found","",null,void 0],DASHBOARD_URL_BASE:E("DASHBOARD_URL_BASE","https://app.archerjessop.com/property-dashboard/")},{ASSIGNMENT_FEE_PERCENTAGE:R,CASH_OFFER_ASSIGNMENT_PERCENTAGE:A,CASH_OFFER_ROUNDING:C,NET_TO_BUYER_PERCENTAGE:N,CLOSING_COSTS_PERCENTAGE:T,HARD_MONEY_RATE:I,SELLER_AGENT_COMMISSION:O,BUYER_AGENT_COMMISSION:S,MINIMUM_COCR15_PRICE:M,MAX_COCR15_PRICE_MULTIPLIER:L,CONSERVATIVE_COCR15_PRICE_MULTIPLIER:P,REHAB_RATE:G}=_;export{R as ASSIGNMENT_FEE_PERCENTAGE,_ as BUSINESS_CONSTANTS,S as BUYER_AGENT_COMMISSION,A as CASH_OFFER_ASSIGNMENT_PERCENTAGE,C as CASH_OFFER_ROUNDING,T as CLOSING_COSTS_PERCENTAGE,P as CONSERVATIVE_COCR15_PRICE_MULTIPLIER,I as HARD_MONEY_RATE,L as MAX_COCR15_PRICE_MULTIPLIER,M as MINIMUM_COCR15_PRICE,N as NET_TO_BUYER_PERCENTAGE,G as REHAB_RATE,O as SELLER_AGENT_COMMISSION};
2
2
  //# sourceMappingURL=business.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"business.js","sources":["../../src/config/business.js"],"sourcesContent":["/**\r\n * Business logic and fee constants\r\n */\r\n\r\nimport { getEnvVar } from \"../environment/utilities.js\";\r\n\r\nexport const BUSINESS_CONSTANTS = {\r\n // Assignment and transaction fees\r\n ASSIGNMENT_FEE_PERCENTAGE: 0.05, // 5% assignment fee\r\n CASH_OFFER_ASSIGNMENT_PERCENTAGE: 0.07, // 7% haircut off the 15%-COCR price for a cash offer (assignment + costs)\r\n NET_TO_BUYER_PERCENTAGE: 0.10, // 10% net to buyer\r\n CLOSING_COSTS_PERCENTAGE: 0.0125, // 1.25% closing costs\r\n HARD_MONEY_RATE: 0.03, // 3% financing fee\r\n REHAB_RATE: 0.0, // 0% rehab fee\r\n \r\n // Agent commissions\r\n SELLER_AGENT_COMMISSION: 0.025, // 3% seller agent commission\r\n BUYER_AGENT_COMMISSION: 0.025, // 3% buyer agent commission\r\n \r\n // COCR15 price calculation limits\r\n MINIMUM_COCR15_PRICE: 10000, // $10,000 minimum\r\n MAX_COCR15_PRICE_MULTIPLIER: 50, // Maximum price as multiple of NOI\r\n CONSERVATIVE_COCR15_PRICE_MULTIPLIER: 20, // Conservative price limit\r\n \r\n // Algorithm parameters (duplicated for business logic context)\r\n\r\n \r\n // Calculation parameters\r\n MAX_ITERATIONS: 50, // Maximum iterations for iterative calculations\r\n CALCULATION_TOLERANCE: 0.001, // Tolerance for convergence\r\n ADJUSTMENT_FACTOR: 0.5, // Adjustment factor for iterations\r\n\r\n\r\n \r\n // URL constants - safe for both Node.js and browser\r\n EXPORT_URL_BASE: getEnvVar(\r\n \"EXPORT_URL_BASE\",\r\n \"https://app.archerjessop.com/property-dashboard/import\"\r\n ),\r\n EXCLUDED_EXPORT_VALUES: [\"Loading...\", \"Not found\", \"\", null, undefined],\r\n DASHBOARD_URL_BASE: getEnvVar(\r\n \"DASHBOARD_URL_BASE\",\r\n \"https://app.archerjessop.com/property-dashboard/\"\r\n ),\r\n \r\n};\r\n\r\n// Convenience exports for commonly used values\r\nexport const {\r\n ASSIGNMENT_FEE_PERCENTAGE,\r\n CASH_OFFER_ASSIGNMENT_PERCENTAGE,\r\n NET_TO_BUYER_PERCENTAGE,\r\n CLOSING_COSTS_PERCENTAGE,\r\n HARD_MONEY_RATE,\r\n SELLER_AGENT_COMMISSION,\r\n BUYER_AGENT_COMMISSION,\r\n MINIMUM_COCR15_PRICE,\r\n MAX_COCR15_PRICE_MULTIPLIER,\r\n CONSERVATIVE_COCR15_PRICE_MULTIPLIER,\r\n REHAB_RATE,\r\n} = BUSINESS_CONSTANTS;\r\n"],"names":["BUSINESS_CONSTANTS","ASSIGNMENT_FEE_PERCENTAGE","CASH_OFFER_ASSIGNMENT_PERCENTAGE","NET_TO_BUYER_PERCENTAGE","CLOSING_COSTS_PERCENTAGE","HARD_MONEY_RATE","REHAB_RATE","SELLER_AGENT_COMMISSION","BUYER_AGENT_COMMISSION","MINIMUM_COCR15_PRICE","MAX_COCR15_PRICE_MULTIPLIER","CONSERVATIVE_COCR15_PRICE_MULTIPLIER","MAX_ITERATIONS","CALCULATION_TOLERANCE","ADJUSTMENT_FACTOR","EXPORT_URL_BASE","getEnvVar","EXCLUDED_EXPORT_VALUES","undefined","DASHBOARD_URL_BASE"],"mappings":"wDAMY,MAACA,EAAqB,CAEhCC,0BAA2B,IAC3BC,iCAAkC,IAClCC,wBAAyB,GACzBC,yBAA0B,MAC1BC,gBAAiB,IACjBC,WAAY,EAGZC,wBAAyB,KACzBC,uBAAwB,KAGxBC,qBAAsB,IACtBC,4BAA6B,GAC7BC,qCAAsC,GAMtCC,eAAgB,GAChBC,sBAAuB,KACvBC,kBAAmB,GAKnBC,gBAAiBC,EACf,kBACA,0DAEFC,uBAAwB,CAAC,aAAc,YAAa,GAAI,UAAMC,GAC9DC,mBAAoBH,EAClB,qBACA,sDAMSf,0BACXA,EAAyBC,iCACzBA,EAAgCC,wBAChCA,EAAuBC,yBACvBA,EAAwBC,gBACxBA,EAAeE,wBACfA,EAAuBC,uBACvBA,EAAsBC,qBACtBA,EAAoBC,4BACpBA,EAA2BC,qCAC3BA,EAAoCL,WACpCA,GACEN"}
1
+ {"version":3,"file":"business.js","sources":["../../src/config/business.js"],"sourcesContent":["/**\r\n * Business logic and fee constants\r\n */\r\n\r\nimport { getEnvVar } from \"../environment/utilities.js\";\r\n\r\nexport const BUSINESS_CONSTANTS = {\r\n // Assignment and transaction fees\r\n ASSIGNMENT_FEE_PERCENTAGE: 0.05, // 5% assignment fee\r\n CASH_OFFER_ASSIGNMENT_PERCENTAGE: 0.07, // 7% haircut off the 15%-COCR price for a cash offer (assignment + costs)\r\n CASH_OFFER_ROUNDING: 10000, // cash offer price floors DOWN to this step (stays at/below the 15%-COCR max threshold)\r\n NET_TO_BUYER_PERCENTAGE: 0.10, // 10% net to buyer\r\n CLOSING_COSTS_PERCENTAGE: 0.0125, // 1.25% closing costs\r\n HARD_MONEY_RATE: 0.03, // 3% financing fee\r\n REHAB_RATE: 0.0, // 0% rehab fee\r\n \r\n // Agent commissions\r\n SELLER_AGENT_COMMISSION: 0.025, // 3% seller agent commission\r\n BUYER_AGENT_COMMISSION: 0.025, // 3% buyer agent commission\r\n \r\n // COCR15 price calculation limits\r\n MINIMUM_COCR15_PRICE: 10000, // $10,000 minimum\r\n MAX_COCR15_PRICE_MULTIPLIER: 50, // Maximum price as multiple of NOI\r\n CONSERVATIVE_COCR15_PRICE_MULTIPLIER: 20, // Conservative price limit\r\n \r\n // Algorithm parameters (duplicated for business logic context)\r\n\r\n \r\n // Calculation parameters\r\n MAX_ITERATIONS: 50, // Maximum iterations for iterative calculations\r\n CALCULATION_TOLERANCE: 0.001, // Tolerance for convergence\r\n ADJUSTMENT_FACTOR: 0.5, // Adjustment factor for iterations\r\n\r\n\r\n \r\n // URL constants - safe for both Node.js and browser\r\n EXPORT_URL_BASE: getEnvVar(\r\n \"EXPORT_URL_BASE\",\r\n \"https://app.archerjessop.com/property-dashboard/import\"\r\n ),\r\n EXCLUDED_EXPORT_VALUES: [\"Loading...\", \"Not found\", \"\", null, undefined],\r\n DASHBOARD_URL_BASE: getEnvVar(\r\n \"DASHBOARD_URL_BASE\",\r\n \"https://app.archerjessop.com/property-dashboard/\"\r\n ),\r\n \r\n};\r\n\r\n// Convenience exports for commonly used values\r\nexport const {\r\n ASSIGNMENT_FEE_PERCENTAGE,\r\n CASH_OFFER_ASSIGNMENT_PERCENTAGE,\r\n CASH_OFFER_ROUNDING,\r\n NET_TO_BUYER_PERCENTAGE,\r\n CLOSING_COSTS_PERCENTAGE,\r\n HARD_MONEY_RATE,\r\n SELLER_AGENT_COMMISSION,\r\n BUYER_AGENT_COMMISSION,\r\n MINIMUM_COCR15_PRICE,\r\n MAX_COCR15_PRICE_MULTIPLIER,\r\n CONSERVATIVE_COCR15_PRICE_MULTIPLIER,\r\n REHAB_RATE,\r\n} = BUSINESS_CONSTANTS;\r\n"],"names":["BUSINESS_CONSTANTS","ASSIGNMENT_FEE_PERCENTAGE","CASH_OFFER_ASSIGNMENT_PERCENTAGE","CASH_OFFER_ROUNDING","NET_TO_BUYER_PERCENTAGE","CLOSING_COSTS_PERCENTAGE","HARD_MONEY_RATE","REHAB_RATE","SELLER_AGENT_COMMISSION","BUYER_AGENT_COMMISSION","MINIMUM_COCR15_PRICE","MAX_COCR15_PRICE_MULTIPLIER","CONSERVATIVE_COCR15_PRICE_MULTIPLIER","MAX_ITERATIONS","CALCULATION_TOLERANCE","ADJUSTMENT_FACTOR","EXPORT_URL_BASE","getEnvVar","EXCLUDED_EXPORT_VALUES","undefined","DASHBOARD_URL_BASE"],"mappings":"wDAMY,MAACA,EAAqB,CAEhCC,0BAA2B,IAC3BC,iCAAkC,IAClCC,oBAAqB,IACrBC,wBAAyB,GACzBC,yBAA0B,MAC1BC,gBAAiB,IACjBC,WAAY,EAGZC,wBAAyB,KACzBC,uBAAwB,KAGxBC,qBAAsB,IACtBC,4BAA6B,GAC7BC,qCAAsC,GAMtCC,eAAgB,GAChBC,sBAAuB,KACvBC,kBAAmB,GAKnBC,gBAAiBC,EACf,kBACA,0DAEFC,uBAAwB,CAAC,aAAc,YAAa,GAAI,UAAMC,GAC9DC,mBAAoBH,EAClB,qBACA,sDAMShB,0BACXA,EAAyBC,iCACzBA,EAAgCC,oBAChCA,EAAmBC,wBACnBA,EAAuBC,yBACvBA,EAAwBC,gBACxBA,EAAeE,wBACfA,EAAuBC,uBACvBA,EAAsBC,qBACtBA,EAAoBC,4BACpBA,EAA2BC,qCAC3BA,EAAoCL,WACpCA,GACEP"}
@@ -1,2 +1,2 @@
1
- import{FINANCIAL_CONSTANTS as e}from"../config/financial.js";import{BUSINESS_CONSTANTS as t}from"../config/business.js";import{PROPERTY_TYPES as r,PROPERTY_TYPE_CONSTANTS as c}from"../config/property-types.js";const a=e.INTEREST_RATE_TIERS[e.DEFAULT_INTEREST_RATE_TYPE];function calculatePMT(e,t,r){if(0===t)return e/(12*r);const c=t/12,a=12*r;return e*(c*Math.pow(1+c,a))/(Math.pow(1+c,a)-1)}function calculateCOCR30(e,t){try{const r=.3*e,c=12*calculatePMT(.7*e,.075,30);return(t-c)/r*100}catch(e){return 0}}function calculateCashFlowYield(e,t){if(!t||t<=0)return 0;return 12*e/t*100}function equityPercentFromDebt(e,t){const r=Number(e),c=Number(t);return!Number.isFinite(r)||r<=0?1:Number.isFinite(c)?(r-c)/r:1}function calculatePriceForCOCR(r,c=.15,n={}){const{downPercent:l=100*e.DEFAULT_DOWN_PAYMENT,dscrLtvPercent:u=100*e.DEFAULT_DSCR_PERCENTAGE,dscrRate:i=a.rate,dscrTerm:o=a.amortization,maxIterations:s=t.MAX_ITERATIONS,tolerance:E=t.CALCULATION_TOLERANCE}=n;try{let e=r/.08,a=0;for(;a<s;){const n=e*(l/100),s=12*calculatePMT(e*(u/100),i,o),T=(r-s)/n;if(Math.abs(T-c)<E)break;const R=T-c,A=R*t.ADJUSTMENT_FACTOR;e*=R>0?1+Math.abs(A):1-Math.abs(A),e<1e3&&(e=1e3),e>r*t.MAX_COCR15_PRICE_MULTIPLIER&&(e=r*t.CONSERVATIVE_COCR15_PRICE_MULTIPLIER),a++}return e<t.MINIMUM_COCR15_PRICE&&(e=t.MINIMUM_COCR15_PRICE),e}catch(e){return 0}}function calculateCOCRAtPercent(e,t,r,c={}){const{dscrRate:n=a.rate,dscrTerm:l=a.amortization}=c;try{const c=e*(r/100),a=12*calculatePMT(e-c,n,l);return(t-a)/c*100}catch(e){return 0}}function calculateSTRNOI(e,t=null,r={}){const{grossRate:a=c.STR.ESTIMATED_GROSS_RATE,noiPercentage:n=c.STR.NOI_PERCENTAGE}=r;try{if(t&&Number.isFinite(t.value)&&t.value>=0){if("noi"===t.type)return t.value;if("gross"===t.type)return t.value*n}return!Number.isFinite(e)||e<=0?0:e*a*n}catch(e){return 0}}function calculateNOIByType(e,t,a=r.MULTIFAMILY,n={}){const{strApiResult:l=null,strGrossIncomeMultiplier:u=c.STR.ESTIMATED_GROSS_RATE,strNoiPercentage:i=c.STR.NOI_PERCENTAGE,assistedIncomePerBedroom:o=c.ASSISTED_LIVING.INCOME_PER_BEDROOM_MONTHLY,bedroomCount:s=c.ASSISTED_LIVING.DEFAULT_BEDROOM_COUNT}=n;try{switch((a||r.MULTIFAMILY).toLowerCase()){case r.STR:return calculateSTRNOI(e,l,{grossRate:u,noiPercentage:i});case r.ASSISTED_LIVING:return s*o*12;case r.MULTIFAMILY:default:return e*t}}catch(e){return 0}}function resolveListingFinancials({bedroomCount:e=null,confirmedNOI:t=null,estimatedCapRate:c,price:a,propertyType:n=r.MULTIFAMILY,reportedCapRate:l=null,strApiResult:u=null}={}){const i=null!=l&&Number.isFinite(l),o=i?l:c,s=u&&Number.isFinite(u.value)&&u.value>=0&&("noi"===u.type||"gross"===u.type);let E,T;if(null!=t&&Number.isFinite(t)&&t>=0)E=t,T="confirmed";else{E=calculateNOIByType(a,o,n,{bedroomCount:e,strApiResult:u});const t=(n||"").toLowerCase();T=t===r.STR?s?"measured":"estimate":t===r.ASSISTED_LIVING?"bedrooms":i?"cap":"estimate"}Number.isFinite(E)||(E=0);return{activeCapRate:Number.isFinite(a)&&a>0?E/a:null,noi:E,noiSource:T,reportedCapRate:i?l:null}}function calculateAssignmentFee(e,r=100*t.ASSIGNMENT_FEE_PERCENTAGE){try{return e*(r/100)}catch(e){return 0}}function calculateCashOfferPrice(e,r=t.CASH_OFFER_ASSIGNMENT_PERCENTAGE){const c=Number(e);return!Number.isFinite(c)||c<=0?null:c*(1-r)}function calculateNetToBuyer(r,c={}){const{buyerCostPercent:a=100*t.NET_TO_BUYER_PERCENTAGE,sellerCostAssignment:n=100*t.ASSIGNMENT_FEE_PERCENTAGE,sellerCostClosing:l=100*t.CLOSING_COSTS_PERCENTAGE,additionalCostRehab:u=100*t.REHAB_RATE,additionalCostFinancing:i=100*t.HARD_MONEY_RATE,dscrLtvPercent:o=100*e.DEFAULT_DSCR_PERCENTAGE}=c;try{return r*(a/100)-r*((n+l)/100)-r*(u/100)-i/100*(r-r*(o/100))}catch(e){return 0}}function calculateBalloonBalance(t,r,c,a=e.DEFAULT_BALLOON_PERIOD_YEARS){try{if(t<=0||r<0||c<=0||a<=0)return 0;if(a>=c)return 0;if(0===r){const e=12*c;return t*(e-12*a)/e}const e=r/12,n=12*c,l=12*a,u=Math.pow(1+e,n),i=t*(u-Math.pow(1+e,l))/(u-1);return Math.max(0,i)}catch(e){return 0}}function calculateAppreciatedValue(t,r=e.APPRECIATION_RATE,c=e.DEFAULT_BALLOON_PERIOD_YEARS){try{return t<=0||r<0||c<0?t:t*Math.pow(1+r,c)}catch(e){return t}}function calculateCashOutAfterRefi(t,r,c,n={}){const{appreciationRate:l=e.APPRECIATION_RATE,balloonYears:u=e.DEFAULT_BALLOON_PERIOD_YEARS,dscrRate:i=a.rate,dscrTerm:o=a.amortization,sellerFiTerm:s=e.SELLER_FI_AMORTIZATION,refiLtvPercent:E=70}=n;try{const a=calculateAppreciatedValue(t,l,u),n=calculateBalloonBalance(r,i,o,u),T=calculateBalloonBalance(c,e.SELLER_FI_INTEREST_RATE,s,u);return a*(E/100)-(n+T)}catch(e){return 0}}function calculateCashFlow(e,t,r){return e-(t+r)}function calculateDiscountFromPrice(e,t){return!e||e<=0?0:(e-t)/e}function calculatePriceFromDiscount(e,t){return!e||e<=0?0:e*(1-t)}function safePercentage(e,t=100){return null==e||isNaN(e)?t:100*e}export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOfferPrice,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSTRNOI,equityPercentFromDebt,resolveListingFinancials,safePercentage};
1
+ import{FINANCIAL_CONSTANTS as e}from"../config/financial.js";import{BUSINESS_CONSTANTS as t}from"../config/business.js";import{PROPERTY_TYPES as r,PROPERTY_TYPE_CONSTANTS as a}from"../config/property-types.js";const c=e.INTEREST_RATE_TIERS[e.DEFAULT_INTEREST_RATE_TYPE];function calculatePMT(e,t,r){if(0===t)return e/(12*r);const a=t/12,c=12*r;return e*(a*Math.pow(1+a,c))/(Math.pow(1+a,c)-1)}function calculateCOCR30(e,t){try{const r=.3*e,a=12*calculatePMT(.7*e,.075,30);return(t-a)/r*100}catch(e){return 0}}function calculateCashFlowYield(e,t){if(!t||t<=0)return 0;return 12*e/t*100}function equityPercentFromDebt(e,t){const r=Number(e),a=Number(t);return!Number.isFinite(r)||r<=0?1:Number.isFinite(a)?(r-a)/r:1}function calculatePriceForCOCR(r,a=.15,n={}){const{downPercent:l=100*e.DEFAULT_DOWN_PAYMENT,dscrLtvPercent:u=100*e.DEFAULT_DSCR_PERCENTAGE,dscrRate:i=c.rate,dscrTerm:o=c.amortization,maxIterations:s=t.MAX_ITERATIONS,tolerance:E=t.CALCULATION_TOLERANCE}=n;try{let e=r/.08,c=0;for(;c<s;){const n=e*(l/100),s=12*calculatePMT(e*(u/100),i,o),T=(r-s)/n;if(Math.abs(T-a)<E)break;const R=T-a,A=R*t.ADJUSTMENT_FACTOR;e*=R>0?1+Math.abs(A):1-Math.abs(A),e<1e3&&(e=1e3),e>r*t.MAX_COCR15_PRICE_MULTIPLIER&&(e=r*t.CONSERVATIVE_COCR15_PRICE_MULTIPLIER),c++}return e<t.MINIMUM_COCR15_PRICE&&(e=t.MINIMUM_COCR15_PRICE),e}catch(e){return 0}}function calculateCOCRAtPercent(e,t,r,a={}){const{dscrRate:n=c.rate,dscrTerm:l=c.amortization}=a;try{const a=e*(r/100),c=12*calculatePMT(e-a,n,l);return(t-c)/a*100}catch(e){return 0}}function calculateSTRNOI(e,t=null,r={}){const{grossRate:c=a.STR.ESTIMATED_GROSS_RATE,noiPercentage:n=a.STR.NOI_PERCENTAGE}=r;try{if(t&&Number.isFinite(t.value)&&t.value>=0){if("noi"===t.type)return t.value;if("gross"===t.type)return t.value*n}return!Number.isFinite(e)||e<=0?0:e*c*n}catch(e){return 0}}function calculateNOIByType(e,t,c=r.MULTIFAMILY,n={}){const{strApiResult:l=null,strGrossIncomeMultiplier:u=a.STR.ESTIMATED_GROSS_RATE,strNoiPercentage:i=a.STR.NOI_PERCENTAGE,assistedIncomePerBedroom:o=a.ASSISTED_LIVING.INCOME_PER_BEDROOM_MONTHLY,bedroomCount:s=a.ASSISTED_LIVING.DEFAULT_BEDROOM_COUNT}=n;try{switch((c||r.MULTIFAMILY).toLowerCase()){case r.STR:return calculateSTRNOI(e,l,{grossRate:u,noiPercentage:i});case r.ASSISTED_LIVING:return s*o*12;case r.MULTIFAMILY:default:return e*t}}catch(e){return 0}}function resolveListingFinancials({bedroomCount:e=null,confirmedNOI:t=null,estimatedCapRate:a,price:c,propertyType:n=r.MULTIFAMILY,reportedCapRate:l=null,strApiResult:u=null}={}){const i=null!=l&&Number.isFinite(l),o=i?l:a,s=u&&Number.isFinite(u.value)&&u.value>=0&&("noi"===u.type||"gross"===u.type);let E,T;if(null!=t&&Number.isFinite(t)&&t>=0)E=t,T="confirmed";else{E=calculateNOIByType(c,o,n,{bedroomCount:e,strApiResult:u});const t=(n||"").toLowerCase();T=t===r.STR?s?"measured":"estimate":t===r.ASSISTED_LIVING?"bedrooms":i?"cap":"estimate"}Number.isFinite(E)||(E=0);return{activeCapRate:Number.isFinite(c)&&c>0?E/c:null,noi:E,noiSource:T,reportedCapRate:i?l:null}}function calculateAssignmentFee(e,r=100*t.ASSIGNMENT_FEE_PERCENTAGE){try{return e*(r/100)}catch(e){return 0}}function calculateCashOfferPrice(e,r=t.CASH_OFFER_ASSIGNMENT_PERCENTAGE,a=t.CASH_OFFER_ROUNDING){const c=Number(e);if(!Number.isFinite(c)||c<=0)return null;const n=c*(1-r);return!Number.isFinite(a)||a<=0?n:Math.floor(Math.round(n)/a)*a}function calculateNetToBuyer(r,a={}){const{buyerCostPercent:c=100*t.NET_TO_BUYER_PERCENTAGE,sellerCostAssignment:n=100*t.ASSIGNMENT_FEE_PERCENTAGE,sellerCostClosing:l=100*t.CLOSING_COSTS_PERCENTAGE,additionalCostRehab:u=100*t.REHAB_RATE,additionalCostFinancing:i=100*t.HARD_MONEY_RATE,dscrLtvPercent:o=100*e.DEFAULT_DSCR_PERCENTAGE}=a;try{return r*(c/100)-r*((n+l)/100)-r*(u/100)-i/100*(r-r*(o/100))}catch(e){return 0}}function calculateBalloonBalance(t,r,a,c=e.DEFAULT_BALLOON_PERIOD_YEARS){try{if(t<=0||r<0||a<=0||c<=0)return 0;if(c>=a)return 0;if(0===r){const e=12*a;return t*(e-12*c)/e}const e=r/12,n=12*a,l=12*c,u=Math.pow(1+e,n),i=t*(u-Math.pow(1+e,l))/(u-1);return Math.max(0,i)}catch(e){return 0}}function calculateAppreciatedValue(t,r=e.APPRECIATION_RATE,a=e.DEFAULT_BALLOON_PERIOD_YEARS){try{return t<=0||r<0||a<0?t:t*Math.pow(1+r,a)}catch(e){return t}}function calculateCashOutAfterRefi(t,r,a,n={}){const{appreciationRate:l=e.APPRECIATION_RATE,balloonYears:u=e.DEFAULT_BALLOON_PERIOD_YEARS,dscrRate:i=c.rate,dscrTerm:o=c.amortization,sellerFiTerm:s=e.SELLER_FI_AMORTIZATION,refiLtvPercent:E=70}=n;try{const c=calculateAppreciatedValue(t,l,u),n=calculateBalloonBalance(r,i,o,u),T=calculateBalloonBalance(a,e.SELLER_FI_INTEREST_RATE,s,u);return c*(E/100)-(n+T)}catch(e){return 0}}function calculateCashFlow(e,t,r){return e-(t+r)}function calculateDiscountFromPrice(e,t){return!e||e<=0?0:(e-t)/e}function calculatePriceFromDiscount(e,t){return!e||e<=0?0:e*(1-t)}function safePercentage(e,t=100){return null==e||isNaN(e)?t:100*e}export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOfferPrice,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSTRNOI,equityPercentFromDebt,resolveListingFinancials,safePercentage};
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\nimport { FINANCIAL_CONSTANTS } from '../config/financial.js';\r\nimport { BUSINESS_CONSTANTS } from '../config/business.js';\r\nimport { PROPERTY_TYPE_CONSTANTS, PROPERTY_TYPES } from '../config/property-types.js';\r\n\r\nconst DEFAULT_TIER = FINANCIAL_CONSTANTS.INTEREST_RATE_TIERS[FINANCIAL_CONSTANTS.DEFAULT_INTEREST_RATE_TYPE];\r\n\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}\r\n\r\nexport function calculateCOCR30(askingPrice, noi) {\r\n try {\r\n const cashInvested = askingPrice * 0.30; // 30% down payment\r\n const dscrLoanAmount = askingPrice * 0.70; // Fixed 70% DSCR loan\r\n const dscrPayment = calculatePMT(dscrLoanAmount, 0.075, 30) * 12; // Annual DSCR payment\r\n const annualCashFlow = noi - dscrPayment;\r\n const cocr = (annualCashFlow / cashInvested) * 100;\r\n \r\n return cocr;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\nexport function calculateCashFlowYield(monthlyCashFlow, purchasePrice) {\r\n if (!purchasePrice || purchasePrice <= 0) return 0;\r\n const annualCashFlow = monthlyCashFlow * 12;\r\n return (annualCashFlow / purchasePrice) * 100;\r\n}\r\n\r\n/**\r\n * Equity as a decimal fraction of price, derived from outstanding debt.\r\n * equity = (price - debtBalance) / price. Recompute whenever the user edits price.\r\n * Falls back to 1 (100% equity) when debt is unavailable or price is non-positive,\r\n * matching the \"estimated = 100%\" rule when the debt service returns no number.\r\n * @param {number} price - Listing/asking/offered price.\r\n * @param {number} debtBalance - Outstanding mortgage balance owing.\r\n * @returns {number} Equity as a decimal (e.g. 0.59 for 59%); 1 when debt is unknown.\r\n */\r\nexport function equityPercentFromDebt(price, debtBalance) {\r\n const p = Number(price);\r\n const d = Number(debtBalance);\r\n if (!Number.isFinite(p) || p <= 0) return 1;\r\n if (!Number.isFinite(d)) return 1;\r\n return (p - d) / p;\r\n}\r\n\r\n\r\n/**\r\n * Calculate the property price that yields a target COCR percentage\r\n * @param {number} noi - Net Operating Income (annual)\r\n * @param {number} targetCOCR - Target COCR as decimal (default: 0.15 for 15%)\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} Calculated property price\r\n */\r\nexport function calculatePriceForCOCR(noi, targetCOCR = 0.15, options = {}) {\r\n const {\r\n downPercent = FINANCIAL_CONSTANTS.DEFAULT_DOWN_PAYMENT * 100,\r\n dscrLtvPercent = FINANCIAL_CONSTANTS.DEFAULT_DSCR_PERCENTAGE * 100,\r\n dscrRate = DEFAULT_TIER.rate,\r\n dscrTerm = DEFAULT_TIER.amortization,\r\n maxIterations = BUSINESS_CONSTANTS.MAX_ITERATIONS,\r\n tolerance = BUSINESS_CONSTANTS.CALCULATION_TOLERANCE\r\n } = options;\r\n\r\n try {\r\n let targetPrice = noi / 0.08; // Initial estimate: NOI / 8% cap rate\r\n let iterations = 0;\r\n \r\n while (iterations < maxIterations) {\r\n const cashInvested = targetPrice * (downPercent / 100);\r\n const dscrLoanAmount = targetPrice * (dscrLtvPercent / 100);\r\n const dscrPayment = calculatePMT(dscrLoanAmount, dscrRate, dscrTerm) * 12;\r\n const annualCashFlow = noi - dscrPayment;\r\n const currentCOCR = annualCashFlow / cashInvested;\r\n \r\n if (Math.abs(currentCOCR - targetCOCR) < tolerance) {\r\n break;\r\n }\r\n \r\n const error = currentCOCR - targetCOCR;\r\n const adjustment = error * BUSINESS_CONSTANTS.ADJUSTMENT_FACTOR;\r\n \r\n if (error > 0) {\r\n targetPrice = targetPrice * (1 + Math.abs(adjustment));\r\n } else {\r\n targetPrice = targetPrice * (1 - Math.abs(adjustment));\r\n }\r\n \r\n // Reasonable bounds during iteration (prevent extreme values)\r\n if (targetPrice < 1000) targetPrice = 1000;\r\n if (targetPrice > noi * BUSINESS_CONSTANTS.MAX_COCR15_PRICE_MULTIPLIER) {\r\n targetPrice = noi * BUSINESS_CONSTANTS.CONSERVATIVE_COCR15_PRICE_MULTIPLIER;\r\n }\r\n \r\n iterations++;\r\n }\r\n \r\n // Apply final bounds check AFTER iteration\r\n if (targetPrice < BUSINESS_CONSTANTS.MINIMUM_COCR15_PRICE) {\r\n targetPrice = BUSINESS_CONSTANTS.MINIMUM_COCR15_PRICE;\r\n }\r\n \r\n return targetPrice;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate COCR at a specific down payment percentage\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} noi - Net Operating Income (annual)\r\n * @param {number} downPercent - Down payment percentage\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} COCR percentage\r\n */\r\nexport function calculateCOCRAtPercent(askingPrice, noi, downPercent, options = {}) {\r\n const {\r\n dscrRate = DEFAULT_TIER.rate,\r\n dscrTerm = DEFAULT_TIER.amortization,\r\n } = options;\r\n\r\n try {\r\n const downDecimal = downPercent / 100;\r\n const cashInvested = askingPrice * downDecimal;\r\n \r\n // Fix financing structure: seller financing reduces available DSCR loan\r\n const dscrLoanAmount = askingPrice - cashInvested;\r\n \r\n const dscrPayment = calculatePMT(dscrLoanAmount, dscrRate, dscrTerm) * 12;\r\n \r\n const annualCashFlow = noi - dscrPayment;\r\n const cocr = (annualCashFlow / cashInvested) * 100;\r\n \r\n return cocr;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate STR (short-term rental) NOI - the single source of STR NOI math.\r\n *\r\n * Resolution order:\r\n * 1. apiResult type 'noi' -> value is already net, return as-is\r\n * 2. apiResult type 'gross' -> apply NOI margin (value * noiPercentage)\r\n * 3. no/invalid apiResult -> estimate from price (price * grossRate * noiPercentage)\r\n *\r\n * apiResult comes from api.archerjessop.com/str-revenue: { value, type }.\r\n * Pass null while that backend is not live (the price estimate is used).\r\n *\r\n * @param {number} askingPrice - Property asking price\r\n * @param {{value:number, type:'noi'|'gross'}|null} apiResult - STR revenue API result\r\n * @param {Object} options - Rate overrides (default to STR config constants)\r\n * @returns {number} Annual NOI (0 on invalid input)\r\n */\r\nexport function calculateSTRNOI(askingPrice, apiResult = null, options = {}) {\r\n const {\r\n grossRate = PROPERTY_TYPE_CONSTANTS.STR.ESTIMATED_GROSS_RATE,\r\n noiPercentage = PROPERTY_TYPE_CONSTANTS.STR.NOI_PERCENTAGE\r\n } = options;\r\n\r\n try {\r\n if (apiResult && Number.isFinite(apiResult.value) && apiResult.value >= 0) {\r\n if (apiResult.type === \"noi\") return apiResult.value;\r\n if (apiResult.type === \"gross\") return apiResult.value * noiPercentage;\r\n }\r\n\r\n if (!Number.isFinite(askingPrice) || askingPrice <= 0) return 0;\r\n return askingPrice * grossRate * noiPercentage;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate NOI based on property type\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} capRate - Cap rate as decimal (e.g., 0.08 for 8%)\r\n * @param {string} propertyType - Property type from PROPERTY_TYPES\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} Calculated NOI\r\n */\r\nexport function calculateNOIByType(askingPrice, capRate, propertyType = PROPERTY_TYPES.MULTIFAMILY, options = {}) {\r\n const {\r\n strApiResult = null,\r\n strGrossIncomeMultiplier = PROPERTY_TYPE_CONSTANTS.STR.ESTIMATED_GROSS_RATE,\r\n strNoiPercentage = PROPERTY_TYPE_CONSTANTS.STR.NOI_PERCENTAGE,\r\n assistedIncomePerBedroom = PROPERTY_TYPE_CONSTANTS.ASSISTED_LIVING.INCOME_PER_BEDROOM_MONTHLY,\r\n bedroomCount = PROPERTY_TYPE_CONSTANTS.ASSISTED_LIVING.DEFAULT_BEDROOM_COUNT\r\n } = options;\r\n\r\n try {\r\n switch ((propertyType || PROPERTY_TYPES.MULTIFAMILY).toLowerCase()) {\r\n case PROPERTY_TYPES.STR:\r\n return calculateSTRNOI(askingPrice, strApiResult, {\r\n grossRate: strGrossIncomeMultiplier,\r\n noiPercentage: strNoiPercentage\r\n });\r\n\r\n case PROPERTY_TYPES.ASSISTED_LIVING:\r\n return bedroomCount * assistedIncomePerBedroom * 12;\r\n \r\n case PROPERTY_TYPES.MULTIFAMILY:\r\n default:\r\n return askingPrice * capRate;\r\n }\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Resolve a listing's canonical NOI and the cap rates derived from it.\r\n *\r\n * NOI is the source of truth; each property type computes it by its own model\r\n * (delegated to calculateNOIByType, unchanged). The active (displayed) cap rate is\r\n * always derived as NOI / price. The reported cap rate is carried through as\r\n * provenance (shown on hover; null => the UI shows \"N/A\"); it only DRIVES the NOI for\r\n * multifamily, where the cap rate is the model input.\r\n *\r\n * NOI precedence: an analyst-confirmed NOI overrides the per-type model; otherwise STR\r\n * uses measured 3rd-party revenue when present and the price-based estimate otherwise;\r\n * assisted uses bedroom count; multifamily uses the reported cap (or estimatedCapRate\r\n * when none was reported).\r\n *\r\n * @param {Object} input\r\n * @param {number|null} [input.bedroomCount] - Bedroom count for assisted living\r\n * @param {number|null} [input.confirmedNOI] - Analyst-confirmed/edited NOI; overrides the per-type model\r\n * @param {number} [input.estimatedCapRate] - Fallback cap (decimal) used for multifamily NOI when none was reported\r\n * @param {number} input.price - Asking price\r\n * @param {string} [input.propertyType] - One of PROPERTY_TYPES\r\n * @param {number|null} [input.reportedCapRate] - Real scraped/listed cap rate as a decimal (e.g. 0.0486); null when none was reported\r\n * @param {{value:number,type:'noi'|'gross'}|null} [input.strApiResult] - Measured STR revenue (3rd-party); null until that backend ships\r\n * @returns {{activeCapRate:(number|null), noi:number, noiSource:string, reportedCapRate:(number|null)}}\r\n */\r\nexport function resolveListingFinancials({\r\n bedroomCount = null,\r\n confirmedNOI = null,\r\n estimatedCapRate,\r\n price,\r\n propertyType = PROPERTY_TYPES.MULTIFAMILY,\r\n reportedCapRate = null,\r\n strApiResult = null,\r\n} = {}) {\r\n const hasReported = reportedCapRate != null && Number.isFinite(reportedCapRate);\r\n const capForNOI = hasReported ? reportedCapRate : estimatedCapRate;\r\n const measured = strApiResult && Number.isFinite(strApiResult.value) && strApiResult.value >= 0 &&\r\n (strApiResult.type === \"noi\" || strApiResult.type === \"gross\");\r\n\r\n let noi;\r\n let noiSource;\r\n if (confirmedNOI != null && Number.isFinite(confirmedNOI) && confirmedNOI >= 0) {\r\n noi = confirmedNOI;\r\n noiSource = \"confirmed\";\r\n } else {\r\n noi = calculateNOIByType(price, capForNOI, propertyType, { bedroomCount, strApiResult });\r\n const type = (propertyType || \"\").toLowerCase();\r\n if (type === PROPERTY_TYPES.STR) {\r\n noiSource = measured ? \"measured\" : \"estimate\";\r\n } else if (type === PROPERTY_TYPES.ASSISTED_LIVING) {\r\n noiSource = \"bedrooms\";\r\n } else {\r\n noiSource = hasReported ? \"cap\" : \"estimate\";\r\n }\r\n }\r\n\r\n if (!Number.isFinite(noi)) noi = 0;\r\n const activeCapRate = Number.isFinite(price) && price > 0 ? noi / price : null;\r\n\r\n return {\r\n activeCapRate,\r\n noi,\r\n noiSource,\r\n reportedCapRate: hasReported ? reportedCapRate : null,\r\n };\r\n}\r\n\r\n/**\r\n * Calculate assignment fee\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} assignmentPercent - Assignment fee percentage (uses config default)\r\n * @returns {number} Assignment fee amount\r\n */\r\nexport function calculateAssignmentFee(askingPrice, assignmentPercent = BUSINESS_CONSTANTS.ASSIGNMENT_FEE_PERCENTAGE * 100) {\r\n try {\r\n return askingPrice * (assignmentPercent / 100);\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate the all-cash offer price from the 15%-COCR price.\r\n * The cash offer is the standard-investor 15%-COCR price less a haircut that\r\n * covers the assignment fee and associated costs.\r\n * @param {number} cocr15Price - Price at which COCR hits 15% on the NOI (dollars)\r\n * @param {number} assignmentPercent - Haircut as a decimal (uses config default)\r\n * @returns {number|null} Cash offer price, or null when cocr15Price is not a positive number\r\n */\r\nexport function calculateCashOfferPrice(cocr15Price, assignmentPercent = BUSINESS_CONSTANTS.CASH_OFFER_ASSIGNMENT_PERCENTAGE) {\r\n const price = Number(cocr15Price);\r\n if (!Number.isFinite(price) || price <= 0) return null;\r\n return price * (1 - assignmentPercent);\r\n}\r\n\r\n/**\r\n * Calculate net to buyer\r\n * @param {number} askingPrice - Property asking price\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} Net to buyer amount\r\n */\r\nexport function calculateNetToBuyer(askingPrice, options = {}) {\r\n const {\r\n buyerCostPercent = BUSINESS_CONSTANTS.NET_TO_BUYER_PERCENTAGE * 100,\r\n sellerCostAssignment = BUSINESS_CONSTANTS.ASSIGNMENT_FEE_PERCENTAGE * 100,\r\n sellerCostClosing = BUSINESS_CONSTANTS.CLOSING_COSTS_PERCENTAGE * 100,\r\n additionalCostRehab = BUSINESS_CONSTANTS.REHAB_RATE * 100,\r\n additionalCostFinancing = BUSINESS_CONSTANTS.HARD_MONEY_RATE * 100,\r\n dscrLtvPercent = FINANCIAL_CONSTANTS.DEFAULT_DSCR_PERCENTAGE * 100\r\n } = options;\r\n\r\n try {\r\n const dscrLoanAmount = askingPrice * (dscrLtvPercent / 100);\r\n \r\n return askingPrice * (buyerCostPercent / 100) - \r\n askingPrice * ((sellerCostAssignment + sellerCostClosing) / 100) - \r\n askingPrice * (additionalCostRehab / 100) - \r\n (additionalCostFinancing / 100) * (askingPrice - dscrLoanAmount);\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate remaining loan balance at end of balloon period\r\n * @param {number} loanAmount - Initial loan amount\r\n * @param {number} interestRate - Annual interest rate as decimal (e.g., 0.075 for 7.5%)\r\n * @param {number} amortizationYears - Full amortization period in years\r\n * @param {number} balloonYears - Balloon period in years\r\n * @returns {number} Remaining balance at end of balloon period\r\n */\r\nexport function calculateBalloonBalance(loanAmount, interestRate, amortizationYears, balloonYears = FINANCIAL_CONSTANTS.DEFAULT_BALLOON_PERIOD_YEARS) {\r\n try {\r\n if (loanAmount <= 0 || interestRate < 0 || amortizationYears <= 0 || balloonYears <= 0) {\r\n return 0;\r\n }\r\n\r\n // If balloon period equals or exceeds amortization, loan is fully paid\r\n if (balloonYears >= amortizationYears) {\r\n return 0;\r\n }\r\n\r\n // Special handling for zero interest rate (simple linear paydown)\r\n if (interestRate === 0) {\r\n const totalPayments = amortizationYears * 12;\r\n const paymentsMade = balloonYears * 12;\r\n return loanAmount * (totalPayments - paymentsMade) / totalPayments;\r\n }\r\n\r\n const monthlyRate = interestRate / 12;\r\n const totalPayments = amortizationYears * 12;\r\n const balloonPayments = balloonYears * 12;\r\n\r\n // Calculate remaining balance using loan balance formula\r\n // Balance = P * [(1 + r)^n - (1 + r)^p] / [(1 + r)^n - 1]\r\n // Where P = principal, r = monthly rate, n = total payments, p = payments made\r\n \r\n const factor1 = Math.pow(1 + monthlyRate, totalPayments);\r\n const factor2 = Math.pow(1 + monthlyRate, balloonPayments);\r\n \r\n const remainingBalance = loanAmount * (factor1 - factor2) / (factor1 - 1);\r\n \r\n return Math.max(0, remainingBalance); // Ensure non-negative\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate property value after appreciation period\r\n * @param {number} currentValue - Current property value\r\n * @param {number} appreciationRate - Annual appreciation rate as decimal\r\n * @param {number} years - Number of years\r\n * @returns {number} Appreciated property value\r\n */\r\nexport function calculateAppreciatedValue(currentValue, appreciationRate = FINANCIAL_CONSTANTS.APPRECIATION_RATE, years = FINANCIAL_CONSTANTS.DEFAULT_BALLOON_PERIOD_YEARS) {\r\n try {\r\n if (currentValue <= 0 || appreciationRate < 0 || years < 0) {\r\n return currentValue;\r\n }\r\n \r\n return currentValue * Math.pow(1 + appreciationRate, years);\r\n } catch (error) {\r\n return currentValue;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate cash out amount after appreciation refinance\r\n * @param {number} originalPrice - Original purchase price\r\n * @param {number} dscrLoanAmount - Original DSCR loan amount \r\n * @param {number} sellerFiAmount - Original seller financing amount\r\n * @param {Object} options - Configuration options\r\n * @returns {number} Cash out amount (positive = cash out, negative = cash in)\r\n */\r\nexport function calculateCashOutAfterRefi(originalPrice, dscrLoanAmount, sellerFiAmount, options = {}) {\r\n const {\r\n appreciationRate = FINANCIAL_CONSTANTS.APPRECIATION_RATE,\r\n balloonYears = FINANCIAL_CONSTANTS.DEFAULT_BALLOON_PERIOD_YEARS,\r\n dscrRate = DEFAULT_TIER.rate,\r\n dscrTerm = DEFAULT_TIER.amortization,\r\n sellerFiTerm = FINANCIAL_CONSTANTS.SELLER_FI_AMORTIZATION,\r\n refiLtvPercent = 70 // 70% LTV on refi\r\n } = options;\r\n\r\n try {\r\n // Calculate appreciated property value\r\n const appreciatedValue = calculateAppreciatedValue(originalPrice, appreciationRate, balloonYears);\r\n \r\n // Calculate remaining balance on DSCR loan\r\n const dscrRemainingBalance = calculateBalloonBalance(dscrLoanAmount, dscrRate, dscrTerm, balloonYears);\r\n \r\n // Calculate remaining balance on seller financing (0% interest)\r\n const sellerFiRemainingBalance = calculateBalloonBalance(sellerFiAmount, FINANCIAL_CONSTANTS.SELLER_FI_INTEREST_RATE, sellerFiTerm, balloonYears);\r\n \r\n // Total remaining debt\r\n const totalRemainingDebt = dscrRemainingBalance + sellerFiRemainingBalance;\r\n \r\n // Calculate new loan amount at 70% LTV of appreciated value\r\n const newLoanAmount = appreciatedValue * (refiLtvPercent / 100);\r\n \r\n // Cash out = new loan - total remaining debt\r\n const cashOut = newLoanAmount - totalRemainingDebt;\r\n \r\n return cashOut;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n// Cash Flow calculation (matching loopnet-analyzer exactly)\r\nexport function calculateCashFlow(monthlyNOI, dscrPayment, sfPayment) {\r\n return monthlyNOI - (dscrPayment + sfPayment);\r\n}\r\n\r\n/**\r\n * Calculate discount percentage from asking price and offered price\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} priceOffered - Offered price\r\n * @returns {number} Discount as decimal (positive = discount, negative = premium)\r\n */\r\nexport function calculateDiscountFromPrice(askingPrice, priceOffered) {\r\n if (!askingPrice || askingPrice <= 0) return 0;\r\n \r\n return (askingPrice - priceOffered) / askingPrice;\r\n}\r\n\r\n/**\r\n * Calculate price from asking price and discount percentage\r\n * @param {number} askingPrice - Property asking price \r\n * @param {number} discountPercent - Discount as decimal (positive = discount, negative = premium)\r\n * @returns {number} Calculated price\r\n */\r\nexport function calculatePriceFromDiscount(askingPrice, discountPercent) {\r\n if (!askingPrice || askingPrice <= 0) return 0;\r\n \r\n return askingPrice * (1 - discountPercent);\r\n}\r\n\r\nexport function safePercentage(value, fallback = 100) {\r\n return (value != null && !isNaN(value)) ? (value * 100) : fallback;\r\n}"],"names":["DEFAULT_TIER","FINANCIAL_CONSTANTS","INTEREST_RATE_TIERS","DEFAULT_INTEREST_RATE_TYPE","calculatePMT","principal","annualRate","years","monthlyRate","numPayments","Math","pow","calculateCOCR30","askingPrice","noi","cashInvested","dscrPayment","error","calculateCashFlowYield","monthlyCashFlow","purchasePrice","equityPercentFromDebt","price","debtBalance","p","Number","d","isFinite","calculatePriceForCOCR","targetCOCR","options","downPercent","DEFAULT_DOWN_PAYMENT","dscrLtvPercent","DEFAULT_DSCR_PERCENTAGE","dscrRate","rate","dscrTerm","amortization","maxIterations","BUSINESS_CONSTANTS","MAX_ITERATIONS","tolerance","CALCULATION_TOLERANCE","targetPrice","iterations","currentCOCR","abs","adjustment","ADJUSTMENT_FACTOR","MAX_COCR15_PRICE_MULTIPLIER","CONSERVATIVE_COCR15_PRICE_MULTIPLIER","MINIMUM_COCR15_PRICE","calculateCOCRAtPercent","calculateSTRNOI","apiResult","grossRate","PROPERTY_TYPE_CONSTANTS","STR","ESTIMATED_GROSS_RATE","noiPercentage","NOI_PERCENTAGE","value","type","calculateNOIByType","capRate","propertyType","PROPERTY_TYPES","MULTIFAMILY","strApiResult","strGrossIncomeMultiplier","strNoiPercentage","assistedIncomePerBedroom","ASSISTED_LIVING","INCOME_PER_BEDROOM_MONTHLY","bedroomCount","DEFAULT_BEDROOM_COUNT","toLowerCase","resolveListingFinancials","confirmedNOI","estimatedCapRate","reportedCapRate","hasReported","capForNOI","measured","noiSource","activeCapRate","calculateAssignmentFee","assignmentPercent","ASSIGNMENT_FEE_PERCENTAGE","calculateCashOfferPrice","cocr15Price","CASH_OFFER_ASSIGNMENT_PERCENTAGE","calculateNetToBuyer","buyerCostPercent","NET_TO_BUYER_PERCENTAGE","sellerCostAssignment","sellerCostClosing","CLOSING_COSTS_PERCENTAGE","additionalCostRehab","REHAB_RATE","additionalCostFinancing","HARD_MONEY_RATE","calculateBalloonBalance","loanAmount","interestRate","amortizationYears","balloonYears","DEFAULT_BALLOON_PERIOD_YEARS","totalPayments","balloonPayments","factor1","remainingBalance","max","calculateAppreciatedValue","currentValue","appreciationRate","APPRECIATION_RATE","calculateCashOutAfterRefi","originalPrice","dscrLoanAmount","sellerFiAmount","sellerFiTerm","SELLER_FI_AMORTIZATION","refiLtvPercent","appreciatedValue","dscrRemainingBalance","sellerFiRemainingBalance","SELLER_FI_INTEREST_RATE","calculateCashFlow","monthlyNOI","sfPayment","calculateDiscountFromPrice","priceOffered","calculatePriceFromDiscount","discountPercent","safePercentage","fallback","isNaN"],"mappings":"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"}
1
+ {"version":3,"file":"calculations.js","sources":["../../src/financial/calculations.js"],"sourcesContent":["// src/financial/calculations.js\r\n\r\nimport { FINANCIAL_CONSTANTS } from '../config/financial.js';\r\nimport { BUSINESS_CONSTANTS } from '../config/business.js';\r\nimport { PROPERTY_TYPE_CONSTANTS, PROPERTY_TYPES } from '../config/property-types.js';\r\n\r\nconst DEFAULT_TIER = FINANCIAL_CONSTANTS.INTEREST_RATE_TIERS[FINANCIAL_CONSTANTS.DEFAULT_INTEREST_RATE_TYPE];\r\n\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}\r\n\r\nexport function calculateCOCR30(askingPrice, noi) {\r\n try {\r\n const cashInvested = askingPrice * 0.30; // 30% down payment\r\n const dscrLoanAmount = askingPrice * 0.70; // Fixed 70% DSCR loan\r\n const dscrPayment = calculatePMT(dscrLoanAmount, 0.075, 30) * 12; // Annual DSCR payment\r\n const annualCashFlow = noi - dscrPayment;\r\n const cocr = (annualCashFlow / cashInvested) * 100;\r\n \r\n return cocr;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\nexport function calculateCashFlowYield(monthlyCashFlow, purchasePrice) {\r\n if (!purchasePrice || purchasePrice <= 0) return 0;\r\n const annualCashFlow = monthlyCashFlow * 12;\r\n return (annualCashFlow / purchasePrice) * 100;\r\n}\r\n\r\n/**\r\n * Equity as a decimal fraction of price, derived from outstanding debt.\r\n * equity = (price - debtBalance) / price. Recompute whenever the user edits price.\r\n * Falls back to 1 (100% equity) when debt is unavailable or price is non-positive,\r\n * matching the \"estimated = 100%\" rule when the debt service returns no number.\r\n * @param {number} price - Listing/asking/offered price.\r\n * @param {number} debtBalance - Outstanding mortgage balance owing.\r\n * @returns {number} Equity as a decimal (e.g. 0.59 for 59%); 1 when debt is unknown.\r\n */\r\nexport function equityPercentFromDebt(price, debtBalance) {\r\n const p = Number(price);\r\n const d = Number(debtBalance);\r\n if (!Number.isFinite(p) || p <= 0) return 1;\r\n if (!Number.isFinite(d)) return 1;\r\n return (p - d) / p;\r\n}\r\n\r\n\r\n/**\r\n * Calculate the property price that yields a target COCR percentage\r\n * @param {number} noi - Net Operating Income (annual)\r\n * @param {number} targetCOCR - Target COCR as decimal (default: 0.15 for 15%)\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} Calculated property price\r\n */\r\nexport function calculatePriceForCOCR(noi, targetCOCR = 0.15, options = {}) {\r\n const {\r\n downPercent = FINANCIAL_CONSTANTS.DEFAULT_DOWN_PAYMENT * 100,\r\n dscrLtvPercent = FINANCIAL_CONSTANTS.DEFAULT_DSCR_PERCENTAGE * 100,\r\n dscrRate = DEFAULT_TIER.rate,\r\n dscrTerm = DEFAULT_TIER.amortization,\r\n maxIterations = BUSINESS_CONSTANTS.MAX_ITERATIONS,\r\n tolerance = BUSINESS_CONSTANTS.CALCULATION_TOLERANCE\r\n } = options;\r\n\r\n try {\r\n let targetPrice = noi / 0.08; // Initial estimate: NOI / 8% cap rate\r\n let iterations = 0;\r\n \r\n while (iterations < maxIterations) {\r\n const cashInvested = targetPrice * (downPercent / 100);\r\n const dscrLoanAmount = targetPrice * (dscrLtvPercent / 100);\r\n const dscrPayment = calculatePMT(dscrLoanAmount, dscrRate, dscrTerm) * 12;\r\n const annualCashFlow = noi - dscrPayment;\r\n const currentCOCR = annualCashFlow / cashInvested;\r\n \r\n if (Math.abs(currentCOCR - targetCOCR) < tolerance) {\r\n break;\r\n }\r\n \r\n const error = currentCOCR - targetCOCR;\r\n const adjustment = error * BUSINESS_CONSTANTS.ADJUSTMENT_FACTOR;\r\n \r\n if (error > 0) {\r\n targetPrice = targetPrice * (1 + Math.abs(adjustment));\r\n } else {\r\n targetPrice = targetPrice * (1 - Math.abs(adjustment));\r\n }\r\n \r\n // Reasonable bounds during iteration (prevent extreme values)\r\n if (targetPrice < 1000) targetPrice = 1000;\r\n if (targetPrice > noi * BUSINESS_CONSTANTS.MAX_COCR15_PRICE_MULTIPLIER) {\r\n targetPrice = noi * BUSINESS_CONSTANTS.CONSERVATIVE_COCR15_PRICE_MULTIPLIER;\r\n }\r\n \r\n iterations++;\r\n }\r\n \r\n // Apply final bounds check AFTER iteration\r\n if (targetPrice < BUSINESS_CONSTANTS.MINIMUM_COCR15_PRICE) {\r\n targetPrice = BUSINESS_CONSTANTS.MINIMUM_COCR15_PRICE;\r\n }\r\n \r\n return targetPrice;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate COCR at a specific down payment percentage\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} noi - Net Operating Income (annual)\r\n * @param {number} downPercent - Down payment percentage\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} COCR percentage\r\n */\r\nexport function calculateCOCRAtPercent(askingPrice, noi, downPercent, options = {}) {\r\n const {\r\n dscrRate = DEFAULT_TIER.rate,\r\n dscrTerm = DEFAULT_TIER.amortization,\r\n } = options;\r\n\r\n try {\r\n const downDecimal = downPercent / 100;\r\n const cashInvested = askingPrice * downDecimal;\r\n \r\n // Fix financing structure: seller financing reduces available DSCR loan\r\n const dscrLoanAmount = askingPrice - cashInvested;\r\n \r\n const dscrPayment = calculatePMT(dscrLoanAmount, dscrRate, dscrTerm) * 12;\r\n \r\n const annualCashFlow = noi - dscrPayment;\r\n const cocr = (annualCashFlow / cashInvested) * 100;\r\n \r\n return cocr;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate STR (short-term rental) NOI - the single source of STR NOI math.\r\n *\r\n * Resolution order:\r\n * 1. apiResult type 'noi' -> value is already net, return as-is\r\n * 2. apiResult type 'gross' -> apply NOI margin (value * noiPercentage)\r\n * 3. no/invalid apiResult -> estimate from price (price * grossRate * noiPercentage)\r\n *\r\n * apiResult comes from api.archerjessop.com/str-revenue: { value, type }.\r\n * Pass null while that backend is not live (the price estimate is used).\r\n *\r\n * @param {number} askingPrice - Property asking price\r\n * @param {{value:number, type:'noi'|'gross'}|null} apiResult - STR revenue API result\r\n * @param {Object} options - Rate overrides (default to STR config constants)\r\n * @returns {number} Annual NOI (0 on invalid input)\r\n */\r\nexport function calculateSTRNOI(askingPrice, apiResult = null, options = {}) {\r\n const {\r\n grossRate = PROPERTY_TYPE_CONSTANTS.STR.ESTIMATED_GROSS_RATE,\r\n noiPercentage = PROPERTY_TYPE_CONSTANTS.STR.NOI_PERCENTAGE\r\n } = options;\r\n\r\n try {\r\n if (apiResult && Number.isFinite(apiResult.value) && apiResult.value >= 0) {\r\n if (apiResult.type === \"noi\") return apiResult.value;\r\n if (apiResult.type === \"gross\") return apiResult.value * noiPercentage;\r\n }\r\n\r\n if (!Number.isFinite(askingPrice) || askingPrice <= 0) return 0;\r\n return askingPrice * grossRate * noiPercentage;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate NOI based on property type\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} capRate - Cap rate as decimal (e.g., 0.08 for 8%)\r\n * @param {string} propertyType - Property type from PROPERTY_TYPES\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} Calculated NOI\r\n */\r\nexport function calculateNOIByType(askingPrice, capRate, propertyType = PROPERTY_TYPES.MULTIFAMILY, options = {}) {\r\n const {\r\n strApiResult = null,\r\n strGrossIncomeMultiplier = PROPERTY_TYPE_CONSTANTS.STR.ESTIMATED_GROSS_RATE,\r\n strNoiPercentage = PROPERTY_TYPE_CONSTANTS.STR.NOI_PERCENTAGE,\r\n assistedIncomePerBedroom = PROPERTY_TYPE_CONSTANTS.ASSISTED_LIVING.INCOME_PER_BEDROOM_MONTHLY,\r\n bedroomCount = PROPERTY_TYPE_CONSTANTS.ASSISTED_LIVING.DEFAULT_BEDROOM_COUNT\r\n } = options;\r\n\r\n try {\r\n switch ((propertyType || PROPERTY_TYPES.MULTIFAMILY).toLowerCase()) {\r\n case PROPERTY_TYPES.STR:\r\n return calculateSTRNOI(askingPrice, strApiResult, {\r\n grossRate: strGrossIncomeMultiplier,\r\n noiPercentage: strNoiPercentage\r\n });\r\n\r\n case PROPERTY_TYPES.ASSISTED_LIVING:\r\n return bedroomCount * assistedIncomePerBedroom * 12;\r\n \r\n case PROPERTY_TYPES.MULTIFAMILY:\r\n default:\r\n return askingPrice * capRate;\r\n }\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Resolve a listing's canonical NOI and the cap rates derived from it.\r\n *\r\n * NOI is the source of truth; each property type computes it by its own model\r\n * (delegated to calculateNOIByType, unchanged). The active (displayed) cap rate is\r\n * always derived as NOI / price. The reported cap rate is carried through as\r\n * provenance (shown on hover; null => the UI shows \"N/A\"); it only DRIVES the NOI for\r\n * multifamily, where the cap rate is the model input.\r\n *\r\n * NOI precedence: an analyst-confirmed NOI overrides the per-type model; otherwise STR\r\n * uses measured 3rd-party revenue when present and the price-based estimate otherwise;\r\n * assisted uses bedroom count; multifamily uses the reported cap (or estimatedCapRate\r\n * when none was reported).\r\n *\r\n * @param {Object} input\r\n * @param {number|null} [input.bedroomCount] - Bedroom count for assisted living\r\n * @param {number|null} [input.confirmedNOI] - Analyst-confirmed/edited NOI; overrides the per-type model\r\n * @param {number} [input.estimatedCapRate] - Fallback cap (decimal) used for multifamily NOI when none was reported\r\n * @param {number} input.price - Asking price\r\n * @param {string} [input.propertyType] - One of PROPERTY_TYPES\r\n * @param {number|null} [input.reportedCapRate] - Real scraped/listed cap rate as a decimal (e.g. 0.0486); null when none was reported\r\n * @param {{value:number,type:'noi'|'gross'}|null} [input.strApiResult] - Measured STR revenue (3rd-party); null until that backend ships\r\n * @returns {{activeCapRate:(number|null), noi:number, noiSource:string, reportedCapRate:(number|null)}}\r\n */\r\nexport function resolveListingFinancials({\r\n bedroomCount = null,\r\n confirmedNOI = null,\r\n estimatedCapRate,\r\n price,\r\n propertyType = PROPERTY_TYPES.MULTIFAMILY,\r\n reportedCapRate = null,\r\n strApiResult = null,\r\n} = {}) {\r\n const hasReported = reportedCapRate != null && Number.isFinite(reportedCapRate);\r\n const capForNOI = hasReported ? reportedCapRate : estimatedCapRate;\r\n const measured = strApiResult && Number.isFinite(strApiResult.value) && strApiResult.value >= 0 &&\r\n (strApiResult.type === \"noi\" || strApiResult.type === \"gross\");\r\n\r\n let noi;\r\n let noiSource;\r\n if (confirmedNOI != null && Number.isFinite(confirmedNOI) && confirmedNOI >= 0) {\r\n noi = confirmedNOI;\r\n noiSource = \"confirmed\";\r\n } else {\r\n noi = calculateNOIByType(price, capForNOI, propertyType, { bedroomCount, strApiResult });\r\n const type = (propertyType || \"\").toLowerCase();\r\n if (type === PROPERTY_TYPES.STR) {\r\n noiSource = measured ? \"measured\" : \"estimate\";\r\n } else if (type === PROPERTY_TYPES.ASSISTED_LIVING) {\r\n noiSource = \"bedrooms\";\r\n } else {\r\n noiSource = hasReported ? \"cap\" : \"estimate\";\r\n }\r\n }\r\n\r\n if (!Number.isFinite(noi)) noi = 0;\r\n const activeCapRate = Number.isFinite(price) && price > 0 ? noi / price : null;\r\n\r\n return {\r\n activeCapRate,\r\n noi,\r\n noiSource,\r\n reportedCapRate: hasReported ? reportedCapRate : null,\r\n };\r\n}\r\n\r\n/**\r\n * Calculate assignment fee\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} assignmentPercent - Assignment fee percentage (uses config default)\r\n * @returns {number} Assignment fee amount\r\n */\r\nexport function calculateAssignmentFee(askingPrice, assignmentPercent = BUSINESS_CONSTANTS.ASSIGNMENT_FEE_PERCENTAGE * 100) {\r\n try {\r\n return askingPrice * (assignmentPercent / 100);\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate the all-cash offer price from the 15%-COCR price.\r\n * The cash offer is the standard-investor 15%-COCR price less a haircut that\r\n * covers the assignment fee and associated costs, then floored DOWN to the\r\n * rounding step. Flooring (never rounding up) keeps the offer at or below the\r\n * 15%-COCR maximum threshold.\r\n * @param {number} cocr15Price - Price at which COCR hits 15% on the NOI (dollars)\r\n * @param {number} assignmentPercent - Haircut as a decimal (uses config default)\r\n * @param {number} roundingStep - Floor the result down to this step; 0 disables (uses config default)\r\n * @returns {number|null} Cash offer price, or null when cocr15Price is not a positive number\r\n */\r\nexport function calculateCashOfferPrice(\r\n cocr15Price,\r\n assignmentPercent = BUSINESS_CONSTANTS.CASH_OFFER_ASSIGNMENT_PERCENTAGE,\r\n roundingStep = BUSINESS_CONSTANTS.CASH_OFFER_ROUNDING\r\n) {\r\n const price = Number(cocr15Price);\r\n if (!Number.isFinite(price) || price <= 0) return null;\r\n const haircutPrice = price * (1 - assignmentPercent);\r\n if (!Number.isFinite(roundingStep) || roundingStep <= 0) return haircutPrice;\r\n // Round to whole dollars first so binary-float noise (e.g. 929999.9999) can't\r\n // knock the value down a whole step, then floor to the step.\r\n return Math.floor(Math.round(haircutPrice) / roundingStep) * roundingStep;\r\n}\r\n\r\n/**\r\n * Calculate net to buyer\r\n * @param {number} askingPrice - Property asking price\r\n * @param {Object} options - Configuration options (uses config constants as defaults)\r\n * @returns {number} Net to buyer amount\r\n */\r\nexport function calculateNetToBuyer(askingPrice, options = {}) {\r\n const {\r\n buyerCostPercent = BUSINESS_CONSTANTS.NET_TO_BUYER_PERCENTAGE * 100,\r\n sellerCostAssignment = BUSINESS_CONSTANTS.ASSIGNMENT_FEE_PERCENTAGE * 100,\r\n sellerCostClosing = BUSINESS_CONSTANTS.CLOSING_COSTS_PERCENTAGE * 100,\r\n additionalCostRehab = BUSINESS_CONSTANTS.REHAB_RATE * 100,\r\n additionalCostFinancing = BUSINESS_CONSTANTS.HARD_MONEY_RATE * 100,\r\n dscrLtvPercent = FINANCIAL_CONSTANTS.DEFAULT_DSCR_PERCENTAGE * 100\r\n } = options;\r\n\r\n try {\r\n const dscrLoanAmount = askingPrice * (dscrLtvPercent / 100);\r\n \r\n return askingPrice * (buyerCostPercent / 100) - \r\n askingPrice * ((sellerCostAssignment + sellerCostClosing) / 100) - \r\n askingPrice * (additionalCostRehab / 100) - \r\n (additionalCostFinancing / 100) * (askingPrice - dscrLoanAmount);\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate remaining loan balance at end of balloon period\r\n * @param {number} loanAmount - Initial loan amount\r\n * @param {number} interestRate - Annual interest rate as decimal (e.g., 0.075 for 7.5%)\r\n * @param {number} amortizationYears - Full amortization period in years\r\n * @param {number} balloonYears - Balloon period in years\r\n * @returns {number} Remaining balance at end of balloon period\r\n */\r\nexport function calculateBalloonBalance(loanAmount, interestRate, amortizationYears, balloonYears = FINANCIAL_CONSTANTS.DEFAULT_BALLOON_PERIOD_YEARS) {\r\n try {\r\n if (loanAmount <= 0 || interestRate < 0 || amortizationYears <= 0 || balloonYears <= 0) {\r\n return 0;\r\n }\r\n\r\n // If balloon period equals or exceeds amortization, loan is fully paid\r\n if (balloonYears >= amortizationYears) {\r\n return 0;\r\n }\r\n\r\n // Special handling for zero interest rate (simple linear paydown)\r\n if (interestRate === 0) {\r\n const totalPayments = amortizationYears * 12;\r\n const paymentsMade = balloonYears * 12;\r\n return loanAmount * (totalPayments - paymentsMade) / totalPayments;\r\n }\r\n\r\n const monthlyRate = interestRate / 12;\r\n const totalPayments = amortizationYears * 12;\r\n const balloonPayments = balloonYears * 12;\r\n\r\n // Calculate remaining balance using loan balance formula\r\n // Balance = P * [(1 + r)^n - (1 + r)^p] / [(1 + r)^n - 1]\r\n // Where P = principal, r = monthly rate, n = total payments, p = payments made\r\n \r\n const factor1 = Math.pow(1 + monthlyRate, totalPayments);\r\n const factor2 = Math.pow(1 + monthlyRate, balloonPayments);\r\n \r\n const remainingBalance = loanAmount * (factor1 - factor2) / (factor1 - 1);\r\n \r\n return Math.max(0, remainingBalance); // Ensure non-negative\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate property value after appreciation period\r\n * @param {number} currentValue - Current property value\r\n * @param {number} appreciationRate - Annual appreciation rate as decimal\r\n * @param {number} years - Number of years\r\n * @returns {number} Appreciated property value\r\n */\r\nexport function calculateAppreciatedValue(currentValue, appreciationRate = FINANCIAL_CONSTANTS.APPRECIATION_RATE, years = FINANCIAL_CONSTANTS.DEFAULT_BALLOON_PERIOD_YEARS) {\r\n try {\r\n if (currentValue <= 0 || appreciationRate < 0 || years < 0) {\r\n return currentValue;\r\n }\r\n \r\n return currentValue * Math.pow(1 + appreciationRate, years);\r\n } catch (error) {\r\n return currentValue;\r\n }\r\n}\r\n\r\n/**\r\n * Calculate cash out amount after appreciation refinance\r\n * @param {number} originalPrice - Original purchase price\r\n * @param {number} dscrLoanAmount - Original DSCR loan amount \r\n * @param {number} sellerFiAmount - Original seller financing amount\r\n * @param {Object} options - Configuration options\r\n * @returns {number} Cash out amount (positive = cash out, negative = cash in)\r\n */\r\nexport function calculateCashOutAfterRefi(originalPrice, dscrLoanAmount, sellerFiAmount, options = {}) {\r\n const {\r\n appreciationRate = FINANCIAL_CONSTANTS.APPRECIATION_RATE,\r\n balloonYears = FINANCIAL_CONSTANTS.DEFAULT_BALLOON_PERIOD_YEARS,\r\n dscrRate = DEFAULT_TIER.rate,\r\n dscrTerm = DEFAULT_TIER.amortization,\r\n sellerFiTerm = FINANCIAL_CONSTANTS.SELLER_FI_AMORTIZATION,\r\n refiLtvPercent = 70 // 70% LTV on refi\r\n } = options;\r\n\r\n try {\r\n // Calculate appreciated property value\r\n const appreciatedValue = calculateAppreciatedValue(originalPrice, appreciationRate, balloonYears);\r\n \r\n // Calculate remaining balance on DSCR loan\r\n const dscrRemainingBalance = calculateBalloonBalance(dscrLoanAmount, dscrRate, dscrTerm, balloonYears);\r\n \r\n // Calculate remaining balance on seller financing (0% interest)\r\n const sellerFiRemainingBalance = calculateBalloonBalance(sellerFiAmount, FINANCIAL_CONSTANTS.SELLER_FI_INTEREST_RATE, sellerFiTerm, balloonYears);\r\n \r\n // Total remaining debt\r\n const totalRemainingDebt = dscrRemainingBalance + sellerFiRemainingBalance;\r\n \r\n // Calculate new loan amount at 70% LTV of appreciated value\r\n const newLoanAmount = appreciatedValue * (refiLtvPercent / 100);\r\n \r\n // Cash out = new loan - total remaining debt\r\n const cashOut = newLoanAmount - totalRemainingDebt;\r\n \r\n return cashOut;\r\n } catch (error) {\r\n return 0;\r\n }\r\n}\r\n\r\n// Cash Flow calculation (matching loopnet-analyzer exactly)\r\nexport function calculateCashFlow(monthlyNOI, dscrPayment, sfPayment) {\r\n return monthlyNOI - (dscrPayment + sfPayment);\r\n}\r\n\r\n/**\r\n * Calculate discount percentage from asking price and offered price\r\n * @param {number} askingPrice - Property asking price\r\n * @param {number} priceOffered - Offered price\r\n * @returns {number} Discount as decimal (positive = discount, negative = premium)\r\n */\r\nexport function calculateDiscountFromPrice(askingPrice, priceOffered) {\r\n if (!askingPrice || askingPrice <= 0) return 0;\r\n \r\n return (askingPrice - priceOffered) / askingPrice;\r\n}\r\n\r\n/**\r\n * Calculate price from asking price and discount percentage\r\n * @param {number} askingPrice - Property asking price \r\n * @param {number} discountPercent - Discount as decimal (positive = discount, negative = premium)\r\n * @returns {number} Calculated price\r\n */\r\nexport function calculatePriceFromDiscount(askingPrice, discountPercent) {\r\n if (!askingPrice || askingPrice <= 0) return 0;\r\n \r\n return askingPrice * (1 - discountPercent);\r\n}\r\n\r\nexport function safePercentage(value, fallback = 100) {\r\n return (value != null && !isNaN(value)) ? (value * 100) : fallback;\r\n}"],"names":["DEFAULT_TIER","FINANCIAL_CONSTANTS","INTEREST_RATE_TIERS","DEFAULT_INTEREST_RATE_TYPE","calculatePMT","principal","annualRate","years","monthlyRate","numPayments","Math","pow","calculateCOCR30","askingPrice","noi","cashInvested","dscrPayment","error","calculateCashFlowYield","monthlyCashFlow","purchasePrice","equityPercentFromDebt","price","debtBalance","p","Number","d","isFinite","calculatePriceForCOCR","targetCOCR","options","downPercent","DEFAULT_DOWN_PAYMENT","dscrLtvPercent","DEFAULT_DSCR_PERCENTAGE","dscrRate","rate","dscrTerm","amortization","maxIterations","BUSINESS_CONSTANTS","MAX_ITERATIONS","tolerance","CALCULATION_TOLERANCE","targetPrice","iterations","currentCOCR","abs","adjustment","ADJUSTMENT_FACTOR","MAX_COCR15_PRICE_MULTIPLIER","CONSERVATIVE_COCR15_PRICE_MULTIPLIER","MINIMUM_COCR15_PRICE","calculateCOCRAtPercent","calculateSTRNOI","apiResult","grossRate","PROPERTY_TYPE_CONSTANTS","STR","ESTIMATED_GROSS_RATE","noiPercentage","NOI_PERCENTAGE","value","type","calculateNOIByType","capRate","propertyType","PROPERTY_TYPES","MULTIFAMILY","strApiResult","strGrossIncomeMultiplier","strNoiPercentage","assistedIncomePerBedroom","ASSISTED_LIVING","INCOME_PER_BEDROOM_MONTHLY","bedroomCount","DEFAULT_BEDROOM_COUNT","toLowerCase","resolveListingFinancials","confirmedNOI","estimatedCapRate","reportedCapRate","hasReported","capForNOI","measured","noiSource","activeCapRate","calculateAssignmentFee","assignmentPercent","ASSIGNMENT_FEE_PERCENTAGE","calculateCashOfferPrice","cocr15Price","CASH_OFFER_ASSIGNMENT_PERCENTAGE","roundingStep","CASH_OFFER_ROUNDING","haircutPrice","floor","round","calculateNetToBuyer","buyerCostPercent","NET_TO_BUYER_PERCENTAGE","sellerCostAssignment","sellerCostClosing","CLOSING_COSTS_PERCENTAGE","additionalCostRehab","REHAB_RATE","additionalCostFinancing","HARD_MONEY_RATE","calculateBalloonBalance","loanAmount","interestRate","amortizationYears","balloonYears","DEFAULT_BALLOON_PERIOD_YEARS","totalPayments","balloonPayments","factor1","remainingBalance","max","calculateAppreciatedValue","currentValue","appreciationRate","APPRECIATION_RATE","calculateCashOutAfterRefi","originalPrice","dscrLoanAmount","sellerFiAmount","sellerFiTerm","SELLER_FI_AMORTIZATION","refiLtvPercent","appreciatedValue","dscrRemainingBalance","sellerFiRemainingBalance","SELLER_FI_INTEREST_RATE","calculateCashFlow","monthlyNOI","sfPayment","calculateDiscountFromPrice","priceOffered","calculatePriceFromDiscount","discountPercent","safePercentage","fallback","isNaN"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOfferPrice,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,CASH_OFFER_ASSIGNMENT_PERCENTAGE,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,calculateCashOfferPrice,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,CASH_OFFER_ASSIGNMENT_PERCENTAGE,CASH_OFFER_ROUNDING,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 calculateCashOfferPrice,\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":"ijEAoEY,MAACA,EAAc"}
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 calculateCashOfferPrice,\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":"qkEAoEY,MAACA,EAAc"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archerjessop/utilities",
3
- "version": "7.28.0",
3
+ "version": "7.29.0",
4
4
  "description": "Shared utilities for ArcherJessop property analysis tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",