← back to Commercialrealestate

scripts/sources/appraisal-panel.js

60 lines

// sources/appraisal-panel.js — credentialed adapter for Steve's APPRAISAL PANEL.
//
// PURPOSE: Steve has direct access to an appraisal panel and can get near-guaranteed supportable values
// on LA-area properties — the gold standard versus our Census-rent / closed-sale value estimates. This
// adapter is the plug-in slot: once the panel's API/login is configured, getSupportableValue(address)
// returns a real supportable value + confidence; until then it is INERT and returns {available:false}.
//
// PLUG-IN CONTRACT:
//   status()                  -> { available:boolean, needs?:string, provider?:string }
//   getSupportableValue({address,city,zip})
//                             -> { available:false, needs:string }                          // not configured
//                             -> { available:true, matched:boolean, value?:number,
//                                  confidence?:'supportable'|'indicative', asOf?:ISO, basis?:string }
//
// GATED: the appraisal-panel access is Steve-provided. Fabricates NOTHING with no credential.
'use strict';

const CREDENTIAL_ENV = 'APPRAISAL_PANEL_API_KEY';   // panel API key / login token
const BASE_URL_ENV   = 'APPRAISAL_PANEL_BASE_URL';  // panel API base, when applicable

function readCredential() {
  if (process.env[CREDENTIAL_ENV]) return { key: process.env[CREDENTIAL_ENV], baseUrl: process.env[BASE_URL_ENV] || null };
  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(),
      baseUrl: (env.match(new RegExp('^' + BASE_URL_ENV + '=(.*)$', 'm')) || [])[1]?.replace(/['"]/g, '').trim() || null
    };
  } catch (_) {}
  return null;
}

const NEEDS = `appraisal-panel access — an ${CREDENTIAL_ENV} (the panel's API key or login token) and `
  + `${BASE_URL_ENV} (the panel API base URL), OR confirmation of how Steve pulls supportable values from `
  + `his panel (manual portal vs API). Steve has direct access; set these in .env once the integration path is chosen.`;

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

// Supportable value for one address. While uncredentialed, {available:false} -> caller uses the existing
// Census/closed-sale estimate and labels it as such.
async function getSupportableValue({ address, city, zip } = {}) {
  const cred = readCredential();
  if (!cred) return { available: false, needs: NEEDS };

  // --- INTEGRATION POINT (wire when panel access exists) ----------------------------------------------
  //   const url = `${cred.baseUrl}/value?address=${encodeURIComponent(address)}&zip=${zip}`;
  //   const r = await fetch(url, { headers: { Authorization: `Bearer ${cred.key}` } });
  //   const j = await r.json();
  //   return { available:true, matched:true, value:j.supportableValue, confidence:'supportable',
  //            asOf:j.effectiveDate, basis:'appraisal-panel supportable value' };
  return { available: true, matched: false, note: 'credential present; panel value mapping not yet wired' };
}

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