← back to Commercialrealestate

scripts/sources/title-records.js

79 lines

// sources/title-records.js — credentialed adapter for TITLE-COMPANY recorded-document search.
//
// PURPOSE: the authoritative answer to "does this property carry an assumable FHA/VA loan" lives in the
// recorded deed of trust (lender, loan type, recording date; rate inferable from program + date). Title
// companies expose this via systems like TitlePro247, Property360, DataTrace, ResWare, or SoftPro. Steve
// can likely get a login. Until that credential is dropped in, this adapter is INERT and returns
// {available:false, needs:'<exact login Steve must provide>'} so callers fall back to the heuristic.
//
// PLUG-IN CONTRACT (zero rework when credentialed):
//   status()                       -> { available:boolean, needs?:string, provider?:string }
//   getRecordedLoans({address,city,zip})
//                                  -> { available:false, needs:string }                        // not configured
//                                  -> { available:true, matched:boolean, loans:RecordedLoan[] } // configured
//   RecordedLoan = { lender, type ('FHA'|'VA'|'Conventional'|...), recordedDate (ISO), origAmount (number), rate? }
//
// The assumable heuristic calls getRecordedLoans() FIRST for every property and only falls back to the
// heuristic when {available:false}. So the moment the title login is configured, real recorded loans
// flip the estimate from "heuristic" to "title-verified" with no caller changes.
//
// GATED: the title login is Steve-provided. This file fabricates NOTHING — with no credential it returns
// {available:false}. Do not stub fake loans here.
'use strict';

// Credential slot. Resolved from env first, then the project .env. The exact var Steve must provide.
const CREDENTIAL_ENV = 'TITLE_RECORDS_API_KEY';                // primary credential (API key or session token)
const PROVIDER_ENV   = 'TITLE_RECORDS_PROVIDER';              // e.g. 'titlepro247' | 'datatrace' | 'property360'
const BASE_URL_ENV   = 'TITLE_RECORDS_BASE_URL';             // provider API base, when applicable

function readCredential() {
  const fromEnv = process.env[CREDENTIAL_ENV];
  if (fromEnv) return { key: fromEnv, provider: process.env[PROVIDER_ENV] || 'unknown', baseUrl: process.env[BASE_URL_ENV] || null };
  // (Optional) fall back to a .env line without taking a dotenv dependency.
  try {
    const fs = require('fs'), path = require('path');
    const env = fs.readFileSync(path.join(__dirname, '..', '..', '.env'), 'utf8');
    const m = env.match(new RegExp('^' + CREDENTIAL_ENV + '=(.*)$', 'm'));
    if (m) return {
      key: m[1].replace(/['"]/g, '').trim(),
      provider: (env.match(new RegExp('^' + PROVIDER_ENV + '=(.*)$', 'm')) || [])[1]?.replace(/['"]/g, '').trim() || 'unknown',
      baseUrl: (env.match(new RegExp('^' + BASE_URL_ENV + '=(.*)$', 'm')) || [])[1]?.replace(/['"]/g, '').trim() || null
    };
  } catch (_) {}
  return null;
}

const NEEDS = `a title-company recorded-document login — a ${CREDENTIAL_ENV} (API key or session token) for one of `
  + `TitlePro247 / Property360 / DataTrace / ResWare / SoftPro, plus ${PROVIDER_ENV} (which system) and `
  + `${BASE_URL_ENV} (the API base URL). Steve provides the login; set these in .env.`;

async function status() {
  const cred = readCredential();
  if (!cred) return { available: false, needs: NEEDS };
  return { available: true, provider: cred.provider };
}

// Look up recorded deeds of trust for one address. Returns the loans the title system has on file.
// While uncredentialed, returns {available:false} — callers fall back to the heuristic.
async function getRecordedLoans({ address, city, zip } = {}) {
  const cred = readCredential();
  if (!cred) return { available: false, needs: NEEDS };

  // --- INTEGRATION POINT (wire when the title login exists) -------------------------------------------
  // Each provider differs; the shape returned to the caller is fixed (RecordedLoan[]). Example skeleton:
  //
  //   const url = `${cred.baseUrl}/search?address=${encodeURIComponent(address)}&zip=${zip}`;
  //   const r = await fetch(url, { headers: { Authorization: `Bearer ${cred.key}` } });
  //   const docs = (await r.json()).documents || [];
  //   const loans = docs.filter(d => /deed of trust/i.test(d.docType)).map(d => ({
  //     lender: d.beneficiary, type: classifyLoanType(d), recordedDate: d.recordedDate,
  //     origAmount: d.amount, rate: null /* inferred downstream from program+date */ }));
  //   return { available: true, matched: loans.length > 0, loans };
  //
  // Until then, signal "configured but not yet wired" so it's obvious the credential landed but the
  // provider mapping is the remaining work.
  return { available: true, matched: false, loans: [], note: `credential present for ${cred.provider}; provider mapping not yet wired` };
}

module.exports = { status, getRecordedLoans, CREDENTIAL_ENV, PROVIDER_ENV, BASE_URL_ENV, NEEDS };