[object Object]

← back to Commercialrealestate

CRCP lending layer: hub page + compliance-gated loan-officers surface + data policy/source register

84529cea20b69c8aded59c8562034e32e5a9926c · 2026-07-31 10:54:32 -0700 · Steve Abrams

lending.html hub links all 5 datasets w/ live counts; loan-officers.html is manual_review_only (ships empty, deep-links NMLS/DFPI, never scraped); DATA_POLICY.md + PUBLIC_SOURCE_REGISTER.md codify the public-only, evidence-first, no-PII rules (TK-10090).

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

Files touched

Diff

commit 84529cea20b69c8aded59c8562034e32e5a9926c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 31 10:54:32 2026 -0700

    CRCP lending layer: hub page + compliance-gated loan-officers surface + data policy/source register
    
    lending.html hub links all 5 datasets w/ live counts; loan-officers.html is manual_review_only (ships empty, deep-links NMLS/DFPI, never scraped); DATA_POLICY.md + PUBLIC_SOURCE_REGISTER.md codify the public-only, evidence-first, no-PII rules (TK-10090).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/loan-officers.json        |  16 +++++++
 docs/DATA_POLICY.md            |  40 ++++++++++++++++
 docs/PUBLIC_SOURCE_REGISTER.md |  27 +++++++++++
 public/lending.html            | 102 +++++++++++++++++++++++++++++++++++++++++
 public/loan-officers.html      |  87 +++++++++++++++++++++++++++++++++++
 5 files changed, 272 insertions(+)

