[object Object]

← back to Re Flyer Aggregator

chore: lint + light refactor of news/property scripts (session close)

b12f6f0671a0decdfd5232450bc58dfec6f3300b · 2026-08-20 09:19:45 -0700 · Steve

- cre-news-monitor: named MS_PER_DAY const (was inline 864e5 twice), SEEN_CAP const
  (was magic 8000), POOL_CONC const + proper default param on pullPooled; split
  siteOf() and dateWindows() off their dense one-liners for readability
- build-property-index: split 5 consecutive size-coalesce assignments and city/state
  guards off a single line; no logic change
- ingest-rentv, serve-viewers: clean, no change needed

node --check + --dry run both green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files touched

Diff

commit b12f6f0671a0decdfd5232450bc58dfec6f3300b
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 20 09:19:45 2026 -0700

    chore: lint + light refactor of news/property scripts (session close)
    
    - cre-news-monitor: named MS_PER_DAY const (was inline 864e5 twice), SEEN_CAP const
      (was magic 8000), POOL_CONC const + proper default param on pullPooled; split
      siteOf() and dateWindows() off their dense one-liners for readability
    - build-property-index: split 5 consecutive size-coalesce assignments and city/state
      guards off a single line; no logic change
    - ingest-rentv, serve-viewers: clean, no change needed
    
    node --check + --dry run both green.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 scripts/build-property-index.mjs |  9 +++++++--
 scripts/cre-news-monitor.mjs     | 23 +++++++++++++++++------
 2 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/scripts/build-property-index.mjs b/scripts/build-property-index.mjs
index cb4c0de..7f22591 100644
--- a/scripts/build-property-index.mjs
+++ b/scripts/build-property-index.mjs
@@ -41,8 +41,13 @@ for (const r of [...store, ...arch]) {
   if (!props.has(key)) props.set(key, { address: addr.trim(), city, state, property_type: null, entries: [], sources: new Set() });
   const p = props.get(key);
   if (!p.property_type) p.property_type = r.property_type || (PTYPE.exec(r.title || '') || [])[1] || null;
-  if (city && !p.city) p.city = city; if (state && !p.state) p.state = state;
-  const sz = sizeOf(r); if (!p.units && sz.units) p.units = sz.units; if (!p.sqft && sz.sqft) p.sqft = sz.sqft; if (!p.size_label && sz.size_label) p.size_label = sz.size_label; if (!p.occupancy_pct && sz.occupancy_pct) p.occupancy_pct = sz.occupancy_pct;
+  if (city && !p.city) p.city = city;
+  if (state && !p.state) p.state = state;
+  const sz = sizeOf(r);
+  if (!p.units && sz.units) p.units = sz.units;
+  if (!p.sqft && sz.sqft) p.sqft = sz.sqft;
+  if (!p.size_label && sz.size_label) p.size_label = sz.size_label;
+  if (!p.occupancy_pct && sz.occupancy_pct) p.occupancy_pct = sz.occupancy_pct;
   p.sources.add(r.source);
   p.entries.push({ date: r.date, txn: r.type, price: num(r), price_label: r.price_label || r.label || (typeof r.price === 'string' ? r.price : null), source: r.source, link: r.link, title: r.title, buyer: r.buyer || null, seller: r.seller || null });
 }
diff --git a/scripts/cre-news-monitor.mjs b/scripts/cre-news-monitor.mjs
index 13c3e17..50588f3 100644
--- a/scripts/cre-news-monitor.mjs
+++ b/scripts/cre-news-monitor.mjs
@@ -30,7 +30,8 @@ const GNEWS_WHEN = process.env.GNEWS_WHEN || '90d';   // keep a rolling 90 days
 const gnews = site => `https://news.google.com/rss/search?q=${encodeURIComponent(`site:${site} ${GQ} when:${GNEWS_WHEN}`)}&hl=en-US&gl=US&ceid=US:en`;
 // client-side recency gate for a "be first" DAILY digest (env-overridable).
 const MAX_AGE_DAYS = parseInt(process.env.MAX_AGE_DAYS || '95', 10);   // rolling ~90-day window (Steve)
