Files
clientflow_backend/scripts/audit_contact_identity_collisions.py

79 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Audit possible polluted Chatwoot contact names in pending work items.
Read-only. It highlights sender names reused across several different sender
emails, which can make the Operator Workbench display the wrong person name.
"""
from __future__ import annotations
import re
from collections import defaultdict
from sqlalchemy import text
from app.db import engine
def norm(value: object) -> str:
return " ".join(re.sub(r"[^0-9a-zA-ZÀ-ÿ]+", " ", str(value or "").casefold()).split())
SQL = """
SELECT
t.id::text AS task_id,
t.created_at,
t.status,
t.action_code,
t.conversation_id,
COALESCE(re.payload->'sender'->>'name', '') AS sender_name,
COALESCE(re.payload->'sender'->>'email', '') AS sender_email,
COALESCE(o.title, '') AS opportunity_title,
COALESCE(cu.name, '') AS fiscal_customer_name
FROM tasks t
LEFT JOIN raw_events re ON re.id = t.raw_event_id
LEFT JOIN opportunities o ON o.id = t.opportunity_id
LEFT JOIN customers cu ON cu.id = o.local_customer_id
WHERE t.status = 'pending'
AND t.source_system = 'chatwoot'
ORDER BY t.created_at DESC
LIMIT :limit
"""
def main() -> int:
with engine.begin() as conn:
rows = [dict(r) for r in conn.execute(text(SQL), {"limit": 250}).mappings().all()]
by_name: dict[str, list[dict]] = defaultdict(list)
for row in rows:
key = norm(row.get("sender_name"))
if key:
by_name[key].append(row)
suspects = []
for key, items in by_name.items():
emails = {str(i.get("sender_email") or "").casefold() for i in items if i.get("sender_email")}
if len(emails) > 1:
suspects.append((key, emails, items))
print("Contact identity collision audit")
print("=" * 80)
print(f"pending_chatwoot_tasks={len(rows)}")
print(f"suspect_reused_names={len(suspects)}")
print()
for key, emails, items in sorted(suspects, key=lambda x: (-len(x[1]), x[0])):
display = items[0].get("sender_name") or key
print("=" * 80)
print(f"sender_name={display!r} reused_with_{len(emails)}_emails")
for item in items[:20]:
print(
f"- task={item['task_id']} conv={item.get('conversation_id')} "
f"email={item.get('sender_email') or '-'} action={item.get('action_code')} "
f"opportunity={item.get('opportunity_title') or '-'} fiscal={item.get('fiscal_customer_name') or '-'}"
)
return 1 if suspects else 0
if __name__ == "__main__":
raise SystemExit(main())