← back to Rentv Sheet Enrich Refine
enrich.py
131 lines
#!/usr/bin/env python3
"""
enrich.py — RENTV CRE-sheet enrichment engine.
LAYER 1 (this file, $0, deterministic): derive each company's Website from its
existing corporate email domain, and add a Website/LinkedIn/Marketing-VP column set.
Only EMPTY target cells are filled; existing data is never overwritten. Every filled
cell is stamped light-green by lib.batch_fill.
Modes:
--dry-run read local data/tab_<gid>.csv, report projected fills, write NOTHING
--apply read the LIVE tab via the Sheets API and write fills (needs connect.py)
LinkedIn + Marketing/VP contact enrichment is LAYER 2 (research) — added next,
after Layer 1 is verified on the live sheet.
"""
import sys, os, csv, re
sys.path.insert(0, os.path.dirname(__file__))
import lib
FREEMAIL = {"gmail.com","hotmail.com","yahoo.com","outlook.com","aol.com","icloud.com",
"me.com","comcast.net","att.net","sbcglobal.net","live.com","msn.com","ymail.com",
"protonmail.com","mac.com","verizon.net","cox.net"}
# Per-tab config keyed by gid (=sheetId). Column indices are 0-based.
# header_row : row index holding column titles (0-based)
# data_start : first data row (0-based)
# company/email : source column indices
# new_website/new_linkedin/new_mvp : destination column indices for the added set
# base = first column of the 4-col added block, chosen to sit AFTER each tab's true
# max-used column (verified against the LIVE read, NOT the misleading CSV export).
TABS = {
1536880271: dict(name="Sponsor & Speaker Companies",
header_row=0, data_start=2, company=2, email=6, base=10), # K-N
2087021739: dict(name="Strong Prospects",
header_row=1, data_start=2, company=3, email=7, base=11), # L-O (skips K status col)
1929460437: dict(name="Event Sponsors",
header_row=1, data_start=2, company=2, email=6, base=9), # J-M (after I status col)
3823360: dict(name="Unique Contacts (all tabs)", # the deduped MASTER
header_row=0, data_start=1, company=2, email=1, base=15), # cols P.. (append after O)
}
NEW_LABELS = ["Website", "LinkedIn (Company)", "LinkedIn (Contact)",
"Marketing/VP Contact", "Updated Email (found)"]
def cols(cfg):
b = cfg["base"]
return dict(new_website=b, new_li_company=b+1, new_li_contact=b+2, new_mvp=b+3)
def cell(row, i):
return row[i].strip() if i < len(row) else ""
def domain_from_emails(emailcell):
"""Pick the first corporate (non-freemail) domain from a possibly multi-email cell."""
for m in re.findall(r'[\w.+-]+@([\w-]+\.[\w.-]+)', emailcell or ""):
d = m.lower().strip(".")
if d not in FREEMAIL:
return d
return ""
def plan_tab(gid, rows):
"""Return (fills, stats). fills = list of {row0,col0,value}."""
cfg = TABS[gid]; nc = cols(cfg)
fills, stats = [], dict(rows=0, has_email=0, website_fill=0, freemail_only=0, no_email=0)
# header cells for the new columns (only if currently blank)
hr = cfg["header_row"]
hdr = rows[hr] if hr < len(rows) else []
for i, label in enumerate(NEW_LABELS):
col = cfg["base"] + i
if cell(hdr, col) == "":
fills.append({"row0": hr, "col0": col, "value": label})
for r in range(cfg["data_start"], len(rows)):
row = rows[r]
comp = cell(row, cfg["company"])
if not comp:
continue
stats["rows"] += 1
emailc = cell(row, cfg["email"])
# only fill Website if that new cell is currently empty
if cell(row, nc["new_website"]) != "":
continue
dom = domain_from_emails(emailc)
if dom:
stats["has_email"] += 1; stats["website_fill"] += 1
fills.append({"row0": r, "col0": nc["new_website"], "value": dom})
elif emailc:
stats["freemail_only"] += 1 # has email but only free-mail -> needs Layer 2
else:
stats["no_email"] += 1 # no email -> needs Layer 2
return fills, stats
def load_csv(gid):
path = os.path.join(os.path.dirname(__file__), "data", f"tab_{gid}.csv")
with open(path, newline="", encoding="utf-8", errors="ignore") as f:
return list(csv.reader(f))
def main():
mode = "--dry-run" if "--apply" not in sys.argv else "--apply"
tok = lib.access_token() if mode == "--apply" else None
meta = lib.get_meta(tok) if tok else None
title_by_gid = {}
if meta:
for s in meta["sheets"]:
title_by_gid[s["properties"]["sheetId"]] = s["properties"]["title"]
only = None
if "--only" in sys.argv:
only = int(sys.argv[sys.argv.index("--only")+1])
for gid, cfg in TABS.items():
if only is not None and gid != only:
continue
if mode == "--apply":
rows = lib.read_tab(tok, title_by_gid[gid])
else:
rows = load_csv(gid)
fills, stats = plan_tab(gid, rows)
data_fills = [f for f in fills if f["row0"] != cfg["header_row"]]
print(f"\n=== {cfg['name']} (gid={gid}) ===")
print(f" companies: {stats['rows']} | website derivable now: {stats['website_fill']}"
f" | freemail-only (Layer2): {stats['freemail_only']} | no-email (Layer2): {stats['no_email']}")
print(f" cells to fill this run: {len(fills)} ({len(data_fills)} websites + headers)")
for f in data_fills[:5]:
print(f" -> R{f['row0']+1}C{lib.col_letter(f['col0'])}: {f['value']}")
if mode == "--apply":
res = lib.batch_fill(tok, gid, fills)
print(f" APPLIED: {res.get('totalUpdatedCells','?')} cells written (light-green).")
if __name__ == "__main__":
main()