← back to Commercialrealestate
SFV pool: mls.html ?shortlist=sfv-pool deep-link (lands on main CRCP with items checked off) + repoint email/page links + keep-building-images loop
5f4acf6e49cfcf87605a9f75b31d4b3c21d9653a · 2026-08-26 09:09:27 -0700 · Steve Abrams
Files touched
M public/mls.htmlM public/sfv-pool.htmlA scripts/keep-building-images.shM scripts/sfv-pool-report.js
Diff
commit 5f4acf6e49cfcf87605a9f75b31d4b3c21d9653a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 26 09:09:27 2026 -0700
SFV pool: mls.html ?shortlist=sfv-pool deep-link (lands on main CRCP with items checked off) + repoint email/page links + keep-building-images loop
---
public/mls.html | 14 ++++++++++++++
public/sfv-pool.html | 1 +
scripts/keep-building-images.sh | 28 ++++++++++++++++++++++++++++
scripts/sfv-pool-report.js | 8 ++++++--
4 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/public/mls.html b/public/mls.html
index 20a0141..d9aefb3 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -706,6 +706,20 @@ document.addEventListener('click',e=>{ const cp=e.target.closest('[data-copy]');
let _qT; $('#q').addEventListener('input',()=>{ clearTimeout(_qT); _qT=setTimeout(()=>{ q=$('#q').value.trim(); render(); },140); });
// deep-link ?q= (from the global nav search) — pre-fill + filter on load
(function(){ const _q=new URLSearchParams(location.search).get('q'); if(_q){ const b=$('#q'); if(b){ b.value=_q; q=_q.trim(); } } })();
+// deep-link ?shortlist=sfv-pool — land here with the SFV $700k-$1.2M set already CHECKED OFF (starred) +
+// the Shortlist filter on, so the email / pool-page "open on CRCP" link shows exactly those listings here.
+// Named preset (not ~950 ids in the URL): fetch the current id set from /api/sfv-pool and star them.
+(async function(){
+ const preset=new URLSearchParams(location.search).get('shortlist'); if(preset!=='sfv-pool') return;
+ try{
+ const j=await (await fetch('/api/sfv-pool')).json();
+ const ids=[...(j.withPool||[]),...(j.withoutPool||[]),...(j.unknown||[])].map(r=>r.id);
+ ids.forEach(id=>SHORT.add(id)); saveShort();
+ F.shortlist=true; if(typeof updateShortBtn==='function') updateShortBtn();
+ const sc=$('#f_shortlist'); if(sc){ sc.checked=true; } // reflect the filter checkbox if present
+ if(typeof render==='function') render();
+ }catch(e){ console.warn('sfv-pool preset failed', e); }
+})();
function exportCSV(){ const rows=filtered(); const cols=visCols(); const cv=(r,c)=>{let v=cval(r,c); v=(v==null?'':String(v)); return '"'+v.replace(/"/g,'""')+'"';};
const csv=cols.map(c=>'"'+c.l+'"').join(',')+'\n'+rows.map(r=>cols.map(c=>cv(r,c)).join(',')).join('\n');
const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'})); a.download='cre-mls-'+rows.length+'.csv'; a.click(); URL.revokeObjectURL(a.href); }
diff --git a/public/sfv-pool.html b/public/sfv-pool.html
index 445be6a..baabd0d 100644
--- a/public/sfv-pool.html
+++ b/public/sfv-pool.html
@@ -74,6 +74,7 @@
<div class="wrap">
<h1>🏊 San Fernando Valley Homes · $700k – $1.2M</h1>
<div class="sub">Active single-family listings, split by swimming pool ·
+ <a href="/mls.html?shortlist=sfv-pool">open these on CRCP (checked off) →</a> ·
<a href="/mls.html">all LA listings →</a> · <span id="src"></span></div>
<div class="stats" id="stats"></div>
diff --git a/scripts/keep-building-images.sh b/scripts/keep-building-images.sh
new file mode 100755
index 0000000..090d19e
--- /dev/null
+++ b/scripts/keep-building-images.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# keep-building-images.sh — repeatedly run the (resumable) SFV pool/photo enrichment until image + pool
+# coverage plateaus. ONE Chrome session at a time (concurrent sessions from one IP just compound Redfin's
+# rate-block). Each pass re-fetches only rows still missing a photo (photo_url IS NULL), so coverage only
+# grows; it also refreshes data/sfr-pools.json each pass so the live page/prod snapshot keep filling in.
+set -uo pipefail
+cd "$HOME/Projects/commercialrealestate" || exit 1
+export NODE_PATH="$HOME/.claude/skills/browserbase/node_modules"
+LOG=tmp/keep-images.log
+mkdir -p tmp
+count_photos(){ psql -h /tmp -d cre -tA -c "SELECT count(*) FILTER (WHERE photo_url IS NOT NULL) FROM sfr_pool p JOIN sfr s ON s.id=p.id WHERE s.status='active' AND s.price::int BETWEEN 700000 AND 1200000 AND s.city IN ('Encino','Tarzana','Woodland Hills','Sherman Oaks','Van Nuys','North Hollywood','Studio City','Reseda','Northridge','Granada Hills','Canoga Park','Winnetka','West Hills','Chatsworth','Panorama City','Sun Valley','Valley Village','Valley Glen','North Hills','Porter Ranch','Arleta','Pacoima','Sylmar','Mission Hills','Lake Balboa','Toluca Lake','Sunland','Tujunga','Shadow Hills');" 2>/dev/null | tr -d ' '; }
+
+echo "===== [$(date '+%F %T')] keep-building-images START =====" >> "$LOG"
+stale=0
+for round in $(seq 1 12); do
+ before=$(count_photos)
+ echo "[$(date '+%T')] round $round: $before photos before" >> "$LOG"
+ CAP=1500 node scripts/enrich-sfr-pools.js >> "$LOG" 2>&1
+ after=$(count_photos)
+ gained=$(( ${after:-0} - ${before:-0} ))
+ echo "[$(date '+%T')] round $round: $after photos after (+$gained)" >> "$LOG"
+ # push the refreshed snapshot to prod so the live page keeps filling in
+ rsync -z data/sfr-pools.json root@45.61.58.125:/root/public-projects/commercialrealestate/data/sfr-pools.json >> "$LOG" 2>&1 || true
+ if [ "${gained:-0}" -lt 5 ]; then stale=$((stale+1)); else stale=0; fi
+ if [ "$stale" -ge 2 ]; then echo "[$(date '+%T')] plateaued ($after photos) — stopping" >> "$LOG"; break; fi
+ sleep 120 # cool-down between passes to ease Redfin's rate limit
+done
+echo "===== [$(date '+%F %T')] keep-building-images DONE — $(count_photos) photos =====" >> "$LOG"
diff --git a/scripts/sfv-pool-report.js b/scripts/sfv-pool-report.js
index d93c653..b688ade 100644
--- a/scripts/sfv-pool-report.js
+++ b/scripts/sfv-pool-report.js
@@ -13,6 +13,9 @@ const SFV_CITIES = ['Encino','Tarzana','Woodland Hills','Sherman Oaks','Van Nuys
'Pacoima','Sylmar','Mission Hills','Lake Balboa','Toluca Lake','Sunland','Tujunga','Shadow Hills'];
const PMIN = 700000, PMAX = 1200000;
const DETAILS_URL = process.env.SFV_POOL_URL || 'https://crcp.agentabrams.com/sfv-pool.html';
+// primary "open on CRCP" link: lands on the MAIN CRCP listings page with exactly these SFV $700k–$1.2M
+// items CHECKED OFF (starred) + the Shortlist filter on (mls.html ?shortlist=sfv-pool deep-link).
+const CRCP_URL = process.env.SFV_CRCP_URL || 'https://crcp.agentabrams.com/mls.html?shortlist=sfv-pool';
const TOP = +(process.env.TOP || 30); // rows per section in the email
const esc = s => String(s ?? '').replace(/[&<>"]/g, c => ({ '&':'&','<':'<','>':'>','"':'"' }[c]));
const money = n => '$' + Number(n).toLocaleString();
@@ -57,7 +60,7 @@ function table(rows, kind) {
<div style="max-width:820px;margin:0 auto">
<h1 style="font-size:20px;margin:0 0 3px">🏊 SFV Homes $700k–$1.2M <span style="color:#3fb950">· ${D}</span></h1>
<div style="color:#8b949e;font-size:12px;margin-bottom:16px">San Fernando Valley · active single-family · split by swimming pool ·
- <a href="${DETAILS_URL}" style="color:#58a6ff">see all + filter →</a></div>
+ <a href="${CRCP_URL}" style="color:#58a6ff">open these on CRCP (checked off) →</a></div>
<table style="width:100%;border-spacing:8px;margin:-8px 0 18px"><tr>
${stat(rows.length, 'Total in band', '#e6edf3')}
${stat(withPool.length, '🏊 With Pool', '#3fb950')}
@@ -65,7 +68,8 @@ function table(rows, kind) {
${stat(pending.length, 'Pool Pending', '#8b949e')}
</tr></table>
<div style="text-align:center;margin:0 0 22px">
- <a href="${DETAILS_URL}" style="display:inline-block;background:#238636;color:#fff;text-decoration:none;font-weight:600;font-size:14px;padding:11px 22px;border-radius:8px">See every listing + pool / city / price filters →</a>
+ <a href="${CRCP_URL}" style="display:inline-block;background:#238636;color:#fff;text-decoration:none;font-weight:600;font-size:14px;padding:11px 22px;border-radius:8px">Open these ${rows.length} on CRCP — checked off →</a>
+ <div style="margin-top:8px"><a href="${DETAILS_URL}" style="color:#58a6ff;font-size:12px">or the photo view with pool / city filters →</a></div>
</div>
<h3 style="font-size:13px;text-transform:uppercase;color:#8b949e;margin:18px 0 6px">🏊 With Pool <span style="color:#3fb950">(${withPool.length})</span> · top ${Math.min(TOP, withPool.length)} by price</h3>
${table(withPool, 'pool')}
← 5ed3148 auto-data-snapshot: 2026-08-26T09:01:00 (1 data files) — dat
·
back to Commercialrealestate
·
Apply Nav Agent to CRCP (9 pages incl. sfv-pool): field togg 84c2a7c →