← back to Commercialrealestate
condos: add Contact-ready outreach chip + 'FHA expires soonest' sort preset
e8f05ab09420cc8bf86dc22f743bfc3436921230 · 2026-08-07 16:02:49 -0700 · Steve Abrams
- 📇 Contact-ready toggle (own rail section, auto-hidden when no listing has a
contact): filters to listings with a reachable agent email/phone (152 live).
Independent of warrantability so it stacks with the Expiring/Lapsed chips.
- New 'FHA expires ↑ (soonest)' option in the sort dropdown: upcoming certs by
nearness, then recently-lapsed, then unmatched last. Shares daysToExp().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit e8f05ab09420cc8bf86dc22f743bfc3436921230
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 7 16:02:49 2026 -0700
condos: add Contact-ready outreach chip + 'FHA expires soonest' sort preset
- 📇 Contact-ready toggle (own rail section, auto-hidden when no listing has a
contact): filters to listings with a reachable agent email/phone (152 live).
Independent of warrantability so it stacks with the Expiring/Lapsed chips.
- New 'FHA expires ↑ (soonest)' option in the sort dropdown: upcoming certs by
nearness, then recently-lapsed, then unmatched last. Shares daysToExp().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/condos.html | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/public/condos.html b/public/condos.html
index e91fe46..7b6a0b1 100644
--- a/public/condos.html
+++ b/public/condos.html
@@ -156,6 +156,7 @@
<div class="shell">
<aside class="rail">
<div class="rsec"><h4>Warrantability</h4><div class="chips" id="fWarr"></div></div>
+ <div class="rsec" id="contactSec" style="display:none"><h4>Outreach</h4><div class="chips" id="fContact"></div></div>
<div class="rsec"><h4>City</h4><div class="chips" id="fCity"></div></div>
<div class="rsec" id="firmSec"><h4>Listing firm</h4><div class="chips" id="fFirm"></div></div>
<div class="rsec" id="bedSec"><h4>Beds (min)</h4><div class="chips" id="fBeds"></div></div>
@@ -185,6 +186,7 @@
<option value="hoa_asc">HOA ↑</option>
<option value="proj_az">Project A→Z</option>
<option value="city_az">City A→Z</option>
+ <option value="expiry_soon">FHA expires ↑ (soonest)</option>
</select>
<label class="dens">Density <input type="range" id="cols" min="1" max="20" step="1" value="3"></label>
<span class="count" id="count">…</span>
@@ -339,7 +341,7 @@ function renderStats(rows){ const sb=$('#statsbar'); if(!sb) return; if(!rows.le
const pill=(l,v)=>v==null?'':`<span class="statpill"><span class="sl">${l}</span> <b>${v}</b></span>`;
sb.innerHTML=pill('Count',rows.length)+pill('Median price',prices.length?'$'+Math.round(median(prices)).toLocaleString():null)+pill('Median $/SF',pps.length?'$'+Math.round(median(pps)).toLocaleString():null)+pill('Median HOA',hoas.length?'$'+Math.round(median(hoas)).toLocaleString()+'/mo':null)+pill('Median year',yrs.length?Math.round(median(yrs)):null);
}
-const F={ warr:'all', cities:new Set(), firms:new Set(), bedsMin:0, bathsMin:0, shortlist:false,
+const F={ warr:'all', cities:new Set(), firms:new Set(), bedsMin:0, bathsMin:0, shortlist:false, contactReady:false,
pmin:null,pmax:null, hoamin:null,hoamax:null, sqftMin:null,sqftMax:null, yearMin:null,yearMax:null, ppsfMin:null,ppsfMax:null };
function rng(v,mn,mx){ if(mn!=null){ if(v==null||!(+v>=mn)) return false; } if(mx!=null){ if(v==null||!(+v<=mx)) return false; } return true; }
// Days until the matched FHA cert expires (negative = lapsed N days ago), parsed from the clean
@@ -371,6 +373,7 @@ function passFacets(c){
if(!rng(c.year_built,F.yearMin,F.yearMax)) return false;
if(!rng(c.sqft?c.price/c.sqft:null,F.ppsfMin,F.ppsfMax)) return false;
if(F.shortlist && !SHORT.has(c.id)) return false;
+ if(F.contactReady && !hasContact(c)) return false;
return true;
}
@@ -387,6 +390,11 @@ function buildRail(){
const n=v==='all'?DATA.length:v==='unwarrantable'?(DATA.length-(wc.fha_approved||0)):(v in scount?scount[v]:wc[v]);
return `<span class="chip warr-${v} ${F.warr===v?'active':''}" data-warr="${v}">${l}<span class="ct">${n}</span></span>`;
}).join('');
+ // 📇 Contact-ready toggle — independent of warrantability; auto-hidden when no listing carries a
+ // contact (e.g. the FHA-directory fallback mode, whose rows have no broker overlay).
+ const cn=DATA.filter(hasContact).length; const cs=$('#contactSec');
+ if(cs) cs.style.display = cn ? '' : 'none';
+ if(cn) $('#fContact').innerHTML=`<span class="chip ${F.contactReady?'active':''}" data-contact="1" title="Only listings with a reachable agent email or phone — the outreach-ready set">📇 Contact-ready<span class="ct">${cn}</span></span>`;
$('#fCity').innerHTML=Object.entries(cc).sort((a,b)=>b[1]-a[1]).slice(0,20).map(([k,c])=>`<span class="chip ${F.cities.has(k)?'active':''}" data-cy="${esc(k)}">${esc(k)}<span class="ct">${c}</span></span>`).join('');
const firms=Object.entries(fc).sort((a,b)=>b[1]-a[1]).slice(0,14);
$('#firmSec').style.display=firms.length?'':'none';
@@ -406,6 +414,9 @@ const mailHref=e=>'mailto:'+String(e||'').trim();
// ── contact resolvers (TK-10243): each falls back through the flat field → primary agent record so
// the List column matches whatever the card shows, and returns '' when genuinely absent (no fake '—'). ──
function agentEmail(c){ return c.agent_email || (c.agents&&c.agents[0]&&c.agents[0].email) || ''; }
+// Contact-ready = we have a reachable channel for the listing agent (email or any phone) — the set
+// worth filtering to for renewal / re-cert outreach. Powers the 📇 Contact-ready toggle chip.
+function hasContact(c){ return !!(agentEmail(c) || c.agent_phone || c.office_phone || (c.agents&&c.agents[0]&&(c.agents[0].phone||c.agents[0].email))); }
// responsible broker carries the brokerage's office mailing ADDRESS (from the CA DRE record)
function respBroker(c){ return c.responsible_broker || (c.agents&&c.agents[0]&&c.agents[0].dre_record&&c.agents[0].dre_record.responsible_broker) || ''; }
// buyer-SIDE agents — SOLD listings only; mirrors agents[]. Filled by the boughtWith scrape
@@ -575,6 +586,7 @@ $('#grid').addEventListener('input',e=>{
},600);
});
$('#fWarr').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;F.warr=c.dataset.warr;$$('#fWarr .chip').forEach(x=>x.classList.toggle('active',x.dataset.warr===F.warr));apply();});
+$('#fContact').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;F.contactReady=!F.contactReady;c.classList.toggle('active',F.contactReady);apply();});
$('#fCity').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.cities,c.dataset.cy);c.classList.toggle('active');apply();});
$('#fFirm').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.firms,c.dataset.fm);c.classList.toggle('active');apply();});
$('#fBeds').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;F.bedsMin=+c.dataset.beds;$$('#fBeds .chip').forEach(x=>x.classList.toggle('active',+x.dataset.beds===F.bedsMin));apply();});
@@ -599,6 +611,9 @@ function sortRows(rows){
hoa_asc:(a,b)=>(num(a.hoa)??Infinity)-(num(b.hoa)??Infinity),
proj_az:(a,b)=>String(a.project_name||a.address||'').localeCompare(String(b.project_name||b.address||'')),
city_az:(a,b)=>String(a.city||'').localeCompare(String(b.city||'')),
+ // FHA expires ↑ (soonest actionable first): upcoming certs by nearness (0,1,2…), THEN already-
+ // lapsed (most-recent first), THEN unmatched (no FHA date) last. Ranks via daysToExp().
+ expiry_soon:(a,b)=>{const k=c=>{const d=daysToExp(c);return d==null?Infinity:(d>=0?d:1e6-d);};return k(a)-k(b);},
}[SORTKEY];
return cmp?rows.slice().sort(cmp):rows;
}
← c25ae4d condos: add date-based quick-filter chips (Expiring ≤90d / L
·
back to Commercialrealestate
·
CRE marketing-exec leads: seed + master contact list (name+p 56e5e81 →