← back to Commercialrealestate
CRCP agent precision: /api/agent-profile + agent.html accept ?id= (exact broker; names collide) — backward-compatible with ?name=; firms roster now deep-links by id
7adb0f78cc8e2a3bb536cf0c3b7fc0fdda7a3642 · 2026-08-19 10:02:33 -0700 · Steve Abrams
Files touched
M public/agent.htmlM public/firms.htmlM scripts/serve.js
Diff
commit 7adb0f78cc8e2a3bb536cf0c3b7fc0fdda7a3642
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 19 10:02:33 2026 -0700
CRCP agent precision: /api/agent-profile + agent.html accept ?id= (exact broker; names collide) — backward-compatible with ?name=; firms roster now deep-links by id
---
public/agent.html | 9 +++++----
public/firms.html | 2 +-
scripts/serve.js | 12 +++++++-----
3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/public/agent.html b/public/agent.html
index 800d082..09ba9da 100644
--- a/public/agent.html
+++ b/public/agent.html
@@ -70,6 +70,7 @@ const esc=s=>(s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').r
const money=n=>n==null||isNaN(+n)?null:'$'+Math.round(+n).toLocaleString();
const qs=new URLSearchParams(location.search);
const NAME=(qs.get('name')||'').trim();
+const ID=(qs.get('id')||'').trim(); // when present, resolves the EXACT agent (names collide)
let LISTINGS=[], sortKey='price', sortDir=-1;
const initials=n=>String(n||'?').trim().split(/\s+/).slice(0,2).map(w=>w[0]||'').join('').toUpperCase()||'?';
@@ -117,10 +118,10 @@ function render(){
$('#sort').innerHTML=[['price:desc','Price ↓'],['price:asc','Price ↑'],['units:desc','Units ↓'],['cap_rate:desc','Cap rate ↓'],['city:asc','City A→Z'],['address:asc','Address A→Z']].map(([k,l])=>`<option value="${k}">Sort: ${l}</option>`).join('');
$('#sort').addEventListener('change',()=>{const v=$('#sort').value.split(':');sortKey=v[0];sortDir=v[1]==='asc'?1:-1;render();});
-if(!NAME){ $('#loading').textContent='No agent specified.'; }
-else fetch('/api/agent-profile?name='+encodeURIComponent(NAME)).then(r=>r.json()).then(a=>{
- document.title=(a.name||NAME)+' · Agent · CRCP';
- if(a.found===false && !a.name){ $('#hero').innerHTML=`<div class="who"><h2>${esc(NAME)}</h2><div class="meta">No record for this agent in our data.</div></div>`; $('#empty').hidden=false; return; }
+if(!NAME && !ID){ $('#loading').textContent='No agent specified.'; }
+else fetch('/api/agent-profile?'+(ID?'id='+encodeURIComponent(ID):'name='+encodeURIComponent(NAME))).then(r=>r.json()).then(a=>{
+ document.title=(a.name||NAME||'Agent')+' · Agent · CRCP';
+ if(a.found===false && !a.name){ $('#hero').innerHTML=`<div class="who"><h2>${esc(NAME||'This agent')}</h2><div class="meta">No record for this agent in our data.</div></div>`; $('#empty').hidden=false; return; }
renderHero(a);
LISTINGS=a.listings||[];
render();
diff --git a/public/firms.html b/public/firms.html
index 638daf5..4b6e399 100644
--- a/public/firms.html
+++ b/public/firms.html
@@ -81,7 +81,7 @@ window.__firmsUI = (function () {
if (d.error) return `<h3>${esc(name)}</h3><div class="sec">Detail unavailable — ${esc(d.error)}</div>`;
const agg = d.agg || {};
const roster = (d.roster || []).map(b =>
- `<div class="litem"><span class="m"><a class="dl brokerlink" href="/agent.html?name=${encodeURIComponent(b.name || '')}" target="_blank" rel="noopener noreferrer" title="Open ${esc(b.name)}'s profile">${esc(b.name)} ↗</a>${b.agent_type ? ' · ' + esc(b.agent_type) : ''}</span><span>${b.phone ? '☎ <a class="dl" href="tel:' + esc(b.phone) + '">' + esc(b.phone) + '</a> ' : ''}${b.email ? '✉ <a class="dl" href="mailto:' + esc(b.email) + '">' + esc(b.email) + '</a> ' : ''}${b.listings && +b.listings ? '· ' + esc(b.listings) + ' listings' : ''}</span></div>`).join('');
+ `<div class="litem"><span class="m"><a class="dl brokerlink" href="/agent.html?id=${encodeURIComponent(b.id)}&name=${encodeURIComponent(b.name || '')}" target="_blank" rel="noopener noreferrer" title="Open ${esc(b.name)}'s profile">${esc(b.name)} ↗</a>${b.agent_type ? ' · ' + esc(b.agent_type) : ''}</span><span>${b.phone ? '☎ <a class="dl" href="tel:' + esc(b.phone) + '">' + esc(b.phone) + '</a> ' : ''}${b.email ? '✉ <a class="dl" href="mailto:' + esc(b.email) + '">' + esc(b.email) + '</a> ' : ''}${b.listings && +b.listings ? '· ' + esc(b.listings) + ' listings' : ''}</span></div>`).join('');
const listings = (d.current || []).slice(0, 25).map(listRow).join('');
return `<h3>${esc(name)}</h3>` +
`<div class="mfirm">Brokerage firm · ${agg.brokers || 0} brokers (${agg.residential || 0} residential) · ${agg.phone || 0}☎ ${agg.email || 0}✉</div>` +
diff --git a/scripts/serve.js b/scripts/serve.js
index deb920c..747c11c 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -341,15 +341,17 @@ app.get('/api/brokers/contact/:id', async (req, res) => {
// returns their contact + the listings WE have for them (broker_listing -> listing). Deliberately
// omits crexi_id / source / any aggregator link — this is our own record, keyed only by name.
app.get('/api/agent-profile', async (req, res) => {
+ const id = req.query.id ? +req.query.id : null; // exact resolution — names collide across agents
const name = String((req.query.name || '')).trim();
- if (!name) return res.status(400).json({ error: 'name required' });
+ if (!id && !name) return res.status(400).json({ error: 'id or name required' });
if (!brokerdb) return res.json({ name, firm: null, listings: [], found: false, note: 'no local DB' });
try {
+ const sel = `SELECT b.id, b.name, f.name AS firm, b.phone, b.email, b.title, b.agent_type, b.license, b.office_addr, b.linkedin
+ FROM broker b LEFT JOIN firm f ON f.id = b.firm_id`;
const b = (await brokerdb.pool.query(
- `SELECT b.id, b.name, f.name AS firm, b.phone, b.email, b.title, b.agent_type, b.license, b.office_addr, b.linkedin
- FROM broker b LEFT JOIN firm f ON f.id = b.firm_id
- WHERE lower(b.name) = lower($1)
- ORDER BY (b.phone IS NOT NULL OR b.email IS NOT NULL) DESC NULLS LAST LIMIT 1`, [name])).rows[0];
+ id ? `${sel} WHERE b.id = $1 LIMIT 1`
+ : `${sel} WHERE lower(b.name) = lower($1) ORDER BY (b.phone IS NOT NULL OR b.email IS NOT NULL) DESC NULLS LAST LIMIT 1`,
+ [id || name])).rows[0];
if (!b) return res.json({ name, firm: null, listings: [], found: false });
const listings = (await brokerdb.pool.query(
`SELECT l.address, l.city, l.zip, l.type, l.price, l.units, l.cap_rate
← 3698131 Wire broker closed-deal book into broker cards: brokerContac
·
back to Commercialrealestate
·
spec: robust Crexi/LoopNet agent-attributed scraper (opencla dddefaf →