LeadGenPro / lead_gen /models.py
MaSTer-suFYan
feat: LeadGen Pro v2.0 β€” full system with bug fixes
beec01d
"""
models.py β€” Shared data models used across all modules.
"""
from dataclasses import dataclass, field, asdict
from typing import Optional, Dict, Any
from datetime import datetime
@dataclass
class Lead:
"""
Represents a single business lead extracted from any source.
All fields are optional except ``business_name`` and ``service_category``.
The ``score`` field is populated by the scoring processor.
"""
# ── Core identity ──────────────────────────────────────────────────────
business_name : str = ""
service_category : str = "" # "web" | "app" | "ai"
# ── Contact ────────────────────────────────────────────────────────────
phone : str = ""
email : str = ""
address : str = ""
website : str = ""
contact_person : str = "" # Contact person name
# ── Meta ───────────────────────────────────────────────────────────────
source : str = "" # e.g. "Google Maps", "JustDial"
lead_source_scraper: str = "" # e.g. "google_maps", "justdial", "indiamart"
notes : str = "" # human-readable signals
user_notes : str = "" # notes added by sales team
score : str = "LOW" # "HIGH" | "MEDIUM" | "LOW"
score_points : int = 0 # raw numeric score
industry : str = "" # auto-detected industry
product_category : str = "" # product/service category from IndiaMART
# ── Lead lifecycle ─────────────────────────────────────────────────────
status : str = "New" # "New" | "Contacted" | "Converted" | "Rejected"
db_id : Optional[int] = None # SQLite row ID
# ── Timestamps ─────────────────────────────────────────────────────────
scraped_at : str = "" # ISO format datetime
updated_at : str = "" # ISO format datetime
# ── Website analysis flags (set by WebsiteAnalyser) ────────────────────
has_https : bool = False
is_mobile_friendly: bool = True
is_outdated : bool = False
has_chatbot : bool = False
has_online_booking: bool = False
# ── Extended analysis (v2) ─────────────────────────────────────────────
social_facebook : str = "" # Facebook page URL
social_instagram : str = "" # Instagram page URL
has_contact_form : bool = False
ssl_expiry_days : Optional[int] = None # Days until SSL cert expires
page_speed_score : Optional[float] = None # Google PageSpeed 0-100
load_time_sec : Optional[float] = None # Page load time in seconds
site_status : str = "" # "live" | "dead" | "404" | "timeout"
gmb_exists : bool = False # Google My Business listing exists
review_count : int = 0 # Number of Google reviews
opportunity_pitch : str = "" # 1-line sales pitch
# ── Dedup key (populated by deduplicator) ──────────────────────────────
fingerprint: str = ""
# ── Raw extra data from scraper ────────────────────────────────────────
extra: Dict[str, Any] = field(default_factory=dict)
# ──────────────────────────────────────────────────────────────────────
def to_dict(self) -> Dict[str, Any]:
d = asdict(self)
d.pop("extra", None) # omit raw blob from exports
return d
@property
def display_name(self) -> str:
return self.business_name or "Unknown Business"
@property
def service_label(self) -> str:
labels = {"web": "Web Development", "app": "App Development", "ai": "AI Automation"}
return labels.get(self.service_category, self.service_category.title())
@property
def whatsapp_link(self) -> str:
"""Generate WhatsApp click-to-chat link from phone number."""
if not self.phone:
return ""
digits = "".join(c for c in self.phone if c.isdigit())
if digits.startswith("91") and len(digits) >= 12:
return f"https://wa.me/{digits}"
elif len(digits) == 10:
return f"https://wa.me/91{digits}"
return ""
def stamp_scraped(self) -> None:
"""Set scraped_at to current time."""
self.scraped_at = datetime.now().isoformat()
def stamp_updated(self) -> None:
"""Set updated_at to current time."""
self.updated_at = datetime.now().isoformat()
def __repr__(self) -> str:
return (
f"<Lead {self.business_name!r} "
f"cat={self.service_category} score={self.score} status={self.status}>"
)