@archerjessop/utilities 7.26.0 → 7.27.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{hasTooltip as e,attachTooltip as t,updateTooltipContent as n}from"../ui/tooltip-manager.js";import{generateDownPaymentTooltipHTML as r,generateCashFlowTooltipHTML as o}from"../financial/tooltip-content-generators.js";import{parseFinancialData as c,parseCashFlowData as s}from"../financial/tooltip-calculations.js";import{parsePriceNumber as i,computeActiveCapDisplay as a,parseReportedCap as l}from"../financial/capRate.js";import{equityPercentFromDebt as p}from"../../financial/calculations.js";import{formatCurrency as u}from"../../financial/formatters.js";import{PROPERTY_TYPE_CONSTANTS as d}from"../../config/property-types.js";const
|
|
1
|
+
import{hasTooltip as e,attachTooltip as t,updateTooltipContent as n}from"../ui/tooltip-manager.js";import{generateDownPaymentTooltipHTML as r,generateCashFlowTooltipHTML as o}from"../financial/tooltip-content-generators.js";import{parseFinancialData as c,parseCashFlowData as s}from"../financial/tooltip-calculations.js";import{parsePriceNumber as i,computeActiveCapDisplay as a,parseReportedCap as l}from"../financial/capRate.js";import{equityPercentFromDebt as p}from"../../financial/calculations.js";import{formatCurrency as u}from"../../financial/formatters.js";import{PROPERTY_TYPE_CONSTANTS as d}from"../../config/property-types.js";import{formatDate as m}from"../../date/utilities.js";const g=["prop-noi","prop-down","prop-net","prop-seller-fi","prop-cocr-30","prop-cocr-15","prop-assignment","prop-dscr","prop-sf","prop-cashflow"];function createRender({ctx:f}){const{state:y,updateState:C}=f;function getCurrentPrice(){if(y.originalPrice&&y.currentPriceDiscount>0){const e=parseFloat(y.originalPrice.replace(/[$,]/g,""))*(1-y.currentPriceDiscount/100);return`$${Math.round(e).toLocaleString()}`}return y.originalPrice}function updatePercentageLabels(){const e=document.querySelector("#prop-down")?.closest(".metric")?.querySelector(".metric-label");e&&(e.textContent=`Down (${y.currentDownPaymentPercent}%)`);const t=document.querySelector("#prop-seller-fi")?.closest(".metric")?.querySelector(".metric-label");t&&(t.textContent=`Seller FI (${y.currentSellerFiPercent}%)`);const n=document.querySelector("#prop-dscr")?.closest(".metric")?.querySelector(".metric-label");n&&(n.textContent=`DSCR (${y.currentDSCRPercent}%)`)}function updateElement(e,t){const n=document.getElementById(e);n&&(n.textContent=t)}return{applyFinancials:function(i){let a=!1,l=!1;if(i){const p={"prop-noi":i.noi,"prop-down":i.down,"prop-net":i.netToBuyer,"prop-seller-fi":i.sellerFi,"prop-cocr-30":i.cocr30,"prop-cocr-15":i.priceForCOCR15,"prop-assignment":i.assignment,"prop-dscr":i.dscr,"prop-sf":i.sfPayment,"prop-cashflow":i.cashFlow};a=i.rawCashFlow<0;const u=y.cachedDebtBalance;l=Number.isFinite(u)&&u>i.rawDown;for(const[e,t]of Object.entries(p))updateElement(e,t);setTimeout(()=>{!function(){const o=document.getElementById("prop-down"),s=document.getElementById("prop-price"),i=document.getElementById("prop-noi");if(!o||!s||!i)return;const a=c(s.textContent,i.textContent);if(a){const c=r(a.price,a.noi,y.currentDownPaymentPercent,y.currentDSCRPercent,y.currentSellerFiPercent),s=o.closest(".metric");s&&(e(s)?n(s,c):t(s,c))}}(),function(){const r=document.getElementById("prop-cashflow"),c=document.getElementById("prop-price");if(!r||!c)return;const i=s(c.textContent,r.textContent);if(i){const c=o(i.price,i.monthlyCashFlow),s=r.closest(".metric");if(s){const r=s.querySelector(".metric-label");r&&r.classList.add("has-tooltip"),e(s)?n(s,c):t(s,c)}}}()},100)}else g.forEach(e=>updateElement(e,"N/A"));updatePercentageLabels(),function(){const r="str"===y.currentPropertyType,o=document.getElementById("prop-noi-awning");o&&(o.style.display=r?"inline-block":"none");const c=document.getElementById("prop-noi");if(!c)return;if(c.style.cursor=r?"pointer":"",c.querySelector("input"))return;const s=c.closest(".metric");if(r&&y.cachedStrValue&&Number.isFinite(y.cachedStrValue.value)){c.textContent=`${c.textContent}*`;const r=y.cachedStrValue.value,o=d.STR.NOI_PERCENTAGE,i=`<strong>Manual STR gross (Awning):</strong> ${u(r)}/yr<br>NOI = gross × ${Math.round(100*o)}% = ${u(r*o)}<hr><em>Click NOI to edit; click the label to reset to the estimate.</em>`;s&&(e(s)?n(s,i):(t(s,i),s.querySelector(".metric-label")?.classList.add("has-tooltip")))}else r&&s&&e(s)&&n(s,"<strong>STR NOI:</strong> 5.5%-of-price estimate.<hr><em>Click NOI to enter Awning's gross revenue.</em>")}();const p=a||l,m=document.getElementById("ln-footer");m&&m.classList.toggle("negative",p);const f=[];a&&f.push("Cash Flow"),l&&f.push("Equity");const C=document.getElementById("ln-red-reasons");C&&(C.textContent=f.join(" · "),C.style.display=f.length?"":"none")},getCurrentPrice:getCurrentPrice,syncUnitsFieldForType:function(e,t){const n=document.querySelector(".units-inline-label");if(n&&(n.textContent="assisted"===e?"beds":"units"),"assisted"===e&&null!=t){const e=document.getElementById("ln-units-input");e&&(e.value=String(t)),C({numberOfUnits:t})}},updateActiveCapDisplay:function(){const r=document.getElementById("prop-cap");if(!r)return;if(r.querySelector("input"))return;const o=getCurrentPrice()||document.getElementById("prop-price")?.textContent||"",c=i(o);r.textContent=a(y.baseNOI,c);const s=l(y.originalCapRate,y.isUsingEstimatedCapRate),p=`${"<strong>Reported cap rate:</strong> "+(null!=s?`${s}%`:"N/A")}<hr><em>Click the cap rate to enter a value; click the label to reset</em>`,u=r.closest(".metric");if(u){const r=u.querySelector(".metric-label");e(u)?n(u,p):(t(u,p),r&&r.classList.add("has-tooltip"))}},updateCapRateLabel:function(){const e=document.querySelector("#prop-cap")?.closest(".metric")?.querySelector(".metric-label");if(e)switch(y.currentPropertyType){case"str":e.textContent="Cap Rate (STR)";break;case"assisted":e.textContent="Cap Rate (Assisted)";break;default:e.textContent="Cap Rate"}},updateElement:updateElement,updateEquityDisplay:function(){const r=document.getElementById("prop-equity");if(!r)return;const o=getCurrentPrice()||document.getElementById("prop-price")?.textContent||"",c=i(o),s=y.cachedDebtBalance,a=null==s,l=p(c,s);r.textContent=`${Math.round(100*l)}%${a?"*":""}`;const fmtUSD=e=>Number.isFinite(Number(e))?`$${Math.round(Number(e)).toLocaleString()}`:"N/A",u=a?"Estimated — no debt data, assuming 100% equity":"scraped"===y.equitySource?"Public records (API)":y.equitySource;let d=`<strong>Debt owing:</strong> ${a?"Unknown":fmtUSD(s)}`;d+=`<br><strong>Source:</strong> ${u}`,y.cachedDebtAddress&&(d+=`<br><strong>Matched:</strong> ${y.cachedDebtAddress}`);const m=Array.isArray(y.cachedMortgages)?y.cachedMortgages:[];m.length&&(d+="<hr>"+m.map(e=>{const t=null!=e.interestRate&&""!==e.interestRate?` @ ${e.interestRate}%`:"";return`<strong>${e.position||"Lien"}:</strong> ${fmtUSD(e.amount)} ${e.loanType||""}${t} (${e.lenderName||"?"})`}).join("<br>"));const g=r.closest(".metric");if(g){const r=g.querySelector(".metric-label");e(g)?n(g,d):(t(g,d),r&&r.classList.add("has-tooltip"))}},updateLeadStatusTooltip:function(r){const o=document.getElementById("prop-lead-status");if(!o)return;let c="No LOI data returned";if(r){const e=r.createdAt?m(r.createdAt):"",t=e?`<br><strong>Filed:</strong> ${e}`:"";c=`\n <strong>Contact:</strong> ${r.contactName} <br>\n ${r.opportunityAddress}${t}\n `}const s=o.closest(".metric");if(s)if(e(s))n(s,c);else{t(s,c);const e=s.querySelector(".metric-label");e&&e.classList.add("has-tooltip")}},updatePercentageLabels:updatePercentageLabels,updatePriceLabel:function(){const e=document.querySelector("#prop-price")?.closest(".metric")?.querySelector(".metric-label");e&&(e.textContent=y.currentPriceDiscount>0?`Price (${y.currentPriceDiscount}%)`:"Price")}}}export{createRender};
|
|
2
2
|
//# sourceMappingURL=render.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sources":["../../../src/browser/widget/render.js"],"sourcesContent":["// Render unit: the panel's DOM-painting helpers — price/label updates, the financial-metric\r\n// paint, the active-cap display, and the hover tooltips. Everything here reads ctx.state and\r\n// writes the DOM; the finance math it needs comes from the pure capRate helpers. Extracted\r\n// verbatim from createAnalyzer's render closures (T12 decompose).\r\n\r\nimport { attachTooltip, hasTooltip, updateTooltipContent } from \"../ui/tooltip-manager.js\";\r\nimport {\r\n generateCashFlowTooltipHTML,\r\n generateDownPaymentTooltipHTML,\r\n} from \"../financial/tooltip-content-generators.js\";\r\nimport { parseCashFlowData, parseFinancialData } from \"../financial/tooltip-calculations.js\";\r\nimport { computeActiveCapDisplay, parsePriceNumber, parseReportedCap } from \"../financial/capRate.js\";\r\nimport { equityPercentFromDebt } from \"../../financial/calculations.js\";\r\nimport { formatCurrency } from \"../../financial/formatters.js\";\r\nimport { PROPERTY_TYPE_CONSTANTS } from \"../../config/property-types.js\";\r\n\r\nconst FINANCIAL_ELEMENT_IDS = [\r\n \"prop-noi\", \"prop-down\", \"prop-net\", \"prop-seller-fi\", \"prop-cocr-30\",\r\n \"prop-cocr-15\", \"prop-assignment\", \"prop-dscr\", \"prop-sf\", \"prop-cashflow\",\r\n];\r\n\r\nexport function createRender({ ctx }) {\r\n const { state, updateState } = ctx;\r\n\r\n function getCurrentPrice() {\r\n if (state.originalPrice && state.currentPriceDiscount > 0) {\r\n const numericPrice = parseFloat(state.originalPrice.replace(/[$,]/g, \"\"));\r\n const discountedPrice = numericPrice * (1 - state.currentPriceDiscount / 100);\r\n return `$${Math.round(discountedPrice).toLocaleString()}`;\r\n }\r\n return state.originalPrice;\r\n }\r\n\r\n function updatePriceLabel() {\r\n const priceLabelElement = document.querySelector(\"#prop-price\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (priceLabelElement) {\r\n priceLabelElement.textContent = state.currentPriceDiscount > 0 ? `Price (${state.currentPriceDiscount}%)` : \"Price\";\r\n }\r\n }\r\n\r\n function updatePercentageLabels() {\r\n const downLabelElement = document.querySelector(\"#prop-down\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (downLabelElement) downLabelElement.textContent = `Down (${state.currentDownPaymentPercent}%)`;\r\n\r\n const sellerFiLabelElement = document.querySelector(\"#prop-seller-fi\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (sellerFiLabelElement) sellerFiLabelElement.textContent = `Seller FI (${state.currentSellerFiPercent}%)`;\r\n\r\n const dscrLabelElement = document.querySelector(\"#prop-dscr\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (dscrLabelElement) dscrLabelElement.textContent = `DSCR (${state.currentDSCRPercent}%)`;\r\n }\r\n\r\n function updateElement(id, value) {\r\n const element = document.getElementById(id);\r\n if (element) element.textContent = value;\r\n }\r\n\r\n function updateCapRateLabel() {\r\n const capLabelElement = document.querySelector(\"#prop-cap\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (!capLabelElement) return;\r\n switch (state.currentPropertyType) {\r\n case \"str\": capLabelElement.textContent = \"Cap Rate (STR)\"; break;\r\n case \"assisted\": capLabelElement.textContent = \"Cap Rate (Assisted)\"; break;\r\n default: capLabelElement.textContent = \"Cap Rate\"; break;\r\n }\r\n }\r\n\r\n // The panel shows the ACTIVE cap rate = NOI / current price (discount-aware via\r\n // getCurrentPrice), so the displayed cap is always internally consistent with the NOI metric\r\n // — including STR/assisted, where NOI is the type estimate/bedroom value and the listed cap\r\n // never drove it. The REPORTED cap (the scraped value, only when it was a real non-estimated\r\n // cap) is shown on hover; \"N/A\" when none was reported. When the cap is an estimate the\r\n // tooltip also keeps the click-to-cycle hint (clicking the cap is a manual NOI override).\r\n function updateActiveCapDisplay() {\r\n const capElement = document.getElementById(\"prop-cap\");\r\n if (!capElement) return;\r\n // Don't clobber an in-progress manual cap edit (the inline input lives in this cell).\r\n if (capElement.querySelector(\"input\")) return;\r\n\r\n const priceText = getCurrentPrice() || document.getElementById(\"prop-price\")?.textContent || \"\";\r\n const price = parsePriceNumber(priceText);\r\n capElement.textContent = computeActiveCapDisplay(state.baseNOI, price);\r\n\r\n const reported = parseReportedCap(state.originalCapRate, state.isUsingEstimatedCapRate);\r\n const reportedLine = `<strong>Reported cap rate:</strong> ${reported != null ? `${reported}%` : \"N/A\"}`;\r\n const editHint = \"<hr><em>Click the cap rate to enter a value; click the label to reset</em>\";\r\n const tooltipContent = `${reportedLine}${editHint}`;\r\n\r\n const metric = capElement.closest(\".metric\");\r\n if (metric) {\r\n const label = metric.querySelector(\".metric-label\");\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tooltipContent);\r\n if (label) label.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n // Equity is DERIVED, not fetched: (price - debt) / price against the current (discount-aware)\r\n // price, so it recomputes on every price edit. No debt figure => 100% (\"estimated\", marked *).\r\n // The hover reveals the $ debt, the recorded liens, the matched address, and how it was acquired.\r\n function updateEquityDisplay() {\r\n const equityElement = document.getElementById(\"prop-equity\");\r\n if (!equityElement) return;\r\n\r\n const priceText = getCurrentPrice() || document.getElementById(\"prop-price\")?.textContent || \"\";\r\n const price = parsePriceNumber(priceText);\r\n const debt = state.cachedDebtBalance;\r\n const estimated = debt === null || debt === undefined;\r\n const equity = equityPercentFromDebt(price, debt);\r\n equityElement.textContent = `${Math.round(equity * 100)}%${estimated ? \"*\" : \"\"}`;\r\n\r\n const fmtUSD = (n) => (Number.isFinite(Number(n)) ? `$${Math.round(Number(n)).toLocaleString()}` : \"N/A\");\r\n const sourceLabel = estimated\r\n ? \"Estimated — no debt data, assuming 100% equity\"\r\n : (state.equitySource === \"scraped\" ? \"Public records (API)\" : state.equitySource);\r\n\r\n let tooltip = `<strong>Debt owing:</strong> ${estimated ? \"Unknown\" : fmtUSD(debt)}`;\r\n tooltip += `<br><strong>Source:</strong> ${sourceLabel}`;\r\n if (state.cachedDebtAddress) tooltip += `<br><strong>Matched:</strong> ${state.cachedDebtAddress}`;\r\n\r\n const mortgages = Array.isArray(state.cachedMortgages) ? state.cachedMortgages : [];\r\n if (mortgages.length) {\r\n tooltip += \"<hr>\" + mortgages.map((m) => {\r\n const rate = (m.interestRate != null && m.interestRate !== \"\") ? ` @ ${m.interestRate}%` : \"\";\r\n return `<strong>${m.position || \"Lien\"}:</strong> ${fmtUSD(m.amount)} ${m.loanType || \"\"}${rate} (${m.lenderName || \"?\"})`;\r\n }).join(\"<br>\");\r\n }\r\n\r\n const metric = equityElement.closest(\".metric\");\r\n if (metric) {\r\n const label = metric.querySelector(\".metric-label\");\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tooltip);\r\n if (label) label.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tooltip);\r\n }\r\n }\r\n }\r\n\r\n function syncUnitsFieldForType(propertyType, bedroomCount) {\r\n const label = document.querySelector(\".units-inline-label\");\r\n if (label) label.textContent = propertyType === \"assisted\" ? \"beds\" : \"units\";\r\n if (propertyType === \"assisted\" && bedroomCount != null) {\r\n const input = document.getElementById(\"ln-units-input\");\r\n if (input) input.value = String(bedroomCount);\r\n updateState({ numberOfUnits: bedroomCount });\r\n }\r\n }\r\n\r\n function updateLeadStatusTooltip(loiData) {\r\n const leadElement = document.getElementById(\"prop-lead-status\");\r\n if (!leadElement) return;\r\n let tooltipContent = \"No LOI data returned\";\r\n if (loiData) {\r\n tooltipContent = `\r\n <strong>Contact:</strong> ${loiData.contactName} <br>\r\n ${loiData.opportunityAddress}\r\n `;\r\n }\r\n const metric = leadElement.closest(\".metric\");\r\n if (metric) {\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tooltipContent);\r\n const label = metric.querySelector(\".metric-label\");\r\n if (label) label.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n function updateDownHoverTooltip() {\r\n const downElement = document.getElementById(\"prop-down\");\r\n const priceElement = document.getElementById(\"prop-price\");\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n if (!downElement || !priceElement || !noiElement) return;\r\n\r\n const financialData = parseFinancialData(priceElement.textContent, noiElement.textContent);\r\n if (financialData) {\r\n const tooltipContent = generateDownPaymentTooltipHTML(\r\n financialData.price,\r\n financialData.noi,\r\n state.currentDownPaymentPercent,\r\n state.currentDSCRPercent,\r\n state.currentSellerFiPercent\r\n );\r\n const metric = downElement.closest(\".metric\");\r\n if (metric) {\r\n if (!hasTooltip(metric)) attachTooltip(metric, tooltipContent);\r\n else updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n function updateCashFlowHoverTooltip() {\r\n const cashFlowElement = document.getElementById(\"prop-cashflow\");\r\n const priceElement = document.getElementById(\"prop-price\");\r\n if (!cashFlowElement || !priceElement) return;\r\n\r\n const cashFlowData = parseCashFlowData(priceElement.textContent, cashFlowElement.textContent);\r\n if (cashFlowData) {\r\n const tooltipContent = generateCashFlowTooltipHTML(cashFlowData.price, cashFlowData.monthlyCashFlow);\r\n const metric = cashFlowElement.closest(\".metric\");\r\n if (metric) {\r\n const label = metric.querySelector(\".metric-label\");\r\n if (label) label.classList.add(\"has-tooltip\");\r\n if (!hasTooltip(metric)) attachTooltip(metric, tooltipContent);\r\n else updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n // STR affordances: the Awning link and the click-to-edit NOI cell exist only in STR mode.\r\n // When a manual STR gross is active (cachedStrValue), mark the painted NOI with a trailing *\r\n // and a source tooltip — mirroring the cap-rate/equity \"*\" convention. Runs after the metric\r\n // paint so the * is appended to the freshly-set NOI text (and re-applied on every recalc).\r\n function updateStrAffordances() {\r\n const isStr = state.currentPropertyType === \"str\";\r\n\r\n const awningLink = document.getElementById(\"prop-noi-awning\");\r\n if (awningLink) awningLink.style.display = isStr ? \"inline-block\" : \"none\";\r\n\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n if (!noiElement) return;\r\n noiElement.style.cursor = isStr ? \"pointer\" : \"\";\r\n if (noiElement.querySelector(\"input\")) return;\r\n\r\n const metric = noiElement.closest(\".metric\");\r\n const manual = isStr && state.cachedStrValue && Number.isFinite(state.cachedStrValue.value);\r\n\r\n if (manual) {\r\n noiElement.textContent = `${noiElement.textContent}*`;\r\n const gross = state.cachedStrValue.value;\r\n const pct = PROPERTY_TYPE_CONSTANTS.STR.NOI_PERCENTAGE;\r\n const tip = `<strong>Manual STR gross (Awning):</strong> ${formatCurrency(gross)}/yr`\r\n + `<br>NOI = gross × ${Math.round(pct * 100)}% = ${formatCurrency(gross * pct)}`\r\n + `<hr><em>Click NOI to edit; click the label to reset to the estimate.</em>`;\r\n if (metric) {\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tip);\r\n metric.querySelector(\".metric-label\")?.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tip);\r\n }\r\n }\r\n } else if (isStr && metric && hasTooltip(metric)) {\r\n updateTooltipContent(metric, \"<strong>STR NOI:</strong> 5.5%-of-price estimate.<hr><em>Click NOI to enter Awning's gross revenue.</em>\");\r\n }\r\n }\r\n\r\n function applyFinancials(financials) {\r\n let isCashFlowNegative = false;\r\n let insufficientEquity = false;\r\n if (financials) {\r\n const elements = {\r\n \"prop-noi\": financials.noi,\r\n \"prop-down\": financials.down,\r\n \"prop-net\": financials.netToBuyer,\r\n \"prop-seller-fi\": financials.sellerFi,\r\n \"prop-cocr-30\": financials.cocr30,\r\n \"prop-cocr-15\": financials.priceForCOCR15,\r\n \"prop-assignment\": financials.assignment,\r\n \"prop-dscr\": financials.dscr,\r\n \"prop-sf\": financials.sfPayment,\r\n \"prop-cashflow\": financials.cashFlow,\r\n };\r\n isCashFlowNegative = financials.rawCashFlow < 0;\r\n // Equity-aware: seller financing requires the seller to clear their existing debt at\r\n // close from the buyer's down payment, so a down payment that can't cover known debt is\r\n // an infeasible deal — same red treatment as negative cash flow (mirrors the dashboard).\r\n // Unknown debt (null) is assumed 100% equity and never flags.\r\n const debt = state.cachedDebtBalance;\r\n insufficientEquity = Number.isFinite(debt) && debt > financials.rawDown;\r\n for (const [id, value] of Object.entries(elements)) updateElement(id, value);\r\n setTimeout(() => {\r\n updateDownHoverTooltip();\r\n updateCashFlowHoverTooltip();\r\n }, 100);\r\n } else {\r\n FINANCIAL_ELEMENT_IDS.forEach((id) => updateElement(id, \"N/A\"));\r\n }\r\n\r\n updatePercentageLabels();\r\n updateStrAffordances();\r\n\r\n const isRed = isCashFlowNegative || insufficientEquity;\r\n const footer = document.getElementById(\"ln-footer\");\r\n if (footer) footer.classList.toggle(\"negative\", isRed);\r\n\r\n const reasons = [];\r\n if (isCashFlowNegative) reasons.push(\"Cash Flow\");\r\n if (insufficientEquity) reasons.push(\"Equity\");\r\n const pill = document.getElementById(\"ln-red-reasons\");\r\n if (pill) {\r\n pill.textContent = reasons.join(\" · \");\r\n pill.style.display = reasons.length ? \"\" : \"none\";\r\n }\r\n }\r\n\r\n return {\r\n applyFinancials,\r\n getCurrentPrice,\r\n syncUnitsFieldForType,\r\n updateActiveCapDisplay,\r\n updateCapRateLabel,\r\n updateElement,\r\n updateEquityDisplay,\r\n updateLeadStatusTooltip,\r\n updatePercentageLabels,\r\n updatePriceLabel,\r\n };\r\n}\r\n"],"names":["FINANCIAL_ELEMENT_IDS","createRender","ctx","state","updateState","getCurrentPrice","originalPrice","currentPriceDiscount","discountedPrice","parseFloat","replace","Math","round","toLocaleString","updatePercentageLabels","downLabelElement","document","querySelector","closest","textContent","currentDownPaymentPercent","sellerFiLabelElement","currentSellerFiPercent","dscrLabelElement","currentDSCRPercent","updateElement","id","value","element","getElementById","applyFinancials","financials","isCashFlowNegative","insufficientEquity","elements","noi","down","netToBuyer","sellerFi","cocr30","priceForCOCR15","assignment","dscr","sfPayment","cashFlow","rawCashFlow","debt","cachedDebtBalance","Number","isFinite","rawDown","Object","entries","setTimeout","downElement","priceElement","noiElement","financialData","parseFinancialData","tooltipContent","generateDownPaymentTooltipHTML","price","metric","hasTooltip","updateTooltipContent","attachTooltip","updateDownHoverTooltip","cashFlowElement","cashFlowData","parseCashFlowData","generateCashFlowTooltipHTML","monthlyCashFlow","label","classList","add","updateCashFlowHoverTooltip","forEach","isStr","currentPropertyType","awningLink","style","display","cursor","cachedStrValue","gross","pct","PROPERTY_TYPE_CONSTANTS","STR","NOI_PERCENTAGE","tip","formatCurrency","updateStrAffordances","isRed","footer","toggle","reasons","push","pill","join","length","syncUnitsFieldForType","propertyType","bedroomCount","input","String","numberOfUnits","updateActiveCapDisplay","capElement","priceText","parsePriceNumber","computeActiveCapDisplay","baseNOI","reported","parseReportedCap","originalCapRate","isUsingEstimatedCapRate","updateCapRateLabel","capLabelElement","updateEquityDisplay","equityElement","estimated","equity","equityPercentFromDebt","fmtUSD","n","sourceLabel","equitySource","tooltip","cachedDebtAddress","mortgages","Array","isArray","cachedMortgages","map","m","rate","interestRate","position","amount","loanType","lenderName","updateLeadStatusTooltip","loiData","leadElement","contactName","opportunityAddress","updatePriceLabel","priceLabelElement"],"mappings":"+nBAgBA,MAAMA,EAAwB,CAC5B,WAAY,YAAa,WAAY,iBAAkB,eACvD,eAAgB,kBAAmB,YAAa,UAAW,iBAGtD,SAASC,cAAaC,IAAEA,IAC7B,MAAMC,MAAEA,EAAKC,YAAEA,GAAgBF,EAE/B,SAASG,kBACP,GAAIF,EAAMG,eAAiBH,EAAMI,qBAAuB,EAAG,CACzD,MACMC,EADeC,WAAWN,EAAMG,cAAcI,QAAQ,QAAS,MAC7B,EAAIP,EAAMI,qBAAuB,KACzE,MAAO,IAAII,KAAKC,MAAMJ,GAAiBK,kBACzC,CACA,OAAOV,EAAMG,aACf,CASA,SAASQ,yBACP,MAAMC,EAAmBC,SAASC,cAAc,eAAeC,QAAQ,YAAYD,cAAc,iBAC7FF,IAAkBA,EAAiBI,YAAc,SAAShB,EAAMiB,+BAEpE,MAAMC,EAAuBL,SAASC,cAAc,oBAAoBC,QAAQ,YAAYD,cAAc,iBACtGI,IAAsBA,EAAqBF,YAAc,cAAchB,EAAMmB,4BAEjF,MAAMC,EAAmBP,SAASC,cAAc,eAAeC,QAAQ,YAAYD,cAAc,iBAC7FM,IAAkBA,EAAiBJ,YAAc,SAAShB,EAAMqB,uBACtE,CAEA,SAASC,cAAcC,EAAIC,GACzB,MAAMC,EAAUZ,SAASa,eAAeH,GACpCE,IAASA,EAAQT,YAAcQ,EACrC,CAwPA,MAAO,CACLG,gBAlDF,SAAyBC,GACvB,IAAIC,GAAqB,EACrBC,GAAqB,EACzB,GAAIF,EAAY,CACd,MAAMG,EAAW,CACf,WAAYH,EAAWI,IACvB,YAAaJ,EAAWK,KACxB,WAAYL,EAAWM,WACvB,iBAAkBN,EAAWO,SAC7B,eAAgBP,EAAWQ,OAC3B,eAAgBR,EAAWS,eAC3B,kBAAmBT,EAAWU,WAC9B,YAAaV,EAAWW,KACxB,UAAWX,EAAWY,UACtB,gBAAiBZ,EAAWa,UAE9BZ,EAAqBD,EAAWc,YAAc,EAK9C,MAAMC,EAAO3C,EAAM4C,kBACnBd,EAAqBe,OAAOC,SAASH,IAASA,EAAOf,EAAWmB,QAChE,IAAK,MAAOxB,EAAIC,KAAUwB,OAAOC,QAAQlB,GAAWT,cAAcC,EAAIC,GACtE0B,WAAW,MAvGf,WACE,MAAMC,EAActC,SAASa,eAAe,aACtC0B,EAAevC,SAASa,eAAe,cACvC2B,EAAaxC,SAASa,eAAe,YAC3C,IAAKyB,IAAgBC,IAAiBC,EAAY,OAElD,MAAMC,EAAgBC,EAAmBH,EAAapC,YAAaqC,EAAWrC,aAC9E,GAAIsC,EAAe,CACjB,MAAME,EAAiBC,EACrBH,EAAcI,MACdJ,EAActB,IACdhC,EAAMiB,0BACNjB,EAAMqB,mBACNrB,EAAMmB,wBAEFwC,EAASR,EAAYpC,QAAQ,WAC/B4C,IACGC,EAAWD,GACXE,EAAqBF,EAAQH,GADTM,EAAcH,EAAQH,GAGnD,CACF,CAmFMO,GAjFN,WACE,MAAMC,EAAkBnD,SAASa,eAAe,iBAC1C0B,EAAevC,SAASa,eAAe,cAC7C,IAAKsC,IAAoBZ,EAAc,OAEvC,MAAMa,EAAeC,EAAkBd,EAAapC,YAAagD,EAAgBhD,aACjF,GAAIiD,EAAc,CAChB,MAAMT,EAAiBW,EAA4BF,EAAaP,MAAOO,EAAaG,iBAC9ET,EAASK,EAAgBjD,QAAQ,WACvC,GAAI4C,EAAQ,CACV,MAAMU,EAAQV,EAAO7C,cAAc,iBAC/BuD,GAAOA,EAAMC,UAAUC,IAAI,eAC1BX,EAAWD,GACXE,EAAqBF,EAAQH,GADTM,EAAcH,EAAQH,EAEjD,CACF,CACF,CAkEMgB,IACC,IACL,MACE3E,EAAsB4E,QAASlD,GAAOD,cAAcC,EAAI,QAG1DZ,yBAlEF,WACE,MAAM+D,EAAsC,QAA9B1E,EAAM2E,oBAEdC,EAAa/D,SAASa,eAAe,mBACvCkD,IAAYA,EAAWC,MAAMC,QAAUJ,EAAQ,eAAiB,QAEpE,MAAMrB,EAAaxC,SAASa,eAAe,YAC3C,IAAK2B,EAAY,OAEjB,GADAA,EAAWwB,MAAME,OAASL,EAAQ,UAAY,GAC1CrB,EAAWvC,cAAc,SAAU,OAEvC,MAAM6C,EAASN,EAAWtC,QAAQ,WAGlC,GAFe2D,GAAS1E,EAAMgF,gBAAkBnC,OAAOC,SAAS9C,EAAMgF,eAAexD,OAEzE,CACV6B,EAAWrC,YAAc,GAAGqC,EAAWrC,eACvC,MAAMiE,EAAQjF,EAAMgF,eAAexD,MAC7B0D,EAAMC,EAAwBC,IAAIC,eAClCC,EAAM,+CAA+CC,EAAeN,gCAC3CzE,KAAKC,MAAY,IAANyE,SAAiBK,EAAeN,EAAQC,8EAE9EvB,IACGC,EAAWD,GAIdE,EAAqBF,EAAQ2B,IAH7BxB,EAAcH,EAAQ2B,GACtB3B,EAAO7C,cAAc,kBAAkBwD,UAAUC,IAAI,gBAK3D,MAAWG,GAASf,GAAUC,EAAWD,IACvCE,EAAqBF,EAAQ,2GAEjC,CAmCE6B,GAEA,MAAMC,EAAQ5D,GAAsBC,EAC9B4D,EAAS7E,SAASa,eAAe,aACnCgE,GAAQA,EAAOpB,UAAUqB,OAAO,WAAYF,GAEhD,MAAMG,EAAU,GACZ/D,GAAoB+D,EAAQC,KAAK,aACjC/D,GAAoB8D,EAAQC,KAAK,UACrC,MAAMC,EAAOjF,SAASa,eAAe,kBACjCoE,IACFA,EAAK9E,YAAc4E,EAAQG,KAAK,OAChCD,EAAKjB,MAAMC,QAAUc,EAAQI,OAAS,GAAK,OAE/C,EAIE9F,gCACA+F,sBAnKF,SAA+BC,EAAcC,GAC3C,MAAM9B,EAAQxD,SAASC,cAAc,uBAErC,GADIuD,IAAOA,EAAMrD,YAA+B,aAAjBkF,EAA8B,OAAS,SACjD,aAAjBA,GAA+C,MAAhBC,EAAsB,CACvD,MAAMC,EAAQvF,SAASa,eAAe,kBAClC0E,IAAOA,EAAM5E,MAAQ6E,OAAOF,IAChClG,EAAY,CAAEqG,cAAeH,GAC/B,CACF,EA4JEI,uBA1OF,WACE,MAAMC,EAAa3F,SAASa,eAAe,YAC3C,IAAK8E,EAAY,OAEjB,GAAIA,EAAW1F,cAAc,SAAU,OAEvC,MAAM2F,EAAYvG,mBAAqBW,SAASa,eAAe,eAAeV,aAAe,GACvF0C,EAAQgD,EAAiBD,GAC/BD,EAAWxF,YAAc2F,EAAwB3G,EAAM4G,QAASlD,GAEhE,MAAMmD,EAAWC,EAAiB9G,EAAM+G,gBAAiB/G,EAAMgH,yBAGzDxD,EAAiB,GAFF,wCAAmD,MAAZqD,EAAmB,GAAGA,KAAc,mFAI1FlD,EAAS6C,EAAWzF,QAAQ,WAClC,GAAI4C,EAAQ,CACV,MAAMU,EAAQV,EAAO7C,cAAc,iBAC9B8C,EAAWD,GAIdE,EAAqBF,EAAQH,IAH7BM,EAAcH,EAAQH,GAClBa,GAAOA,EAAMC,UAAUC,IAAI,eAInC,CACF,EAkNE0C,mBA3PF,WACE,MAAMC,EAAkBrG,SAASC,cAAc,cAAcC,QAAQ,YAAYD,cAAc,iBAC/F,GAAKoG,EACL,OAAQlH,EAAM2E,qBACZ,IAAK,MAAOuC,EAAgBlG,YAAc,iBAAkB,MAC5D,IAAK,WAAYkG,EAAgBlG,YAAc,sBAAuB,MACtE,QAASkG,EAAgBlG,YAAc,WAE3C,EAoPEM,4BACA6F,oBA/MF,WACE,MAAMC,EAAgBvG,SAASa,eAAe,eAC9C,IAAK0F,EAAe,OAEpB,MAAMX,EAAYvG,mBAAqBW,SAASa,eAAe,eAAeV,aAAe,GACvF0C,EAAQgD,EAAiBD,GACzB9D,EAAO3C,EAAM4C,kBACbyE,EAAY1E,QACZ2E,EAASC,EAAsB7D,EAAOf,GAC5CyE,EAAcpG,YAAc,GAAGR,KAAKC,MAAe,IAAT6G,MAAiBD,EAAY,IAAM,KAE7E,MAAMG,OAAUC,GAAO5E,OAAOC,SAASD,OAAO4E,IAAM,IAAIjH,KAAKC,MAAMoC,OAAO4E,IAAI/G,mBAAqB,MAC7FgH,EAAcL,EAChB,iDACwB,YAAvBrH,EAAM2H,aAA6B,uBAAyB3H,EAAM2H,aAEvE,IAAIC,EAAU,gCAAgCP,EAAY,UAAYG,OAAO7E,KAC7EiF,GAAW,gCAAgCF,IACvC1H,EAAM6H,oBAAmBD,GAAW,iCAAiC5H,EAAM6H,qBAE/E,MAAMC,EAAYC,MAAMC,QAAQhI,EAAMiI,iBAAmBjI,EAAMiI,gBAAkB,GAC7EH,EAAU9B,SACZ4B,GAAW,OAASE,EAAUI,IAAKC,IACjC,MAAMC,EAA0B,MAAlBD,EAAEE,cAA2C,KAAnBF,EAAEE,aAAuB,MAAMF,EAAEE,gBAAkB,GAC3F,MAAO,WAAWF,EAAEG,UAAY,oBAAoBd,OAAOW,EAAEI,WAAWJ,EAAEK,UAAY,KAAKJ,MAASD,EAAEM,YAAc,SACnH1C,KAAK,SAGV,MAAMpC,EAASyD,EAAcrG,QAAQ,WACrC,GAAI4C,EAAQ,CACV,MAAMU,EAAQV,EAAO7C,cAAc,iBAC9B8C,EAAWD,GAIdE,EAAqBF,EAAQiE,IAH7B9D,EAAcH,EAAQiE,GAClBvD,GAAOA,EAAMC,UAAUC,IAAI,eAInC,CACF,EA0KEmE,wBA9JF,SAAiCC,GAC/B,MAAMC,EAAc/H,SAASa,eAAe,oBAC5C,IAAKkH,EAAa,OAClB,IAAIpF,EAAiB,uBACjBmF,IACFnF,EAAiB,qCACWmF,EAAQE,4BAClCF,EAAQG,4BAGZ,MAAMnF,EAASiF,EAAY7H,QAAQ,WACnC,GAAI4C,EACF,GAAKC,EAAWD,GAKdE,EAAqBF,EAAQH,OALN,CACvBM,EAAcH,EAAQH,GACtB,MAAMa,EAAQV,EAAO7C,cAAc,iBAC/BuD,GAAOA,EAAMC,UAAUC,IAAI,cACjC,CAIJ,EA2IE5D,8CACAoI,iBAvRF,WACE,MAAMC,EAAoBnI,SAASC,cAAc,gBAAgBC,QAAQ,YAAYD,cAAc,iBAC/FkI,IACFA,EAAkBhI,YAAchB,EAAMI,qBAAuB,EAAI,UAAUJ,EAAMI,yBAA2B,QAEhH,EAoRF"}
|
|
1
|
+
{"version":3,"file":"render.js","sources":["../../../src/browser/widget/render.js"],"sourcesContent":["// Render unit: the panel's DOM-painting helpers — price/label updates, the financial-metric\r\n// paint, the active-cap display, and the hover tooltips. Everything here reads ctx.state and\r\n// writes the DOM; the finance math it needs comes from the pure capRate helpers. Extracted\r\n// verbatim from createAnalyzer's render closures (T12 decompose).\r\n\r\nimport { attachTooltip, hasTooltip, updateTooltipContent } from \"../ui/tooltip-manager.js\";\r\nimport {\r\n generateCashFlowTooltipHTML,\r\n generateDownPaymentTooltipHTML,\r\n} from \"../financial/tooltip-content-generators.js\";\r\nimport { parseCashFlowData, parseFinancialData } from \"../financial/tooltip-calculations.js\";\r\nimport { computeActiveCapDisplay, parsePriceNumber, parseReportedCap } from \"../financial/capRate.js\";\r\nimport { equityPercentFromDebt } from \"../../financial/calculations.js\";\r\nimport { formatCurrency } from \"../../financial/formatters.js\";\r\nimport { PROPERTY_TYPE_CONSTANTS } from \"../../config/property-types.js\";\r\nimport { formatDate } from \"../../date/utilities.js\";\r\n\r\nconst FINANCIAL_ELEMENT_IDS = [\r\n \"prop-noi\", \"prop-down\", \"prop-net\", \"prop-seller-fi\", \"prop-cocr-30\",\r\n \"prop-cocr-15\", \"prop-assignment\", \"prop-dscr\", \"prop-sf\", \"prop-cashflow\",\r\n];\r\n\r\nexport function createRender({ ctx }) {\r\n const { state, updateState } = ctx;\r\n\r\n function getCurrentPrice() {\r\n if (state.originalPrice && state.currentPriceDiscount > 0) {\r\n const numericPrice = parseFloat(state.originalPrice.replace(/[$,]/g, \"\"));\r\n const discountedPrice = numericPrice * (1 - state.currentPriceDiscount / 100);\r\n return `$${Math.round(discountedPrice).toLocaleString()}`;\r\n }\r\n return state.originalPrice;\r\n }\r\n\r\n function updatePriceLabel() {\r\n const priceLabelElement = document.querySelector(\"#prop-price\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (priceLabelElement) {\r\n priceLabelElement.textContent = state.currentPriceDiscount > 0 ? `Price (${state.currentPriceDiscount}%)` : \"Price\";\r\n }\r\n }\r\n\r\n function updatePercentageLabels() {\r\n const downLabelElement = document.querySelector(\"#prop-down\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (downLabelElement) downLabelElement.textContent = `Down (${state.currentDownPaymentPercent}%)`;\r\n\r\n const sellerFiLabelElement = document.querySelector(\"#prop-seller-fi\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (sellerFiLabelElement) sellerFiLabelElement.textContent = `Seller FI (${state.currentSellerFiPercent}%)`;\r\n\r\n const dscrLabelElement = document.querySelector(\"#prop-dscr\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (dscrLabelElement) dscrLabelElement.textContent = `DSCR (${state.currentDSCRPercent}%)`;\r\n }\r\n\r\n function updateElement(id, value) {\r\n const element = document.getElementById(id);\r\n if (element) element.textContent = value;\r\n }\r\n\r\n function updateCapRateLabel() {\r\n const capLabelElement = document.querySelector(\"#prop-cap\")?.closest(\".metric\")?.querySelector(\".metric-label\");\r\n if (!capLabelElement) return;\r\n switch (state.currentPropertyType) {\r\n case \"str\": capLabelElement.textContent = \"Cap Rate (STR)\"; break;\r\n case \"assisted\": capLabelElement.textContent = \"Cap Rate (Assisted)\"; break;\r\n default: capLabelElement.textContent = \"Cap Rate\"; break;\r\n }\r\n }\r\n\r\n // The panel shows the ACTIVE cap rate = NOI / current price (discount-aware via\r\n // getCurrentPrice), so the displayed cap is always internally consistent with the NOI metric\r\n // — including STR/assisted, where NOI is the type estimate/bedroom value and the listed cap\r\n // never drove it. The REPORTED cap (the scraped value, only when it was a real non-estimated\r\n // cap) is shown on hover; \"N/A\" when none was reported. When the cap is an estimate the\r\n // tooltip also keeps the click-to-cycle hint (clicking the cap is a manual NOI override).\r\n function updateActiveCapDisplay() {\r\n const capElement = document.getElementById(\"prop-cap\");\r\n if (!capElement) return;\r\n // Don't clobber an in-progress manual cap edit (the inline input lives in this cell).\r\n if (capElement.querySelector(\"input\")) return;\r\n\r\n const priceText = getCurrentPrice() || document.getElementById(\"prop-price\")?.textContent || \"\";\r\n const price = parsePriceNumber(priceText);\r\n capElement.textContent = computeActiveCapDisplay(state.baseNOI, price);\r\n\r\n const reported = parseReportedCap(state.originalCapRate, state.isUsingEstimatedCapRate);\r\n const reportedLine = `<strong>Reported cap rate:</strong> ${reported != null ? `${reported}%` : \"N/A\"}`;\r\n const editHint = \"<hr><em>Click the cap rate to enter a value; click the label to reset</em>\";\r\n const tooltipContent = `${reportedLine}${editHint}`;\r\n\r\n const metric = capElement.closest(\".metric\");\r\n if (metric) {\r\n const label = metric.querySelector(\".metric-label\");\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tooltipContent);\r\n if (label) label.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n // Equity is DERIVED, not fetched: (price - debt) / price against the current (discount-aware)\r\n // price, so it recomputes on every price edit. No debt figure => 100% (\"estimated\", marked *).\r\n // The hover reveals the $ debt, the recorded liens, the matched address, and how it was acquired.\r\n function updateEquityDisplay() {\r\n const equityElement = document.getElementById(\"prop-equity\");\r\n if (!equityElement) return;\r\n\r\n const priceText = getCurrentPrice() || document.getElementById(\"prop-price\")?.textContent || \"\";\r\n const price = parsePriceNumber(priceText);\r\n const debt = state.cachedDebtBalance;\r\n const estimated = debt === null || debt === undefined;\r\n const equity = equityPercentFromDebt(price, debt);\r\n equityElement.textContent = `${Math.round(equity * 100)}%${estimated ? \"*\" : \"\"}`;\r\n\r\n const fmtUSD = (n) => (Number.isFinite(Number(n)) ? `$${Math.round(Number(n)).toLocaleString()}` : \"N/A\");\r\n const sourceLabel = estimated\r\n ? \"Estimated — no debt data, assuming 100% equity\"\r\n : (state.equitySource === \"scraped\" ? \"Public records (API)\" : state.equitySource);\r\n\r\n let tooltip = `<strong>Debt owing:</strong> ${estimated ? \"Unknown\" : fmtUSD(debt)}`;\r\n tooltip += `<br><strong>Source:</strong> ${sourceLabel}`;\r\n if (state.cachedDebtAddress) tooltip += `<br><strong>Matched:</strong> ${state.cachedDebtAddress}`;\r\n\r\n const mortgages = Array.isArray(state.cachedMortgages) ? state.cachedMortgages : [];\r\n if (mortgages.length) {\r\n tooltip += \"<hr>\" + mortgages.map((m) => {\r\n const rate = (m.interestRate != null && m.interestRate !== \"\") ? ` @ ${m.interestRate}%` : \"\";\r\n return `<strong>${m.position || \"Lien\"}:</strong> ${fmtUSD(m.amount)} ${m.loanType || \"\"}${rate} (${m.lenderName || \"?\"})`;\r\n }).join(\"<br>\");\r\n }\r\n\r\n const metric = equityElement.closest(\".metric\");\r\n if (metric) {\r\n const label = metric.querySelector(\".metric-label\");\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tooltip);\r\n if (label) label.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tooltip);\r\n }\r\n }\r\n }\r\n\r\n function syncUnitsFieldForType(propertyType, bedroomCount) {\r\n const label = document.querySelector(\".units-inline-label\");\r\n if (label) label.textContent = propertyType === \"assisted\" ? \"beds\" : \"units\";\r\n if (propertyType === \"assisted\" && bedroomCount != null) {\r\n const input = document.getElementById(\"ln-units-input\");\r\n if (input) input.value = String(bedroomCount);\r\n updateState({ numberOfUnits: bedroomCount });\r\n }\r\n }\r\n\r\n function updateLeadStatusTooltip(loiData) {\r\n const leadElement = document.getElementById(\"prop-lead-status\");\r\n if (!leadElement) return;\r\n let tooltipContent = \"No LOI data returned\";\r\n if (loiData) {\r\n const filedDate = loiData.createdAt ? formatDate(loiData.createdAt) : \"\";\r\n const filedLine = filedDate ? `<br><strong>Filed:</strong> ${filedDate}` : \"\";\r\n tooltipContent = `\r\n <strong>Contact:</strong> ${loiData.contactName} <br>\r\n ${loiData.opportunityAddress}${filedLine}\r\n `;\r\n }\r\n const metric = leadElement.closest(\".metric\");\r\n if (metric) {\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tooltipContent);\r\n const label = metric.querySelector(\".metric-label\");\r\n if (label) label.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n function updateDownHoverTooltip() {\r\n const downElement = document.getElementById(\"prop-down\");\r\n const priceElement = document.getElementById(\"prop-price\");\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n if (!downElement || !priceElement || !noiElement) return;\r\n\r\n const financialData = parseFinancialData(priceElement.textContent, noiElement.textContent);\r\n if (financialData) {\r\n const tooltipContent = generateDownPaymentTooltipHTML(\r\n financialData.price,\r\n financialData.noi,\r\n state.currentDownPaymentPercent,\r\n state.currentDSCRPercent,\r\n state.currentSellerFiPercent\r\n );\r\n const metric = downElement.closest(\".metric\");\r\n if (metric) {\r\n if (!hasTooltip(metric)) attachTooltip(metric, tooltipContent);\r\n else updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n function updateCashFlowHoverTooltip() {\r\n const cashFlowElement = document.getElementById(\"prop-cashflow\");\r\n const priceElement = document.getElementById(\"prop-price\");\r\n if (!cashFlowElement || !priceElement) return;\r\n\r\n const cashFlowData = parseCashFlowData(priceElement.textContent, cashFlowElement.textContent);\r\n if (cashFlowData) {\r\n const tooltipContent = generateCashFlowTooltipHTML(cashFlowData.price, cashFlowData.monthlyCashFlow);\r\n const metric = cashFlowElement.closest(\".metric\");\r\n if (metric) {\r\n const label = metric.querySelector(\".metric-label\");\r\n if (label) label.classList.add(\"has-tooltip\");\r\n if (!hasTooltip(metric)) attachTooltip(metric, tooltipContent);\r\n else updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n }\r\n\r\n // STR affordances: the Awning link and the click-to-edit NOI cell exist only in STR mode.\r\n // When a manual STR gross is active (cachedStrValue), mark the painted NOI with a trailing *\r\n // and a source tooltip — mirroring the cap-rate/equity \"*\" convention. Runs after the metric\r\n // paint so the * is appended to the freshly-set NOI text (and re-applied on every recalc).\r\n function updateStrAffordances() {\r\n const isStr = state.currentPropertyType === \"str\";\r\n\r\n const awningLink = document.getElementById(\"prop-noi-awning\");\r\n if (awningLink) awningLink.style.display = isStr ? \"inline-block\" : \"none\";\r\n\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n if (!noiElement) return;\r\n noiElement.style.cursor = isStr ? \"pointer\" : \"\";\r\n if (noiElement.querySelector(\"input\")) return;\r\n\r\n const metric = noiElement.closest(\".metric\");\r\n const manual = isStr && state.cachedStrValue && Number.isFinite(state.cachedStrValue.value);\r\n\r\n if (manual) {\r\n noiElement.textContent = `${noiElement.textContent}*`;\r\n const gross = state.cachedStrValue.value;\r\n const pct = PROPERTY_TYPE_CONSTANTS.STR.NOI_PERCENTAGE;\r\n const tip = `<strong>Manual STR gross (Awning):</strong> ${formatCurrency(gross)}/yr`\r\n + `<br>NOI = gross × ${Math.round(pct * 100)}% = ${formatCurrency(gross * pct)}`\r\n + `<hr><em>Click NOI to edit; click the label to reset to the estimate.</em>`;\r\n if (metric) {\r\n if (!hasTooltip(metric)) {\r\n attachTooltip(metric, tip);\r\n metric.querySelector(\".metric-label\")?.classList.add(\"has-tooltip\");\r\n } else {\r\n updateTooltipContent(metric, tip);\r\n }\r\n }\r\n } else if (isStr && metric && hasTooltip(metric)) {\r\n updateTooltipContent(metric, \"<strong>STR NOI:</strong> 5.5%-of-price estimate.<hr><em>Click NOI to enter Awning's gross revenue.</em>\");\r\n }\r\n }\r\n\r\n function applyFinancials(financials) {\r\n let isCashFlowNegative = false;\r\n let insufficientEquity = false;\r\n if (financials) {\r\n const elements = {\r\n \"prop-noi\": financials.noi,\r\n \"prop-down\": financials.down,\r\n \"prop-net\": financials.netToBuyer,\r\n \"prop-seller-fi\": financials.sellerFi,\r\n \"prop-cocr-30\": financials.cocr30,\r\n \"prop-cocr-15\": financials.priceForCOCR15,\r\n \"prop-assignment\": financials.assignment,\r\n \"prop-dscr\": financials.dscr,\r\n \"prop-sf\": financials.sfPayment,\r\n \"prop-cashflow\": financials.cashFlow,\r\n };\r\n isCashFlowNegative = financials.rawCashFlow < 0;\r\n // Equity-aware: seller financing requires the seller to clear their existing debt at\r\n // close from the buyer's down payment, so a down payment that can't cover known debt is\r\n // an infeasible deal — same red treatment as negative cash flow (mirrors the dashboard).\r\n // Unknown debt (null) is assumed 100% equity and never flags.\r\n const debt = state.cachedDebtBalance;\r\n insufficientEquity = Number.isFinite(debt) && debt > financials.rawDown;\r\n for (const [id, value] of Object.entries(elements)) updateElement(id, value);\r\n setTimeout(() => {\r\n updateDownHoverTooltip();\r\n updateCashFlowHoverTooltip();\r\n }, 100);\r\n } else {\r\n FINANCIAL_ELEMENT_IDS.forEach((id) => updateElement(id, \"N/A\"));\r\n }\r\n\r\n updatePercentageLabels();\r\n updateStrAffordances();\r\n\r\n const isRed = isCashFlowNegative || insufficientEquity;\r\n const footer = document.getElementById(\"ln-footer\");\r\n if (footer) footer.classList.toggle(\"negative\", isRed);\r\n\r\n const reasons = [];\r\n if (isCashFlowNegative) reasons.push(\"Cash Flow\");\r\n if (insufficientEquity) reasons.push(\"Equity\");\r\n const pill = document.getElementById(\"ln-red-reasons\");\r\n if (pill) {\r\n pill.textContent = reasons.join(\" · \");\r\n pill.style.display = reasons.length ? \"\" : \"none\";\r\n }\r\n }\r\n\r\n return {\r\n applyFinancials,\r\n getCurrentPrice,\r\n syncUnitsFieldForType,\r\n updateActiveCapDisplay,\r\n updateCapRateLabel,\r\n updateElement,\r\n updateEquityDisplay,\r\n updateLeadStatusTooltip,\r\n updatePercentageLabels,\r\n updatePriceLabel,\r\n };\r\n}\r\n"],"names":["FINANCIAL_ELEMENT_IDS","createRender","ctx","state","updateState","getCurrentPrice","originalPrice","currentPriceDiscount","discountedPrice","parseFloat","replace","Math","round","toLocaleString","updatePercentageLabels","downLabelElement","document","querySelector","closest","textContent","currentDownPaymentPercent","sellerFiLabelElement","currentSellerFiPercent","dscrLabelElement","currentDSCRPercent","updateElement","id","value","element","getElementById","applyFinancials","financials","isCashFlowNegative","insufficientEquity","elements","noi","down","netToBuyer","sellerFi","cocr30","priceForCOCR15","assignment","dscr","sfPayment","cashFlow","rawCashFlow","debt","cachedDebtBalance","Number","isFinite","rawDown","Object","entries","setTimeout","downElement","priceElement","noiElement","financialData","parseFinancialData","tooltipContent","generateDownPaymentTooltipHTML","price","metric","hasTooltip","updateTooltipContent","attachTooltip","updateDownHoverTooltip","cashFlowElement","cashFlowData","parseCashFlowData","generateCashFlowTooltipHTML","monthlyCashFlow","label","classList","add","updateCashFlowHoverTooltip","forEach","isStr","currentPropertyType","awningLink","style","display","cursor","cachedStrValue","gross","pct","PROPERTY_TYPE_CONSTANTS","STR","NOI_PERCENTAGE","tip","formatCurrency","updateStrAffordances","isRed","footer","toggle","reasons","push","pill","join","length","syncUnitsFieldForType","propertyType","bedroomCount","input","String","numberOfUnits","updateActiveCapDisplay","capElement","priceText","parsePriceNumber","computeActiveCapDisplay","baseNOI","reported","parseReportedCap","originalCapRate","isUsingEstimatedCapRate","updateCapRateLabel","capLabelElement","updateEquityDisplay","equityElement","estimated","equity","equityPercentFromDebt","fmtUSD","n","sourceLabel","equitySource","tooltip","cachedDebtAddress","mortgages","Array","isArray","cachedMortgages","map","m","rate","interestRate","position","amount","loanType","lenderName","updateLeadStatusTooltip","loiData","leadElement","filedDate","createdAt","formatDate","filedLine","contactName","opportunityAddress","updatePriceLabel","priceLabelElement"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{fetchDebt as e}from"../../services/debt.js";import{fetchStrRevenue as t}from"../services/str-revenue.js";import{lookupLOI as a}from"../../services/loi-lookup.js";import{MATCH_TYPES as r,LOI_SENT_STATUS as o}from"../../config/loi-lookup.js";function createServices({ctx:c}){const{state:s,updateState:n}=c;function applyDebtResult(e,t){e&&null!==e.estimatedMortgageBalance?n({cachedDebtAddress:e.address,cachedDebtBalance:e.estimatedMortgageBalance,cachedMortgages:Array.isArray(e.currentMortgages)?e.currentMortgages:[],debtLoaded:!0,equitySource:"scraped"}):n({cachedDebtAddress:e?.address??t,cachedDebtBalance:null,cachedMortgages:Array.isArray(e?.currentMortgages)?e.currentMortgages:[],debtLoaded:!0,equitySource:"estimated"})}return{ensureDebtLoaded:async function(t){if(!s.debtLoaded)try{const a=new Promise(e=>setTimeout(()=>e(null),3e3)),r=e(t).catch(()=>null);applyDebtResult(await Promise.race([r,a]),t)}catch(e){console.error("❌ Error ensuring debt loaded:",e),applyDebtResult(null,t)}return{address:s.cachedDebtAddress,balance:s.cachedDebtBalance,mortgages:s.cachedMortgages,source:s.equitySource}},loadDebt:async function(t,a){if(s.debtLoaded)return;n({equityLoadingStartTime:Date.now()});const r=document.getElementById("prop-equity");r&&(r.classList.add("loading"),r.textContent="");let o=null;try{o=await e(t)}catch(e){console.error("Error fetching debt:",e),o=null}const c=Date.now()-s.equityLoadingStartTime;await new Promise(e=>setTimeout(e,Math.max(0,2e3-c))),a.isStale()||(r&&r.classList.remove("loading"),applyDebtResult(o,t))},loadLeadStatus:async function(e){if(s.cachedLoiData||n({cachedLoiData:{}}),s.cachedLoiData.leadStatus)return{
|
|
1
|
+
import{fetchDebt as e}from"../../services/debt.js";import{fetchStrRevenue as t}from"../services/str-revenue.js";import{lookupLOI as a}from"../../services/loi-lookup.js";import{MATCH_TYPES as r,LOI_SENT_STATUS as o}from"../../config/loi-lookup.js";function createServices({ctx:c}){const{state:s,updateState:n}=c;function applyDebtResult(e,t){e&&null!==e.estimatedMortgageBalance?n({cachedDebtAddress:e.address,cachedDebtBalance:e.estimatedMortgageBalance,cachedMortgages:Array.isArray(e.currentMortgages)?e.currentMortgages:[],debtLoaded:!0,equitySource:"scraped"}):n({cachedDebtAddress:e?.address??t,cachedDebtBalance:null,cachedMortgages:Array.isArray(e?.currentMortgages)?e.currentMortgages:[],debtLoaded:!0,equitySource:"estimated"})}return{ensureDebtLoaded:async function(t){if(!s.debtLoaded)try{const a=new Promise(e=>setTimeout(()=>e(null),3e3)),r=e(t).catch(()=>null);applyDebtResult(await Promise.race([r,a]),t)}catch(e){console.error("❌ Error ensuring debt loaded:",e),applyDebtResult(null,t)}return{address:s.cachedDebtAddress,balance:s.cachedDebtBalance,mortgages:s.cachedMortgages,source:s.equitySource}},loadDebt:async function(t,a){if(s.debtLoaded)return;n({equityLoadingStartTime:Date.now()});const r=document.getElementById("prop-equity");r&&(r.classList.add("loading"),r.textContent="");let o=null;try{o=await e(t)}catch(e){console.error("Error fetching debt:",e),o=null}const c=Date.now()-s.equityLoadingStartTime;await new Promise(e=>setTimeout(e,Math.max(0,2e3-c))),a.isStale()||(r&&r.classList.remove("loading"),applyDebtResult(o,t))},loadLeadStatus:async function(e){if(s.cachedLoiData||n({cachedLoiData:{}}),s.cachedLoiData.leadStatus)return{contactName:s.cachedLoiData.contactName,createdAt:s.cachedLoiData.createdAt,leadStatus:s.cachedLoiData.leadStatus,opportunityAddress:s.cachedLoiData.opportunityAddress};try{const t=await a(e);let c="New Lead";const s=t?.data||{};switch(t?.matchType){case r.NO_RESPONSE:s.contactName="LOI lookup failed",s.opportunityAddress="to supply a response";break;case r.NO_MATCH:s.contactName="LOI lookup replied with",s.opportunityAddress="no match found";break;case r.EXACT:case r.FUZZY:c=o;break;default:s.contactName="LOI lookup failed",s.opportunityAddress="unknown match type"}const d={contactName:s.contactName||"No contact available",createdAt:s.createdAt||null,leadStatus:c,opportunityAddress:s.opportunityAddress||"(Unknown)"};return n({cachedLoiData:d}),d}catch(e){return console.error("💥 Error fetching lead status:",e),{leadStatus:"New Lead*",contactName:"No contact available",opportunityAddress:"Error fetching address"}}},loadStrValue:async function(e,a){if(s.cachedStrValue)return s.cachedStrValue;const r=document.getElementById("prop-noi");r&&r.classList.add("loading");try{const r=await t(e);return a.isStale()?null:r&&Number.isFinite(r.value)&&("noi"===r.type||"gross"===r.type)?(n({cachedStrValue:r}),r):null}catch(e){return console.error("Error fetching STR revenue:",e),null}finally{!a.isStale()&&r&&r.classList.remove("loading")}}}}export{createServices};
|
|
2
2
|
//# sourceMappingURL=services.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"services.js","sources":["../../../src/browser/widget/services.js"],"sourcesContent":["// Services unit: the orchestration around the agnostic fetchers (caching, the panel loading\r\n// indicator, the 2s minimum loading, the stale-drop via the pipeline guard, the provenance +\r\n// fallbacks). The fetchers themselves stay pure IO in ../services. Extracted verbatim (T12).\r\n\r\nimport { fetchDebt } from \"../../services/debt.js\";\r\nimport { fetchStrRevenue } from \"../services/str-revenue.js\";\r\nimport { lookupLOI } from \"../../services/loi-lookup.js\";\r\nimport { LOI_SENT_STATUS, MATCH_TYPES } from \"../../config/loi-lookup.js\";\r\n\r\nexport function createServices({ ctx }) {\r\n const { state, updateState } = ctx;\r\n\r\n // Caching + match-type mapping for the LOI lead status. lookupLOI is the agnostic call; the\r\n // engine owns the cache (ctx.state.cachedLoiData) and the mapping.\r\n async function loadLeadStatus(address) {\r\n if (!state.cachedLoiData) updateState({ cachedLoiData: {} });\r\n if (state.cachedLoiData.leadStatus) {\r\n return {\r\n leadStatus: state.cachedLoiData.leadStatus,\r\n contactName: state.cachedLoiData.contactName,\r\n opportunityAddress: state.cachedLoiData.opportunityAddress,\r\n };\r\n }\r\n try {\r\n const result = await lookupLOI(address);\r\n let leadStatus = \"New Lead\";\r\n const data = result?.data || {};\r\n switch (result?.matchType) {\r\n case MATCH_TYPES.NO_RESPONSE:\r\n data.contactName = \"LOI lookup failed\";\r\n data.opportunityAddress = \"to supply a response\";\r\n break;\r\n case MATCH_TYPES.NO_MATCH:\r\n data.contactName = \"LOI lookup replied with\";\r\n data.opportunityAddress = \"no match found\";\r\n break;\r\n case MATCH_TYPES.EXACT:\r\n case MATCH_TYPES.FUZZY:\r\n leadStatus = LOI_SENT_STATUS;\r\n break;\r\n default:\r\n data.contactName = \"LOI lookup failed\";\r\n data.opportunityAddress = \"unknown match type\";\r\n break;\r\n }\r\n const loiData = {\r\n leadStatus,\r\n contactName: data.contactName || \"No contact available\",\r\n opportunityAddress: data.opportunityAddress || \"(Unknown)\",\r\n };\r\n updateState({ cachedLoiData: loiData });\r\n return loiData;\r\n } catch (error) {\r\n console.error(\"💥 Error fetching lead status:\", error);\r\n return {\r\n leadStatus: \"New Lead*\",\r\n contactName: \"No contact available\",\r\n opportunityAddress: \"Error fetching address\",\r\n };\r\n }\r\n }\r\n\r\n // Apply a fetchDebt result (or a null/failed fetch) to ctx. A numeric balance is provenance\r\n // \"scraped\"; no number means the service had no figure, so equity falls back to 100%\r\n // (\"estimated\"). debtLoaded guards against re-fetching when the balance is legitimately null.\r\n function applyDebtResult(result, address) {\r\n if (result && result.estimatedMortgageBalance !== null) {\r\n updateState({\r\n cachedDebtAddress: result.address,\r\n cachedDebtBalance: result.estimatedMortgageBalance,\r\n cachedMortgages: Array.isArray(result.currentMortgages) ? result.currentMortgages : [],\r\n debtLoaded: true,\r\n equitySource: \"scraped\",\r\n });\r\n } else {\r\n updateState({\r\n cachedDebtAddress: result?.address ?? address,\r\n cachedDebtBalance: null,\r\n cachedMortgages: Array.isArray(result?.currentMortgages) ? result.currentMortgages : [],\r\n debtLoaded: true,\r\n equitySource: \"estimated\",\r\n });\r\n }\r\n }\r\n\r\n // Debt orchestration: cache-first, panel loading indicator, 2s minimum loading, stale-drop\r\n // via the pipeline guard. Stores the debt; the panel DERIVES equity from it vs the current\r\n // price (render.updateEquityDisplay), so equity tracks user price edits.\r\n async function loadDebt(address, guard) {\r\n if (state.debtLoaded) return;\r\n\r\n updateState({ equityLoadingStartTime: Date.now() });\r\n const equityElement = document.getElementById(\"prop-equity\");\r\n if (equityElement) {\r\n equityElement.classList.add(\"loading\");\r\n equityElement.textContent = \"\";\r\n }\r\n\r\n let result = null;\r\n try {\r\n result = await fetchDebt(address);\r\n } catch (error) {\r\n console.error(\"Error fetching debt:\", error);\r\n result = null;\r\n }\r\n\r\n const elapsed = Date.now() - state.equityLoadingStartTime;\r\n await new Promise((resolve) => setTimeout(resolve, Math.max(0, 2000 - elapsed)));\r\n\r\n if (guard.isStale()) return;\r\n if (equityElement) equityElement.classList.remove(\"loading\");\r\n\r\n applyDebtResult(result, address);\r\n }\r\n\r\n // STR-revenue orchestration: cache-first, NOI loading indicator, stale-drop, payload\r\n // validation. Returns null until the backend ships.\r\n async function loadStrValue(address, guard) {\r\n if (state.cachedStrValue) return state.cachedStrValue;\r\n\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n if (noiElement) noiElement.classList.add(\"loading\");\r\n\r\n try {\r\n const data = await fetchStrRevenue(address);\r\n if (guard.isStale()) return null;\r\n if (data && Number.isFinite(data.value) && (data.type === \"noi\" || data.type === \"gross\")) {\r\n updateState({ cachedStrValue: data });\r\n return data;\r\n }\r\n return null;\r\n } catch (error) {\r\n console.error(\"Error fetching STR revenue:\", error);\r\n return null;\r\n } finally {\r\n if (!guard.isStale() && noiElement) noiElement.classList.remove(\"loading\");\r\n }\r\n }\r\n\r\n // Export-time guarantee: ensure debt is loaded (cache-first, 3s timeout => estimated), then\r\n // return the debt snapshot so the export carries the balance + mortgages and a price-derived\r\n // equity. Never throws; a failure becomes the \"estimated\" case.\r\n async function ensureDebtLoaded(address) {\r\n if (!state.debtLoaded) {\r\n try {\r\n const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 3000));\r\n const debtPromise = fetchDebt(address).catch(() => null);\r\n const result = await Promise.race([debtPromise, timeoutPromise]);\r\n applyDebtResult(result, address);\r\n } catch (error) {\r\n console.error(\"❌ Error ensuring debt loaded:\", error);\r\n applyDebtResult(null, address);\r\n }\r\n }\r\n return {\r\n address: state.cachedDebtAddress,\r\n balance: state.cachedDebtBalance,\r\n mortgages: state.cachedMortgages,\r\n source: state.equitySource,\r\n };\r\n }\r\n\r\n return { ensureDebtLoaded, loadDebt, loadLeadStatus, loadStrValue };\r\n}\r\n"],"names":["createServices","ctx","state","updateState","applyDebtResult","result","address","estimatedMortgageBalance","cachedDebtAddress","cachedDebtBalance","cachedMortgages","Array","isArray","currentMortgages","debtLoaded","equitySource","ensureDebtLoaded","async","timeoutPromise","Promise","resolve","setTimeout","debtPromise","fetchDebt","catch","race","error","console","balance","mortgages","source","loadDebt","guard","equityLoadingStartTime","Date","now","equityElement","document","getElementById","classList","add","textContent","elapsed","Math","max","isStale","remove","loadLeadStatus","cachedLoiData","leadStatus","contactName","opportunityAddress","lookupLOI","data","matchType","MATCH_TYPES","NO_RESPONSE","NO_MATCH","EXACT","FUZZY","LOI_SENT_STATUS","loiData","loadStrValue","cachedStrValue","noiElement","fetchStrRevenue","Number","isFinite","value","type"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"services.js","sources":["../../../src/browser/widget/services.js"],"sourcesContent":["// Services unit: the orchestration around the agnostic fetchers (caching, the panel loading\r\n// indicator, the 2s minimum loading, the stale-drop via the pipeline guard, the provenance +\r\n// fallbacks). The fetchers themselves stay pure IO in ../services. Extracted verbatim (T12).\r\n\r\nimport { fetchDebt } from \"../../services/debt.js\";\r\nimport { fetchStrRevenue } from \"../services/str-revenue.js\";\r\nimport { lookupLOI } from \"../../services/loi-lookup.js\";\r\nimport { LOI_SENT_STATUS, MATCH_TYPES } from \"../../config/loi-lookup.js\";\r\n\r\nexport function createServices({ ctx }) {\r\n const { state, updateState } = ctx;\r\n\r\n // Caching + match-type mapping for the LOI lead status. lookupLOI is the agnostic call; the\r\n // engine owns the cache (ctx.state.cachedLoiData) and the mapping.\r\n async function loadLeadStatus(address) {\r\n if (!state.cachedLoiData) updateState({ cachedLoiData: {} });\r\n if (state.cachedLoiData.leadStatus) {\r\n return {\r\n contactName: state.cachedLoiData.contactName,\r\n createdAt: state.cachedLoiData.createdAt,\r\n leadStatus: state.cachedLoiData.leadStatus,\r\n opportunityAddress: state.cachedLoiData.opportunityAddress,\r\n };\r\n }\r\n try {\r\n const result = await lookupLOI(address);\r\n let leadStatus = \"New Lead\";\r\n const data = result?.data || {};\r\n switch (result?.matchType) {\r\n case MATCH_TYPES.NO_RESPONSE:\r\n data.contactName = \"LOI lookup failed\";\r\n data.opportunityAddress = \"to supply a response\";\r\n break;\r\n case MATCH_TYPES.NO_MATCH:\r\n data.contactName = \"LOI lookup replied with\";\r\n data.opportunityAddress = \"no match found\";\r\n break;\r\n case MATCH_TYPES.EXACT:\r\n case MATCH_TYPES.FUZZY:\r\n leadStatus = LOI_SENT_STATUS;\r\n break;\r\n default:\r\n data.contactName = \"LOI lookup failed\";\r\n data.opportunityAddress = \"unknown match type\";\r\n break;\r\n }\r\n const loiData = {\r\n contactName: data.contactName || \"No contact available\",\r\n createdAt: data.createdAt || null,\r\n leadStatus,\r\n opportunityAddress: data.opportunityAddress || \"(Unknown)\",\r\n };\r\n updateState({ cachedLoiData: loiData });\r\n return loiData;\r\n } catch (error) {\r\n console.error(\"💥 Error fetching lead status:\", error);\r\n return {\r\n leadStatus: \"New Lead*\",\r\n contactName: \"No contact available\",\r\n opportunityAddress: \"Error fetching address\",\r\n };\r\n }\r\n }\r\n\r\n // Apply a fetchDebt result (or a null/failed fetch) to ctx. A numeric balance is provenance\r\n // \"scraped\"; no number means the service had no figure, so equity falls back to 100%\r\n // (\"estimated\"). debtLoaded guards against re-fetching when the balance is legitimately null.\r\n function applyDebtResult(result, address) {\r\n if (result && result.estimatedMortgageBalance !== null) {\r\n updateState({\r\n cachedDebtAddress: result.address,\r\n cachedDebtBalance: result.estimatedMortgageBalance,\r\n cachedMortgages: Array.isArray(result.currentMortgages) ? result.currentMortgages : [],\r\n debtLoaded: true,\r\n equitySource: \"scraped\",\r\n });\r\n } else {\r\n updateState({\r\n cachedDebtAddress: result?.address ?? address,\r\n cachedDebtBalance: null,\r\n cachedMortgages: Array.isArray(result?.currentMortgages) ? result.currentMortgages : [],\r\n debtLoaded: true,\r\n equitySource: \"estimated\",\r\n });\r\n }\r\n }\r\n\r\n // Debt orchestration: cache-first, panel loading indicator, 2s minimum loading, stale-drop\r\n // via the pipeline guard. Stores the debt; the panel DERIVES equity from it vs the current\r\n // price (render.updateEquityDisplay), so equity tracks user price edits.\r\n async function loadDebt(address, guard) {\r\n if (state.debtLoaded) return;\r\n\r\n updateState({ equityLoadingStartTime: Date.now() });\r\n const equityElement = document.getElementById(\"prop-equity\");\r\n if (equityElement) {\r\n equityElement.classList.add(\"loading\");\r\n equityElement.textContent = \"\";\r\n }\r\n\r\n let result = null;\r\n try {\r\n result = await fetchDebt(address);\r\n } catch (error) {\r\n console.error(\"Error fetching debt:\", error);\r\n result = null;\r\n }\r\n\r\n const elapsed = Date.now() - state.equityLoadingStartTime;\r\n await new Promise((resolve) => setTimeout(resolve, Math.max(0, 2000 - elapsed)));\r\n\r\n if (guard.isStale()) return;\r\n if (equityElement) equityElement.classList.remove(\"loading\");\r\n\r\n applyDebtResult(result, address);\r\n }\r\n\r\n // STR-revenue orchestration: cache-first, NOI loading indicator, stale-drop, payload\r\n // validation. Returns null until the backend ships.\r\n async function loadStrValue(address, guard) {\r\n if (state.cachedStrValue) return state.cachedStrValue;\r\n\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n if (noiElement) noiElement.classList.add(\"loading\");\r\n\r\n try {\r\n const data = await fetchStrRevenue(address);\r\n if (guard.isStale()) return null;\r\n if (data && Number.isFinite(data.value) && (data.type === \"noi\" || data.type === \"gross\")) {\r\n updateState({ cachedStrValue: data });\r\n return data;\r\n }\r\n return null;\r\n } catch (error) {\r\n console.error(\"Error fetching STR revenue:\", error);\r\n return null;\r\n } finally {\r\n if (!guard.isStale() && noiElement) noiElement.classList.remove(\"loading\");\r\n }\r\n }\r\n\r\n // Export-time guarantee: ensure debt is loaded (cache-first, 3s timeout => estimated), then\r\n // return the debt snapshot so the export carries the balance + mortgages and a price-derived\r\n // equity. Never throws; a failure becomes the \"estimated\" case.\r\n async function ensureDebtLoaded(address) {\r\n if (!state.debtLoaded) {\r\n try {\r\n const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 3000));\r\n const debtPromise = fetchDebt(address).catch(() => null);\r\n const result = await Promise.race([debtPromise, timeoutPromise]);\r\n applyDebtResult(result, address);\r\n } catch (error) {\r\n console.error(\"❌ Error ensuring debt loaded:\", error);\r\n applyDebtResult(null, address);\r\n }\r\n }\r\n return {\r\n address: state.cachedDebtAddress,\r\n balance: state.cachedDebtBalance,\r\n mortgages: state.cachedMortgages,\r\n source: state.equitySource,\r\n };\r\n }\r\n\r\n return { ensureDebtLoaded, loadDebt, loadLeadStatus, loadStrValue };\r\n}\r\n"],"names":["createServices","ctx","state","updateState","applyDebtResult","result","address","estimatedMortgageBalance","cachedDebtAddress","cachedDebtBalance","cachedMortgages","Array","isArray","currentMortgages","debtLoaded","equitySource","ensureDebtLoaded","async","timeoutPromise","Promise","resolve","setTimeout","debtPromise","fetchDebt","catch","race","error","console","balance","mortgages","source","loadDebt","guard","equityLoadingStartTime","Date","now","equityElement","document","getElementById","classList","add","textContent","elapsed","Math","max","isStale","remove","loadLeadStatus","cachedLoiData","leadStatus","contactName","createdAt","opportunityAddress","lookupLOI","data","matchType","MATCH_TYPES","NO_RESPONSE","NO_MATCH","EXACT","FUZZY","LOI_SENT_STATUS","loiData","loadStrValue","cachedStrValue","noiElement","fetchStrRevenue","Number","isFinite","value","type"],"mappings":"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"}
|