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