feat(prospecting): add complete prospecting module for lead discovery and scoring
Some checks failed
Some checks failed
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>
This commit is contained in:
231
app/modules/prospecting/tests/conftest.py
Normal file
231
app/modules/prospecting/tests/conftest.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# app/modules/prospecting/tests/conftest.py
|
||||
"""
|
||||
Module-specific fixtures for prospecting tests.
|
||||
Core fixtures (db, client, etc.) are inherited from the root conftest.py.
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.prospecting.models import (
|
||||
CampaignTemplate,
|
||||
Prospect,
|
||||
ProspectContact,
|
||||
ProspectInteraction,
|
||||
ProspectPerformanceProfile,
|
||||
ProspectScore,
|
||||
ProspectTechProfile,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def digital_prospect(db):
|
||||
"""Create a digital prospect with a domain."""
|
||||
prospect = Prospect(
|
||||
channel="digital",
|
||||
domain_name=f"test-{uuid.uuid4().hex[:8]}.lu",
|
||||
status="active",
|
||||
source="domain_scan",
|
||||
has_website=True,
|
||||
uses_https=True,
|
||||
http_status_code=200,
|
||||
country="LU",
|
||||
)
|
||||
db.add(prospect)
|
||||
db.commit()
|
||||
db.refresh(prospect)
|
||||
return prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def offline_prospect(db):
|
||||
"""Create an offline prospect with business details."""
|
||||
prospect = Prospect(
|
||||
channel="offline",
|
||||
business_name=f"Test Business {uuid.uuid4().hex[:8]}",
|
||||
status="pending",
|
||||
source="networking_event",
|
||||
city="Luxembourg",
|
||||
postal_code="1234",
|
||||
country="LU",
|
||||
notes="Met at networking event",
|
||||
tags=json.dumps(["networking", "no-website"]),
|
||||
)
|
||||
db.add(prospect)
|
||||
db.commit()
|
||||
db.refresh(prospect)
|
||||
return prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prospect_with_tech(db, digital_prospect):
|
||||
"""Create a digital prospect with tech profile."""
|
||||
tech = ProspectTechProfile(
|
||||
prospect_id=digital_prospect.id,
|
||||
cms="WordPress",
|
||||
cms_version="5.9",
|
||||
server="nginx",
|
||||
js_framework="jQuery",
|
||||
analytics="Google Analytics",
|
||||
ecommerce_platform=None,
|
||||
)
|
||||
db.add(tech)
|
||||
db.commit()
|
||||
db.refresh(digital_prospect)
|
||||
return digital_prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prospect_with_performance(db, digital_prospect):
|
||||
"""Create a digital prospect with performance profile."""
|
||||
perf = ProspectPerformanceProfile(
|
||||
prospect_id=digital_prospect.id,
|
||||
performance_score=45,
|
||||
accessibility_score=70,
|
||||
seo_score=60,
|
||||
first_contentful_paint_ms=2500,
|
||||
largest_contentful_paint_ms=4200,
|
||||
total_blocking_time_ms=350,
|
||||
cumulative_layout_shift=0.15,
|
||||
is_mobile_friendly=False,
|
||||
)
|
||||
db.add(perf)
|
||||
db.commit()
|
||||
db.refresh(digital_prospect)
|
||||
return digital_prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prospect_with_contacts(db, digital_prospect):
|
||||
"""Create a digital prospect with contacts."""
|
||||
contacts = [
|
||||
ProspectContact(
|
||||
prospect_id=digital_prospect.id,
|
||||
contact_type="email",
|
||||
value="info@test.lu",
|
||||
source_url="https://test.lu/contact",
|
||||
is_primary=True,
|
||||
),
|
||||
ProspectContact(
|
||||
prospect_id=digital_prospect.id,
|
||||
contact_type="phone",
|
||||
value="+352 123 456",
|
||||
source_url="https://test.lu/contact",
|
||||
is_primary=True,
|
||||
),
|
||||
]
|
||||
db.add_all(contacts)
|
||||
db.commit()
|
||||
db.refresh(digital_prospect)
|
||||
return digital_prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prospect_with_score(db, digital_prospect):
|
||||
"""Create a digital prospect with a score."""
|
||||
score = ProspectScore(
|
||||
prospect_id=digital_prospect.id,
|
||||
score=72,
|
||||
technical_health_score=25,
|
||||
modernity_score=20,
|
||||
business_value_score=20,
|
||||
engagement_score=7,
|
||||
reason_flags=json.dumps(["no_ssl", "slow"]),
|
||||
score_breakdown=json.dumps({"no_ssl": 15, "slow": 10}),
|
||||
lead_tier="top_priority",
|
||||
)
|
||||
db.add(score)
|
||||
db.commit()
|
||||
db.refresh(digital_prospect)
|
||||
return digital_prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prospect_full(db):
|
||||
"""Create a fully enriched digital prospect with all related data."""
|
||||
prospect = Prospect(
|
||||
channel="digital",
|
||||
domain_name=f"full-{uuid.uuid4().hex[:8]}.lu",
|
||||
status="active",
|
||||
source="domain_scan",
|
||||
has_website=True,
|
||||
uses_https=False,
|
||||
http_status_code=200,
|
||||
country="LU",
|
||||
)
|
||||
db.add(prospect)
|
||||
db.flush()
|
||||
|
||||
tech = ProspectTechProfile(
|
||||
prospect_id=prospect.id,
|
||||
cms="Drupal",
|
||||
cms_version="7.0",
|
||||
server="Apache",
|
||||
js_framework="jQuery",
|
||||
)
|
||||
perf = ProspectPerformanceProfile(
|
||||
prospect_id=prospect.id,
|
||||
performance_score=25,
|
||||
accessibility_score=50,
|
||||
seo_score=40,
|
||||
is_mobile_friendly=False,
|
||||
)
|
||||
contact = ProspectContact(
|
||||
prospect_id=prospect.id,
|
||||
contact_type="email",
|
||||
value="info@full-test.lu",
|
||||
is_primary=True,
|
||||
)
|
||||
score = ProspectScore(
|
||||
prospect_id=prospect.id,
|
||||
score=85,
|
||||
technical_health_score=35,
|
||||
modernity_score=20,
|
||||
business_value_score=20,
|
||||
engagement_score=10,
|
||||
reason_flags=json.dumps(["no_ssl", "very_slow", "outdated_cms", "not_mobile_friendly"]),
|
||||
lead_tier="top_priority",
|
||||
)
|
||||
|
||||
db.add_all([tech, perf, contact, score])
|
||||
db.commit()
|
||||
db.refresh(prospect)
|
||||
return prospect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def campaign_template(db):
|
||||
"""Create a campaign template."""
|
||||
template = CampaignTemplate(
|
||||
name="Test Template",
|
||||
lead_type="bad_website",
|
||||
channel="email",
|
||||
language="fr",
|
||||
subject_template="Votre site {domain} a des problèmes",
|
||||
body_template="Bonjour {business_name},\n\nVotre site {domain} a un score de {score}.\n\nIssues: {issues}",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(template)
|
||||
db.commit()
|
||||
db.refresh(template)
|
||||
return template
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def interaction(db, digital_prospect):
|
||||
"""Create a prospect interaction."""
|
||||
interaction = ProspectInteraction(
|
||||
prospect_id=digital_prospect.id,
|
||||
interaction_type="call",
|
||||
subject="Initial contact",
|
||||
notes="Discussed website needs",
|
||||
outcome="positive",
|
||||
next_action="Send proposal",
|
||||
created_by_user_id=1,
|
||||
)
|
||||
db.add(interaction)
|
||||
db.commit()
|
||||
db.refresh(interaction)
|
||||
return interaction
|
||||
Reference in New Issue
Block a user