# campaigns/send_thanks.py import argparse from datetime import datetime from utils.tools import get_client, log_message CENTRE_NAME = "Lincoln Audi" # TODO: make configurable later def load_template(client, channel: str) -> str: resp = ( client.table("reply_template") .select("body") .eq("slug", "thanks_feedback") .eq("channel", channel) .eq("active", True) .limit(1) .execute() ) if not resp.data: raise RuntimeError( f"Template 'thanks_feedback' not found for channel '{channel}' or inactive" ) return resp.data[0]["body"] def render_template(body: str, first_name: str, centre_name: str) -> str: text = body.replace("{{first_name}}", first_name or "") text = text.replace("{{centre_name}}", centre_name or "") return text def fetch_thanks_queue(client, limit: int): # Uses the v_thanks_queue view we created in Supabase resp = ( client.table("v_thanks_queue") .select( "appt_id, agreement_id, agreement_number, first_name, status, " "created_at, thanks_needed, thanks_sent_at" ) .eq("thanks_needed", True) .is_("thanks_sent_at", None) .order("created_at", desc=False) .limit(limit) .execute() ) return resp.data or [] def mark_thanks_sent(client, appt_id: int, channel: str): now_iso = datetime.utcnow().isoformat() ( client.table("appointment_request") .update({"thanks_sent_at": now_iso, "thanks_needed": False}) .eq("id", appt_id) .execute() ) def send_thanks(channel: str, limit: int, dry_run: bool = False): client = get_client() print("=" * 80) print("💐 THANKS MESSAGE SENDER") print("=" * 80) print(f"Channel: {channel}") print(f"Limit: {limit}") if dry_run: print("🔍 DRY RUN MODE (no writes)") print() # Load template print("📝 Loading thanks_feedback template...") try: tmpl_body = load_template(client, channel) print(f" ✅ Template loaded ({len(tmpl_body)} chars)") except Exception as e: print(f" ❌ {e}") return # Fetch queue print() print(f"🔍 Fetching queue from v_thanks_queue (limit={limit})...") rows = fetch_thanks_queue(client, limit) print(f" Found {len(rows)} appointment(s) needing thanks") if not rows: print() print("â„šī¸ No thanks messages to send") print("=" * 80) return sent = 0 print() print("📤 Sending thanks...") print() for idx, row in enumerate(rows, start=1): appt_id = row["appt_id"] agreement_id = row["agreement_id"] agr_no = row.get("agreement_number") first_name = row.get("first_name") or "" print(f" [{idx}/{len(rows)}] {agr_no} ({first_name})") msg = render_template(tmpl_body, first_name, CENTRE_NAME) if dry_run: print(f" [DRY RUN] Would send: {msg[:120]}...") sent += 1 continue # Log outbound message try: log_message( agreement_id, channel, "outbound", message_text=msg, intent_label="post_appt_thanks", template_slug="thanks_feedback", source="ai", model=None, ) # Mark thanks sent on appointment mark_thanks_sent(client, appt_id, channel) print(" ✅ Sent and marked as thanked") sent += 1 except Exception as e: print(f" ❌ Error sending: {e}") print() print("=" * 80) print("✨ SUMMARY") print("=" * 80) print(f"Total appointments considered: {len(rows)}") print(f"Successfully processed: {sent}") if dry_run: print("âš ī¸ DRY RUN: No data was written to database") print("=" * 80) def main(): parser = argparse.ArgumentParser( description="Send post-appointment thank-you messages via WhatsApp" ) parser.add_argument( "--channel", default="whatsapp", help="Channel to send on (default: whatsapp)", ) parser.add_argument( "--limit", type=int, default=10, help="Maximum number of messages to send (default: 10)", ) parser.add_argument( "--dry-run", action="store_true", help="Preview messages without writing to the database", ) args = parser.parse_args() send_thanks(channel=args.channel, limit=args.limit, dry_run=args.dry_run) if __name__ == "__main__": main()