← 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
M package-lock.jsonM package.jsonM public/global-search.jsM scripts/enrich-condo-brokers.jsM scripts/serve.js
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 →