-const ageDays = s => { const t = Date.parse(s); return isNaN(t) ? null : (Date.now() - t) / 864e5; };
+const MS_PER_DAY = 864e5;
+const ageDays = s => { const t = Date.parse(s); return isNaN(t) ? null : (Date.now() - t) / MS_PER_DAY; };
 const FEEDS = [
   // The Real Deal — per-market direct feeds
   { name: 'TRD-National', region: 'National', mode: 'rss', url: 'https://therealdeal.com/national/feed/' },
@@ -140,8 +141,16 @@ const fetchXml = async (url, ua) => (await fetch(url, { headers: { 'User-Agent':
 const DEEP = args.includes('--deep');
 const nap = ms => new Promise(r => setTimeout(r, ms));
 const jitter = (a, b) => a + Math.random() * (b - a);
-const siteOf = feed => { if (feed.mode === 'gnews') { const m = decodeURIComponent(feed.url).match(/site:([^ )]+)/); return m ? m[1] : null; } try { return new URL(feed.url).hostname.replace(/^www\.|^news\./, ''); } catch { return null; } };
-function dateWindows(nDays = 90, win = 15) { const out = [], day = 864e5, now = Date.now(); for (let end = now; end > now - nDays * day; end -= win * day) out.push([new Date(end - win * day).toISOString().slice(0, 10), new Date(end).toISOString().slice(0, 10)]); return out; }
+function siteOf(feed) {
+  if (feed.mode === 'gnews') { const m = decodeURIComponent(feed.url).match(/site:([^ )]+)/); return m ? m[1] : null; }
+  try { return new URL(feed.url).hostname.replace(/^www\.|^news\./, ''); } catch { return null; }
+}
+function dateWindows(nDays = 90, win = 15) {
+  const out = [], now = Date.now();
+  for (let end = now; end > now - nDays * MS_PER_DAY; end -= win * MS_PER_DAY)
+    out.push([new Date(end - win * MS_PER_DAY).toISOString().slice(0, 10), new Date(end).toISOString().slice(0, 10)]);
+  return out;
+}
 const gnewsWin = (site, q, a, b) => `https://news.google.com/rss/search?q=${encodeURIComponent(`site:${site} ${q} after:${a} before:${b}`)}&hl=en-US&gl=US&ceid=US:en`;
 
 async function pull(feed) {
@@ -167,8 +176,9 @@ async function pull(feed) {
 }
 
 // bounded concurrency — low in DEEP mode (serial-ish) to avoid Google News 429s; normal 8 for the daily run.
-async function pullPooled(feeds, conc) {
-  const c = conc || (DEEP ? 3 : 8), out = []; let i = 0;
+const POOL_CONC = DEEP ? 3 : 8;
+async function pullPooled(feeds, conc = POOL_CONC) {
+  const c = conc, out = []; let i = 0;
   const worker = async () => { while (i < feeds.length) { const f = feeds[i++]; out.push(...await pull(f)); } };
   await Promise.all(Array.from({ length: Math.min(c, feeds.length) }, worker));
   return out;
@@ -219,6 +229,7 @@ let added = 0;
 for (const d of dealItems) { const s = storeSig(d); if (!have.has(s)) { have.add(s); store.push({ type: d.type, price: d.price, label: d.label, market: d.location || d.feed_region, title: d.title, source: d.feed, link: d.link, date: d.date, buyer: null, seller: null, added: new Date().toISOString() }); added++; } }
 store.sort((a, b) => (b.date || '').localeCompare(a.date || '') || (b.price || 0) - (a.price || 0));
 const STORE_CAP = parseInt(process.env.STORE_CAP || '12000', 10);   // aligned with the deeds ingest so a backfill doesn't truncate
+const SEEN_CAP  = parseInt(process.env.SEEN_CAP  || '8000',  10);   // rolling sig-set cap; env-overridable
 if (store.length > STORE_CAP) store = store.slice(0, STORE_CAP);
 if (!args.includes('--dry')) writeFileSync(STORE, JSON.stringify(store, null, 0));
 
@@ -249,7 +260,7 @@ for (const d of fresh.slice(0, 15)) console.log(`  ${(d.label||'—').padStart(7
 
 if (!args.includes('--dry')) {
   dealItems.forEach(x => seen.add(sigOf(x)));   // store DEAL sigs only (bounded, not every item)
-  writeFileSync(SEEN, JSON.stringify([...seen].slice(-8000), null, 0));   // cap so it never grows unbounded
+  writeFileSync(SEEN, JSON.stringify([...seen].slice(-SEEN_CAP), null, 0));   // cap so it never grows unbounded
   if (fresh.length) {
     const stamp = new Date().toISOString().slice(0, 10);
     writeFileSync(join(ROOT, 'out', `crenews-new-deals-${stamp}.json`), JSON.stringify(fresh, null, 2));

← 62868fe ingest-usre-parcels: deterministic byte-identical output (re  ·  back to Re Flyer Aggregator  ·  reflyers: auto-sync fresh viewer data to Kamatera prod after 3bbdac8 →