JAA-ATS-Tool / README.md
saitejatirunagari's picture
feat(ui): merge legacy platform checkboxes into unified platform selector
53c490d
|
Raw
History Blame
12.1 kB
metadata
title: JAA ATS Tool
emoji: πŸ€–
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
license: mit

Job Automation Agent β€” PM Edition

Automated Product Manager job search, AI-powered assessment, ATS-optimized resume generation, and Google Sheets reporting β€” all in one pipeline.


What It Does

Step What Happens
1 Parses your PDF resume (Kimi-K2.6, ~5s)
2 Scrapes PM-only jobs from LinkedIn, Indeed, Glassdoor, Remotive, WeWorkRemotely β€” ALL selected roles Γ— locations, last N days
3 Filters non-PM roles at scrape time; dedup by URL + (title, company) + 30-day SQLite history; platform-level total cap
4 Fetches FULL job descriptions in bulk (parallel HTTP / single browser session)
5 Assesses ALL jobs using 7 parallel AI models (10-model pool via NVIDIA API)
6 Generates ATS-optimized resumes for ALL jobs β€” DOCX and PDF, After β‰₯ Before guaranteed, target 95%
7 Writes everything to your Google Sheet with direct job links
8 Saves local Excel report + resumes in data/output/resumes/YYYY-MM-DD/
9 Saves run to history β€” reload past runs in one click from the History panel

Quick Start

# 1. Install dependencies
pip install -r requirements.txt
playwright install chromium

# 2. Copy and fill in your API keys
copy .env.example .env
# Edit .env with your NVIDIA_API_KEY, GOOGLE_SHEET_ID, etc.

# 3. Place your resume PDF
# Copy your resume to: data/resume/resume.pdf

# 4. Connect Google (one-time browser login)
python connect_google.py

# 5. Run a test (5 jobs)
# In config.py: ASSESSMENT["test_mode"] = True
python main.py

# 6. Run full production mode (all 100+ PM jobs)
# In config.py: ASSESSMENT["test_mode"] = False
python main.py

# 7. Or use the Streamlit UI
streamlit run ui.py

ever-jobs Integration (160+ Platforms)

This project integrates the ever-jobs NestJS service, which provides REST API access to 160+ job board scrapers.

Prerequisites

Setup (one-time)

# Clone ever-jobs to vendor/ directory (done automatically during setup)
git clone https://github.com/ever-jobs/ever-jobs.git vendor/ever-jobs --depth=1

# Start via Docker (preferred)
cd vendor/ever-jobs && docker compose up -d

# Verify API is running
curl http://localhost:3001/health
# OR from Python:
python -c "from src.ever_jobs_bridge.server import is_running; print(is_running())"

Automatic Startup

The pipeline calls ensure_running() automatically before any ever-jobs platforms are scraped. It tries Docker first, falls back to npm run start if Docker is unavailable.

Platform Selection

The UI has a single "🌐 Job Platforms" section with three groups. Selecting LinkedIn, Indeed, Glassdoor, Remotive, WeWorkRemotely, or Naukri uses their dedicated high-quality scrapers; all other platforms go through the ever-jobs REST API.

Group Count Description
Search Boards 94 General job boards. India-relevant defaults pre-selected.
ATS Platforms 37 Greenhouse, Lever, Workday etc. β€” companies post jobs here. NOT resume scoring.
Company Pages 39 Direct career pages (Flipkart, Swiggy, Amazon, Google, etc.)

India default platforms: linkedin, naukri, indeed, glassdoor, google, bdjobs, internshala, bayt, iimjobs, foundit

ATS Clarification

Important: "ATS" in ever-jobs means Applicant Tracking System PLATFORMS (tools companies use to post jobs, like Greenhouse or Lever). It does NOT mean ATS resume scoring.

Our ATS resume scoring (src/ats_scorer.py) uses a 70% JD keyword match + 30% resume quality hybrid and is NOT changed by this integration.

Performance Notes

Selection Expected Time
Default 10 platforms ~1–3 minutes
30 platforms ~3–5 minutes
100+ platforms 5–10 minutes (warning shown in UI)

Playwright-based scrapers (some company pages) are inherently slower than API-based boards.

Cross-Platform Deduplication

In addition to URL-based dedup, a content fingerprint (SHA-256 of normalized title+company) catches cross-platform duplicates. For example, the same "Product Manager at Google" posting on LinkedIn AND Greenhouse (different URLs) is detected and deduplicated.


