zangir commited on
Commit
3fd9b4e
·
verified ·
1 Parent(s): 4c2e3f0

Importer: guess names from addresses

Browse files
Files changed (1) hide show
  1. backend/app/importer.py +19 -1
backend/app/importer.py CHANGED
@@ -45,6 +45,19 @@ def _first_email(*cells) -> tuple[str, str]:
45
  return "", ("bad_email" if seen_any else "no_email")
46
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  def _split_keywords(s: str) -> list[str]:
49
  out = []
50
  for k in re.split(r"[;,\n|/]+", s):
@@ -92,7 +105,7 @@ def parse_workbook(data: bytes, classify, country_code, suppressed: set[str] = f
92
  """-> (records, report). `classify(text, body) -> [field]`, `country_code(text) -> ISO or ''` are passed in so this module
93
  stays free of the rest of the app. `suppressed` and `existing` are sets of lower-case e-mails to leave alone."""
94
  wb = load_workbook(io.BytesIO(data), read_only=True, data_only=True)
95
- report = {"sheets": {}, "rows": 0, "no_email": [], "bad_email": [], "no_name": 0, "merged": 0, "already_accounts": 0, "suppressed": 0}
96
  by_email: dict[str, dict] = {}
97
  for ws in wb.worksheets:
98
  n = 0
@@ -100,6 +113,9 @@ def parse_workbook(data: bytes, classify, country_code, suppressed: set[str] = f
100
  n += 1
101
  report["rows"] += 1
102
  email, why = _first_email(*r["email_cells"])
 
 
 
103
  if not r["name"] or len(r["name"]) < 2:
104
  report["no_name"] += 1
105
  continue
@@ -112,6 +128,8 @@ def parse_workbook(data: bytes, classify, country_code, suppressed: set[str] = f
112
  if email in existing:
113
  report["already_accounts"] += 1
114
  continue
 
 
115
  rec = by_email.get(email)
116
  if rec:
117
  report["merged"] += 1
 
45
  return "", ("bad_email" if seen_any else "no_email")
46
 
47
 
48
+ GENERIC = {"info", "admin", "office", "contact", "mail", "hello", "team", "support", "secretary", "noreply", "no-reply", "sales", "press"}
49
+
50
+
51
+ def _name_from_email(email: str) -> str:
52
+ """A person with an address but no name in the sheet still gets an invitation; the name is a best guess from the part
53
+ before the @ ("dias.omarov" -> "Dias Omarov") and they correct it when they claim the profile. Shared mailboxes are not people."""
54
+ local = email.split("@")[0].lower()
55
+ if local in GENERIC:
56
+ return ""
57
+ parts = [p for p in re.split(r"[._\-+]+", re.sub(r"\d+", "", local)) if len(p) > 1]
58
+ return " ".join(p.capitalize() for p in parts[:3]) if len(parts) >= 1 and len(" ".join(parts)) >= 3 else ""
59
+
60
+
61
  def _split_keywords(s: str) -> list[str]:
62
  out = []
63
  for k in re.split(r"[;,\n|/]+", s):
 
105
  """-> (records, report). `classify(text, body) -> [field]`, `country_code(text) -> ISO or ''` are passed in so this module
106
  stays free of the rest of the app. `suppressed` and `existing` are sets of lower-case e-mails to leave alone."""
107
  wb = load_workbook(io.BytesIO(data), read_only=True, data_only=True)
108
+ report = {"sheets": {}, "rows": 0, "no_email": [], "bad_email": [], "no_name": 0, "merged": 0, "already_accounts": 0, "suppressed": 0, "name_from_email": 0}
109
  by_email: dict[str, dict] = {}
110
  for ws in wb.worksheets:
111
  n = 0
 
113
  n += 1
114
  report["rows"] += 1
115
  email, why = _first_email(*r["email_cells"])
116
+ guessed = False
117
+ if (not r["name"] or len(r["name"]) < 2) and email:
118
+ r["name"], guessed = _name_from_email(email), True
119
  if not r["name"] or len(r["name"]) < 2:
120
  report["no_name"] += 1
121
  continue
 
128
  if email in existing:
129
  report["already_accounts"] += 1
130
  continue
131
+ if guessed:
132
+ report["name_from_email"] += 1
133
  rec = by_email.get(email)
134
  if rec:
135
  report["merged"] += 1