AI-Powered Lead Scoring Pipeline
Build a Python pipeline that scores B2B leads using Gemini AI and SQL.
Introduction
30 Second Summary
Every B2B sales team has the same problem. A pile of incoming leads, limited rep capacity, and no reliable way to know which prospects deserve attention first.
In this project, you will build an AI-powered lead scoring pipeline that evaluates B2B leads against your Ideal Customer Profile using the Google Gemini API, stores scored results in a SQL database, and visualizes the output in a Tableau dashboard.
What You'll Build
A Python pipeline that pulls lead data from SQL Server LocalDB, sends each lead to Google Gemini with structured ICP scoring criteria, writes fit scores and routing recommendations back to the database, and connects to Tableau Desktop for stakeholder-ready reporting.
By the end of this project, you'll have:
- A SQL Server database with structured B2B lead data and AI-generated scores, reasoning, and routing recommendations.
- A Python scoring pipeline that calls the Gemini API to evaluate leads against a defined Ideal Customer Profile.
- A Tableau Desktop dashboard connected live to your scored leads database, showing lead prioritization and score distribution.
- Secret Mission: Add a deal velocity prediction that estimates days-to-close based on lead attributes and scores.
Are there any prerequisites?
You should be comfortable with basic Python scripting and basic SQL (CREATE TABLE, INSERT, SELECT). Familiarity with VS Code is also helpful.
Set Up Your Environment and API Key
Before your pipeline can score leads, it needs two things: a way to talk to Google's AI and a way to talk to your SQL database.
Two Python packages handle this. In this step, you will install them, grab a free API key from Google, and confirm your database server is ready to accept connections.
In this step, get ready to:
- Install the google-genai and pyodbc Python packages.
- Obtain a free Gemini API key and store it securely in a project folder.
- Verify your SQL Server LocalDB instance is running.
Install the Python packages
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Open the integrated terminal by pressing Ctrl+` (backtick key, top-left of your keyboard).
- Install the two packages your project needs by running this command:
pip install google-genai pyodbc
What do these packages do?
google-genai is the official Python client for the Google Gemini API. It handles authentication, request formatting, and structured JSON responses from the AI model.
pyodbc connects Python to SQL Server databases using the ODBC protocol. Your scoring pipeline will use it to read lead data and write scores back.
- Verify both packages installed correctly by running this command:
python -c "from google import genai; import pyodbc; print('Ready')"
- You should see Ready printed in the terminal.
Seeing an error instead of Ready?
- If you see ModuleNotFoundError, the install may not have completed. Re-run the pip install command and check for error messages in the output.
- If python is not recognized, try python3 instead. Your system may use a different alias for the Python binary.
Need help troubleshooting?
Get your free Gemini API key
The Gemini API is what your pipeline will call to score each lead. You need an API key to authenticate those requests.
- Open your browser and go to aistudio.google.com.
- Sign in with your Google account. If you are a new user, Google AI Studio automatically creates a default project after you accept the Terms of Service.
- In the left panel, open the Dashboard and navigate to API Keys.
- Click Create API key.
- Copy the generated key. You will only see it once, so keep it handy.
Why is this free?
The Gemini API free tier includes free input and output tokens with no credit card required. Your 15-lead scoring pipeline fits comfortably within the free allowance.
Now you need a project folder to store your code and configuration files.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir ai-lead-scoring-pipeline
cd ai-lead-scoring-pipeline
- Open this folder in VS Code by running:
code .
What does code . do?
The code . command opens VS Code with the current folder as the workspace. A new VS Code window will appear with ai-lead-scoring-pipeline shown in the Explorer sidebar.
- In the new VS Code window, open the terminal again with Ctrl+`.
- In VS Code's Explorer sidebar, click the New File icon (the page icon with a plus sign).
- Name the file .env and press Enter.
- Paste the following content into .env, replacing your-api-key-here with the API key you just copied:
GEMINI_API_KEY=your-api-key-here
- Save .env with Ctrl+S.
- Click the New File icon again in the Explorer sidebar.
- Name this file .gitignore and press Enter.
- Paste the following content into .gitignore:
.env
__pycache__/
*.pyc
*.twbx
Why do you need a .gitignore file?
The .gitignore file tells Git which files to exclude from version control. Your .env file contains a secret API key that should never be committed to a public repository.
The other entries keep generated files (Python cache and Tableau packaged workbooks) out of your repo to keep it clean.
- Save .gitignore with Ctrl+S.
- Confirm both files exist by running:
ls -a
- You should see .env and .gitignore in the output.
Don't see your files listed?
- Make sure you are running ls -a (with the -a flag). Files starting with a dot are hidden by default and only appear with this flag.
- Check that you are in the ai-lead-scoring-pipeline folder. Run pwd to confirm your current location.
Help me find my project files.
✔️ Awesome, I've got everything!
Great. Make sure both files are saved before moving on.
ⓧ I'd like to double check the full code
Here is the complete content for each file:
GEMINI_API_KEY=your-api-key-here
.env
__pycache__/
*.pyc
*.twbx
Verify your LocalDB instance is running
Your lead data will live in SQL Server LocalDB. Before you can create a database, the LocalDB instance needs to be running.
- Check the status of your LocalDB instance by running this command:
sqllocaldb info MSSQLLocalDB
✔️ I see State: Running
Your LocalDB instance is already running. No further action needed.
ⓧ I see State: Stopped
- Start the instance by running this command:
sqllocaldb start MSSQLLocalDB
- Run sqllocaldb info MSSQLLocalDB again to confirm the state now shows Running.
ⓧ sqllocaldb is not recognized
This means SQL Server LocalDB is either not installed or its path is missing from your system PATH variable.
- Download and install SQL Server Express LocalDB from the official Microsoft download page.
- After installation, close and reopen VS Code so the terminal picks up the updated PATH.
- Try the sqllocaldb info MSSQLLocalDB command again.
Help me get LocalDB working.
Your environment is locked and loaded. Next up, you will create the SQL database and fill it with realistic B2B lead data for the AI to score.
Create the Leads Database and Seed Data
In Step 1, you set up your Python packages and Gemini API key. Now it's time to build the data layer that your scoring pipeline will read from and write to.
Every lead scoring pipeline needs structured data to work with. In this step, you'll create a SQL Server database, define a table schema designed for B2B lead data, and populate it with 15 realistic sample leads.
In this step, get ready to:
- Create a LeadScoring database and leads table in SQL Server LocalDB.
- Seed 15 sample B2B leads with varied firmographic data.
- Verify the data loaded correctly with a SELECT query.
Create the database connection and schema
Before you can score leads, you need a place to store them. You'll write a Python script that connects to your LocalDB instance and creates the database and table structure.
- Create a new file called setup_database.py in your project folder.
- Add the connection helper and database creation functions by pasting this code:
import pyodbc
def get_connection(database="master"):
conn_str = (
r"DRIVER={ODBC Driver 17 for SQL Server};"
r"SERVER=(localdb)\MSSQLLocalDB;"
f"DATABASE={database};"
r"Trusted_Connection=yes;"
)
return pyodbc.connect(conn_str, autocommit=True)
def create_database():
conn = get_connection("master")
cursor = conn.cursor()
cursor.execute("""
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'LeadScoring')
CREATE DATABASE LeadScoring
""")
cursor.close()
conn.close()
print("Database 'LeadScoring' ready.")
What does this code do?
get_connection() builds an ODBC connection string pointing at your LocalDB instance. The autocommit=True parameter tells pyodbc to commit each SQL statement immediately. This is required because CREATE DATABASE cannot run inside a user transaction.
create_database() uses an IF NOT EXISTS guard so you can safely re-run the script without errors. It creates a database called LeadScoring on your local instance.
- Save setup_database.py.
- Confirm the file exists by running:
ls setup_database.py
You should see setup_database.py listed in the output.
File not found?
Make sure you created the file inside your project folder, not a parent directory. Check your terminal's current path with cd and verify it matches your project folder location.
help me find where my setup_database.py file was saved
Now add the table schema. This defines where each lead's data will live.
- In setup_database.py, add the following function below create_database():
def create_leads_table():
conn = get_connection("LeadScoring")
cursor = conn.cursor()
cursor.execute("""
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'leads')
CREATE TABLE leads (
lead_id INT IDENTITY(1,1) PRIMARY KEY,
company_name NVARCHAR(200) NOT NULL,
industry NVARCHAR(100) NOT NULL,
employee_count INT NOT NULL,
annual_revenue NVARCHAR(50) NOT NULL,
job_title NVARCHAR(100) NOT NULL,
lead_source NVARCHAR(100) NOT NULL,
company_description NVARCHAR(500) NOT NULL,
score INT NULL,
score_reasoning NVARCHAR(1000) NULL,
routing_recommendation NVARCHAR(50) NULL,
scored_at DATETIME NULL
)
""")
cursor.close()
conn.close()
print("Table 'leads' ready.")
What does this table schema represent?
The first eight columns (company_name through company_description) store the raw lead data. These are firmographic fields you'd typically import from a CRM or marketing automation tool.
The last four columns (score, score_reasoning, routing_recommendation, scored_at) are nullable because they'll be filled later by the AI scoring pipeline.
- Save setup_database.py.
You won't see a change yet. The next function populates this table with data.
Seed the database with sample leads
A scoring pipeline needs leads to score. You'll add 15 sample leads that represent a realistic mix of high-fit, medium-fit, and low-fit prospects so you can see how the AI differentiates between them later.
- In setup_database.py, add the following function below create_leads_table():
def seed_leads():
conn = get_connection("LeadScoring")
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM leads")
count = cursor.fetchone()[0]
if count > 0:
print(f"Leads table already has {count} rows. Skipping seed.")
cursor.close()
conn.close()
return
leads = [
("Nextera SaaS", "SaaS", 250, "$25M", "VP of Sales", "Inbound Demo Request", "B2B sales engagement platform serving mid-market companies"),
("Quantum Fintech", "Fintech", 400, "$60M", "CTO", "Conference Lead", "Payment processing platform for enterprise financial institutions"),
("CloudStack Pro", "Enterprise Software", 150, "$12M", "Director of Revenue Operations", "Webinar Attendee", "Cloud infrastructure management tool for DevOps teams"),
("GreenLeaf Manufacturing", "Manufacturing", 2000, "$200M", "Office Manager", "Cold Outbound", "Industrial equipment manufacturer for agriculture sector"),
("TinyApps Studio", "SaaS", 8, "$500K", "Founder", "Product Hunt", "Early-stage mobile app development studio"),
("DataVault Analytics", "Enterprise Software", 90, "$8M", "Head of Growth", "Partner Referral", "Data warehousing solution for mid-market analytics teams"),
("Metro Consulting Group", "Consulting", 50, "$4M", "Managing Partner", "LinkedIn Ad", "Management consulting firm serving local businesses"),
("Apex Security Solutions", "Cybersecurity", 300, "$45M", "VP of Engineering", "Inbound Demo Request", "Enterprise endpoint security and threat detection platform"),
("BrightPath Education", "EdTech", 75, "$6M", "Marketing Intern", "Trade Show", "Online learning platform for K-12 schools"),
("Velocity Commerce", "E-commerce", 500, "$80M", "Chief Revenue Officer", "Inbound Demo Request", "Enterprise e-commerce platform with AI-powered recommendations"),
("Harbor Logistics", "Logistics", 1200, "$150M", "IT Coordinator", "Cold Outbound", "Freight and supply chain management for maritime shipping"),
("Spark AI Labs", "AI/ML", 60, "$10M", "CEO", "Inbound Content Download", "Machine learning ops platform for data science teams"),
("Regional Insurance Co", "Insurance", 800, "$90M", "Claims Adjuster", "Purchased List", "Regional property and casualty insurance provider"),
("FlowState CRM", "SaaS", 180, "$20M", "Director of Sales", "G2 Review Intent", "CRM platform purpose-built for professional services firms"),
("Budget Prints LLC", "Print Services", 12, "$800K", "Owner", "Google Ads", "Local print shop offering business cards and flyers"),
]
cursor.executemany("""
INSERT INTO leads (company_name, industry, employee_count, annual_revenue,
job_title, lead_source, company_description)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", leads)
conn.commit()
cursor.close()
conn.close()
print("Seeded 15 sample leads.")
What does this function do?
The function first checks if the table already has data. If it does, it skips the insert to prevent duplicates on re-runs. This makes the script idempotent.
The 15 leads cover a deliberate range. Companies like Nextera SaaS (250 employees, VP of Sales, inbound demo) are strong ICP fits. Companies like Budget Prints LLC (12 employees, owner, Google Ads) are poor fits. The AI will need to differentiate between these later.
- Save setup_database.py.
You won't see a change yet. The next section adds the verification logic and runs everything.
Run the script and verify your data
The final piece adds a verification function that queries the database and prints a summary. Then you'll wire everything together with a main block and run the script.
- In setup_database.py, add the following below seed_leads():
def verify_data():
conn = get_connection("LeadScoring")
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM leads")
count = cursor.fetchone()[0]
print(f"\nTotal leads in database: {count}")
print("-" * 60)
cursor.execute("SELECT TOP 5 lead_id, company_name, industry, job_title FROM leads")
for row in cursor.fetchall():
print(f" [{row.lead_id}] {row.company_name} | {row.industry} | {row.job_title}")
cursor.close()
conn.close()
if __name__ == "__main__":
create_database()
create_leads_table()
seed_leads()
verify_data()
What does this code do?
verify_data() runs a SELECT COUNT(*) to confirm all 15 leads loaded, then prints the first 5 rows so you can spot-check the data.
The if __name__ == "__main__" block chains all four functions in order: create the database, create the table, seed the data, and verify. This means one python command does everything.
- Save setup_database.py.
- Run the script by executing this command in your terminal:
python setup_database.py
You should see output like this:
Database 'LeadScoring' ready.
Table 'leads' ready.
Seeded 15 sample leads.
Total leads in database: 15
------------------------------------------------------------
[1] Nextera SaaS | SaaS | VP of Sales
[2] Quantum Fintech | Fintech | CTO
[3] CloudStack Pro | Enterprise Software | Director of Revenue Operations
[4] GreenLeaf Manufacturing | Manufacturing | Office Manager
[5] TinyApps Studio | SaaS | Founder
Seeing a connection error?
If you see an error mentioning the ODBC driver, confirm that ODBC Driver 17 for SQL Server is installed on your system. LocalDB installations typically include it.
If the error says LocalDB is not running, start it by running sqllocaldb start MSSQLLocalDB in your terminal, then try again.
help me debug my pyodbc connection error to SQL Server LocalDB
✔️ Awesome, I've got everything!
Great. Double check you've saved setup_database.py and that you see 15 leads in the output.
ⓧ I'd like to double check the full code
Here's the complete setup_database.py file for comparison:
import pyodbc
def get_connection(database="master"):
conn_str = (
r"DRIVER={ODBC Driver 17 for SQL Server};"
r"SERVER=(localdb)\MSSQLLocalDB;"
f"DATABASE={database};"
r"Trusted_Connection=yes;"
)
return pyodbc.connect(conn_str, autocommit=True)
def create_database():
conn = get_connection("master")
cursor = conn.cursor()
cursor.execute("""
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'LeadScoring')
CREATE DATABASE LeadScoring
""")
cursor.close()
conn.close()
print("Database 'LeadScoring' ready.")
def create_leads_table():
conn = get_connection("LeadScoring")
cursor = conn.cursor()
cursor.execute("""
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'leads')
CREATE TABLE leads (
lead_id INT IDENTITY(1,1) PRIMARY KEY,
company_name NVARCHAR(200) NOT NULL,
industry NVARCHAR(100) NOT NULL,
employee_count INT NOT NULL,
annual_revenue NVARCHAR(50) NOT NULL,
job_title NVARCHAR(100) NOT NULL,
lead_source NVARCHAR(100) NOT NULL,
company_description NVARCHAR(500) NOT NULL,
score INT NULL,
score_reasoning NVARCHAR(1000) NULL,
routing_recommendation NVARCHAR(50) NULL,
scored_at DATETIME NULL
)
""")
cursor.close()
conn.close()
print("Table 'leads' ready.")
def seed_leads():
conn = get_connection("LeadScoring")
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM leads")
count = cursor.fetchone()[0]
if count > 0:
print(f"Leads table already has {count} rows. Skipping seed.")
cursor.close()
conn.close()
return
leads = [
("Nextera SaaS", "SaaS", 250, "$25M", "VP of Sales", "Inbound Demo Request", "B2B sales engagement platform serving mid-market companies"),
("Quantum Fintech", "Fintech", 400, "$60M", "CTO", "Conference Lead", "Payment processing platform for enterprise financial institutions"),
("CloudStack Pro", "Enterprise Software", 150, "$12M", "Director of Revenue Operations", "Webinar Attendee", "Cloud infrastructure management tool for DevOps teams"),
("GreenLeaf Manufacturing", "Manufacturing", 2000, "$200M", "Office Manager", "Cold Outbound", "Industrial equipment manufacturer for agriculture sector"),
("TinyApps Studio", "SaaS", 8, "$500K", "Founder", "Product Hunt", "Early-stage mobile app development studio"),
("DataVault Analytics", "Enterprise Software", 90, "$8M", "Head of Growth", "Partner Referral", "Data warehousing solution for mid-market analytics teams"),
("Metro Consulting Group", "Consulting", 50, "$4M", "Managing Partner", "LinkedIn Ad", "Management consulting firm serving local businesses"),
("Apex Security Solutions", "Cybersecurity", 300, "$45M", "VP of Engineering", "Inbound Demo Request", "Enterprise endpoint security and threat detection platform"),
("BrightPath Education", "EdTech", 75, "$6M", "Marketing Intern", "Trade Show", "Online learning platform for K-12 schools"),
("Velocity Commerce", "E-commerce", 500, "$80M", "Chief Revenue Officer", "Inbound Demo Request", "Enterprise e-commerce platform with AI-powered recommendations"),
("Harbor Logistics", "Logistics", 1200, "$150M", "IT Coordinator", "Cold Outbound", "Freight and supply chain management for maritime shipping"),
("Spark AI Labs", "AI/ML", 60, "$10M", "CEO", "Inbound Content Download", "Machine learning ops platform for data science teams"),
("Regional Insurance Co", "Insurance", 800, "$90M", "Claims Adjuster", "Purchased List", "Regional property and casualty insurance provider"),
("FlowState CRM", "SaaS", 180, "$20M", "Director of Sales", "G2 Review Intent", "CRM platform purpose-built for professional services firms"),
("Budget Prints LLC", "Print Services", 12, "$800K", "Owner", "Google Ads", "Local print shop offering business cards and flyers"),
]
cursor.executemany("""
INSERT INTO leads (company_name, industry, employee_count, annual_revenue,
job_title, lead_source, company_description)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", leads)
conn.commit()
cursor.close()
conn.close()
print("Seeded 15 sample leads.")
def verify_data():
conn = get_connection("LeadScoring")
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM leads")
count = cursor.fetchone()[0]
print(f"\nTotal leads in database: {count}")
print("-" * 60)
cursor.execute("SELECT TOP 5 lead_id, company_name, industry, job_title FROM leads")
for row in cursor.fetchall():
print(f" [{row.lead_id}] {row.company_name} | {row.industry} | {row.job_title}")
cursor.close()
conn.close()
if __name__ == "__main__":
create_database()
create_leads_table()
seed_leads()
verify_data()
Your data layer is ready. You have 15 leads waiting to be scored. Next up, you'll build the Python pipeline that sends each lead to the Gemini API and writes the AI's scoring results back into those empty columns.
Build the AI Scoring Pipeline
Your database is loaded with 15 B2B leads, but they are just raw data sitting in rows. To turn this data into actionable intelligence, you need a way to evaluate each lead against your Ideal Customer Profile and assign a priority score.
In this step, you will build a Python script that pulls unscored leads from the database, sends each one to the Gemini API with a structured scoring prompt, and writes the results back. By the end, every lead will have a fit score (1-10), reasoning, and a routing recommendation.
In this step, get ready to:
- Define your ICP criteria as a structured prompt template.
- Build a scoring function that calls the Gemini API with a JSON response schema.
- Run the pipeline and verify all 15 leads are scored and routed.
Create the scoring script and define your ICP prompt
The ICP (Ideal Customer Profile) is the blueprint your scoring engine uses to judge every lead. You will encode it as a prompt that tells the LLM exactly what a high-fit lead looks like.
- Install the python-dotenv package so your script can read the API key from your .env file by running:
pip install python-dotenv
Why python-dotenv?
On Windows, environment variables set in a .env file are not automatically available to your Python scripts. The python-dotenv library loads them into os.environ at runtime so your code can access GEMINI_API_KEY without manual exports.
- Create a new file called score_leads.py in your project folder and add the imports, API client setup, and ICP prompt by pasting this code:
import json
import os
import pyodbc
from datetime import datetime
from google import genai
from google.genai import types
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
if not GEMINI_API_KEY:
from dotenv import load_dotenv
load_dotenv()
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
client = genai.Client(api_key=GEMINI_API_KEY)
ICP_PROMPT = """You are a RevOps lead scoring engine. Score the following B2B lead against our Ideal Customer Profile (ICP).
**Our ICP:**
- Target Industries: SaaS, Fintech, Enterprise Software, Cybersecurity, AI/ML
- Company Size: 50-1000 employees
- Annual Revenue: $5M-$100M
- Decision-Maker Titles: C-level, VP, Director, Head of department
- Preferred Lead Sources: Inbound Demo Request, Partner Referral, G2 Review Intent
**Scoring Scale (1-10):**
- 9-10: Perfect ICP fit. Right industry, size, revenue, decision-maker, strong intent signal.
- 7-8: Strong fit. Matches most ICP criteria with minor gaps.
- 5-6: Moderate fit. Matches some criteria but has notable gaps.
- 3-4: Weak fit. Few ICP matches, unlikely to convert.
- 1-2: Poor fit. Does not match ICP at all.
**Routing Rules:**
- Score 7-10: "Immediate outreach"
- Score 4-6: "Nurture sequence"
- Score 1-3: "Low priority"
**Lead Data:**
- Company: {company_name}
- Industry: {industry}
- Employee Count: {employee_count}
- Annual Revenue: {annual_revenue}
- Contact Title: {job_title}
- Lead Source: {lead_source}
- Company Description: {company_description}
Return your assessment as JSON with exactly these keys: score, reasoning, routing_recommendation"""
What does this code do?
The top section loads your Gemini API key. It first checks the system environment, and if the key is not found there, it uses python-dotenv to load it from your .env file.
The ICP_PROMPT is a multi-line string that acts as the scoring rubric. It defines target industries, company size ranges, revenue bands, decision-maker titles, and a 1-10 scoring scale with routing rules. The curly-brace placeholders ({company_name}, {industry}, etc.) get filled in with each lead's actual data before being sent to Gemini.
- Save score_leads.py.
Seeing a red underline on the import?
If VS Code highlights from google import genai with an error, make sure you installed google-genai (not google-generativeai) in the previous step. The old package is deprecated and uses a different import path.
help me fix a google-genai import error in VS Code
Write the scoring and database functions
Now you need the functions that connect to your database, call the Gemini API, and write results back. Each function handles one responsibility in the pipeline.
- In score_leads.py, add the database connection and query functions below the ICP_PROMPT by pasting this code:
def get_connection():
# Connect to the LeadScoring database on LocalDB
conn_str = (
r"DRIVER={ODBC Driver 17 for SQL Server};"
r"SERVER=(localdb)\MSSQLLocalDB;"
r"DATABASE=LeadScoring;"
r"Trusted_Connection=yes;"
)
return pyodbc.connect(conn_str)
def fetch_unscored_leads(cursor):
# Pull leads that have not been scored yet
cursor.execute("""
SELECT lead_id, company_name, industry, employee_count,
annual_revenue, job_title, lead_source, company_description
FROM leads
WHERE score IS NULL
""")
columns = [desc[0] for desc in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
What does this code do?
get_connection() opens a connection to your LeadScoring database using the same LocalDB instance from the previous step.
fetch_unscored_leads() runs a SELECT query that only returns leads where score IS NULL. It converts each row into a Python dictionary so you can reference fields by name (like lead["company_name"]) when building the prompt.
- Next, add the core scoring function that calls the Gemini API:
def score_lead(lead):
# Fill the ICP prompt template with this lead's data
prompt = ICP_PROMPT.format(
company_name=lead["company_name"],
industry=lead["industry"],
employee_count=lead["employee_count"],
annual_revenue=lead["annual_revenue"],
job_title=lead["job_title"],
lead_source=lead["lead_source"],
company_description=lead["company_description"],
)
# Call Gemini with a structured JSON response schema
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_json_schema={
"type": "object",
"properties": {
"score": {"type": "integer"},
"reasoning": {"type": "string"},
"routing_recommendation": {"type": "string"},
},
"required": ["score", "reasoning", "routing_recommendation"],
},
temperature=0.2,
),
)
return json.loads(response.text)
What does this code do?
score_lead() takes a single lead dictionary, fills in the ICP prompt with that lead's data, and sends it to gemini-2.5-flash.
The response_json_schema parameter forces Gemini to return structured JSON with exactly the keys you need: score, reasoning, and routing_recommendation. The low temperature=0.2 keeps scores consistent and deterministic across runs.
Why structured JSON output?
Without a schema, the LLM might return free-text answers that are hard to parse. By setting response_mime_type="application/json" and providing a schema, Gemini guarantees every response is valid JSON with the exact fields your database expects. This eliminates brittle regex parsing and makes the pipeline production-ready.
- Finally, add the update function and the main orchestration loop:
def update_lead_score(cursor, lead_id, result):
# Write the AI-generated score back to the database
cursor.execute("""
UPDATE leads
SET score = ?, score_reasoning = ?, routing_recommendation = ?, scored_at = ?
WHERE lead_id = ?
""", (
result["score"],
result["reasoning"][:1000],
result["routing_recommendation"],
datetime.now(),
lead_id,
))
def main():
conn = get_connection()
cursor = conn.cursor()
leads = fetch_unscored_leads(cursor)
print(f"Found {len(leads)} unscored leads.\n")
for lead in leads:
try:
result = score_lead(lead)
update_lead_score(cursor, lead["lead_id"], result)
conn.commit()
print(f" [{result['score']:>2}/10] {lead['company_name']:<25} -> {result['routing_recommendation']}")
except Exception as e:
print(f" [ERR] {lead['company_name']:<25} -> {e}")
print("\nScoring complete.")
cursor.execute("SELECT COUNT(*) FROM leads WHERE score IS NOT NULL")
scored = cursor.fetchone()[0]
print(f"Total scored leads: {scored}")
cursor.close()
conn.close()
if __name__ == "__main__":
main()
What does this code do?
update_lead_score() writes the AI's response back to the database. It truncates the reasoning to 1000 characters to fit the column limit and stamps the current time into scored_at.
main() orchestrates the full pipeline. It fetches all unscored leads, loops through each one, calls score_lead(), saves the result, and prints progress to the terminal. The try/except block catches any API errors per-lead so one failure does not stop the entire batch.
- Save score_leads.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved score_leads.py with all the functions in place.
ⓧ I'd like to double check the full code
import json
import os
import pyodbc
from datetime import datetime
from google import genai
from google.genai import types
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
if not GEMINI_API_KEY:
from dotenv import load_dotenv
load_dotenv()
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
client = genai.Client(api_key=GEMINI_API_KEY)
ICP_PROMPT = """You are a RevOps lead scoring engine. Score the following B2B lead against our Ideal Customer Profile (ICP).
**Our ICP:**
- Target Industries: SaaS, Fintech, Enterprise Software, Cybersecurity, AI/ML
- Company Size: 50-1000 employees
- Annual Revenue: $5M-$100M
- Decision-Maker Titles: C-level, VP, Director, Head of department
- Preferred Lead Sources: Inbound Demo Request, Partner Referral, G2 Review Intent
**Scoring Scale (1-10):**
- 9-10: Perfect ICP fit. Right industry, size, revenue, decision-maker, strong intent signal.
- 7-8: Strong fit. Matches most ICP criteria with minor gaps.
- 5-6: Moderate fit. Matches some criteria but has notable gaps.
- 3-4: Weak fit. Few ICP matches, unlikely to convert.
- 1-2: Poor fit. Does not match ICP at all.
**Routing Rules:**
- Score 7-10: "Immediate outreach"
- Score 4-6: "Nurture sequence"
- Score 1-3: "Low priority"
**Lead Data:**
- Company: {company_name}
- Industry: {industry}
- Employee Count: {employee_count}
- Annual Revenue: {annual_revenue}
- Contact Title: {job_title}
- Lead Source: {lead_source}
- Company Description: {company_description}
Return your assessment as JSON with exactly these keys: score, reasoning, routing_recommendation"""
def get_connection():
conn_str = (
r"DRIVER={ODBC Driver 17 for SQL Server};"
r"SERVER=(localdb)\MSSQLLocalDB;"
r"DATABASE=LeadScoring;"
r"Trusted_Connection=yes;"
)
return pyodbc.connect(conn_str)
def fetch_unscored_leads(cursor):
cursor.execute("""
SELECT lead_id, company_name, industry, employee_count,
annual_revenue, job_title, lead_source, company_description
FROM leads
WHERE score IS NULL
""")
columns = [desc[0] for desc in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
def score_lead(lead):
prompt = ICP_PROMPT.format(
company_name=lead["company_name"],
industry=lead["industry"],
employee_count=lead["employee_count"],
annual_revenue=lead["annual_revenue"],
job_title=lead["job_title"],
lead_source=lead["lead_source"],
company_description=lead["company_description"],
)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_json_schema={
"type": "object",
"properties": {
"score": {"type": "integer"},
"reasoning": {"type": "string"},
"routing_recommendation": {"type": "string"},
},
"required": ["score", "reasoning", "routing_recommendation"],
},
temperature=0.2,
),
)
return json.loads(response.text)
def update_lead_score(cursor, lead_id, result):
cursor.execute("""
UPDATE leads
SET score = ?, score_reasoning = ?, routing_recommendation = ?, scored_at = ?
WHERE lead_id = ?
""", (
result["score"],
result["reasoning"][:1000],
result["routing_recommendation"],
datetime.now(),
lead_id,
))
def main():
conn = get_connection()
cursor = conn.cursor()
leads = fetch_unscored_leads(cursor)
print(f"Found {len(leads)} unscored leads.\n")
for lead in leads:
try:
result = score_lead(lead)
update_lead_score(cursor, lead["lead_id"], result)
conn.commit()
print(f" [{result['score']:>2}/10] {lead['company_name']:<25} -> {result['routing_recommendation']}")
except Exception as e:
print(f" [ERR] {lead['company_name']:<25} -> {e}")
print("\nScoring complete.")
cursor.execute("SELECT COUNT(*) FROM leads WHERE score IS NOT NULL")
scored = cursor.fetchone()[0]
print(f"Total scored leads: {scored}")
cursor.close()
conn.close()
if __name__ == "__main__":
main()
Run the pipeline and verify scored leads
Time to put the pipeline to work. The script will process all 15 leads, printing each score and routing recommendation as it goes.
- Run the scoring pipeline by executing this command in your VS Code terminal:
python score_leads.py
- Wait for the script to finish processing all 15 leads. This takes about 30-60 seconds since each lead requires a separate API call.
You should see output like this in your terminal:
Found 15 unscored leads.
[ 9/10] Nextera SaaS -> Immediate outreach
[ 8/10] Quantum Fintech -> Immediate outreach
[ 7/10] CloudStack Pro -> Immediate outreach
[ 2/10] GreenLeaf Manufacturing -> Low priority
...
Scoring complete.
Total scored leads: 15
What should I see?
Each line shows a lead's score (1-10), company name, and routing recommendation. High-fit leads like SaaS companies with VP-level contacts and inbound lead sources score 7-10 and route to "Immediate outreach". Low-fit leads like manufacturers with non-decision-maker contacts score 1-3 and route to "Low priority".
Your exact scores may differ slightly from the example because the LLM has some variance, but the overall pattern should hold.
Seeing API errors or 0 scored leads?
If you see [ERR] next to every lead, your API key is likely not loading correctly. Open your .env file and confirm it has GEMINI_API_KEY= followed by your actual key with no spaces around the equals sign.
If you see a rate limit error, wait 60 seconds and run the script again. The free tier has per-minute request limits. Leads that were already scored will be skipped on the second run because their score column is no longer NULL.
help me debug Gemini API errors in my lead scoring script
All 15 leads are now scored, reasoned, and routed in your database. Next up, you will connect Tableau to this data and build a dashboard that makes the scores visible to stakeholders.
Connect Tableau and Build the Dashboard
Your scored leads are sitting in the database with scores, reasoning, and routing recommendations. But stakeholders don't query SQL directly.
In this step, you'll connect Tableau Desktop live to your LocalDB instance and build a dashboard that makes the AI scoring output legible to any hiring manager or sales leader.
In this step, get ready to:
- Connect Tableau Desktop to your LocalDB leads table.
- Build three visualizations showing lead prioritization and routing.
- Combine everything into a stakeholder-ready dashboard.
Connect Tableau Desktop to your database
Tableau Desktop has a native Microsoft SQL Server connector that works directly with LocalDB. You'll point it at your running instance and pull in the scored leads table.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Tableau Desktop and press Enter to open it.
- On the start page, look at the left panel under Connect.
- Click Microsoft SQL Server.
A connection dialog appears asking for your server details.
- In the Server field, enter (localdb)\MSSQLLocalDB.
- Make sure Use Windows Authentication is selected (this is the default).
- Click Sign In.
Why Windows Authentication?
LocalDB runs under your Windows user account. Windows Authentication passes your logged-in credentials automatically, so you never need a separate username or password.
After connecting, Tableau shows you the database and table selection page.
- In the Database dropdown, select LeadScoring.
- In the left panel under Table, find leads and drag it onto the canvas area in the center.
Tableau displays a data preview showing your 15 scored leads with all columns. You should see score, score_reasoning, and routing_recommendation populated for every row.
Can't see the LeadScoring database?
Make sure your LocalDB instance is running. Switch back to VS Code and run sqllocaldb start MSSQLLocalDB in the terminal, then retry the connection in Tableau.
Also double-check that the server name is exactly (localdb)\MSSQLLocalDB with the backslash, not a forward slash.
Help me connect Tableau to my LocalDB instance
Build the three visualizations
A good stakeholder dashboard answers three questions at a glance: which leads are hottest, how scores are distributed, and where each lead should be routed. You'll build one visualization for each.
Visualization 1: horizontal bar chart ranked by score
This chart shows every lead ranked from highest to lowest score, color-coded so a stakeholder instantly sees who to prioritize.
- Click Sheet 1 at the bottom of the Tableau window to open a new worksheet.
- Drag Company Name from the left data panel to the Rows shelf.
- Drag Score to the Columns shelf.
- Click the sort descending button in the toolbar (the icon with a downward arrow and bars) to rank leads from highest to lowest score.
Now add color-coding to distinguish Hot, Warm, and Cold tiers.
- Drag Score to the Color mark on the Marks card.
- Click the color legend that appears, then click Edit Colors.
- Change the color palette to Red-Green Diverging so that high scores appear green (Hot), mid scores yellow (Warm), and low scores red (Cold).
- Click OK to apply.
- Right-click the sheet tab at the bottom and rename it to Lead Rankings.
You should see a horizontal bar chart with all 15 companies ranked by score, colored from green (high) to red (low).
Visualization 2: score distribution histogram
This histogram shows how scores are distributed across all leads. It helps stakeholders quickly see whether most leads are high-fit or low-fit.
- Click the new worksheet icon (the tab with a plus sign) at the bottom of the window.
- Drag Score to the Columns shelf.
- Right-click Score on the Columns shelf and select Dimension (this treats each score as a discrete bucket rather than a continuous range).
- Drag leads (Count) or the Number of Records measure to the Rows shelf.
- Right-click the sheet tab and rename it to Score Distribution.
You should see a bar chart with score values along the x-axis and the count of leads for each score on the y-axis.
Visualization 3: routing recommendation breakdown
This table shows how many leads fall into each routing category. Sales teams use this to understand pipeline volume at each tier.
- Click the new worksheet icon to create another sheet.
- Drag Routing Recommendation to the Rows shelf.
- Drag leads (Count) or Number of Records to the Columns shelf.
- Click Show Me in the top-right toolbar and select the text table option (grid icon).
- Right-click the sheet tab and rename it to Routing Breakdown.
You should see three rows: Immediate outreach, Nurture sequence, and Low priority, each with a count of leads in that category.
Why these three visualizations?
Each one answers a different stakeholder question. The bar chart answers "who should we call first?" The histogram answers "is our pipeline healthy or bottom-heavy?" The routing table answers "how many leads need immediate action vs nurturing?"
Combine into a dashboard and save
Individual sheets are useful for building, but stakeholders want everything on one screen. A Tableau dashboard lets you combine all three views into a single page with a title and context.
- Click the New Dashboard icon at the bottom of the window (the tab icon with a grid).
- From the Sheets panel on the left, drag Lead Rankings onto the dashboard canvas.
- Drag Score Distribution to the right side of the canvas.
- Drag Routing Breakdown below the Score Distribution chart.
Now add a title and context so stakeholders understand the scoring logic.
- Double-click the dashboard title at the top.
- Change it to AI Lead Scoring - Pipeline Prioritization.
- Click OK.
- In the left panel under Objects, drag a Text object onto the dashboard (below the title or in an empty corner).
- In the text editor that opens, type your ICP criteria summary:
Use something like this for the ICP text box content:
- ICP: SaaS/Fintech/Enterprise Software | 50-1000 employees | $5M-$100M revenue | Decision-maker title | Inbound/Referral source
- Click OK to add the text box to the dashboard.
Why include ICP criteria on the dashboard?
Stakeholders who view this dashboard may not know how the scores were generated. The ICP text box gives immediate context so they trust the output without needing to read the Python code.
Finally, save the workbook to your project folder.
- Click File in the top menu bar.
- Click Save As.
- Navigate to your project folder on the Desktop.
- Name the file lead_scoring_dashboard and make sure the file type is Tableau Packaged Workbook (.twbx).
- Click Save.
You should now see lead_scoring_dashboard.twbx in your project folder. Your dashboard shows all three visualizations with the title and ICP context in one view.
Dashboard looks cramped or overlapping?
Try resizing the individual sheets on the dashboard by clicking their edges and dragging. You can also change the dashboard size under Dashboard > Size in the left panel. Choose Automatic if the fixed dimensions don't fit your screen.
Help me arrange my Tableau dashboard layout
Your AI-scored leads now have a professional visualization layer. Next up, you'll push the entire project to GitHub as a portfolio piece.
Push to GitHub
Your pipeline scores leads and your dashboard visualizes the results. The last piece is making this project shareable and version-controlled.
In this step, you will initialize a Git repository, write a README that explains the project to anyone who finds it, and push everything to GitHub.
In this step, get ready to:
- Initialize a Git repository and create a README.
- Commit and push your code to GitHub.
- Export the Tableau dashboard as a screenshot and push it to your repository.
Initialize your repository and create a README
A Git repository tracks every change you make to your project files. A README is the first thing visitors see on your GitHub page.
- Initialize a new Git repository in your project folder by running this command:
git init
- Confirm the repository was initialized by running:
ls -a
You should see a .git folder in the output.
Don't see .git?
Make sure your terminal is inside your project folder. Run pwd to check your current directory.
Help me figure out why git init isn't working in my project folder.
Now you need a README that explains your project to anyone who visits the repository. This is your chance to communicate the architecture and setup process clearly.
- Create a new file called README.md in your project folder and paste the following content:
# AI-Powered Lead Scoring Pipeline
An end-to-end RevOps workflow that scores B2B leads against an Ideal Customer Profile (ICP) using AI, stores results in SQL Server, and visualizes the output in Tableau.
## Architecture
```
SQL Server LocalDB (Lead Data)
|
v
Python Pipeline (score_leads.py)
|
v
Google Gemini API (AI Scoring)
|
v
SQL Server LocalDB (Scored Results)
|
v
Tableau Desktop (Dashboard)
```
## What It Does
1. Stores B2B lead/account data in a SQL Server database
2. Pulls unscored leads and sends each to Google Gemini with structured ICP criteria
3. Writes back a fit score (1-10), reasoning, and routing recommendation
4. Tableau connects live to the database for stakeholder-ready visualization
## Setup
1. Ensure SQL Server LocalDB is running: `sqllocaldb start MSSQLLocalDB`
2. Install Python dependencies: `pip install google-genai pyodbc python-dotenv`
3. Create a `.env` file with your Gemini API key: `GEMINI_API_KEY=your-key`
4. Run database setup: `python setup_database.py`
5. Run the scoring pipeline: `python score_leads.py`
6. Open Tableau Desktop and connect to `(localdb)\MSSQLLocalDB` > `LeadScoring`
## Dashboard

