import os from dotenv import load_dotenv load_dotenv() NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1" # ── Candidate contact ──────────────────────────────────────────────────────── # Resume-header address line. ATS checkers (Jobalytics / Resume Worded) flag a # missing address; a "City, State, Country" line satisfies that check. Used as # the fallback when the uploaded resume has no parseable location. CONTACT_LOCATION = os.getenv( "CONTACT_LOCATION", "Hyderabad, Telangana, India · Open to relocate" ) # Primary key — GLM 5.1 (most reliable, used for resume parsing + customization) NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY") GLM_BASE_URL = NVIDIA_BASE_URL GLM_MODEL = "z-ai/glm-5.1" # ── 10 assessment models ──────────────────────────────────────────────────── # phase1 = used for quick keyword scoring (instant, no LLM needed now) # phase2 = used for detailed LLM assessment of top jobs ASSESSMENT_MODELS = [ # ───────────────────────────────────────────────────────────────────── # Phase 2 (detailed LLM assessment) speed results from test run: # Kimi-K2.6 ~5s/batch FAST ✅ phase2=True # Step-3.7-Flash ~8-35s FAST ✅ phase2=True # Qwen3.5-397b ~9s FAST ✅ phase2=True # Qwen3.5-122b-v2 ~12s FAST ✅ phase2=True # GPT-OSS-120b ~11s FAST ✅ phase2=True # Qwen3.5-122b ~40s OK ✅ phase2=True # DeepSeek-v4-Pro ~42s OK ✅ phase2=True # DeepSeek-v4-Flash ~229s SLOW ⚠️ phase2=False (too slow) # GLM-5.1 ~234s SLOW ⚠️ phase2=False (too slow) # MiniMax-M2.7 ~908s BLOCKED❌ phase2=False # ───────────────────────────────────────────────────────────────────── { "name": "Kimi-K2.6", "model": "moonshotai/kimi-k2.6", "api_key": os.getenv("NVIDIA_API_KEY_3"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": True, # ~5s per batch FAST "tailor": True, # capable tier — strong at structured JSON rewrites }, { "name": "Step-3.7-Flash", "model": "stepfun-ai/step-3.7-flash", "api_key": os.getenv("NVIDIA_API_KEY_8"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": True, # ~8-35s FAST "tailor": False, # weaker — drops roles / sparse bullets; not for tailoring }, { "name": "Qwen3.5-397b", "model": "qwen/qwen3.5-397b-a17b", "api_key": os.getenv("NVIDIA_API_KEY_7"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": True, # ~9s FAST "tailor": True, # capable tier — large model, good instruction following }, { "name": "Qwen3.5-122b-v2", "model": "qwen/qwen3.5-122b-a10b", "api_key": os.getenv("NVIDIA_API_KEY_7"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": True, # ~12s FAST "tailor": False, # smaller — keep for assessment, not tailoring }, { "name": "GPT-OSS-120b", "model": "openai/gpt-oss-120b", "api_key": os.getenv("NVIDIA_API_KEY_5"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": True, # ~11s FAST "tailor": True, # capable tier }, { "name": "Qwen3.5-122b", "model": "qwen/qwen3.5-122b-a10b", "api_key": os.getenv("NVIDIA_API_KEY_4"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": True, # ~40s OK "tailor": False, # smaller }, { "name": "DeepSeek-v4-Pro", "model": "deepseek-ai/deepseek-v4-pro", "api_key": os.getenv("NVIDIA_API_KEY_2"), "base_url": NVIDIA_BASE_URL, "extra_body": {"chat_template_kwargs": {"thinking": False}}, "phase1": True, "phase2": True, # ~42s OK "tailor": True, # capable tier — strong reasoning }, { "name": "DeepSeek-v4-Flash", "model": "deepseek-ai/deepseek-v4-flash", "api_key": os.getenv("NVIDIA_API_KEY_6"), "base_url": NVIDIA_BASE_URL, "extra_body": {"chat_template_kwargs": {"thinking": True, "reasoning_effort": "high"}}, "phase1": True, "phase2": False, # ~229s too slow for phase2 }, { "name": "GLM-5.1", "model": "z-ai/glm-5.1", "api_key": os.getenv("NVIDIA_API_KEY"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": False, # ~234s too slow for phase2; used for resume customization }, { "name": "MiniMax-M2.7", "model": "minimaxai/minimax-m2.7", "api_key": os.getenv("NVIDIA_API_KEY_2"), "base_url": NVIDIA_BASE_URL, "extra_body": {}, "phase1": True, "phase2": False, # ~908s BLOCKED }, ] # ── Google Sheets / Drive ────────────────────────────────────────────────── GOOGLE = { "sheet_id": os.getenv("GOOGLE_SHEET_ID", "1Ehxt3eortehbtySdtgcSrMhCqmxIMUAmvRqSkII0HJk"), "sheet_tab": "Job Applications", "drive_folder": "Job Automation Agent — Resumes", "credentials_file": "google_credentials.json", # Service account JSON "token_file": "google_token.json", # OAuth token (auto-created) } # ── Job Search — Product Manager roles ONLY ─────────────────────────────── JOB_SEARCH = { "roles": [ "Product Manager", "Senior Product Manager", "AI Product Manager", "Technical Product Manager", "Product Owner", ], # Required words in job title — scraper DROPS any job without these "required_title_words": ["product"], # Blocked words in title — scraper DROPS any job with these "blocked_title_words": [ "teacher", "faculty", "engineer", "developer", "designer", "accountant", "analyst", "consultant", "manager data", "project manager", "program manager", "marketing manager", "sales", "hr ", "recruiter", "finance", "legal", "civil", "mechanical", "electrical", "hardware", "network", "software engineer", "data engineer", "ml engineer", ], "locations": ["India", "Bangalore", "Mumbai", "Delhi NCR", "Hyderabad", "Pune", "Remote"], "experience_min": 2, "experience_max": 15, "max_jobs_per_platform": 30, "days_posted": 7, "primary_role": "Product Manager", } # Platforms (Naukri blocked by Akamai) PLATFORMS = { "linkedin": True, "naukri": False, "indeed": True, "glassdoor": True, } # Assessment — process ALL PM jobs ASSESSMENT = { "min_score_for_llm_resume": 6, # LLM-tailored resume for score >= this "generate_all_resumes": True, # Template resume for ALL PM-relevant jobs "max_llm_resumes": 30, # Max LLM-tailored resumes per run "process_all_jobs": True, # Assess ALL scraped jobs (not just top N) "dedup_days": 30, # Skip jobs seen in last N days (0 = no dedup) "test_mode": False, # Set True for quick test run "test_jobs_limit": 10, # Max jobs in test mode } # ── Production LLM generation (provider abstraction + fallback chain) ────────── # The pipeline is model-independent: any provider may generate content, but # deterministic validation (schema + independent score + parse) decides if it # passes. Providers are tried in order until one yields a READY (>=90) resume. LLM_GENERATION = { "provider_order": ["kimi", "nvidia_primary", "deterministic"], "temperature": 0.1, "max_retries": 2, "require_json_schema": True, "fallback_on_schema_error": True, "fallback_on_low_independent_score": True, "fallback_on_parse_failure": True, } # ── Resume automation policy (AUTO_AGGRESSIVE) ───────────────────────────────── # Controls how aggressively plausible JD terms are auto-included and which # resumes are downloadable, so a 100-job batch runs mostly hands-off. AUTOMATION = { "automation_mode": "auto_aggressive", # What to do per risk severity (see candidate_fit.severity): "review_policy": { "LOW_RISK_AUTO_INCLUDED": "auto_include", "MEDIUM_RISK_REVIEW_RECOMMENDED": "auto_include_with_review_flag", "HIGH_RISK_NEEDS_CONFIRMATION": "ask_user", "BLOCKED_DO_NOT_INCLUDE": "exclude", }, # Which statuses are downloadable: "download_policy": { "READY_90_PLUS": "allow", "READY_90_PLUS_REVIEW_RECOMMENDED": "allow_with_warning", "WEAK_90_INTERNAL_ONLY": "block", "NEEDS_REPAIR": "block", "NEEDS_USER_INPUT": "block", "NOT_ELIGIBLE_LOW_FIT": "block", "PARSE_FAILED": "block", }, } # ── ever-jobs integration ───────────────────────────────────────────────────── from src.ever_jobs_bridge.platforms import INDIA_DEFAULT_PLATFORMS EVER_JOBS = { # Point this at a HOSTED ever-jobs sidecar to enable the 150+ extra # platforms on HF Spaces (where a local Node process can't run). Defaults # to the local dev sidecar. "api_url": os.getenv("EVER_JOBS_API_URL", "http://localhost:3001"), "default_platforms": INDIA_DEFAULT_PLATFORMS, "max_results": 25, "request_timeout": 30, "http_timeout": 120, "max_platforms_per_call": 20, } # Output OUTPUT = { "excel_path": "data/output/reports/job_report.xlsx", "resumes_dir": "data/output/resumes/", "report_dir": "data/output/reports/", } # Resume RESUME = { "pdf_path": os.getenv("RESUME_PATH", "data/resume/resume.pdf"), }