Project Structure

Job Automation Agent/
β”œβ”€β”€ main.py                    # Main pipeline (6-step orchestrator)
β”œβ”€β”€ config.py                  # All configuration β€” models, platforms, ATS settings
β”œβ”€β”€ ui.py                      # Streamlit UI (4 tabs: Search, Results, Job Details, Research)
β”œβ”€β”€ connect_google.py          # One-time Google OAuth setup
β”œβ”€β”€ setup_google.py            # Service account alternative
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ .env                       # API keys (never commit)
β”œβ”€β”€ .env.example               # Template for .env
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ resume_parser.py       # PDF β†’ plain text (pdfplumber)
β”‚   β”œβ”€β”€ llm_client.py          # GLM 5.1 wrapper (profile extract, resume customize)
β”‚   β”œβ”€β”€ model_pool.py          # 10-model parallel AI pool (NVIDIA API)
β”‚   β”œβ”€β”€ job_assessor.py        # Phase 1 keyword scoring + Phase 2 LLM assessment
β”‚   β”œβ”€β”€ job_history.py         # SQLite dedup store (data/job_history.db)
β”‚   β”œβ”€β”€ ats_scorer.py          # Hybrid ATS scoring (70% JD match + 30% quality)
β”‚   β”œβ”€β”€ resume_customizer.py   # LLM-tailored DOCX resume generator
β”‚   β”œβ”€β”€ gsheets.py             # Google Sheets + Drive upload
β”‚   β”œβ”€β”€ excel_reporter.py      # Local Excel report
β”‚   β”œβ”€β”€ odysseus_llm_core.py   # Deep research LLM core
β”‚   β”‚
β”‚   β”œβ”€β”€ ever_jobs_bridge/       # ever-jobs NestJS REST API adapter
β”‚   β”‚   β”œβ”€β”€ __init__.py        # Package init
β”‚   β”‚   β”œβ”€β”€ server.py          # Docker/npm server lifecycle (start/stop/health)
β”‚   β”‚   β”œβ”€β”€ client.py          # HTTP client for POST /api/jobs/search
β”‚   β”‚   β”œβ”€β”€ mapper.py          # IJob JSON β†’ Job dataclass mapper
β”‚   β”‚   └── platforms.py       # 170 platform catalog with group metadata
β”‚   β”‚
β”‚   β”œβ”€β”€ scrapers/
β”‚   β”‚   β”œβ”€β”€ base.py            # Job dataclass + BaseScraper + is_pm_role() filter
β”‚   β”‚   β”œβ”€β”€ linkedin.py        # LinkedIn scraper (requests + BeautifulSoup)
β”‚   β”‚   β”œβ”€β”€ indeed.py          # Indeed scraper (Playwright for JS rendering)
β”‚   β”‚   β”œβ”€β”€ glassdoor.py       # Glassdoor scraper (Playwright)
β”‚   β”‚   β”œβ”€β”€ naukri.py          # Naukri (disabled β€” blocked by Akamai)
β”‚   β”‚   └── ever_jobs.py       # EverJobsScraper (REST adapter for 160+ platforms)
β”‚   β”‚
β”‚   └── research/
β”‚       β”œβ”€β”€ deep_researcher.py # Odysseus IterResearch engine (Thinkβ†’Searchβ†’Extractβ†’Synthesize)
β”‚       └── search.py          # DuckDuckGo + Bing fallback, 12h cache
β”‚
└── data/
    β”œβ”€β”€ resume/resume.pdf      # Your resume (add this)
    β”œβ”€β”€ job_history.db         # Dedup SQLite DB (auto-created)
    β”œβ”€β”€ research_cache/        # 12h DuckDuckGo result cache
    └── output/
        β”œβ”€β”€ resumes/           # Generated DOCX resumes (Company_JobTitle.docx)
        └── reports/           # Excel reports

Configuration (config.py)

AI Models (10-model pool via NVIDIA API)

Model Speed Phase 2 Notes
Kimi-K2.6 ~5s/batch βœ… Fastest, handles most work
Step-3.7-Flash ~8-35s βœ…
Qwen3.5-397b ~9s βœ…
Qwen3.5-122b-v2 ~12s βœ…
GPT-OSS-120b ~11s βœ…
Qwen3.5-122b ~40s βœ…
DeepSeek-v4-Pro ~42s βœ…
DeepSeek-v4-Flash ~229s ❌ Too slow for phase 2
GLM-5.1 ~234s ❌ Used for resume parsing only
MiniMax-M2.7 ~908s ❌ Blocked/rate-limited

