# How Posting Keywords Are Matched Against Extracted PDF Text with Synonym Handling

> Learn how posting keywords match PDF text with synonym handling in this AI job search workflow. See how coverage is validated respecting original language.

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

---

**The AI job search workflow validates CV coverage by extracting PDF text with `pdftotext`, then matching posting keywords against the content in three states—covered, synonym-only, or missing—using a static synonym map to handle industry term variations while respecting the posting's original language.**

The `MadsLorentzen/ai-job-search` repository automates job application workflows by verifying that generated CVs contain required posting keywords. This verification process extracts text from PDF resumes and performs intelligent matching against job requirements, including explicit handling of technical synonyms and language-specific considerations.

## PDF Text Extraction Pipeline

The matching process begins with extracting clean text from the generated CV PDF. In [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py), the system runs the `pdftotext` command with the `-layout` flag to preserve whitespace and formatting during extraction.

```python
from tools.verify_pdf import run_tool

pdf_path = "cv/main_company.tex.pdf"
pdf_text = run_tool(["pdftotext", "-layout", pdf_path, "-"])

```

The `-layout` flag ensures that the extracted text maintains the original document structure, enabling reliable string searching against the posting's keyword list.

## Three-Tier Keyword Matching Logic

Once the PDF text is extracted, the system compares it against the posting's keyword list through a hierarchical matching process defined in the knowledge-base files. Each keyword is evaluated against three possible match states.

### Exact Match Detection (Covered)

The primary validation performs a **case-insensitive string search** for the exact keyword as it appears in the job posting. If `keyword.lower()` exists in `pdf_text.lower()`, the system marks the term as **covered**. According to the CV-template guidelines in [`.claude/skills/job-application-assistant/05-cv-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/05-cv-templates.md), exact matches are preferred because "ATS matching is often literal."

### Synonym-Only Fallback

When the exact keyword is not found, the system consults a **static synonym map** stored in the skill-definition files under `.claude/skills/`. This dictionary maps common industry terms to their accepted variants—for example, `"machine learning"` maps to `["ml", "machine‑learning"]`, and `"software engineer"` maps to `["developer", "programmer"]`.

If a synonym is detected in the PDF text while the exact term is absent, the system marks the keyword as **synonym-only**. This status indicates that the concept is present but expressed using different terminology.

### Language-Aware Validation

The matching logic respects the **language of the posting** as documented in [`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md) (lines 135-138) and [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md) (lines 244-251). When validating a Danish posting against an English CV, the system searches for Danish keywords in the extracted text. If the concept is covered only in English, it is marked as **synonym-only** with a note indicating the language difference, ensuring accurate coverage reporting across multilingual applications.

## Implementation Code and File Structure

The following Python implementation demonstrates the complete matching routine used in the verification workflow:

```python

# Example of a simple keyword‑matching routine with synonym handling

keyword_map = {
    "machine learning": ["ml", "machine‑learning"],
    "software engineer": ["developer", "programmer"],
    # … add more pairs as needed …

}

def match_keyword(keyword, text):
    # Exact match (case‑insensitive)

    if keyword.lower() in text.lower():
        return "covered"
    # Synonym‑only match

    for syn in keyword_map.get(keyword, []):
        if syn.lower() in text.lower():
            return "synonym‑only"
    return "missing"

# Iterate over posting keywords

posting_keywords = ["Machine Learning", "Software Engineer", "Docker"]
results = {}
for kw in posting_keywords:
    results[kw] = match_keyword(kw, pdf_text)

print(results)

# {'Machine Learning': 'covered', 'Software Engineer': 'synonym‑only', 'Docker': 'missing'}

```

**Key Files in the Repository**

- **[`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py)** – Extracts PDF text using `pdftotext -layout` and provides base verification utilities.
- **[`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md)** – Documents the verification checklist and the synonym-only policy for keyword coverage (lines 135-138).
- **[`.claude/skills/job-application-assistant/05-cv-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/05-cv-templates.md)** – Details the keyword-matching process and the preference for exact terms over synonyms.
- **[`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md)** – Provides the concrete reporting format for keyword coverage, including how to handle language differences (lines 244-251).

## Summary

- **PDF extraction** relies on `pdftotext -layout` in [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py) to generate searchable plain text from CV documents.
- **Three match states** classify keywords as covered (exact), synonym-only (mapped term), or missing (not found).
- **Synonym handling** uses a static dictionary in `.claude/skills/` to recognize industry term variations like "ML" for "machine learning."
- **Language awareness** ensures Danish posting keywords are matched against Danish text, with English equivalents marked as synonym-only when applicable.

## Frequently Asked Questions

### How does the system extract text from PDF files?

The system uses the `pdftotext` command-line utility with the `-layout` flag via [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py). This preserves the document's whitespace and formatting, producing reliable plain text that supports accurate case-insensitive keyword searching without losing structural context.

### What happens when a keyword is found only as a synonym?

When the exact keyword is absent but a mapped synonym is present in the PDF text, the system marks the entry as **synonym-only**. This indicates that the underlying concept is covered in the CV, but the terminology differs from the posting's exact requirement—a distinction that is explicitly noted in the verification report to guide potential CV refinements.

### How does the matching logic handle multilingual job postings?

The system matches keywords in the posting's original language. For Danish postings, it searches for Danish terms in the extracted text. If the CV contains only the English equivalent of a Danish keyword, the system marks it as synonym-only and notes the language difference, as specified in [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md) (lines 244-251).

### Where is the synonym mapping defined?

The synonym dictionary is stored in static skill-definition files under the `.claude/skills/` directory. This mapping contains common industry equivalences (such as "machine learning" ↔ "ML") and is consulted during the comparison phase only after exact keyword matching fails.