mz_chatbot / scripts /check_call_tz.py
Shurem's picture
added multiple changes
ecef140
Raw History Blame Contribute Delete
6.57 kB
"""
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())