[object Object]

← 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 →