mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 16:46:24 +00:00
Nothing in the framework checks a posting's language requirements
against what the candidate actually speaks. It is not one of the five
Scoring Dimensions in 04-job-evaluation.md, it is not checked in
/scrape's Step 3 fit assessment, and it is not a field in /rank's JSON
output - even though /apply's Step 1 already extracts a posting's
required language generically, with nowhere to report a mismatch to.
This adds a Language Gate, structured like the existing Eligibility
Gate (read the posting, classify against profile data, hard-stop on a
real mismatch), built on a new structured Languages table in CLAUDE.md
/ 01-candidate-profile.md. /setup now asks for it directly (Path C), or
infers it from a CV/LinkedIn export (Paths A/B - LinkedIn exports
already carry a self-rated Languages section).
The gate compares a posting's stated language requirements against
that table with three outcomes:
- Requires a language not declared at all -> hard FAIL, never
presented.
- Requires a higher level in a language that is declared (e.g. "fluent
English" against a declared B1/B2) -> FLAG, not an auto-reject -
scored and drafted normally, with the gap surfaced so the candidate
judges it themselves (a "fluent" bar reads very differently from a
strict employer vs. one that's flexible on it).
- Requires a language at or below the declared level -> clean PASS.
Wired through the three places that need it: /scrape (Step 3), /rank
(new language_gate/language_note fields alongside the existing
location veto - both are now persisted to seen_jobs.json, not just
used transiently to decide one run's shortlist), and /apply (Step 1's
language extraction now has somewhere to report to).
Out of scope, deliberately: this does not touch the free-form
Deal-breakers list or how it's used elsewhere (e.g. Scoring Dimension
4's relocation check) - that's a separate question this change takes
no position on.
Validated with two live-testing passes against real, unfetched
postings (not fabricated text) across 3 portals and 3 market languages
(Danish, German, Spanish/Argentina): 8/8 postings gated correctly in
the first pass, including ambiguous real-world wording ("you
communicate well in English") a rigid rule would have gotten wrong. A
second pass, run specifically to force a hard-FAIL case, found one
(a Danish posting requiring the ability to read Danish) and confirmed
it persists correctly and would be excluded from /rank's shortlist.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
218 lines
12 KiB
Markdown
218 lines
12 KiB
Markdown
---
|
|
framework_version: 1.2.2
|
|
---
|
|
|
|
# Job Evaluation Framework
|
|
|
|
<!-- SETUP: Skill match areas and career goals are personalized by running /setup -->
|
|
|
|
## Eligibility Gate — run before scoring
|
|
|
|
If the candidate is not a citizen or permanent resident of the country they are applying in, run this first. It is a hard filter, not a scoring dimension, and it is separate from work-permit *timing*: timing asks "can they work the required hours yet?", eligibility asks "are they permitted to hold this job at all?". A candidate can pass timing and still be categorically excluded.
|
|
|
|
Read the posting's eligibility / work rights / "who can apply" section **verbatim** and classify:
|
|
|
|
| Posting wording | Verdict |
|
|
|-----------------|---------|
|
|
| Names a **citizenship or permanent-residency requirement** ("must be a citizen of X", "permanent resident", "PR required", "full working rights" where the employer means citizen/PR) | **FAIL — hard stop.** Do not score, do not draft. Quote the exact wording back to the user. |
|
|
| Requires a **security clearance** at any level | **FAIL** in most countries, since clearance is normally gated on citizenship. Verify the specific scheme rather than assuming. |
|
|
| **Explicitly names** the candidate's permit class, or says "international applicants welcome", "visa holders considered", "we sponsor" | **PASS** — verified acceptance. Worth noting as a positive in the application. |
|
|
| **Silent** on citizenship or residency | **PROCEED, but mark unverified.** Check the employer's own careers or international-applicant page before drafting. |
|
|
|
|
**Two rules that are easy to get wrong:**
|
|
|
|
1. **Silence is not permission.** Large graduate programs frequently gate eligibility on their own website rather than in the job ad. Highest-risk categories: professional-services firms, government and defence, banking, telecommunications, and anything touching critical infrastructure.
|
|
2. **A company-wide "we accept international applicants" statement is not role-level permission.** The common pattern is a general welcome followed by a *named list* of the specific programs or service lines it covers. Confirm the **specific posting or stream** appears on that list before drafting.
|
|
|
|
**Report an eligibility failure to the user with the quoted source** rather than silently dropping the role. They may know something about their own status that the profile does not record.
|
|
|
|
If the candidate's permit also constrains *hours* or *start date* (a student visa with a term-time cap, a permit that begins on graduation), record that as a second gate under this section during `/setup`, with the specific dates. Do not merge it with the eligibility question above — they fail for different reasons and need different answers.
|
|
|
|
A role that fails this gate is not scored and not drafted. Everything below applies only to roles that pass it.
|
|
|
|
## Language Gate — run before scoring
|
|
|
|
No dimension or gate anywhere in this framework currently checks a posting's language requirements against what the candidate actually speaks - it is not one of the five Scoring Dimensions below, not a field `/scrape` or `/rank` track, and not something `/apply`'s language detection (Step 1, which already extracts a posting's required language generically) has anywhere to report to. This gate adds that check, structured the same way as the Eligibility Gate above: read the posting, classify against profile data, and treat a hard mismatch as FAIL before scoring.
|
|
|
|
Read the posting's language requirements as stated for **the role itself** — not the language the ad happens to be written in. A posting written in a language you don't work in, for a role that only needs languages you do work in on the job, passes fine; only an explicit job-condition requirement ("fluent X required," "must communicate with the Y team in Z") triggers this check. For each language the posting requires as a job condition, compare it against your Languages table in CLAUDE.md / `01-candidate-profile.md`:
|
|
|
|
| Posting requirement vs. your Languages table | Verdict |
|
|
|---|---|
|
|
| Requires a language **not on your table at all** (e.g. "fluent Polish required," "must communicate with the Warsaw team in Russian," and you list no Polish/Russian row) | **FAIL — hard stop.** Do not score, do not draft. Quote the exact requirement line. |
|
|
| Requires a language you **do** list, but the posting's stated bar (as written — "fluent," "native," "C1+," "business-level") reads as plausibly **higher** than your declared level | **FLAG, then proceed.** Not a fail. Score and draft normally, but surface the gap explicitly in your report to the user (quote both the posting's requirement and your declared level) so they can judge it themselves — bars like "fluent" vary a lot by company and geography, and a recruiter may be flexible. Never silently drop the posting and never silently treat it as a clean pass. |
|
|
| Requires a language you list, at or below your declared level (or the posting doesn't specify a level at all — just names the language) | **PASS.** No note needed. |
|
|
|
|
Judge the level comparison the same way you judge everything else in this framework: read both sides as written and reason about it, don't force either into a rigid scale — CEFR letters, LinkedIn-style buckets ("professional working proficiency"), and plain-English words ("conversational," "fluent," "native") all appear in the wild and don't map onto each other precisely. When genuinely unsure whether a stated bar exceeds the candidate's level, prefer FLAG over a silent PASS — the human is meant to be the tiebreaker, not the gate.
|
|
|
|
**Worked example:** a candidate whose Languages table lists Spanish (Native) and English (B1/B2). A posting requiring "fluent Russian" → **FAIL**, Russian isn't declared at all. A posting requiring "fluent English" → **FLAG**, English is declared but "fluent" plausibly exceeds B1/B2 — score and draft the application, but tell the candidate this posting's bar may be a stretch and let them decide. A posting requiring "conversational English" or unspecified English → **PASS**, B1/B2 clears a "conversational" bar cleanly.
|
|
|
|
## Scoring Dimensions
|
|
|
|
Evaluate each job posting against these five dimensions:
|
|
|
|
### 1. Technical Skills Match (0-100)
|
|
How well do the required/preferred skills align with the candidate's capabilities?
|
|
|
|
| Score | Meaning |
|
|
|-------|---------|
|
|
| 80-100 | Core requirements are primary skills |
|
|
| 60-79 | Most requirements match, 1-2 gaps that are learnable |
|
|
| 40-59 | Partial match, significant upskilling needed |
|
|
| 0-39 | Fundamental mismatch |
|
|
|
|
**Strong match areas:** [YOUR_PRIMARY_SKILLS]
|
|
**Moderate match areas:** [YOUR_SECONDARY_SKILLS]
|
|
**Weak match areas:** [SKILLS_YOU_LACK]
|
|
|
|
### 2. Experience Match (0-100)
|
|
Does work history align with what they're looking for?
|
|
|
|
| Score | Meaning |
|
|
|-------|---------|
|
|
| 80-100 | Direct experience in the same domain and role type |
|
|
| 60-79 | Related experience, transferable skills clear |
|
|
| 40-59 | Adjacent experience, would need to make the case |
|
|
| 0-39 | Unrelated experience |
|
|
|
|
**Strong:** [YOUR_DIRECT_EXPERIENCE_DOMAINS]
|
|
**Moderate:** [YOUR_ADJACENT_EXPERIENCE]
|
|
**Entry-level:** [ROLES_WITH_LIMITED_EXPERIENCE]
|
|
|
|
### 3. Behavioral/Culture Fit (0-100)
|
|
Does the role and company culture match the behavioral profile?
|
|
|
|
| Score | Meaning |
|
|
|-------|---------|
|
|
| 80-100 | Culture strongly matches behavioral preferences |
|
|
| 60-79 | Mixed signals but mostly compatible |
|
|
| 40-59 | Some friction areas |
|
|
| 0-39 | Significant culture mismatch |
|
|
|
|
**Red flags to research:** Department disorganization, work dominated by maintenance over development, poor chemistry with leadership, culture mismatches. Check reviews, media coverage, LinkedIn connections, and network contacts for insider perspective.
|
|
|
|
### 4. Location & Logistics (Pass/Fail + Notes)
|
|
- Within commute range: PASS
|
|
- Remote with occasional office: PASS
|
|
- Requires relocation: FAIL (deal-breaker)
|
|
- Frequent international travel: FLAG (discuss with user)
|
|
|
|
### 5. Career Alignment & Motivation (0-100)
|
|
Does this role advance career goals and contain tasks that energize?
|
|
|
|
| Score | Meaning |
|
|
|-------|---------|
|
|
| 80-100 | Strongly aligned with career direction, clear growth path |
|
|
| 60-79 | Good role but only partially aligned with long-term goals |
|
|
| 40-59 | Decent job but doesn't build toward career goals |
|
|
| 0-39 | Dead end or backwards step |
|
|
|
|
**Career goals:**
|
|
- [YOUR_CAREER_GOAL_1]
|
|
- [YOUR_CAREER_GOAL_2]
|
|
- [YOUR_CAREER_GOAL_3]
|
|
|
|
**Motivation filter:** Evaluate not just whether you *can* do the tasks, but whether the tasks will *energize* you. Consider:
|
|
- Tasks that energize: [YOUR_ENERGIZING_TASKS]
|
|
- Tasks that drain: [YOUR_DRAINING_TASKS]
|
|
- Non-task factors: leadership style, department culture, company values, degree of autonomy
|
|
|
|
**Life situation alignment:** Consider personal constraints:
|
|
- **Security**: [YOUR_FINANCIAL_SITUATION_CONTEXT]
|
|
- **Flexibility**: [YOUR_SCHEDULE_CONSTRAINTS]
|
|
- **Professional development**: [YOUR_GROWTH_PRIORITIES]
|
|
|
|
### 6. Salary Benchmark (Optional)
|
|
|
|
If the salary lookup tool is configured (`salary_data.json` exists), look up the company:
|
|
```
|
|
python salary_lookup.py "<Company Name>" --json
|
|
```
|
|
|
|
If a city is known from the posting, add `--city "<City>"` to narrow results.
|
|
|
|
Present findings as:
|
|
```
|
|
### Salary Benchmark
|
|
| Metric | Value |
|
|
|--------|-------|
|
|
| [Category] index | XX.X (+/-X.X% vs baseline) |
|
|
| Overall index | XX.X (+/-X.X% vs baseline) |
|
|
```
|
|
|
|
Interpret results relative to the baseline defined in the data file's metadata. For index-based data, higher typically means above-market compensation.
|
|
|
|
If the salary tool is not configured, skip this section.
|
|
|
|
## Output Format
|
|
|
|
Present the evaluation as:
|
|
|
|
```
|
|
## Job Fit Evaluation: [Role] at [Company]
|
|
|
|
| Dimension | Score | Notes |
|
|
|-----------|-------|-------|
|
|
| Technical Skills | XX/100 | [brief note] |
|
|
| Experience Match | XX/100 | [brief note] |
|
|
| Behavioral Fit | XX/100 | [brief note] |
|
|
| Location | PASS/FAIL | [brief note] |
|
|
| Career Alignment | XX/100 | [brief note] |
|
|
|
|
**Overall Score: XX/100** (weighted average of scored dimensions)
|
|
|
|
### Verdict: [Strong Fit / Good Fit / Moderate Fit / Weak Fit / Poor Fit]
|
|
|
|
### Key Strengths for This Role
|
|
- [bullet points]
|
|
|
|
### Gaps to Address
|
|
- [bullet points]
|
|
|
|
### Recommendation
|
|
[1-2 sentences: apply/skip/apply with caveats]
|
|
|
|
### Company Research Checklist
|
|
- [ ] Checked company website (mission, values, recent news)
|
|
- [ ] Checked review sites (Glassdoor, Jobindex, etc.)
|
|
- [ ] Checked LinkedIn for team size, recent hires, connections
|
|
- [ ] Checked media for restructuring, growth, or workplace issues
|
|
- [ ] Identified network contacts who may know the team/manager
|
|
```
|
|
|
|
## Weighting
|
|
- Technical Skills: 30%
|
|
- Experience Match: 25%
|
|
- Behavioral Fit: 15%
|
|
- Career Alignment: 30%
|
|
|
|
(Location is pass/fail, not weighted)
|
|
|
|
## Thresholds
|
|
- **Strong Fit** (75+): Definitely apply, tailor everything
|
|
- **Good Fit** (60-74): Apply, address gaps in cover letter
|
|
- **Moderate Fit** (45-59): Consider carefully, discuss with user
|
|
- **Weak Fit** (30-44): Probably skip unless strategic reasons
|
|
- **Poor Fit** (<30): Skip
|
|
|
|
## Pre-Application: Call the Employer (Best Practice)
|
|
|
|
Before writing the application, consider whether the candidate should call the contact person listed in the posting. **Only call if there are substantive questions** - never call just to "be remembered."
|
|
|
|
### When to Suggest Calling
|
|
- The posting has unclear or ambiguous requirements
|
|
- It's unclear which competencies are essential vs. nice-to-have
|
|
- The role description is vague about day-to-day tasks
|
|
- There's a named contact person who invites questions
|
|
|
|
### Good Questions to Ask
|
|
- "What are the primary challenges in this role?"
|
|
- "How is time typically divided across the listed responsibilities?"
|
|
- "Which competencies are most critical for success in this position?"
|
|
- "What does success look like in the first 6-12 months?"
|
|
|
|
### Rules for the Call
|
|
- Prepare a 30-second "elevator pitch" about your background in case they ask
|
|
- The call's purpose is **gathering information**, not delivering a pitch
|
|
- Take notes - use what you learn to tailor the application
|
|
- Reference the conversation naturally in the cover letter ("After speaking with [name], I was especially drawn to...")
|