Key Settings

ASSESSMENT = {
    "min_score_for_llm_resume": 6,   # LLM-tailored resume for score >= this
    "generate_all_resumes":     True, # Template resume for ALL PM jobs
    "max_llm_resumes":          30,   # Max LLM resumes per run
    "dedup_days":               30,   # Skip jobs seen in last 30 days
    "test_mode":                True, # ← Set False for full production run
    "test_jobs_limit":          5,    # Max jobs in test mode
}

ATS Scoring Method

Hybrid scoring: 70% JD Match + 30% Resume Quality

  • JD Match (70%): Extract keywords FROM the specific job description β†’ match against resume using word-boundary regex ((?<!\w)kw(?!\w)) β€” same approach as Resume-Matcher
  • Resume Quality (30%): 6-factor structural analysis (measurable achievements, contact info, education, etc.)
  • Iterative optimization: Up to 3 LLM attempts to push score to 95%+, gap report fed back each iteration
  • Fallback: _inject_missing_keywords() if LLM still can't reach 95%

Benchmark results (correctly differentiates):

  • EdTech/AI PM JD β†’ 90% on your resume
  • SAP/ERP PM JD β†’ 53% on your resume

Google Sheet Columns

Column Description
Batch Date When the run happened
Rank Score rank within this batch
Job Title / Company / Location Job details
Platform LinkedIn / Indeed / Glassdoor
Relevance Score AI assessment (1–10)
ATS Before (%) ATS score on original resume
ATS After (%) ATS score on tailored resume
ATS Improvement After βˆ’ Before
Resume Quality Structural quality score
Priority High / Medium / Low
Matching / Missing Skills Gap analysis
AI Recommendation LLM reasoning
Apply Link Direct job URL (clickable)
Resume Link Google Drive link to tailored resume
Application Status Dropdown: Not Applied β†’ Offer

Environment Variables (.env)

NVIDIA_API_KEY=nvapi-...          # GLM 5.1 + primary key
NVIDIA_API_KEY_2=nvapi-...        # DeepSeek-v4-Pro, MiniMax
NVIDIA_API_KEY_3=nvapi-...        # Kimi-K2.6
NVIDIA_API_KEY_4=nvapi-...        # Qwen3.5-122b
NVIDIA_API_KEY_5=nvapi-...        # GPT-OSS-120b
NVIDIA_API_KEY_6=nvapi-...        # DeepSeek-v4-Flash
NVIDIA_API_KEY_7=nvapi-...        # Qwen3.5-397b, Qwen3.5-122b-v2
NVIDIA_API_KEY_8=nvapi-...        # Step-3.7-Flash
GOOGLE_SHEET_ID=1Ehxt3eo...       # Your Google Sheet ID
RESUME_PATH=data/resume/resume.pdf

Logging & Debugging

Every pipeline run writes a timestamped log to data/logs/run_YYYY-MM-DD_HH-MM-SS.log.

To diagnose failures:

  1. Run a search from the UI
  2. Switch to the πŸ“‹ Logs tab
  3. Errors show in red, warnings in yellow
  4. Use Download Full Log File to share or inspect offline
  5. Previous runs are also listed in the selector

The log captures:

  • Every scrape attempt (role, location, raw result count)
  • Full Python tracebacks on any exception
  • All print() output from scrapers and LLM clients
  • Playwright browser output

Known Issues / Pending

Issue Status Notes
Google Drive upload 'Client' object has no attribute 'auth' Pending fix gspread auth method mismatch
LLM resumes = 0 (GLM timeout during customization) Pending fix Switch to Kimi/Step for resume generation
Naukri blocked by Akamai Permanent skip Returns 406 / "Access Denied" with Playwright
Google OAuth "Access blocked" Fixed Add email as test user at GCP console

Running the UI

streamlit run ui.py
# Opens at http://localhost:8501

# Tabs:
# 1. Search     β€” configure and run the full pipeline
# 2. Results    β€” view all assessed jobs with scores
# 3. Job Details β€” expand any job for full AI breakdown
# 4. Deep Research β€” Odysseus engine to research companies