← back to donspeedie__realdeal

Function bodies 338 total

All specs Real LLM only Function bodies
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 withinMarket
calculateWeightedPricePerSqft 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 Mat
sanitizeForFlutterFlow 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("• Mo
seed 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 {
      futu
getConfig 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(two
estimateMonthlyRent 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 && ma
calculateSizeAdjustedPrice 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, priceA
calculateImprovementCost 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 annualIR
calculateIRRFromCashFlows 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) maxAbsoluteInc
calculateBedroomTierValue 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: minLotSizeFo
checkAddOnFeasibility 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] ?? aduRentFa
estimateADUMarketRent 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 (validRent
calculateMarketGRM 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.l
Repobility — 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
‹ prevpage 6 / 7next ›