Skip to content

Commit cb0f66c

Browse files
committed
Add a new example project on AI resume parsing and job matching
1 parent 91de9da commit cb0f66c

File tree

12 files changed

+724
-0
lines changed

12 files changed

+724
-0
lines changed

ai-resume-job-matching/.env.example

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FIRECRAWL_API_KEY="your_firecrawl_api_key"
2+
ANTHROPIC_API_KEY="your_anthropic_api_key"
3+
SUPABASE_URL="your_supabase_url"
4+
SUPABASE_KEY="your_supabase_key"
5+
DISCORD_WEBHOOK_URL="your_discord_webhook_url"
6+
RESUME_URL="your_resume_url"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Job Matcher Scheduler
2+
3+
on:
4+
push:
5+
branches: [main]
6+
schedule:
7+
- cron: "0 0 * * 1" # Run every start of the week
8+
9+
jobs:
10+
check-jobs:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v2
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v2
18+
with:
19+
python-version: "3.10"
20+
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install -r requirements.txt
25+
26+
- name: Run job checker
27+
env:
28+
FIRECRAWL_API_KEY: ${{ secrets.FIRECRAWL_API_KEY }}
29+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
30+
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
31+
RESUME_URL: ${{ secrets.RESUME_URL }}
32+
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
33+
SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
34+
CHECK_INTERVAL_MINUTES: 15
35+
run: |
36+
python -m src.scheduler

ai-resume-job-matching/.gitignore

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Virtual Environment
24+
venv/
25+
env/
26+
ENV/
27+
.env
28+
.venv
29+
30+
# IDE
31+
.idea/
32+
.vscode/
33+
*.swp
34+
*.swo
35+
.DS_Store
36+
37+
# Project specific
38+
*.log
39+
.coverage
40+
htmlcov/
41+
.pytest_cache/
42+
.mypy_cache/

