← back to AbramsOS

lib/home-value.js

38 lines

// home-value.js — optional automatic property valuation via RentCast AVM.
//
// One-key-from-working: set RENTCAST_API_KEY (free tier = 50 lookups/mo at
// rentcast.io) and the Assets "Estimate" button fills current_value from RentCast's
// automated valuation model. No key → graceful "not configured" (never fabricates a value).
// Zillow's Zestimate API is retired; RentCast is the current no-cost option.

function isConfigured() {
  return Boolean(process.env.RENTCAST_API_KEY);
}

async function estimateValue(address) {
  const key = process.env.RENTCAST_API_KEY;
  if (!key) return { ok: false, reason: 'no_key' };
  if (!address) return { ok: false, reason: 'no_address' };
  const url = `https://api.rentcast.io/v1/avm/value?address=${encodeURIComponent(address)}`;
  try {
    const ctrl = new AbortController();
    const t = setTimeout(() => ctrl.abort(), 12000);
    const r = await fetch(url, { headers: { 'X-Api-Key': key, accept: 'application/json' }, signal: ctrl.signal });
    clearTimeout(t);
    if (!r.ok) return { ok: false, reason: `http_${r.status}` };
    const j = await r.json();
    const price = Number(j.price ?? j.value);
    if (!Number.isFinite(price)) return { ok: false, reason: 'no_price' };
    return {
      ok: true,
      price: Math.round(price),
      low: Number.isFinite(Number(j.priceRangeLow)) ? Math.round(Number(j.priceRangeLow)) : null,
      high: Number.isFinite(Number(j.priceRangeHigh)) ? Math.round(Number(j.priceRangeHigh)) : null,
    };
  } catch (e) {
    return { ok: false, reason: e.message };
  }
}

module.exports = { isConfigured, estimateValue };