[object Object]

← 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

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 →