[object Object]

← back to Commercialrealestate

CRCP properties: add Max units + Built-before to the units/size filter (min AND max bounds)

706f95a459377692815d6638d65e43a108e6e3f3 · 2026-08-17 16:02:17 -0700 · steve

Files touched

Diff

commit 706f95a459377692815d6638d65e43a108e6e3f3
Author: steve <steve@designerwallcoverings.com>
Date:   Mon Aug 17 16:02:17 2026 -0700

    CRCP properties: add Max units + Built-before to the units/size filter (min AND max bounds)
---
 public/index.html | 37 ++++++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 9 deletions(-)

diff --git a/public/index.html b/public/index.html
index 3ebb3a6..0974c7d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -440,7 +440,9 @@
   </div>
   <div class="fsec"><h4>Units / size</h4>
     <div class="row2"><div class="fld"><label>Min units</label><input type="number" id="fUnits" placeholder="any" min="0" step="1"></div>
-    <div class="fld"><label>Built after</label><input type="number" id="fYear" placeholder="any" min="1900" max="2026" step="1"></div></div>
+    <div class="fld"><label>Max units</label><input type="number" id="fUnitsMax" placeholder="any" min="0" step="1"></div></div>
+    <div class="row2" style="margin-top:8px"><div class="fld"><label>Built after</label><input type="number" id="fYear" placeholder="any" min="1900" max="2026" step="1"></div>
+    <div class="fld"><label>Built before</label><input type="number" id="fYearMax" placeholder="any" min="1900" max="2026" step="1"></div></div>
   </div>
   <div class="fsec"><h4>Status &amp; budget</h4>
     <div class="chips" id="fStatus">
@@ -1001,7 +1003,7 @@ function loopnetLink(p){
 }
 // Rich filter state
 const F = { types:new Set(), firms:new Set(), cityQuery:'', priceMin:null, priceMax:null,
-            capMin:0, unitsMin:null, yearMin:null, status:'all', afford:false, unwarrantable:false, nonqm:false, shortlist:false };
+            capMin:0, unitsMin:null, unitsMax:null, yearMin:null, yearMax:null, status:'all', afford:false, unwarrantable:false, nonqm:false, shortlist:false };
 
 // ── SEARCH ALL DATA (Steve list-build rule 7) ──────────────────────────────────
 // Free-text search across EVERY field on a listing — not just the visible/defined columns,
