← back to Rentv Sheet Enrich Refine
team_vp.py
82 lines
#!/usr/bin/env python3
"""
team_vp.py — $0 LOCAL Marketing/VP finder. All local: headless urllib fetch of a company's
own Team/Leadership/About pages + Muse (glimmer, local Ollama) to EXTRACT the marketing
lead from the REAL page text. Anti-fabrication: the extracted name must literally appear
in the fetched text, else dropped. No Browserbase, no Exa, no cloud.
Usage: team_vp.py [limit] (processes companies that have a Website but no Marketing/VP Name)
"""
import lib, json, re, os, ssl, sys, urllib.request
from concurrent.futures import ThreadPoolExecutor
GID=3823360
MODEL=os.environ.get("VP_MODEL","qwen3:14b") # was muse-glimmer:30b-mlx; repointed 2026-08-31 (TK-10980, muse removed to reclaim 19GB). Override via VP_MODEL.
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
UA={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/537.36"}
MKT=re.compile(r'(chief marketing|vp[, ]+marketing|vice president[, ]+marketing|marketing (director|manager|officer)|head of marketing|director[, ]+marketing|marketing & communications|communications director)',re.I)
TAG=re.compile(r'<[^>]+>')
def fetch(u):
try: return urllib.request.urlopen(urllib.request.Request(u,headers=UA),timeout=8,context=ctx).read(400000).decode("utf-8","ignore")
except Exception: return ""
def team_text(domain):
txt=""
for p in ("/team","/leadership","/our-team","/about","/about-us","/people","/management","/company","/who-we-are"):
for sch in ("https://","http://"):
h=fetch(sch+domain+p)
if h and MKT.search(h): txt+=" "+TAG.sub(" ",h); break
return re.sub(r'\s+',' ',txt)[:6000]
def ask(prompt):
body=json.dumps({"model":MODEL,"prompt":prompt,"stream":False,"options":{"temperature":0}}).encode()
try:
r=urllib.request.urlopen(urllib.request.Request("http://localhost:11434/api/generate",data=body,headers={"Content-Type":"application/json"}),timeout=90)
return json.load(r).get("response","")
except Exception: return ""
def extract_vp(domain, text):
if not text: return None
p=(f"From this company web-page text, find the ONE person whose title is a marketing role "
f"(Chief Marketing Officer / VP Marketing / Marketing Director / Marketing Manager / Head of Marketing / Communications Director). "
f'Reply ONLY compact JSON: {{"name":"Full Name","title":"Their Title"}} — or {{}} if no marketing person is named. '
f"Use ONLY a name that appears verbatim in the text. Do not invent.\n\nTEXT: {text}")
out=ask(p)
m=re.search(r'\{[^{}]*\}',out)
if not m: return None
try: d=json.loads(m.group(0))
except Exception: return None
name=(d.get("name") or "").strip()
if not name or name.lower() not in text.lower(): return None # verify-in-fetch guard
return {"name":name,"title":(d.get("title") or "").strip()}
def main():
limit=int(sys.argv[1]) if len(sys.argv)>1 else 9999
tok=lib.access_token()
title={s["properties"]["sheetId"]:s["properties"]["title"] for s in lib.get_meta(tok)["sheets"]}[GID]
rows=lib.read_tab(tok,title);hdr=rows[0]
c={v.strip():j for j,v in enumerate(hdr) if v.strip()}
def g(r,i): return (r[i] if i<len(r) else "").strip()
wi=c['Website']; ni=c['Marketing/VP Name']; cvi=c['Company/Venue']
# unique companies with website but no marketing name yet
seen=set(); todo=[]
for r in range(1,len(rows)):
comp=g(rows[r],cvi); dom=g(rows[r],wi)
k=comp.lower()
if not comp or not dom or k in seen or g(rows[r],ni): continue
seen.add(k); todo.append((comp,dom))
todo=todo[:limit]
print(f"{len(todo)} companies to try (model={MODEL})",flush=True)
# fetch team text concurrently, then extract sequentially (LLM is the bottleneck)
with ThreadPoolExecutor(max_workers=24) as ex:
texts=list(ex.map(lambda cd: team_text(cd[1]), todo))
res=[]; done=0
for (comp,dom),text in zip(todo,texts):
vp=extract_vp(dom,text); done+=1
if vp:
res.append({"company":comp,"mvp":f"{vp['name']} — {vp['title']} (from {dom})",
"why":f"Marketing lead named on {dom} team page (local glimmer extract, verified)"})
print(f" {comp[:30]:30s} -> {vp['name']} ({vp['title']})",flush=True)
if done%25==0: print(f" ...{done}/{len(todo)}",flush=True)
out="/tmp/team_vp.json"; json.dump(res,open(out,"w"))
print(f"{len(res)} marketing leads found",flush=True)
if res:
import subprocess; subprocess.run(["python3",os.path.join(os.path.dirname(__file__),"write_by_name.py"),str(GID),out])
if __name__=="__main__": main()