#!/usr/bin/env python3 """ Junior AE v4 - Process ALL leads, auto-detect temperature - Processes notes with or without temperature prefixes - Auto-detects temperature from content if not in title - Elevates HOT/WARM leads, skips COLD """ import json, re, time, urllib.request, urllib.error from datetime import datetime, timedelta from pathlib import Path import ssl SCRIPT_DIR = Path(__file__).parent for d in [SCRIPT_DIR / "state", SCRIPT_DIR / "logs"]: d.mkdir(parents=True, exist_ok=True) STATE_FILE = SCRIPT_DIR / "state" / "jae-v4-state.json" LOG_FILE = SCRIPT_DIR / "logs" / f"jae-v4-{datetime.now().strftime('%Y%m%d')}.log" CRM_URL = "https://salesforce.hoaledgeriq.com/rest" CRM_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5M2FmNGFmNS0zZWQ0LTQ1ZDMtOWE5Zi01MDMzZjc3YTY3MjMiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiOTNhZjRhZjUtM2VkNC00NWQzLTlhOWYtNTAzM2Y3N2E2NzIzIiwiaWF0IjoxNzczMzI4NDQzLCJleHAiOjE4MDQ3ODE2NDIsImp0aSI6IjIwZjEyYzkwLTRkMDctNGJmNi1iMzk3LTZjNmU3MzlmMThjOCJ9.zeM5NvwCSGEcz99m2LYtgb0sVD6WUXcCF7SwonFg930" def log(msg): ts = datetime.now().strftime('%H:%M:%S') print(f"[{ts}] {msg}") with open(LOG_FILE, 'a') as f: f.write(f"[{ts}] {msg}\n") def load_state(): if STATE_FILE.exists(): return json.loads(STATE_FILE.read_text()) return {"last_check": (datetime.now() - timedelta(days=7)).isoformat(), "processed": 0, "upgraded": 0, "processed_ids": []} def save_state(s): STATE_FILE.write_text(json.dumps(s, indent=2)) def fetch_notes(): try: req = urllib.request.Request( f"{CRM_URL}/notes?limit=200&order[createdAt]=desc", headers={"Authorization": f"Bearer {CRM_TOKEN}", "Accept": "application/json"} ) with urllib.request.urlopen(req, timeout=15) as r: return json.loads(r.read().decode()).get('data', {}).get('notes', []) except Exception as e: log(f"Fetch error: {e}") return [] def detect_temp(title, body=""): """Detect temperature from title or content""" text = f"{title} {body}".upper() # Check for explicit temperature if 'HOT' in text or 'HIGH' in text or 'URGENT' in text: return 'HOT' if 'WARM' in text or 'MEDIUM' in text or 'INTERESTED' in text: return 'WARM' if 'COLD' in text or 'LOW' in text or 'NOT INTERESTED' in text: return 'COLD' # Auto-detect from engagement signals hot_signals = ['READY', 'INTERESTED', 'WANTS', 'NEEDS', 'BUDGET', 'TIMELINE', 'SOON', 'QUICK'] warm_signals = ['CONSIDERING', 'THINKING', 'MAYBE', 'LATER', 'RESEARCH', 'COMPARE'] for signal in hot_signals: if signal in text: return 'WARM' # Default to WARM if unsure for signal in warm_signals: if signal in text: return 'WARM' # Default to WARM for unclassified leads (better to over-qualify) return 'WARM' def update_note_temp(note_id, new_temp): """Update note title with temperature""" try: # Get current note req = urllib.request.Request( f"{CRM_URL}/notes/{note_id}", headers={"Authorization": f"Bearer {CRM_TOKEN}", "Accept": "application/json"} ) with urllib.request.urlopen(req, timeout=10) as r: note = json.loads(r.read().decode()).get('data', {}) # Update title old_title = note.get('title', '') new_title = re.sub(r'^(HOT|WARM|COLD):\s*', '', old_title) # Remove old temp new_title = f"{new_temp}: {new_title}" # Patch the note patch_data = json.dumps({"title": new_title}).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' ) with urllib.request.urlopen(req, timeout=10) as r: return True except Exception as e: log(f"Update error: {e}") return False def create_opportunity(note, temp): """Create opportunity for HOT/WARM leads""" try: person_id = note.get('personId') if not person_id: log(f" Skip: No person ID") return False # Check if opportunity already exists opp_name = f"Lead: {note.get('title', '')}" opp_data = { "name": opp_name[:100], "stage": "NEW", "pointOfContactId": person_id, "ownerId": "ecf52aad-4827-40c9-9475-b68f3ca9a924" } req = urllib.request.Request( f"{CRM_URL}/opportunities", data=json.dumps(opp_data).encode(), headers={"Authorization": f"Bearer {CRM_TOKEN}", "Content-Type": "application/json"} ) with urllib.request.urlopen(req, timeout=15) as r: opp = json.loads(r.read().decode()) log(f" ✓ UPGRADED to Opportunity: {opp.get('id', 'N/A')}") return True except Exception as e: log(f" ✗ Create opp error: {e}") return False def main(): log("=== JAE v4 Starting - Auto-Temperature Detection ===") state = load_state() processed_ids = state.get('processed_ids', []) notes = fetch_notes() log(f"Fetched {len(notes)} notes") upgraded = 0 processed = 0 for note in notes: note_id = note.get('id') title = note.get('title', '') # Skip if already processed if note_id in processed_ids: continue processed += 1 processed_ids.append(note_id) # Detect temperature body = note.get('body', '') temp = detect_temp(title, body) log(f"Processing: {title[:60]}... -> {temp}") # Update title with temperature if not title.startswith(f"{temp}:"): update_note_temp(note_id, temp) # Create opportunity for HOT/WARM if temp in ['HOT', 'WARM']: if create_opportunity(note, temp): upgraded += 1 else: log(f" Skipped: COLD lead") # Rate limit time.sleep(0.5) # Save state state['processed'] = processed state['upgraded'] = state.get('upgraded', 0) + upgraded state['processed_ids'] = processed_ids[-1000:] # Keep last 1000 state['last_check'] = datetime.now().isoformat() save_state(state) log(f"=== Done: {processed} processed, {upgraded} upgraded ===") log("Waiting 3 hours...") if __name__ == "__main__": main()