LRAG / database.py
jarvisemitra
Fix Unicode encoding issue in database.py
c230e25
Raw
History Blame Contribute Delete
10.6 kB
"""
database.py β€” SQLite CRM Schema + All DB Operations
Free Tier: SQLite (100% free, no signup needed)
"""
import sqlite3
import os
from datetime import datetime
DB_PATH = os.path.join(os.path.dirname(__file__), "crm.db")
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # lets you access columns by name
return conn
def init_db():
"""Create all tables if they don't exist."""
conn = get_connection()
c = conn.cursor()
# ── LEADS TABLE ──────────────────────────────────────────────
c.execute("""
CREATE TABLE IF NOT EXISTS leads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
business_name TEXT NOT NULL,
owner_name TEXT,
business_type TEXT, -- restaurant / startup / agency / creator / shop
city TEXT,
website TEXT,
whatsapp TEXT,
email TEXT,
instagram TEXT,
linkedin TEXT,
google_reviews INTEGER DEFAULT 0,
ig_followers INTEGER DEFAULT 0,
score INTEGER DEFAULT 0,
status TEXT DEFAULT 'new', -- new / contacted / replied / qualified / closed
preferred_channel TEXT,
notes TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)
""")
# ── MESSAGES TABLE ────────────────────────────────────────────
c.execute("""
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
lead_id INTEGER NOT NULL,
channel TEXT NOT NULL, -- whatsapp / email / instagram / linkedin
direction TEXT NOT NULL, -- outbound / inbound
content TEXT NOT NULL,
status TEXT DEFAULT 'draft', -- draft / sent / delivered / read / replied
sent_at TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (lead_id) REFERENCES leads(id)
)
""")
# ── FOLLOW-UPS TABLE ──────────────────────────────────────────
c.execute("""
CREATE TABLE IF NOT EXISTS followups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
lead_id INTEGER NOT NULL,
scheduled TEXT NOT NULL, -- datetime string
channel TEXT NOT NULL,
message TEXT NOT NULL,
done INTEGER DEFAULT 0, -- 0=pending, 1=done
FOREIGN KEY (lead_id) REFERENCES leads(id)
)
""")
# ── SCORES TABLE ─────────────────────────────────────────────
c.execute("""
CREATE TABLE IF NOT EXISTS scores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
lead_id INTEGER NOT NULL UNIQUE,
website_score INTEGER DEFAULT 0,
social_score INTEGER DEFAULT 0,
channel_score INTEGER DEFAULT 0,
potential_score INTEGER DEFAULT 0,
total INTEGER DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (lead_id) REFERENCES leads(id)
)
""")
# ── AGENT LOGS TABLE ──────────────────────────────────────────
c.execute("""
CREATE TABLE IF NOT EXISTS agent_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT DEFAULT (datetime('now')),
level TEXT DEFAULT 'info', -- info / success / warning / error
message TEXT NOT NULL
)
""")
conn.commit()
conn.close()
print("Database ready -> crm.db")
# ── LEAD CRUD ─────────────────────────────────────────────────────
def add_lead(data: dict) -> int:
conn = get_connection()
c = conn.cursor()
c.execute("""
INSERT INTO leads
(business_name, owner_name, business_type, city,
website, whatsapp, email, instagram, linkedin,
google_reviews, ig_followers, notes)
VALUES
(:business_name, :owner_name, :business_type, :city,
:website, :whatsapp, :email, :instagram, :linkedin,
:google_reviews, :ig_followers, :notes)
""", data)
lead_id = c.lastrowid
conn.commit()
conn.close()
return lead_id
def get_lead(lead_id: int) -> dict:
conn = get_connection()
c = conn.cursor()
row = c.execute("SELECT * FROM leads WHERE id=?", (lead_id,)).fetchone()
conn.close()
return dict(row) if row else None
def get_all_leads(status: str = None) -> list:
conn = get_connection()
c = conn.cursor()
if status:
rows = c.execute("SELECT * FROM leads WHERE status=? ORDER BY score DESC", (status,)).fetchall()
else:
rows = c.execute("SELECT * FROM leads ORDER BY score DESC").fetchall()
conn.close()
return [dict(r) for r in rows]
def update_lead(lead_id: int, fields: dict):
fields["updated_at"] = datetime.now().isoformat()
fields["id"] = lead_id
set_clause = ", ".join(f"{k}=:{k}" for k in fields if k != "id")
conn = get_connection()
conn.execute(f"UPDATE leads SET {set_clause} WHERE id=:id", fields)
conn.commit()
conn.close()
def delete_lead(lead_id: int):
conn = get_connection()
conn.execute("DELETE FROM leads WHERE id=?", (lead_id,))
conn.execute("DELETE FROM messages WHERE lead_id=?", (lead_id,))
conn.execute("DELETE FROM followups WHERE lead_id=?", (lead_id,))
conn.execute("DELETE FROM scores WHERE lead_id=?", (lead_id,))
conn.commit()
conn.close()
# ── MESSAGE CRUD ──────────────────────────────────────────────────
def save_message(lead_id: int, channel: str, direction: str, content: str, status="draft") -> int:
conn = get_connection()
c = conn.cursor()
c.execute("""
INSERT INTO messages (lead_id, channel, direction, content, status)
VALUES (?, ?, ?, ?, ?)
""", (lead_id, channel, direction, content, status))
msg_id = c.lastrowid
conn.commit()
conn.close()
return msg_id
def mark_message_sent(msg_id: int):
conn = get_connection()
conn.execute(
"UPDATE messages SET status='sent', sent_at=datetime('now') WHERE id=?",
(msg_id,)
)
conn.commit()
conn.close()
def get_messages(lead_id: int) -> list:
conn = get_connection()
rows = conn.execute(
"SELECT * FROM messages WHERE lead_id=? ORDER BY created_at", (lead_id,)
).fetchall()
conn.close()
return [dict(r) for r in rows]
# ── SCORE UPSERT ──────────────────────────────────────────────────
def save_score(lead_id: int, breakdown: dict):
conn = get_connection()
conn.execute("""
INSERT INTO scores (lead_id, website_score, social_score, channel_score, potential_score, total)
VALUES (:lead_id, :website_score, :social_score, :channel_score, :potential_score, :total)
ON CONFLICT(lead_id) DO UPDATE SET
website_score=excluded.website_score,
social_score=excluded.social_score,
channel_score=excluded.channel_score,
potential_score=excluded.potential_score,
total=excluded.total,
updated_at=datetime('now')
""", {"lead_id": lead_id, **breakdown})
conn.execute("UPDATE leads SET score=? WHERE id=?", (breakdown["total"], lead_id))
conn.commit()
conn.close()
# ── FOLLOWUP CRUD ─────────────────────────────────────────────────
def schedule_followup(lead_id: int, scheduled: str, channel: str, message: str):
conn = get_connection()
conn.execute("""
INSERT INTO followups (lead_id, scheduled, channel, message)
VALUES (?, ?, ?, ?)
""", (lead_id, scheduled, channel, message))
conn.commit()
conn.close()
def get_pending_followups() -> list:
conn = get_connection()
now = datetime.now().isoformat()
rows = conn.execute("""
SELECT f.*, l.business_name FROM followups f
JOIN leads l ON f.lead_id = l.id
WHERE f.done=0 AND f.scheduled <= ?
ORDER BY f.scheduled
""", (now,)).fetchall()
conn.close()
return [dict(r) for r in rows]
def mark_followup_done(followup_id: int):
conn = get_connection()
conn.execute("UPDATE followups SET done=1 WHERE id=?", (followup_id,))
conn.commit()
conn.close()
# ── AGENT LOGS CRUD ───────────────────────────────────────────────
def log_agent_activity(message: str, level: str = "info"):
"""Log an activity message from the autonomous agent to the database."""
conn = get_connection()
conn.execute(
"INSERT INTO agent_logs (message, level) VALUES (?, ?)",
(message, level)
)
conn.commit()
conn.close()
def get_agent_logs(limit: int = 100) -> list:
"""Retrieve the latest autonomous agent logs."""
conn = get_connection()
rows = conn.execute(
"SELECT id, timestamp, level, message FROM agent_logs ORDER BY id DESC LIMIT ?",
(limit,)
).fetchall()
conn.close()
return [dict(r) for r in rows]
def clear_agent_logs():
"""Clear all agent logs from the database."""
conn = get_connection()
conn.execute("DELETE FROM agent_logs")
conn.commit()
conn.close()
def get_score_breakdown(lead_id: int) -> dict:
"""Retrieve the score breakdown for a lead."""
conn = get_connection()
c = conn.cursor()
row = c.execute("SELECT * FROM scores WHERE lead_id=?", (lead_id,)).fetchone()
conn.close()
if row:
return dict(row)
else:
return {
"website_score": 0,
"social_score": 0,
"channel_score": 0,
"potential_score": 0,
"total": 0
}