← back to Commercialrealestate
DRE->CRCP-id join: link condo/residential brokers to broker profiles
89fb4917da26166b73b21573869769c0aed92512 · 2026-08-19 10:29:31 -0700 · Steve
- serve.js: GET /api/brokers/by-dre/:license — snapshot-first resolver, digits-only
match on dre_license then license; returns {id,name} or {id:null}. Works identical
local + prod (snapshot mode).
- broker.html: accept ?dre=<license> (+ optional &name=) -> resolve to id and render;
graceful 'not in CRCP index' fallback (name + live CA DRE record link, never a dead end).
- condos.html: unobtrusive 'profile' link next to each listing-agent DRE#.
- residential-brokers.html: name links to profile for CA licenses only (CRCP is CA-focused;
out-of-state would resolve to 'not in index', so kept high-signal).
Verified E2E on an ephemeral instance (prod :9911 untouched): Cameron Samimi DRE 02035763
-> CRCP id 26, full profile w/ 153 listings; dash-formatted DRE normalizes; bogus DRE ->
graceful fallback. 0 console errors.
Files touched
M public/broker.htmlM public/condos.htmlM public/residential-brokers.htmlM scripts/serve.js
Diff
commit 89fb4917da26166b73b21573869769c0aed92512
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 19 10:29:31 2026 -0700
DRE->CRCP-id join: link condo/residential brokers to broker profiles
- serve.js: GET /api/brokers/by-dre/:license — snapshot-first resolver, digits-only
match on dre_license then license; returns {id,name} or {id:null}. Works identical
local + prod (snapshot mode).
- broker.html: accept ?dre=<license> (+ optional &name=) -> resolve to id and render;
graceful 'not in CRCP index' fallback (name + live CA DRE record link, never a dead end).
- condos.html: unobtrusive 'profile' link next to each listing-agent DRE#.
- residential-brokers.html: name links to profile for CA licenses only (CRCP is CA-focused;
out-of-state would resolve to 'not in index', so kept high-signal).
Verified E2E on an ephemeral instance (prod :9911 untouched): Cameron Samimi DRE 02035763
-> CRCP id 26, full profile w/ 153 listings; dash-formatted DRE normalizes; bogus DRE ->
graceful fallback. 0 console errors.
---
public/broker.html | 37 +++++++++++++++++++++++++++++++++----
public/condos.html | 4 +++-
public/residential-brokers.html | 7 ++++++-
scripts/serve.js | 20 ++++++++++++++++++++
4 files changed, 62 insertions(+), 6 deletions(-)
diff --git a/public/broker.html b/public/broker.html
index a4f8229..320f0f4 100644
--- a/public/broker.html
+++ b/public/broker.html
@@ -93,6 +93,8 @@
const esc = s => String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
const qs = new URLSearchParams(location.search);
const ID = qs.get('id') || qs.get('broker') || qs.get('dbId');
+const DRE = qs.get('dre') || qs.get('license'); // condo / residential directories link by DRE#
+const NM = qs.get('name') || ''; // optional display name for the not-found fallback
const fmtMoney = v => { const n=Number(String(v).replace(/[^0-9.]/g,'')); if(!n||!isFinite(n)) return null;
if(n>=1e6) return '$'+(n/1e6).toFixed(n>=1e7?0:1)+'M'; if(n>=1e3) return '$'+Math.round(n/1e3)+'K'; return '$'+n.toLocaleString(); };
const initials = name => (name||'?').split(/[\s,]+/).filter(Boolean).slice(0,2).map(w=>w[0]).join('').toUpperCase()||'?';
@@ -247,13 +249,40 @@ function render(b){
renderListings();
}
-(function boot(){
+function loadById(id){
const root=document.getElementById('root');
- if(!ID){ root.innerHTML='<div class="empty">No broker id supplied. Open this page as <code>broker.html?id=<brokerId></code> — e.g. from the <a href="/brokers.html">broker directory</a>.</div>'; return; }
- fetch('/api/brokers/contact/'+encodeURIComponent(ID))
+ return fetch('/api/brokers/contact/'+encodeURIComponent(id))
.then(r=>{ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
.then(b=>{ if(!b||b.error||b.id==null){ throw new Error(b&&b.error||'broker not found'); } render(b); })
- .catch(e=>{ root.innerHTML='<div class="empty">Could not load broker <b>'+esc(ID)+'</b>: '+esc(e.message)+'.<br><a href="/brokers.html">← back to directory</a></div>'; });
+ .catch(e=>{ root.innerHTML='<div class="empty">Could not load broker <b>'+esc(id)+'</b>: '+esc(e.message)+'.<br><a href="/brokers.html">← back to directory</a></div>'; });
+}
+// Graceful "not in the CRCP commercial index" state — shown when a ?dre= link (from the condo /
+// residential directories) points at a license we haven't harvested as a Crexi commercial broker.
+// Never a dead end: shows the name we were handed + a live CA DRE record link.
+function renderNotInIndex(dre,name){
+ const root=document.getElementById('root'); const digits=String(dre||'').replace(/\D/g,'');
+ root.innerHTML='<div class="card" style="margin-top:14px">'
+ +'<h2>Not in the CRCP commercial index</h2>'
+ +'<div class="bio">'+(name?'<b>'+esc(name)+'</b> ':'This license ')+'isn’t in our Crexi-sourced commercial broker index yet'
+ +(digits?', so there’s no CRCP deal profile to show.':'.')+'</div>'
+ +(digits?'<div class="kv" style="margin-top:12px"><span class="ic">🪪</span><span>DRE #'+esc(digits)
+ +' · <a href="https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+encodeURIComponent(digits)
+ +'" target="_blank" rel="noopener noreferrer">view the CA DRE license record →</a></span></div>':'')
+ +'<div class="indexed" style="margin-top:12px">CRCP indexes ~2,500 commercial brokers from Crexi. Residential / condo listing agents are matched by DRE# only when they also appear in that set.</div>'
+ +'<div style="margin-top:12px"><a href="/brokers.html">← back to broker directory</a></div></div>';
+ document.title=(name||'Broker')+' — not in CRCP index';
+}
+(function boot(){
+ const root=document.getElementById('root');
+ if(ID){ loadById(ID); return; }
+ if(DRE){
+ fetch('/api/brokers/by-dre/'+encodeURIComponent(String(DRE).replace(/\D/g,'')))
+ .then(r=>r.json())
+ .then(m=>{ if(m && m.id!=null) loadById(m.id); else renderNotInIndex(DRE,NM); })
+ .catch(()=>renderNotInIndex(DRE,NM));
+ return;
+ }
+ root.innerHTML='<div class="empty">No broker supplied. Open this page as <code>broker.html?id=<brokerId></code> or <code>broker.html?dre=<license#></code> — e.g. from the <a href="/brokers.html">broker directory</a>.</div>';
})();
</script>
</body>
diff --git a/public/condos.html b/public/condos.html
index 5e28302..f781f2f 100644
--- a/public/condos.html
+++ b/public/condos.html
@@ -322,7 +322,9 @@ function fcell(c,f){ const v=fval(c,f);
const extra=(Array.isArray(c.agents)&&c.agents.length>1)?` <span style="color:var(--mut);font-size:10px" title="${c.agents.length-1} co-agent license(s) — see card/detail">+${c.agents.length-1}</span>`:'';
if(!/^\d{6,8}$/.test(String(primary))) return esc(String(primary))+extra; // defensive: only link a real DRE#
const u=c.dre_url||('https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+primary);
- return `<a href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">${esc(primary)} ↗</a>${extra}`; }
+ const pn=c.broker_name||(c.agents&&c.agents[0]&&c.agents[0].name)||'';
+ const prof=` <a href="/broker.html?dre=${encodeURIComponent(String(primary).replace(/\D/g,''))}${pn?'&name='+encodeURIComponent(pn):''}" title="CRCP broker profile (if in our commercial index)" style="text-decoration:none">👤</a>`;
+ return `<a href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">${esc(primary)} ↗</a>${prof}${extra}`; }
// ── contact-block cells (TK-10243) — resolve nested/flat, phone→tel, email→mailto, sortable text ──
if(f.k==='agent_phone'){ const p=v||(c.agents&&c.agents[0]&&c.agents[0].phone); return p?`<a href="${telHref(p)}">${esc(p)}</a>`:'—'; }
if(f.k==='office_phone'){ return v?`<a href="${telHref(v)}">${esc(v)}</a>`:'—'; }
diff --git a/public/residential-brokers.html b/public/residential-brokers.html
index 27c0ce4..ce8dc30 100644
--- a/public/residential-brokers.html
+++ b/public/residential-brokers.html
@@ -96,7 +96,12 @@
// and the all-field sort menu. `get` = sortable/plain value; `render` = optional HTML.
// `lock:true` = contact columns that can never be hidden (Steve hard rule).
const FIELDS = [
- {k:'name', label:'Name', get:r=>r.name},
+ {k:'name', label:'Name', get:r=>r.name,
+ // Link to the CRCP broker profile ONLY for CA licenses (CRCP's commercial index is CA-focused,
+ // so an out-of-state license would almost always resolve to "not in index"). broker.html?dre=
+ // degrades gracefully when the DRE isn't in the commercial set.
+ render:r=>{const nm=r.name||'';const lic=r.license_no;const ca=(r.license_state==='CA'||r.state_code==='CA');
+ return (lic&&ca)?`<a href="/broker.html?dre=${encodeURIComponent(String(lic).replace(/[^0-9]/g,''))}&name=${encodeURIComponent(nm)}" onclick="event.stopPropagation()" title="CRCP broker profile (if in our commercial index)">${esc(nm)}</a>`:esc(nm);}},
{k:'firm', label:'Firm', get:r=>r.firm_name},
{k:'city', label:'City', get:r=>[r.city,r.state_code].filter(Boolean).join(', ')},
{k:'license', label:'License', get:r=>r.license_no ? (r.license_no+(r.license_state?' · '+r.license_state:'')) : ''},
diff --git a/scripts/serve.js b/scripts/serve.js
index 6daacc3..6334ff0 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -336,6 +336,26 @@ app.get('/api/brokers/contact/:id', async (req, res) => {
} catch (e) { const b = snapContact(req.params.id); return b ? res.json(b) : res.status(502).json({ error: String(e.message).split('\n')[0] }); }
});
+// Resolve a CRCP broker id from a DRE / license number. Powers /broker.html?dre=<n> links from the
+// condo + residential directories, whose row ids are a DIFFERENT id space (condo id / usre-DRE id)
+// than the CRCP broker table. Snapshot-first (the snapshot carries dre_license + license for all
+// brokers and is what prod serves): digits-only match on dre_license first, then the raw license.
+// Returns {id,name} on a hit or {id:null} on a miss (broker.html renders a graceful fallback).
+const snapByDre = (lic) => {
+ const digits = String(lic || '').replace(/\D/g, ''); if (digits.length < 5) return null;
+ const s = readBrokerSnap(); if (!s) return null;
+ const norm = v => String(v == null ? '' : v).replace(/\D/g, '');
+ const b = (s.brokers || []).find(x => norm(x.dre_license) === digits)
+ || (s.brokers || []).find(x => norm(x.license) === digits);
+ return b ? { id: b.id, name: b.name } : null;
+};
+app.get('/api/brokers/by-dre/:license', (req, res) => {
+ const digits = String(req.params.license || '').replace(/\D/g, '');
+ if (digits.length < 5) return res.status(400).json({ error: 'bad license', id: null });
+ const b = snapByDre(digits);
+ res.json(b || { id: null });
+});
+
// OUR-OWN agent profile by NAME (Steve 2026-08-19: "click any agent -> a page about their
// listings, built from OUR data, NEVER crexi"). Resolves the broker by name from our graph and
// returns their contact + the listings WE have for them (broker_listing -> listing). Deliberately
← 435a304 chore: lint (node --check clean, no linter configured) + ref
·
back to Commercialrealestate
·
Fold 8,796 new active broker-book listings into catalog (nat a04fb2b →