mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
Add a Language Gate - no dimension currently checks language requirements (#275)
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
20d863044b
commit
6392ca1628
@@ -13,6 +13,22 @@ per-file diff commands.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **Language Gate** - no dimension or gate anywhere in the framework checked a posting's
|
||||
language requirements against what the candidate actually speaks (not a Scoring Dimension,
|
||||
not a `/scrape`/`/rank` field, nothing for `/apply`'s existing generic language detection
|
||||
to report to). Adds that check, structured like the existing Eligibility Gate, on a new
|
||||
structured `Languages` table in CLAUDE.md / `01-candidate-profile.md` (`/setup` asks, or
|
||||
infers it from a CV/LinkedIn export): a posting requiring a language you haven't declared
|
||||
at all is a hard **FAIL**; one requiring a higher level than you declared in a language you
|
||||
*do* work in is **FLAG**, not an auto-reject, so borderline cases (a strict "fluent" bar vs.
|
||||
your own B1/B2) get your judgment instead of a silent drop; a requirement at or below your
|
||||
declared level is a clean **PASS**. Wired through `/scrape`, `/rank`, and `/apply`, with
|
||||
`language_gate`/`language_note` persisted into `seen_jobs.json` alongside the existing
|
||||
`location` veto so a re-read of the file (or a future debugging session) can recover why a
|
||||
job did or didn't make the shortlist.
|
||||
|
||||
### Security & privacy
|
||||
|
||||
- **The gitignore guard now covers every personal-output rule** - `security_guards.py`
|
||||
|
||||
Reference in New Issue
Block a user