← back to Commercialrealestate
Cody-gate fixes: null build-after-transfer est prices; filter EDGAR earnings-releases (40->35); remove 'resellable' overclaim + add CA R&T 408.3(c) resale caveat; label EDGAR price-pending
342556fe0608020da6723e3c0ba39dc5b62306f4 · 2026-07-31 09:20:53 -0700 · steve
Files touched
M docs/DATA-LICENSING.mdM public/deals-flow.htmlM scripts/derive-sale-prices.pyM scripts/fetch-edgar-reit-deals.js
Diff
commit 342556fe0608020da6723e3c0ba39dc5b62306f4
Author: steve <steve@designerwallcoverings.com>
Date: Fri Jul 31 09:20:53 2026 -0700
Cody-gate fixes: null build-after-transfer est prices; filter EDGAR earnings-releases (40->35); remove 'resellable' overclaim + add CA R&T 408.3(c) resale caveat; label EDGAR price-pending
---
docs/DATA-LICENSING.md | 10 ++++++++++
public/deals-flow.html | 8 ++++----
scripts/derive-sale-prices.py | 9 ++++++++-
scripts/fetch-edgar-reit-deals.js | 3 +++
4 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/docs/DATA-LICENSING.md b/docs/DATA-LICENSING.md
index 8553a32..fa2c58c 100644
--- a/docs/DATA-LICENSING.md
+++ b/docs/DATA-LICENSING.md
@@ -17,6 +17,16 @@ Research: 3 parallel agents, free web search, $0. Numbers are current-ish (2025-
## Commercial real estate isn't even in the MLS
CRCP's core (multifamily / retail / office) is **not** in the residential MLS. Commercial listing data = **CoStar/LoopNet ($131–1,200/mo), CREXi, Reonomy ($500–5k/mo), Catylist/Moody's ($1k–10k/mo), AIR CRE**. Proprietary, expensive, restrictive; CoStar largely closed to new entrants. B2B reuse of these = custom paid license.
+## ⚠️ CA R&T §408.3(c) — assessor data resale restriction (Cody-gate, 2026-07-31)
+The LA County **Assessor bulk roll** carries a statutory use restriction: R&T §408.3(c) — *"No person
+shall use information obtained pursuant to this section for the purpose of commercial solicitation of
+owners of the real property."* A B2B deal-flow product sold to brokers/investors (to approach owners)
+may fall under this, and the restriction may run to downstream buyers. **So "resellable" is NOT
+automatic for assessor-derived data** — it needs an attorney opinion before we sell it (gated to Steve;
+memo in pending-approval). SEC EDGAR + HUD/FHA + Census are FEDERAL public data with no such
+restriction → resellable today. Assessor-derived sale prices: display/internal until §408.3(c) cleared.
+Also: the Prop-13 est-price nulls build-after-transfer parcels (current value ≠ sale price there).
+
## The doctrine-compliant takeaway
- A **consumer display site** (residential): ~$1–2k/yr, legit — but display-only, no B2B, and doesn't cover commercial.
- A **B2B listing-data service**: needs a commercial license ($5k–50k+/yr, gated) regardless of source — this is *why* scraping is tempting and *why* it's risky.
diff --git a/public/deals-flow.html b/public/deals-flow.html
index 3c4a824..17a7406 100644
--- a/public/deals-flow.html
+++ b/public/deals-flow.html
@@ -43,7 +43,7 @@ select{background:var(--card);border:1px solid var(--line);color:var(--ink);bord
</style></head><body>
<header>
<h1><b>Closed Deal Flow</b> · LA Commercial</h1>
- <span class="badge">100% public record · resellable</span>
+ <span class="badge">100% public record</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">
@@ -57,7 +57,7 @@ select{background:var(--card);border:1px solid var(--line);color:var(--ink);bord
</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="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 cards are REIT filings that mention an acquisition (price/property pending parse — not yet extracted). County "est" price is an ESTIMATE (assessed value ÷ 1.02^yrs), not a recorded sale price; parcels with a building built after the transfer are excluded. <b style="color:var(--gold)">Resale rights pending legal review</b> (CA R&T §408.3(c) restricts commercial-solicitation use of assessor bulk data) — do not resell until cleared.</div>
<div class="toolbar">
<input class="search" id="q" placeholder="search address, company, use, year…">
<select id="sort"></select>
@@ -127,7 +127,7 @@ function render(){
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(--gold)">$${(totalVal/1e9).toFixed(2)}B</b><span>est. value (assessed-derived)</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()}`;
@@ -149,7 +149,7 @@ Promise.all([
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'}));
+ const e=(edgar.deals||[]).map(d=>({...d,kind:'reit-filing',name:d.company,use:'REIT filing · price pending',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';});
diff --git a/scripts/derive-sale-prices.py b/scripts/derive-sale-prices.py
index f98001c..d2d2502 100644
--- a/scripts/derive-sale-prices.py
+++ b/scripts/derive-sale-prices.py
@@ -40,7 +40,14 @@ for ain, addr, ploc, use, units, yb, rdate, val in rows:
# confidence: fresher transfer + residential/multifamily = tighter est_sale ~= price;
# older or non-standard use = looser (new construction / partial transfers add noise).
conf = 'high' if years <= 2 else ('med' if years <= 5 else 'low')
- ins.append((ain, (addr or '').strip() or (ploc or '').strip(), None, use, units, yb, rdate, ty, val, est, 'assessor_prop13_reassessment', conf))
+ # Cody-gate fix (2026-07-31): if the building was built AFTER the transfer, the current roll value
+ # includes construction that didn't exist at sale -> de-escalation OVERSTATES the sale price. Can't
+ # estimate a sale price honestly here; null it + flag, so it never ships as a "sale".
+ if yb and ty and yb > ty:
+ est = None; conf = 'na'; psrc = 'assessor_value_only_post_transfer_build'
+ else:
+ psrc = 'assessor_prop13_reassessment'
+ ins.append((ain, (addr or '').strip() or (ploc or '').strip(), None, use, units, yb, rdate, ty, val, est, psrc, conf))
n += 1
if len(ins) >= 5000:
c.executemany('INSERT INTO derived_sale VALUES (?,?,?,?,?,?,?,?,?,?,?,?)', ins); ins = []
diff --git a/scripts/fetch-edgar-reit-deals.js b/scripts/fetch-edgar-reit-deals.js
index ee8b459..4a3ac9f 100644
--- a/scripts/fetch-edgar-reit-deals.js
+++ b/scripts/fetch-edgar-reit-deals.js
@@ -81,6 +81,9 @@ async function acquisitionFilings(startdt, enddt) {
if (seen.has(h.id)) continue; seen.add(h.id);
if (!reits.has(h.cik)) continue;
const acc = h.id.split(':')[0].replace(/-/g, ''); const file = h.id.split(':')[1] || '';
+ // Cody-gate fix (2026-07-31): FTS matches earnings releases/supplements that merely MENTION a past
+ // acquisition. Those aren't acquisition filings — drop them so the feed isn't padded with them.
+ if (/earning|supplement|quarterly|[-_]er[-_.]|q[1-4]x?20\d\d|x?er\d|bodrep/i.test(file)) continue;
deals.push({
company: (h.display.split(' (')[0] || h.display).trim(), cik: h.cik, ticker: (h.display.match(/\(([A-Z.\-]{1,6})\)/) || [])[1] || null,
form: h.form, filed: h.date,
← c0a2efa crcp: draft gated cleanup script — firm case-merge + null-fi
·
back to Commercialrealestate
·
auto-save: 2026-07-31T09:26:15 (6 files) — data/brokers-snap d6175f2 →