#!/usr/bin/env python3 """ Tier 1 Batch Updater - Rate Limited - Updates CRM 'tier' field with "Tier 1", "Tier 2", etc. - Processes 1 lead per second to avoid rate limits - Runs in background, updates state """ import json, re, time, urllib.request, ssl from datetime import datetime from pathlib import Path SCRIPT_DIR = Path(__file__).parent STATE_FILE = SCRIPT_DIR / "state" / "tier1-batch-state.json" CRM_URL = "https://salesforce.hoaledgeriq.com/rest" CRM_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5M2FmNGFmNS0zZWQ0LTQ1ZDMtOWE5Zi01MDMzZjc3YTY3MjMiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiOTNhZjRhZjUtM2VkNC00NWQzLTlhOWYtNTAzM2Y3N2E2NzIzIiwiaWF0IjoxNzczMzI4NDQzLCJleHAiOjE4MDQ3ODE2NDIsImp0aSI6IjIwZjEyYzkwLTRkMDctNGJmNi1iMzk3LTZjNmU3MzlmMThjOCJ9.zeM5NvwCSGEcz99m2LYtgb0sVD6WUXcCF7SwonFg930" ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE def log(msg): ts = datetime.now().strftime('%H:%M:%S') print(f"[{ts}] {msg}") def load_state(): if STATE_FILE.exists(): return json.loads(STATE_FILE.read_text()) return {"processed_ids": [], "batch_start": None} def save_state(s): STATE_FILE.write_text(json.dumps(s, indent=2)) def get_tier_label(score): """Convert score to tier label (CRM format)""" if score >= 8: return "TIER_1" elif score >= 6: return "TIER_2" elif score >= 4: return "TIER_3" else: return "TIER_4" def score_lead(note): """Score a lead 1-10 based on criteria""" temp = note.get('temp', 'COLD') if not temp or temp not in ['HOT', 'WARM', 'COLD']: temp = 'COLD' title = note.get('title', '').upper() if title.startswith('HOT:'): temp = 'HOT' elif title.startswith('WARM:'): temp = 'WARM' body = note.get('bodyV2', {}).get('markdown', '') if isinstance(note.get('bodyV2'), dict) else '' title = note.get('title', '') text = f"{title} {body}".lower() # Extract units units = None for pattern in [r'units:\s*(\d{1,4})', r'(\d{1,4})\s*(?:homes|units|lots)', r'community\s*of\s*(\d{1,4})']: match = re.search(pattern, text, re.IGNORECASE) if match: try: units = int(match.group(1)) if 10 <= units <= 5000: break except: pass # Check budget has_budget = 'budget pdf' in text or 'budget.pdf' in text or ('budget' in text and 'found' in text) has_site = 'https://' in text or 'http://' in text score = 0 # Temperature (max 3) if temp == 'HOT': score += 3 elif temp == 'WARM': score += 2 # Units (max 4) if units: if 150 <= units <= 400: score += 4 elif 100 <= units < 150 or 400 < units <= 500: score += 3 elif 50 <= units < 100 or 500 < units <= 1000: score += 2 else: score += 1 # Budget (max 2) if has_budget: score += 2 elif has_site: score += 1 # Website (max 1) if has_site: score += 1 return score def update_crm_tier(note_id, tier_label): """Update CRM note with tier field""" try: patch_data = json.dumps({ "tier": tier_label }).encode() req = urllib.request.Request( f"{CRM_URL}/notes/{note_id}", data=patch_data, headers={ "Authorization": f"Bearer {CRM_TOKEN}", "Content-Type": "application/json" }, method='PATCH' ) opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=ssl_context)) with opener.open(req, timeout=20) as r: return True except Exception as e: log(f" ✗ Update failed: {e}") return False def fetch_recent_notes(limit=200): """Fetch recent notes""" try: req = urllib.request.Request( f"{CRM_URL}/notes?limit={limit}&order[createdAt]=desc", headers={"Authorization": f"Bearer {CRM_TOKEN}", "Accept": "application/json"} ) opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=ssl_context)) with opener.open(req, timeout=30) as r: data = json.loads(r.read().decode()) return data.get('data', {}).get('notes', []) except Exception as e: log(f"Fetch error: {e}") return [] def main(): log("=" * 60) log("Tier 1 Batch Updater - Starting") log("=" * 60) state = load_state() processed_ids = set(state.get('processed_ids', [])) if not state.get('batch_start'): state['batch_start'] = datetime.now().isoformat() save_state(state) # Fetch recent notes notes = fetch_recent_notes(500) log(f"Fetched {len(notes)} recent notes") updated = 0 skipped = 0 for i, note in enumerate(notes): note_id = note.get('id') title = note.get('title', '')[:50] # Skip if already processed if note_id in processed_ids: skipped += 1 continue # Score the lead score = score_lead(note) tier_label = get_tier_label(score) # Update CRM if update_crm_tier(note_id, tier_label): log(f" ✓ [{score}/10] {tier_label}: {title}") updated += 1 # Save state after each update processed_ids.add(note_id) state['processed_ids'] = list(processed_ids)[-25000:] state['last_updated'] = datetime.now().isoformat() save_state(state) # Rate limit: 1 second between updates time.sleep(1) log("\n" + "=" * 60) log(f"Batch Complete!") log(f" Updated: {updated}") log(f" Skipped: {skipped}") log(f" Total processed: {len(processed_ids)}") log("=" * 60) if __name__ == "__main__": main()