--- 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 ```powershell # 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](https://github.com/ever-jobs/ever-jobs) NestJS service, which provides REST API access to 160+ job board scrapers. ### Prerequisites - **Docker Desktop** (preferred): [Install Docker Desktop](https://docs.docker.com/desktop/install/windows-install/) - OR **Node.js 24.x** for npm subprocess fallback ### Setup (one-time) ```bash # 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 ```python 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 (`(?