← back to Commercialrealestate
add 1pm afternoon CRE report: new-listings diff + red-hot deals, launchd 13:00 PT
99d728abfb525b50281bf21183ac5d9f8b2716c5 · 2026-06-26 12:51:36 -0700 · Steve
Files touched
A data/afternoon-report.htmlA deploy/com.steve.cre-afternoon-report.plistA scripts/afternoon-report.jsA scripts/run-afternoon-report.sh
Diff
commit 99d728abfb525b50281bf21183ac5d9f8b2716c5
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 12:51:36 2026 -0700
add 1pm afternoon CRE report: new-listings diff + red-hot deals, launchd 13:00 PT
---
data/afternoon-report.html | 44 ++++++++++
deploy/com.steve.cre-afternoon-report.plist | 26 ++++++
scripts/afternoon-report.js | 126 ++++++++++++++++++++++++++++
scripts/run-afternoon-report.sh | 34 ++++++++
4 files changed, 230 insertions(+)
diff --git a/data/afternoon-report.html b/data/afternoon-report.html
new file mode 100644
index 0000000..97a58be
--- /dev/null
+++ b/data/afternoon-report.html
@@ -0,0 +1,44 @@
+<div style="font-family:-apple-system,Segoe UI,Arial,sans-serif;max-width:780px">
+<h2 style="margin:0 0 2px">SFV CRE — 1pm Update: New Listings & Red-Hot Deals</h2>
+<div style="color:#666;font-size:13px">2026-06-26 · San Fernando Valley, CA · $400,000 down (25% / 6.75% / 30yr) · 85 listings tracked</div>
+<p style="font-size:13px;color:#444;margin:12px 0">Afternoon re-scrape of Crexi vs this morning. Cap rates are broker/MLS-stated — verify NOI in DD.
+Most SFV multifamily runs negative/breakeven leverage at today's rates; positive-leverage (green CoC) or seller-financed deals are the standouts.</p>
+
+<h3 style="margin:16px 0 4px;font-size:15px">🆕 Additional listings since this morning (3)</h3>
+<div style="border-top:1px solid #e2e2e2;padding:10px 0">
+ <div style="font-size:14px"><span style="background:#0a7d2c;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;margin-right:6px;vertical-align:middle">NEW</span><b><a href="https://www.crexi.com/properties/2553265/california-9350-tampa">9350 Tampa</a>, Northridge</b> <span style="color:#888;font-weight:400">score 54</span></div>
+ <div style="color:#666;font-size:12px">Multifamily · 1 unit · $1,590,000 · cap 9.24% (broker cap)</div>
+ <div style="font-size:12px;color:#333;margin:3px 0">Cash to close <b>$437,250</b> (OVER budget) · CoC <span style="color:#178a3a">12.37%</span> · DSCR 1.58 · est NOI $146,916</div>
+ <div style="font-size:13px;color:#222;margin:4px 0">This multifamily property in Northridge offers an attractive cap rate of 9.24%, suggesting strong potential for cash flow and appreciation in a growing market. However, the required down payment of $437,250 exceeds the investor's $400,000 budget, necessitating careful evaluation of financing options or potential compromises in the deal structure.</div>
+ <div style="font-size:12px;color:#a15c00;margin:4px 0"><b>Risks:</b> Insufficient down payment to meet computed requirements without stretching financing terms; Unverified rent control status may impact long-term rental income and tenant stability; Broker-reported cap rate is unverified, requiring due diligence on financials and rent roll</div>
+ <div style="font-size:12px;color:#555">Auto-added by weekly Crexi refresh 2026-06-23. Broker cap 9.24% (unverified). Verify status, rent roll, and financials before acting.</div>
+ </div>
+<div style="border-top:1px solid #e2e2e2;padding:10px 0">
+ <div style="font-size:14px"><span style="background:#0a7d2c;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;margin-right:6px;vertical-align:middle">NEW</span><b><a href="https://www.crexi.com/properties/2414348/california-16122-san-fernando-mission-boulevard">16122 San Fernando Mission Boulevard</a>, Granada Hills</b> <span style="color:#888;font-weight:400">score 53</span></div>
+ <div style="color:#666;font-size:12px">Multifamily · 3 units · built 1954 · $1,298,000 · cap 6.66% (broker cap)</div>
+ <div style="font-size:12px;color:#333;margin:3px 0">Cash to close <b>$356,950</b> (in budget) · CoC <span style="color:#178a3a">2.99%</span> · DSCR 1.14 · est NOI $86,447</div>
+ <div style="font-size:13px;color:#222;margin:4px 0">This triplex in Granada Hills offers a value-add opportunity with a strong cap rate and potential for increased rental income through re-leasing the vacant JADU and ADU units. The property is well-positioned for stabilization and appreciation in a growing market.</div>
+ <div style="font-size:12px;color:#a15c00;margin:4px 0"><b>Risks:</b> Occupancy and lease-up risk for the JADU and ADU units, which are currently unoccupied and may take time to re-lease.; Uncertainty around in-place income, as the reported NOI is based on seller-provided data and not verified.; Light expense assumptions may not reflect long-term stabilized costs, potentially impacting cash flow.</div>
+ <div style="font-size:12px;color:#555">DD-CHECKED (True North Realty / MLS SR26083260): renovated front duplex + new-2024 detached ADU. Seller NOI $86,496 -> ~6.66% cap, but that is PRO-FORMA: only the main unit ($3,390/mo) is currently rented; the JADU ($1,990) and ADU ($3,100) are 'previously rented' = lease-up/occupancy risk. Expenses light (~$15.3k: ins $3k + tax $7.5k, RUBS) with NO vacancy/management/reserves -> real stabilized NOI lower. Was in escrow at $1.30M (fell out). NOT marked verified: income is seller/prior, not confirmed in-place. Positive leverage IF re-leased.</div>
+ </div>
+<div style="border-top:1px solid #e2e2e2;padding:10px 0">
+ <div style="font-size:14px"><span style="background:#0a7d2c;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;margin-right:6px;vertical-align:middle">NEW</span><b><a href="https://www.compass.com/homedetails/7304-Canby-Ave-Reseda-CA-91335/1IO7AS_pid/">7302-7304 Canby Ave</a>, Reseda</b> <span style="color:#888;font-weight:400">score 52</span></div>
+ <div style="color:#666;font-size:12px">Mixed-use · 5 units · built 1926 · $1,250,000 · cap 5.96% (✓ verified in-place)</div>
+ <div style="font-size:12px;color:#333;margin:3px 0">Cash to close <b>$343,750</b> (in budget) · CoC <span style="color:#178a3a">0.45%</span> · DSCR 1.02 · est NOI $74,500</div>
+ <div style="font-size:13px;color:#222;margin:4px 0">This mixed-use property in Reseda offers a value-add opportunity with a verified in-place cap rate of 5.96% and potential to increase to 7.8% through rent increases and commercial unit optimization. The motivated seller, seller financing, and development optionality under C2 zoning make it an attractive leveraged play for a $400k-down buyer.</div>
+ <div style="font-size:12px;color:#a15c00;margin:4px 0"><b>Risks:</b> Occupancy status is unclear and needs verification; Rent control restrictions may limit rent increases; Value-add projections depend on successful execution of tenant improvements and re-leasing</div>
+ <div style="font-size:12px;color:#555">VERIFIED (MLS #26654413): actual in-place cap ~5.96% (NOI $74,497 on $112,200 gross, $37,703 exp) — the 7.8% headline is PROJECTED, achieved mainly by doubling the commercial unit $2,000->$4,000/mo + bumping the 3bd. SELLER FINANCING confirmed. Price CUT $1,399,000->$1,250,000 (motivated seller). Occupancy ambiguous (listing desc says 3 occupied vs MLS 0% vacancy — verify rent roll). C2 development optionality.</div>
+ </div>
+
+<h3 style="margin:20px 0 4px;font-size:15px">🔥 Red-hot opportunities (1)</h3>
+<div style="border-top:1px solid #e2e2e2;padding:10px 0">
+ <div style="font-size:14px"><span style="background:#c81e1e;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;margin-right:6px;vertical-align:middle">🔥 HOT</span><b><a href="https://www.compass.com/homedetails/7304-Canby-Ave-Reseda-CA-91335/1IO7AS_pid/">7302-7304 Canby Ave</a>, Reseda</b> <span style="color:#888;font-weight:400">score 52</span></div>
+ <div style="color:#666;font-size:12px">Mixed-use · 5 units · built 1926 · $1,250,000 · cap 5.96% (✓ verified in-place)</div>
+ <div style="font-size:12px;color:#333;margin:3px 0">Cash to close <b>$343,750</b> (in budget) · CoC <span style="color:#178a3a">0.45%</span> · DSCR 1.02 · est NOI $74,500</div>
+ <div style="font-size:13px;color:#222;margin:4px 0">This mixed-use property in Reseda offers a value-add opportunity with a verified in-place cap rate of 5.96% and potential to increase to 7.8% through rent increases and commercial unit optimization. The motivated seller, seller financing, and development optionality under C2 zoning make it an attractive leveraged play for a $400k-down buyer.</div>
+ <div style="font-size:12px;color:#a15c00;margin:4px 0"><b>Risks:</b> Occupancy status is unclear and needs verification; Rent control restrictions may limit rent increases; Value-add projections depend on successful execution of tenant improvements and re-leasing</div>
+ <div style="font-size:12px;color:#555">VERIFIED (MLS #26654413): actual in-place cap ~5.96% (NOI $74,497 on $112,200 gross, $37,703 exp) — the 7.8% headline is PROJECTED, achieved mainly by doubling the commercial unit $2,000->$4,000/mo + bumping the 3bd. SELLER FINANCING confirmed. Price CUT $1,399,000->$1,250,000 (motivated seller). Occupancy ambiguous (listing desc says 3 occupied vs MLS 0% vacancy — verify rent roll). C2 development optionality.</div>
+ </div>
+
+<p style="color:#888;font-size:12px;margin-top:16px">Live viewer: http://127.0.0.1:9911 · 6am report covers the full Top-10 · generated by commercialrealestate/scripts/afternoon-report.js</p>
+</div>
\ No newline at end of file
diff --git a/deploy/com.steve.cre-afternoon-report.plist b/deploy/com.steve.cre-afternoon-report.plist
new file mode 100644
index 0000000..f174362
--- /dev/null
+++ b/deploy/com.steve.cre-afternoon-report.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>com.steve.cre-afternoon-report</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/bin/zsh</string>
+ <string>/Users/stevestudio2/Projects/commercialrealestate/scripts/run-afternoon-report.sh</string>
+ </array>
+ <key>StartCalendarInterval</key>
+ <dict>
+ <key>Hour</key>
+ <integer>13</integer>
+ <key>Minute</key>
+ <integer>0</integer>
+ </dict>
+ <key>RunAtLoad</key>
+ <false/>
+ <key>StandardOutPath</key>
+ <string>/tmp/cre-afternoon-report.out</string>
+ <key>StandardErrorPath</key>
+ <string>/tmp/cre-afternoon-report.err</string>
+</dict>
+</plist>
diff --git a/scripts/afternoon-report.js b/scripts/afternoon-report.js
new file mode 100644
index 0000000..1046edd
--- /dev/null
+++ b/scripts/afternoon-report.js
@@ -0,0 +1,126 @@
+// afternoon-report.js — the 1pm "what's new since this morning" report.
+// Reads ranked.json (re-ranked after the 1pm Crexi re-scrape) plus the pre-scrape id snapshot
+// (data/.pre-afternoon-ids.json, written by run-afternoon-report.sh BEFORE refresh-listings.js)
+// and surfaces TWO things Steve asked for:
+// 1. ADDITIONAL LISTINGS — listings whose id wasn't present before the 1pm scrape (genuinely new to the ranker)
+// 2. RED-HOT OPPORTUNITIES — active + in-budget deals with standout economics (high composite / strong CoC / AI "Buy")
+// Builds HTML -> data/afternoon-report.html and emails it to steve-office + steve-personal via George's
+// local /api/send (both recipients are George-internal, so the external-send guard passes; no claude -p,
+// no paid API — the only cost in the whole 1pm job is the Browserbase scrape upstream).
+const fs = require('fs');
+const path = require('path');
+const http = require('http');
+const ROOT = path.join(__dirname, '..');
+const CREFin = require('../public/finance.js');
+
+const { meta, assumptions, ranked } = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'ranked.json'), 'utf8'));
+const A = assumptions || { budget: 400000, downPct: 25, ratePct: 6.75, amortYears: 30, closingPct: 2.5 };
+
+// pre-scrape snapshot of listing ids (written by the shell before refresh). Missing/empty => treat as "no baseline"
+let preIds = new Set();
+let hadBaseline = false;
+try {
+ const snap = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', '.pre-afternoon-ids.json'), 'utf8'));
+ if (Array.isArray(snap.ids)) { preIds = new Set(snap.ids); hadBaseline = snap.ids.length > 0; }
+} catch (_) {}
+
+// recompute finance at the canonical 25%-down scenario (same as morning-report.js)
+const rows = ranked.map(p => {
+ const f = CREFin.finance(p, A);
+ const fs2 = CREFin.financeScore(p, f);
+ return { ...p, finance: f, composite: CREFin.composite(p, f, fs2.score, p.qwenScore) };
+});
+const isActive = p => !/Under Contract|Pending|OUT OF BUDGET/i.test(p.status || '');
+
+// 1) ADDITIONAL LISTINGS: in the current ranker but not in the pre-scrape snapshot
+const newListings = hadBaseline ? rows.filter(p => !preIds.has(p.id)) : [];
+newListings.sort((a, b) => b.composite - a.composite);
+
+// 2) RED-HOT: active + affordable + standout economics. Exclude nothing by recency — a hot deal is hot
+// whether it's new or has been sitting. Bar = high blended score OR strong cash-on-cash OR AI says Buy.
+const isHot = p => isActive(p) && p.finance.affordable &&
+ (p.composite >= 68 || (p.finance.coc != null && p.finance.coc >= 4) || (p.qwen && p.qwen.recommendation === 'Buy'));
+let redHot = rows.filter(isHot).sort((a, b) => b.composite - a.composite).slice(0, 6);
+// Fallback: if nothing clears the bar, show the 3 best active in-budget deals so the section is never empty.
+let hotFallback = false;
+if (!redHot.length) {
+ hotFallback = true;
+ redHot = rows.filter(p => isActive(p) && p.finance.affordable).sort((a, b) => b.composite - a.composite).slice(0, 3);
+}
+
+const today = process.env.REPORT_DATE || new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' });
+const fmt = n => n == null ? '—' : '$' + Math.round(n).toLocaleString();
+
+const card = (p, badge) => {
+ const f = p.finance;
+ const ver = p.verified === true ? '✓ verified in-place' : (p.cap_rate != null ? 'broker cap' : 'no income data');
+ const cap = p.cap_rate != null ? p.cap_rate + '%' : '—';
+ const posLev = f.coc != null && f.coc > 0;
+ return `<div style="border-top:1px solid #e2e2e2;padding:10px 0">
+ <div style="font-size:14px">${badge || ''}<b><a href="${p.source}">${p.address}</a>, ${p.city}</b> <span style="color:#888;font-weight:400">score ${p.composite}</span></div>
+ <div style="color:#666;font-size:12px">${p.type} · ${p.units} unit${p.units > 1 ? 's' : ''}${p.year_built ? ` · built ${p.year_built}` : ''} · ${fmt(p.price)} · cap ${cap} (${ver})</div>
+ <div style="font-size:12px;color:#333;margin:3px 0">Cash to close <b>${fmt(f.cashNeeded)}</b> (${f.affordable ? 'in budget' : 'OVER budget'}) · CoC <span style="color:${posLev ? '#178a3a' : '#b00'}">${f.coc != null ? f.coc + '%' : 'n/a'}</span> · DSCR ${f.dscr ?? 'n/a'} · est NOI ${f.noi ? fmt(f.noi) : 'n/d'}</div>
+ ${p.qwen && p.qwen.thesis ? `<div style="font-size:13px;color:#222;margin:4px 0">${p.qwen.thesis}</div>` : ''}
+ ${p.qwen && p.qwen.risks && p.qwen.risks.length ? `<div style="font-size:12px;color:#a15c00;margin:4px 0"><b>Risks:</b> ${p.qwen.risks.slice(0, 3).join('; ')}</div>` : ''}
+ <div style="font-size:12px;color:#555">${p.upside_note || ''}</div>
+ </div>`;
+};
+const NEW_BADGE = '<span style="background:#0a7d2c;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;margin-right:6px;vertical-align:middle">NEW</span>';
+const HOT_BADGE = '<span style="background:#c81e1e;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;margin-right:6px;vertical-align:middle">🔥 HOT</span>';
+
+const newSection = !hadBaseline
+ ? `<p style="font-size:13px;color:#a15c00;margin:8px 0">No pre-scrape baseline was available this run, so "new since morning" couldn't be computed. (Resolves automatically on the next 1pm run.)</p>`
+ : newListings.length
+ ? newListings.map(p => card(p, NEW_BADGE)).join('\n')
+ : `<p style="font-size:13px;color:#444;margin:8px 0">No new SFV listings hit Crexi since this morning's run. The ranker still tracks ${rows.length} listings; red-hot standouts below.</p>`;
+
+const hotSection = redHot.length
+ ? (hotFallback ? `<p style="font-size:12px;color:#a15c00;margin:6px 0">Nothing cleared the red-hot bar today — showing the best active, in-budget deals instead.</p>` : '')
+ + redHot.map(p => card(p, HOT_BADGE)).join('\n')
+ : `<p style="font-size:13px;color:#444;margin:8px 0">No active in-budget deals to flag right now.</p>`;
+
+const html = `<div style="font-family:-apple-system,Segoe UI,Arial,sans-serif;max-width:780px">
+<h2 style="margin:0 0 2px">SFV CRE — 1pm Update: New Listings & Red-Hot Deals</h2>
+<div style="color:#666;font-size:13px">${today} · ${meta.market} · $${A.budget.toLocaleString()} down (${A.downPct}% / ${A.ratePct}% / ${A.amortYears}yr) · ${rows.length} listings tracked</div>
+<p style="font-size:13px;color:#444;margin:12px 0">Afternoon re-scrape of Crexi vs this morning. Cap rates are broker/MLS-stated — verify NOI in DD.
+Most SFV multifamily runs negative/breakeven leverage at today's rates; positive-leverage (green CoC) or seller-financed deals are the standouts.</p>
+
+<h3 style="margin:16px 0 4px;font-size:15px">🆕 Additional listings since this morning${hadBaseline ? ` (${newListings.length})` : ''}</h3>
+${newSection}
+
+<h3 style="margin:20px 0 4px;font-size:15px">🔥 Red-hot opportunities (${redHot.length})</h3>
+${hotSection}
+
+<p style="color:#888;font-size:12px;margin-top:16px">Live viewer: http://127.0.0.1:9911 · 6am report covers the full Top-10 · generated by commercialrealestate/scripts/afternoon-report.js</p>
+</div>`;
+
+fs.writeFileSync(path.join(ROOT, 'data', 'afternoon-report.html'), html);
+
+if (process.env.DRY_RUN) {
+ console.log(`DRY_RUN: built afternoon-report.html — baseline=${hadBaseline} new=${hadBaseline ? newListings.length : 'n/a'} redHot=${redHot.length}${hotFallback ? ' (fallback)' : ''}; not sending.`);
+ process.exit(0);
+}
+
+// ---- send via George local API (both recipients are internal; guard passes) ----
+const payload = JSON.stringify({
+ account: 'steve-office',
+ to: 'steve@designerwallcoverings.com, steveabramsdesigns@gmail.com',
+ subject: `SFV CRE 1pm — ${hadBaseline ? newListings.length : '?'} new · ${redHot.length} red-hot (${today})`,
+ body: html,
+});
+const req = http.request({
+ host: '127.0.0.1', port: 9850, path: '/api/send', method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Content-Length': Buffer.byteLength(payload),
+ 'Authorization': 'Basic ' + Buffer.from('admin:').toString('base64'),
+ },
+}, res => {
+ let d = ''; res.on('data', c => d += c);
+ res.on('end', () => {
+ console.log('George /api/send ->', res.statusCode, d);
+ process.exit(res.statusCode === 200 ? 0 : 1);
+ });
+});
+req.on('error', e => { console.error('send error:', e.message); process.exit(1); });
+req.write(payload); req.end();
diff --git a/scripts/run-afternoon-report.sh b/scripts/run-afternoon-report.sh
new file mode 100755
index 0000000..b2f4d92
--- /dev/null
+++ b/scripts/run-afternoon-report.sh
@@ -0,0 +1,34 @@
+#!/bin/zsh
+# Daily 1pm SFV CRE update -> emails "additional listings since morning + red-hot opportunities"
+# to steve-office + steve-personal via George. Wired to launchd com.steve.cre-afternoon-report (13:00 PT).
+# Flow: snapshot current listing ids -> Browserbase/Crexi re-scrape (the ONLY paid step, ~$0.30-0.60/run)
+# -> Qwen re-rank (local/free) -> afternoon-report.js builds HTML + sends via George local /api/send.
+export PATH="/usr/local/bin:/opt/homebrew/bin:$HOME/.npm-global/bin:$HOME/.claude/local:$PATH"
+export NODE_PATH="$HOME/.claude/skills/browserbase/node_modules"
+cd "$HOME/Projects/commercialrealestate" || exit 1
+LOG=/tmp/cre-afternoon-report.log
+echo "===== afternoon run $(date) =====" >> "$LOG"
+
+# 1) Snapshot listing ids BEFORE the scrape so the report can diff "what's new since morning".
+/usr/local/bin/node -e '
+ const fs=require("fs");
+ const d=JSON.parse(fs.readFileSync("data/listings.json","utf8"));
+ fs.writeFileSync("data/.pre-afternoon-ids.json", JSON.stringify({ at:new Date().toISOString(), ids:d.listings.map(l=>l.id) }));
+ console.log("snapshot ids:", d.listings.length);
+' >> "$LOG" 2>&1
+
+# 2) Re-scrape Crexi (appends new in-band SFV listings, deduped). Non-fatal: if Browserbase is down we still
+# re-rank + report the red-hot deals on existing data.
+/usr/local/bin/node scripts/refresh-listings.js >> "$LOG" 2>&1 || echo "refresh-listings failed (continuing)" >> "$LOG"
+
+# 3) Re-rank (local Qwen, cached -> fresh only for genuinely new listings).
+/usr/local/bin/node scripts/analyze.js >> "$LOG" 2>&1
+
+# 4) Build the afternoon report HTML and email it to both recipients (self-contained send).
+/usr/local/bin/node scripts/afternoon-report.js >> "$LOG" 2>&1
+
+# 5) Commit the day's data + report.
+git -c user.email=steve@designerwallcoverings.com -c user.name="Steve" add -A >> "$LOG" 2>&1
+git -c user.email=steve@designerwallcoverings.com -c user.name="Steve" commit -q -m "afternoon CRE update $(date +%Y-%m-%d)" >> "$LOG" 2>&1 || echo "nothing to commit" >> "$LOG"
+
+echo "===== done $(date) =====" >> "$LOG"
← 42bff37 weekly Crexi refresh 2026-06-22
·
back to Commercialrealestate
·
morning CRE report: add steve-personal as second recipient 133b583 →