[object Object]

← back to Commercialrealestate

CRCP: contrarian fixes on broker rollup — disclose 123 broker-licensed non-supervisors, persist broker sort, guard agent re-fetch, reorder cleanBrokerName address-strip

71cc61781c7885b861fd67171a38d61dffbe74b1 · 2026-07-10 23:02:08 -0700 · Steve Abrams

Files touched

Diff

commit 71cc61781c7885b861fd67171a38d61dffbe74b1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 10 23:02:08 2026 -0700

    CRCP: contrarian fixes on broker rollup — disclose 123 broker-licensed non-supervisors, persist broker sort, guard agent re-fetch, reorder cleanBrokerName address-strip
---
 package-lock.json           |  4 ++--
 package.json                |  2 +-
 public/licensed-agents.html | 24 +++++++++++++++---------
 scripts/serve.js            | 10 ++++++----
 4 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 9164f7f..0f14ecc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "commercialrealestate",
-  "version": "0.6.0",
+  "version": "0.6.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "commercialrealestate",
-      "version": "0.6.0",
+      "version": "0.6.1",
       "dependencies": {
         "better-sqlite3": "^12.11.1",
         "express": "^4.22.2",
diff --git a/package.json b/package.json
index 64644f4..4280764 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "commercialrealestate",
-  "version": "0.6.0",
+  "version": "0.6.1",
   "private": true,
   "description": "LA County CRE investment explorer — multi-firm sourced, Census + assessor enriched, Qwen-analyzed",
   "scripts": {
diff --git a/public/licensed-agents.html b/public/licensed-agents.html
index 493beee..9df5234 100644
--- a/public/licensed-agents.html
+++ b/public/licensed-agents.html
@@ -139,17 +139,22 @@ function pills(){
   $('#pills').innerHTML=defs.map(([k,l,f])=>`<span class="pill ${filt===k?'on':''}" data-f="${k}">${l} <b>${n(f)}</b></span>`).join('');
   document.querySelectorAll('.pill').forEach(p=>p.onclick=()=>{filt=p.dataset.f;pills();render();});
 }
+let aLoaded=false, AGENT_NOTE='';
 async function load(){
-  let d; try{ d=await (await fetch('/api/agents/licensed')).json(); }catch(e){ $('#note').textContent='Failed to load /api/agents/licensed'; return; }
-  DATA=d.agents||[]; META=d.meta||{};
-  const c=META.counts||{};
-  const proof=META.is_proof_run?` <b style="color:var(--warn)">⚠ PROOF RUN — ${c.in_snapshot||DATA.length} of ${META.total_agents} agents processed so far</b>`:'';
-  $('#note').innerHTML=`Source: <b>${esc(d.source||'?')}</b> · ${esc(d.label||'')}${proof} `+
-    (c.verified!=null?`— <span class="verified">${c.verified} verified</span>, ${c.probable||0} probable, ${c.unmatched||0} unmatched, ${c.errors||0} errors. ${esc(META.cost||'')}`:'');
+  if(!aLoaded){
+    let d; try{ d=await (await fetch('/api/agents/licensed')).json(); }catch(e){ $('#note').textContent='Failed to load /api/agents/licensed'; return; }
+    DATA=d.agents||[]; META=d.meta||{};
+    const c=META.counts||{};
+    const proof=META.is_proof_run?` <b style="color:var(--warn)">⚠ PROOF RUN — ${c.in_snapshot||DATA.length} of ${META.total_agents} agents processed so far</b>`:'';
+    AGENT_NOTE=`Source: <b>${esc(d.source||'?')}</b> · ${esc(d.label||'')}${proof} `+
+      (c.verified!=null?`— <span class="verified">${c.verified} verified</span>, ${c.probable||0} probable, ${c.unmatched||0} unmatched, ${c.errors||0} errors. ${esc(META.cost||'')}`:'');
+    aLoaded=true;
+  }
+  $('#note').innerHTML=AGENT_NOTE;
   pills(); render();
 }
 // ── Brokers view (rolled up from /api/brokers/licensed — the "broker database") ──
-let mode='agents', BROKERS=[], bLoaded=false, bSort='agents';
+let mode='agents', BROKERS=[], bLoaded=false, bSort=localStorage.getItem('laBSort')||'agents';
 const AGENT_SORTS=[['name','Name A→Z'],['status','Status'],['type','License type'],['exp','Expiration (soonest)'],['brokerage','Brokerage'],['conf','Match confidence']];
 const BROKER_SORTS=[['agents','Agent count (most)'],['verified','Verified (most)'],['name','Broker A→Z']];
 function fillSort(opts,cur){ $('#sortsel').innerHTML=opts.map(([v,l])=>`<option value="${v}">Sort: ${l}</option>`).join(''); $('#sortsel').value=cur; }
@@ -158,7 +163,8 @@ async function loadBrokers(){
   let d; try{ d=await (await fetch('/api/brokers/licensed')).json(); }catch(e){ $('#note').textContent='Failed to load /api/brokers/licensed'; return; }
   BROKERS=d.brokers||[]; bLoaded=true;
   const m=d.meta||{};
-  $('#note').innerHTML=`Source: <b>${esc(d.source||'?')}</b> · ${esc(d.label||'')} — <b>${m.broker_count||BROKERS.length}</b> distinct brokers ${esc(m.cost||'')}`;
+  $('#note').innerHTML=`Source: <b>${esc(d.source||'?')}</b> · ${esc(d.label||'')} — <b>${m.broker_count||BROKERS.length}</b> supervising brokers (derived from the agents' responsible-broker field). ${esc(m.cost||'')}`+
+    ` <span style="color:var(--mut)">Broker-licensed individuals in the set who supervise no one here appear under <b>Agents → Brokers</b> pill.</span>`;
 }
 function brokerCard(b){
   const total=b.agent_count||0;
@@ -197,7 +203,7 @@ document.querySelectorAll('.mode').forEach(x=>x.onclick=()=>setMode(x.dataset.m)
 
 // controls
 $('#q').oninput=e=>{q=e.target.value.toLowerCase().trim();draw();};
-$('#sortsel').onchange=e=>{ if(mode==='brokers'){bSort=e.target.value;} else {sortKey=e.target.value;localStorage.setItem('laSort',sortKey);} draw();};
+$('#sortsel').onchange=e=>{ if(mode==='brokers'){bSort=e.target.value;localStorage.setItem('laBSort',bSort);} else {sortKey=e.target.value;localStorage.setItem('laSort',sortKey);} draw();};
 $('#density').oninput=e=>{document.documentElement.style.setProperty('--cw',e.target.value+'px');localStorage.setItem('laDens',e.target.value);};
 // restore persisted
 (()=>{const s=localStorage.getItem('laSort'); if(s){sortKey=s;$('#sortsel').value=s;}
diff --git a/scripts/serve.js b/scripts/serve.js
index 7bdf59a..75bdc48 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -254,10 +254,12 @@ app.get('/api/agents/licensed', (req, res) => {
 // name; each broker carries its supervised-agent count + license-verification breakdown.
 const BROKERS_LABEL = 'Brokers derived from the CA DRE responsible-broker field of the licensed-agent set — supervising broker + how many verified/probable/unmatched agents report to each ($0, govt data).';
 // The DRE responsible_broker field is "NAME <street#> ADDRESS CITY, CA <zip>". Cut from the
-// first street-number run through the trailing state — CA addresses reliably end in "…, CA".
-// This is robust to numbered streets ("8TH ST") and directionals ("W GLENOAKS") that a
-// caps-only pattern misses, while names without a ", CA" address (e.g. "Century 21 Peak") stay whole.
-const cleanBrokerName = s => String(s || '').replace(/\s{2,}.*$/, '').replace(/\s\d+\s.*,\s*CA\b.*$/, '').trim();
+// first street-number run through the trailing state — CA addresses reliably end in "…, CA" —
+// THEN collapse any remaining double-space tail. Order matters: strip the address first so a
+// stray double-space inside the address can't truncate the name before the address is removed.
+// Handles numbered streets ("8TH ST") and directionals ("W GLENOAKS") on the current snapshot;
+// names without a ", CA" address (e.g. "Century 21 Peak") pass through unchanged.
+const cleanBrokerName = s => String(s || '').replace(/\s\d+\s.*,\s*CA\b.*$/, '').replace(/\s{2,}.*$/, '').trim();
 app.get('/api/brokers/licensed', (req, res) => {
   try {
     const { meta = {}, agents = [] } = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'licensed-agents.json'), 'utf8'));

← 93c2642 CRCP: broker-level rollup view — /api/brokers/licensed + Age  ·  back to Commercialrealestate  ·  auto-save: 2026-07-11T00:10:56 (1 files) — data/condos-redfi 2bb63e5 →