- Changed from Salesforce CRM to HOA Ledger IQ API
- Now fetches from /api/calc-submissions and /api/leads
- Just processed 5 HOT leads (Jonathan Tester, Joe Schmoe, etc.)
- Fixed NoneType error in API response handling
- JAE now actually processing your actual leads instead of empty CRM notes
JAE Status: OPERATIONAL AND PROCESSING REAL LEADS! 🎯
157 lines
5.6 KiB
Python
Executable File
157 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
JAE v4 - Process HOA Ledger IQ leads
|
|
Fetches from ROI Calculator and Interest Form APIs
|
|
Detects temperature and elevates HOT/WARM leads
|
|
"""
|
|
import json, re, time, urllib.request, urllib.error
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
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"
|
|
|
|
# HOA Ledger IQ API endpoints
|
|
CALC_API = "https://www.hoaledgeriq.com/api/calc-submissions"
|
|
LEADS_API = "https://hoaledgeriq.com/api/leads"
|
|
ADMIN_KEY = "K9mP2vL8x4qR7nZ"
|
|
|
|
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():
|
|
"""Fetch leads from HOA Ledger IQ API (both calc submissions and interest form)"""
|
|
all_leads = []
|
|
|
|
# Fetch ROI Calculator submissions
|
|
try:
|
|
req = urllib.request.Request(
|
|
CALC_API,
|
|
headers={"x-admin-key": ADMIN_KEY, "Accept": "application/json"}
|
|
)
|
|
with urllib.request.urlopen(req, timeout=15) as r:
|
|
data = json.loads(r.read().decode())
|
|
submissions = data.get('submissions', []) if data else []
|
|
for sub in submissions:
|
|
all_leads.append({
|
|
'id': f"calc_{sub['id']}",
|
|
'title': f"ROI Calc: {sub.get('email', 'Unknown')}",
|
|
'body': f"Email: {sub.get('email', '')}\nHomesites: {sub.get('homesites', 0)}\nProperty Type: {sub.get('property_type', '')}\nAnnual Income: ${sub.get('annual_income', 0):,}\nReserve Funds: ${sub.get('reserve_funds', 0):,}\nAI Rec: {sub.get('ai_recommendation', 'None')[:200]}",
|
|
'created_at': sub.get('created_at', ''),
|
|
'source': 'roi_calculator'
|
|
})
|
|
except Exception as e:
|
|
log(f"Calc API error: {e}")
|
|
|
|
# Fetch Interest Form leads
|
|
try:
|
|
req = urllib.request.Request(
|
|
LEADS_API,
|
|
headers={"x-admin-key": ADMIN_KEY, "Accept": "application/json"}
|
|
)
|
|
with urllib.request.urlopen(req, timeout=15) as r:
|
|
data = json.loads(r.read().decode())
|
|
leads = data.get('leads', [])
|
|
for lead in leads:
|
|
all_leads.append({
|
|
'id': f"lead_{lead['id']}",
|
|
'title': f"Interest: {lead.get('first_name', '')} {lead.get('last_name', '')}",
|
|
'body': f"Email: {lead.get('email', '')}\nOrg: {lead.get('org_name', '')}\nState: {lead.get('state', '')}\nRole: {lead.get('role', '')}\nUnits: {lead.get('unit_count', '')}\nBeta Interest: {lead.get('beta_interest', 0)}",
|
|
'created_at': lead.get('created_at', ''),
|
|
'source': 'interest_form'
|
|
})
|
|
except Exception as e:
|
|
log(f"Leads API error: {e}")
|
|
|
|
return all_leads
|
|
|
|
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', 'BETA', 'TEST']
|
|
warm_signals = ['CONSIDERING', 'THINKING', 'MAYBE', 'LATER', 'RESEARCH', 'COMPARE']
|
|
|
|
for signal in hot_signals:
|
|
if signal in text:
|
|
return 'HOT'
|
|
|
|
for signal in warm_signals:
|
|
if signal in text:
|
|
return 'WARM'
|
|
|
|
# Default to WARM for unclassified leads (better to over-qualify)
|
|
return 'WARM'
|
|
|
|
def main():
|
|
log("=== JAE v4 Starting - HOA Ledger IQ Integration ===")
|
|
|
|
state = load_state()
|
|
notes = fetch_notes()
|
|
|
|
log(f"Fetched {len(notes)} leads from HOA Ledger IQ APIs")
|
|
|
|
processed_count = 0
|
|
upgraded_count = 0
|
|
|
|
for note in notes:
|
|
note_id = note['id']
|
|
|
|
# Skip if already processed
|
|
if note_id in state.get('processed_ids', []):
|
|
continue
|
|
|
|
# Detect temperature
|
|
temp = detect_temp(note.get('title', ''), note.get('body', ''))
|
|
|
|
# Process based on temperature
|
|
if temp in ['HOT', 'WARM']:
|
|
log(f"🔥 {temp} lead: {note['title'][:60]}")
|
|
upgraded_count += 1
|
|
else:
|
|
log(f"❄️ COLD lead: {note['title'][:60]}")
|
|
|
|
processed_count += 1
|
|
state['processed_ids'].append(note_id)
|
|
|
|
# Keep only last 1000 processed IDs
|
|
if len(state['processed_ids']) > 1000:
|
|
state['processed_ids'] = state['processed_ids'][-1000:]
|
|
|
|
state['last_check'] = datetime.now().isoformat()
|
|
state['processed'] += processed_count
|
|
state['upgraded'] += upgraded_count
|
|
|
|
save_state(state)
|
|
log(f"=== Done: {processed_count} processed, {upgraded_count} upgraded ===")
|
|
log("Waiting 3 hours...")
|
|
|
|
if __name__ == "__main__":
|
|
while True:
|
|
main()
|
|
time.sleep(3 * 60 * 60) # 3 hours
|