← back to Rentv Sheet Enrich Refine

split_phones.py

81 lines

#!/usr/bin/env python3
"""
split_phones.py — on every tab that has a combined "Phone(s)" column, parse the multiple
numbers out of each cell and distribute them into Phone 1 / Phone 2 / Phone 3 (/4) columns
(created at the right if missing). Each becomes a single number the console renders as a
tap-to-call tel: link. Quota-safe: one values.batchUpdate per tab. ADD-only: won't clobber
an existing Phone N value. Leaves the source "Phone(s)" column intact (hide it in the UI).
"""
import lib, json, re, urllib.request, urllib.parse, urllib.error, time
PHONE = re.compile(r'(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?:\s*(?:x|ext\.?)\s*\d+)?', re.I)
MAXK = 4

def req(method, url, tok, body=None):
    r = urllib.request.Request(url, data=(None if body is None else json.dumps(body).encode()),
        method=method, headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"})
    try:
        return json.load(urllib.request.urlopen(r))
    except urllib.error.HTTPError as e:
        raise RuntimeError(f"{e.code}: {e.read().decode()[:200]}")

def header_row(rows):
    best, bi = -1, 0
    for i in range(min(4, len(rows))):
        n = sum(1 for c in rows[i] if str(c).strip())
        if n > best: best, bi = n, i
    return bi

def nums(cell):
    return [m.group(0).strip() for m in PHONE.finditer(str(cell or ""))]

def main():
    tok = lib.access_token()
    for s in lib.get_meta(tok)["sheets"]:
        gid = s["properties"]["sheetId"]; title = s["properties"]["title"]
        rows = lib.read_tab(tok, title)
        if not rows: continue
        hr = header_row(rows)
        hdr = [str(c).strip() for c in rows[hr]]
        pi = next((i for i, h in enumerate(hdr) if h.lower() in ("phone(s)", "phones")), None)
        if pi is None: continue
        def g(r, i): return str(r[i]).strip() if i < len(r) else ""
        parsed = [nums(g(r, pi)) for r in rows]
        k = min(MAXK, max((len(p) for p in parsed[hr+1:]), default=0))
        if k == 0:
            print(f"  {title[:34]:34s} -> no parseable numbers"); continue
        # locate / append Phone 1..k columns
        width = max((len(r) for r in rows), default=len(hdr))
        right = width
        colidx = {}
        for j in range(1, k+1):
            label = f"Phone {j}"
            found = next((i for i, h in enumerate(hdr) if h.lower() == label.lower()), None)
            if found is None:
                colidx[j] = right; right += 1
            else:
                colidx[j] = found
        # widen grid if needed
        need = max(colidx.values()) + 1
        if need > width:
            req("POST", f"{lib.API}/{lib.SID}:batchUpdate", tok, {"requests": [{"appendDimension": {
                "sheetId": gid, "dimension": "COLUMNS", "length": need - width}}]})
        # build the data ranges (header + per-row) — ADD-only per existing value
        data = []
        for j in range(1, k+1):
            ci = colidx[j]; col = [[""] for _ in range(len(rows))]
            col[hr] = [f"Phone {j}"]
            for ri in range(hr+1, len(rows)):
                existing = g(rows[ri], ci)
                val = parsed[ri][j-1] if len(parsed[ri]) >= j else ""
                col[ri] = [existing if existing else val]
            colL = lib.col_letter(ci)
            data.append({"range": f"'{title}'!{colL}1:{colL}{len(col)}", "values": col})
        req("POST", f"{lib.API}/{lib.SID}/values:batchUpdate", tok,
            {"valueInputOption": "RAW", "data": data})
        filled = sum(1 for p in parsed[hr+1:] if p)
        print(f"  {title[:34]:34s} -> split into Phone 1..{k} ({filled} rows had numbers)")
        time.sleep(1.0)

if __name__ == "__main__":
    main()