← back to Rentv Sheet Enrich Refine
Generative email inference: learn each company's email convention from known emails, infer 800 likely emails (560 pattern-detected, 240 default) into flagged 'Likely Email (inferred)' column
f2b7ed713474577beb59545050d212a0627076c6 · 2026-08-13 13:12:20 -0700 · Steve Abrams
Files touched
A data/ocbatches/oc_06_li.jsonA infer_emails.py
Diff
commit f2b7ed713474577beb59545050d212a0627076c6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 13 13:12:20 2026 -0700
Generative email inference: learn each company's email convention from known emails, infer 800 likely emails (560 pattern-detected, 240 default) into flagged 'Likely Email (inferred)' column
---
data/ocbatches/oc_06_li.json | 1 +
infer_emails.py | 99 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 100 insertions(+)
diff --git a/data/ocbatches/oc_06_li.json b/data/ocbatches/oc_06_li.json
new file mode 100644
index 0000000..e8c745c
--- /dev/null
+++ b/data/ocbatches/oc_06_li.json
@@ -0,0 +1 @@
+[{"company": "Sandstone Capital", "company_li": "https://www.linkedin.com/company/sandstone-capital-inc"}, {"company": "Logan Capital Advisors", "company_li": "https://www.linkedin.com/company/logan-capital-advisors-ltd"}, {"company": "Ashland Pacific", "company_li": "https://www.linkedin.com/company/ashland-pacific-management"}, {"company": "Madison Communities Arizona", "company_li": "https://www.linkedin.com/company/madison-communities-arizona"}, {"company": "Stewart Title", "company_li": "https://www.linkedin.com/company/stewarttitle"}, {"company": "Landmark Dividend", "company_li": "https://www.linkedin.com/company/landmark-dividend"}, {"company": "Poppy Bank", "company_li": "https://www.linkedin.com/company/poppy-bank"}, {"company": "IRONMARK Bulding Company", "company_li": "https://www.linkedin.com/company/ironmark-building-co"}, {"company": "Leader1031", "company_li": "https://www.linkedin.com/company/leader1031"}, {"company": "HCI One", "company_li": "https://www.linkedin.com/company/hci-systems-inc"}, {"company": "Monday Group", "company_li": "https://www.linkedin.com/company/the-monday-group"}, {"company": "West Bay Capital | Capital Real Estate Advisors", "company_li": "https://www.linkedin.com/company/west-bay-capital-llc"}, {"company": "Rockbottom Rentals", "company_li": "https://www.linkedin.com/company/rockbottom-rentals"}, {"company": "Ten-X", "company_li": "https://www.linkedin.com/company/ten-x"}, {"company": "Saratoga Capital Inc", "company_li": "https://www.linkedin.com/company/saratoga-capital-inc"}, {"company": "Moxie Properties", "company_li": "https://www.linkedin.com/company/moxie-management"}, {"company": "Westlake Realty Group", "company_li": "https://www.linkedin.com/company/westlake-realty-group"}, {"company": "Atlas Properties", "company_li": "https://www.linkedin.com/company/atlaspropertiesland"}, {"company": "Evergreen Devco | Evergreen Development | EMAIL", "company_li": "https://www.linkedin.com/company/evergreen-devco-inc"}, {"company": "Scottsdale Resort and Spa", "company_li": "https://www.linkedin.com/company/scottsdale-resort-and-spa"}, {"company": "The Apartment Directory", "company_li": "https://www.linkedin.com/company/the-apartment-directory"}, {"company": "Edge Real Estate", "company_li": "https://www.linkedin.com/company/edge-real-estate-agency"}, {"company": "Mission Profitable", "company_li": "https://www.linkedin.com/company/mission-profitable-inc"}, {"company": "LBG Funds", "company_li": "https://www.linkedin.com/company/lbgfinancial"}, {"company": "Investment RE Source", "company_li": "https://www.linkedin.com/company/investment-r-e-source"}, {"company": "CHASSE Building team", "company_li": "https://www.linkedin.com/company/chasse-building-team"}, {"company": "Westfield", "company_li": "https://www.linkedin.com/company/unibail-rodamco-westfield"}, {"company": "Westland Commercial Leasing", "company_li": "https://www.linkedin.com/company/westland-real-estate-group"}, {"company": "Bliss Realty & Investments", "company_li": "https://www.linkedin.com/company/bliss-realty-investments"}, {"company": "Geneva Street Partners", "company_li": "https://www.linkedin.com/company/the-geneva-partners"}, {"company": "Westside Retail", "company_li": "https://www.linkedin.com/company/the-westside"}, {"company": "SelectLeaders", "company_li": "https://www.linkedin.com/company/selectleaders"}, {"company": "MetroWest Realty Consultants", "company_li": "https://www.linkedin.com/company/metrowest-realty-consultants-inc-"}]
\ No newline at end of file
diff --git a/infer_emails.py b/infer_emails.py
new file mode 100644
index 0000000..e705c93
--- /dev/null
+++ b/infer_emails.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+"""
+infer_emails.py — GENERATIVE email inference (Steve 2026-08-13). For each named contact
+missing a real email, generate the LIKELY email by LEARNING that company's actual naming
+convention from its known emails (e.g. if 'blake.thompson@nmrk.com' exists, Newmark uses
+first.last -> infer 'jane.doe@nmrk.com'). Falls back to first.last@domain when a company
+has a domain but no detectable pattern. Written to a clearly-labeled "Likely Email
+(inferred)" column, GREEN + flagged — never the real Email column, so guesses are never
+mistaken for verified. ADD-only; skips contacts that already have a real or inferred email.
+"""
+import lib, re
+GID = 3823360
+FREE = {'gmail.com','yahoo.com','hotmail.com','aol.com','outlook.com','icloud.com','me.com',
+ 'comcast.net','sbcglobal.net','att.net','msn.com','ymail.com','live.com','mac.com'}
+def alpha(s): return re.sub(r'[^a-z]', '', (s or '').lower())
+def parts(name):
+ toks = [t for t in re.split(r'[\s,]+', name.strip()) if t and t[0].isalpha()]
+ if not toks: return None, None
+ first = alpha(toks[0]); last = alpha(toks[-1]) if len(toks) > 1 else ""
+ return first or None, last or None
+
+# email local-part templates (first, last) -> string
+TEMPLATES = {
+ 'first.last': lambda f,l: f"{f}.{l}",
+ 'firstlast': lambda f,l: f"{f}{l}",
+ 'flast': lambda f,l: f"{f[0]}{l}",
+ 'f.last': lambda f,l: f"{f[0]}.{l}",
+ 'first_last': lambda f,l: f"{f}_{l}",
+ 'firstl': lambda f,l: f"{f}{l[0]}",
+ 'lastf': lambda f,l: f"{l}{f[0]}",
+ 'last.first': lambda f,l: f"{l}.{f}",
+ 'first': lambda f,l: f,
+ 'last': lambda f,l: l,
+}
+def detect(first, last, local):
+ """Which template(s) produce this local-part for this name."""
+ local = alpha(local); out = []
+ if not first: return out
+ for name, fn in TEMPLATES.items():
+ try:
+ if (last or name in ('first',)) and alpha(fn(first, last or first)) == local:
+ out.append(name)
+ except Exception:
+ pass
+ return out
+
+def main():
+ tok = lib.access_token()
+ title = "Unique Contacts (all tabs)"
+ rows = lib.read_tab(tok, title); hdr = [h.strip() for h in rows[0]]
+ c = {v: j for j, v in enumerate(hdr) if v}
+ def g(r, i): return (r[i] if i is not None and i < len(r) else "").strip()
+ NAME = c['Contact Name']; EMAIL = c['Email']; CO = c['Company/Venue']; UEM = c['Updated Email (found)']
+ # ensure the inferred column exists
+ INF = c.get('Likely Email (inferred)')
+ if INF is None:
+ INF = max(c.values()) + 1
+ lib.batch_fill(tok, GID, [{"row0": 0, "col0": INF, "value": "Likely Email (inferred)"}])
+
+ # 1) learn per-company domain + pattern votes from KNOWN emails
+ dom = {}; votes = {}
+ for r in rows[1:]:
+ e = g(r, EMAIL).lower(); co = g(r, CO).lower(); nm = g(r, NAME)
+ m = re.search(r'([^@\s]+)@([\w.-]+)', e)
+ if not m or not co: continue
+ local, d = m.group(1), m.group(2).lower()
+ if d in FREE: continue
+ dom.setdefault(co, d)
+ f, l = parts(nm)
+ for t in detect(f, l, local):
+ votes.setdefault(co, {}).setdefault(t, 0)
+ votes[co][t] += 1
+ # dominant pattern per company
+ pat = {co: max(v.items(), key=lambda x: x[1])[0] for co, v in votes.items()}
+
+ # 2) infer for named contacts missing any email
+ cells = []; n_pat = 0; n_default = 0
+ for ri, r in enumerate(rows[1:], start=1):
+ nm = g(r, NAME)
+ if not nm or not nm[0].isalpha(): continue
+ if g(r, EMAIL) or g(r, UEM) or g(r, INF): continue # already has some email
+ co = g(r, CO).lower(); d = dom.get(co)
+ if not d: continue # no company domain -> can't infer
+ f, l = parts(nm)
+ if not f or not l: continue
+ if co in pat:
+ local = alpha(TEMPLATES[pat[co]](f, l)); n_pat += 1
+ else:
+ local = f"{f}.{l}"; n_default += 1 # generic fallback
+ cells.append({"row0": ri, "col0": INF, "value": f"{local}@{d}"})
+ lib.batch_fill(tok, GID, cells)
+ print(f"companies with a detected email pattern: {len(pat)}")
+ print(f"inferred emails written: {len(cells)} ({n_pat} from company pattern, {n_default} from first.last default)")
+ # show a few examples
+ for cell in cells[:8]:
+ print(" ", g(rows[cell['row0']], NAME), "->", cell['value'])
+
+if __name__ == "__main__":
+ main()
← afcba93 Fold 257 unenriched companies into the loop: append 312 cont
·
back to Rentv Sheet Enrich Refine
·
auto-data-snapshot: 2026-08-13T13:27:38 (3 data files) — dat 0d5bb2d →