Files
clientflow_backend/scripts/review_domain_auto_applied_suggestions.py
2026-06-09 22:55:58 +01:00

90 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""List/revert risky fiscal suggestions auto-applied from domain-only matches."""
from __future__ import annotations
import argparse
import json
from sqlalchemy import text
from app.db import engine
DOMAIN_MATCHES = (
"email_principal_dominio",
"email_dominio_empresa_associada",
"contacto_email_dominio",
"dominio",
"email_dominio",
"website_dominio",
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--apply", action="store_true", help="revert accepted domain-only suggestions and unlink matching opportunity customer")
args = parser.parse_args()
with engine.begin() as conn:
rows = conn.execute(text("""
SELECT
s.id::text,
s.opportunity_id::text,
s.suggested_customer_id::text,
s.suggested_name,
s.suggested_nif,
s.match_type,
s.confidence,
o.customer_name,
o.customer_email,
o.local_customer_id::text AS current_customer_id
FROM fiscal_customer_suggestions s
JOIN opportunities o ON o.id = s.opportunity_id
WHERE s.status = 'accepted'
AND s.auto_applied = TRUE
AND s.match_type = ANY(:matches)
ORDER BY s.updated_at DESC
"""), {"matches": list(DOMAIN_MATCHES)}).mappings().all()
print(f"Sugestões por domínio auto-aplicadas: {len(rows)}")
for r in rows:
print(f"- {r['customer_name']} <{r['customer_email']}> -> {r['suggested_name']} / {r['suggested_nif']} | {r['match_type']} | {r['confidence']} | opp={r['opportunity_id']}")
if not args.apply:
print("Dry-run. Para reverter: repetir com --apply")
return
updated = 0
with engine.begin() as conn:
for r in rows:
# Only unlink if the opportunity is still linked to the same customer suggested by this risky suggestion.
if r["suggested_customer_id"] and r["current_customer_id"] == r["suggested_customer_id"]:
conn.execute(text("""
UPDATE opportunities
SET local_customer_id = NULL,
metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object(
'domain_match_auto_apply_reverted', true,
'domain_match_reverted_suggestion_id', :suggestion_id,
'domain_match_reverted_customer_name', :customer_name,
'domain_match_reverted_at', now()
),
updated_at = now()
WHERE id = CAST(:opportunity_id AS UUID)
"""), {
"opportunity_id": r["opportunity_id"],
"suggestion_id": r["id"],
"customer_name": r["suggested_name"],
})
conn.execute(text("""
UPDATE fiscal_customer_suggestions
SET status = 'rejected', auto_applied = FALSE, resolved_by = 'domain_match_safety_review',
resolved_at = now(), reason = COALESCE(reason, '') || ' | reverted: domain-only auto-apply is unsafe',
updated_at = now()
WHERE id = CAST(:id AS UUID)
"""), {"id": r["id"]})
updated += 1
print(f"Revertidas: {updated}")
if __name__ == "__main__":
main()