← 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 };