@archerjessop/utilities 4.6.9 → 4.6.11
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/config/business.js +1 -1
- package/dist/config/business.js.map +1 -1
- package/dist/config/financial.js +1 -1
- package/dist/config/financial.js.map +1 -1
- package/dist/financial/calculations.js +1 -1
- package/dist/financial/calculations.js.map +1 -1
- package/dist/formatting/financial-formatting.js +1 -1
- package/dist/formatting/financial-formatting.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/config/business.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const E={ASSIGNMENT_FEE_PERCENTAGE:.05,NET_TO_BUYER_PERCENTAGE:.1,CLOSING_COSTS_PERCENTAGE:.0125,
|
|
1
|
+
const E={ASSIGNMENT_FEE_PERCENTAGE:.05,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:"https://app.archerjessop.com/property-dashboard/import",EXCLUDED_EXPORT_VALUES:["Loading...","Not found","",null,void 0],DASHBOARD_URL_BASE:"https://app.archerjessop.com/property-dashboard/"},{ASSIGNMENT_FEE_PERCENTAGE:_,NET_TO_BUYER_PERCENTAGE:R,CLOSING_COSTS_PERCENTAGE:C,HARD_MONEY_RATE:T,SELLER_AGENT_COMMISSION:A,BUYER_AGENT_COMMISSION:I,MINIMUM_COCR15_PRICE:N,MAX_COCR15_PRICE_MULTIPLIER:O,CONSERVATIVE_COCR15_PRICE_MULTIPLIER:S,REHAB_RATE:M}=E;export{_ as ASSIGNMENT_FEE_PERCENTAGE,E as BUSINESS_CONSTANTS,I as BUYER_AGENT_COMMISSION,C as CLOSING_COSTS_PERCENTAGE,S as CONSERVATIVE_COCR15_PRICE_MULTIPLIER,T as HARD_MONEY_RATE,O as MAX_COCR15_PRICE_MULTIPLIER,N as MINIMUM_COCR15_PRICE,R as NET_TO_BUYER_PERCENTAGE,M as REHAB_RATE,A 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\nexport const BUSINESS_CONSTANTS = {\r\n // Assignment and transaction fees\r\n ASSIGNMENT_FEE_PERCENTAGE: 0.05, // 5% assignment fee\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
|
|
1
|
+
{"version":3,"file":"business.js","sources":["../../src/config/business.js"],"sourcesContent":["/**\r\n * Business logic and fee constants\r\n */\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 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\r\n EXPORT_URL_BASE: \"https://app.archerjessop.com/property-dashboard/import\",\r\n EXCLUDED_EXPORT_VALUES: [\"Loading...\", \"Not found\", \"\", null, undefined],\r\n\r\n DASHBOARD_URL_BASE: \"https://app.archerjessop.com/property-dashboard/\",\r\n \r\n};\r\n\r\n// Convenience exports for commonly used values\r\nexport const {\r\n ASSIGNMENT_FEE_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","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","EXCLUDED_EXPORT_VALUES","undefined","DASHBOARD_URL_BASE"],"mappings":"AAIY,MAACA,EAAqB,CAEhCC,0BAA2B,IAC3BC,wBAAyB,GACzBC,yBAA0B,MAC1BC,gBAAiB,IACjBC,WAAY,EAGZC,wBAAyB,KACzBC,uBAAwB,KAGxBC,qBAAsB,IACtBC,4BAA6B,GAC7BC,qCAAsC,GAMtCC,eAAgB,GAChBC,sBAAuB,KACvBC,kBAAmB,GAKnBC,gBAAiB,yDACjBC,uBAAwB,CAAC,aAAc,YAAa,GAAI,UAAMC,GAE9DC,mBAAoB,qDAKThB,0BACXA,EAAyBC,wBACzBA,EAAuBC,yBACvBA,EAAwBC,gBACxBA,EAAeE,wBACfA,EAAuBC,uBACvBA,EAAsBC,qBACtBA,EAAoBC,4BACpBA,EAA2BC,qCAC3BA,EAAoCL,WACpCA,GACEL"}
|
package/dist/config/financial.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const E={DSCR_INTEREST_RATE:.075,SELLER_FI_INTEREST_RATE:0,DSCR_AMORTIZATION:30,SELLER_FI_AMORTIZATION:30,DEFAULT_BALLOON_PERIOD_YEARS:7,DEFAULT_DOWN_PAYMENT:.3,SELLER_FI_DOWN_PAYMENT:.6,SELLER_FI_CARRY:.4,MIN_DOWN_PAYMENT_PERCENT:0,DEFAULT_EQUITY_ESTIMATE:.4,DEFAULT_DSCR_PERCENTAGE:.7,MAX_DSCR_PERCENTAGE:.7,DEFAULT_CAP_RATE:.05,MAX_ESTIMATED_CAP_RATE:
|
|
1
|
+
const E={DSCR_INTEREST_RATE:.075,SELLER_FI_INTEREST_RATE:0,DSCR_AMORTIZATION:30,SELLER_FI_AMORTIZATION:30,DEFAULT_BALLOON_PERIOD_YEARS:7,DEFAULT_DOWN_PAYMENT:.3,SELLER_FI_DOWN_PAYMENT:.6,SELLER_FI_CARRY:.4,MIN_DOWN_PAYMENT_PERCENT:0,DEFAULT_EQUITY_ESTIMATE:.4,DEFAULT_DSCR_PERCENTAGE:.7,MAX_DSCR_PERCENTAGE:.7,DEFAULT_CAP_RATE:.05,MAX_ESTIMATED_CAP_RATE:25,APPRECIATION_RATE:.045},{CALCULATION_TOLERANCE:_,DEFAULT_CAP_RATE:T,DEFAULT_DOWN_PAYMENT:A,DEFAULT_DSCR_PERCENTAGE:R,DEFAULT_EQUITY_ESTIMATE:I,DSCR_AMORTIZATION:L,DSCR_INTEREST_RATE:N,MAX_ITERATIONS:S,SELLER_FI_AMORTIZATION:D,SELLER_FI_CARRY:C,SELLER_FI_DOWN_PAYMENT:O,SELLER_FI_INTEREST_RATE:F}=E;export{_ as CALCULATION_TOLERANCE,T as DEFAULT_CAP_RATE,A as DEFAULT_DOWN_PAYMENT,R as DEFAULT_DSCR_PERCENTAGE,I as DEFAULT_EQUITY_ESTIMATE,L as DSCR_AMORTIZATION,N as DSCR_INTEREST_RATE,E as FINANCIAL_CONSTANTS,S as MAX_ITERATIONS,D as SELLER_FI_AMORTIZATION,C as SELLER_FI_CARRY,O as SELLER_FI_DOWN_PAYMENT,F as SELLER_FI_INTEREST_RATE};
|
|
2
2
|
//# sourceMappingURL=financial.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"financial.js","sources":["../../src/config/financial.js"],"sourcesContent":["/**\r\n * Financial calculation constants\r\n */\r\n\r\nexport const FINANCIAL_CONSTANTS = {\r\n // Interest rates\r\n DSCR_INTEREST_RATE: 0.075, // 7.5%\r\n SELLER_FI_INTEREST_RATE: 0.0, // 0% for seller financing\r\n \r\n // Loan terms and amortization\r\n DSCR_AMORTIZATION: 30, // 30 years\r\n SELLER_FI_AMORTIZATION: 30, // 30 years\r\n DEFAULT_BALLOON_PERIOD_YEARS: 7, // 7 years\r\n \r\n // Down payments and financing structure\r\n DEFAULT_DOWN_PAYMENT: 0.30, // 30%\r\n SELLER_FI_DOWN_PAYMENT: 0.60, // 60%\r\n SELLER_FI_CARRY: 0.40, // 40% seller financing\r\n MIN_DOWN_PAYMENT_PERCENT: 0, // 0% minimum down\r\n DEFAULT_EQUITY_ESTIMATE: 0.40, // 40% seller equity\r\n \r\n // DSCR loan percentages\r\n DEFAULT_DSCR_PERCENTAGE: 0.70, // 70%\r\n MAX_DSCR_PERCENTAGE: 0.70, // Maximum DSCR loan\r\n \r\n // Cap rates\r\n DEFAULT_CAP_RATE: 0.05, // 5% default cap rate\r\n MAX_ESTIMATED_CAP_RATE:
|
|
1
|
+
{"version":3,"file":"financial.js","sources":["../../src/config/financial.js"],"sourcesContent":["/**\r\n * Financial calculation constants\r\n */\r\n\r\nexport const FINANCIAL_CONSTANTS = {\r\n // Interest rates\r\n DSCR_INTEREST_RATE: 0.075, // 7.5%\r\n SELLER_FI_INTEREST_RATE: 0.0, // 0% for seller financing\r\n \r\n // Loan terms and amortization\r\n DSCR_AMORTIZATION: 30, // 30 years\r\n SELLER_FI_AMORTIZATION: 30, // 30 years\r\n DEFAULT_BALLOON_PERIOD_YEARS: 7, // 7 years\r\n \r\n // Down payments and financing structure\r\n DEFAULT_DOWN_PAYMENT: 0.30, // 30%\r\n SELLER_FI_DOWN_PAYMENT: 0.60, // 60%\r\n SELLER_FI_CARRY: 0.40, // 40% seller financing\r\n MIN_DOWN_PAYMENT_PERCENT: 0, // 0% minimum down\r\n DEFAULT_EQUITY_ESTIMATE: 0.40, // 40% seller equity\r\n \r\n // DSCR loan percentages\r\n DEFAULT_DSCR_PERCENTAGE: 0.70, // 70%\r\n MAX_DSCR_PERCENTAGE: 0.70, // Maximum DSCR loan\r\n \r\n // Cap rates\r\n DEFAULT_CAP_RATE: 0.05, // 5% default cap rate\r\n MAX_ESTIMATED_CAP_RATE: 25, // 20% maximum cap rate\r\n \r\n // Market assumptions\r\n APPRECIATION_RATE: 0.045, // 4.5% annual appreciation\r\n};\r\n\r\n// Convenience exports for common calculations\r\nexport const {\r\n CALCULATION_TOLERANCE,\r\n DEFAULT_CAP_RATE,\r\n DEFAULT_DOWN_PAYMENT,\r\n DEFAULT_DSCR_PERCENTAGE,\r\n DEFAULT_EQUITY_ESTIMATE,\r\n DSCR_AMORTIZATION,\r\n DSCR_INTEREST_RATE,\r\n MAX_ITERATIONS,\r\n SELLER_FI_AMORTIZATION,\r\n SELLER_FI_CARRY,\r\n SELLER_FI_DOWN_PAYMENT,\r\n SELLER_FI_INTEREST_RATE,\r\n} = FINANCIAL_CONSTANTS;\r\n"],"names":["FINANCIAL_CONSTANTS","DSCR_INTEREST_RATE","SELLER_FI_INTEREST_RATE","DSCR_AMORTIZATION","SELLER_FI_AMORTIZATION","DEFAULT_BALLOON_PERIOD_YEARS","DEFAULT_DOWN_PAYMENT","SELLER_FI_DOWN_PAYMENT","SELLER_FI_CARRY","MIN_DOWN_PAYMENT_PERCENT","DEFAULT_EQUITY_ESTIMATE","DEFAULT_DSCR_PERCENTAGE","MAX_DSCR_PERCENTAGE","DEFAULT_CAP_RATE","MAX_ESTIMATED_CAP_RATE","APPRECIATION_RATE","CALCULATION_TOLERANCE","MAX_ITERATIONS"],"mappings":"AAIY,MAACA,EAAsB,CAEjCC,mBAAoB,KACpBC,wBAAyB,EAGzBC,kBAAmB,GACnBC,uBAAwB,GACxBC,6BAA8B,EAG9BC,qBAAsB,GACtBC,uBAAwB,GACxBC,gBAAiB,GACjBC,yBAA0B,EAC1BC,wBAAyB,GAGzBC,wBAAyB,GACzBC,oBAAqB,GAGrBC,iBAAkB,IAClBC,uBAAwB,GAGxBC,kBAAmB,OAIRC,sBACXA,EAAqBH,iBACrBA,EAAgBP,qBAChBA,EAAoBK,wBACpBA,EAAuBD,wBACvBA,EAAuBP,kBACvBA,EAAiBF,mBACjBA,EAAkBgB,eAClBA,EAAcb,uBACdA,EAAsBI,gBACtBA,EAAeD,uBACfA,EAAsBL,wBACtBA,GACEF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{FINANCIAL_CONSTANTS as t}from"../config/financial.js";import{BUSINESS_CONSTANTS as e}from"../config/business.js";import{PROPERTY_TYPES as c,PROPERTY_TYPE_CONSTANTS as a}from"../config/property-types.js";function calculatePMT(t,e,c){if(0===e)return t/(12*c);const a=e/12,r=12*c;return t*(a*Math.pow(1+a,r))/(Math.pow(1+a,r)-1)}function calculateCOCR30(t,e){try{const c=.3*t,a=12*calculatePMT(.7*t,.075,30);return(e-a)/c*100}catch(t){return 0}}function calculateCashFlowYield(t,e){if(!e||e<=0)return 0;return 12*t/e*100}function calculatePriceForCOCR(c,a=.15,r={}){const{downPercent:n=100*t.DEFAULT_DOWN_PAYMENT,dscrLtvPercent:l=100*t.DEFAULT_DSCR_PERCENTAGE,dscrRate:u=t.DSCR_INTEREST_RATE,dscrTerm:o=t.DSCR_AMORTIZATION,maxIterations:E=e.MAX_ITERATIONS,tolerance:T=e.CALCULATION_TOLERANCE}=r;try{let t=c/.08,r=0;for(;r<E;){const E=t*(n/100),R=12*calculatePMT(t*(l/100),u,o),s=(c-R)/E;if(Math.abs(s-a)<T)break;const A=s-a,i=A*e.ADJUSTMENT_FACTOR;t*=A>0?1+Math.abs(i):1-Math.abs(i),t<1e3&&(t=1e3),t>c*e.MAX_COCR15_PRICE_MULTIPLIER&&(t=c*e.CONSERVATIVE_COCR15_PRICE_MULTIPLIER),r++}return t<e.MINIMUM_COCR15_PRICE&&(t=e.MINIMUM_COCR15_PRICE),t}catch(t){return 0}}function calculateCOCRAtPercent(e,c,a,r={}){const{dscrRate:n=t.DSCR_INTEREST_RATE,dscrTerm:l=t.DSCR_AMORTIZATION}=r;try{const t=e*(a/100),r=12*calculatePMT(e-t,n,l);return(c-r)/t*100}catch(t){return 0}}function calculateNOIByType(t,e,r=c.MULTIFAMILY,n={}){const{strGrossIncomeMultiplier:l=a.STR.ESTIMATED_GROSS_RATE,strNoiPercentage:u=a.STR.NOI_PERCENTAGE,assistedIncomePerBedroom:o=a.ASSISTED_LIVING.INCOME_PER_BEDROOM_MONTHLY,bedroomCount:E=a.ASSISTED_LIVING.DEFAULT_BEDROOM_COUNT}=n;try{switch(r.toLowerCase()){case c.STR:return t*l*u;case c.ASSISTED_LIVING:return E*o*12;case c.MULTIFAMILY:default:return t*e}}catch(t){return 0}}function calculateAssignmentFee(t,c=100*e.ASSIGNMENT_FEE_PERCENTAGE){try{return t*(c/100)}catch(t){return 0}}function calculateNetToBuyer(c,a={}){const{buyerCostPercent:r=100*e.NET_TO_BUYER_PERCENTAGE,sellerCostAssignment:n=100*e.ASSIGNMENT_FEE_PERCENTAGE,sellerCostClosing:l=100*e.CLOSING_COSTS_PERCENTAGE,additionalCostRehab:u=100*e.
|
|
1
|
+
import{FINANCIAL_CONSTANTS as t}from"../config/financial.js";import{BUSINESS_CONSTANTS as e}from"../config/business.js";import{PROPERTY_TYPES as c,PROPERTY_TYPE_CONSTANTS as a}from"../config/property-types.js";function calculatePMT(t,e,c){if(0===e)return t/(12*c);const a=e/12,r=12*c;return t*(a*Math.pow(1+a,r))/(Math.pow(1+a,r)-1)}function calculateCOCR30(t,e){try{const c=.3*t,a=12*calculatePMT(.7*t,.075,30);return(e-a)/c*100}catch(t){return 0}}function calculateCashFlowYield(t,e){if(!e||e<=0)return 0;return 12*t/e*100}function calculatePriceForCOCR(c,a=.15,r={}){const{downPercent:n=100*t.DEFAULT_DOWN_PAYMENT,dscrLtvPercent:l=100*t.DEFAULT_DSCR_PERCENTAGE,dscrRate:u=t.DSCR_INTEREST_RATE,dscrTerm:o=t.DSCR_AMORTIZATION,maxIterations:E=e.MAX_ITERATIONS,tolerance:T=e.CALCULATION_TOLERANCE}=r;try{let t=c/.08,r=0;for(;r<E;){const E=t*(n/100),R=12*calculatePMT(t*(l/100),u,o),s=(c-R)/E;if(Math.abs(s-a)<T)break;const A=s-a,i=A*e.ADJUSTMENT_FACTOR;t*=A>0?1+Math.abs(i):1-Math.abs(i),t<1e3&&(t=1e3),t>c*e.MAX_COCR15_PRICE_MULTIPLIER&&(t=c*e.CONSERVATIVE_COCR15_PRICE_MULTIPLIER),r++}return t<e.MINIMUM_COCR15_PRICE&&(t=e.MINIMUM_COCR15_PRICE),t}catch(t){return 0}}function calculateCOCRAtPercent(e,c,a,r={}){const{dscrRate:n=t.DSCR_INTEREST_RATE,dscrTerm:l=t.DSCR_AMORTIZATION}=r;try{const t=e*(a/100),r=12*calculatePMT(e-t,n,l);return(c-r)/t*100}catch(t){return 0}}function calculateNOIByType(t,e,r=c.MULTIFAMILY,n={}){const{strGrossIncomeMultiplier:l=a.STR.ESTIMATED_GROSS_RATE,strNoiPercentage:u=a.STR.NOI_PERCENTAGE,assistedIncomePerBedroom:o=a.ASSISTED_LIVING.INCOME_PER_BEDROOM_MONTHLY,bedroomCount:E=a.ASSISTED_LIVING.DEFAULT_BEDROOM_COUNT}=n;try{switch(r.toLowerCase()){case c.STR:return t*l*u;case c.ASSISTED_LIVING:return E*o*12;case c.MULTIFAMILY:default:return t*e}}catch(t){return 0}}function calculateAssignmentFee(t,c=100*e.ASSIGNMENT_FEE_PERCENTAGE){try{return t*(c/100)}catch(t){return 0}}function calculateNetToBuyer(c,a={}){const{buyerCostPercent:r=100*e.NET_TO_BUYER_PERCENTAGE,sellerCostAssignment:n=100*e.ASSIGNMENT_FEE_PERCENTAGE,sellerCostClosing:l=100*e.CLOSING_COSTS_PERCENTAGE,additionalCostRehab:u=100*e.REHAB_RATE,additionalCostFinancing:o=100*e.HARD_MONEY_RATE,dscrLtvPercent:E=100*t.DEFAULT_DSCR_PERCENTAGE}=a;try{return c*(r/100)-c*((n+l)/100)-c*(u/100)-o/100*(c-c*(E/100))}catch(t){return 0}}function calculateBalloonBalance(e,c,a,r=t.DEFAULT_BALLOON_PERIOD_YEARS){try{if(e<=0||c<0||a<=0||r<=0)return 0;if(r>=a)return 0;if(0===c){const t=12*a;return e*(t-12*r)/t}const t=c/12,n=12*a,l=12*r,u=Math.pow(1+t,n),o=e*(u-Math.pow(1+t,l))/(u-1);return Math.max(0,o)}catch(t){return 0}}function calculateAppreciatedValue(e,c=t.APPRECIATION_RATE,a=t.DEFAULT_BALLOON_PERIOD_YEARS){try{return e<=0||c<0||a<0?e:e*Math.pow(1+c,a)}catch(t){return e}}function calculateCashOutAfterRefi(e,c,a,r={}){const{appreciationRate:n=t.APPRECIATION_RATE,balloonYears:l=t.DEFAULT_BALLOON_PERIOD_YEARS,dscrRate:u=t.DSCR_INTEREST_RATE,dscrTerm:o=t.DSCR_AMORTIZATION,sellerFiTerm:E=t.SELLER_FI_AMORTIZATION,refiLtvPercent:T=70}=r;try{const r=calculateAppreciatedValue(e,n,l),R=calculateBalloonBalance(c,u,o,l),s=calculateBalloonBalance(a,t.SELLER_FI_INTEREST_RATE,E,l);return r*(T/100)-(R+s)}catch(t){return 0}}function calculateDscrPayment(t,e,c,a){return calculatePMT(t*(e/100),c,a)}function calculateSfPayment(t,e,c,a){return calculatePMT(t*(e/100),c,a)}function calculateCashFlow(t,e,c){return t-(e+c)}function calculateDiscountFromPrice(t,e){return!t||t<=0?0:(t-e)/t}function calculatePriceFromDiscount(t,e){return!t||t<=0?0:t*(1-e)}function safePercentage(t,e=100){return null==t||isNaN(t)?e:100*t}export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateDscrPayment,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSfPayment,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\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/**\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 = FINANCIAL_CONSTANTS.DSCR_INTEREST_RATE,\r\n dscrTerm = FINANCIAL_CONSTANTS.DSCR_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 = FINANCIAL_CONSTANTS.DSCR_INTEREST_RATE,\r\n dscrTerm = FINANCIAL_CONSTANTS.DSCR_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 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 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.toLowerCase()) {\r\n case PROPERTY_TYPES.STR:\r\n const estimatedGrossIncome = askingPrice * strGrossIncomeMultiplier;\r\n return estimatedGrossIncome * strNoiPercentage;\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 * 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 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_PERCENTAGE * 100,\r\n additionalCostFinancing = BUSINESS_CONSTANTS.FINANCING_FEE_PERCENTAGE * 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 = FINANCIAL_CONSTANTS.DSCR_INTEREST_RATE,\r\n dscrTerm = FINANCIAL_CONSTANTS.DSCR_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// DSCR Payment calculation using existing calculatePMT\r\nexport function calculateDscrPayment(askingPrice, dscrPercent, dscrRate, dscrAmortization) {\r\n const dscrLoanAmount = askingPrice * (dscrPercent / 100);\r\n return calculatePMT(dscrLoanAmount, dscrRate, dscrAmortization);\r\n}\r\n\r\n// SF Payment calculation using existing calculatePMT \r\nexport function calculateSfPayment(askingPrice, sellerFiPercent, sellerFiRate, sellerFiAmortization) {\r\n const sellerFiAmount = askingPrice * (sellerFiPercent / 100);\r\n return calculatePMT(sellerFiAmount, sellerFiRate, sellerFiAmortization);\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":["calculatePMT","principal","annualRate","years","monthlyRate","numPayments","Math","pow","calculateCOCR30","askingPrice","noi","cashInvested","dscrPayment","error","calculateCashFlowYield","monthlyCashFlow","purchasePrice","calculatePriceForCOCR","targetCOCR","options","downPercent","FINANCIAL_CONSTANTS","DEFAULT_DOWN_PAYMENT","dscrLtvPercent","DEFAULT_DSCR_PERCENTAGE","dscrRate","DSCR_INTEREST_RATE","dscrTerm","DSCR_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","calculateNOIByType","capRate","propertyType","PROPERTY_TYPES","MULTIFAMILY","strGrossIncomeMultiplier","PROPERTY_TYPE_CONSTANTS","STR","ESTIMATED_GROSS_RATE","strNoiPercentage","NOI_PERCENTAGE","assistedIncomePerBedroom","ASSISTED_LIVING","INCOME_PER_BEDROOM_MONTHLY","bedroomCount","DEFAULT_BEDROOM_COUNT","toLowerCase","calculateAssignmentFee","assignmentPercent","ASSIGNMENT_FEE_PERCENTAGE","calculateNetToBuyer","buyerCostPercent","NET_TO_BUYER_PERCENTAGE","sellerCostAssignment","sellerCostClosing","CLOSING_COSTS_PERCENTAGE","additionalCostRehab","REHAB_PERCENTAGE","additionalCostFinancing","FINANCING_FEE_PERCENTAGE","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","calculateDscrPayment","dscrPercent","dscrAmortization","calculateSfPayment","sellerFiPercent","sellerFiRate","sellerFiAmortization","calculateCashFlow","monthlyNOI","sfPayment","calculateDiscountFromPrice","priceOffered","calculatePriceFromDiscount","discountPercent","safePercentage","value","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\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/**\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 = FINANCIAL_CONSTANTS.DSCR_INTEREST_RATE,\r\n dscrTerm = FINANCIAL_CONSTANTS.DSCR_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 = FINANCIAL_CONSTANTS.DSCR_INTEREST_RATE,\r\n dscrTerm = FINANCIAL_CONSTANTS.DSCR_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 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 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.toLowerCase()) {\r\n case PROPERTY_TYPES.STR:\r\n const estimatedGrossIncome = askingPrice * strGrossIncomeMultiplier;\r\n return estimatedGrossIncome * strNoiPercentage;\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 * 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 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 = FINANCIAL_CONSTANTS.DSCR_INTEREST_RATE,\r\n dscrTerm = FINANCIAL_CONSTANTS.DSCR_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// DSCR Payment calculation using existing calculatePMT\r\nexport function calculateDscrPayment(askingPrice, dscrPercent, dscrRate, dscrAmortization) {\r\n const dscrLoanAmount = askingPrice * (dscrPercent / 100);\r\n return calculatePMT(dscrLoanAmount, dscrRate, dscrAmortization);\r\n}\r\n\r\n// SF Payment calculation using existing calculatePMT \r\nexport function calculateSfPayment(askingPrice, sellerFiPercent, sellerFiRate, sellerFiAmortization) {\r\n const sellerFiAmount = askingPrice * (sellerFiPercent / 100);\r\n return calculatePMT(sellerFiAmount, sellerFiRate, sellerFiAmortization);\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":["calculatePMT","principal","annualRate","years","monthlyRate","numPayments","Math","pow","calculateCOCR30","askingPrice","noi","cashInvested","dscrPayment","error","calculateCashFlowYield","monthlyCashFlow","purchasePrice","calculatePriceForCOCR","targetCOCR","options","downPercent","FINANCIAL_CONSTANTS","DEFAULT_DOWN_PAYMENT","dscrLtvPercent","DEFAULT_DSCR_PERCENTAGE","dscrRate","DSCR_INTEREST_RATE","dscrTerm","DSCR_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","calculateNOIByType","capRate","propertyType","PROPERTY_TYPES","MULTIFAMILY","strGrossIncomeMultiplier","PROPERTY_TYPE_CONSTANTS","STR","ESTIMATED_GROSS_RATE","strNoiPercentage","NOI_PERCENTAGE","assistedIncomePerBedroom","ASSISTED_LIVING","INCOME_PER_BEDROOM_MONTHLY","bedroomCount","DEFAULT_BEDROOM_COUNT","toLowerCase","calculateAssignmentFee","assignmentPercent","ASSIGNMENT_FEE_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","calculateDscrPayment","dscrPercent","dscrAmortization","calculateSfPayment","sellerFiPercent","sellerFiRate","sellerFiAmortization","calculateCashFlow","monthlyNOI","sfPayment","calculateDiscountFromPrice","priceOffered","calculatePriceFromDiscount","discountPercent","safePercentage","value","fallback","isNaN"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const formatInputDisplay=(t,e)=>{const r=parseFloat(t)||0,n=r%1!=0;switch(e){case"currency":return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:n?2:0,maximumFractionDigits:2}).format(r);case"percent":return new Intl.NumberFormat("en-US",{minimumFractionDigits:n&&Math.abs(r)>=.1?1:n?3:0,maximumFractionDigits:Math.abs(r)<1?3:2}).format(r)+"%";case"years":return new Intl.NumberFormat("en-US",{minimumFractionDigits:n?1:0,maximumFractionDigits:1}).format(r)+" yrs.";case"months":return new Intl.NumberFormat("en-US",{minimumFractionDigits:0,maximumFractionDigits:0}).format(r)+" mos.";case"number":return new Intl.NumberFormat("en-US",{minimumFractionDigits:n?2:0,maximumFractionDigits:2}).format(r);default:return t}},parseNumericInput=t=>{if(null==t)return 0;const e=t.toString().replace(/[^0-9.-]/g,"");return parseFloat(e)||0},filterNumericInput=(t,e=!0)=>{if(!t)return"";let r=t.toString();if(r=r.replace(/[^0-9.-]/g,""),e){if((r.match(/-/g)||[]).length>1){const e=t.toString().trim().startsWith("-");r=r.replace(/-/g,""),e&&(r="-"+r)}else r.includes("-")&&!r.startsWith("-")&&(r="-"+r.replace(/-/g,""))}else r=r.replace(/-/g,"");if((r.match(/\./g)||[]).length>1){const t=r.split(".");r=t[0]+"."+t.slice(1).join("")}return r},formatLiveNumber=(t,e="default",r=!1)=>{if(!t&&0!==t)return"";const n=t.toString(),
|
|
1
|
+
const formatInputDisplay=(t,e)=>{const r=parseFloat(t)||0,n=r%1!=0;switch(e){case"currency":return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:n?2:0,maximumFractionDigits:2}).format(r);case"percent":return new Intl.NumberFormat("en-US",{minimumFractionDigits:n&&Math.abs(r)>=.1?1:n?3:0,maximumFractionDigits:Math.abs(r)<1?3:2}).format(r)+"%";case"years":return new Intl.NumberFormat("en-US",{minimumFractionDigits:n?1:0,maximumFractionDigits:1}).format(r)+" yrs.";case"months":return new Intl.NumberFormat("en-US",{minimumFractionDigits:0,maximumFractionDigits:0}).format(r)+" mos.";case"number":return new Intl.NumberFormat("en-US",{minimumFractionDigits:n?2:0,maximumFractionDigits:2}).format(r);default:return t}},parseNumericInput=t=>{if(null==t)return 0;const e=t.toString().replace(/[^0-9.-]/g,"");return parseFloat(e)||0},filterNumericInput=(t,e=!0)=>{if(!t)return"";let r=t.toString();if(r=r.replace(/[^0-9.-]/g,""),e){if((r.match(/-/g)||[]).length>1){const e=t.toString().trim().startsWith("-");r=r.replace(/-/g,""),e&&(r="-"+r)}else r.includes("-")&&!r.startsWith("-")&&(r="-"+r.replace(/-/g,""))}else r=r.replace(/-/g,"");if((r.match(/\./g)||[]).length>1){const t=r.split(".");r=t[0]+"."+t.slice(1).join("")}return r},formatLiveNumber=(t,e="default",r=!1)=>{if(!t&&0!==t)return"";const n=t.toString(),s=parseFloat(n);if(isNaN(s))return"";const a=s<0,i=a?n.substring(1):n;let c;if(i.includes("."))if(r){const[t,e]=i.split(".");c=t.replace(/\B(?=(\d{3})+(?!\d))/g,",")+"."+e}else if("months"===e){c=Math.round(Math.abs(s)).toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")}else if("percent"===e||"years"===e){let t=Math.abs(s).toFixed(2);t=t.replace(/\.?0+$/,"");const[e,r]=t.split("."),n=e.replace(/\B(?=(\d{3})+(?!\d))/g,",");c=r?n+"."+r:n}else{const t=Math.abs(s).toFixed(2),[e,r]=t.split(".");c=e.replace(/\B(?=(\d{3})+(?!\d))/g,",")+"."+r}else{c=Math.floor(Math.abs(s)).toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")}return a?"-"+c:c},formatLiveInput=(t,e,r=!1)=>{const n=filterNumericInput(t,!0);let s=formatLiveNumber(n,e,r);switch(""!==s&&null!=s||(s="0"),e){case"currency":return"$ "+s;case"percent":return s+" %";case"years":return s+" yrs.";case"months":return s+" mos.";default:return s}},calculateCursorPosition=(t,e,r,n)=>{if(r<=0)return 0;if(r>=t.length){if("currency"===n)return e.length;{const t=e.match(/^[0-9,.\-\s]+/);return t?t[0].length:0}}const getPrefixLength=(t,e)=>"currency"===e&&t.startsWith("$ ")?2:0,getContentEnd=(t,e,r)=>{const n=t.substring(r);if("percent"===e){const t=n.match(/^([0-9,.\-]+)/);return t?t[1].length:n.length}if("years"===e){const t=n.match(/^([0-9,.\-]+)/);return t?t[1].length:n.length}if("months"===e){const t=n.match(/^([0-9,.\-]+)/);return t?t[1].length:n.length}return n.length},s=getPrefixLength(t,n),a=getPrefixLength(e,n),i=getContentEnd(t,n,s),c=getContentEnd(e,n,a),l=Math.max(0,r-s),o=Math.min(l,i),m=t.substring(s,s+i);let u=0;for(let t=0;t<o;t++){const e=m[t];/[0-9.\-]/.test(e)&&u++}const g=e.substring(a,a+c);let h=0,f=0;for(let t=0;t<g.length;t++){const e=g[t];if(/[0-9.\-]/.test(e)&&(f++,f===u)){h=t+1<g.length&&","===g[t+1]?t+2:t+1;break}}0===u&&(h=0);const p=a+h,F=a+c;return Math.min(p,F)},extractNumericValue=(t,e)=>{if(!t)return 0;let r=t.toString();switch(e){case"currency":r=r.replace(/^\$\s*/,"").trim();break;case"percent":r=r.replace(/\s*%$/,"").trim();break;case"years":r=r.replace(/\s*yrs\.$/,"").trim();break;case"months":r=r.replace(/\s*mos\.$/,"").trim()}return r=r.replace(/,/g,""),parseFloat(r)||0};export{calculateCursorPosition,extractNumericValue,filterNumericInput,formatInputDisplay,formatLiveInput,formatLiveNumber,parseNumericInput};
|
|
2
2
|
//# sourceMappingURL=financial-formatting.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"financial-formatting.js","sources":["../../src/formatting/financial-formatting.js"],"sourcesContent":["// Enhanced formatting for input fields\r\nexport const formatInputDisplay = (value, type) => {\r\n const num = parseFloat(value) || 0;\r\n const hasDecimals = num % 1 !== 0;\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: \"USD\",\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n case \"percent\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals && Math.abs(num) >= 0.1 ? 1 : hasDecimals ? 3 : 0,\r\n maximumFractionDigits: Math.abs(num) < 1 ? 3 : 2\r\n }).format(num) + \"%\";\r\n \r\n case \"years\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 1 : 0,\r\n maximumFractionDigits: 1\r\n }).format(num) + \" yrs.\";\r\n \r\n case \"months\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 0\r\n }).format(num) + \" mos.\";\r\n \r\n case \"number\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n default:\r\n return value;\r\n }\r\n};\r\n\r\nexport const parseNumericInput = (value) => {\r\n // Handle null, undefined, or non-string inputs\r\n if (value === null || value === undefined) {\r\n return 0;\r\n }\r\n \r\n // Remove all non-numeric characters except decimal point and negative sign\r\n const cleaned = value.toString().replace(/[^0-9.-]/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};\r\n\r\n// Add these functions to the existing file:\r\n\r\nexport const filterNumericInput = (value, allowNegative = true) => {\r\n if (!value) return \"\";\r\n \r\n let filtered = value.toString();\r\n \r\n // Remove symbols and text, keep only numbers, decimal, and negative\r\n // This regex keeps numeric chars, decimal point, and negative sign\r\n filtered = filtered.replace(/[^0-9.-]/g, \"\");\r\n \r\n // Handle negative sign - only allow at the beginning\r\n if (allowNegative) {\r\n const negativeCount = (filtered.match(/-/g) || []).length;\r\n if (negativeCount > 1) {\r\n // Remove extra negative signs, keep only the first one if input started with -\r\n const startsWithNegative = value.toString().trim().startsWith(\"-\");\r\n filtered = filtered.replace(/-/g, \"\");\r\n if (startsWithNegative) {\r\n filtered = \"-\" + filtered;\r\n }\r\n } else if (filtered.includes(\"-\") && !filtered.startsWith(\"-\")) {\r\n // Move negative to the front if it's not already there\r\n filtered = \"-\" + filtered.replace(/-/g, \"\");\r\n }\r\n } else {\r\n filtered = filtered.replace(/-/g, \"\");\r\n }\r\n \r\n // Handle decimal - only allow one decimal point\r\n const decimalCount = (filtered.match(/\\./g) || []).length;\r\n if (decimalCount > 1) {\r\n const parts = filtered.split(\".\");\r\n filtered = parts[0] + \".\" + parts.slice(1).join(\"\");\r\n }\r\n \r\n return filtered;\r\n};\r\n\r\nexport const formatLiveNumber = (value, type = \"default\", preserveTyping = false) => {\r\n if (!value && value !== 0) return \"\";\r\n \r\n const originalStr = value.toString();\r\n const num = parseFloat(originalStr);\r\n if (isNaN(num)) return \"\";\r\n \r\n const isNegative = num < 0;\r\n const absStr = isNegative ? originalStr.substring(1) : originalStr;\r\n const hasDecimal = absStr.includes(\".\");\r\n \r\n let result;\r\n \r\n if (!hasDecimal) {\r\n // No decimal point - format as integer\r\n const intStr = Math.floor(Math.abs(num)).toString();\r\n result = intStr.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else {\r\n // Has decimal point\r\n if (preserveTyping) {\r\n // LIVE TYPING: Preserve exactly what user typed, just add commas\r\n const [intPart, decPart] = absStr.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n } else {\r\n // FINAL FORMATTING: Apply type-specific decimal rules\r\n if (type === \"months\") {\r\n const rounded = Math.round(Math.abs(num));\r\n result = rounded.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else if (type === \"percent\" || type === \"years\") {\r\n let formatted = Math.abs(num).toFixed(2);\r\n formatted = formatted.replace(/\\.?0+$/, '');\r\n const [intPart, decPart] = formatted.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = decPart ? commaInt + \".\" + decPart : commaInt;\r\n } else {\r\n // Currency: always 2 decimal places\r\n const twoDecimal = Math.abs(num).toFixed(2);\r\n const [intPart, decPart] = twoDecimal.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n }\r\n }\r\n }\r\n \r\n return isNegative ? \"-\" + result : result;\r\n};\r\n\r\nexport const formatLiveInput = (value, type, preserveTyping = false) => {\r\n const filtered = filterNumericInput(value, true);\r\n let formatted = formatLiveNumber(filtered, type, preserveTyping);\r\n \r\n if (formatted === \"\" || formatted === null || formatted === undefined) formatted = \"0\";\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return \"$ \" + formatted;\r\n case \"percent\":\r\n return formatted + \" %\";\r\n case \"years\":\r\n return formatted + \" yrs.\";\r\n case \"months\":\r\n return formatted + \" mos.\";\r\n case \"number\":\r\n return formatted;\r\n default:\r\n return formatted;\r\n }\r\n};\r\n\r\nexport const calculateCursorPosition = (oldValue, newValue, oldCursor, type) => {\r\n if (oldCursor <= 0) return 0;\r\n if (oldCursor >= oldValue.length) {\r\n // Cursor was at end - handle based on format type\r\n if (type === \"currency\") {\r\n // Currency: cursor can go to the very end\r\n return newValue.length;\r\n } else {\r\n // Percentage, years, months: cursor stays before suffix symbols\r\n const match = newValue.match(/^[0-9,.-]+/);\r\n return match ? match[0].length : 0;\r\n }\r\n }\r\n \r\n // Count logical characters before cursor position in old value\r\n let logicalCharsBefore = 0;\r\n for (let i = 0; i < oldCursor; i++) {\r\n if (oldValue[i] !== ',') {\r\n logicalCharsBefore++;\r\n }\r\n }\r\n \r\n // Find position after the same number of logical characters in new value\r\n let charsProcessed = 0;\r\n \r\n for (let i = 0; i < newValue.length; i++) {\r\n // For non-currency types, stop before suffix symbols\r\n if (type !== \"currency\" && /[a-zA-Z]/.test(newValue[i])) {\r\n break;\r\n }\r\n \r\n // If we've found all the logical characters we need\r\n if (charsProcessed === logicalCharsBefore) {\r\n return newValue[i] === ',' ? i + 1 : i;\r\n }\r\n \r\n // Count non-comma, non-space characters (but for currency, allow $ in count)\r\n if (newValue[i] !== ',' && newValue[i] !== ' ') {\r\n charsProcessed++;\r\n }\r\n }\r\n \r\n // Fallback: find appropriate end position based on type\r\n if (type === \"currency\") {\r\n return newValue.length;\r\n } else {\r\n const match = newValue.match(/^[0-9,.\\s-]+/);\r\n return match ? Math.min(match[0].length, newValue.length) : 0;\r\n }\r\n};\r\n\r\nexport const extractNumericValue = (formattedValue, type) => {\r\n if (!formattedValue) return 0;\r\n \r\n let cleaned = formattedValue.toString();\r\n \r\n switch (type) {\r\n case \"currency\":\r\n cleaned = cleaned.replace(/^\\$\\s*/, \"\").trim();\r\n break;\r\n case \"percent\":\r\n cleaned = cleaned.replace(/\\s*%$/, \"\").trim();\r\n break;\r\n case \"years\":\r\n cleaned = cleaned.replace(/\\s*yrs\\.$/, \"\").trim();\r\n break;\r\n case \"months\":\r\n cleaned = cleaned.replace(/\\s*mos\\.$/, \"\").trim();\r\n break;\r\n }\r\n \r\n cleaned = cleaned.replace(/,/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};"],"names":["formatInputDisplay","value","type","num","parseFloat","hasDecimals","Intl","NumberFormat","style","currency","minimumFractionDigits","maximumFractionDigits","format","Math","abs","parseNumericInput","cleaned","toString","replace","filterNumericInput","allowNegative","filtered","match","length","startsWithNegative","trim","startsWith","includes","parts","split","slice","join","formatLiveNumber","preserveTyping","originalStr","isNaN","isNegative","absStr","substring","result","intPart","decPart","round","formatted","toFixed","commaInt","twoDecimal","floor","formatLiveInput","calculateCursorPosition","oldValue","newValue","oldCursor","logicalCharsBefore","i","charsProcessed","test","min","extractNumericValue","formattedValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"financial-formatting.js","sources":["../../src/formatting/financial-formatting.js"],"sourcesContent":["// Enhanced formatting for input fields\r\nexport const formatInputDisplay = (value, type) => {\r\n const num = parseFloat(value) || 0;\r\n const hasDecimals = num % 1 !== 0;\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: \"USD\",\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n case \"percent\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals && Math.abs(num) >= 0.1 ? 1 : hasDecimals ? 3 : 0,\r\n maximumFractionDigits: Math.abs(num) < 1 ? 3 : 2\r\n }).format(num) + \"%\";\r\n \r\n case \"years\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 1 : 0,\r\n maximumFractionDigits: 1\r\n }).format(num) + \" yrs.\";\r\n \r\n case \"months\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 0\r\n }).format(num) + \" mos.\";\r\n \r\n case \"number\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n default:\r\n return value;\r\n }\r\n};\r\n\r\nexport const parseNumericInput = (value) => {\r\n // Handle null, undefined, or non-string inputs\r\n if (value === null || value === undefined) {\r\n return 0;\r\n }\r\n \r\n // Remove all non-numeric characters except decimal point and negative sign\r\n const cleaned = value.toString().replace(/[^0-9.-]/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};\r\n\r\n// Add these functions to the existing file:\r\n\r\nexport const filterNumericInput = (value, allowNegative = true) => {\r\n if (!value) return \"\";\r\n \r\n let filtered = value.toString();\r\n \r\n // Remove symbols and text, keep only numbers, decimal, and negative\r\n // This regex keeps numeric chars, decimal point, and negative sign\r\n filtered = filtered.replace(/[^0-9.-]/g, \"\");\r\n \r\n // Handle negative sign - only allow at the beginning\r\n if (allowNegative) {\r\n const negativeCount = (filtered.match(/-/g) || []).length;\r\n if (negativeCount > 1) {\r\n // Remove extra negative signs, keep only the first one if input started with -\r\n const startsWithNegative = value.toString().trim().startsWith(\"-\");\r\n filtered = filtered.replace(/-/g, \"\");\r\n if (startsWithNegative) {\r\n filtered = \"-\" + filtered;\r\n }\r\n } else if (filtered.includes(\"-\") && !filtered.startsWith(\"-\")) {\r\n // Move negative to the front if it's not already there\r\n filtered = \"-\" + filtered.replace(/-/g, \"\");\r\n }\r\n } else {\r\n filtered = filtered.replace(/-/g, \"\");\r\n }\r\n \r\n // Handle decimal - only allow one decimal point\r\n const decimalCount = (filtered.match(/\\./g) || []).length;\r\n if (decimalCount > 1) {\r\n const parts = filtered.split(\".\");\r\n filtered = parts[0] + \".\" + parts.slice(1).join(\"\");\r\n }\r\n \r\n return filtered;\r\n};\r\n\r\nexport const formatLiveNumber = (value, type = \"default\", preserveTyping = false) => {\r\n if (!value && value !== 0) return \"\";\r\n \r\n const originalStr = value.toString();\r\n const num = parseFloat(originalStr);\r\n if (isNaN(num)) return \"\";\r\n \r\n const isNegative = num < 0;\r\n const absStr = isNegative ? originalStr.substring(1) : originalStr;\r\n const hasDecimal = absStr.includes(\".\");\r\n \r\n let result;\r\n \r\n if (!hasDecimal) {\r\n // No decimal point - format as integer\r\n const intStr = Math.floor(Math.abs(num)).toString();\r\n result = intStr.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else {\r\n // Has decimal point\r\n if (preserveTyping) {\r\n // LIVE TYPING: Preserve exactly what user typed, just add commas\r\n const [intPart, decPart] = absStr.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n } else {\r\n // FINAL FORMATTING: Apply type-specific decimal rules\r\n if (type === \"months\") {\r\n const rounded = Math.round(Math.abs(num));\r\n result = rounded.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else if (type === \"percent\" || type === \"years\") {\r\n let formatted = Math.abs(num).toFixed(2);\r\n formatted = formatted.replace(/\\.?0+$/, '');\r\n const [intPart, decPart] = formatted.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = decPart ? commaInt + \".\" + decPart : commaInt;\r\n } else {\r\n // Currency: always 2 decimal places\r\n const twoDecimal = Math.abs(num).toFixed(2);\r\n const [intPart, decPart] = twoDecimal.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n }\r\n }\r\n }\r\n \r\n return isNegative ? \"-\" + result : result;\r\n};\r\n\r\nexport const formatLiveInput = (value, type, preserveTyping = false) => {\r\n const filtered = filterNumericInput(value, true);\r\n let formatted = formatLiveNumber(filtered, type, preserveTyping);\r\n \r\n if (formatted === \"\" || formatted === null || formatted === undefined) formatted = \"0\";\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return \"$ \" + formatted;\r\n case \"percent\":\r\n return formatted + \" %\";\r\n case \"years\":\r\n return formatted + \" yrs.\";\r\n case \"months\":\r\n return formatted + \" mos.\";\r\n case \"number\":\r\n return formatted;\r\n default:\r\n return formatted;\r\n }\r\n};\r\n\r\nexport const calculateCursorPosition = (oldValue, newValue, oldCursor, type) => {\r\n if (oldCursor <= 0) return 0;\r\n \r\n // Handle end-of-input positioning\r\n if (oldCursor >= oldValue.length) {\r\n if (type === \"currency\") {\r\n return newValue.length;\r\n } else {\r\n const match = newValue.match(/^[0-9,.\\-\\s]+/);\r\n return match ? match[0].length : 0;\r\n }\r\n }\r\n \r\n // Get prefix length for different input types\r\n const getPrefixLength = (value, inputType) => {\r\n if (inputType === \"currency\" && value.startsWith(\"$ \")) return 2;\r\n return 0;\r\n };\r\n \r\n // Get the end of numeric content (before suffix)\r\n const getContentEnd = (value, inputType, prefixLength) => {\r\n const content = value.substring(prefixLength);\r\n \r\n if (inputType === \"percent\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n } else if (inputType === \"years\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n } else if (inputType === \"months\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n }\r\n \r\n return content.length;\r\n };\r\n \r\n const oldPrefixLength = getPrefixLength(oldValue, type);\r\n const newPrefixLength = getPrefixLength(newValue, type);\r\n \r\n const oldContentEnd = getContentEnd(oldValue, type, oldPrefixLength);\r\n const newContentEnd = getContentEnd(newValue, type, newPrefixLength);\r\n \r\n // Convert cursor position to be relative to numeric content\r\n const cursorInOldContent = Math.max(0, oldCursor - oldPrefixLength);\r\n const maxOldContentCursor = Math.min(cursorInOldContent, oldContentEnd);\r\n \r\n // Count logical position: how many significant characters before cursor\r\n const oldContent = oldValue.substring(oldPrefixLength, oldPrefixLength + oldContentEnd);\r\n let logicalPosition = 0;\r\n \r\n for (let i = 0; i < maxOldContentCursor; i++) {\r\n const char = oldContent[i];\r\n if (/[0-9.\\-]/.test(char)) {\r\n logicalPosition++;\r\n }\r\n }\r\n \r\n // Find physical position for this logical position in new content\r\n const newContent = newValue.substring(newPrefixLength, newPrefixLength + newContentEnd);\r\n let physicalPosition = 0;\r\n let logicalCount = 0;\r\n \r\n // Scan through new content to find the target position\r\n for (let i = 0; i < newContent.length; i++) {\r\n const char = newContent[i];\r\n if (/[0-9.\\-]/.test(char)) {\r\n logicalCount++;\r\n \r\n // If we've reached our target logical position\r\n if (logicalCount === logicalPosition) {\r\n // Look ahead to see if there's a comma after this character\r\n if (i + 1 < newContent.length && newContent[i + 1] === ',') {\r\n // Position cursor after the comma\r\n physicalPosition = i + 2;\r\n } else {\r\n // Position cursor after this character\r\n physicalPosition = i + 1;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n \r\n // Handle case where we need position 0 (before any characters)\r\n if (logicalPosition === 0) {\r\n physicalPosition = 0;\r\n }\r\n \r\n // Convert back to full string position\r\n const finalPosition = newPrefixLength + physicalPosition;\r\n \r\n // Ensure we don't exceed the content boundary\r\n const maxAllowedPosition = newPrefixLength + newContentEnd;\r\n return Math.min(finalPosition, maxAllowedPosition);\r\n};\r\n\r\n// Helper function to find position after N significant characters\r\nconst findPositionAfterNSignificantChars = (content, targetCount) => {\r\n let significantChars = 0;\r\n \r\n for (let i = 0; i < content.length; i++) {\r\n if (significantChars === targetCount) {\r\n return i;\r\n }\r\n \r\n if (/[0-9.-]/.test(content[i])) {\r\n significantChars++;\r\n }\r\n }\r\n \r\n return content.length;\r\n};\r\n\r\nexport const extractNumericValue = (formattedValue, type) => {\r\n if (!formattedValue) return 0;\r\n \r\n let cleaned = formattedValue.toString();\r\n \r\n switch (type) {\r\n case \"currency\":\r\n cleaned = cleaned.replace(/^\\$\\s*/, \"\").trim();\r\n break;\r\n case \"percent\":\r\n cleaned = cleaned.replace(/\\s*%$/, \"\").trim();\r\n break;\r\n case \"years\":\r\n cleaned = cleaned.replace(/\\s*yrs\\.$/, \"\").trim();\r\n break;\r\n case \"months\":\r\n cleaned = cleaned.replace(/\\s*mos\\.$/, \"\").trim();\r\n break;\r\n }\r\n \r\n cleaned = cleaned.replace(/,/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};"],"names":["formatInputDisplay","value","type","num","parseFloat","hasDecimals","Intl","NumberFormat","style","currency","minimumFractionDigits","maximumFractionDigits","format","Math","abs","parseNumericInput","cleaned","toString","replace","filterNumericInput","allowNegative","filtered","match","length","startsWithNegative","trim","startsWith","includes","parts","split","slice","join","formatLiveNumber","preserveTyping","originalStr","isNaN","isNegative","absStr","substring","result","intPart","decPart","round","formatted","toFixed","commaInt","twoDecimal","floor","formatLiveInput","calculateCursorPosition","oldValue","newValue","oldCursor","getPrefixLength","inputType","getContentEnd","prefixLength","content","oldPrefixLength","newPrefixLength","oldContentEnd","newContentEnd","cursorInOldContent","max","maxOldContentCursor","min","oldContent","logicalPosition","i","char","test","newContent","physicalPosition","logicalCount","finalPosition","maxAllowedPosition","extractNumericValue","formattedValue"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateDscrPayment,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSfPayment,safePercentage}from"./financial/calculations.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{extractBedrooms,extractPhoneNumber}from"./data/extractors.js";export{calculateDOM}from"./date/utilities.js";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,DSCR_AMORTIZATION,DSCR_INTEREST_RATE,FINANCIAL_CONSTANTS,MAX_ITERATIONS,SELLER_FI_AMORTIZATION,SELLER_FI_CARRY,SELLER_FI_DOWN_PAYMENT,SELLER_FI_INTEREST_RATE}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,
|
|
1
|
+
export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateDscrPayment,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSfPayment,safePercentage}from"./financial/calculations.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{extractBedrooms,extractPhoneNumber}from"./data/extractors.js";export{calculateDOM}from"./date/utilities.js";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,DSCR_AMORTIZATION,DSCR_INTEREST_RATE,FINANCIAL_CONSTANTS,MAX_ITERATIONS,SELLER_FI_AMORTIZATION,SELLER_FI_CARRY,SELLER_FI_DOWN_PAYMENT,SELLER_FI_INTEREST_RATE}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";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 calculateDscrPayment,\r\n calculateNetToBuyer,\r\n calculateNOIByType,\r\n calculatePMT, \r\n calculatePriceForCOCR,\r\n calculatePriceFromDiscount,\r\n calculateSfPayment,\r\n safePercentage,\r\n} from \"./financial/calculations.js\";\r\n\r\n// Financial formatters\r\nexport { formatCurrency, formatPriceValue, formatPercentage } from \"./financial/formatters.js\";\r\n\r\n// Data extractors\r\nexport { extractPhoneNumber, extractBedrooms } from \"./data/extractors.js\";\r\n\r\n// Date utilities\r\nexport { calculateDOM } 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"],"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 calculateDscrPayment,\r\n calculateNetToBuyer,\r\n calculateNOIByType,\r\n calculatePMT, \r\n calculatePriceForCOCR,\r\n calculatePriceFromDiscount,\r\n calculateSfPayment,\r\n safePercentage,\r\n} from \"./financial/calculations.js\";\r\n\r\n// Financial formatters\r\nexport { formatCurrency, formatPriceValue, formatPercentage } from \"./financial/formatters.js\";\r\n\r\n// Data extractors\r\nexport { extractPhoneNumber, extractBedrooms } from \"./data/extractors.js\";\r\n\r\n// Date utilities\r\nexport { calculateDOM } 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"],"names":["STYLES_PATH"],"mappings":"ugDAsDY,MAACA,EAAc"}
|