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
This commit is contained in:
124
agents/junior-ae/junior-ae.py
Normal file
124
agents/junior-ae/junior-ae.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Junior AE - Lead Validation & Temperature Optimization"""
|
||||
import json, re, time, urllib.request
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
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-state.json"
|
||||
LOG_FILE = SCRIPT_DIR / "logs" / f"jae-{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(hours=2)).isoformat(), "processed": 0, "upgraded": 0}
|
||||
|
||||
def save_state(s):
|
||||
STATE_FILE.write_text(json.dumps(s, indent=2))
|
||||
|
||||
def fetch_notes():
|
||||
try:
|
||||
with urllib.request.urlopen(urllib.request.Request(
|
||||
f"{CRM_URL}/notes?limit=50&order[createdAt]=desc",
|
||||
headers={"Authorization": f"Bearer {CRM_TOKEN}", "Accept": "application/json"}
|
||||
), 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 get_temp(title):
|
||||
t = title.upper()
|
||||
if 'HOT' in t: return 'HOT'
|
||||
if 'WARM' in t: return 'WARM'
|
||||
if 'COLD' in t: return 'COLD'
|
||||
return None
|
||||
|
||||
def extract_url(body):
|
||||
m = re.search(r'Site:\s*(https?://[^\s\n]+)', str(body))
|
||||
return m.group(1) if m else None
|
||||
|
||||
def validate_url(url):
|
||||
if not url: return False, "no_url"
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}, method='HEAD')
|
||||
with urllib.request.urlopen(req, timeout=10, context=ssl._create_unverified_context()) as r:
|
||||
return True, str(r.getcode())
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in [200, 201, 301, 302]: return True, str(e.code)
|
||||
return False, str(e.code)
|
||||
except Exception as e:
|
||||
return False, str(e)[:30]
|
||||
|
||||
def upgrade(temp):
|
||||
return {'COLD': 'WARM', 'WARM': 'HOT', 'HOT': 'HOT'}.get(temp, temp)
|
||||
|
||||
def update_note(note_id, body, new_temp, status):
|
||||
try:
|
||||
body = body + f"\n\n**JAE Validated:** {datetime.now().strftime('%Y-%m-%d %H:%M')}\n**New Temp:** {new_temp}\n**Status:** {status}"
|
||||
data = json.dumps({"bodyV2": {"markdown": body}}).encode()
|
||||
urllib.request.urlopen(urllib.request.Request(
|
||||
f"{CRM_URL}/notes/{note_id}",
|
||||
headers={"Authorization": f"Bearer {CRM_TOKEN}", "Content-Type": "application/json"},
|
||||
data=data, method='PUT'
|
||||
), timeout=10)
|
||||
return True
|
||||
except Exception as e:
|
||||
log(f"Update failed: {e}")
|
||||
return False
|
||||
|
||||
def process():
|
||||
s = load_state()
|
||||
log(f"=== JAE Starting ===")
|
||||
|
||||
notes = fetch_notes()
|
||||
for note in notes:
|
||||
if '__JAE_Validated__' in note.get('bodyV2', {}).get('markdown', ''):
|
||||
continue
|
||||
|
||||
title = note.get('title', '')
|
||||
body = note.get('bodyV2', {}).get('markdown', '')
|
||||
note_id = note.get('id')
|
||||
|
||||
temp = get_temp(title)
|
||||
if not temp:
|
||||
continue
|
||||
|
||||
url = extract_url(body)
|
||||
is_valid, status = validate_url(url)
|
||||
|
||||
if is_valid and temp != 'HOT':
|
||||
new_temp = upgrade(temp)
|
||||
log(f"UPGRADE: {title[:40]}... {temp}->{new_temp}")
|
||||
if update_note(note_id, body, new_temp, status):
|
||||
s['upgraded'] += 1
|
||||
s['processed'] += 1
|
||||
else:
|
||||
log(f"Verified: {title[:40]}... {temp} (valid={is_valid})")
|
||||
s['processed'] += 1
|
||||
|
||||
s['last_check'] = datetime.now().isoformat()
|
||||
save_state(s)
|
||||
log(f"=== Done: {s['processed']} processed, {s['upgraded']} upgraded ===")
|
||||
|
||||
def main():
|
||||
while True:
|
||||
process()
|
||||
log("Waiting 3 hours...")
|
||||
time.sleep(10800)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user