""" 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"" )