## Tech Stack
- Python 3.14
- SQL Server LocalDB
- Google Gemini API (free tier)
- Tableau Desktop
What does this README include?
- An architecture diagram showing the data flow from SQL to Python to Gemini and back to SQL, ending in Tableau.
- A setup section so someone else (or future you) can reproduce the project from scratch.
- A dashboard image placeholder that will display once you export the screenshot in Substep 3.
- Save README.md.
- Stage all project files for commit by running:
git add .
What about .env and .twbx?
Your .gitignore file already excludes .env (your API key), __pycache__/, and *.twbx files. Git will skip these automatically when you run git add ..
- Verify which files are staged by running:
git status
You should see setup_database.py, score_leads.py, README.md, and .gitignore listed in green. You should NOT see .env or any .twbx files.
Commit and push to GitHub
You need a remote repository on GitHub to host your code. You will create it on github.com, then connect your local repository to it.
- Open your browser and go to github.com.
- Click the + icon in the top right corner.
- Select New repository.
- Enter ai-lead-scoring-pipeline as the repository name.
- Leave all other settings as default (Public, no README, no .gitignore, no license).
- Click Create repository.
Why no README or .gitignore on GitHub?
You already have both files locally. If GitHub creates them too, you will get a merge conflict on your very first push.
- Back in your VS Code terminal, connect your local repository to GitHub and push by running these commands:
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/ai-lead-scoring-pipeline.git
git branch -M main
git commit -m "Initial commit: AI lead scoring pipeline"
git push -u origin main
What do these commands do?
- git remote add origin links your local repository to the GitHub repository you just created.
- git branch -M main renames your default branch to main.
- git commit saves a snapshot of all staged files with a descriptive message.
- git push -u origin main uploads your commit to GitHub and sets main as the default tracking branch.
- Refresh your GitHub repository page in the browser.
You should see setup_database.py, score_leads.py, README.md, and .gitignore listed in the repository. The README content should render below the file list.
Push failed or permission denied?
If Git asks for authentication, you may need to sign in. GitHub no longer accepts passwords for Git operations. Use a Personal Access Token or sign in through the browser prompt that appears.
Double-check that the URL in your git remote add origin command matches the URL shown on your new GitHub repository page.
Help me troubleshoot why my git push to GitHub is failing.
Export the dashboard screenshot and push again
Your README already references an image called dashboard_screenshot.png. Now you will export the actual image from Tableau so GitHub can display it.
- Switch back to Tableau Desktop.
- Make sure you are viewing your AI Lead Scoring - Pipeline Prioritization dashboard (click its tab at the bottom if needed).
- Click Dashboard in the top menu bar.
- Click Export Image.
- In the save dialog, navigate to your project folder.
- Name the file dashboard_screenshot.png and save it.
- Back in your VS Code terminal, confirm the file exists by running:
ls
You should see dashboard_screenshot.png listed alongside your other project files.
- Add the screenshot, commit, and push to GitHub by running these commands:
git add dashboard_screenshot.png
git commit -m "Add dashboard screenshot"
git push
What does this do?
This stages only the new image file, commits it with a descriptive message, and pushes to your existing remote. Since you already set the tracking branch with -u earlier, a plain git push is enough now.
- Refresh your GitHub repository page.
Scroll down to the README section. You should see your Tableau dashboard image rendered inline under the Dashboard heading.
Image not showing in the README?
Make sure the filename is exactly dashboard_screenshot.png (case-sensitive). The markdown image tag in your README must match the filename character for character.
Also confirm the file was committed by checking git status. If the PNG is still listed as untracked, stage and commit it again.
Help me figure out why my image isn't rendering in my GitHub README.
✔️ Awesome, I've got everything!
Great. Double check you have saved all your files and that your GitHub repo shows the dashboard image in the README.
ⓧ I'd like to double check the full code
Here is the full README.md (the only new file created in this step):
# AI-Powered Lead Scoring Pipeline
An end-to-end RevOps workflow that scores B2B leads against an Ideal Customer Profile (ICP) using AI, stores results in SQL Server, and visualizes the output in Tableau.
## Architecture
```
SQL Server LocalDB (Lead Data)
|
v
Python Pipeline (score_leads.py)
|
v
Google Gemini API (AI Scoring)
|
v
SQL Server LocalDB (Scored Results)
|
v
Tableau Desktop (Dashboard)
```
## What It Does
1. Stores B2B lead/account data in a SQL Server database
2. Pulls unscored leads and sends each to Google Gemini with structured ICP criteria
3. Writes back a fit score (1-10), reasoning, and routing recommendation
4. Tableau connects live to the database for stakeholder-ready visualization
## Setup
1. Ensure SQL Server LocalDB is running: `sqllocaldb start MSSQLLocalDB`
2. Install Python dependencies: `pip install google-genai pyodbc python-dotenv`
3. Create a `.env` file with your Gemini API key: `GEMINI_API_KEY=your-key`
4. Run database setup: `python setup_database.py`
5. Run the scoring pipeline: `python score_leads.py`
6. Open Tableau Desktop and connect to `(localdb)\MSSQLLocalDB` > `LeadScoring`
## Dashboard

