[object Object]

← back to Commercialrealestate

chore: lint (global-search clear-on-error), refactor (SEARCH_LIMIT, BB_RATE_PER_MIN consts), v0.17.0 (session close)

d0c5f86752edd1df0fd8ea8034134ed02e3aef73 · 2026-08-03 12:56:49 -0700 · Steve

Files touched

Diff

commit d0c5f86752edd1df0fd8ea8034134ed02e3aef73
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 3 12:56:49 2026 -0700

    chore: lint (global-search clear-on-error), refactor (SEARCH_LIMIT, BB_RATE_PER_MIN consts), v0.17.0 (session close)
---
 package-lock.json               |  4 ++--
 package.json                    |  2 +-
 public/global-search.js         |  2 +-
 scripts/enrich-condo-brokers.js | 13 +++++++------
 scripts/serve.js                |  3 ++-
 5 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index ddae611..c3aac17 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "commercialrealestate",
-  "version": "0.16.0",
+  "version": "0.17.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "commercialrealestate",
-      "version": "0.16.0",
+      "version": "0.17.0",
       "dependencies": {
         "better-sqlite3": "^12.11.1",
         "express": "^4.22.2",
diff --git a/package.json b/package.json
index 88e8d02..1a9a5c3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "commercialrealestate",
-  "version": "0.16.0",
+  "version": "0.17.0",
   "private": true,
   "description": "LA County CRE investment explorer — multi-firm sourced, Census + assessor enriched, Qwen-analyzed",
   "scripts": {
diff --git a/public/global-search.js b/public/global-search.js
index d1f854c..f2f816d 100644
--- a/public/global-search.js
+++ b/public/global-search.js
@@ -52,7 +52,7 @@
     var q = input.value.trim(); if (q === lastQ) return; lastQ = q;
     if (q.length < 2) { box.classList.remove('on'); return; }
     fetch('/api/search?q=' + encodeURIComponent(q), { headers: { 'Accept': 'application/json' } })
-      .then(function (r) { return r.json(); }).then(render).catch(function () { });
+      .then(function (r) { return r.json(); }).then(render).catch(function () { box.classList.remove('on'); });
   }
   input.addEventListener('input', function () { clearTimeout(timer); timer = setTimeout(run, 160); });
   input.addEventListener('focus', function () { if (box.innerHTML && input.value.trim().length >= 2) box.classList.add('on'); });
diff --git a/scripts/enrich-condo-brokers.js b/scripts/enrich-condo-brokers.js
index 0dc76bd..c559efe 100644
--- a/scripts/enrich-condo-brokers.js
+++ b/scripts/enrich-condo-brokers.js
@@ -33,6 +33,7 @@ const BROKERS = path.join(ROOT, 'data', 'condo-brokers.json');
 const DRECACHE = path.join(ROOT, 'data', 'dre-bylicense-cache.json');
 const LICENSED = path.join(ROOT, 'data', 'licensed-agents.json');
 const DRE_BASE = 'https://www2.dre.ca.gov/PublicASP/pplinfo.asp';
+const BB_RATE_PER_MIN = 0.15;   // Browserbase billing rate ($/session-minute)
 const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15';
 
 const arg = (k, d) => { const i = process.argv.indexOf(k); return i > -1 ? (process.argv[i + 1] ?? true) : d; };
@@ -194,7 +195,7 @@ async function main() {
     const KEY = (env.match(/BROWSERBASE_API_KEY=(.*)/) || [])[1].trim();
     const bb = new Browserbase({ apiKey: KEY });
     bbSession = await bb.sessions.create({ projectId: PROJECT, proxies: true, browserSettings: { solveCaptchas: true } });
-    console.log(`  mode: Browserbase (metered ~$0.15/min) · session ${bbSession.id}`);
+    console.log(`  mode: Browserbase (metered ~$${BB_RATE_PER_MIN}/min) · session ${bbSession.id}`);
     browser = await chromium.connectOverCDP(bbSession.connectUrl);
     ctx = browser.contexts()[0]; page = ctx.pages()[0] || await ctx.newPage();
     page.setDefaultTimeout(60000);
@@ -212,12 +213,12 @@ async function main() {
   // Cost/abort guardrails (Codex dissent): a hot Redfin WAF polls ~25s per blocked listing, so an
   // unbounded run can blow past its cost estimate. Bail early when the WAF is clearly hot.
   const MAX_CONSEC_BLOCKS = +process.env.MAX_CONSEC_BLOCKS || 8;
-  const MAX_SESSION_MIN = +process.env.MAX_SESSION_MIN || 12;   // ~$1.80 hard ceiling at $0.15/min
+  const MAX_SESSION_MIN = +process.env.MAX_SESSION_MIN || 12;   // ~$1.80 hard ceiling at BB_RATE_PER_MIN/min
   let consecBlocks = 0, aborted = null;
   for (const c of targets) {
     if (done >= LIMIT) break;
     const elapsedMin = (Date.now() - startedAt) / 60000;
-    if (elapsedMin > MAX_SESSION_MIN) { aborted = `session cap ${MAX_SESSION_MIN}min (~$${(MAX_SESSION_MIN * 0.15).toFixed(2)})`; break; }
+    if (elapsedMin > MAX_SESSION_MIN) { aborted = `session cap ${MAX_SESSION_MIN}min (~$${(MAX_SESSION_MIN * BB_RATE_PER_MIN).toFixed(2)})`; break; }
     if (consecBlocks >= MAX_CONSEC_BLOCKS) { aborted = `${consecBlocks} consecutive WAF blocks — WAF is hot, stopping to save spend`; break; }
     done++;
     const { status, html } = await fetchRedfin(c.source, page);
@@ -264,13 +265,13 @@ async function main() {
   if (browser) await browser.close();
   if (bbSession) {
     const mins = (Date.now() - startedAt) / 60000;
-    const cost = +(mins * 0.15).toFixed(2);
-    console.log(`  Browserbase session ~${mins.toFixed(1)} min → est $${cost.toFixed(2)} (@ $0.15/session-min, + proxy bandwidth)`);
+    const cost = +(mins * BB_RATE_PER_MIN).toFixed(2);
+    console.log(`  Browserbase session ~${mins.toFixed(1)} min → est $${cost.toFixed(2)} (@ $${BB_RATE_PER_MIN}/session-min, + proxy bandwidth)`);
     // Log to the shared cost-ledger so AbramsEgo (:9773) tracks this spend against its budget.
     try {
       const row = { ts: new Date().toISOString(), api: 'browserbase', vendor: 'Browserbase',
         app: 'commercialrealestate/enrich-condo-brokers', note: `condo broker Redfin scrape (${gotBroker} listings, ${mins.toFixed(1)} session-min)`,
-        units: [{ qty: +mins.toFixed(2), unit: 'session_min', rate: 0.15, subtotal: cost }], cost_usd: cost };
+        units: [{ qty: +mins.toFixed(2), unit: 'session_min', rate: BB_RATE_PER_MIN, subtotal: cost }], cost_usd: cost };
       fs.appendFileSync(path.join(process.env.HOME, '.claude', 'cost-ledger.jsonl'), JSON.stringify(row) + '\n');
       console.log('  ✔ logged to cost-ledger (AbramsEgo budget tracking)');
     } catch (e) { console.log('  (cost-ledger log skipped:', e.message, ')'); }
diff --git a/scripts/serve.js b/scripts/serve.js
index face287..65c3319 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -372,6 +372,7 @@ function snapFirmDetail(res, name) {
 //    address / name / firm / license / DRE#. Backs the top-nav global search on every page.
 //    DB-first; on DB-less prod (crcp.agentabrams.com has no cre DB) it falls back per-group to the
 //    shipped JSON snapshots, so search works identically live. ──
+const SEARCH_LIMIT = 8;   // max results per group in /api/search
 const _snapSearchCache = {};
 function _snapArr(file) {
   const p = path.join(ROOT, 'data', file);
@@ -398,7 +399,7 @@ function snapSearch(q, N) {
 app.get('/api/search', async (req, res) => {
   const q = String(req.query.q || '').trim();
   if (q.length < 2) return res.json({ q, brokers: [], condos: [], sfrs: [] });
-  const N = 8, j = s => encodeURIComponent(s || '');
+  const N = SEARCH_LIMIT, j = s => encodeURIComponent(s || '');
   let dbB = null, dbC = null, dbS = null;
   if (brokerdb) {
     const like = '%' + q.replace(/[%_\\]/g, '\\$&') + '%';

← fc0a362 auto-save: 2026-08-03T12:54:16 (2 files) — data/residential-  ·  back to Commercialrealestate  ·  condos: sort control is now a visible BAR of all 8 sort-opti 044dfa4 →