Files
HOALedgerIQ_Website/agents/junior-ae/junior-ae-v4.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

191 lines
6.5 KiB
Python
Executable File

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