← back to Commercialrealestate

scripts/crcp-leads.js

45 lines

// crcp-leads.js — P2 assumable-loan LEAD ENGINE (docs/TOOL-SPEC.md). The Pro-tier money feature for a
// loan officer: rank FHA-financed multifamily by ASSUMABLE ADVANTAGE (how far the loan's rate sits below
// the market rate at origination — a buyer can assume that below-market note). 100% HUD public data, $0.
// Mount from serve.js AFTER accounts:  require('./crcp-leads')(app, ROOT, acct.userOf);
'use strict';
const fs = require('fs');
const path = require('path');

module.exports = function mountLeads(app, ROOT, userOf) {
  const FHA = path.join(ROOT, 'data', 'fha-loans.json');
  const load = () => { try { return JSON.parse(fs.readFileSync(FHA, 'utf8')).rows || []; } catch { return []; } };

  // GET /api/leads/assumable?state=CA&city=&limit=100&minSpread=1
  // Ranked assumable-loan targets. Gated to signed-in users (it's the Pro feature); free tier gets a taste.
  app.get('/api/leads/assumable', (req, res) => {
    const u = userOf && userOf(req);
    const rows = load();
    const state = (req.query.state || 'CA').toUpperCase();
    const city = (req.query.city || '').toUpperCase();
    const minSpread = req.query.minSpread != null ? +req.query.minSpread : 0.5;
    let leads = rows
      .filter(r => r.rate != null && r.pmmsBenchmark != null && (!state || r.state === state) && (!city || String(r.city || '').toUpperCase() === city))
      .map(r => ({
        property: r.property, city: r.city, state: r.state, zip: r.zip, units: r.units,
        loan_amount: r.originalAmount, rate: r.rate, market_at_origination: r.pmmsBenchmark,
        assumable_spread: +(r.pmmsBenchmark - r.rate).toFixed(2),   // pts below market = the pitch
        below_market: !!r.belowMarket, originated: r.originationDate, holder: r.holder,
        source: 'HUD/FHA Multifamily Insured Mortgages (public, resellable)',
      }))
      .filter(l => l.assumable_spread >= minSpread)
      .sort((a, b) => b.assumable_spread - a.assumable_spread);
    const total = leads.length;
    // Free/anon get a 5-row taste; signed-in get the full ranked list (capped by limit).
    const paid = u && u.email;
    const limit = paid ? Math.min(+req.query.limit || 200, 1000) : 5;
    res.json({
      total, returned: Math.min(total, limit), tier: paid ? (u.tier || 'free') : 'anon',
      teaser: !paid, leads: leads.slice(0, limit),
      note: paid ? undefined : 'Sign in for the full ranked assumable-lead list.',
    });
  });

  console.log('[crcp-leads] assumable-loan lead engine mounted (/api/leads/assumable)');
};