← back to Commercialrealestate
deals-flow: property-detail drill — click a county deal -> inline public-record dossier (TK-10313 Cycle 3)
e08762443825e43c261656d92a0235a3aa74d1d3 · 2026-08-06 17:32:50 -0700 · Steve Abrams
County deal cards (which carry an AIN) get a '🔍 property detail' link that fetches
/api/property/:ain and renders the free dossier inline in a modal: parcel facts
(address/use/size/age/units/beds-baths), assessed values, roll year, last transfer,
the ULA $5.3M+ ZIP-candidate sales (with instrument numbers), and the gated-deed
declaration. Verified via real-Chrome screenshot on 4150 Madison Ave. $0 local.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit e08762443825e43c261656d92a0235a3aa74d1d3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 6 17:32:50 2026 -0700
deals-flow: property-detail drill — click a county deal -> inline public-record dossier (TK-10313 Cycle 3)
County deal cards (which carry an AIN) get a '🔍 property detail' link that fetches
/api/property/:ain and renders the free dossier inline in a modal: parcel facts
(address/use/size/age/units/beds-baths), assessed values, roll year, last transfer,
the ULA $5.3M+ ZIP-candidate sales (with instrument numbers), and the gated-deed
declaration. Verified via real-Chrome screenshot on 4150 Madison Ave. $0 local.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/deals-flow.html | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/public/deals-flow.html b/public/deals-flow.html
index 829e1a0..0385c25 100644
--- a/public/deals-flow.html
+++ b/public/deals-flow.html
@@ -126,6 +126,14 @@ select{background:var(--card);border:1px solid var(--line);color:var(--ink);bord
<div class="grid" id="grid"></div>
</div></div>
+<!-- property-detail drill (public-record dossier) -->
+<div class="modal" id="propModal" role="dialog" aria-modal="true" aria-label="Property detail">
+ <div class="box" style="max-width:560px;width:100%;max-height:82vh;overflow:auto">
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><h3 style="margin:0;flex:1" id="pmTitle">Property detail</h3><span style="cursor:pointer;color:var(--mut);font-size:18px" onclick="document.getElementById('propModal').classList.remove('on')">✕</span></div>
+ <div id="pmBody"><span style="color:var(--mut)">Loading…</span></div>
+ </div>
+</div>
+
<!-- sign-in modal (username + password) -->
<div class="modal" id="loginModal" role="dialog" aria-modal="true" aria-label="Sign in to CRCP">
<div class="box">
@@ -204,6 +212,9 @@ function card(r){
if(r.date) details.push(`<span class="i">📅 ${esc(r.date)}</span>`);
if(r.ticker) details.push(`<span class="i">📈 ${esc(r.ticker)}</span>`);
if(r.holder) details.push(`<span class="i">🏦 ${esc(String(r.holder).slice(0,24))}</span>`);
+ // Property-detail drill (county cards have an AIN): opens the free public-record dossier inline.
+ const ainForDrill=r.ain?String(r.ain).replace(/\D/g,''):'';
+ if(isC&&ainForDrill) details.push(`<span class="i propdetail" data-ain="${ainForDrill}" style="cursor:pointer;color:var(--blue)" title="Full public-record dossier (assessor + ULA + deed)">🔍 property detail</span>`);
const conf=r.confidence?`<span class="conf ${r.confidence}">${r.confidence} confidence</span>`:'';
const link=r.url?`<a class="f" href="${esc(r.url)}" target="_blank" rel="noopener noreferrer">🔗 SEC filing ↗</a>`:'';
const bigPrice=(price&&(isC||isF))?`<div class="price">${price}${isC?' <span class="mut" style="font-size:11px">est sale</span>':isF?' <span class="mut" style="font-size:11px">loan</span>':''}</div>`:'';
@@ -302,7 +313,33 @@ $('#watchBtn').addEventListener('click',()=>{if(!ME||!ME.email){signIn();return;
$('#fSaved').addEventListener('click',async e=>{const del=e.target.closest('[data-del]');if(del){await fetch('/api/saved-searches/'+del.dataset.del,{method:'DELETE'});loadSaved();return;}const c=e.target.closest('[data-sid]');if(!c)return;const s=(window._saved||[]).find(x=>x.id===c.dataset.sid);if(!s)return;const f=s.filters||{};F.src=new Set(f.src||[]);F.use=new Set(f.use||[]);F.year=new Set(f.year||[]);F.conf=new Set(f.conf||[]);F.pMin=f.pMin??null;F.uMin=f.uMin??null;q=f.q||'';$('#q').value=q;render();});
// href-drill rule: card values drill to their filtered view (reuses the rail F filters + keeps rail chips in sync)
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 ──
+async function openPropDetail(ain){
+ 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){ body.innerHTML='<span style="color:var(--bad,#f85149)">Lookup failed: '+esc(err.message)+'</span>'; return; }
+ 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>`:'';
+ const dollar=v=>v?('$'+Number(v).toLocaleString()):'';
+ title.textContent=p.address||('AIN '+ain);
+ 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||'?')):'')
+ +row('Assessed land',dollar(p.assessed_land))+row('Assessed improvement',dollar(p.assessed_improvement))+row('Assessed total',dollar(p.assessed_total))
+ +row('Roll year',p.assessed_as_of)+row('Last transfer recorded',p.last_transfer_recorded);
+ if(d.ula_zip_candidates&&d.ula_zip_candidates.length){
+ html+=`<div style="margin-top:12px;font-weight:600;color:var(--gold)">Recent $5.3M+ sales in ZIP ${esc(p.zip||'')} (ULA · candidates)</div>`;
+ d.ula_zip_candidates.slice(0,6).forEach(c=>{html+=`<div style="padding:3px 0;border-bottom:1px solid var(--line)">${esc(c.date||'')} · <b style="color:var(--gold)">$${Number(c.price).toLocaleString()}</b> · ${esc(c.use||'')}${c.instrument?` · <span class="mut">instr ${esc(c.instrument)}</span>`:''}</div>`;});
+ if(d.ula_note)html+=`<div class="mut" style="font-size:11px;margin-top:4px">${esc(d.ula_note)}</div>`;
+ }
+ if(d.deed&&!d.deed.available){ html+=`<div style="margin-top:12px;font-weight:600">Buyer / seller (recorded deed)</div><div class="mut" style="font-size:11.5px">${esc(d.deed.reason)}</div>`; }
+ html+=`<div class="mut" style="font-size:10.5px;margin-top:10px">Sources: ${esc((d.sources||[]).join(' · '))}</div></div>`;
+ body.innerHTML=html;
+}
+$('#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;}
const fu=e.target.closest('[data-fu]');if(fu){tog(F.use,fu.dataset.fu);syncRailActive('u',F.use);render();return;}
const fs=e.target.closest('[data-fs]');if(fs){tog(F.src,fs.dataset.fs);syncRailActive('s',F.src);render();return;}
const b=e.target.closest('.starbtn');if(!b)return;if(!ME||!ME.email){signIn();return;}const r=await api('/api/watchlist',{method:'POST',body:JSON.stringify({id:b.dataset.k})});WATCH=new Set(r.ids||[]);render();});
← 2d61ca7 enrich endpoints: Cody Cycle-2 hardening (timeout + address
·
back to Commercialrealestate
·
property-detail drill: Cody Cycle-3 fixes (Escape-close + st 59bdb78 →