79 lines
2.6 KiB
Python
Executable File
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())
|