[object Object]

← back to Commercialrealestate

deal-flow card viewer: icon cards over both free public sources (assessor sales + SEC EDGAR REIT), per-card source badge, left filters + sort + density; 15040 deals $27.69B; headless-verified

4c2ac2a773622afefbbf9207f35410d747a56be9 · 2026-07-31 09:10:37 -0700 · steve

Files touched

Diff

commit 4c2ac2a773622afefbbf9207f35410d747a56be9
Author: steve <steve@designerwallcoverings.com>
Date:   Fri Jul 31 09:10:37 2026 -0700

    deal-flow card viewer: icon cards over both free public sources (assessor sales + SEC EDGAR REIT), per-card source badge, left filters + sort + density; 15040 deals $27.69B; headless-verified
---
 .gitignore                   |   1 +
 public/deals-flow.html       | 156 +++++++++++++++++++++++++++++++++++++++++++
 scripts/export-deals-flow.py |  22 ++++++
 3 files changed, 179 insertions(+)

diff --git a/.gitignore b/.gitignore
index 6ce6cf5..a137049 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ data/*.pre-*.bak
 data/raw/*.pre-*.bak
 
 data/edgar-reit-deals.json
+data/deals-flow.json
diff --git a/public/deals-flow.html b/public/deals-flow.html
new file mode 100644
index 0000000..3c4a824
--- /dev/null
+++ b/public/deals-flow.html
@@ -0,0 +1,156 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Closed Deal Flow — Public Record · CRCP</title>
+<style>
+:root{--bg:#0e1116;--card:#161b22;--line:#2a313c;--ink:#e6edf3;--mut:#8b949e;--acc:#3fb950;--blue:#58a6ff;--gold:#ffa600;--violet:#bc8cff;--cols:4}
+*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:-apple-system,Helvetica,Arial,sans-serif;font-size:13px}
+header{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;flex-wrap:wrap;gap:10px;align-items:center}
+h1{font-size:17px;margin:0}h1 b{color:#fff}
+.badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#0e1116;background:var(--acc);padding:3px 8px;border-radius:6px}
+.nav a{color:var(--blue);text-decoration:none;font-size:12px;margin-left:12px}
+.shell{display:flex;align-items:flex-start}
+.rail{width:230px;flex:0 0 230px;border-right:1px solid var(--line);padding:12px;height:calc(100vh - 52px);overflow:auto;position:sticky;top:0}
+.rsec{border-bottom:1px solid var(--line);padding:9px 0}
+.rsec h4{margin:0 0 7px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut)}
+.chips{display:flex;flex-wrap:wrap;gap:5px}
+.chip{border:1px solid var(--line);border-radius:20px;padding:3px 9px;font-size:11px;cursor:pointer;background:var(--card);user-select:none}
+.chip.active{background:var(--blue);color:#0e1116;border-color:var(--blue);font-weight:600}
+.chip .ct{color:var(--mut);margin-left:4px;font-size:10px}.chip.active .ct{color:#0e1116}
+.rail input[type=number]{width:100%;background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:7px 9px;font-size:12px}
+.main{flex:1;padding:12px 16px;min-width:0}
+.toolbar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:10px}
+.search{flex:1;min-width:200px;background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:8px 11px;font-size:13px}
+select{background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:7px 9px;font-size:12px}
+.count{color:var(--mut);font-size:12px}
+.prov{font-size:11px;color:var(--mut);margin:0 0 12px;line-height:1.5}
+.stat{display:inline-block;background:var(--card);border:1px solid var(--line);border-radius:9px;padding:7px 12px;margin:0 8px 10px 0}
+.stat b{font-size:18px;color:#fff}.stat span{display:block;font-size:10px;text-transform:uppercase;color:var(--mut);letter-spacing:.4px}
+.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:12px}
+.dcard{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:13px 14px;display:flex;flex-direction:column;gap:7px}
+.dcard .top{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
+.dcard .name{font-weight:600;font-size:13.5px;line-height:1.25}
+.src{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;padding:2px 6px;border-radius:6px;white-space:nowrap;flex-shrink:0}
+.src.county{background:rgba(63,185,80,.16);color:var(--acc)}
+.src.edgar{background:rgba(188,140,255,.16);color:var(--violet)}
+.dcard .price{font-size:20px;font-weight:700;color:var(--gold);font-variant-numeric:tabular-nums}
+.dl{display:flex;flex-wrap:wrap;gap:5px 14px;font-size:12px;color:var(--ink)}
+.dl .i{color:var(--mut);display:inline-flex;align-items:center;gap:4px}
+.dl .i b{color:var(--ink);font-weight:600}
+.dcard a.f{color:var(--blue);text-decoration:none;font-size:12px}
+.provline{font-size:10px;color:var(--mut);border-top:1px solid #20262f;padding-top:6px;margin-top:1px;display:flex;align-items:center;gap:5px}
+.conf{font-size:9.5px;padding:1px 6px;border-radius:20px}
+.conf.high{background:rgba(63,185,80,.16);color:var(--acc)} .conf.med{background:rgba(210,153,34,.16);color:var(--gold)} .conf.low{background:rgba(139,148,158,.16);color:var(--mut)}
+</style></head><body>
+<header>
+  <h1><b>Closed Deal Flow</b> · LA Commercial</h1>
+  <span class="badge">100% public record · resellable</span>
+  <div class="nav"><a href="/fha-leads.html">Assumable Leads</a><a href="/mls.html">Listings</a><a href="/">Explorer</a></div>
+</header>
+<div class="shell">
+<aside class="rail">
+  <div class="rsec"><h4>Source</h4><div class="chips" id="fSrc"></div></div>
+  <div class="rsec"><h4>Property type</h4><div class="chips" id="fUse"></div></div>
+  <div class="rsec"><h4>Transaction year</h4><div class="chips" id="fYear"></div></div>
+  <div class="rsec"><h4>Confidence</h4><div class="chips" id="fConf"></div></div>
+  <div class="rsec"><h4>Min price $</h4><input type="number" id="pMin" placeholder="e.g. 5000000" step="1000000"></div>
+  <div class="rsec"><h4>Min units</h4><input type="number" id="uMin" placeholder="e.g. 20"></div>
+</aside>
+<div class="main">
+  <div id="stats"></div>
+  <div class="prov">Every card shows its <b>source</b> (🏛️ LA County Assessor · 📄 SEC EDGAR) — all public-record, resellable, $0. County price = Prop-13 reassessment estimate (assessed value de-escalated to transfer year ≈ sale price). SEC deals are institutional REIT acquisitions filed on Form 8-K.</div>
+  <div class="toolbar">
+    <input class="search" id="q" placeholder="search address, company, use, year…">
+    <select id="sort"></select>
+    <label style="font-size:11px;color:var(--mut)">density <input type="range" id="dens" min="2" max="7" step="1"></label>
+    <span class="count" id="count"></span>
+  </div>
+  <div class="grid" id="grid"></div>
+</div></div>
+<script>
+const $=s=>document.querySelector(s);
+const esc=s=>(s==null?'':String(s)).replace(/&/g,'&amp;').replace(/</g,'&lt;');
+const money=n=>n==null||isNaN(+n)?null:'$'+Math.round(+n).toLocaleString();
+let DATA=[],q='',sortKey='date',sortDir=-1;
+let dens=(function(){const v=parseInt(localStorage.getItem('dfDens'),10);return(v>=2&&v<=7)?v:4;})();
+const F={src:new Set(),use:new Set(),year:new Set(),conf:new Set(),pMin:null,uMin:null};
+const tog=(s,v)=>{s.has(v)?s.delete(v):s.add(v);};
+
+function pass(r){
+  if(F.src.size&&!F.src.has(r.source_label))return false;
+  if(F.use.size&&!F.use.has(r.use||'—'))return false;
+  if(F.year.size&&!F.year.has(String(r.year||'—')))return false;
+  if(F.conf.size&&!F.conf.has(r.confidence||'—'))return false;
+  if(F.pMin!=null&&!(+r.price>=F.pMin))return false;
+  if(F.uMin!=null&&!(+r.units>=F.uMin))return false;
+  if(q){const t=q.toLowerCase();return [r.name,r.use,r.year,r.company,r.source_label].some(v=>v!=null&&String(v).toLowerCase().includes(t));}
+  return true;
+}
+function filtered(){
+  let rows=DATA.filter(pass);
+  rows=rows.slice().sort((a,b)=>{let x=a[sortKey],y=b[sortKey];const xm=(x==null||x===''),ym=(y==null||y==='');
+    if(xm&&ym)return 0;if(xm)return 1;if(ym)return -1;
+    if(!isNaN(+x)&&!isNaN(+y)){x=+x;y=+y;}else{x=String(x).toLowerCase();y=String(y).toLowerCase();}
+    return x<y?-1*sortDir:x>y?sortDir:0;});
+  return rows;
+}
+function chip(obj,attr,lim){return Object.entries(obj).sort((a,b)=>b[1]-a[1]).slice(0,lim||99).map(([k,c])=>`<span class="chip" data-${attr}="${esc(k)}">${esc(k)}<span class="ct">${c}</span></span>`).join('');}
+function buildRail(){
+  const sc={},uc={},yc={},cc={};
+  DATA.forEach(r=>{sc[r.source_label]=(sc[r.source_label]||0)+1; const u=r.use||'—';uc[u]=(uc[u]||0)+1; const y=String(r.year||'—');yc[y]=(yc[y]||0)+1; const cf=r.confidence||'—';cc[cf]=(cc[cf]||0)+1;});
+  $('#fSrc').innerHTML=chip(sc,'s');$('#fUse').innerHTML=chip(uc,'u',10);$('#fYear').innerHTML=chip(yc,'y',8);$('#fConf').innerHTML=chip(cc,'c');
+}
+function card(r){
+  const isE=r.kind==='reit-filing';
+  const srcBadge=isE?'<span class="src edgar">📄 SEC EDGAR</span>':'<span class="src county">🏛️ Assessor</span>';
+  const price=money(r.price);
+  const details=[];
+  if(price) details.push(`<span class="i">💰 <b>${price}</b>${isE?'':' <span class="mut">est</span>'}</span>`);
+  if(r.units) details.push(`<span class="i">🏢 <b>${r.units}</b> units</span>`);
+  if(r.use) details.push(`<span class="i">🏷️ ${esc(r.use)}</span>`);
+  if(r.year_built) details.push(`<span class="i">🏗️ built ${r.year_built}</span>`);
+  if(r.date) details.push(`<span class="i">📅 ${esc(r.date)}</span>`);
+  if(r.ticker) details.push(`<span class="i">📈 ${esc(r.ticker)}</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>`:'';
+  return `<div class="dcard">
+    <div class="top"><div class="name">${esc(r.name)}</div>${srcBadge}</div>
+    ${price&&isE?'':price?`<div class="price">${price}</div>`:''}
+    <div class="dl">${details.join('')}</div>
+    ${link}${conf?'<div>'+conf+'</div>':''}
+    <div class="provline">📋 ${esc(r.source)}</div>
+  </div>`;
+}
+function render(){
+  document.documentElement.style.setProperty('--cols',dens);
+  const rows=filtered();
+  const county=rows.filter(r=>r.kind!=='reit-filing');
+  const totalVal=county.reduce((s,r)=>s+(+r.price||0),0);
+  $('#stats').innerHTML=
+    `<div class="stat"><b>${rows.length.toLocaleString()}</b><span>deals shown</span></div>`+
+    `<div class="stat"><b style="color:var(--gold)">$${(totalVal/1e9).toFixed(2)}B</b><span>county deal value</span></div>`+
+    `<div class="stat"><b style="color:var(--violet)">${rows.filter(r=>r.kind==='reit-filing').length}</b><span>SEC REIT filings</span></div>`+
+    `<div class="stat"><b>${new Set(rows.map(r=>r.year)).size}</b><span>years</span></div>`;
+  $('#count').textContent=`${rows.length.toLocaleString()} of ${DATA.length.toLocaleString()}`;
+  $('#grid').innerHTML=rows.slice(0,600).map(card).join('')||'<div style="color:var(--mut);padding:40px">No deals match.</div>';
+}
+$('#fSrc').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.src,c.dataset.s);c.classList.toggle('active');render();});
+$('#fUse').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.use,c.dataset.u);c.classList.toggle('active');render();});
+$('#fYear').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.year,c.dataset.y);c.classList.toggle('active');render();});
+$('#fConf').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.conf,c.dataset.c);c.classList.toggle('active');render();});
+$('#pMin').addEventListener('input',()=>{F.pMin=$('#pMin').value?+$('#pMin').value:null;render();});
+$('#uMin').addEventListener('input',()=>{F.uMin=$('#uMin').value?+$('#uMin').value:null;render();});
+$('#q').addEventListener('input',()=>{q=$('#q').value.trim();render();});
+$('#sort').addEventListener('change',()=>{sortKey=$('#sort').value;sortDir=(sortKey==='name'||sortKey==='use')?1:-1;render();});
+$('#dens').addEventListener('input',()=>{dens=+$('#dens').value;localStorage.setItem('dfDens',dens);render();});
+$('#sort').innerHTML=[['date','Date'],['price','Price'],['units','Units'],['year','Year'],['name','Name']].map(([k,l])=>`<option value="${k}"${k==='date'?' selected':''}>Sort: ${l}</option>`).join('');
+$('#dens').value=dens;
+Promise.all([
+  fetch('/data/deals-flow.json').then(r=>r.json()).catch(()=>({deals:[]})),
+  fetch('/data/edgar-reit-deals.json').then(r=>r.json()).catch(()=>({deals:[]}))
+]).then(([county,edgar])=>{
+  const c=(county.deals||[]).map(d=>({...d,kind:'county-sale',name:d.address||d.ain,source_label:'LA County Assessor'}));
+  const e=(edgar.deals||[]).map(d=>({...d,kind:'reit-filing',name:d.company,use:'REIT acquisition',date:d.filed,url:d.filing_url,source_label:'SEC EDGAR'}));
+  DATA=c.concat(e);
+  buildRail();render();
+}).catch(()=>{$('#count').textContent='failed to load deal data';});
+</script></body></html>
diff --git a/scripts/export-deals-flow.py b/scripts/export-deals-flow.py
new file mode 100644
index 0000000..254ed60
--- /dev/null
+++ b/scripts/export-deals-flow.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+# export-deals-flow.py — curate the RESELLABLE deal-flow JSON for public/deals-flow.html from the
+# public-record derived_sale table (assessor Prop-13). Source-stamped per row (docs/SOURCING.md +
+# always-document-source). $0, local. -> data/deals-flow.json
+import sqlite3, json, os
+DB=os.path.join(os.path.dirname(__file__),'..','data','assessor.sqlite')
+OUT=os.path.join(os.path.dirname(__file__),'..','data','deals-flow.json')
+c=sqlite3.connect(DB)
+rows=c.execute("""
+  SELECT ain,address,use_desc,units,year_built,transfer_date,transfer_year,
+         assessed_value,est_sale_price,confidence
+    FROM derived_sale
+   WHERE units>=2 AND transfer_year>=2022 AND est_sale_price>=500000
+   ORDER BY transfer_date DESC, est_sale_price DESC
+   LIMIT 15000
+""").fetchall()
+deals=[{'ain':r[0],'address':r[1],'use':r[2],'units':r[3],'year_built':r[4],
+        'date':r[5],'year':r[6],'assessed':r[7],'price':r[8],'confidence':r[9],
+        'source':'LA County Assessor (Prop-13 reassessment)','source_type':'public-record','kind':'county-sale'} for r in rows]
+json.dump({'generated':'2026-07-31','source':'LA County Assessor roll (public, resellable)','count':len(deals),'deals':deals},open(OUT,'w'))
+print(f'deals-flow.json: {len(deals):,} curated county deals (multifamily 2022+, >=$500k)')
+c.close()

← fe58f9e EDGAR REIT deal-flow: fetch-edgar-reit-deals.js — institutio  ·  back to Commercialrealestate  ·  crcp: draft gated cleanup script — firm case-merge + null-fi c0a2efa →