File size: 6,458 Bytes
81dda2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
email-deliverability-scorer — LeadsBlue Hugging Face Space.

Estimates an email list's expected deliverability (0-100) from honest, real
factors: verification claim, data age, country compliance friction, and
industry receptiveness (derived from benchmark open rates).

This is an estimate, not a guarantee (stated in the UI).

Data source: country-insights.json, industry-insights.json
Author: Luther Johnson (ORCID 0009-0008-9836-1280) · LeadsBlue Research
License: CC BY 4.0 · Dataset DOI: 10.5281/zenodo.20136256
"""

import gradio as gr

from leadsblue_common import (
    CITATION_FOOTER,
    softwareapplication_jsonld,
    load_country_insights,
    load_industry_insights,
    parse_pct_range,
)

try:
    COUNTRIES = load_country_insights()
    INDUSTRIES = load_industry_insights()
    LOAD_ERROR = None
except Exception as exc:  # pragma: no cover
    COUNTRIES, INDUSTRIES, LOAD_ERROR = {}, {}, str(exc)

COUNTRY_CHOICES = sorted(c.title() for c in COUNTRIES.keys())
INDUSTRY_CHOICES = sorted(INDUSTRIES.keys())
VERIFICATION_CHOICES = ["Yes — recently verified", "Partial", "No / unknown"]
AGE_CHOICES = ["Under 6 months", "6–12 months", "12–24 months", "24+ months"]

# GDPR / strict-consent jurisdictions (compliance friction for cold B2B email)
GDPR_COUNTRIES = {
    "austria", "belgium", "bulgaria", "croatia", "cyprus", "czech republic",
    "denmark", "estonia", "finland", "france", "germany", "greece", "hungary",
    "ireland", "italy", "latvia", "lithuania", "luxembourg", "malta",
    "netherlands", "poland", "portugal", "romania", "slovakia", "slovenia",
    "spain", "sweden", "united kingdom",
}
CANSPAM_COUNTRIES = {"united states", "canada"}

VERIFICATION_POINTS = {"Yes — recently verified": 30, "Partial": 10, "No / unknown": 0}
AGE_POINTS = {"Under 6 months": 0, "6–12 months": -10, "12–24 months": -20, "24+ months": -35}


def _compliance_friction(country):
    c = country.lower()
    if c in GDPR_COUNTRIES:
        return -10, "GDPR jurisdiction (consent expectations): -10"
    if c in CANSPAM_COUNTRIES:
        return -3, "CAN-SPAM/CASL jurisdiction: -3"
    return 0, "Lighter regulatory friction: 0"


def _industry_receptiveness(industry):
    perf = INDUSTRIES.get(industry, {}).get("cold_email_performance", {})
    if "open_rate" not in perf:
        return 0, "Industry receptiveness: no data (0)"
    lo, hi = parse_pct_range(perf["open_rate"])
    mid = (lo + hi) / 2
    if mid >= 25:
        return 10, "High-receptiveness industry (open ~%.0f%%): +10" % mid
    if mid >= 20:
        return 6, "Above-average receptiveness (open ~%.0f%%): +6" % mid
    if mid >= 15:
        return 3, "Moderate receptiveness (open ~%.0f%%): +3" % mid
    return 0, "Lower-receptiveness industry (open ~%.0f%%): 0" % mid


def score(country, industry, verification, data_age, list_source):
    if LOAD_ERROR:
        return "**Data failed to load.** %s" % LOAD_ERROR
    if not country or not industry or not verification or not data_age:
        return "Please complete country, industry, verification, and data age."

    base = 50
    parts = ["Baseline: 50"]

    v = VERIFICATION_POINTS[verification]
    parts.append("Verification (%s): %+d" % (verification, v))

    a = AGE_POINTS[data_age]
    parts.append("Data age (%s): %+d" % (data_age, a))

    cf, cf_note = _compliance_friction(country)
    parts.append(cf_note)

    ir, ir_note = _industry_receptiveness(industry)
    parts.append(ir_note)

    total = max(0, min(100, base + v + a + cf + ir))

    if total >= 75:
        verdict = "Strong — list profile supports good deliverability."
    elif total >= 55:
        verdict = "Moderate — workable with verification and warm-up."
    elif total >= 35:
        verdict = "Caution — refresh and re-verify before sending at volume."
    else:
        verdict = "High risk — re-source or heavily clean before use."

    recs = []
    if v < 30:
        recs.append("Verify the list with a real-time validation pass before sending.")
    if a < 0:
        recs.append("Refresh records older than 6 months; B2B data decays ~2-3%/month.")
    if cf < 0:
        recs.append("For this jurisdiction, ensure a legitimate-interest basis and easy opt-out.")
    if not recs:
        recs.append("Maintain list hygiene and monitor bounce/complaint rates per send.")

    body = (
        "## Deliverability estimate: %d / 100\n\n"
        "**%s**\n\n"
        "### Score breakdown\n%s\n\n"
        "### Recommended actions\n%s\n\n"
        "> _This is an estimate based on benchmark factors, not a guarantee. "
        "Actual deliverability depends on sender reputation, content, and ESP "
        "configuration._\n\n---\n%s"
    ) % (
        total,
        verdict,
        "\n".join("- " + p for p in parts),
        "\n".join("- " + r for r in recs),
        CITATION_FOOTER,
    )
    return body


HEAD = softwareapplication_jsonld(
    "LeadsBlue Email Deliverability Scorer",
    "Estimates B2B email list deliverability from verification, data age, jurisdiction, and industry.",
)

_blocks_kwargs = {"title": "LeadsBlue Email Deliverability Scorer"}
try:
    demo = gr.Blocks(head=HEAD, **_blocks_kwargs)
except TypeError:
    demo = gr.Blocks(**_blocks_kwargs)  # older/newer gradio: head set at launch()
with demo:
    gr.Markdown(
        "# LeadsBlue Email Deliverability Scorer\n"
        "An honest, factor-based estimate of how a B2B list is likely to perform. "
        "No guarantees — just transparent scoring."
    )
    with gr.Row():
        country = gr.Dropdown(COUNTRY_CHOICES, label="Country", value=("United States" if "United States" in COUNTRY_CHOICES else None))
        industry = gr.Dropdown(INDUSTRY_CHOICES, label="Industry", value=(INDUSTRY_CHOICES[0] if INDUSTRY_CHOICES else None))
    with gr.Row():
        verification = gr.Dropdown(VERIFICATION_CHOICES, label="Email verification", value="Partial")
        data_age = gr.Dropdown(AGE_CHOICES, label="Data age", value="6–12 months")
    list_source = gr.Textbox(label="List source (optional)", placeholder="e.g. opt-in webinar, scraped directory, purchased...")
    btn = gr.Button("Score deliverability", variant="primary")
    out = gr.Markdown()
    btn.click(score, [country, industry, verification, data_age, list_source], out)
    gr.Markdown("---\n_%s_" % CITATION_FOOTER)

if __name__ == "__main__":
    try:
        demo.launch(head=HEAD)
    except TypeError:
        demo.launch()