← back to Commercialrealestate
enrich-property: fix Cody-verified field bugs + honest labeling (Cycle 1 revise)
d30373f7aa5514e66c2f042d864b825e93b00f98 · 2026-08-06 16:30:53 -0700 · Steve Abrams
- zip: read ALL-CAPS SitusZIP5 (was camelCase SitusZip5 -> always null)
- assessed_total: read Roll_TotalValue (2025 roll column) -> was null
- add assessed_as_of (roll year, data-age transparency)
- rename last_recording_date -> last_transfer_recorded + note it's the lagged
reassessment-triggering transfer, not necessarily the latest sale
- ULA match relabeled ula_zip_candidates (COARSE, ZIP-level, not parcel-confirmed);
surfaces each instrument# so a user can pull the deed to confirm. Precise
instrument->AIN match stays in the gated deed layer (assessor has no instr field).
Dropped phantom: endpoint returns 2025 roll (verified), not stale 2021.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/enrich-property.js
Diff
commit d30373f7aa5514e66c2f042d864b825e93b00f98
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 6 16:30:53 2026 -0700
enrich-property: fix Cody-verified field bugs + honest labeling (Cycle 1 revise)
- zip: read ALL-CAPS SitusZIP5 (was camelCase SitusZip5 -> always null)
- assessed_total: read Roll_TotalValue (2025 roll column) -> was null
- add assessed_as_of (roll year, data-age transparency)
- rename last_recording_date -> last_transfer_recorded + note it's the lagged
reassessment-triggering transfer, not necessarily the latest sale
- ULA match relabeled ula_zip_candidates (COARSE, ZIP-level, not parcel-confirmed);
surfaces each instrument# so a user can pull the deed to confirm. Precise
instrument->AIN match stays in the gated deed layer (assessor has no instr field).
Dropped phantom: endpoint returns 2025 roll (verified), not stale 2021.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/enrich-property.js | 32 +++++++++++++++++++++++---------
1 file changed, 23 insertions(+), 9 deletions(-)
diff --git a/scripts/enrich-property.js b/scripts/enrich-property.js
index 8f9725d..13888b8 100644
--- a/scripts/enrich-property.js
+++ b/scripts/enrich-property.js
@@ -44,7 +44,7 @@ function normalize(a) {
address: clean(a.PropertyLocation),
street: [clean(a.SitusHouseNo), clean(a.SitusStreet), clean(a.SitusDirection)].filter(Boolean).join(' ') || null,
city: clean(a.SitusCity),
- zip: clean(a.SitusZip5) || clean(a.SitusZipCode),
+ zip: clean(a.SitusZIP5) || clean(a.SitusZIP), // field is ALL-CAPS SitusZIP5 (Cody-verified fix)
use_type: clean(a.UseType),
use_desc: clean(a.UseDescription) || clean(a.SpecificUseType) || clean(a.GeneralUseType),
year_built: a.YearBuilt || null,
@@ -53,10 +53,13 @@ function normalize(a) {
units: a.Units || null,
bedrooms: a.Bedrooms || null,
bathrooms: a.Bathrooms || null,
- assessed_total: a.TotalValue ?? a.Roll_totalValue ?? null,
+ assessed_total: a.Roll_TotalValue ?? a.TotalValue ?? a.Roll_totLandImp ?? null, // 2025 roll uses Roll_TotalValue
assessed_land: a.LandValue ?? a.Roll_LandValue ?? null,
assessed_improvement: a.ImpValue ?? a.Roll_ImpValue ?? null,
- last_recording_date: epochToDate(a.RecordingDate), // last transfer the assessor processed (lagged)
+ assessed_as_of: clean(a.RollYear), // which assessment roll these figures come from (data age)
+ // NOTE: this is the last transfer the ASSESSOR processed that triggered a Prop-13 reassessment —
+ // lagged, and NOT necessarily the most recent sale (exclusion-eligible transfers may not appear).
+ last_transfer_recorded: epochToDate(a.RecordingDate),
tax_rate_area: clean(a.TaxRateArea),
};
}
@@ -90,12 +93,16 @@ async function assessorByAddress(addr, limit = 8) {
return [...byAin.values()].slice(0, limit).map(normalize);
}
-// Best-effort ULA price match (LA City $5.3M+): by ZIP. Exact instrument→AIN match needs the deed.
-function ulaMatches(zip) {
+// ULA sales in the same ZIP — a COARSE, ZIP-level candidate list, NOT a confirmed match for this
+// parcel. A precise instrument→AIN match needs the gated RR/CC deed lookup (the assessor layer has
+// no instrument field to join on — Cody-verified). Each candidate carries its doc_number so a user
+// can pull the actual recorded deed to confirm whether it's THIS property.
+function ulaZipCandidates(zip) {
+ if (!zip) return [];
try {
const u = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'data', 'ula-la-deals.json'), 'utf8'));
- return (u.deals || []).filter(d => d.zip && zip && String(d.zip) === String(zip))
- .slice(0, 10).map(d => ({ date: d.date, price: d.price, use: d.use, doc_number: d.doc_number, basis: d.price_basis }));
+ return (u.deals || []).filter(d => d.zip && String(d.zip) === String(zip))
+ .slice(0, 12).map(d => ({ date: d.date, price: d.price, use: d.use, instrument: d.doc_number, basis: d.price_basis }));
} catch (_) { return []; }
}
@@ -109,14 +116,21 @@ function deedStub(dossier) {
{ name: 'NETR per-record', cost: '~$3.50–8.00/property (metered, gated)', gives: 'transfer detail + document image' },
{ name: 'Assessor data-sales change-of-ownership file', cost: '~$500–5,000 bulk (gated)', gives: 'price + parties + AIN, all-county' },
],
- bridge_hint: dossier && dossier.last_recording_date ? `assessor shows last transfer recorded ${dossier.last_recording_date}` : null,
+ bridge_hint: dossier && dossier.last_transfer_recorded ? `assessor shows last reassessment-triggering transfer recorded ${dossier.last_transfer_recorded} (lagged; may not be the latest sale)` : null,
};
}
async function enrichByAin(ain) {
const parcel = await assessorByAin(ain);
if (!parcel) return { ain, found: false };
- return { ain, found: true, parcel, ula_sales: ulaMatches(parcel.zip), deed: deedStub(parcel), sources: ['LA County Assessor (public)', 'LA City ULA (public)'] };
+ const cands = ulaZipCandidates(parcel.zip);
+ return {
+ ain, found: true, parcel,
+ ula_zip_candidates: cands, // COARSE ZIP-level candidate sales — confirm via each instrument's deed
+ ula_note: cands.length ? `${cands.length} ULA $5.3M+ sale(s) recorded in ZIP ${parcel.zip} — candidates, not parcel-confirmed (precise match needs the gated deed lookup)` : null,
+ deed: deedStub(parcel),
+ sources: ['LA County Assessor parcel roll (public)', 'LA City Measure ULA (public)'],
+ };
}
async function enrichByAddress(addr) {
← e515bc5 CRCP: surface listing.created_at as '🕓 captured <date+time>
·
back to Commercialrealestate
·
CRCP: snapLeads() fallback for /api/leads/top — homepage bro fe2521c →