Spaces:
Running
Running
File size: 5,707 Bytes
beec01d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | """
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}>"
)
|