## Tech Stack
- Python 3.14
- SQL Server LocalDB
- Google Gemini API (free tier)
- Tableau Desktop
Secret mission
Add Deal Velocity Prediction
Your lead scoring pipeline tells reps WHO to call first. But what if it could also tell them HOW LONG each deal will take to close? Extend your pipeline to predict days-to-close for every lead, then visualize the relationship between score and deal velocity in Tableau.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with a free-tier API key, so there are no ongoing costs.
Resources you used:
- SQL Server LocalDB database LeadScoring (local, no cloud costs).
- Google Gemini API key (free tier, no charges within free tier limits).
- Local project folder on your Desktop with Python scripts, .env, Tableau workbook, and screenshots.
- GitHub repository ai-lead-scoring-pipeline.
Keep everything running
No action needed. Choose this if you are still actively building or want to extend the pipeline later.
- The LocalDB database is local and costs nothing. It stops automatically when not in use.
- Your Gemini API key remains free within the free tier limits.
- Your project folder and GitHub repository stay as they are.
Pause - I'll come back to this later
Shut down running processes to free up memory. Your files and data remain intact.
- LocalDB stops automatically when no connections are active. No manual action needed.
- Your project folder, GitHub repo, and API key all remain available when you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
- Delete the LocalDB instance (this removes all databases including LeadScoring) by running these commands in your terminal:
- Delete your project folder from the Desktop by running:
- Delete your Gemini API key. Go to the AI Studio API Keys page at aistudio.google.com/app/apikey. Find your key in the list and delete it.
- Delete your GitHub repository. On github.com, navigate to your ai-lead-scoring-pipeline repository. Click Settings, scroll to the bottom of the page, and click Delete this repository under the Danger Zone section.
sqllocaldb stop MSSQLLocalDB
sqllocaldb delete MSSQLLocalDB
What does this do?
The sqllocaldb stop command shuts down the running instance. The sqllocaldb delete command permanently removes the instance and all databases associated with it, including LeadScoring.
cd ~/Desktop
rmdir /s /q ai-lead-scoring-pipeline
macOS or Linux?
If you are on macOS or Linux, use this command instead:
cd ~/Desktop
rm -rf ai-lead-scoring-pipeline
Nice Work!
Nice Work!
You just built a complete AI-powered lead scoring pipeline from scratch. Raw B2B lead data goes into SQL, Python scores it against your ICP using Google Gemini, and Tableau makes the results legible to any stakeholder.
You've learned how to:
- Build a Python ETL pipeline that pulls unscored leads from SQL Server and sends each to the Google Gemini API for AI-powered scoring against an ICP definition.
- Engineer a structured prompt with a JSON response schema that returns consistent scores, reasoning, and routing recommendations from an LLM.
- Connect Tableau Desktop live to your SQL database and build a stakeholder-ready dashboard with lead prioritization visuals and score distribution charts.
- Secret Mission: Added a deal velocity prediction that estimates days-to-close for each lead based on attributes and score tier.
Ready to quiz yourself?