Files
orion/app/modules/prospecting/models/prospect.py
Samir Boulahtit 6d6eba75bf
Some checks failed
CI / pytest (push) Failing after 48m31s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
CI / ruff (push) Successful in 11s
CI / validate (push) Successful in 23s
CI / dependency-scanning (push) Successful in 28s
feat(prospecting): add complete prospecting module for lead discovery and scoring
Migrates scanning pipeline from marketing-.lu-domains app into Orion module.
Supports digital (domain scan) and offline (manual capture) lead channels
with enrichment, scoring, campaign management, and interaction tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 00:59:47 +01:00

83 lines
3.0 KiB
Python

# app/modules/prospecting/models/prospect.py
"""
Prospect model - core entity for lead discovery.
Supports two channels:
- digital: discovered via domain scanning (.lu domains)
- offline: manually captured (street encounters, networking)
"""
import enum
from sqlalchemy import Boolean, Column, DateTime, Enum, Float, Integer, String, Text
from sqlalchemy.orm import relationship
from app.core.database import Base
from models.database.base import TimestampMixin
class ProspectChannel(str, enum.Enum):
DIGITAL = "digital"
OFFLINE = "offline"
class ProspectStatus(str, enum.Enum):
PENDING = "pending"
ACTIVE = "active"
INACTIVE = "inactive"
PARKED = "parked"
ERROR = "error"
CONTACTED = "contacted"
CONVERTED = "converted"
class Prospect(Base, TimestampMixin):
"""Represents a business prospect (potential client)."""
__tablename__ = "prospects"
id = Column(Integer, primary_key=True, index=True)
channel = Column(Enum(ProspectChannel), nullable=False, default=ProspectChannel.DIGITAL)
business_name = Column(String(255), nullable=True)
domain_name = Column(String(255), nullable=True, unique=True, index=True)
status = Column(Enum(ProspectStatus), nullable=False, default=ProspectStatus.PENDING)
source = Column(String(100), nullable=True)
# Website status (digital channel)
has_website = Column(Boolean, nullable=True)
uses_https = Column(Boolean, nullable=True)
http_status_code = Column(Integer, nullable=True)
redirect_url = Column(Text, nullable=True)
# Location (offline channel)
address = Column(String(500), nullable=True)
city = Column(String(100), nullable=True)
postal_code = Column(String(10), nullable=True)
country = Column(String(2), nullable=False, default="LU")
# Notes and metadata
notes = Column(Text, nullable=True)
tags = Column(Text, nullable=True) # JSON string of tags
# Capture info
captured_by_user_id = Column(Integer, nullable=True)
location_lat = Column(Float, nullable=True)
location_lng = Column(Float, nullable=True)
# Scan timestamps
last_http_check_at = Column(DateTime, nullable=True)
last_tech_scan_at = Column(DateTime, nullable=True)
last_perf_scan_at = Column(DateTime, nullable=True)
last_contact_scrape_at = Column(DateTime, nullable=True)
# Relationships
tech_profile = relationship("ProspectTechProfile", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
performance_profile = relationship("ProspectPerformanceProfile", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
score = relationship("ProspectScore", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
contacts = relationship("ProspectContact", back_populates="prospect", cascade="all, delete-orphan")
interactions = relationship("ProspectInteraction", back_populates="prospect", cascade="all, delete-orphan")
@property
def display_name(self) -> str:
return self.business_name or self.domain_name or f"Prospect #{self.id}"