← back to Commercialrealestate
feat(index+mls): merge condos IN-GRID as a real 'Condo' asset type (727 rows → 2134 total) — filters in place like any asset class; ⚑ Unwarrantable in-grid filter (713); +6 condo data points (beds/baths/sqft/HOA/warrantability/broker) as toggles on index, auto-discovered as columns on mls; $/unit suppressed for 1-unit condos
44c3be8ade861dfbcb6c0443eef7dbc25f2ab3e2 · 2026-07-02 10:20:02 -0700 · Steve Abrams
Files touched
M public/index.htmlM public/mls.html
Diff
commit 44c3be8ade861dfbcb6c0443eef7dbc25f2ab3e2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 10:20:02 2026 -0700
feat(index+mls): merge condos IN-GRID as a real 'Condo' asset type (727 rows → 2134 total) — filters in place like any asset class; ⚑ Unwarrantable in-grid filter (713); +6 condo data points (beds/baths/sqft/HOA/warrantability/broker) as toggles on index, auto-discovered as columns on mls; $/unit suppressed for 1-unit condos
---
public/index.html | 18 +++++++++++++++---
public/mls.html | 15 +++++++++++----
2 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index 15dce00..5ded577 100644
--- a/public/index.html
+++ b/public/index.html
@@ -731,7 +731,7 @@ function card(p){
<div><div class="rank">#${p.rank} · score</div><div class="addr">${p.address}</div><div class="city">${p.city}, CA ${p.zip||''}</div></div>
${vis('score')?`<div class="score">${p.composite}<small>fin ${p.financeScore}${p.qwenScore!=null?' · ai '+p.qwenScore:''}</small></div>`:''}
</div>
- <div class="badges">${vis('deal')?dealBadge(p):''}${vis('firm')?firmB:''}${vis('type')?`<span class="b type">${p.type}</span>`:''}${vis('units')?`<span class="b">${p.units} unit${p.units>1?'s':''}</span>`:''}${vis('price')?`<span class="b">${fmt(p.price)}</span>`:''}${vis('ppu')&&ppu(p)?`<span class="b" title="Price per unit">$${ppu(p).toLocaleString()}/unit</span>`:''}${vis('pricePerSf')&&ppsf(p)?`<span class="b" title="Price per building SF">$${ppsf(p).toLocaleString()}/sf</span>`:''}${vis('cap')?capB:''}${vis('verified')?verB:''}${vis('yield')&&f.grossYield?`<span class="b ok" title="Annual gross rent ÷ price">${f.grossYield}% rent yld</span>`:''}${vis('grm')&&grm(p)!=null?`<span class="b" title="Gross rent multiplier — price ÷ annual gross rent (lower = cheaper vs rent)">GRM ${grm(p)}</span>`:''}${vis('yearBuilt')&&p.year_built?`<span class="b" title="Year built">🏗 ${p.year_built}</span>`:''}${vis('dom')&&daysOnMarket(p)!=null?`<span class="b" title="Days since listed">${daysOnMarket(p)}d on mkt</span>`:''}${vis('beds')&&p.beds!=null?`<span class="b">${p.beds} bd</span>`:''}${vis('baths')&&p.baths!=null?`<span class="b">${p.baths} ba</span>`:''}${vis('sqft')&&p.sqft?`<span class="b">${(+p.sqft).toLocaleString()} sf</span>`:''}${vis('hoa')&&p.hoa?`<span class="b" title="HOA dues">$${(+p.hoa).toLocaleString()}/mo</span>`:''}${vis('warr')&&p.warrantable_status?`<span class="b ${p.warrantable_status==='fha_approved'?'ok':'warn'}" title="Condo warrantability (FHA-approval proxy)">${p.warrantable_status==='fha_approved'?'✓ warrantable':'⚑ '+p.warrantable_status}</span>`:''}${vis('broker')&&p.broker_name?`<span class="b" title="Listing broker">🧑💼 ${esc(p.broker_name)}</span>`:''}${vis('status')?statusB:''} ${vis('afford')?aff:''} ${vis('rec')?rec:''}</div>
+ <div class="badges">${vis('deal')?dealBadge(p):''}${vis('firm')?firmB:''}${vis('type')?`<span class="b type">${p.type}</span>`:''}${vis('units')?`<span class="b">${p.units} unit${p.units>1?'s':''}</span>`:''}${vis('price')?`<span class="b">${fmt(p.price)}</span>`:''}${vis('ppu')&&ppu(p)&&!p.isCondo?`<span class="b" title="Price per unit">$${ppu(p).toLocaleString()}/unit</span>`:''}${vis('pricePerSf')&&ppsf(p)?`<span class="b" title="Price per building SF">$${ppsf(p).toLocaleString()}/sf</span>`:''}${vis('cap')?capB:''}${vis('verified')?verB:''}${vis('yield')&&f.grossYield?`<span class="b ok" title="Annual gross rent ÷ price">${f.grossYield}% rent yld</span>`:''}${vis('grm')&&grm(p)!=null?`<span class="b" title="Gross rent multiplier — price ÷ annual gross rent (lower = cheaper vs rent)">GRM ${grm(p)}</span>`:''}${vis('yearBuilt')&&p.year_built?`<span class="b" title="Year built">🏗 ${p.year_built}</span>`:''}${vis('dom')&&daysOnMarket(p)!=null?`<span class="b" title="Days since listed">${daysOnMarket(p)}d on mkt</span>`:''}${vis('beds')&&p.beds!=null?`<span class="b">${p.beds} bd</span>`:''}${vis('baths')&&p.baths!=null?`<span class="b">${p.baths} ba</span>`:''}${vis('sqft')&&p.sqft?`<span class="b">${(+p.sqft).toLocaleString()} sf</span>`:''}${vis('hoa')&&p.hoa?`<span class="b" title="HOA dues">$${(+p.hoa).toLocaleString()}/mo</span>`:''}${vis('warr')&&p.warrantable_status?`<span class="b ${p.warrantable_status==='fha_approved'?'ok':'warn'}" title="Condo warrantability (FHA-approval proxy)">${p.warrantable_status==='fha_approved'?'✓ warrantable':'⚑ '+p.warrantable_status}</span>`:''}${vis('broker')&&p.broker_name?`<span class="b" title="Listing broker">🧑💼 ${esc(p.broker_name)}</span>`:''}${vis('status')?statusB:''} ${vis('afford')?aff:''} ${vis('rec')?rec:''}</div>
${vis('brokerflag')?brokerFlagBadge:''}
${vis('statusline')?statusLine(p):''}
${vis('txhist')?txHistHTML(bh):''}
@@ -969,9 +969,21 @@ fetch('/api/broker-history').then(r=>r.json()).then(j=>{
// ---- boot ----
fetch('data/comps.json').then(r=>r.json()).then(c=>{ if(DATA) DATA.comps=c; else window.__comps=c; }).catch(()=>{});
fetch('data/demographics.json').then(r=>r.json()).then(d=>{ window.DEMO=d.byCity||{}; if(DATA) render(); }).catch(()=>{});
-fetch('data/ranked.json').then(r=>r.json()).then(d=>{
+// Map a condo (separate /api/condos schema) into the explorer's property shape so it flows through
+// finance, scoring, filters, sort, and the card/list renderers as a first-class 'Condo' asset type.
+function mapCondo(c){
+ return { id:c.id, address:c.address, city:c.city, zip:c.zip, type:'Condo',
+ price:+c.price||0, units:1, cap_rate:null, year_built:c.year_built||null,
+ status:'Active', source:c.source, firm:c.firm_name||null, verified:false,
+ isCondo:true, warrantable_status:c.warrantable_status,
+ beds:c.beds, baths:c.baths, sqft:c.sqft, hoa:c.hoa, broker_name:c.broker_name,
+ project_name:c.project_name, upside_note:null, qwen:null };
+}
+fetch('data/ranked.json').then(r=>r.json()).then(async d=>{
DATA=d; if(window.__comps) DATA.comps=window.__comps;
- $('#sub').innerHTML = `${d.ranked.length} listings · <b>${d.meta.market}</b>${d.generated?' · sourced '+d.generated:''}`;
+ // Merge condo inventory in-grid (best-effort; explorer still works if the API is down).
+ let condoN=0; try{ const cd=await (await fetch('/api/condos')).json(); const cs=(cd.condos||[]).map(mapCondo); condoN=cs.length; DATA.ranked=DATA.ranked.concat(cs); }catch(e){}
+ $('#sub').innerHTML = `${d.ranked.length} listings${condoN?` · ${condoN} condos`:''} · <b>${d.meta.market}</b>${d.generated?' · sourced '+d.generated:''}`;
scenario = localStorage.getItem('cre_scenario')||'25';
$$('#scenario button').forEach(b=>b.classList.toggle('active',b.dataset.s===scenario));
diff --git a/public/mls.html b/public/mls.html
index 971ffe0..b89374f 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -79,7 +79,7 @@
<div class="shell">
<aside class="rail">
<div class="rsec"><h4>Asset class</h4><div class="chips" id="fType"></div>
- <div style="margin-top:9px;display:flex;flex-wrap:wrap;gap:6px"><a class="navchip" href="/condos">🏢 Condos →</a><a class="navchip" href="/condos?status=unwarrantable" style="background:rgba(248,81,73,.10);border-color:rgba(248,81,73,.4);color:var(--bad)" title="Non-FHA-approved condos — Arcstone non-QM target">⚑ Unwarrantable →</a></div></div>
+ <div style="margin-top:9px;display:flex;flex-wrap:wrap;gap:6px"><span class="chip" id="unwChip" data-warr="unwarrantable" style="border-color:rgba(248,81,73,.4);color:var(--bad)" title="Filter the table to non-FHA-approved condos (Arcstone non-QM target)">⚑ Unwarrantable condos</span><a class="navchip" href="/condos" title="Full condo drill-down page">condos page →</a></div></div>
<div class="rsec"><h4>Status</h4><div class="chips" id="fStatus"></div></div>
<div class="rsec"><h4>Submarket / city</h4><div class="chips" id="fCity"></div></div>
<div class="rsec"><h4>Price</h4><div class="row2"><div class="fld"><label>Min $</label><input type="number" id="pMin" placeholder="any" step="50000"></div><div class="fld"><label>Max $</label><input type="number" id="pMax" placeholder="any" step="50000"></div></div></div>
@@ -121,7 +121,8 @@ const COLS=[
{k:'source',l:'Source',t:'link'},
];
let DATA=[], view='table', sortKey='rank', sortDir=1, q='';
-const F={types:new Set(),cities:new Set(),status:new Set(),pmin:null,pmax:null};
+const F={types:new Set(),cities:new Set(),status:new Set(),pmin:null,pmax:null,unwarrantable:false};
+function mapCondo(c){ return { id:c.id, address:c.address, city:c.city, zip:c.zip, type:'Condo', price:+c.price||0, units:1, cap_rate:null, year_built:c.year_built||null, status:'Active', source:c.source, firm:c.firm_name||null, beds:c.beds, baths:c.baths, sqft:c.sqft, hoa:c.hoa, warrantable_status:c.warrantable_status, broker_name:c.broker_name, project_name:c.project_name }; }
let VISCOL=(function(){let s={};try{s=JSON.parse(localStorage.getItem('mlsCols')||'{}');}catch(e){}return s;})();
function colVis(k){ if(k in VISCOL) return !!VISCOL[k]; const c=COLS.find(x=>x.k===k); return c?c.def!==0:true; }
function visCols(){ return COLS.filter(c=>colVis(c.k)); }
@@ -131,7 +132,8 @@ function tog(set,v){ set.has(v)?set.delete(v):set.add(v); }
function autoCols(){
const known=new Set(COLS.map(c=>c.k)); const skip=new Set(['id','finance','qwen','deal','warrant_signals','listings_json','tx_history','risks']);
const seen=new Set();
- DATA.slice(0,60).forEach(r=>Object.keys(r||{}).forEach(k=>{
+ // sample from BOTH ends so appended datasets (e.g. condos) contribute their unique fields
+ DATA.slice(0,50).concat(DATA.slice(-50)).forEach(r=>Object.keys(r||{}).forEach(k=>{
if(known.has(k)||skip.has(k)||seen.has(k)) return; const v=r[k]; if(v==null) return;
if(typeof v==='object') return; seen.add(k);
const isNum = typeof v==='number' || (v!==''&&!isNaN(+v));
@@ -152,6 +154,7 @@ function passFacets(r){
if(F.status.size){ const s=String(r.status||'').startsWith('Active')?'Active':'Off-market'; if(!F.status.has(s)) return false; }
if(F.pmin!=null && !(+r.price>=F.pmin)) return false;
if(F.pmax!=null && !(+r.price<=F.pmax)) return false;
+ if(F.unwarrantable && !(r.type==='Condo' && r.warrantable_status && r.warrantable_status!=='fha_approved')) return false;
return true;
}
const cval=(r,c)=>c.calc?c.calc(r):r[c.k];
@@ -233,7 +236,11 @@ $('#pMax').addEventListener('input',()=>{F.pmax=$('#pMax').value?+$('#pMax').val
$('#fFields').addEventListener('change',e=>{const cb=e.target.closest('input[data-ck]');if(!cb)return;VISCOL[cb.dataset.ck]=cb.checked;saveCols();render();});
$('#fAll').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=true);saveCols();buildFieldToggles();render();});
$('#fNone').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=false);saveCols();buildFieldToggles();render();});
-fetch('/data/ranked.json').then(r=>r.json()).then(d=>{ DATA=(d.ranked||d)||[]; autoCols(); buildRail(); render(); })
+const unwEl=document.getElementById('unwChip');
+if(unwEl) unwEl.addEventListener('click',()=>{ F.unwarrantable=!F.unwarrantable; unwEl.classList.toggle('active',F.unwarrantable); if(F.unwarrantable){ F.types=new Set(['Condo']); document.querySelectorAll('#fType .chip').forEach(x=>x.classList.toggle('active',x.dataset.ty==='Condo')); } render(); });
+fetch('/data/ranked.json').then(r=>r.json()).then(async d=>{ DATA=(d.ranked||d)||[];
+ try{ const cd=await (await fetch('/api/condos')).json(); DATA=DATA.concat((cd.condos||[]).map(mapCondo)); }catch(e){}
+ autoCols(); buildRail(); render(); })
.catch(()=>{ $('#count').textContent='failed to load /data/ranked.json'; });
</script>
</body>
← ad43c25 auto-save: 2026-07-02T10:17:10 (1 files) — public/index.html
·
back to Commercialrealestate
·
feat(index): collapsible filter tabs (all collapsed on load) e9d13a8 →