← back to Commercialrealestate
deals-flow: 'look up any LA property' address box -> dossier (TK-10313 Cycle 4)
1d92fcb954074b0cdc708eaa05cba94ba2d94e51 · 2026-08-06 18:07:15 -0700 · Steve Abrams
Adds a lookup bar: type any LA address -> /api/property/by-address -> the same
public-record dossier modal. Refactored the renderer into renderDossier(d) reused
by both the by-AIN drill and by-address lookup; multi-parcel matches show a
pick-list (.pdpick -> openPropDetail). Enter + button wired; in-flight guard kept.
Extends enrichment to ANY property, not just grid deals. Verified via Chrome
(single hit, 8-candidate pick-list, pick->dossier). Use-code normalization
deferred (needs authoritative LA use-code legend to avoid mislabels). $0 local.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 1d92fcb954074b0cdc708eaa05cba94ba2d94e51
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 6 18:07:15 2026 -0700
deals-flow: 'look up any LA property' address box -> dossier (TK-10313 Cycle 4)
Adds a lookup bar: type any LA address -> /api/property/by-address -> the same
public-record dossier modal. Refactored the renderer into renderDossier(d) reused
by both the by-AIN drill and by-address lookup; multi-parcel matches show a
pick-list (.pdpick -> openPropDetail). Enter + button wired; in-flight guard kept.
Extends enrichment to ANY property, not just grid deals. Verified via Chrome
(single hit, 8-candidate pick-list, pick->dossier). Use-code normalization
deferred (needs authoritative LA use-code legend to avoid mislabels). $0 local.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/deals-flow.html | 45 +++++++++++++++++++++++++++++++++++----------
1 file changed, 35 insertions(+), 10 deletions(-)
diff --git a/public/deals-flow.html b/public/deals-flow.html
index 10e3a85..1e3256c 100644
--- a/public/deals-flow.html
+++ b/public/deals-flow.html
@@ -123,6 +123,10 @@ select{background:var(--card);border:1px solid var(--line);color:var(--ink);bord
<button id="watchBtn" title="Show only starred deals" style="background:var(--card);border:1px solid var(--line);color:var(--gold);border-radius:8px;padding:7px 10px;font-size:12px;cursor:pointer">☆ Watchlist</button>
<span class="count" id="count"></span>
</div>
+ <div class="toolbar" style="margin-top:-4px">
+ <input class="search" id="propLookup" placeholder="🔍 Look up ANY LA property by address — full public-record dossier" style="flex:1" title="Enter a street address; opens the public-record dossier (assessor + recent sales) for that parcel" aria-label="Look up any LA property by address">
+ <button id="propLookupBtn" style="background:var(--card);border:1px solid var(--blue);color:var(--blue);border-radius:8px;padding:7px 14px;font-size:12px;cursor:pointer">Look up →</button>
+ </div>
<div class="grid" id="grid"></div>
</div></div>
@@ -315,17 +319,12 @@ $('#fSaved').addEventListener('click',async e=>{const del=e.target.closest('[dat
function syncRailActive(attr,set){const map={s:'#fSrc',u:'#fUse',y:'#fYear',c:'#fConf'},el=$(map[attr]);if(el)el.querySelectorAll('.chip').forEach(ch=>ch.classList.toggle('active',set.has(ch.dataset[attr])));}
// ── property-detail drill: fetch the free public-record dossier + render inline ──
let _pdSeq=0; // guards against a stale fetch overwriting a newer one on rapid clicks (Cody Cycle-3)
-async function openPropDetail(ain){
- const seq=++_pdSeq;
- const m=$('#propModal'),body=$('#pmBody'),title=$('#pmTitle');
- title.textContent='Property '+ain; body.innerHTML='<span style="color:var(--mut)">Loading public records…</span>'; m.classList.add('on');
- let d; try{ d=await api('/api/property/'+encodeURIComponent(ain)); }catch(err){ if(seq===_pdSeq)body.innerHTML='<span style="color:var(--bad,#f85149)">Lookup failed: '+esc(err.message)+'</span>'; return; }
- if(seq!==_pdSeq)return; // a newer drill superseded this one — drop the stale render
- if(!d||!d.found){ body.innerHTML='<span class="mut">No assessor record for AIN '+esc(ain)+'.</span>'; return; }
- const p=d.parcel||{};
- const row=(k,v)=>(v||v===0)?`<div style="display:flex;gap:8px;padding:3px 0;border-bottom:1px solid var(--line)"><span style="color:var(--mut);min-width:140px;flex-shrink:0">${k}</span><span>${esc(String(v))}</span></div>`:'';
+// Render a dossier object (from /api/property/:ain or a resolved by-address hit) into the modal.
+function renderDossier(d,fallbackTitle){
+ const body=$('#pmBody'),title=$('#pmTitle'),p=d.parcel||{};
+ const row=(k,v)=>(v||v===0)?`<div style="display:flex;gap:8px;padding:3px 0;border-bottom:1px solid var(--line)"><span style="color:var(--mut);min-width:140px;flex-shrink:0">${esc(k)}</span><span>${esc(String(v))}</span></div>`:'';
const dollar=v=>v?('$'+Number(v).toLocaleString()):'';
- title.textContent=p.address||('AIN '+ain);
+ title.textContent=p.address||fallbackTitle||'Property';
let html='<div style="font-size:12.5px">'
+row('AIN',p.ain)+row('Address',p.address)+row('Use',p.use_desc||p.use_type)+row('Year built',p.year_built)
+row('Sq ft',p.sqft)+row('Units',p.units)+row('Beds / baths',(p.bedrooms||p.bathrooms)?((p.bedrooms||'?')+' / '+(p.bathrooms||'?')):'')
@@ -340,6 +339,32 @@ async function openPropDetail(ain){
html+=`<div class="mut" style="font-size:10.5px;margin-top:10px">Sources: ${esc((d.sources||[]).join(' · '))}</div></div>`;
body.innerHTML=html;
}
+async function openPropDetail(ain){
+ const seq=++_pdSeq,body=$('#pmBody'),title=$('#pmTitle');
+ title.textContent='Property '+ain; body.innerHTML='<span style="color:var(--mut)">Loading public records…</span>'; $('#propModal').classList.add('on');
+ let d; try{ d=await api('/api/property/'+encodeURIComponent(ain)); }catch(err){ if(seq===_pdSeq)body.innerHTML='<span style="color:var(--bad,#f85149)">Lookup failed: '+esc(err.message)+'</span>'; return; }
+ if(seq!==_pdSeq)return;
+ if(!d||!d.found){ body.innerHTML='<span class="mut">No assessor record for AIN '+esc(ain)+'.</span>'; return; }
+ renderDossier(d,'AIN '+ain);
+}
+// Look up ANY LA property by address → dossier (or a pick-list when multiple parcels match).
+async function openPropDetailByAddress(qstr){
+ const q=String(qstr||'').trim(); if(!q)return;
+ const seq=++_pdSeq,body=$('#pmBody'),title=$('#pmTitle');
+ title.textContent='Property lookup'; body.innerHTML='<span style="color:var(--mut)">Searching public records for “'+esc(q)+'”…</span>'; $('#propModal').classList.add('on');
+ let d; try{ d=await api('/api/property/by-address?q='+encodeURIComponent(q)); }catch(err){ if(seq===_pdSeq)body.innerHTML='<span style="color:var(--bad,#f85149)">Lookup failed: '+esc(err.message)+'</span>'; return; }
+ if(seq!==_pdSeq)return;
+ if(!d||!d.found){ body.innerHTML='<span class="mut">No LA County parcel matched “'+esc(q)+'”. Try a house number + street (e.g. 4150 Madison Ave).</span>'; return; }
+ if(d.multiple&&Array.isArray(d.candidates)){
+ title.textContent='Matches for “'+q+'”';
+ body.innerHTML='<div class="mut" style="margin-bottom:8px">'+d.candidates.length+' parcels matched — pick one:</div>'+d.candidates.map(c=>`<div class="pdpick" data-ain="${esc(String(c.ain||'').replace(/\D/g,''))}" style="cursor:pointer;padding:7px 9px;border:1px solid var(--line);border-radius:8px;margin-bottom:6px"><b>${esc(c.address||c.ain)}</b><div class="mut" style="font-size:11px">${esc(c.use_desc||c.use_type||'')} · AIN ${esc(c.ain)}</div></div>`).join('');
+ return;
+ }
+ renderDossier(d,q);
+}
+$('#pmBody').addEventListener('click',e=>{const pk=e.target.closest('.pdpick');if(pk)openPropDetail(pk.dataset.ain);});
+$('#propLookupBtn').addEventListener('click',()=>openPropDetailByAddress($('#propLookup').value));
+$('#propLookup').addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();openPropDetailByAddress(e.target.value);}});
$('#propModal').addEventListener('click',e=>{if(e.target.id==='propModal')e.currentTarget.classList.remove('on');});
$('#grid').addEventListener('click',async e=>{
const pd=e.target.closest('.propdetail');if(pd){openPropDetail(pd.dataset.ain);return;}
← f6db22c CRCP mls: surface '🕓 Newest (first seen)' at top of sort dr
·
back to Commercialrealestate
·
auto-data-snapshot: 2026-08-06T18:06:44 (2 data files) — dat 899eba8 →