# How `/rank` Batch-Scores Multiple Job Postings Against the Fit Framework

> Learn how /rank batch-scores job postings using the Fit Framework. This AI tool efficiently evaluates job content against a rubric, generating a shortlist and excluding mismatches.

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

---

**The `/rank` command performs a lightweight triage scoring pass over new job postings by parallelizing agent-based evaluation of posting content against a four-dimensional rubric, then aggregating weighted scores to generate a shortlist while excluding previously applied positions and location mismatches.**

The `/rank` command in the `MadsLorentzen/ai-job-search` repository serves as the critical bridge between bulk scraping (`/scrape`) and deep-dive evaluation (`/apply`). It batch-scores multiple job postings against the fit framework using parallel agent processing to quickly identify high-potential opportunities without expensive company research or salary lookups.

## Input Parsing and Job Selection

The command determines which postings to evaluate based on CLI arguments parsed at runtime.

- **No arguments**: Targets all jobs with status `new` in [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json)
- **Focus term** (e.g., `/rank data science`): Filters for jobs whose title or stored fit-notes contain the term
- **`--all`**: Re-scores every non-applied job, useful after profile updates
- **`--top <N>`**: Defines the shortlist size (default 5)

## State Loading and Exclusion Logic

Before scoring, the system establishes the evaluation universe by reading persistent state files.

It first attempts to read [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json), exiting with a prompt to run `/scrape` if the file is missing. Simultaneously, it reads `job_search_tracker.csv` to build an exclusion set of company-role pairs for positions already applied to. The pipeline filters candidate jobs against this exclusion set and any focus term, ensuring only relevant, unapplied positions enter the scoring queue.

## Framework and Profile Loading

The scoring rubric and candidate profile load once at the start of the batch process to ensure consistency across parallel evaluations.

According to the specification in [`/.claude/commands/rank.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/commands/rank.md), the system pulls evaluation criteria from [`.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) and candidate constraints from [`.claude/skills/job-application-assistant/01-candidate-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/01-candidate-profile.md). This single-load approach prevents rubric drift across the distributed agent calls.

## Parallel Agent Scoring

### Batch Processing with WebFetch

Jobs are grouped into batches of approximately five per parallel general-purpose agent. Each agent receives an inline payload containing the job list (title, company, URL) plus the compact rubric extracted during initialization. Agents fetch posting URLs using **WebFetch** and score exclusively from the fetched content. Dead or redirected URLs are marked `expired` rather than scored.

### Evaluation Dimensions

The returned JSON for each job contains raw scores for four dimensions: **technical**, **experience**, **behavioral**, and **career** fit. It also includes a location verdict, deadline, identified strengths, gaps, and language detection. As specified in [`/.claude/commands/rank.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main//.claude/commands/rank.md) lines 44-56, the agent outputs structured data without conducting additional web searches or salary lookups.

```json
{
  "key": "12345",
  "status": "scored",
  "scores": { "technical": 82, "experience": 75, "behavioral": 68, "career": 90 },
  "location": "PASS",
  "deadline": "2026-08-01",
  "strengths": ["Implemented CI pipelines", "Led a 5-person team"],
  "gaps": ["No cloud-native experience"],
  "language": "en"
}

```

## Score Aggregation and Ranking Logic

### Weighted Score Calculation

Back in the main context, an overall score is computed using weights defined in [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md): Technical 30%, Experience 25%, Behavioral 15%, and Career 30%. These weights map to verdict bands: **Strong Fit** (≥75), **Good Fit** (60-74), **Moderate Fit** (45-59), **Weak Fit** (30-44), and **Poor Fit** (<30).

### Location Veto and Deadline Urgency

A location verdict of `FAIL` removes the job from the shortlist entirely, while `FLAG` keeps it with a warning. Jobs with deadlines within seven days receive a 🔥 marker and win tie-breakers. Past-due jobs are marked `expired` and excluded from ranking. The final list sorts by overall score descending, with urgency as the secondary sort key.

## State Persistence and Output

### Updating Job Status

The system updates [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json) in-place. Ranked jobs receive `"status":"ranked"` plus computed fields `rank_score`, `rank_verdict`, and `rank_date`. Expired or past-deadline jobs receive `"status":"expired"`. The `job_search_tracker.csv` remains untouched, as it only records actual applications.

### Presentation Format

The command outputs a markdown table summarizing the shortlist, below-threshold jobs, and excluded items. The display explicitly states that numbers represent **triage scores from posting text only**, prompting users to invoke `/apply` for selected jobs to trigger deeper evaluation.

## Usage Examples

Invoke `/rank` from the CLI using the following patterns:

```bash

# Rank all new postings

$ claude /rank

# Filter for specific roles

$ claude /rank data science

# Re-score everything and return top 10

$ claude /rank --all --top 10

```

Sample output table format:

```markdown

## Job Ranking - 2026-07-14

Ranked 12 new postings (5 shortlisted, 4 below threshold, 3 expired/vetoed).

### Shortlist

| # | Score | Verdict | Title | Company | Location | Deadline | |

|---|-------|---------|-------|---------|----------|----------|-|
| 1 | 78 | Strong Fit | Senior Data Engineer | Acme Corp | Remote | 2026-08-03 | 🔥 |
| 2 | 71 | Good Fit | ML Researcher | Beta Ltd | Copenhagen | — | |

```

## Summary

- `/rank` acts as a triage layer between `/scrape` and `/apply`, batch-scoring multiple job postings using parallel agents
- The system evaluates four dimensions (Technical 30%, Experience 25%, Behavioral 15%, Career 30%) using rubrics from [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md)
- Location vetoes (`FAIL`) and deadline urgency (≤7 days) automatically filter and prioritize the shortlist
- State updates persist to [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json) while respecting exclusions from `job_search_tracker.csv`
- Output remains deliberately shallow—no company research or salary data—to maintain processing speed

## Frequently Asked Questions

### How does `/rank` differ from the `/apply` command?

`/rank` performs a lightweight triage scoring pass using only the job posting content fetched via WebFetch, while `/apply` conducts deep-dive evaluation including company research, salary lookups, and comprehensive fit analysis. `/rank` updates [`seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/seen_jobs.json) with triage scores, whereas `/apply` generates application materials and logs entries to `job_search_tracker.csv`.

### What happens if a job posting URL is dead or redirected?

Agents mark dead or redirected URLs as `expired` during the WebFetch phase. These jobs receive `"status":"expired"` in [`job_scraper/seen_jobs.json`](https://github.com/MadsLorentzen/ai-job-search/blob/main/job_scraper/seen_jobs.json) and are excluded from the ranked shortlist, preventing wasted evaluation cycles on inaccessible postings.

### Can I re-score jobs after updating my candidate profile?

Yes. Use the `--all` flag to re-score every non-applied job against your updated profile. This is useful after modifying [`.claude/skills/job-application-assistant/01-candidate-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/01-candidate-profile.md) or changing evaluation criteria in [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md), as it forces a fresh batch-scoring pass regardless of previous `new` or `ranked` statuses.

### How are ties broken when multiple jobs have the same score?

The ranking algorithm uses deadline urgency as the primary tie-breaker. Jobs with deadlines within seven days receive a 🔥 marker and priority placement. If urgency is equal, the original scrape order or internal key may determine final positioning, though the specific secondary sort key is not explicitly weighted in the aggregation logic.