138 lines
4.9 KiB
Python
138 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Read-only: clasifica la incoherencia Canal de Origen / Fuente de Prospecto.
|
|
|
|
Detecta en SUCURSALES los dos patrones incoherentes y, para cada contacto,
|
|
lee ``createdBy.source`` EN VIVO (solo viene en el GET individual) para decidir
|
|
la verdad del lead:
|
|
|
|
Patron A: Canal=SUCURSAL & Fuente=LEAD DIGITAL (viola AGENTS Cap.3)
|
|
- createdBy in {WEB_USER, MOBILE_USER} -> manual sucursal -> arreglar FUENTE (->SUCURSAL)
|
|
- otro (INTEGRATION/form/etc.) -> lead digital -> arreglar CANAL (->FORMULARIO/FACEBOOK)
|
|
Patron B: Fuente=REDES SOCIALES -> lead digital -> Canal=FACEBOOK + Fuente=LEAD DIGITAL
|
|
|
|
No escribe nada. Imprime la particion y el plan por contacto.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
from collections import Counter
|
|
|
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
if ROOT_DIR not in sys.path:
|
|
sys.path.insert(0, ROOT_DIR)
|
|
|
|
import paths # noqa: E402
|
|
from tag_canal_origen_workflow import ( # noqa: E402
|
|
MAIN_LOCATION_ID,
|
|
contact_display_name,
|
|
ghl_request,
|
|
load_locations,
|
|
)
|
|
from canal_origen_resolver import classify_source # noqa: E402
|
|
|
|
USER_SOURCES = {"WEB_USER", "MOBILE_USER"}
|
|
|
|
|
|
def cfval(custom_fields, fid):
|
|
for f in custom_fields or []:
|
|
if f.get("id") == fid:
|
|
for k in ("value", "fieldValue", "fieldValueString"):
|
|
if f.get(k) is not None:
|
|
return f[k]
|
|
return None
|
|
|
|
|
|
def field_maps(conn, location_id):
|
|
m = {}
|
|
for r in conn.execute(
|
|
"select field_id, field_name from object_schemas where location_id=? and object_key='contact'",
|
|
(location_id,),
|
|
):
|
|
m[r[1].strip().lower()] = r[0]
|
|
return m
|
|
|
|
|
|
def incoherent_from_cache(conn, location_id):
|
|
"""Devuelve [(contact_id, patron, canal, fuente)] desde la cache."""
|
|
fm = field_maps(conn, location_id)
|
|
canal_id = fm.get("canal de origen")
|
|
fuente_id = fm.get("fuente de prospecto")
|
|
out = []
|
|
for r in conn.execute(
|
|
"select id, custom_fields_json from contacts where location_id=?",
|
|
(location_id,),
|
|
):
|
|
cf = json.loads(r[1] or "[]")
|
|
canal = cfval(cf, canal_id)
|
|
fuente = cfval(cf, fuente_id)
|
|
if canal == "SUCURSAL" and fuente == "LEAD DIGITAL":
|
|
out.append((r[0], "A", canal, fuente))
|
|
elif fuente == "REDES SOCIALES":
|
|
out.append((r[0], "B", canal, fuente))
|
|
return out
|
|
|
|
|
|
def get_contact_full(contact_id, token):
|
|
data = ghl_request("GET", f"/contacts/{contact_id}", token)
|
|
inner = data.get("contact")
|
|
return inner if isinstance(inner, dict) else data
|
|
|
|
|
|
def main():
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
parser = argparse.ArgumentParser(description="Audit read-only de incoherencia origen/fuente")
|
|
parser.add_argument("--all", action="store_true", help="Todas las sucursales productivas")
|
|
parser.add_argument("--location", help="Una location especifica")
|
|
args = parser.parse_args()
|
|
|
|
accounts = load_locations(include_main=False)
|
|
accounts = [a for a in accounts if a["location_id"] != MAIN_LOCATION_ID]
|
|
if args.location:
|
|
accounts = [a for a in accounts if a["location_id"] == args.location]
|
|
elif not args.all:
|
|
raise SystemExit("Usa --all o --location <id>")
|
|
|
|
conn = sqlite3.connect(paths.DB_PATH)
|
|
|
|
grand = Counter()
|
|
for acc in accounts:
|
|
loc = acc["location_id"]
|
|
token = acc["token"]
|
|
targets = incoherent_from_cache(conn, loc)
|
|
if not targets:
|
|
continue
|
|
print(f"\n{'='*70}\n{acc['nombre']} ({loc}) - {len(targets)} incoherentes\n{'='*70}")
|
|
for cid, patron, canal, fuente in targets:
|
|
full = get_contact_full(cid, token)
|
|
created = (full.get("createdBy") or {}).get("source")
|
|
src = full.get("source")
|
|
name = contact_display_name(full)
|
|
if patron == "B":
|
|
plan = "Canal->FACEBOOK + Fuente->LEAD DIGITAL"
|
|
bucket = "B_redes->digital"
|
|
else:
|
|
if created in USER_SOURCES:
|
|
plan = "Fuente->SUCURSAL (manual sucursal)"
|
|
bucket = "A_manual->fuente"
|
|
else:
|
|
# createdBy no-usuario => digital; canal segun source
|
|
src_tag = classify_source(src)
|
|
canal_target = "FACEBOOK" if src_tag == "facebook-ads" else "FORMULARIO"
|
|
plan = f"Canal->{canal_target} (digital)"
|
|
bucket = f"A_digital->canal({canal_target})"
|
|
grand[bucket] += 1
|
|
print(f" {name:35.35} | createdBy={created or '-':12} source={src or '-':12} | {plan}")
|
|
|
|
print(f"\n{'='*70}\nRESUMEN GLOBAL (plan)\n{'='*70}")
|
|
for k, v in grand.most_common():
|
|
print(f" {v:4} {k}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|