Spaces:
Sleeping
docs: add AGENT_CONTEXT.md — full project handoff doc for new agents/IDEs
Browse filesComprehensive context document for new Claude/Cursor/agent sessions to pick up
where current work left off. Covers:
- Project mission + pipeline flow
- Complete file map with purpose of each module
- 3 design phases delivered (scale to 170+ platforms, HR-grade resume, ATS floor 90%+)
- 4 known bugs identified in production audit (table-header noise leak, orphan
continuation lines, duplicate dates, v1 schema fallback)
- How to verify changes (test scripts, postconditions, deployment)
- LLM model pool details
- 6 critical user-stated policies (no Skills section, no hidden text, etc.)
- Deployment details (GitHub + HF Spaces)
- Where to start for common task types (ATS fixes, format bugs, LLM behavior, UI)
- Prioritized punch list of what to do next
- Conventions, glossary, quick command reference
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AGENT_CONTEXT.md +386 -0
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Agent Context — Job Automation Agent
|
| 2 |
+
|
| 3 |
+
> **Read this first.** This document gives a new agent (Claude/Cursor/etc.) or developer everything they need to understand the project, its architecture, the design decisions that shaped it, what's working, what's broken, and where to start.
|
| 4 |
+
|
| 5 |
+
Last updated: 2026-06-16
|
| 6 |
+
Active branch: `main`
|
| 7 |
+
Last deployed commit: see `git log --oneline -1`
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 1. Project mission in one sentence
|
| 12 |
+
|
| 13 |
+
Upload a PM resume → the tool scrapes Product Manager jobs from 170+ platforms, scores each against the resume, tailors a per-job resume that targets ATS systems (≥90% match), and logs everything to Google Sheets.
|
| 14 |
+
|
| 15 |
+
Stack: Python 3.11 + Streamlit UI + NestJS sidecar (`ever-jobs`) + multi-LLM pool (GLM / Kimi / Step / Qwen) + python-docx + reportlab + Hugging Face Spaces (Docker).
|
| 16 |
+
|
| 17 |
+
Live: https://huggingface.co/spaces/saitejatirunagari/JAA-ATS-Tool
|
| 18 |
+
Repo: https://github.com/saitejatiru/JAA-ATS-Tool
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## 2. End-to-end pipeline flow
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
User uploads resume.pdf
|
| 26 |
+
↓
|
| 27 |
+
[ui.py / Streamlit wizard]
|
| 28 |
+
- 7-step wizard collects: roles, locations, platforms, freshness, AI score, sheets
|
| 29 |
+
- "Start AI Job Search" triggers run_pipeline() in a thread
|
| 30 |
+
↓
|
| 31 |
+
[Scrapers] — src/scrapers/*.py + ever-jobs sidecar
|
| 32 |
+
- Dedicated: linkedin / indeed / glassdoor / remotive / weworkremotely / naukri
|
| 33 |
+
- All other 160+ platforms: EverJobsScraper → calls NestJS sidecar at localhost:3001
|
| 34 |
+
↓
|
| 35 |
+
[Deduplication] — URL + content-fingerprint
|
| 36 |
+
↓
|
| 37 |
+
[Job assessment] — src/job_assessor.py + multi-LLM pool
|
| 38 |
+
- LLM extracts: relevance_score (1-10), matching_skills, missing_skills, ats_keywords
|
| 39 |
+
↓
|
| 40 |
+
[Resume tailoring] — src/resume_customizer.py
|
| 41 |
+
- Per-job, parallel across LLM pool keys
|
| 42 |
+
- Calls llm_client.customize_resume_fast with INDEXED bullets (role:idx)
|
| 43 |
+
- LLM returns: professional_summary + rewritten_bullets + new_bullets
|
| 44 |
+
- DOCX written via python-docx; iteration loop scores and retries (up to 3x)
|
| 45 |
+
- Falls back to template + aggressive keyword injection if LLM weak
|
| 46 |
+
↓
|
| 47 |
+
[ATS scoring] — src/ats_scorer.py
|
| 48 |
+
- Extracts JD keywords (regex + LLM-extracted from assessment)
|
| 49 |
+
- Lemma + phrase matching against resume text
|
| 50 |
+
- Returns: ats_score, jd_match_score, resume_quality, matched_kw, missing_kw, penalties
|
| 51 |
+
↓
|
| 52 |
+
[PDF generation] — src/pdf_writer.py
|
| 53 |
+
- Windows + Word installed → docx2pdf (perfect fidelity, dev only)
|
| 54 |
+
- HF Spaces / Linux → reportlab (replicates DOCX in document order)
|
| 55 |
+
↓
|
| 56 |
+
[Google Sheets logging] — src/gsheets.py
|
| 57 |
+
- One row per job with ATS Before/After, resume path, JD URL, status
|
| 58 |
+
↓
|
| 59 |
+
[Run history] — data/output/run_history/<timestamp>.json
|
| 60 |
+
- Snapshot of every run for re-display in UI
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 3. File map (what each file does)
|
| 66 |
+
|
| 67 |
+
### Entry points
|
| 68 |
+
| File | Purpose |
|
| 69 |
+
|---|---|
|
| 70 |
+
| `ui.py` | Streamlit app — the only user-facing entry point. ~2300 lines. Contains all UI, CSS, wizard, results display, history panel, and orchestration of `run_pipeline()`. |
|
| 71 |
+
| `main.py` | CLI entry (not user-facing on HF). Useful for debugging headless. |
|
| 72 |
+
| `start.sh` | Boots NestJS sidecar (ever-jobs) on :3001, then Streamlit on :7860. |
|
| 73 |
+
| `Dockerfile` | HF Spaces build: Python 3.11-slim + Node 20 + Playwright Chromium + ever-jobs clone. |
|
| 74 |
+
|
| 75 |
+
### Core pipeline (`src/`)
|
| 76 |
+
| File | Purpose |
|
| 77 |
+
|---|---|
|
| 78 |
+
| `resume_parser.py` | Extracts text + contact info from PDF resume via pdfplumber. |
|
| 79 |
+
| `job_assessor.py` | LLM-driven relevance scoring per job. Returns dict with score / matching / missing / keywords. |
|
| 80 |
+
| `resume_customizer.py` | **The heart of the tool.** DOCX generation, LLM-tailoring loop, keyword injection, postcondition checks. ~1100 lines. |
|
| 81 |
+
| `llm_client.py` | Multi-model LLM client. Supports GLM / Kimi / Step / Qwen via OpenAI-compatible APIs. Handles JSON extraction, retries, parallel calls. |
|
| 82 |
+
| `model_pool.py` | Round-robin model selection across configured keys. |
|
| 83 |
+
| `ats_scorer.py` | Lemma + phrase-aware keyword matching. Scoring formula: JD-match 70% + quality 30%. |
|
| 84 |
+
| `pdf_writer.py` | DOCX → PDF (docx2pdf on Windows, reportlab fallback elsewhere). |
|
| 85 |
+
| `gsheets.py` | Google Sheets append. Service account or OAuth. |
|
| 86 |
+
| `excel_reporter.py` | XLSX export of results. |
|
| 87 |
+
| `run_history.py` | Save/load past run snapshots. |
|
| 88 |
+
| `job_history.py` | SQLite store for "already-applied" deduplication. |
|
| 89 |
+
| `app_logger.py` | Color-formatted logging. |
|
| 90 |
+
|
| 91 |
+
### Scrapers (`src/scrapers/`)
|
| 92 |
+
| File | Purpose |
|
| 93 |
+
|---|---|
|
| 94 |
+
| `base.py` | Abstract Scraper class, common Job dataclass. |
|
| 95 |
+
| `linkedin.py` / `indeed.py` / `glassdoor.py` / `remotive.py` / `weworkremotely.py` / `naukri.py` | Dedicated scrapers with hand-tuned selectors. |
|
| 96 |
+
| `ever_jobs.py` | Adapter that calls the NestJS sidecar for 160+ other platforms. |
|
| 97 |
+
|
| 98 |
+
### Ever-jobs integration (`src/ever_jobs_bridge/`)
|
| 99 |
+
| File | Purpose |
|
| 100 |
+
|---|---|
|
| 101 |
+
| `server.py` | Lifecycle management of the NestJS sidecar process. |
|
| 102 |
+
| `client.py` | HTTP client for the sidecar (host: localhost:3001). |
|
| 103 |
+
| `mapper.py` | Maps ever-jobs `IJob` shape → our `Job` dataclass. |
|
| 104 |
+
| `platforms.py` | Registry of all 170+ platforms grouped into Search Boards / ATS Platforms / Company Pages. |
|
| 105 |
+
|
| 106 |
+
### Research (deprecated, kept for reference)
|
| 107 |
+
| File | Purpose |
|
| 108 |
+
|---|---|
|
| 109 |
+
| `research/deep_researcher.py` etc. | Earlier experiment — JD-driven deep research before assessment. Not in the production path. |
|
| 110 |
+
|
| 111 |
+
### Config
|
| 112 |
+
| File | Purpose |
|
| 113 |
+
|---|---|
|
| 114 |
+
| `config.py` | Loads `.env`, builds MODELS list, GOOGLE config dict. |
|
| 115 |
+
| `.env` | API keys for LLMs (GLM/Kimi/Step/Qwen) + Google Sheets ID. **Never committed.** |
|
| 116 |
+
| `google_credentials.json` / `google_oauth_client.json` | Sheets auth files. **Never committed.** |
|
| 117 |
+
|
| 118 |
+
### Planning (gsd workflow artifacts)
|
| 119 |
+
| Directory | Purpose |
|
| 120 |
+
|---|---|
|
| 121 |
+
| `.planning/ROADMAP.md` | Milestone + phases. |
|
| 122 |
+
| `.planning/REQUIREMENTS.md` | R1-R10 numbered requirements. |
|
| 123 |
+
| `.planning/STATE.md` | Project-level decisions. |
|
| 124 |
+
| `.planning/phases/<NN>-<slug>/<NN>-<MM>-PLAN.md` | Per-plan execution specs. |
|
| 125 |
+
|
| 126 |
+
### Tests + scripts
|
| 127 |
+
| File | Purpose |
|
| 128 |
+
|---|---|
|
| 129 |
+
| `tests/fixtures/jds/*.txt` | Real production JDs used for verification (airtel, sumo_logic, edgeverve, aditya_birla, aisensy, navi, zenda, generic). |
|
| 130 |
+
| `scripts/verify_phase3.py` | Validate 2 hardest JDs hit ≥90%. |
|
| 131 |
+
| `scripts/verify_phase3_all4.py` | Validate all 4 failing JDs hit ≥90%. |
|
| 132 |
+
| `scripts/verify_weak_llm_recovery.py` | Validate weak-LLM + injection still recovers to 87-91%. |
|
| 133 |
+
|
| 134 |
+
### Output (gitignored)
|
| 135 |
+
| Path | Purpose |
|
| 136 |
+
|---|---|
|
| 137 |
+
| `data/resume/resume.pdf` | User's uploaded resume. |
|
| 138 |
+
| `data/output/resumes/YYYY-MM-DD/<Company>_<Role>.docx` and `.pdf` | Generated resumes. |
|
| 139 |
+
| `data/output/reports/*.xlsx` | Excel summary per run. |
|
| 140 |
+
| `data/output/run_history/*.json` | Run snapshots for history panel. |
|
| 141 |
+
| `data/logs/tailoring_YYYY-MM-DD.jsonl` | Per-job diagnostic log (added Phase 3). |
|
| 142 |
+
| `data/research_cache/` | LLM response cache to save tokens. |
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## 4. The 3 design phases delivered
|
| 147 |
+
|
| 148 |
+
### Phase 1 — Scale to 170+ platforms (committed)
|
| 149 |
+
- Integrated `github.com/ever-jobs/ever-jobs` as a NestJS sidecar
|
| 150 |
+
- Adapter pattern in `EverJobsScraper`
|
| 151 |
+
- Cross-platform dedup via URL + content fingerprint
|
| 152 |
+
- Streamlit UI grouped multiselect with 170 platforms across 3 categories
|
| 153 |
+
- ATS scoring confirmed adequate (R3 closed — ever-jobs has no resume scorer)
|
| 154 |
+
|
| 155 |
+
### Phase 2 — HR-grade resume + bullet-rewriter contract (committed)
|
| 156 |
+
**Trigger**: User saw a 1-page truncated resume with header "Internal Product" (broken name extraction), missing BYJU's/ML Edutech roles, no Education, empty Core Competencies, plus a spam "Additional relevant skills: adani • godrej • yakult" footer.
|
| 157 |
+
|
| 158 |
+
**Fixes**:
|
| 159 |
+
- New `_extract_candidate_name()` handles ALL CAPS names and PDF letter-spacing
|
| 160 |
+
- Experience parser walks the whole experience blob, finds all date ranges (including line-wrapped "Oct 2021 – Dec\n2022"), splits into roles, preserves sub-section headers as bold §§HEADER§§ markers
|
| 161 |
+
- LLM contract v2: returns `rewritten_bullets["0:3"]` keyed by `role:idx` instead of generic highlights block
|
| 162 |
+
- `_inject_missing_keywords` no longer appends a footer — weaves missing skills into a closing sentence of the Professional Summary
|
| 163 |
+
- Postcondition `_assert_no_dump_footer` raises if any banned section header or "Additional relevant skills" line slips through
|
| 164 |
+
- **User explicitly directed: NO CORE COMPETENCIES section** — keywords live only in summary + bullets
|
| 165 |
+
|
| 166 |
+
**Net**: Original 57/100 → Tailored 92/100 on AiSensy JD (verified with handcrafted LLM response).
|
| 167 |
+
|
| 168 |
+
### Phase 3 — ATS floor 90%+ on real LLM runs (committed)
|
| 169 |
+
**Trigger**: Real LLM in production averaged ~60% (airtel 79, Aditya Birla 48, EdgeVerve 63, Sumo Logic 52) — Phase 2 v2 contract worked but real LLM rewrites covered fewer keywords than handcrafted tests.
|
| 170 |
+
|
| 171 |
+
**Fixes**:
|
| 172 |
+
- Rules-based lemmatizer (no NLTK): `automated`↔`automation`, `roadmaps`↔`roadmap`
|
| 173 |
+
- Phrase-aware matching: multi-word JD keywords match exact OR all-lemmas-in-5-token-window
|
| 174 |
+
- Aggressive JD noise filter: drops 30+ categories (adjectives like proven/solid, modals like will/must, process verbs like perform/establish, JD section words like what/doing/inc/bachelor)
|
| 175 |
+
- LLM prompt updated with **liberal-keyword policy** (user authorized): assume candidate has touched any JD-named common tool over 5+ years
|
| 176 |
+
- **Recruiter-pitch opener**: every Professional Summary opens with `"Strong-fit candidate for <role> at <company>: ..."` — visible to humans + AI screeners (the safe alternative to the user's hidden-text request, which modern ATS auto-rejects)
|
| 177 |
+
- Aggressive keyword injection: trusts JD extractor's filter, drops only lemmatizer artifacts and short tokens
|
| 178 |
+
- Diagnostic JSONL log: per-job record of JD keywords / matched / missing / LLM schema / pitch detection — for debugging future score regressions
|
| 179 |
+
- UI: removed "🤖 Job Automation Agent" title; added baseweb CSS overrides for dark-on-dark dropdowns
|
| 180 |
+
|
| 181 |
+
**Verified results** (handcrafted v3 simulations against the 4 failing JDs):
|
| 182 |
+
- Airtel: 79 �� **92** (+13pp)
|
| 183 |
+
- EdgeVerve: 63 → **91** (+28pp)
|
| 184 |
+
- Sumo Logic: 52 → **92** (+40pp)
|
| 185 |
+
- Aditya Birla: 48 → **91** (+43pp)
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
+
|
| 189 |
+
## 5. Known issues — what's still wrong
|
| 190 |
+
|
| 191 |
+
After the user audited a real production output (Aditya Birla LLM-tailored resume, reported 95% by our scorer), three concrete bugs were identified that drop the **honest** ATS score to ~85%:
|
| 192 |
+
|
| 193 |
+
### Bug A: JD table-header words leak into keyword injection
|
| 194 |
+
The Aditya Birla JD has tabular formatting like `KRA (Accountabilities) (Max 1325 Characters)` and `Supporting Actions`. The keyword extractor pulls **Accountabilities, Max, Characters, Actions, Show** as proper nouns. The aggressive injection then weaves them into the Summary as if they were skills:
|
| 195 |
+
|
| 196 |
+
> *"Toolchain and domain coverage includes Accountabilities, Max, Characters, Actions, and Show."*
|
| 197 |
+
|
| 198 |
+
This is obviously garbage and would make a recruiter think the resume is AI-spam.
|
| 199 |
+
|
| 200 |
+
**Fix needed** in `src/ats_scorer.py` `_JD_NOISE_WORDS`: add table-cell boilerplate (Accountabilities, Max, Characters, Show, Supporting, KRA, KRAs, and similar tabular-formatting terms).
|
| 201 |
+
|
| 202 |
+
### Bug B: Multi-line bullets leave orphan continuation lines
|
| 203 |
+
When the original PDF resume has a multi-line bullet (text wraps in the source), `pdfplumber` extracts each line as a separate text block. `_extract_experience_sections` treats each as a separate bullet → indexes them as `0:0`, `0:1`, `0:2`. The LLM rewrites `0:0` but the orphan continuation (the rest of the original bullet) renders as a ghost-bullet.
|
| 204 |
+
|
| 205 |
+
Example from the user's audit:
|
| 206 |
+
- Bullet 0:0 rewritten: *"Owned end-to-end revamp of NIAT Application Portal—a unified digital IT project covering landing pages → OTP login → personal details → payment, delivering on approved scope, time, and cost."*
|
| 207 |
+
- Orphan that follows: *"details → payment → slot booking → exam → report → sales flow—integrated with CRM, WebEngage, and payment systems."*
|
| 208 |
+
|
| 209 |
+
**Fix needed** in `src/resume_customizer.py` `_extract_experience_sections`: detect line wraps (line ends mid-sentence without punctuation, no bullet character) and join continuation lines into the previous bullet's text before indexing.
|
| 210 |
+
|
| 211 |
+
### Bug C: Date wrapping creates duplicate dates
|
| 212 |
+
`Think & Learn Pvt. Ltd. (BYJU'S) | Bengaluru, IndiaOct 2021 – Dec | Oct 2021 – Dec 2022` — the date appears twice because the original PDF had the date wrapping across lines, and the parser captures both the partial first line and the full second line.
|
| 213 |
+
|
| 214 |
+
**Fix needed**: collapse whitespace and detect duplicate date strings in `_extract_experience_sections`.
|
| 215 |
+
|
| 216 |
+
### Bug D: LLM sometimes returns v1 schema in production
|
| 217 |
+
The new v2 contract asks for `rewritten_bullets["0:3"]` but smaller models (Step / Qwen smaller variants) sometimes return the old v1 `experience_bullets` shape. Backward-compat handles it but the result is generic bullets that don't use JD-specific phrasing.
|
| 218 |
+
|
| 219 |
+
**Fix needed**: stricter JSON-schema enforcement in `customize_resume_fast` (retry if v2 fields are missing); OR add a converter that maps v1 → v2 by best-effort matching of rewritten bullets to original bullets via fuzzy substring match.
|
| 220 |
+
|
| 221 |
+
---
|
| 222 |
+
|
| 223 |
+
## 6. How to verify changes
|
| 224 |
+
|
| 225 |
+
| Goal | How |
|
| 226 |
+
|---|---|
|
| 227 |
+
| Test scorer changes alone | `PYTHONPATH=. python scripts/verify_phase3.py` (uses handcrafted LLM v3 sims) |
|
| 228 |
+
| Test all 4 failing JDs | `PYTHONPATH=. python scripts/verify_phase3_all4.py` |
|
| 229 |
+
| Test injection works on weak LLM output | `PYTHONPATH=. python scripts/verify_weak_llm_recovery.py` |
|
| 230 |
+
| Test UI locally | `streamlit run ui.py --server.port 8502` |
|
| 231 |
+
| Read tailoring diagnostic log | `cat data/logs/tailoring_$(date +%Y-%m-%d).jsonl | jq` |
|
| 232 |
+
| Verify the no-Skills-section postcondition | All generated DOCX files run through `_assert_no_dump_footer` — raises on banned sections |
|
| 233 |
+
| Push to HF Spaces | `git push hf main` (auto-rebuilds in ~5 min) |
|
| 234 |
+
|
| 235 |
+
---
|
| 236 |
+
|
| 237 |
+
## 7. LLM model pool
|
| 238 |
+
|
| 239 |
+
Configured via `.env`:
|
| 240 |
+
```
|
| 241 |
+
GLM_KEY_1=... ← Primary (GLM-4.6) — best JSON adherence
|
| 242 |
+
KIMI_KEY_1=... ← Kimi K2 — fast, good for parallel calls
|
| 243 |
+
STEP_KEY_1=... ← Step-1 — fallback
|
| 244 |
+
QWEN_KEY_1=... ← Qwen — fallback
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
`model_pool.py` round-robins across configured keys. Calls run in parallel via `ThreadPoolExecutor` (up to 6 workers).
|
| 248 |
+
|
| 249 |
+
Two LLM call sites:
|
| 250 |
+
1. `LLMClient.assess_job()` — fast model, JSON output: `{relevance_score, matching, missing, ats_keywords}`
|
| 251 |
+
2. `LLMClient.customize_resume_fast()` — fast model, JSON output (v2 contract): `{professional_summary, rewritten_bullets, new_bullets, key_achievements}`
|
| 252 |
+
|
| 253 |
+
If JSON parse fails, retries up to 2 times per call. If all retry, `_empty_customization()` returns empty schema and the pipeline falls through to template + injection.
|
| 254 |
+
|
| 255 |
+
---
|
| 256 |
+
|
| 257 |
+
## 8. Critical user-stated policies (do not change without asking)
|
| 258 |
+
|
| 259 |
+
1. **No CORE COMPETENCIES / Skills section anywhere in the tailored resume.** Keywords MUST live in Summary + experience bullets only. The `_assert_no_dump_footer` postcondition enforces this.
|
| 260 |
+
|
| 261 |
+
2. **Liberal keyword inclusion is authorized.** When a JD names a common PM tool (Jira/Figma/Mixpanel/etc.) or methodology (PRDs/sprint/MLOps), the LLM is told to include it assuming the candidate has touched it. The user owns interview-side risk.
|
| 262 |
+
|
| 263 |
+
3. **No hidden text / prompt injection.** User asked for white-on-white prompts targeting AI screeners — refused because modern ATS detects this pattern and auto-rejects + blacklists. Replaced with the visible recruiter-pitch opener.
|
| 264 |
+
|
| 265 |
+
4. **No "Tailored for X at Y" footer or banner.** Removed in Phase 2.
|
| 266 |
+
|
| 267 |
+
5. **All 4 candidate roles must be preserved** (NxtWave + BYJU's-1 + BYJU's-2 + ML Edutech). The experience parser must handle all dated headers.
|
| 268 |
+
|
| 269 |
+
6. **Output is BOTH .docx AND .pdf.** PDF via docx2pdf when on Windows + Word; reportlab everywhere else (HF Spaces). PDFs must include table content via document-order body iteration.
|
| 270 |
+
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
## 9. Deployment + branches
|
| 274 |
+
|
| 275 |
+
- **GitHub**: https://github.com/saitejatiru/JAA-ATS-Tool (branch: `main`)
|
| 276 |
+
- **HF Spaces**: https://huggingface.co/spaces/saitejatirunagari/JAA-ATS-Tool (auto-builds on `git push hf main`)
|
| 277 |
+
- Single `main` branch; no PR workflow. Direct commits to main, pushed to both remotes.
|
| 278 |
+
- HF rebuild takes ~5-7 min. The build log shows the commit SHA at the top.
|
| 279 |
+
|
| 280 |
+
---
|
| 281 |
+
|
| 282 |
+
## 10. Where to start as a new agent
|
| 283 |
+
|
| 284 |
+
If your task is **fixing low ATS scores**:
|
| 285 |
+
1. Read `src/ats_scorer.py` `score_resume()` and `extract_jd_keywords()`
|
| 286 |
+
2. Read `src/resume_customizer.py` `_generate_resume()` (the iteration loop) and `_inject_missing_keywords()`
|
| 287 |
+
3. Check `data/logs/tailoring_*.jsonl` for what the LLM actually returned
|
| 288 |
+
4. Test changes with `scripts/verify_phase3_all4.py`
|
| 289 |
+
|
| 290 |
+
If your task is **fixing format / layout bugs**:
|
| 291 |
+
1. Read `src/resume_customizer.py` `_write_docx()` and `_extract_experience_sections()`
|
| 292 |
+
2. Read `src/pdf_writer.py` `_reportlab_render()`
|
| 293 |
+
3. Generate a test DOCX locally: see `scripts/verify_phase3.py` for the pattern
|
| 294 |
+
4. Inspect with python-docx or open in Word/Preview
|
| 295 |
+
|
| 296 |
+
If your task is **adding new LLM behavior**:
|
| 297 |
+
1. Read `src/llm_client.py` `_resume_customize_prompt()` — the v2 contract prompt
|
| 298 |
+
2. Update prompt and schema validator (`_customization_valid()`)
|
| 299 |
+
3. Verify backward compatibility — older models may return v1 schema
|
| 300 |
+
|
| 301 |
+
If your task is **UI changes**:
|
| 302 |
+
1. `ui.py` is ~2300 lines — search for the relevant section by keyword
|
| 303 |
+
2. CSS is at the top (~lines 30-200); be careful with `[data-baseweb=...]` overrides
|
| 304 |
+
3. The 7-step wizard logic uses `st.session_state.setup_step` and persistent `_cfg_*` keys
|
| 305 |
+
|
| 306 |
+
---
|
| 307 |
+
|
| 308 |
+
## 11. The "what to do next" punch list
|
| 309 |
+
|
| 310 |
+
In priority order:
|
| 311 |
+
|
| 312 |
+
1. **Fix Bug A** (JD table-header words leaking into injection) — add `Accountabilities`, `Max`, `Characters`, `Actions`, `Show`, `Supporting`, `KRA`, `KRAs` to `_JD_NOISE_WORDS` in `src/ats_scorer.py`. ~5 min.
|
| 313 |
+
|
| 314 |
+
2. **Fix Bug B** (orphan continuation lines from multi-line bullets) — in `src/resume_customizer.py` `_extract_experience_sections`, before indexing bullets, join consecutive non-bullet lines into the previous bullet's text. ~30 min, test against the production Aditya Birla DOCX.
|
| 315 |
+
|
| 316 |
+
3. **Fix Bug C** (duplicate date string) — collapse whitespace and dedupe in the role header parser. ~10 min.
|
| 317 |
+
|
| 318 |
+
4. **Address Bug D** (v1 schema fallback hurts quality) — either enforce v2 schema with stricter retry, or add a v1→v2 converter that fuzzy-matches each "highlight" bullet to its closest original bullet via SequenceMatcher and rewrites the original in place. ~1-2 hr.
|
| 319 |
+
|
| 320 |
+
5. **Verify** by re-running on the same 4 JDs in production, then asking the user to share the new docx for honest audit.
|
| 321 |
+
|
| 322 |
+
---
|
| 323 |
+
|
| 324 |
+
## 12. Conventions you should follow
|
| 325 |
+
|
| 326 |
+
- **Read HISTORY.md** for the chronological log of fixes — gives context on WHY decisions were made.
|
| 327 |
+
- **Read .planning/REQUIREMENTS.md** — R1-R10 are the named requirements.
|
| 328 |
+
- **Don't change git history** — single `main` branch, no rebases.
|
| 329 |
+
- **Always test against the real resume PDF** at `C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf` (Saiteja's actual resume) — that's the gold reference.
|
| 330 |
+
- **Commit messages**: Conventional Commits style (`fix(scope): ...`, `feat(scope): ...`), with a Co-Authored-By trailer if AI-assisted.
|
| 331 |
+
- **No emojis in code or commit messages** unless the user explicitly asks for them.
|
| 332 |
+
- **No hidden text / prompt injection in resumes** — see policy #3.
|
| 333 |
+
- **The user wants honesty over false confidence.** If a fix only addresses part of the problem, say so. If our scorer reports 95% but real ATS would score 85%, say so.
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
## 13. Quick command reference
|
| 338 |
+
|
| 339 |
+
```bash
|
| 340 |
+
# Run UI locally
|
| 341 |
+
streamlit run ui.py --server.port 8502
|
| 342 |
+
|
| 343 |
+
# Verify Phase 3 against 2 hardest JDs
|
| 344 |
+
PYTHONPATH=. python scripts/verify_phase3.py
|
| 345 |
+
|
| 346 |
+
# Verify all 4 failing JDs
|
| 347 |
+
PYTHONPATH=. python scripts/verify_phase3_all4.py
|
| 348 |
+
|
| 349 |
+
# Generate a test resume (handcrafted v3 LLM sim)
|
| 350 |
+
PYTHONPATH=. python scripts/verify_weak_llm_recovery.py
|
| 351 |
+
|
| 352 |
+
# Deploy to GitHub + HF Spaces
|
| 353 |
+
git add -A
|
| 354 |
+
git commit -m "fix(scope): short description"
|
| 355 |
+
git push origin main
|
| 356 |
+
git push hf main
|
| 357 |
+
|
| 358 |
+
# Check HF build status
|
| 359 |
+
# → Open https://huggingface.co/spaces/saitejatirunagari/JAA-ATS-Tool and check the Logs tab
|
| 360 |
+
|
| 361 |
+
# Tail tailoring diagnostic log (after a run)
|
| 362 |
+
type "data\logs\tailoring_2026-06-16.jsonl" # Windows
|
| 363 |
+
cat data/logs/tailoring_2026-06-16.jsonl # Linux/Mac/git-bash
|
| 364 |
+
```
|
| 365 |
+
|
| 366 |
+
---
|
| 367 |
+
|
| 368 |
+
## 14. Glossary of project-specific terms
|
| 369 |
+
|
| 370 |
+
| Term | Meaning |
|
| 371 |
+
|---|---|
|
| 372 |
+
| **v1 contract** | Original LLM output schema: `{summary, core_competencies, experience_bullets, key_achievements}`. Deprecated but still supported via backward compat. |
|
| 373 |
+
| **v2 contract** | Current LLM output schema (Phase 2): `{summary, rewritten_bullets, new_bullets, key_achievements}`. Bullets keyed by `role_idx:bullet_idx`. |
|
| 374 |
+
| **v3 contract** | Phase 3 prompt with liberal keyword policy + recruiter-pitch opener requirement. Same schema as v2. |
|
| 375 |
+
| **Recruiter pitch** | First sentence of Professional Summary, format: *"Strong-fit candidate for <role> at <company>: <N> years of <relevant experience> directly applicable to <3 JD requirements>."* |
|
| 376 |
+
| **Indexed bullets** | `[(role_idx, bullet_idx, role_name, bullet_text), ...]` tuples passed to the LLM so it can reference specific original bullets. |
|
| 377 |
+
| **§§HEADER§§** / **§§META§§** | Internal markers in `_extract_experience_sections` for sub-section bold headers and italic meta lines (like "Scope:"). |
|
| 378 |
+
| **JD noise filter** | Set of words `_JD_NOISE_WORDS` in `ats_scorer.py` that get dropped from keyword extraction (e.g. proven/solid/will/bachelor). |
|
| 379 |
+
| **Postcondition** | `_assert_no_dump_footer` runs at end of every `_generate_resume` to catch banned patterns (Skills sections, dump footers). |
|
| 380 |
+
| **Lemma matching** | Rules-based stemmer: `automated`/`automation`/`automate` all collapse to `automat`. Enables forgiving keyword matching. |
|
| 381 |
+
| **Phrase matching** | Multi-word JD keywords match if all component lemmas appear within a 5-token sliding window in the resume. |
|
| 382 |
+
| **Aggressive injection** | Trusts JD extractor's noise filter — injects every still-missing keyword (capped at 15) into the Summary's closing sentence. |
|
| 383 |
+
|
| 384 |
+
---
|
| 385 |
+
|
| 386 |
+
End of context document. If anything in here contradicts current code, the code is the source of truth — but please update this doc when you finish your task.
|