← back to Rentv Sheet Enrich Refine
propagate.py
92 lines
#!/usr/bin/env python3
"""
propagate.py — push the MASTER's company-level enrichment out to EVERY tab.
Builds an enrichment map keyed by normalized company from the master
"Unique Contacts (all tabs)", then for each other tab: auto-detects its company
column (header contains company/venue/firm), auto-expands the grid, adds the
enrichment columns if missing, and fills them by company-name match (empty cells
only, light-green, LinkedIn as =HYPERLINK). Per-tab try/except so one bad tab
never blocks the rest.
"""
import lib, json, re, urllib.request
def norm(s): return re.sub(r'[^a-z0-9]','',(s or '').lower())
API="https://sheets.googleapis.com/v4/spreadsheets"
# columns we copy from master -> each tab (label -> is-hyperlink)
FIELDS=[("Website",False),("Company Phone (found)",False),("LinkedIn (Company)",True),
("Marketing/VP Name",False),("Marketing/VP Position",False),
("Marketing/VP LinkedIn",True),("Why (Marketing/VP)",False)]
SKIP={"Color Legend (inferred)","3","Unique Contacts (all tabs)","All Contacts (exploded)","Contacts (exploded)"}
CKEYS=("company","company/venue","venue","firm","organization","organization/venue")
def expand(tok,gid,to):
req={"requests":[{"appendDimension":{"sheetId":gid,"dimension":"COLUMNS","length":to}}]}
urllib.request.urlopen(urllib.request.Request(f"{API}/{lib.SID}:batchUpdate",
data=json.dumps(req).encode(),headers={"Authorization":f"Bearer {tok}","Content-Type":"application/json"},method="POST"))
def main():
tok=lib.access_token()
meta=json.load(urllib.request.urlopen(urllib.request.Request(
f"{API}/{lib.SID}?fields=sheets(properties(sheetId,title))",headers={"Authorization":f"Bearer {tok}"})))
# 1) build enrichment map from master
mrows=lib.read_tab(tok,"Unique Contacts (all tabs)")
mh={v.strip():j for j,v in enumerate(mrows[0]) if v.strip()}
def mg(r,i): return (r[i] if i<len(r) else "").strip()
emap={}
for r in range(1,len(mrows)):
c=norm(mg(mrows[r],mh["Company/Venue"]))
if not c or c in emap: continue
d={}
for lab,_ in FIELDS:
if lab in mh:
v=mg(mrows[r],mh[lab])
if v.startswith("=HYPERLINK"):
m=re.match(r'=HYPERLINK\("([^"]+)"\)',v); v=m.group(1) if m else v
if v: d[lab]=v
if d: emap[c]=d
print(f"master enrichment map: {len(emap)} companies")
for s in meta["sheets"]:
title=s["properties"]["title"]; gid=s["properties"]["sheetId"]
if title in SKIP: continue
try:
rows=lib.read_tab(tok,title)
# find company column + header row
ccol=chrow=None
for hi in range(min(3,len(rows))):
for j,v in enumerate(rows[hi]):
if v.strip().lower() in CKEYS: ccol,chrow=j,hi; break
if ccol is not None: break
if ccol is None:
print(f" SKIP {title!r}: no company column"); continue
hdr=rows[chrow]
hlabels={v.strip():j for j,v in enumerate(hdr) if v.strip()}
right=max((j for j,v in enumerate(hdr) if v.strip()),default=0)
width=max(len(r) for r in rows) if rows else right+1
# assign/locate target columns
col={}; newh=[]; nxt=right+1
for lab,_ in FIELDS:
if lab in hlabels: col[lab]=hlabels[lab]
else: col[lab]=nxt; newh.append((nxt,lab)); nxt+=1
need=nxt
if need>width: expand(tok,gid,need-width+2)
def cg(r,i): return (rows[r][i] if r<len(rows) and i<len(rows[r]) else "").strip()
cells=[{"row0":chrow,"col0":c,"value":lab} for c,lab in newh]
for r in range(chrow+1,len(rows)):
comp=norm(cg(r,ccol))
d=emap.get(comp)
if not d: continue
for lab,islink in FIELDS:
val=d.get(lab)
if not val or cg(r,col[lab]): continue
if islink: cells.append({"row0":r,"col0":col[lab],"value":'=HYPERLINK("%s")'%val.replace('"',""),"formula":True})
else: cells.append({"row0":r,"col0":col[lab],"value":val})
res=lib.batch_fill(tok,gid,cells)
print(f" {title!r}: +{len(newh)} cols, {res.get('totalUpdatedCells',0)} cells")
except BaseException as e:
print(f" ERROR {title!r}: {str(e)[:80]}")
if __name__=="__main__":
main()