← back to Rentv Ad Engine
Phase 2b: advertiser ADDRESS field via free website lookup (regex + local-LLM fallback) — deep in-video frames blocked by Vimeo DRM + addresses are read verbally not shown; UI shows address (maps link) + needs-web-search flag
e9257f66fae570e14e375b1e63d867f377e701eb · 2026-08-07 09:27:12 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M data/advertisers.jsonM public/index.htmlA scripts/enrich-addresses.py
Diff
commit e9257f66fae570e14e375b1e63d867f377e701eb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 7 09:27:12 2026 -0700
Phase 2b: advertiser ADDRESS field via free website lookup (regex + local-LLM fallback) — deep in-video frames blocked by Vimeo DRM + addresses are read verbally not shown; UI shows address (maps link) + needs-web-search flag
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
data/advertisers.json | 103 ++++++++++++++++++++++++++++------------
public/index.html | 3 ++
scripts/enrich-addresses.py | 111 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 187 insertions(+), 30 deletions(-)
diff --git a/data/advertisers.json b/data/advertisers.json
index 2c2fd16..ddbdfe2 100644
--- a/data/advertisers.json
+++ b/data/advertisers.json
@@ -1,5 +1,5 @@
{
- "generated_at": "2026-08-07 08:55:23",
+ "generated_at": "2026-08-07 09:26:24",
"count": 29,
"advertisers": [
{
@@ -115,7 +115,8 @@
"title": "RENTV CRE Talk Episode #1 - Bill Shopoff",
"url": "https://vimeo.com/771348410"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Fidelity Mortgage Lenders",
@@ -217,7 +218,8 @@
"title": "RENTV FINANCE OCT 19 2022.mp4",
"url": "https://vimeo.com/763447786"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Paramount Property Tax Appeal",
@@ -295,7 +297,8 @@
"title": "RENTV CRE Talk Episode #1 - Bill Shopoff",
"url": "https://vimeo.com/771348410"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Chase Partners",
@@ -364,7 +367,11 @@
"title": "RENTV CRE Talk Episode #14 - John McNellis",
"url": "https://vimeo.com/1056513923"
}
- ]
+ ],
+ "addresses": [
+ "Chase Partners Ltd., P.O Box 3944, Glendale, CA 91221"
+ ],
+ "address_status": "found"
},
{
"name": "Redwood Mortgage",
@@ -414,7 +421,11 @@
"title": "RENTV CRE Talk Episode #1 - Bill Shopoff",
"url": "https://vimeo.com/771348410"
}
- ]
+ ],
+ "addresses": [
+ "155 Bovet Road, Suite 302 San Mateo, CA 94402"
+ ],
+ "address_status": "found"
},
{
"name": "Provident Savings Bank",
@@ -450,7 +461,11 @@
"title": "CRE Talk Episode #20 with Colton Sudberry",
"url": "https://vimeo.com/1178561985"
}
- ]
+ ],
+ "addresses": [
+ "1891 S. Santa Fe Ave., San Jacinto, CA 92581"
+ ],
+ "address_status": "found"
},
{
"name": "Rockefeller Group",
@@ -480,7 +495,13 @@
"title": "CRE Talk Episode #20 with Colton Sudberry",
"url": "https://vimeo.com/1178561985"
}
- ]
+ ],
+ "addresses": [
+ "2000 Our Offices Arizona 2325 East Camelback Road Suite 400 Phoenix, AZ 85016",
+ "5082 California 3161 Michelson Drive Suite 900 Irvine, CA 92612",
+ "1800 Colorado 3400 Walnut Street Suite 240 Denver, CO 80205"
+ ],
+ "address_status": "found"
},
{
"name": "The Bascom Group",
@@ -503,7 +524,8 @@
"title": "RENTV ORANGE COUNTY MULTIFAMILY PANEL July 26, 2022",
"url": "https://vimeo.com/739081927"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Advanced Real Estate Services",
@@ -521,7 +543,8 @@
"title": "The Multifamily Panel at RENTV's Greater LA State of the Market Conference",
"url": "https://vimeo.com/865035925"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Advanced Real Estate The Firm",
@@ -539,7 +562,8 @@
"title": "RENTV ORANGE COUNTY MULTIFAMILY PANEL July 26, 2022",
"url": "https://vimeo.com/739081927"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Axo Bank",
@@ -557,7 +581,8 @@
"title": "The Finance Panel at RENTV's Greater LA State of the Market Conference",
"url": "https://vimeo.com/862593366"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Axos Bank",
@@ -575,7 +600,8 @@
"title": "RENTV FINANCE OCT 19 2022.mp4",
"url": "https://vimeo.com/763447786"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "CBRE",
@@ -593,7 +619,8 @@
"title": "RENTV ORANGE COUNTY MULTIFAMILY PANEL July 26, 2022",
"url": "https://vimeo.com/739081927"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "CBRE's National Retail Partners West Team",
@@ -613,7 +640,8 @@
"title": "The Retail Panel at RENTV's Inland Empire State of The Market Conference",
"url": "https://vimeo.com/830276773"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "City of Ontario",
@@ -633,7 +661,8 @@
"title": "The Retail Panel at RENTV's Inland Empire State of The Market Conference",
"url": "https://vimeo.com/830276773"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "First",
@@ -651,7 +680,8 @@
"title": "RENTV CRE Talk Episode #13 - Ethan Penner",
"url": "https://vimeo.com/1039101858"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Jones Lang LaSalle (JLL)",
@@ -671,7 +701,8 @@
"title": "The Office Panel at RENTV's Inland Empire State of The Market Conference",
"url": "https://vimeo.com/830279080"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "MG R Real Estate",
@@ -689,7 +720,8 @@
"title": "The Office Panel at RENTV's Inland Empire State of The Market Conference",
"url": "https://vimeo.com/830279080"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "MGR Real Estate Services",
@@ -707,7 +739,8 @@
"title": "RENTV CRE Talk Episode #11 - Michael Rademaker",
"url": "https://vimeo.com/953281926"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Ms. Rahi TE Bank",
@@ -725,7 +758,8 @@
"title": "The Finance Panel at RENTV's Greater LA State of the Market Conference",
"url": "https://vimeo.com/862593366"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Newmark Merrill Companies",
@@ -745,7 +779,8 @@
"title": "The Retail Panel at RENTV's Inland Empire State of The Market Conference",
"url": "https://vimeo.com/830276773"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Pacific Southwest Realty Services",
@@ -763,7 +798,8 @@
"title": "The Finance Panel at RENTV's Greater LA State of the Market Conference",
"url": "https://vimeo.com/862593366"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Paragon Commercial Group",
@@ -783,7 +819,8 @@
"title": "The Retail Panel at RENTV's Inland Empire State of The Market Conference",
"url": "https://vimeo.com/830276773"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Provident Bank",
@@ -801,7 +838,8 @@
"title": "The Finance Panel at RENTV's Greater LA State of the Market Conference",
"url": "https://vimeo.com/862593366"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "rent TV",
@@ -819,7 +857,8 @@
"title": "CLIC Vlog w Steve Bloom.mov",
"url": "https://vimeo.com/721184140"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Say What's Properties",
@@ -837,7 +876,8 @@
"title": "RENTV ORANGE COUNTY MULTIFAMILY PANEL July 26, 2022",
"url": "https://vimeo.com/739081927"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Torrey Pines Bank",
@@ -855,7 +895,8 @@
"title": "RENTV FINANCE OCT 19 2022.mp4",
"url": "https://vimeo.com/763447786"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Universe Holdings",
@@ -873,7 +914,8 @@
"title": "The Multifamily Panel at RENTV's Greater LA State of the Market Conference",
"url": "https://vimeo.com/865035925"
}
- ]
+ ],
+ "address_status": "needs_web_search"
},
{
"name": "Walker and Dunlop",
@@ -891,7 +933,8 @@
"title": "RENTV FINANCE OCT 19 2022.mp4",
"url": "https://vimeo.com/763447786"
}
- ]
+ ],
+ "address_status": "needs_web_search"
}
]
}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 7d664a6..ab7910b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -183,6 +183,7 @@ function renderCards(main,L){
${(a.websites||[]).map(w=>`<div class="kv">🌐 <a href="${siteURL(esc(w))}" target="_blank" rel="noopener">${esc(w)}</a></div>`).join("")}
${(a.phones||[]).map(p=>`<div class="kv">📞 <a href="${telURL(p)}">${esc(p)}</a></div>`).join("")}
${(a.emails||[]).map(m=>`<div class="kv">✉️ <a href="mailto:${esc(m)}">${esc(m)}</a></div>`).join("")}
+ ${(a.addresses||[]).slice(0,1).map(ad=>`<div class="kv">📍 <a href="https://maps.google.com/?q=${encodeURIComponent(ad)}" target="_blank" rel="noopener">${esc(ad)}</a></div>`).join("")}
${(a.contacts||[]).length?`<div class="kv">👤 <b>${esc(a.contacts.join(", "))}</b></div>`:""}
<div class="row">${epChips(a.episodes)}</div>`;
g.appendChild(c);
@@ -303,6 +304,8 @@ window.openAdv=name=>{
${(a.websites||[]).map(w=>`<div>🌐 <a href="${siteURL(esc(w))}" target="_blank" rel="noopener">${esc(w)}</a></div>`).join("")}
${(a.phones||[]).map(p=>`<div>📞 <a href="${telURL(p)}">${esc(p)}</a></div>`).join("")}
${(a.emails||[]).map(m=>`<div>✉️ <a href="mailto:${esc(m)}">${esc(m)}</a></div>`).join("")}
+ ${(a.addresses||[]).map(ad=>`<div>📍 <a href="https://maps.google.com/?q=${encodeURIComponent(ad)}" target="_blank" rel="noopener">${esc(ad)}</a></div>`).join("")}
+ ${a.address_status==="needs_web_search"?'<div class="muted">📍 address: needs web lookup (no website captured)</div>':""}
${(a.contacts||[]).length?`<div>👤 ${esc(a.contacts.join(", "))}</div>`:""}
<div style="margin:12px 0"><button class="mailbtn" onclick='genEmail("advertiser",${JSON.stringify(a.name)},0)'>✉️ Generate email to client</button></div>
<h3 style="margin-top:8px">Ran on these episodes</h3>
diff --git a/scripts/enrich-addresses.py b/scripts/enrich-addresses.py
new file mode 100644
index 0000000..8f768fc
--- /dev/null
+++ b/scripts/enrich-addresses.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+"""Phase 2 (b) — fill the ADVERTISER ADDRESS field via free web lookup.
+
+Deep in-video frame OCR was blocked by Vimeo's stream DRM AND the addresses are read
+verbally, not shown on screen — so the reliable source of an advertiser's mailing
+address is their OWN website. For each advertiser that has a website, we fetch the
+homepage + common contact pages, extract the best US street address (regex, with a
+local-LLM cleanup on ambiguous cases), and write it into advertisers.json.
+
+Advertisers with NO website captured are flagged needs_web_search (a metered lookup,
+left for a Steve-gated pass). Local + free ($0); writes addresses[] onto each record.
+
+Env: OLLAMA_URL, OLLAMA_MODEL (default qwen2.5:latest), FORCE=1
+"""
+import json, os, re, urllib.request, time
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ADV = os.path.join(ROOT, "data", "advertisers.json")
+OLLAMA = os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434")
+MODEL = os.environ.get("OLLAMA_MODEL", "qwen2.5:latest")
+FORCE = os.environ.get("FORCE", "") == "1"
+UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1"
+
+ADDR_RE = re.compile(
+ r"\d{1,6}\s+[A-Z][A-Za-z0-9.'&/ ]{2,40}"
+ r"(?:Street|St\.?|Avenue|Ave\.?|Boulevard|Blvd\.?|Road|Rd\.?|Drive|Dr\.?|Lane|Ln\.?|"
+ r"Way|Plaza|Center|Centre|Court|Ct\.?|Parkway|Pkwy\.?|Circle|Highway|Hwy\.?)"
+ r"[A-Za-z0-9.,'# ]{0,40}?"
+ r"(?:Suite|Ste\.?|Floor|Fl\.?|#|Unit)?[A-Za-z0-9.,'# ]{0,20}?"
+ r",?\s+[A-Z][A-Za-z ]{2,20},?\s+[A-Z]{2}\s+\d{5}")
+
+
+def fetch(url):
+ try:
+ return urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": UA}), timeout=18)\
+ .read().decode("utf-8", "ignore")
+ except Exception:
+ return ""
+
+
+def page_text(html):
+ t = re.sub(r"<(script|style)[^>]*>.*?</\1>", " ", html, flags=re.S | re.I)
+ t = re.sub(r"<[^>]+>", " ", t)
+ import html as _h
+ return re.sub(r"\s+", " ", _h.unescape(t))
+
+
+def best_address(text):
+ cands = []
+ for m in ADDR_RE.finditer(text):
+ s = re.sub(r"\s+", " ", m.group(0)).strip(" ,")
+ if 12 <= len(s) <= 100 and s not in cands:
+ cands.append(s)
+ return cands[:3]
+
+
+def llm_pick(name, blob):
+ """Ambiguous case: ask local model for the company's HQ mailing address from page text."""
+ prompt = (f'From this website text for "{name}", extract the company mailing address if present. '
+ 'Return ONLY JSON: {"address":"full street address incl city, state, ZIP, or empty if none"}\n\n'
+ + blob[:4000])
+ try:
+ body = json.dumps({"model": MODEL, "prompt": prompt, "format": "json", "stream": False,
+ "options": {"temperature": 0}}).encode()
+ raw = json.load(urllib.request.urlopen(urllib.request.Request(
+ OLLAMA + "/api/generate", data=body, headers={"Content-Type": "application/json"}), timeout=120)
+ ).get("response", "")
+ a = json.loads(raw).get("address", "").strip()
+ return a if a and re.search(r"\d{5}", a) else ""
+ except Exception:
+ return ""
+
+
+def main():
+ doc = json.load(open(ADV))
+ advs = doc["advertisers"]
+ done = need = 0
+ for a in advs:
+ if a.get("addresses") and not FORCE:
+ continue
+ site = a["websites"][0] if a.get("websites") else ""
+ if not site:
+ a["address_status"] = "needs_web_search" # metered, Steve-gated
+ need += 1
+ continue
+ base = "https://" + site
+ found = []
+ for path in ("", "/contact", "/contact-us", "/about", "/contact.html"):
+ txt = page_text(fetch(base + path))
+ if txt:
+ found = best_address(txt)
+ if found:
+ break
+ if not found:
+ # LLM fallback on the contact page text
+ txt = page_text(fetch(base + "/contact") or fetch(base))
+ g = llm_pick(a["name"], txt)
+ if g:
+ found = [g]
+ a["addresses"] = found
+ a["address_status"] = "found" if found else "not_on_site"
+ done += 1 if found else 0
+ print(f" {a['name'][:34]:34} -> {found[0] if found else '(none on site)'}")
+ time.sleep(0.3)
+ doc["generated_at"] = time.strftime("%Y-%m-%d %H:%M:%S")
+ json.dump(doc, open(ADV, "w"), indent=1)
+ print(f"[addresses] DONE found={done} needs_web_search={need} -> advertisers.json")
+
+
+if __name__ == "__main__":
+ main()
← 8ae82f6 Emails tab: batch-drafted client emails for all 29 advertise
·
back to Rentv Ad Engine
·
Phase 2b: web-search address enrichment — 24/29 advertisers 6eeb40a →