Files
HOALedgerIQ_Website/agents/junior-ae/tier1-batch-updater.py
olsch01 5319bcd30b feat: Add Chatwoot Agent Bot prototype and FAQ knowledge base
- Created chatwoot-agent-bot/ with Node.js webhook server
- Bot detects intent (greeting, billing, technical, features, account)
- Auto-responds from FAQ knowledge base or escalates to human
- FAQ-KB.md: Living knowledge base that grows with customer questions
- CHATWOOT-SETUP.md: Complete deployment and configuration guide
- Supports Telegram notifications on escalation
- Bot runs on port 3001, ready for Chatwoot webhook integration
2026-04-01 16:26:05 -04:00

200 lines
6.0 KiB
Python

#!/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()