Update android_handler.py to support rule-based objection handling with Supabase templates. Requirements: 1) Imports - from supabase import create_client - import os, re, datetime as dt - from zoneinfo import ZoneInfo - from tools import ( resolve_agreement_id, log_message, open_thread, close_thread, schedule_followup, book_appt_request, get_equity_snapshot ) - Handle OPENAI_API_KEY optionally: if missing, skip OpenAI and use template-only replies. 2) New helpers a) get_client(): - Create a single Supabase client from SUPABASE_URL and SUPABASE_SERVICE_KEY. b) fetch_rules_and_templates(client) -> (rules, templates): - rules: select * from objection_rule order by priority asc; - templates: select slug, body from reply_template where is_active = true; - Return two dicts: rules = [{label, match_any, template_slug, priority}], templates = {slug: body}. c) fetch_customer_context(agreement_number) -> dict: - Query v_agreement_latest to get: first_name, last_name, registration, make, model, agreement_number. - Return a dict with keys: first_name, last_name, vehicle_model (combine make + model), registration, agreement_number. d) personalise(template_str, ctx) -> str: - Replace {{first_name}}, {{last_name}}, {{vehicle_model}}, {{registration}} safely (fallback to empty if missing). e) match_rule(text, rules) -> template_slug | None: - Lowercase text; for each rule in priority order, if ANY keyword in rule.match_any is a substring, return template_slug. - Use simple normalised matching (lowercase; strip punctuation). f) ok_to_reply(): - Use Europe/London time; return True if 06:00–23:59:59. 3) Reply logic (in the message processing loop) - After detecting a new inbound message: - ctx = fetch_customer_context(agreement_number) - rules, templates = fetch_rules_and_templates(client) - slug = match_rule(inbound_text, rules) - if slug and slug in templates: reply_text = personalise(templates[slug], ctx) else: # Fallback to OpenAI (if key present) using Lia’s UK system prompt # System prompt must emphasise UK English dealership tone and “no pricing promises”. # If OpenAI not available, reply_text = personalise(templates.get("ack_hello", "Hi {{first_name}}, it’s Lia at Lincoln Audi. How can I help?"), ctx) - Respect ok_to_reply(); if False, schedule_followup for next day 06:05 UK and do not send now (just log deferral). - Otherwise: - log_message(agreement_number, channel, "outbound", reply_text) 4) Lia’s system prompt (use for OpenAI path) - System: "You are Lia Carter, Customer Loyalty Advisor at Lincoln Audi. Always write in natural UK English with warm, conversational phrasing typical of a British dealership professional. Be helpful, relaxed and human. Never promise pricing or 'same payment'. Invite a short visit or a quick call if needed. Keep replies short." - User content: include the inbound_text plus a compact summary of ctx (first_name, vehicle_model, registration). 5) Minimal safety: - If template 'book_service' was used, DO NOT fake-book. Ask for rough mileage + preferred dates + courtesy-car. (We already have that text.) - If price-related keywords matched ('too_expensive'), DO NOT quote; invite short visit/review and offer to check options. 6) Console logs: - Print: matched rule label (or "no rule → OpenAI"), chosen template slug, and that outbound was logged. Deliverable: - Modify android_handler.py accordingly. - Do not remove existing polling/loop; just integrate this logic where replies are generated. - When done, print: UPDATED android_handler.py (rules + templates integrated)