@@ -1291,7 +1293,9 @@ function applyFilters(list){
     if(F.priceMax!=null && p.price>F.priceMax) return false;
     if(F.capMin>0 && !(p.cap_rate!=null && p.cap_rate>=F.capMin)) return false;
     if(F.unitsMin!=null && !(p.units>=F.unitsMin)) return false;
+    if(F.unitsMax!=null && !(p.units!=null && p.units<=F.unitsMax)) return false;
     if(F.yearMin!=null && !(p.year_built && p.year_built>=F.yearMin)) return false;
+    if(F.yearMax!=null && !(p.year_built && p.year_built<=F.yearMax)) return false;
     if(F.status==='active' && /Under Contract|Pending|OUT OF BUDGET/i.test(p.status)) return false;
     if(F.afford && !p.finance.affordable) return false;
     if(F.unwarrantable && !(p.isCondo && p.warrantable_status && p.warrantable_status!=='fha_approved')) return false;
@@ -1310,7 +1314,9 @@ function passExcept(p, skip){
   if(F.priceMax!=null && p.price>F.priceMax) return false;
   if(F.capMin>0 && !(p.cap_rate!=null && p.cap_rate>=F.capMin)) return false;
   if(F.unitsMin!=null && !(p.units>=F.unitsMin)) return false;
+  if(F.unitsMax!=null && !(p.units!=null && p.units<=F.unitsMax)) return false;
   if(F.yearMin!=null && !(p.year_built && p.year_built>=F.yearMin)) return false;
+  if(F.yearMax!=null && !(p.year_built && p.year_built<=F.yearMax)) return false;
   if(F.status==='active' && /Under Contract|Pending|OUT OF BUDGET/i.test(p.status)) return false;
   if(F.afford && !p.finance.affordable) return false;
   if(F.unwarrantable && !(p.isCondo && p.warrantable_status && p.warrantable_status!=='fha_approved')) return false;
@@ -1332,7 +1338,7 @@ function activeFilterCount(){
   let n=0;
   if(F.types.size) n++; if(F.firms.size) n++; if(F.cityQuery) n++;
   if(F.priceMin!=null||F.priceMax!=null) n++; if(F.capMin>0) n++;
-  if(F.unitsMin!=null) n++; if(F.yearMin!=null) n++;
+  if(F.unitsMin!=null) n++; if(F.unitsMax!=null) n++; if(F.yearMin!=null) n++; if(F.yearMax!=null) n++;
   if(F.status!=='all') n++; if(F.afford) n++; if(F.unwarrantable) n++; if(F.nonqm) n++; if(F.shortlist) n++;
   return n;
 }
@@ -1356,7 +1362,9 @@ function renderActiveBar(){
   if(F.priceMax!=null) chips.push(['priceMax', '≤ $'+(+F.priceMax).toLocaleString()]);
   if(F.capMin>0) chips.push(['capMin', 'cap ≥ '+F.capMin+'%']);
   if(F.unitsMin!=null) chips.push(['unitsMin', '≥ '+F.unitsMin+' units']);
+  if(F.unitsMax!=null) chips.push(['unitsMax', '≤ '+F.unitsMax+' units']);
   if(F.yearMin!=null) chips.push(['yearMin', 'built ≥ '+F.yearMin]);
+  if(F.yearMax!=null) chips.push(['yearMax', 'built ≤ '+F.yearMax]);
   if(F.status!=='all') chips.push(['status', 'active only']);
   if(F.afford) chips.push(['afford', 'in budget']);
   if(F.unwarrantable) chips.push(['unwarrantable', '⚑ unwarrantable']);
@@ -1374,7 +1382,9 @@ function clearFilter(spec){
   else if(spec==='priceMax'){ F.priceMax=null; if($('#fPriceMax')) $('#fPriceMax').value=''; }
   else if(spec==='capMin'){ F.capMin=0; if($('#fCap')){ $('#fCap').value=0; $('#capVal').textContent='any'; } }
   else if(spec==='unitsMin'){ F.unitsMin=null; if($('#fUnits')) $('#fUnits').value=''; }
+  else if(spec==='unitsMax'){ F.unitsMax=null; if($('#fUnitsMax')) $('#fUnitsMax').value=''; }
   else if(spec==='yearMin'){ F.yearMin=null; if($('#fYear')) $('#fYear').value=''; }
+  else if(spec==='yearMax'){ F.yearMax=null; if($('#fYearMax')) $('#fYearMax').value=''; }
   else if(spec==='status'){ F.status='all'; }
   else if(spec==='afford'){ F.afford=false; if($('#fAfford')) $('#fAfford').checked=false; }
   else if(spec==='unwarrantable'){ F.unwarrantable=false; }
@@ -1392,7 +1402,9 @@ function stateToURL(){
   if(F.priceMax!=null) p.set('pmax',F.priceMax);
   if(F.capMin>0) p.set('cap',F.capMin);
   if(F.unitsMin!=null) p.set('u',F.unitsMin);
+  if(F.unitsMax!=null) p.set('ux',F.unitsMax);
   if(F.yearMin!=null) p.set('yr',F.yearMin);
+  if(F.yearMax!=null) p.set('yrx',F.yearMax);
   if(F.status!=='all') p.set('st',F.status);
   if(F.afford) p.set('aff','1');
   if(F.unwarrantable) p.set('unw','1');
@@ -1415,7 +1427,9 @@ function urlToState(){
   if(p.get('pmax')){ F.priceMax=+p.get('pmax'); if($('#fPriceMax'))$('#fPriceMax').value=F.priceMax; any=true; }
   if(p.get('cap')){ F.capMin=+p.get('cap'); if($('#fCap')){$('#fCap').value=F.capMin; $('#capVal').textContent=F.capMin+'%+';} any=true; }
   if(p.get('u')){ F.unitsMin=+p.get('u'); if($('#fUnits'))$('#fUnits').value=F.unitsMin; any=true; }
+  if(p.get('ux')){ F.unitsMax=+p.get('ux'); if($('#fUnitsMax'))$('#fUnitsMax').value=F.unitsMax; any=true; }
   if(p.get('yr')){ F.yearMin=+p.get('yr'); if($('#fYear'))$('#fYear').value=F.yearMin; any=true; }
+  if(p.get('yrx')){ F.yearMax=+p.get('yrx'); if($('#fYearMax'))$('#fYearMax').value=F.yearMax; any=true; }
   if(p.get('st')){ F.status=p.get('st'); any=true; }
   if(p.get('aff')==='1'){ F.afford=true; if($('#fAfford'))$('#fAfford').checked=true; any=true; }
   if(p.get('unw')==='1'){ F.unwarrantable=true; any=true; }
@@ -1596,7 +1610,7 @@ function buildFacets(){
 function getContext(){
   const lines=[];
   lines.push(`MARKET: ${DATA.meta.market}. Budget: $400,000 down. Active scenario: ${scenario==='cash'?'ALL CASH':SCEN[scenario].downPct+'% down, '+SCEN[scenario].ratePct+'%/'+SCEN[scenario].amortYears+'yr'}.`);
-  lines.push(`Active filters: types=[${[...F.types]}], firms=[${[...F.firms]}], city~"${F.cityQuery}", price ${F.priceMin||'any'}-${F.priceMax||'any'}, capMin=${F.capMin}, unitsMin=${F.unitsMin||'any'}, builtAfter=${F.yearMin||'any'}, status=${F.status}, inBudgetOnly=${F.afford}, sort=${$('#sort').value}.`);
+  lines.push(`Active filters: types=[${[...F.types]}], firms=[${[...F.firms]}], city~"${F.cityQuery}", price ${F.priceMin||'any'}-${F.priceMax||'any'}, capMin=${F.capMin}, unitsMin=${F.unitsMin||'any'}, unitsMax=${F.unitsMax||'any'}, builtAfter=${F.yearMin||'any'}, builtBefore=${F.yearMax||'any'}, status=${F.status}, inBudgetOnly=${F.afford}, sort=${$('#sort').value}.`);
   lines.push('RANKED PROPERTIES (current scenario):');
   applyFilters(DATA.ranked).slice(0,120).forEach(p=>{ const f=p.finance;
     lines.push(`#${p.rank} id=${p.id} | ${p.address}, ${p.city} | ${p.type} | $${p.price.toLocaleString()} | ${p.units}u | cap ${p.cap_rate!=null?p.cap_rate+'%':'n/d'}${p.verified===true?' VERIFIED':''} | firm ${firmLabel(p)} | CoC ${f.coc!=null?f.coc+'%':'n/a'} | DSCR ${f.dscr??'n/a'} | cash needed $${f.cashNeeded.toLocaleString()} ${f.affordable?'(in budget)':'(OVER)'} | composite ${p.composite} | status ${p.status}`);
@@ -1628,6 +1642,9 @@ function applyAction(a){
   if(a.minPrice!=null && !isNaN(+a.minPrice)){ F.priceMin=+a.minPrice; $('#fPriceMin').value=+a.minPrice; done.push('min $'+(+a.minPrice).toLocaleString()); }
   if(a.capMin!=null && !isNaN(+a.capMin)){ F.capMin=+a.capMin; $('#fCap').value=+a.capMin; $('#capVal').textContent=F.capMin?F.capMin+'%+':'any'; done.push('cap ≥'+a.capMin+'%'); }
   if(a.unitsMin!=null && !isNaN(+a.unitsMin)){ F.unitsMin=+a.unitsMin; $('#fUnits').value=+a.unitsMin; done.push('units ≥'+a.unitsMin); }
+  if(a.unitsMax!=null && !isNaN(+a.unitsMax)){ F.unitsMax=+a.unitsMax; $('#fUnitsMax').value=+a.unitsMax; done.push('units ≤'+a.unitsMax); }
+  if(a.yearMin!=null && !isNaN(+a.yearMin)){ F.yearMin=+a.yearMin; $('#fYear').value=+a.yearMin; done.push('built ≥'+a.yearMin); }
+  if(a.yearMax!=null && !isNaN(+a.yearMax)){ F.yearMax=+a.yearMax; $('#fYearMax').value=+a.yearMax; done.push('built ≤'+a.yearMax); }
   syncChips(); render();
   if(a.highlightId){ const el=$('#card-'+a.highlightId); if(el){ el.scrollIntoView({behavior:'smooth',block:'center'}); el.classList.add('flash'); setTimeout(()=>el.classList.remove('flash'),2200); done.push('→'+a.highlightId); } }
   return done;
@@ -1646,17 +1663,17 @@ function syncChips(){
 // ---- persistence ----
 function saveState(){ try{ localStorage.setItem('cre_filters', JSON.stringify({
   types:[...F.types],firms:[...F.firms],cityQuery:F.cityQuery,priceMin:F.priceMin,priceMax:F.priceMax,
-  capMin:F.capMin,unitsMin:F.unitsMin,yearMin:F.yearMin,status:F.status,afford:F.afford,sort:$('#sort').value })); }catch(e){} }
+  capMin:F.capMin,unitsMin:F.unitsMin,unitsMax:F.unitsMax,yearMin:F.yearMin,yearMax:F.yearMax,status:F.status,afford:F.afford,sort:$('#sort').value })); }catch(e){} }
 function loadState(){ try{ const s=JSON.parse(localStorage.getItem('cre_filters')||'{}');
   if(s.types) F.types=new Set(s.types); if(s.firms) F.firms=new Set(s.firms);
   F.cityQuery=s.cityQuery||''; F.priceMin=s.priceMin??null; F.priceMax=s.priceMax??null;
-  F.capMin=s.capMin||0; F.unitsMin=s.unitsMin??null; F.yearMin=s.yearMin??null; F.status=s.status||'all'; F.afford=!!s.afford;
+  F.capMin=s.capMin||0; F.unitsMin=s.unitsMin??null; F.unitsMax=s.unitsMax??null; F.yearMin=s.yearMin??null; F.yearMax=s.yearMax??null; F.status=s.status||'all'; F.afford=!!s.afford;
   if(s.cityQuery) $('#fCity').value=s.cityQuery; if(s.priceMin) $('#fPriceMin').value=s.priceMin; if(s.priceMax) $('#fPriceMax').value=s.priceMax;
-  if(s.capMin){ $('#fCap').value=s.capMin; $('#capVal').textContent=s.capMin+'%+'; } if(s.unitsMin) $('#fUnits').value=s.unitsMin; if(s.yearMin) $('#fYear').value=s.yearMin;
+  if(s.capMin){ $('#fCap').value=s.capMin; $('#capVal').textContent=s.capMin+'%+'; } if(s.unitsMin) $('#fUnits').value=s.unitsMin; if(s.unitsMax) $('#fUnitsMax').value=s.unitsMax; if(s.yearMin) $('#fYear').value=s.yearMin; if(s.yearMax) $('#fYearMax').value=s.yearMax;
   $('#fAfford').checked=F.afford; if(s.sort){ $('#sort').value=s.sort; }
 }catch(e){} }
-function resetAll(){ F.types.clear(); F.firms.clear(); F.cityQuery=''; F.priceMin=F.priceMax=F.unitsMin=F.yearMin=null; F.capMin=0; F.status='all'; F.afford=false; F.unwarrantable=false; F.nonqm=false;
-  ['fCity','fPriceMin','fPriceMax','fUnits','fYear'].forEach(id=>$('#'+id).value=''); $('#fCap').value=0; $('#capVal').textContent='any'; $('#fAfford').checked=false;
+function resetAll(){ F.types.clear(); F.firms.clear(); F.cityQuery=''; F.priceMin=F.priceMax=F.unitsMin=F.unitsMax=F.yearMin=F.yearMax=null; F.capMin=0; F.status='all'; F.afford=false; F.unwarrantable=false; F.nonqm=false;
+  ['fCity','fPriceMin','fPriceMax','fUnits','fUnitsMax','fYear','fYearMax'].forEach(id=>$('#'+id).value=''); $('#fCap').value=0; $('#capVal').textContent='any'; $('#fAfford').checked=false;
   syncChips(); render(); }
 
 // ---- chat ----
@@ -1958,7 +1975,9 @@ function loadRanked(_retry){ return fetch('data/ranked.json').then(r=>{ if(!r.ok
   $('#fPriceMax').oninput=()=>{ F.priceMax=$('#fPriceMax').value?+$('#fPriceMax').value:null; render(); };
   $('#fCap').oninput=()=>{ F.capMin=+$('#fCap').value; $('#capVal').textContent=F.capMin?F.capMin+'%+':'any'; render(); };
   $('#fUnits').oninput=()=>{ F.unitsMin=$('#fUnits').value?+$('#fUnits').value:null; render(); };
+  $('#fUnitsMax').oninput=()=>{ F.unitsMax=$('#fUnitsMax').value?+$('#fUnitsMax').value:null; render(); };
   $('#fYear').oninput=()=>{ F.yearMin=$('#fYear').value?+$('#fYear').value:null; render(); };
+  $('#fYearMax').oninput=()=>{ F.yearMax=$('#fYearMax').value?+$('#fYearMax').value:null; render(); };
   $('#fAfford').onchange=()=>{ F.afford=$('#fAfford').checked; render(); };
   $('#scenario').onclick=e=>{ if(!e.target.dataset.s) return; setScenario(e.target.dataset.s); render(); };
 

← 7c44543 auto-data-snapshot: 2026-08-17T15:59:57 (30 data files) — ct  ·  back to Commercialrealestate  ·  CRCP listings + just-listed: add Max units (and Units/size m b36a4de →