Automate job filtering and cover letter drafting locally with Python and SQLite.
Welcome to another official UVF IT beginner build. Today we are constructing a lightweight, local-first clone of an AI Job Search Agent, inspired by the workflow of MadsLorentzen's popular open-source framework.
Instead of relying on complex cloud pipelines or paid APIs, we will build a minimal working prototype using Python and SQLite. This guide focuses on the core logic: ingesting job data, filtering it based on criteria, and generating tailored application materials.
sqlite3 available in your Python environment (standard library) and requests installed via pip.Create the project directory and initialize the SQLite database with a schema for storing job postings. We will define a simple table with fields for title, company, and description.
Write the initialization script that creates the database file and inserts sample data so we have something to work with immediately.
setup.sh
#!/bin/bash
mkdir -p uvf_ai_job_search
cd uvf_ai_job_search
pip install requests
python -c "import sqlite3; print('SQLite3 ready')"db_init.py
import sqlite3
DB_NAME = 'jobs.db'
def init_db():
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
# Create table
cursor.execute('''
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
company TEXT NOT NULL,
description TEXT NOT NULL,
keywords TEXT
)
''')
# Insert sample data if empty
cursor.execute('SELECT count(*) FROM jobs')
if cursor.fetchone()[0] == 0:
sample_jobs = [
('Senior Python Developer', 'TechCorp', 'We are looking for an experienced Python developer to join our remote team. Must have 5+ years experience.', 'python,remote,senior'),
('Junior Web Developer', 'StartupInc', 'Looking for a junior developer to help build our web platform. Knowledge of JavaScript and React required.', 'javascript,react,junior'),
('Data Scientist', 'DataDriven', 'Seeking a data scientist with strong Python and SQL skills to analyze large datasets.', 'python,sql,data')
]
cursor.executemany('INSERT INTO jobs (title, company, description, keywords) VALUES (?, ?, ?, ?)', sample_jobs)
conn.commit()
print(f'Inserted {len(sample_jobs)} sample jobs.')
else:
print('Database already populated.')
conn.close()
if __name__ == '__main__':
init_db()
print('Database initialized successfully.')Implement the core filtering logic. We will create a function that queries the SQLite database for jobs matching specific keywords (e.g., 'Python', 'Remote').
This step replaces the complex scraping engine with a simple data retrieval mechanism, allowing us to focus on the AI integration later.
job_filter.py
import sqlite3
DB_NAME = 'jobs.db'
def filter_jobs(keywords):
"""
Query the database for jobs matching the provided keywords.
Keywords is a list of strings.
"""
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
# Simple LIKE query for each keyword
# Note: This is a basic implementation. For production, consider full-text search.
conditions = " OR ".join(["description LIKE ?" for _ in keywords])
params = [f'%{kw.lower()}%' for kw in keywords]
query = f"SELECT id, title, company, description FROM jobs WHERE {conditions}"
try:
cursor.execute(query, params)
results = cursor.fetchall()
return results
except Exception as e:
print(f"Error querying database: {e}")
return []
finally:
conn.close()
if __name__ == '__main__':
# Test the filter
keywords = ['Python']
jobs = filter_jobs(keywords)
print(f"Found {len(jobs)} jobs matching '{keywords}':")
for job in jobs:
print(f"- {job[1]} at {job[2]}")Integrate an LLM API to generate tailored cover letters. We will use the requests library to send the job description and a static user profile to the API.
Parse the JSON response from the AI and format it into a readable string. This mimics the 'tailor your CV' feature of the original project.
ai_agent.py
import requests
import os
import json
# Use environment variable for API key, or set a default for testing
API_KEY = os.getenv('OPENAI_API_KEY', 'sk-your-api-key-here')
API_URL = 'https://api.openai.com/v1/chat/completions'
def generate_cover_letter(job_title, company, job_description, user_profile):
"""
Generate a cover letter using the OpenAI API.
"""
if API_KEY == 'sk-your-api-key-here':
return "[API Key not set. Set OPENAI_API_KEY environment variable.]"
prompt = f"""
Write a professional cover letter for the following job application:
Job Title: {job_title}
Company: {company}
Job Description: {job_description}
User Profile: {user_profile}
Please keep the tone professional and enthusiastic.
"""
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {API_KEY}'
}
payload = {
'model': 'gpt-3.5-turbo',
'messages': [
{'role': 'system', 'content': 'You are a helpful career assistant.'},
{'role': 'user', 'content': prompt}
],
'max_tokens': 500
}
try:
response = requests.post(API_URL, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
return data['choices'][0]['message']['content']
except requests.exceptions.RequestException as e:
print(f"API Error: {e}")
return None
except (KeyError, IndexError) as e:
print(f"Response parsing error: {e}")
return None
if __name__ == '__main__':
# Test the AI agent
test_job = {
'title': 'Python Developer',
'company': 'TechCorp',
'description': 'Looking for a Python expert.'
}
user_profile = "I am a passionate Python developer with 3 years of experience."
letter = generate_cover_letter(
test_job['title'],
test_job['company'],
test_job['description'],
user_profile
)
print("Generated Cover Letter:")
print(letter)Combine the database query and AI generation into a single main script. This script will find matching jobs and generate a cover letter for the first match.
Add basic error handling for API failures and database connections to ensure the script runs smoothly locally.
main.py
import sqlite3
import json
import os
from job_filter import get_matching_jobs
from ai_agent import generate_cover_letter
def main():
db_path = "jobs.db"
if not os.path.exists(db_path):
print("Error: Database not found. Run db_init.py first.")
return
try:
# 1. Filter jobs
keywords = ["Python", "Remote"]
jobs = get_matching_jobs(db_path, keywords)
if not jobs:
print("No matching jobs found.")
return
print(f"Found {len(jobs)} matching job(s).")
# 2. Generate cover letter for the first match
target_job = jobs[0]
print(f"\nGenerating cover letter for: {target_job['title']} at {target_job['company']}...")
# Mock user profile (static for this minimal clone)
user_profile = {
"name": "Alex Dev",
"experience": "3 years in Python backend development",
"skills": ["Python", "SQL", "Docker"]
}
cover_letter = generate_cover_letter(target_job, user_profile)
# 3. Output result
print("\n--- Generated Cover Letter ---")
print(cover_letter)
print("--- End of Letter ---")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()Execute the main script to verify the end-to-end flow. You should see a job title printed followed by a generated cover letter.
Check the SQLite database to ensure the job data persists and can be queried again.
python main.py| Check | Command / Why it matters |
|---|---|
| Database created with sample data | sqlite3 jobs.db 'SELECT count(*) FROM jobs;' should return > 0 |
| Filtering works correctly | Running python job_filter.py should print job titles matching keywords |
| AI generates text | Running python main.py should output a cover letter string |
| Error handling is robust | Running python main.py with an invalid API key should print a clear error message instead of crashing |
To extend this clone, consider adding a simple CLI using argparse to allow users to input their own keywords, or integrate a real resume parser using pdfplumber.