diff --git a/data/loan-officers.json b/data/loan-officers.json
new file mode 100644
index 0000000..6766390
--- /dev/null
+++ b/data/loan-officers.json
@@ -0,0 +1,16 @@
+{
+  "meta": {
+    "source": "NMLS Consumer Access (official lookup) + authorized manual entry",
+    "source_home": "https://www.nmlsconsumeraccess.org/",
+    "access_method": "manual_review_only",
+    "personal_data": true,
+    "count": 0,
+    "policy": "Individual mortgage loan officers are added ONLY from an officially-permitted source or authorized manual entry. NMLS Consumer Access prohibits automated access/scraping, so this dataset is never bulk-crawled. No personal mobile numbers, personal emails, or home addresses are stored. Every record must carry a public NMLS license number + source_url. See docs/DATA_POLICY.md.",
+    "official_lookups": [
+      { "name": "NMLS Consumer Access", "url": "https://www.nmlsconsumeraccess.org/", "scope": "nationwide MLO + company license lookup" },
+      { "name": "CA DFPI licensee search", "url": "https://dfpi.ca.gov/", "scope": "California mortgage lenders & originators" }
+    ],
+    "retrieved_at": null
+  },
+  "officers": []
+}
diff --git a/docs/DATA_POLICY.md b/docs/DATA_POLICY.md
new file mode 100644
index 0000000..1aa0706
--- /dev/null
+++ b/docs/DATA_POLICY.md
@@ -0,0 +1,40 @@
+# CRCP Lending-Intelligence — Data Policy
+
+Code-enforced rules for the lending layer (FDIC / NCUA / HMDA / SBA / NMLS-LO). These extend the
+existing CRCP sourcing doctrine (`docs/SOURCING.md`) — public data only, evidence-first, honest blanks.
+
+## Hard rules
+1. **Public / open sources only.** No paid or proprietary data feed is required for the core product.
+   Allowed access methods: `official_api`, `official_bulk_download`, `official_open_data_portal`,
+   `first_party_public_web` (allowlisted), `manual_review_only`.
+2. **Do not scrape or republish** Model Match, LinkedIn, CoStar, LoopNet, ZoomInfo, Apollo, MMI, Modex,
+   MLS systems, NMLS Consumer Access, or any login-gated / proprietary database. Do not bypass auth,
+   paywalls, CAPTCHAs, robots directives, or rate limits.
+3. **No personal contact inference.** Never infer personal emails from name patterns. Never publish
+   personal mobile numbers, personal emails, dates of birth, or home addresses. Business contact points
+   only, and only when explicitly published by the org, a government source, or an authorized user.
+4. **Evidence-first.** Every displayed factual field must be traceable to a source record: `source`
+   (owner), `source_url` (official public URL), `identifier` (e.g. FDIC CERT #, NMLS #), and
+   `retrieved_at`. No unlabeled calculated metric is displayed.
+5. **HMDA is institution-level.** Public HMDA aggregates are never represented as any individual loan
+   officer's production. Demographic HMDA fields are used only as privacy-safe market aggregates, if at
+   all — never to rank or target people.
+6. **No individual-production inference.** Individual loan-officer performance is shown only when a
+   reliable public source explicitly states it, labeled with source + date.
+7. **Manual-only fallback.** If an official source offers only a restricted interactive lookup with no
+   permitted automated/bulk route (e.g. NMLS Consumer Access, CA DFPI), it is registered as
+   `manual_review_only`: we deep-link to the official lookup and accept authorized manual entry — we do
+   not automate it.
+8. **Never fabricate.** A dataset with no permitted population path ships honestly empty with an
+   explanatory empty-state, never with synthetic individuals.
+
+## Individual loan officers (NMLS)
+The `loan-officers` dataset is `manual_review_only`. It starts empty. Records are added only from an
+officially-permitted source or authorized manual entry, and each must carry a public NMLS license number
+plus a `source_url`. Populating it at scale from a live individual-LO feed is a **gated** decision
+requiring a permitted/licensed source — surfaced to Steve, never auto-executed.
+
+## Enforcement
+- The `/api/lending/:dataset` route is allowlisted to known datasets only.
+- Ingestion scripts fetch only from the official endpoints recorded in `PUBLIC_SOURCE_REGISTER.md`.
+- Every row written by an ingestion script carries `source`, `source_url`, `retrieved_at`.
diff --git a/docs/PUBLIC_SOURCE_REGISTER.md b/docs/PUBLIC_SOURCE_REGISTER.md
new file mode 100644
index 0000000..ddd42c5
--- /dev/null
+++ b/docs/PUBLIC_SOURCE_REGISTER.md
@@ -0,0 +1,27 @@
+# CRCP Lending-Intelligence — Public Source Register
+
+Every source powering the lending layer, its official endpoint, access method, and review status.
+Scope is Los Angeles County, CA (the CRCP footprint). All `$0`, public, unauthenticated unless noted.
+
+| Dataset | Source (owner) | Official endpoint | Access method | Personal data | Ingestion script | Status |
+|---|---|---|---|---|---|---|
+| `lenders` | FDIC BankFind Suite | `https://api.fdic.gov/banks/institutions` + `/banks/locations` | official_api | No | `scripts/fetch-fdic-lenders.js` | ✅ live (95 banks / 1,414 LA branches) |
+| `branches` | FDIC BankFind Suite | `https://api.fdic.gov/banks/locations` | official_api | No | `scripts/fetch-fdic-lenders.js` | ✅ live |
+| `credit-unions` | NCUA | NCUA Research/Quarterly Call Report data files | official_bulk_download | No | `scripts/fetch-ncua-credit-unions.js` | ⏳ building |
+| `market` | CFPB / FFIEC HMDA | `https://ffiec.cfpb.gov/v2/data-browser-api/...` | official_api | No (aggregate only) | `scripts/fetch-hmda-market.js` | ⏳ building |
+| `sba-lenders` | U.S. SBA | SBA 7(a) & 504 FOIA datasets (data.sba.gov / CSV) | official_bulk_download | No | `scripts/fetch-sba-lenders.js` | ⏳ building |
+| `loan-officers` | NMLS Consumer Access / CA DFPI | `https://www.nmlsconsumeraccess.org/` (lookup) | manual_review_only | Yes | — (authorized entry only) | 🔒 gated, ships empty |
+
+## Notes
+- **FDIC** — the `api.fdic.gov/banks` host is current (the old `banks.data.fdic.gov/api` 301-redirects to
+  it). Institutions filtered to those with ≥1 branch in LA County; joined to institution metadata by CERT.
+  Pagination captures `total` from the first page only (a later page occasionally omits it).
+- **HMDA** — institution-level originations by lender + geography for LA County (FIPS 06037). Never
+  represented as individual production (see `DATA_POLICY.md` §5).
+- **NCUA** — no clean REST API like FDIC; the public quarterly Call Report / active-institution files are
+  the permitted bulk route. Scope to credit unions with an LA-County office/branch.
+- **SBA** — 7(a)/504 loan-level FOIA data is large; aggregate to lender + volume for the LA market.
+- **NMLS** — Consumer Access prohibits automated access; registered `manual_review_only`. We deep-link to
+  the official lookup and never crawl it.
+
+_Last reviewed: 2026-07-31._
diff --git a/public/lending.html b/public/lending.html
new file mode 100644
index 0000000..c505a02
--- /dev/null
+++ b/public/lending.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230c0f15'/%3E%3Ctext x='8' y='12' font-size='9' text-anchor='middle' fill='%23C8A24B'%3E%F0%9F%92%B0%3C/text%3E%3C/svg%3E">
+<title>Lending Intelligence — Public-data lenders, credit unions, market activity</title>
+<link rel="stylesheet" href="/lending-grid.css">
+<style>
+  .hero{padding:34px 28px 10px;max-width:1100px;}
+  .hero h1{font-size:26px;margin:0 0 6px;} .hero h1 b{color:var(--gold);}
+  .hero p{color:var(--mut);max-width:760px;line-height:1.5;}
+  .cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:14px 28px 40px;max-width:1100px;}
+  .lc{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:18px 20px;text-decoration:none;color:var(--ink);display:block;transition:border-color .15s;}
+  .lc:hover{border-color:var(--gold);}
+  .lc h3{margin:0 0 4px;font-size:17px;} .lc h3 .ic{margin-right:7px;}
+  .lc .src{color:var(--acc);font-size:11px;text-transform:uppercase;letter-spacing:.4px;}
+  .lc .desc{color:var(--mut);font-size:13px;line-height:1.45;margin:8px 0 10px;}
+  .lc .cnt{font-size:12px;color:var(--ink);} .lc .cnt b{color:var(--gold);}
+  .lc.gated{opacity:.96;border-style:dashed;}
+  .note{max-width:1100px;margin:0 28px 40px;color:var(--mut);font-size:12px;line-height:1.55;border-top:1px solid var(--line);padding-top:16px;}
+  .note a{color:var(--blue);}
+  .pub{color:var(--acc);border:1px solid rgba(112,173,71,.5);border-radius:8px;padding:2px 9px;font-size:11px;text-transform:uppercase;letter-spacing:.4px;}
+</style>
+</head>
+<body>
+<header>
+  <h1><b>💰 Lending</b> Intelligence</h1>
+  <div class="nav"><a href="/crcp.html">← CRCP</a><a href="/">Explorer</a><a href="/broker-grid.html">👥 Brokers</a><a href="/fha-loans.html">🏘️ FHA Loans</a></div>
+  <span class="spacer"></span>
+  <span class="pub">100% public data</span>
+</header>
+
+<div class="hero">
+  <h1>Lenders, credit unions &amp; mortgage-market activity — <b>for loan officers</b></h1>
+  <p>A source-cited directory and market view of the banks, credit unions, and mortgage lenders active in
+  Los Angeles County — built entirely from official government / open-data sources (FDIC, NCUA, CFPB/HMDA,
+  SBA). Every record links back to its authoritative public record. No proprietary databases, no scraped
+  personal contacts, no inferred emails.</p>
+</div>
+
+<div class="cards" id="cards">
+  <a class="lc" href="/lenders.html" data-ds="lenders">
+    <span class="src">FDIC BankFind</span>
+    <h3><span class="ic">🏦</span>Lenders — Banks</h3>
+    <div class="desc">Every FDIC-insured bank with a branch in LA County — charter type, assets, deposits, HQ, branch count.</div>
+    <div class="cnt"><b class="n">…</b> banks · <span class="b">…</span> LA branches</div>
+  </a>
+  <a class="lc" href="/credit-unions.html" data-ds="credit-unions">
+    <span class="src">NCUA</span>
+    <h3><span class="ic">🤝</span>Credit Unions</h3>
+    <div class="desc">Federally-insured credit unions serving LA County — membership, assets, member counts, branches.</div>
+    <div class="cnt"><b class="n">…</b> credit unions</div>
+  </a>
+  <a class="lc" href="/mortgage-market.html" data-ds="market">
+    <span class="src">CFPB / HMDA</span>
+    <h3><span class="ic">📊</span>Mortgage Market</h3>
+    <div class="desc">Public HMDA lending activity — who originates mortgages in LA County by lender, count, and dollar volume. Institution-level only.</div>
+    <div class="cnt"><b class="n">…</b> lender-market rows</div>
+  </a>
+  <a class="lc" href="/sba-lenders.html" data-ds="sba-lenders">
+    <span class="src">SBA</span>
+    <h3><span class="ic">🏢</span>SBA Lenders</h3>
+    <div class="desc">Lenders originating SBA 7(a) / 504 small-business &amp; commercial loans in the LA market, with volumes.</div>
+    <div class="cnt"><b class="n">…</b> SBA lenders</div>
+  </a>
+  <a class="lc gated" href="/loan-officers.html" data-ds="loan-officers">
+    <span class="src">NMLS Consumer Access</span>
+    <h3><span class="ic">👤</span>Loan Officers</h3>
+    <div class="desc">Individual mortgage loan officers by NMLS license. Populated only from authorized entry or an officially-permitted source — never scraped. Deep-links to the official NMLS lookup.</div>
+    <div class="cnt"><b class="n">…</b> officers on file</div>
+  </a>
+</div>
+
+<div class="note">
+  <strong>Data sources &amp; methodology.</strong> All data is drawn from official public / open-government
+  sources and is displayed with its source, identifier, and retrieval date. Institution-level HMDA lending
+  metrics are never represented as any individual's production. We do not scrape or republish NMLS, LinkedIn,
+  CoStar, MLS, or any login-gated / proprietary database, and we do not infer or publish personal contact
+  details. Individual loan-officer records are <a href="/loan-officers.html">compliance-gated</a> and the
+  per-record evidence panel on each page shows the exact public source. Corrections: contact the CRCP admin.
+</div>
+
+<script>
+const H = { credentials: 'same-origin' };
+async function counts(){
+  const els = document.querySelectorAll('.lc[data-ds]');
+  for (const el of els){
+    const ds = el.dataset.ds;
+    try{
+      const d = await (await fetch('/api/lending/'+ds, H)).json();
+      const key = { lenders:'lenders','credit-unions':'credit_units', market:'market','sba-lenders':'lenders','loan-officers':'officers' }[ds];
+      const arr = d.credit_unions || d.lenders || d.market || d.officers || [];
+      const n = el.querySelector('.n'); if(n) n.textContent = (d.meta && d.meta.count!=null ? d.meta.count : arr.length).toLocaleString();
+      if(ds==='lenders'){ const b=el.querySelector('.b'); if(b){ let br=0; try{ br=(await (await fetch('/api/lending/branches',H)).json()).meta.count; }catch(e){} b.textContent=br.toLocaleString(); } }
+    }catch(e){ const n=el.querySelector('.n'); if(n) n.textContent='—'; }
+  }
+}
+counts();
+</script>
+</body>
+</html>
diff --git a/public/loan-officers.html b/public/loan-officers.html
new file mode 100644
index 0000000..c35beac
--- /dev/null
+++ b/public/loan-officers.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230c0f15'/%3E%3Ctext x='8' y='12' font-size='9' text-anchor='middle' fill='%23C8A24B'%3E%F0%9F%91%A4%3C/text%3E%3C/svg%3E">
+<title>Loan Officers — NMLS-licensed (compliance-gated)</title>
+<link rel="stylesheet" href="/lending-grid.css">
+<style>
+  .compliance{margin:16px 20px 0;padding:14px 18px;background:rgba(210,153,34,.08);border:1px solid rgba(210,153,34,.4);border-radius:11px;color:var(--ink);font-size:13px;line-height:1.5;max-width:980px;}
+  .compliance b{color:var(--warn);}
+  .compliance a{color:var(--blue);} .compliance .lk{display:inline-block;margin-top:8px;margin-right:14px;}
+  .empty{margin:20px;padding:40px 24px;text-align:center;color:var(--mut);border:1px dashed var(--line);border-radius:12px;}
+</style>
+</head>
+<body>
+<header>
+  <h1><b>👤 Loan Officers</b></h1>
+  <div class="nav"><a href="/lending.html">← Lending</a><a href="/lenders.html">🏦 Lenders</a><a href="/credit-unions.html">🤝 Credit Unions</a><a href="/mortgage-market.html">📊 Market</a></div>
+  <span class="spacer"></span>
+  <input class="search" id="q" type="text" placeholder="search all fields — name, company, NMLS #… (space = AND)">
+  <div class="toggle" id="view"><button data-v="table" class="on">▦ List</button><button data-v="grid">▤ Grid</button></div>
+  <select id="sortsel" title="Sort"></select>
+  <label id="denswrap" title="Card density — grid view">⤢<input type="range" id="density" min="1" max="10" step="1"></label>
+  <button class="csvbtn" id="csv" title="Export current rows to CSV">⬇ CSV</button>
+  <span class="count" id="count">…</span>
+</header>
+<div class="compliance">
+  <b>⚖️ Compliance-gated dataset.</b> Individual loan-officer records are added <b>only</b> from an
+  officially-permitted source or authorized manual entry — <b>never scraped</b> from NMLS Consumer Access,
+  LinkedIn, or any login-gated database. Personal mobile numbers, personal emails, and home addresses are
+  never stored. Look up a licensed originator at the official source:
+  <div>
+    <a class="lk" href="https://www.nmlsconsumeraccess.org/" target="_blank" rel="noopener noreferrer">🔎 NMLS Consumer Access ↗</a>
+    <a class="lk" href="https://dfpi.ca.gov/" target="_blank" rel="noopener noreferrer">🔎 CA DFPI licensee search ↗</a>
+  </div>
+</div>
+<div class="shell">
+<aside class="rail">
+  <div id="railFacets"></div>
+  <div class="rsec"><h4>Fields / columns — show / hide</h4>
+    <div class="fieldtoggles" id="fFields"></div>
+    <div style="margin-top:8px"><button class="minibtn" id="fAll">All</button><button class="minibtn" id="fNone">None</button><button class="minibtn" id="fReset">↺ Reset</button></div>
+  </div>
+</aside>
+<div class="wrap">
+  <div class="databar"><span class="pubtag">Public data</span><span id="demoNote">NMLS Consumer Access — manual/authorized entry only</span></div>
+  <div id="emptyState" class="empty hide">No loan-officer records on file yet. Records appear here once added from a permitted source or authorized entry — each with a public NMLS license number and a source link. Use the official lookups above in the meantime.</div>
+  <div id="tableView" class="tblwrap"><table class="g" data-no-sort><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
+  <div id="gridView" class="grid hide"></div>
+</div>
+</div>
+<div id="ov"><div id="modal"><button class="x" aria-label="Close" onclick="document.getElementById('ov').classList.remove('on')">✕</button><div id="mbody"></div></div></div>
+<script>
+window.GRID_CONFIG = {
+  ns: 'crcpLO', noun: 'loan officers', nameKey: 'name', idKey: 'id',
+  title: 'NMLS-licensed loan officer',
+  api: '/api/lending/loan-officers', dataKey: 'officers',
+  sortKey: 'name', sortDir: 1,
+  defaultCols: ['name', 'company', 'nmls_id', 'city', 'source'],
+  cols: [
+    { k: 'name', l: 'Name', t: 's', g: 'Identity' },
+    { k: 'company', l: 'Company', t: 's', g: 'Identity' },
+    { k: 'nmls_id', l: 'NMLS #', t: 's', g: 'Identity' },
+    { k: 'title', l: 'Title', t: 's', g: 'Identity', def: 0 },
+    { k: 'city', l: 'City', t: 's', g: 'Location' },
+    { k: 'state', l: 'State', t: 's', g: 'Location', def: 0 },
+    { k: 'business_phone', l: 'Business phone', t: 'tel', g: 'Contact', def: 0 },
+    { k: 'source', l: 'Source', t: 'src', g: 'Evidence' },
+    { k: 'retrieved_at', l: 'Retrieved', t: 'date', g: 'Evidence', def: 0 },
+  ],
+  facets: [
+    { k: 'company', l: 'Company', limit: 16 },
+    { k: 'city', l: 'City', limit: 16 },
+  ],
+};
+</script>
+<script src="/lending-grid.js" defer></script>
+<script src="/col-resize.js" defer></script>
+<script>
+// Show the empty-state notice when there are zero records (compliance-gated dataset starts empty).
+fetch('/api/lending/loan-officers', { credentials:'same-origin' }).then(r=>r.json()).then(d=>{
+  if(!(d.officers||[]).length){ document.getElementById('emptyState').classList.remove('hide'); document.getElementById('tableView').classList.add('hide'); }
+}).catch(()=>{});
+</script>
+</body>
+</html>

← e25aad3 crcp: drilldown on Warrantability (FHA proxy) — bars + tile  ·  back to Commercialrealestate  ·  CRCP: Just Listed active-inventory page (just-listed.html) + 384b541 →