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>
223 lines
12 KiB
Python
223 lines
12 KiB
Python
"""prospecting: initial tables for lead discovery and campaign management
|
|
|
|
Revision ID: prospecting_001
|
|
Revises: None
|
|
Create Date: 2026-02-27
|
|
"""
|
|
import sqlalchemy as sa
|
|
|
|
from alembic import op
|
|
|
|
revision = "prospecting_001"
|
|
down_revision = None
|
|
branch_labels = ("prospecting",)
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# --- prospects ---
|
|
op.create_table(
|
|
"prospects",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("channel", sa.String(10), nullable=False, server_default="digital"),
|
|
sa.Column("business_name", sa.String(255), nullable=True),
|
|
sa.Column("domain_name", sa.String(255), nullable=True, unique=True, index=True),
|
|
sa.Column("status", sa.String(20), nullable=False, server_default="pending"),
|
|
sa.Column("source", sa.String(100), nullable=True),
|
|
sa.Column("has_website", sa.Boolean(), nullable=True),
|
|
sa.Column("uses_https", sa.Boolean(), nullable=True),
|
|
sa.Column("http_status_code", sa.Integer(), nullable=True),
|
|
sa.Column("redirect_url", sa.Text(), nullable=True),
|
|
sa.Column("address", sa.String(500), nullable=True),
|
|
sa.Column("city", sa.String(100), nullable=True),
|
|
sa.Column("postal_code", sa.String(10), nullable=True),
|
|
sa.Column("country", sa.String(2), nullable=False, server_default="LU"),
|
|
sa.Column("notes", sa.Text(), nullable=True),
|
|
sa.Column("tags", sa.Text(), nullable=True),
|
|
sa.Column("captured_by_user_id", sa.Integer(), nullable=True),
|
|
sa.Column("location_lat", sa.Float(), nullable=True),
|
|
sa.Column("location_lng", sa.Float(), nullable=True),
|
|
sa.Column("last_http_check_at", sa.DateTime(), nullable=True),
|
|
sa.Column("last_tech_scan_at", sa.DateTime(), nullable=True),
|
|
sa.Column("last_perf_scan_at", sa.DateTime(), nullable=True),
|
|
sa.Column("last_contact_scrape_at", sa.DateTime(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- prospect_tech_profiles ---
|
|
op.create_table(
|
|
"prospect_tech_profiles",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("prospect_id", sa.Integer(), sa.ForeignKey("prospects.id", ondelete="CASCADE"), nullable=False, unique=True),
|
|
sa.Column("cms", sa.String(100), nullable=True),
|
|
sa.Column("cms_version", sa.String(50), nullable=True),
|
|
sa.Column("server", sa.String(100), nullable=True),
|
|
sa.Column("server_version", sa.String(50), nullable=True),
|
|
sa.Column("hosting_provider", sa.String(100), nullable=True),
|
|
sa.Column("cdn", sa.String(100), nullable=True),
|
|
sa.Column("has_valid_cert", sa.Boolean(), nullable=True),
|
|
sa.Column("cert_issuer", sa.String(200), nullable=True),
|
|
sa.Column("cert_expires_at", sa.DateTime(), nullable=True),
|
|
sa.Column("js_framework", sa.String(100), nullable=True),
|
|
sa.Column("analytics", sa.String(200), nullable=True),
|
|
sa.Column("tag_manager", sa.String(100), nullable=True),
|
|
sa.Column("ecommerce_platform", sa.String(100), nullable=True),
|
|
sa.Column("tech_stack_json", sa.Text(), nullable=True),
|
|
sa.Column("scan_source", sa.String(50), nullable=True),
|
|
sa.Column("scan_error", sa.Text(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- prospect_performance_profiles ---
|
|
op.create_table(
|
|
"prospect_performance_profiles",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("prospect_id", sa.Integer(), sa.ForeignKey("prospects.id", ondelete="CASCADE"), nullable=False, unique=True),
|
|
sa.Column("performance_score", sa.Integer(), nullable=True),
|
|
sa.Column("accessibility_score", sa.Integer(), nullable=True),
|
|
sa.Column("best_practices_score", sa.Integer(), nullable=True),
|
|
sa.Column("seo_score", sa.Integer(), nullable=True),
|
|
sa.Column("first_contentful_paint_ms", sa.Integer(), nullable=True),
|
|
sa.Column("largest_contentful_paint_ms", sa.Integer(), nullable=True),
|
|
sa.Column("total_blocking_time_ms", sa.Integer(), nullable=True),
|
|
sa.Column("cumulative_layout_shift", sa.Float(), nullable=True),
|
|
sa.Column("speed_index", sa.Integer(), nullable=True),
|
|
sa.Column("time_to_interactive_ms", sa.Integer(), nullable=True),
|
|
sa.Column("is_mobile_friendly", sa.Boolean(), nullable=True),
|
|
sa.Column("viewport_configured", sa.Boolean(), nullable=True),
|
|
sa.Column("font_size_ok", sa.Boolean(), nullable=True),
|
|
sa.Column("tap_targets_ok", sa.Boolean(), nullable=True),
|
|
sa.Column("total_bytes", sa.Integer(), nullable=True),
|
|
sa.Column("html_bytes", sa.Integer(), nullable=True),
|
|
sa.Column("css_bytes", sa.Integer(), nullable=True),
|
|
sa.Column("js_bytes", sa.Integer(), nullable=True),
|
|
sa.Column("image_bytes", sa.Integer(), nullable=True),
|
|
sa.Column("font_bytes", sa.Integer(), nullable=True),
|
|
sa.Column("total_requests", sa.Integer(), nullable=True),
|
|
sa.Column("js_requests", sa.Integer(), nullable=True),
|
|
sa.Column("css_requests", sa.Integer(), nullable=True),
|
|
sa.Column("image_requests", sa.Integer(), nullable=True),
|
|
sa.Column("lighthouse_json", sa.Text(), nullable=True),
|
|
sa.Column("scan_strategy", sa.String(20), nullable=True),
|
|
sa.Column("scan_error", sa.Text(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- prospect_scores ---
|
|
op.create_table(
|
|
"prospect_scores",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("prospect_id", sa.Integer(), sa.ForeignKey("prospects.id", ondelete="CASCADE"), nullable=False, unique=True),
|
|
sa.Column("score", sa.Integer(), nullable=False, server_default="0", index=True),
|
|
sa.Column("technical_health_score", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("modernity_score", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("business_value_score", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("engagement_score", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("reason_flags", sa.Text(), nullable=True),
|
|
sa.Column("score_breakdown", sa.Text(), nullable=True),
|
|
sa.Column("lead_tier", sa.String(20), nullable=True, index=True),
|
|
sa.Column("notes", sa.Text(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- prospect_contacts ---
|
|
op.create_table(
|
|
"prospect_contacts",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("prospect_id", sa.Integer(), sa.ForeignKey("prospects.id", ondelete="CASCADE"), nullable=False, index=True),
|
|
sa.Column("contact_type", sa.String(20), nullable=False),
|
|
sa.Column("value", sa.String(500), nullable=False),
|
|
sa.Column("label", sa.String(100), nullable=True),
|
|
sa.Column("source_url", sa.Text(), nullable=True),
|
|
sa.Column("source_element", sa.String(100), nullable=True),
|
|
sa.Column("is_validated", sa.Boolean(), nullable=False, server_default="0"),
|
|
sa.Column("validation_error", sa.Text(), nullable=True),
|
|
sa.Column("is_primary", sa.Boolean(), nullable=False, server_default="0"),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- prospect_scan_jobs ---
|
|
op.create_table(
|
|
"prospect_scan_jobs",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("job_type", sa.String(30), nullable=False),
|
|
sa.Column("status", sa.String(20), nullable=False, server_default="pending"),
|
|
sa.Column("total_items", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("processed_items", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("failed_items", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("skipped_items", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("started_at", sa.DateTime(), nullable=True),
|
|
sa.Column("completed_at", sa.DateTime(), nullable=True),
|
|
sa.Column("config", sa.Text(), nullable=True),
|
|
sa.Column("result_summary", sa.Text(), nullable=True),
|
|
sa.Column("error_log", sa.Text(), nullable=True),
|
|
sa.Column("source_file", sa.String(500), nullable=True),
|
|
sa.Column("celery_task_id", sa.String(255), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- prospect_interactions ---
|
|
op.create_table(
|
|
"prospect_interactions",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("prospect_id", sa.Integer(), sa.ForeignKey("prospects.id", ondelete="CASCADE"), nullable=False, index=True),
|
|
sa.Column("interaction_type", sa.String(20), nullable=False),
|
|
sa.Column("subject", sa.String(255), nullable=True),
|
|
sa.Column("notes", sa.Text(), nullable=True),
|
|
sa.Column("outcome", sa.String(20), nullable=True),
|
|
sa.Column("next_action", sa.String(255), nullable=True),
|
|
sa.Column("next_action_date", sa.Date(), nullable=True),
|
|
sa.Column("created_by_user_id", sa.Integer(), nullable=False),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- campaign_templates ---
|
|
op.create_table(
|
|
"campaign_templates",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("name", sa.String(255), nullable=False),
|
|
sa.Column("lead_type", sa.String(30), nullable=False),
|
|
sa.Column("channel", sa.String(20), nullable=False, server_default="email"),
|
|
sa.Column("language", sa.String(5), nullable=False, server_default="fr"),
|
|
sa.Column("subject_template", sa.String(500), nullable=True),
|
|
sa.Column("body_template", sa.Text(), nullable=False),
|
|
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="1"),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
# --- campaign_sends ---
|
|
op.create_table(
|
|
"campaign_sends",
|
|
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
|
sa.Column("template_id", sa.Integer(), sa.ForeignKey("campaign_templates.id", ondelete="SET NULL"), nullable=True),
|
|
sa.Column("prospect_id", sa.Integer(), sa.ForeignKey("prospects.id", ondelete="CASCADE"), nullable=False, index=True),
|
|
sa.Column("channel", sa.String(20), nullable=False),
|
|
sa.Column("rendered_subject", sa.String(500), nullable=True),
|
|
sa.Column("rendered_body", sa.Text(), nullable=True),
|
|
sa.Column("status", sa.String(20), nullable=False, server_default="draft"),
|
|
sa.Column("sent_at", sa.DateTime(), nullable=True),
|
|
sa.Column("sent_by_user_id", sa.Integer(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("campaign_sends")
|
|
op.drop_table("campaign_templates")
|
|
op.drop_table("prospect_interactions")
|
|
op.drop_table("prospect_scan_jobs")
|
|
op.drop_table("prospect_contacts")
|
|
op.drop_table("prospect_scores")
|
|
op.drop_table("prospect_performance_profiles")
|
|
op.drop_table("prospect_tech_profiles")
|
|
op.drop_table("prospects")
|