# How the Language Gate Filters Job Postings in the AI-Job-Search Repository

> Discover how the language gate filters job postings in the ai-job-search repository. Learn how it assigns PASS FLAG or FAIL verdicts based on candidate language skills during the /rank command.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-08-05

---

**The language gate is a deal-breaker rule that filters job postings based on the candidate's declared language capabilities, assigning each posting a PASS, FLAG, or FAIL verdict during the `/rank` command.**

The language gate ensures that job postings match the candidate's actual language skills as defined in the **Languages** table of [`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md). This prevents wasted time on positions requiring fluency in languages the candidate does not speak or where their proficiency falls short of requirements. According to the `MadsLorentzen/ai-job-search` source code, the gate operates as a structured evaluation layer within the broader job scoring framework.

## Where the Language Gate Is Defined

The core logic for the language gate resides in **[`.claude/skills/job-application-assistant/04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/04-job-evaluation.md)**. This file establishes:

- The candidate's **Languages** table as the authoritative data source
- Comparison rules between required and declared proficiency levels
- The three-verdict classification system (PASS, FLAG, FAIL)

The gate functions as one of several "deal-breaker" evaluations alongside location and other filters, but it applies distinct logic based on partial matches rather than binary inclusion.

## How the Language Gate Verdict Works

When the **`/rank`** command executes Step 2 (batch-fetch and score), each job posting receives a `language_gate` field with one of three possible values:

| Verdict | When Applied | Effect on Results |
|---------|------------|-------------------|
| **PASS** | No language required, OR required proficiency ≤ candidate's declared level | Job proceeds normally; `language_gate` omitted or `null` |
| **FLAG** | Required language is declared, BUT required proficiency exceeds candidate's level | Job retained with ⚠ marker and `language_note` displayed |
| **FAIL** | Required language not declared at all | Job excluded from shortlist, listed under "Excluded" |

The `language_note` field accompanies FLAG and FAIL verdicts, quoting the specific requirement that triggered the mismatch. This transparency allows candidates to understand why a posting was demoted or removed.

## Implementation Flow in the `/rank` Command

The orchestration logic in **[`.claude/commands/rank.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/rank.md)** implements the gate through four discrete steps:

1. **Load State** – Import the scoring framework from [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md) and candidate profile data
2. **Batch-Fetch and Score** – For each posting, the scoring agent identifies language requirements and compares against declared capabilities, emitting JSON with `language_gate` and `language_note`
3. **Aggregate and Rank** – Apply veto logic: FAIL entries removed, FLAG entries highlighted, PASS entries unchanged
4. **Persist** – Write `language_gate` and `language_note` back to [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json) for reproducibility in re-rankings

This persistence layer ensures that `--all` re-ranking commands can reproduce identical decisions without re-invoking the scoring agent.

## Code Examples

### Scoring Agent Output

The scoring agent produces structured JSON for each evaluated posting:

```json
{
  "key": "12345",
  "status": "scored",
  "scores": { "technical": 78, "experience": 65, "behavioral": 55, "career": 72 },
  "location": "PASS",
  "language_gate": "FLAG",
  "language_note": "Requires German fluency (candidate level: B1)",
  "deadline": null,
  "strengths": ["Strong Python experience", "Relevant fintech background"],
  "gaps": ["German fluency needed"],
  "language": "en"
}

```

### Veto Application Logic

The ranking step implements gate enforcement through conditional branching:

```python
def apply_language_gate(job):
    gate = job["language_gate"]
    note = job.get("language_note")
    if gate == "FAIL":
        # Exclude from shortlist, add to excluded list

        excluded.append((job, f"language FAIL: {note}"))
        return None
    if gate == "FLAG":
        # Keep, but mark with warning

        job["flags"].append("⚠ language")
        job["notes"].append(note)
    # PASS – nothing to modify

    return job

```

### Shortlist Display Format

Flagged postings appear with visual indicators in the ranked output:

```

- Senior Backend Engineer at FinTech SA – 78 – Strong Fit – ⚠ – language NOTE: Requires German fluency (candidate level: B1)

```

## Test Coverage

The regression test suite in **[`tests/test_rank_command.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_rank_command.py)** validates gate behavior through four specific assertions:

- Schema includes `language_gate` and `language_note` fields
- Verdict set matches location veto set (PASS/FAIL/FLAG)
- FAIL verdicts trigger proper exclusion documentation
- Persistence into [`seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/seen_jobs.json) functions correctly

These guards prevent regression as the evaluation framework evolves.

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`.claude/skills/job-application-assistant/04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/04-job-evaluation.md) | Gate definition and Languages table reference |
| [`.claude/commands/rank.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/rank.md) | `/rank` command orchestration and veto logic |
| [`tests/test_rank_command.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_rank_command.py) | Regression tests for gate behavior |
| [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json) | Runtime storage of gate results |

## Summary

- The **language gate** evaluates job postings against candidate-declared language capabilities during the `/rank` command
- Three verdicts exist: **PASS** (proceed), **FLAG** (warn but include), and **FAIL** (exclude entirely)
- Gate logic is defined in [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md) and orchestrated through [`rank.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/rank.md)
- Results persist in [`seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/seen_jobs.json) to enable reproducible re-rankings
- Test coverage in [`test_rank_command.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/test_rank_command.py) ensures behavioral stability

## Frequently Asked Questions

### What happens if a job posting doesn't specify any language requirement?

The language gate assigns **PASS** automatically. Since no constraint exists to violate, the posting proceeds through ranking without any language-related annotation or flag.

### Can I override a FAIL verdict and still view the excluded posting?

The source implementation does not provide an override mechanism within the standard `/rank` workflow. Excluded postings appear only in the "Excluded" section with explanatory `language_note` text. Modifying this would require changes to the veto logic in [`rank.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/rank.md).

### How does the gate handle proficiency levels like "fluent" versus "conversational"?

The comparison logic in [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md) maps declared proficiency levels against requirements. When the candidate's level meets or exceeds the requirement, **PASS** results. When the candidate has the language but at insufficient proficiency, **FLAG** applies. The specific mapping of named levels (B1, fluent, conversational) depends on the standardized schema in the **Languages** table.

### Why are FLAG and FAIL treated differently?

**FLAG** indicates a surmountable gap—the candidate possesses foundational capability in the required language and could potentially improve. **FAIL** indicates complete absence of the language from the candidate's profile, making the mismatch fundamental rather than incremental. This distinction allows candidates to prioritize applications where language acquisition might close the gap.