Function bodies 338 total
extractLevels function · javascript · L132-L138 (7 LOC)functions/propertyProcessor.js
function extractLevels(c) {
// Extract levels (stories) from lotSize object if available
if (typeof c.lotSize === "object" && c.lotSize !== null) {
return c.lotSize.level || null;
}
return null;
}calculateMedian function · javascript · L139-L144 (6 LOC)functions/propertyProcessor.js
function calculateMedian(sortedArray) {
const mid = Math.floor(sortedArray.length / 2);
return sortedArray.length % 2 === 0 ?
(sortedArray[mid - 1] + sortedArray[mid]) / 2 :
sortedArray[mid];
}calculateMAD function · javascript · L145-L148 (4 LOC)functions/propertyProcessor.js
function calculateMAD(values, median) {
const deviations = values.map((value) => Math.abs(value - median));
return calculateMedian(deviations.sort((a, b) => a - b));
}percentile function · javascript · L149-L159 (11 LOC)functions/propertyProcessor.js
function percentile(sortedArray, p) {
const index = (p / 100) * (sortedArray.length - 1);
if (Math.floor(index) === index) {
return sortedArray[index];
} else {
const lower = Math.floor(index);
const upper = Math.ceil(index);
const weight = index - lower;
return sortedArray[lower] * (1 - weight) + sortedArray[upper] * weight;
}
}detectOutliersEnhanced function · javascript · L160-L181 (22 LOC)functions/propertyProcessor.js
function detectOutliersEnhanced(comps, subjectProperty, subjectPricePerSqft) {
if (comps.length < 3) return comps;
const pricesPerSqFt = comps.map((c) => extractCompPrice(c) / extractCompSqft(c));
const marketReasonableRange = {
min: subjectPricePerSqft > 0 ? subjectPricePerSqft * 0.3 : 100,
max: subjectPricePerSqft > 0 ? subjectPricePerSqft * 1.3 : 600,
};
const sortedPrices = [...pricesPerSqFt].sort((a, b) => a - b);
const median = calculateMedian(sortedPrices);
const mad = calculateMAD(sortedPrices, median);
const p10 = percentile(sortedPrices, 10);
const p80 = percentile(sortedPrices, 80);
const filteredComps = comps.filter((comp, index) => {
const pricePerSqft = pricesPerSqFt[index];
const modifiedZScore = mad > 0 ? Math.abs(0.6745 * (pricePerSqft - median) / mad) : 0;
const passesZScore = modifiedZScore < 3.5;
const withinPercentiles = pricePerSqft >= p10 && pricePerSqft <= p80;
const withinMarketcalculateWeightedPricePerSqft function · javascript · L182-L202 (21 LOC)functions/propertyProcessor.js
function calculateWeightedPricePerSqft(comps, subjectProperty) {
const subjectSqft = subjectProperty.livingArea || 0;
const subjectBeds = subjectProperty.bedrooms || 0;
const weightedPrices = comps.map((comp) => {
const compSqft = extractCompSqft(comp);
const compPrice = extractCompPrice(comp);
const compBeds = comp.beds || 0;
const sizeSimilarity = subjectSqft > 0 ?
1 - Math.abs(compSqft - subjectSqft) / Math.max(compSqft, subjectSqft) : 0.5;
const bedSimilarity = Math.max(0, 1 - Math.abs(compBeds - subjectBeds) / 5);
const weight = (sizeSimilarity * 0.6 + bedSimilarity * 0.4) * 0.5 + 0.5; // min 0.5
return {
pricePerSqft: compPrice / compSqft,
weight: weight,
};
});
const totalWeight = weightedPrices.reduce((sum, w) => sum + w.weight, 0);
const weightedAvg = totalWeight > 0 ?
weightedPrices.reduce((sum, w) => sum + (w.pricePerSqft * w.weight), 0) / totalWeight : 250;
return MatsanitizeForFlutterFlow function · javascript · L478-L524 (47 LOC)functions/propertyProcessor.js
function sanitizeForFlutterFlow(obj) {
if (obj === null || obj === undefined) return {};
if (typeof obj !== "object") return obj;
const numericFields = [
"futureValue", "impValue", "totalCosts", "netSaleProceeds", "netReturn", "netROI",
"sellingCosts", "cashNeeded", "loanAmount", "downPayment", "monthlyPayment",
"loanPayments", "loanFees", "permitsFees", "propertyTaxes", "propertyIns",
"price", "livingArea", "bedrooms", "bathrooms", "sequence", "total",
"monthlyRent", "annualRent", "annualNOI", "annualCashFlow", "monthlyCashFlow",
"bestReturn", "bestROI", "strategiesAvailable", "zestimate", "pricePerSqft",
"cashOnCashReturn", "optimalOffer", "avgDollarPerSqft", "avgDollarPerBdrm",
"avgRentPerSqft", "duration", "futureLivingArea", "mtgRate", "extraValue",
"totalValue", "mortgage", "propTaxIns", "yearBuilt", "lotAreaValue",
"propertyIndex", "totalProperties", "rentZestimate", "irr", "roe", "groc", "dscr",
"avgPricePerSqFt", "avgCompPrice"Repobility · severity-and-effort ranking · https://repobility.com
analyzeARVDiscrepancy function · javascript · L4-L86 (83 LOC)functions/scripts/analyze-arv-discrepancy.js
function analyzeARVDiscrepancy() {
console.log("📊 FROM SCREENSHOT:");
console.log("After Repair Value (ARV): $588,821");
console.log("Average $/Comp: $461,245");
console.log("Discrepancy: $127,576 (27.7% higher)");
console.log();
console.log("🔍 POSSIBLE CAUSES OF HIGHER ARV:");
console.log("━".repeat(50));
console.log("\n1️⃣ IMPROVEMENT FACTOR APPLIED:");
console.log("• Fix & Flip uses 1.03 improvement factor (3% premium)");
console.log("• Formula: ARV = Living Area × Price Per SqFt × 1.03");
console.log("• This adds a renovation premium above raw comps");
console.log("\n2️⃣ PRICE PER SQFT vs TOTAL PRICE:");
console.log("• ARV uses: Price Per SqFt × Subject Property Size");
console.log("• Comps average: Total comp prices (different sizes)");
console.log("• If subject is LARGER than average comp, ARV will be higher");
console.log("\n3️⃣ WEIGHTED CALCULATION:");
console.log("• Price per sqft is weighted by similarity to subject");
console.log("• Moseed function · javascript · L44-L82 (39 LOC)functions/seed-scanconfigs.js
async function seed() {
console.log("=== Seeding scanConfigs ===\n");
// 1. Delete existing scanConfigs
const existing = await db.collection("scanConfigs").get();
if (!existing.empty) {
console.log(`Deleting ${existing.size} existing scanConfigs...`);
const batch = db.batch();
existing.forEach((doc) => batch.delete(doc.ref));
await batch.commit();
console.log(" Deleted.\n");
}
// 2. Create new scanConfigs
console.log(`Creating ${MARKETS.length} scanConfigs...`);
const batch = db.batch();
for (const market of MARKETS) {
const docRef = db.collection("scanConfigs").doc();
batch.set(docRef, {
...SHARED_CONFIG,
...market,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
});
console.log(` + ${market.name} (${market.location})`);
}
await batch.commit();
console.log(`\nDone! ${MARKETS.length} scanConfigs created.`);
// 3. Verify
const verify = await db.collection("scanConfigs").where("active", "==",initSSE function · javascript · L1-L34 (34 LOC)functions/sseWriter.js
function initSSE(res) {
res.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": "*",
});
res.flushHeaders();
const keepAlive = setInterval(() => {
res.write(`: keep-alive\n\n`);
// Conditionally flush if available
if (typeof res.flush === "function") res.flush();
}, 5000);
return {
write: (msg) => {
res.write(msg);
if (typeof res.flush === "function") res.flush();
},
writeEvent: (event, data) => {
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
if (typeof res.flush === "function") res.flush();
},
end: () => {
clearInterval(keepAlive);
res.end();
},
flush: () => {
if (typeof res.flush === "function") res.flush();
},
keepAlive: {stop: () => clearInterval(keepAlive)},
};
}ensureInt function · javascript · L4-L6 (3 LOC)functions/strategyCalculator.js
function ensureInt(value) {
return round(Number(value) || 0);
}ensureDouble function · javascript · L9-L11 (3 LOC)functions/strategyCalculator.js
function ensureDouble(value) {
return Number(value) || 0.0;
}calculateStrategy function · javascript · L13-L321 (309 LOC)functions/strategyCalculator.js
function calculateStrategy(method, prop, params, pricePerSqFt, twoBedAvg, bedroomAnalysis = []) {
try {
const purchasePrice = round(Number(prop?.price || 0));
const livingArea = round(Number(prop?.livingArea || 0));
const bedrooms = round(Number(prop?.bedrooms || 0));
const safePricePerSqFt = Number(pricePerSqFt) || 250;
const safeTwoBedAvg = Number(twoBedAvg) || 0;
const config = getConfig(method, params, safeTwoBedAvg);
if (!config) throw new Error(`Invalid strategy method: ${method}`);
const futureLivingArea = calculateFutureLivingArea(method, livingArea, config);
// Override support: Use prop.futureValue or params.futureValue if provided
let futureValue;
if (typeof prop.futureValue !== "undefined" && prop.futureValue !== null) {
futureValue = Number(prop.futureValue);
} else if (typeof params.futureValue !== "undefined" && params.futureValue !== null) {
futureValue = Number(params.futureValue);
} else {
futugetConfig function · javascript · L325-L388 (64 LOC)functions/strategyCalculator.js
function getConfig(method, params = {}, twoBedAvg) {
const base = {
mtgRate: Number(params.interestRate) || 0.06,
salRate: Number(params.salRate) || 0.04,
loanFeesRate: Number(params.loanFeesRate) || 0.01,
permitsFees: Number(params.permitsFees) || 1000,
annualTaxRate: 0.01,
annualInsRate: 0.0035,
};
const strategyConfigs = {
"Fix & Flip": {
...base,
duration: Number(params.fixFlipDuration) || 3,
impFactor: 1.08,
rate: 25,
areaMult: 1,
extraValue: 0,
},
"Add-On": {
...base,
duration: Number(params.addOnDuration) || 6,
impFactor: 1.0,
rate: 150,
addOnArea: 120,
areaMult: 1.2,
extraValue: Number(params.oneBdrmMarketValue) || 0,
},
"ADU": {
...base,
duration: Number(params.aduDuration) || 9,
rate: 200,
aduArea: 750,
aduImpRate: Number(params.aduImpRate) || 300,
impFactor: 0.95,
areaMult: 1.5,
extraValue: Number(twoestimateMonthlyRent function · javascript · L390-L395 (6 LOC)functions/strategyCalculator.js
function estimateMonthlyRent(propertyValue, state = 'CA') {
const safeValue = Number(propertyValue) || 0;
// California uses 0.5% rule, all other states use 1% rule for rent estimation
const rentRate = (state === 'CA' || state === 'California') ? 0.005 : 0.01;
return round(safeValue * rentRate);
}Repobility — same analyzer, your code, free for public repos · /scan/
calculateFutureLivingArea function · javascript · L397-L402 (6 LOC)functions/strategyCalculator.js
function calculateFutureLivingArea(method, baseArea, config) {
if (method === "Add-On") return baseArea + config.addOnArea;
if (method === "ADU") return baseArea + config.aduArea;
if (method === "New Build") return round(baseArea * config.areaMult);
return baseArea;
}estimateFutureValue function · javascript · L404-L561 (158 LOC)functions/strategyCalculator.js
function estimateFutureValue(method, futureArea, pricePerSqFt, config, bedrooms, bedroomAnalysis, filteredComps, subjectProperty) {
if (method === "Add-On") {
// Check if Add-On will physically fit on the lot before valuation
const addOnFeasibility = checkAddOnFeasibility(subjectProperty, config);
if (!addOnFeasibility.feasible) {
config.valuationMethod = "addon_lot_size_insufficient";
return -1; // Signal Add-On strategy rejection
}
// Add-On calculation: Use target bedroom comps for total futureValue
const targetBedrooms = Math.min(5, bedrooms + 1);
// Apply bedroom ceiling cap for 4+ bedroom properties (diminishing returns)
const maxIncrementalValue = {
4: 50000, // Adding 4th bedroom: up to $50k value
5: 40000, // Adding 5th bedroom: up to $40k value
6: 25000, // Adding 6th bedroom: up to $25k value (limited market)
7: 15000, // Adding 7th+ bedroom: up to $15k value
};
if (targetBedrooms >= 4 && macalculateSizeAdjustedPrice function · javascript · L563-L591 (29 LOC)functions/strategyCalculator.js
function calculateSizeAdjustedPrice(futureArea, basePricePerSqFt, filteredComps, config) {
// Calculate average comp size for comparison
if (!filteredComps || filteredComps.length === 0) {
return basePricePerSqFt; // No adjustment if no comps available
}
const avgCompSize = filteredComps.reduce((sum, comp) => {
const sqft = extractCompSqftStrategy(comp);
return sum + (sqft || 0);
}, 0) / filteredComps.length;
if (avgCompSize <= 0) {
return basePricePerSqFt; // No adjustment if invalid comp sizes
}
// Size-based price adjustment: larger homes get slight discount
const sizeDeviation = (futureArea - avgCompSize) / avgCompSize;
// Market research shows: every 25% size increase = 5% price/sqft decrease
// This reflects economies of scale in larger homes
const priceAdjustment = -sizeDeviation * 0.20; // 20% adjustment per 100% size change
// Cap adjustment to prevent extreme values
const cappedAdjustment = Math.max(-0.15, Math.min(0.15, priceAcalculateImprovementCost function · javascript · L593-L600 (8 LOC)functions/strategyCalculator.js
function calculateImprovementCost(method, futureArea, config) {
if (method === "ADU") return config.aduImpRate * 750;
if (method === "Add-On") {
return round(config.addOnArea * config.rate); // Only bedroom addition cost
}
if (method === "Rental") return 0;
return round(futureArea * config.rate);
}calculateLoan function · javascript · L602-L628 (27 LOC)functions/strategyCalculator.js
function calculateLoan(futureValue, purchasePrice, impValue, config) {
const safeFutureValue = Number(futureValue) || 0;
const safePurchasePrice = Number(purchasePrice) || 0;
const safeImpValue = Number(impValue) || 0;
const safeMtgRate = Number(config.mtgRate) || 0.06;
const safeDuration = Number(config.duration) || 12;
const safeLoanFeesRate = Number(config.loanFeesRate) || 0.01;
const safeAnnualTaxRate = Number(config.annualTaxRate) || 0.01;
const safeAnnualInsRate = Number(config.annualInsRate) || 0.005;
const totalProjectCost = safePurchasePrice + safeImpValue;
const maxLtcLoan = totalProjectCost * 0.90;
const maxArvLoan = safeFutureValue * 0.75;
const loanAmount = round(Math.min(maxLtcLoan, maxArvLoan));
const downPayment = round(totalProjectCost - loanAmount);
const mortgage = totalProjectCost - downPayment;
const monthlyPayment = round(loanAmount * (safeMtgRate / 12));
const loanPayments = round(monthlyPayment * safeDuration);
const loanFees calculateTotalCosts function · javascript · L630-L643 (14 LOC)functions/strategyCalculator.js
function calculateTotalCosts({
purchasePrice,
impValue,
loanPayments,
loanFees,
permitsFees,
propertyTaxes,
propertyIns,
sellingCosts,
}) {
return Number(purchasePrice || 0) + Number(impValue || 0) + Number(loanPayments || 0) +
Number(loanFees || 0) + Number(permitsFees || 0) + Number(propertyTaxes || 0) +
Number(propertyIns || 0) + Number(sellingCosts || 0);
}calculateClosingDates function · javascript · L645-L658 (14 LOC)functions/strategyCalculator.js
function calculateClosingDates(durationMonths) {
const safeDuration = Number(durationMonths) || 12;
const purchaseDate = new Date();
purchaseDate.setDate(purchaseDate.getDate() + 21);
const saleDate = new Date(purchaseDate);
saleDate.setMonth(saleDate.getMonth() + safeDuration);
return {
purchase: purchaseDate.toISOString().split("T")[0],
sale: saleDate.toISOString().split("T")[0],
};
}calculateIRR function · javascript · L670-L681 (12 LOC)functions/strategyCalculator.js
function calculateIRR(strategyResult, params = {}, holdingPeriod = null) {
const {method} = strategyResult;
// Determine holding period (convert months to years)
const duration = holdingPeriod || (getDurationMonths(method, params) / 12);
if (method === "Rental") {
return calculateRentalIRR(strategyResult, duration);
} else {
return calculateDevelopmentIRR(strategyResult, duration);
}
}Repobility · code-quality intelligence · https://repobility.com
calculateRentalIRR function · javascript · L689-L726 (38 LOC)functions/strategyCalculator.js
function calculateRentalIRR(strategyResult, holdingPeriodYears) {
const {
downPayment,
permitsFees,
propertyTaxes,
annualCashFlow,
futureValue,
loanAmount,
} = strategyResult;
// Initial investment (negative cash flow)
const initialInvestment = -(downPayment + permitsFees + propertyTaxes);
// Annual cash flows during holding period
const annualCashFlows = Array(Math.floor(holdingPeriodYears)).fill(annualCashFlow);
// Final year: annual cash flow + sale proceeds - remaining loan balance
const loanBalance = calculateRemainingLoanBalance(loanAmount, strategyResult.mtgRate, holdingPeriodYears);
const saleProceeds = futureValue * (1 + 0.035) ** holdingPeriodYears; // 3.5% annual appreciation
const sellingCosts = saleProceeds * 0.06; // 6% selling costs
const finalCashFlow = annualCashFlow + (saleProceeds - sellingCosts - loanBalance);
// Construct complete cash flow array
const cashFlows = [initialInvestment, ...annualCashFlows.slice(0, calculateDevelopmentIRR function · javascript · L734-L777 (44 LOC)functions/strategyCalculator.js
function calculateDevelopmentIRR(strategyResult, holdingPeriodYears) {
const {
downPayment,
permitsFees,
propertyTaxes,
monthlyPayment,
futureValue,
sellingCosts,
loanFees,
propertyIns,
} = strategyResult;
// Initial investment
const initialInvestment = -(downPayment + permitsFees + propertyTaxes + loanFees);
// Monthly carrying costs during development/holding period
const monthlyCarryingCosts = -(monthlyPayment + (propertyIns / 12));
const totalMonths = Math.floor(holdingPeriodYears * 12);
// Final cash flow: sale proceeds minus selling costs
const finalCashFlow = futureValue - sellingCosts;
// Construct monthly cash flow array
const cashFlows = [
initialInvestment,
...Array(totalMonths - 1).fill(monthlyCarryingCosts),
finalCashFlow + monthlyCarryingCosts, // Last month includes sale
];
// Calculate IRR on monthly basis, then annualize
const monthlyIRR = calculateIRRFromCashFlows(cashFlows);
const annualIRcalculateIRRFromCashFlows function · javascript · L784-L817 (34 LOC)functions/strategyCalculator.js
function calculateIRRFromCashFlows(cashFlows) {
const maxIterations = 1000;
const tolerance = 1e-10;
let rate = 0.1; // Initial guess: 10%
for (let i = 0; i < maxIterations; i++) {
const npv = calculateNPV(cashFlows, rate);
const npvDerivative = calculateNPVDerivative(cashFlows, rate);
if (Math.abs(npv) < tolerance) {
return rate;
}
if (Math.abs(npvDerivative) < tolerance) {
// Derivative too small, try different starting point
rate = Math.random() * 0.5;
continue;
}
const newRate = rate - (npv / npvDerivative);
// Prevent negative or extremely high rates
if (newRate < -0.99) {
rate = -0.95;
} else if (newRate > 10) {
rate = 1.0;
} else {
rate = newRate;
}
}
// If Newton-Raphson fails, try bisection method
return calculateIRRBisection(cashFlows);
}calculateNPV function · javascript · L825-L829 (5 LOC)functions/strategyCalculator.js
function calculateNPV(cashFlows, rate) {
return cashFlows.reduce((npv, cashFlow, period) => {
return npv + (cashFlow / Math.pow(1 + rate, period));
}, 0);
}calculateNPVDerivative function · javascript · L837-L842 (6 LOC)functions/strategyCalculator.js
function calculateNPVDerivative(cashFlows, rate) {
return cashFlows.reduce((derivative, cashFlow, period) => {
if (period === 0) return derivative;
return derivative - (period * cashFlow / Math.pow(1 + rate, period + 1));
}, 0);
}calculateIRRBisection function · javascript · L851-L875 (25 LOC)functions/strategyCalculator.js
function calculateIRRBisection(cashFlows, lowRate = -0.99, highRate = 10) {
const tolerance = 1e-10;
const maxIterations = 1000;
for (let i = 0; i < maxIterations; i++) {
const midRate = (lowRate + highRate) / 2;
const npv = calculateNPV(cashFlows, midRate);
if (Math.abs(npv) < tolerance) {
return midRate;
}
if (calculateNPV(cashFlows, lowRate) * npv < 0) {
highRate = midRate;
} else {
lowRate = midRate;
}
if (Math.abs(highRate - lowRate) < tolerance) {
return (lowRate + highRate) / 2;
}
}
return NaN; // IRR not found
}calculateRemainingLoanBalance function · javascript · L884-L901 (18 LOC)functions/strategyCalculator.js
function calculateRemainingLoanBalance(loanAmount, annualRate, yearsElapsed) {
const monthlyRate = annualRate / 12;
const totalPayments = 30 * 12; // Assume 30-year amortization
const paymentsElapsed = yearsElapsed * 12;
if (paymentsElapsed >= totalPayments) {
return 0; // Loan fully paid off
}
// const monthlyPayment = loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, totalPayments)) /
// (Math.pow(1 + monthlyRate, totalPayments) - 1);
const remainingBalance = loanAmount *
(Math.pow(1 + monthlyRate, totalPayments) - Math.pow(1 + monthlyRate, paymentsElapsed)) /
(Math.pow(1 + monthlyRate, totalPayments) - 1);
return Math.max(0, remainingBalance);
}getDurationMonths function · javascript · L909-L919 (11 LOC)functions/strategyCalculator.js
function getDurationMonths(method, params) {
const durations = {
"Fix & Flip": params.fixFlipDuration || 3,
"Add-On": params.addOnDuration || 6,
"ADU": params.aduDuration || 9,
"New Build": params.newBuildDuration || 12,
"Rental": 12, // Default to 1 year for analysis
};
return durations[method] || 12;
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
enhanceStrategyWithIRR function · javascript · L928-L936 (9 LOC)functions/strategyCalculator.js
function enhanceStrategyWithIRR(strategyResult, params = {}, holdingPeriod = null) {
const irrAnalysis = calculateIRR(strategyResult, params, holdingPeriod);
return {
...strategyResult,
irrAnalysis,
irr: irrAnalysis.irr,
};
}calculateIncrementalBedroomValue function · javascript · L941-L984 (44 LOC)functions/strategyCalculator.js
function calculateIncrementalBedroomValue(currentBeds, targetBeds, filteredComps, subjectProperty, futureArea) {
// Calculate the incremental value difference between current and target bedroom tiers
// Step 1: Get current bedroom tier value from filtered comps
const currentBedroomValue = calculateBedroomTierValue(currentBeds, filteredComps, subjectProperty.livingArea);
// Step 2: Get target bedroom tier value from filtered comps
const targetBedroomValue = calculateBedroomTierValue(targetBeds, filteredComps, futureArea);
// Step 3: Calculate incremental difference with multiple restrictions
if (currentBedroomValue > 0 && targetBedroomValue > 0) {
const rawIncrementalValue = targetBedroomValue - currentBedroomValue;
// Apply multiple caps to prevent excessive increments
const subjectPrice = subjectProperty.price || 500000;
// Cap 1: Absolute dollar caps based on property tier
let maxAbsoluteIncrement;
if (subjectPrice >= 1500000) maxAbsoluteInccalculateBedroomTierValue function · javascript · L986-L1033 (48 LOC)functions/strategyCalculator.js
function calculateBedroomTierValue(bedrooms, filteredComps, referenceArea) {
// Get comps matching the specified bedroom count
const bedroomComps = (filteredComps || []).filter((comp) => {
const compBeds = comp.beds || 0;
return compBeds === bedrooms;
});
if (bedroomComps.length < 2) {
return 0;
}
// Weight comps by size similarity to reference area
const weightedComps = bedroomComps.map((comp) => {
const compPrice = extractCompPriceStrategy(comp);
const compSqft = extractCompSqftStrategy(comp);
if (!compPrice || !compSqft || compPrice <= 0 || compSqft <= 0) {
return null;
}
// Size similarity to reference area
const sizeSimilarity = referenceArea > 0 ?
1 - Math.abs(compSqft - referenceArea) / Math.max(compSqft, referenceArea) : 0.5;
// Additional market reasonableness check
const pricePerSqft = compPrice / compSqft;
if (pricePerSqft < 50 || pricePerSqft > 800) {
return null;
}
const weight = calculateFallbackBedroomIncrement function · javascript · L1035-L1045 (11 LOC)functions/strategyCalculator.js
function calculateFallbackBedroomIncrement(subjectProperty) {
// Very conservative fixed increments - based on realistic bedroom value premiums
const propertyValue = subjectProperty.price || 500000;
// Reduced increments to be more realistic (roughly 3-8% of property value)
if (propertyValue >= 1500000) return 45000; // $45k increment for high-end (3%)
if (propertyValue >= 1000000) return 40000; // $40k increment for upper-mid (4%)
if (propertyValue >= 700000) return 35000; // $35k increment for mid-range (5%)
if (propertyValue >= 500000) return 30000; // $30k increment for lower-mid (6%)
return 25000; // $25k increment for budget (5-8%)
}calculateBedroomPremium function · javascript · L1047-L1061 (15 LOC)functions/strategyCalculator.js
function calculateBedroomPremium(currentBeds, targetBeds) {
// Conservative bedroom premium calculation
const bedroomDiff = targetBeds - currentBeds;
if (bedroomDiff <= 0) return 0;
// Diminishing returns: first bedroom adds more value
const basePremiums = [0.15, 0.10, 0.08, 0.05]; // 15%, 10%, 8%, 5%
let totalPremium = 0;
for (let i = 0; i < Math.min(bedroomDiff, basePremiums.length); i++) {
totalPremium += basePremiums[i];
}
return Math.min(totalPremium, 0.25); // Cap at 25% premium
}calculateADUPremium function · javascript · L1063-L1070 (8 LOC)functions/strategyCalculator.js
function calculateADUPremium(mainHouseSqft) {
// More attractive ADU premiums to show more opportunities
// Larger houses get smaller relative premium from ADU
if (mainHouseSqft >= 3000) return 0.20; // 20% for large houses (was 15%)
if (mainHouseSqft >= 2000) return 0.25; // 25% for medium houses (was 20%)
if (mainHouseSqft >= 1500) return 0.30; // 30% for typical houses (was 25%)
return 0.35; // 35% for smaller houses (was 30%)
}checkADULotFeasibility function · javascript · L1072-L1174 (103 LOC)functions/strategyCalculator.js
function checkADULotFeasibility(subjectProperty, config) {
const lotAreaSqft = subjectProperty.lotAreaValue || 0;
const mainHouseSqft = subjectProperty.livingArea || 0;
const aduSqft = config.aduArea || 750;
// Basic lot area check - no lot area data
if (!lotAreaSqft || lotAreaSqft <= 0) {
return {
feasible: true, // Default to feasible if no lot data (to avoid blocking all ADUs)
reason: "Lot size unknown - assuming feasible",
details: {
lotArea: lotAreaSqft,
mainHouse: mainHouseSqft,
aduSize: aduSqft,
availableSpace: "unknown"
}
};
}
// Minimum lot size requirements for ADU
const minLotSizeForADU = 5000; // 5,000 sqft minimum lot
if (lotAreaSqft < minLotSizeForADU) {
return {
feasible: false,
reason: `Lot too small: ${lotAreaSqft} sqft < ${minLotSizeForADU} sqft minimum`,
details: {
lotArea: lotAreaSqft,
minRequired: minLotSizeForADU,
shortage: minLotSizeFocheckAddOnFeasibility function · javascript · L1176-L1240 (65 LOC)functions/strategyCalculator.js
function checkAddOnFeasibility(subjectProperty, config) {
const lotAreaSqft = subjectProperty.lotAreaValue || 0;
const mainHouseSqft = subjectProperty.livingArea || 0;
const addOnArea = config.addOnArea || 120;
// Basic lot area check - no lot area data
if (!lotAreaSqft || lotAreaSqft <= 0) {
return {
feasible: true, // Default to feasible if no lot data
reason: "Lot size unknown - assuming feasible",
details: {
lotArea: lotAreaSqft,
mainHouse: mainHouseSqft,
addOnSize: addOnArea,
availableSpace: "unknown"
}
};
}
// Calculate available space considering setbacks and main house footprint
const setbackArea = lotAreaSqft * 0.45; // 45% of lot for setbacks
// Use actual levels data if available, otherwise estimate
const levels = subjectProperty.levels || null;
let houseFootprint;
// Validate levels data is reasonable (1-4 stories for residential)
if (levels && levels >= 1 && levels <= 4) {
// Repobility · severity-and-effort ranking · https://repobility.com
calculateADUIncomeValue function · javascript · L1242-L1286 (45 LOC)functions/strategyCalculator.js
function calculateADUIncomeValue(subjectProperty, filteredComps, config, state = 'CA') {
// Get 2BR rent from filtered comps or subject property
let twoBedRentZestimate = 0;
const propertyState = String(state);
// Try to find 2BR rent from subject property
if (subjectProperty.rentZestimate && subjectProperty.bedrooms === 2) {
twoBedRentZestimate = subjectProperty.rentZestimate;
} else {
// Find 2BR rent from comps
const twoBedComps = (filteredComps || []).filter(comp => comp.beds === 2);
if (twoBedComps.length > 0) {
const avgTwoBedPrice = twoBedComps.reduce((sum, comp) => {
const price = extractCompPriceStrategy(comp);
return sum + (price || 0);
}, 0) / twoBedComps.length;
// Estimate rent using state-based rule: CA = 0.5%, Other = 1%
const rentRate = (propertyState === 'CA' || propertyState === 'California') ? 0.005 : 0.01;
const rentRatePercent = (propertyState === 'CA' || propertyState === 'California') ? 'valueHousePlusADU_MarketHouse_IncomeADU function · javascript · L1288-L1350 (63 LOC)functions/strategyCalculator.js
function valueHousePlusADU_MarketHouse_IncomeADU({
currentMarketValue,
twoBedRentZestimate,
capRate,
options = {}
}) {
if (!Number.isFinite(twoBedRentZestimate) || twoBedRentZestimate <= 0) {
throw new Error("twoBedRentZestimate must be a positive monthly rent.");
}
if (!Number.isFinite(capRate) || capRate <= 0) {
throw new Error("capRate must be a positive decimal (e.g., 0.055 for 5.5%).");
}
const {
aduBedrooms = 1,
aduRentFactorMap = {
0: 0.55, // studio ~55% of 2BR
1: 0.65, // 1BR ~65% of 2BR
2: 0.80, // 2BR ~80% of 2BR
3: 0.95, // 3BR ~95% of 2BR
},
aduOverrideMonthlyRent,
vacancyRate = 0.05,
expenseRatio = 0.30,
otherMonthlyIncome = 0,
otherMonthlyOpEx = 0,
} = options;
const derivedADURent = (() => {
if (aduOverrideMonthlyRent != null && Number.isFinite(aduOverrideMonthlyRent)) {
return aduOverrideMonthlyRent;
}
const factor = aduRentFactorMap[aduBedrooms] ?? aduRentFaestimateADUMarketRent function · javascript · L1352-L1410 (59 LOC)functions/strategyCalculator.js
function estimateADUMarketRent(subjectProperty, filteredComps) {
// Method 1: Use subject property rent-to-price ratio if available
if (subjectProperty.rentZestimate && subjectProperty.price) {
const subjectRentRatio = subjectProperty.rentZestimate / subjectProperty.price;
// Apply ratio to 2BR comp prices to estimate their rents
const twoBedComps = (filteredComps || []).filter((comp) => {
const compBeds = comp.beds || 0;
return compBeds === 2;
});
if (twoBedComps.length >= 2) {
let totalEstimatedRent = 0;
let validRentEstimates = 0;
twoBedComps.forEach((comp) => {
const compPrice = extractCompPriceStrategy(comp);
if (compPrice > 0) {
const estimatedRent = compPrice * subjectRentRatio;
if (estimatedRent >= 500 && estimatedRent <= 10000) { // Loosened rent range
totalEstimatedRent += estimatedRent;
validRentEstimates++;
}
}
});
if (validRentcalculateMarketGRM function · javascript · L1412-L1436 (25 LOC)functions/strategyCalculator.js
function calculateMarketGRM(subjectProperty, filteredComps) {
// Calculate Gross Rent Multiplier from subject property if rent data available
if (subjectProperty.rentZestimate && subjectProperty.price) {
const monthlyRent = subjectProperty.rentZestimate;
const propertyPrice = subjectProperty.price;
const subjectGRM = round(propertyPrice / monthlyRent);
// Validate GRM is reasonable (expanded range for current market conditions)
if (subjectGRM >= 5 && subjectGRM <= 300) { // Expanded range to accommodate varying rent data quality
return subjectGRM;
}
}
// Fallback: Use market-typical GRM based on property characteristics
const propertyValue = subjectProperty.price || 500000;
let marketGRM = 12; // Default
if (propertyValue >= 1500000) marketGRM = 15; // High-value areas (lower yields)
else if (propertyValue >= 1000000) marketGRM = 13;
else if (propertyValue >= 700000) marketGRM = 12;
else if (propertyValue >= 500000) marketGRM = 11;
estimateCapRate function · javascript · L1438-L1450 (13 LOC)functions/strategyCalculator.js
function estimateCapRate(subjectProperty) {
// Estimate capitalization rate based on property value and location characteristics
const propertyValue = subjectProperty.price || 500000;
let capRate = 0.05; // Default 5%
if (propertyValue >= 1500000) capRate = 0.04; // 4% for high-value areas
else if (propertyValue >= 1000000) capRate = 0.045; // 4.5%
else if (propertyValue >= 700000) capRate = 0.05; // 5%
else if (propertyValue >= 500000) capRate = 0.055; // 5.5%
else capRate = 0.06; // 6% for lower-value areas
return capRate;
}extractCompPriceStrategy function · javascript · L1452-L1455 (4 LOC)functions/strategyCalculator.js
function extractCompPriceStrategy(comp) {
return comp.data?.aboveTheFold?.addressSectionInfo?.priceInfo?.amount ||
(typeof comp.price === "object" ? comp.price?.value : comp.price) || 0;
}extractCompSqftStrategy function · javascript · L1457-L1460 (4 LOC)functions/strategyCalculator.js
function extractCompSqftStrategy(comp) {
return comp.data?.aboveTheFold?.addressSectionInfo?.sqFt?.value ||
(typeof comp.sqFt === "object" ? comp.sqFt?.value : comp.sqFt) || 0;
}testGA4Connection function · javascript · L9-L75 (67 LOC)functions/test-ga4-connection.js
async function testGA4Connection() {
console.log('🧪 Testing GA4 Connection...\n');
try {
// Get config from .env
const propertyId = process.env.GA4_PROPERTY_ID;
const keyPath = process.env.GA4_SERVICE_ACCOUNT_PATH;
console.log('📊 Configuration:');
console.log(` Property ID: ${propertyId}`);
console.log(` Key Path: ${keyPath}`);
console.log('');
// Initialize client
console.log('🔌 Initializing GA4 client...');
initializeGA4Client(keyPath);
console.log('✅ Client initialized\n');
// Test connection
console.log('🔍 Testing connection to GA4...');
const connectionResult = await testConnection(propertyId);
if (!connectionResult.success) {
console.error('❌ Connection failed:', connectionResult.error);
process.exit(1);
}
console.log('✅ Connection successful!');
console.log(` Property ID: ${connectionResult.propertyId}`);
console.log(` Row count: ${connectionResult.rowCount}`);
console.lRepobility — same analyzer, your code, free for public repos · /scan/
withTimeout function · javascript · L23-L30 (8 LOC)functions/trigger-scan.js
function withTimeout(promise, ms, label) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms: ${label}`)), ms),
),
]);
}loadUserCalcParams function · javascript · L32-L46 (15 LOC)functions/trigger-scan.js
async function loadUserCalcParams(db, email) {
const snap = await db.collection("UserData")
.where("email", "==", email).limit(1).get();
if (snap.empty) return {};
const u = snap.docs[0].data();
const params = {};
if (u.interestRate) params.interestRate = u.interestRate;
if (u.closingCostRate) params.closingCostRate = u.closingCostRate;
if (u.improvementRate) params.improvementRate = u.improvementRate;
if (u.salRate) params.salRate = u.salRate;
if (u.loanFeesRate) params.loanFeesRate = u.loanFeesRate;
if (u.permitsFees) params.permitsFees = u.permitsFees;
if (u.minimumReturn) params.minReturn = u.minimumReturn;
return params;
}runScan function · javascript · L48-L195 (148 LOC)functions/trigger-scan.js
async function runScan() {
console.log("[Scan] Starting manual scan...\n");
const startTime = Date.now();
const configsSnap = await db.collection("scanConfigs")
.where("active", "==", true).get();
if (configsSnap.empty) {
console.log("[Scan] No active scan configs!");
return;
}
console.log(`[Scan] Found ${configsSnap.size} active configs\n`);
// Dedup against recent alerts
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const existingSnap = await db.collection("dealAlerts")
.where("scannedAt", ">=", weekAgo).get();
const existingKeys = new Set();
existingSnap.docs.forEach((d) => {
const data = d.data();
existingKeys.add(`${data.zpid}_${data.method}`);
});
console.log(`[Scan] ${existingKeys.size} existing alerts (dedup)\n`);
const notifyEmails = new Set();
for (const doc of configsSnap.docs) {
const cfg = doc.data();
if (cfg.notifyEmail) notifyEmails.add(cfg.notifyEmail);
}
const primaryEmail = [...no