Spaces:
Running
Running
File size: 6,566 Bytes
ecef140 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | """
check_call_tz.py — what timezone does validation.call_timezone() give for a
number, and how many of the CRM's real leads resolve to a zone at all?
Read-only. Two parts:
1. A few known numbers with the zone they SHOULD map to (PASS/FAIL).
2. Every lead in the DB: how many resolve to a timezone, the distribution,
and a sample of the ones that come back unknown (toll-free, area codes
spanning two zones, no country).
uv run python scripts/check_call_tz.py
uv run python scripts/check_call_tz.py --unknown 30 # print more unknown rows
uv run python scripts/check_call_tz.py --sample 20 # cross-check real leads by eye
--sample N picks N random leads that resolved to a timezone and prints, for each,
what libphonenumber's OWN geography database says the number belongs to (city /
state, from the area code) next to the timezone we derived and the local time
right now -- so you can check "212 -> New York -> 6:04 AM" against a world clock
yourself instead of trusting the code.
"""
from __future__ import annotations
import argparse
import random
import sys
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from sqlalchemy import select # noqa: E402
from app.database import SessionLocal # noqa: E402
from app.models import Lead # noqa: E402
from app.services.validation import call_timezone # noqa: E402
# (phone, country hint, expected zone or "" for unknown)
KNOWN = [
("+1 214-555-0142", "US", "America/Chicago"), # Dallas
("+1 212-555-0142", "US", "America/New_York"), # New York
("+1 310-555-0142", "US", "America/Los_Angeles"), # Los Angeles
("+1 604-555-0142", "CA", "America/Vancouver"), # Vancouver
("+44 20 7946 0958", "GB", "Europe/London"),
("+971 4 555 0142", "AE", "Asia/Dubai"),
("+1 800-555-0142", "US", ""), # toll-free, US multi-zone -> unknown
("+1 800-555-0142", "", ""),
("", "GB", "Europe/London"), # no number, single-zone country
("", "US", ""),
]
def _print_sample(leads, n: int) -> None:
import phonenumbers
from phonenumbers import geocoder
try:
from zoneinfo import ZoneInfo
except ImportError: # pragma: no cover
ZoneInfo = None # type: ignore[assignment]
pool = [l for l in leads if call_timezone(l.contact_phone or "", l.phone_country or l.country or "")]
picks = random.sample(pool, min(n, len(pool)))
now_utc = datetime.now(timezone.utc)
print(f"\n3. Cross-check sample ({len(picks)} random leads). UTC now: {now_utc:%Y-%m-%d %H:%M}")
print(" Compare 'number belongs to' and 'local time' with a world clock / a quick search.")
warned = False
for lead in picks:
raw = (lead.contact_phone or "").split(",")[0].strip()
if raw.startswith("00"):
raw = "+" + raw[2:]
region = (lead.phone_country or lead.country or "").upper() or None
where = "?"
try:
parsed = phonenumbers.parse(raw, None if raw.startswith("+") else region)
where = geocoder.description_for_number(parsed, "en") or "(country only)"
except Exception:
pass
tz = call_timezone(lead.contact_phone or "", lead.phone_country or lead.country or "")
local = "?"
if ZoneInfo is not None:
try:
t = now_utc.astimezone(ZoneInfo(tz))
weekday = t.strftime("%a")
is_open = weekday not in ("Sat", "Sun") and 9 <= t.hour < 17
local = f"{t:%a %H:%M} {'OPEN ' if is_open else 'closed'}"
except Exception:
if not warned:
print(" (Windows has no IANA timezone data for Python: run uv pip install tzdata to see local times here)")
warned = True
print(f" #{lead.id:<5} {lead.company_name[:26]:26} {raw[:19]:19} belongs to: {where[:22]:22} tz={tz:20} local={local}")
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--unknown", type=int, default=15, help="how many unknown-zone leads to print")
ap.add_argument("--sample", type=int, default=0, help="cross-check N random leads (geography + local time)")
args = ap.parse_args()
print("1. Known numbers")
bad = 0
for phone, country, want in KNOWN:
got = call_timezone(phone, country)
ok = got == want
bad += 0 if ok else 1
print(f" {'PASS' if ok else 'FAIL'} {phone or '(none)':20} {country or '-':3} -> {got or 'unknown':22} (want {want or 'unknown'})")
db = SessionLocal()
try:
leads = list(db.scalars(select(Lead)).all())
finally:
db.close()
zones: Counter = Counter()
unknown: list[Lead] = []
for lead in leads:
tz = call_timezone(lead.contact_phone or "", lead.phone_country or lead.country or "")
zones[tz or "(unknown)"] += 1
if not tz:
unknown.append(lead)
total = len(leads)
known = total - len(unknown)
no_phone = sum(1 for l in unknown if not (l.contact_phone or "").strip())
has_phone_total = sum(1 for l in leads if (l.contact_phone or "").strip())
print(f"\n2. {total} lead(s) in the CRM: {known} resolve to a timezone, {len(unknown)} unknown"
+ (f" ({100 * known // total}% known)" if total else ""))
print(f" of the {len(unknown)} unknown: {no_phone} have NO phone number at all, "
f"{len(unknown) - no_phone} have a number but no single timezone (toll-free / multi-zone area code)")
print(f" leads with any phone number: {has_phone_total} of {total}"
+ (f" ({100 * has_phone_total // total}%)" if total else ""))
for tz, n in zones.most_common():
print(f" {n:>5} {tz}")
if unknown:
print(f"\n Unknown sample (first {min(args.unknown, len(unknown))}):")
for lead in unknown[: args.unknown]:
print(
f" #{lead.id:<5} {lead.company_name[:32]:32} phone={lead.contact_phone[:22]:22} "
f"country={lead.country or '-':3} phone_country={lead.phone_country or '-'}"
)
if args.sample:
_print_sample(leads, args.sample)
print("\nRESULT:", "all known-number checks passed" if not bad else f"{bad} known-number check(s) FAILED")
return 0 if not bad else 1
if __name__ == "__main__":
sys.exit(main())
|