ai-resume-job-matching/README.md

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# AI Resume Job Matcher
2+
3+
An intelligent job matching system that automatically analyzes job postings against your resume using AI, with real-time notifications for matching positions.
4+
5+
## Features
6+
7+
- 🤖 AI-powered job matching using Claude 3 Sonnet
8+
- 🔍 Automated job posting scraping
9+
- 📄 Resume PDF parsing
10+
- 💬 Discord notifications for matching jobs
11+
- 🔄 Scheduled job checking
12+
- 📊 Web interface for managing job sources
13+
- 🗃️ Persistent storage with Supabase
14+
15+
## Prerequisites
16+
17+
- Python 3.10+
18+
- [Supabase](https://supabase.com) account
19+
- [Discord webhook](https://discord.com/developers/docs/resources/webhook) (for notifications)
20+
- [Anthropic](https://www.anthropic.com) API key
21+
- [Firecrawl](https://firecrawl.co) API key
22+
23+
## Setup
24+
25+
1. **Clone the repository**
26+
27+
```bash
28+
git clone https://github.com/yourusername/ai-resume-matcher.git
29+
cd ai-resume-matcher
30+
```
31+
32+
2. **Create a virtual environment**
33+
34+
```bash
35+
python -m venv venv
36+
source venv/bin/activate # On Windows: venv\Scripts\activate
37+
```
38+
39+
3. **Install dependencies**
40+
41+
```bash
42+
pip install -r requirements.txt
43+
```
44+
45+
4. **Configure environment variables**
46+
47+
Create a `.env` file in the root directory with the following variables:
48+
49+
```bash
50+
FIRECRAWL_API_KEY=your_firecrawl_key
51+
ANTHROPIC_API_KEY=your_anthropic_key
52+
DISCORD_WEBHOOK_URL=your_discord_webhook_url
53+
RESUME_URL=your_resume_pdf_url
54+
SUPABASE_URL=your_supabase_url
55+
SUPABASE_KEY=your_supabase_key
56+
CHECK_INTERVAL_MINUTES=15
57+
```
58+
59+
5. **Set up Supabase**
60+
61+
Create a new Supabase project and create the following table:
62+
63+
```sql
64+
create table job_sources (
65+
url text primary key,
66+
last_checked timestamp with time zone
67+
);
68+
```
69+
70+
## Running Locally
71+
72+
1. **Start the Streamlit web interface**
73+
74+
```bash
75+
streamlit run app.py
76+
```
77+
78+
2. **Run the job checker scheduler**
79+
80+
```bash
81+
python -m src.scheduler
82+
```
83+
84+
## Deployment
85+
86+
### GitHub Actions Scheduler
87+
88+
The project includes a GitHub Actions workflow that runs the job checker on a schedule. To set it up:
89+
90+
1. Add all environment variables from the `.env` file as GitHub repository secrets
91+
2. The scheduler will run automatically every Monday (configurable in `.github/workflows/scheduler.yml`)
92+
93+
### Manual Deployment
94+
95+
1. Set up a server with Python 3.10+
96+
2. Clone the repository and follow the local setup steps
97+
3. Use a process manager like PM2 or Supervisor to run:
98+
99+
```bash
100+
# For the web interface
101+
streamlit run app.py
102+
103+
# For the scheduler
104+
python -m src.scheduler
105+
```
106+
107+
## Project Structure
108+
109+
- `app.py`: Main Streamlit web application
110+
- `src/`
111+
- `scraper.py`: Job and resume parsing logic
112+
- `matcher.py`: AI-powered job matching
113+
- `discord.py`: Discord notification system
114+
- `database.py`: Supabase database operations
115+
- `models.py`: Pydantic data models
116+
- `scheduler.py`: Automated job checking
117+
118+
## Contributing
119+
120+
1. Fork the repository
121+
2. Create a feature branch
122+
3. Commit your changes
123+
4. Push to the branch
124+
5. Create a Pull Request
125+
126+
## License
127+
128+
This project is licensed under the MIT License - see the LICENSE file for details.

ai-resume-job-matching/app.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import streamlit as st
2+
import asyncio
3+
from dotenv import load_dotenv
4+
from src.scraper import JobScraper
5+
from src.matcher import JobMatcher
6+
from src.discord import DiscordNotifier
7+
from src.database import Database
8+
9+
load_dotenv()
10+
11+
12+
async def process_job(scraper, matcher, notifier, job, resume_content):
13+
"""Process a single job posting"""
14+
job_content = await scraper.scrape_job_content(job.url)
15+
result = await matcher.evaluate_match(resume_content, job_content)
16+
17+
if result["is_match"]:
18+
await notifier.send_match(job, result["reason"])
19+
20+
return job, result
21+
22+
23+
async def main():
24+
"""Main function to run the resume parser application."""
25+
st.title("Resume Parser and Job Matcher")
26+
27+
# Initialize services
28+
scraper = JobScraper()
29+
matcher = JobMatcher()
30+
notifier = DiscordNotifier()
31+
db = Database()
32+
33+
# Sidebar for managing job sources
34+
with st.sidebar:
35+
st.header("Manage Job Sources")
36+
37+
# Add new job source
38+
new_source = st.text_input(
39+
"Add Job Source URL", placeholder="https://www.company.com/jobs"
40+
)
41+
42+
if st.button("Add Source"):
43+
db.save_job_source(new_source)
44+
st.success("Job source added!")
45+
46+
# List and delete existing sources
47+
st.subheader("Current Sources")
48+
for source in db.get_job_sources():
49+
col1, col2 = st.columns([3, 1])
50+
with col1:
51+
st.text(source.url)
52+
with col2:
53+
if st.button("Delete", key=source.url):
54+
db.delete_job_source(source.url)
55+
st.rerun()
56+
57+
# Main content
58+
st.markdown(
59+
"""
60+
This app helps you find matching jobs by:
61+
- Analyzing your resume from a PDF URL
62+
- Scraping job postings from your saved job sources
63+
- Using AI to evaluate if you're a good fit for each position
64+
65+
Simply paste your resume URL below to get started!
66+
"""
67+
)
68+
69+
# Resume PDF URL input
70+
resume_url = st.text_input(
71+
"**Enter Resume PDF URL**",
72+
placeholder="https://www.website.com/resume.pdf",
73+
)
74+
75+
if st.button("Analyze") and resume_url:
76+
with st.spinner("Parsing resume..."):
77+
resume_content = await scraper.parse_resume(resume_url)
78+
79+
# Get job sources from database
80+
sources = db.get_job_sources()
81+
if not sources:
82+
st.warning("No job sources configured. Add some in the sidebar!")
83+
return
84+
85+
with st.spinner("Scraping job postings..."):
86+
jobs = await scraper.scrape_job_postings([s.url for s in sources])
87+
88+
if not jobs:
89+
st.warning("No jobs found in the configured sources.")
90+
return
91+
92+
with st.spinner(f"Analyzing {len(jobs)} jobs..."):
93+
tasks = []
94+
for job in jobs:
95+
task = process_job(scraper, matcher, notifier, job, resume_content)
96+
tasks.append(task)
97+
98+
for coro in asyncio.as_completed(tasks):
99+
job, result = await coro
100+
st.subheader(f"Job: {job.title}")
101+
st.write(f"URL: {job.url}")
102+
st.write(f"Match: {'✅' if result['is_match'] else '❌'}")
103+
st.write(f"Reason: {result['reason']}")
104+
st.divider()
105+
106+
st.success(f"Analysis complete! Processed {len(jobs)} jobs.")
107+
108+
109+
if __name__ == "__main__":
110+
asyncio.run(main())

0 commit comments

Comments
 (0)