[object Object]

← back to Commercialrealestate

CRCP: /api/property/:ain + /api/property/by-address enrichment endpoints (TK-10313 Cycle 2)

03cae430e0e2c24232c2ccd82eb664e3ffe1f28d · 2026-08-06 17:00:22 -0700 · Steve Abrams

Wire scripts/enrich-property.js into serve.js as read-only public-record endpoints:
GET /api/property/:ain(\d+) and /api/property/by-address?q= return the per-property
dossier (assessor parcel facts + ULA price candidates + gated-deed declaration).
10-min in-memory cache; AIN digit-constrained so /by-address isn't shadowed. $0,
public gov APIs. Deploy + paid deed lookups stay Steve-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 03cae430e0e2c24232c2ccd82eb664e3ffe1f28d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 6 17:00:22 2026 -0700

    CRCP: /api/property/:ain + /api/property/by-address enrichment endpoints (TK-10313 Cycle 2)
    
    Wire scripts/enrich-property.js into serve.js as read-only public-record endpoints:
    GET /api/property/:ain(\d+) and /api/property/by-address?q= return the per-property
    dossier (assessor parcel facts + ULA price candidates + gated-deed declaration).
    10-min in-memory cache; AIN digit-constrained so /by-address isn't shadowed. $0,
    public gov APIs. Deploy + paid deed lookups stay Steve-gated.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/serve.js | 32 ++++++++++++++++++++++++++++----
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/scripts/serve.js b/scripts/serve.js
index 56bb4b6..423a6c5 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -806,10 +806,34 @@ function overlayBroker(row) {
   return o;
 }
 
-// ── Per-listing scratch NOTES — now PER-USER (Steve 2026-08-06, TK-10301). The store + the
-// /api/condo-notes routes moved to scripts/crcp-notes.js (mounted above via require('./crcp-notes')),
-// which scopes each user's notes to their own account (hybrid CRM: private notes + shared contacts)
-// and auto-migrates the legacy global condo-notes.json under Frank. Left intentionally blank here.
+// ── Per-property PUBLIC-RECORD enrichment (TK-10313) — "figure each property out". GET
+// /api/property/:ain returns the free dossier (LA Assessor parcel facts + ULA price candidates +
+// an explicit gated-deed declaration). Also /api/property/by-address?q=... . Read-only against
+// public gov APIs, $0. 10-min in-memory cache so repeat drills don't re-hit the assessor. The paid
+// deed-parties layer (grantor/grantee) is a Steve-gated source decision (pending-approval memo).
+const { enrichByAin: _enrichAin, enrichByAddress: _enrichAddr } = require('./enrich-property');
+const _propCache = new Map();   // ain|addr -> { at, data }
+const _propTTL = 10 * 60 * 1000;
+async function _cachedEnrich(key, fn) {
+  const hit = _propCache.get(key);
+  if (hit && (Date.now() - hit.at) < _propTTL) return hit.data;
+  const data = await fn();
+  _propCache.set(key, { at: Date.now(), data });
+  if (_propCache.size > 2000) _propCache.clear();   // bounded
+  return data;
+}
+app.get('/api/property/:ain(\\d+)', async (req, res) => {   // digit-constrained so /by-address isn't captured
+  const ain = String(req.params.ain || '').replace(/\D/g, '').slice(0, 20);
+  if (!ain) return res.status(400).json({ error: 'numeric AIN required' });
+  try { res.json(await _cachedEnrich('ain:' + ain, () => _enrichAin(ain))); }
+  catch (e) { res.status(502).json({ error: 'assessor lookup failed', detail: String(e.message).split('\n')[0] }); }
+});
+app.get('/api/property/by-address', async (req, res) => {
+  const q = String(req.query.q || '').slice(0, 120);
+  if (!q.trim()) return res.status(400).json({ error: 'q (street address) required' });
+  try { res.json(await _cachedEnrich('addr:' + q.toLowerCase(), () => _enrichAddr(q))); }
+  catch (e) { res.status(502).json({ error: 'assessor lookup failed', detail: String(e.message).split('\n')[0] }); }
+});
 
 const CONDO_LABEL = 'FHA/VA-approval-based proxy, NOT lender-verified Fannie/Freddie warrantability.';
 app.get('/api/condos', async (req, res) => {

← bc720b1 CRCP mls: make 'First seen' visible per admin-card rule — un  ·  back to Commercialrealestate  ·  DSCR Qualifier: add vacancy % + interest-only underwriting l 8e33148 →