mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 16:46:24 +00:00
An ATS reads the compiled PDF's embedded text layer, not the rendered page, and LaTeX can silently produce PDFs whose text extracts as garbage: icon glyphs where contact details should be, (cid:*) markers from fonts without Unicode mappings, interleaved lines from multi-column layouts. This matters more now that /add-template lets users bring arbitrary templates. The existing Step 5 loop verifies what a human sees; this adds verification of what a parser sees. New Step 5d in /apply (CV only - cover letters rarely go through keyword screening; cleanup renumbered to 5e): - Extract the CV PDF's text layer with pdftotext -layout. pdftotext (poppler) is an optional dependency: if missing, the mechanical check is skipped with a warning and keyword coverage falls back to the visual PDF read - the same graceful-skip pattern as salary_lookup.py - Parseability checks verified against a real extraction of the stock template: email/phone must survive as literal text (fontawesome icons extract as harmless glyph-name noise like MOBILE-ALT/Envelope, but a contact detail carried only by an icon or hyperlink is invisible to ATS), no (cid:*) or replacement-character garbage, reading order matching visual order, dates present - Keyword coverage reuses the required/preferred list from Step 1, matched in the posting's language, reported as covered / synonym-only / missing-have-it / missing-gap. Honesty rule enforced: keywords the profile genuinely supports get added to experience bullets; genuine gaps stay visible, never stuffed Integration: CLAUDE.md verification checklist section, ATS Parseability guidance in 05-cv-templates.md, narrow Bash(pdftotext:*) entry in the pre-approved permissions (keeping with the tightened scope from #27), cv/*.txt gitignored (extraction is personal data; also deleted by the step itself), and optional-dependency docs in README and SETUP.
259 lines
13 KiB
Markdown
259 lines
13 KiB
Markdown
# CV Templates and Tailoring Guide
|
|
|
|
<!-- SETUP: Profile statements and section ordering are personalized by running /setup -->
|
|
|
|
## Template: LaTeX moderncv (Banking Style)
|
|
|
|
All CVs use the moderncv LaTeX package with the "banking" style and "blue" color scheme.
|
|
|
|
**Output file:** `cv/main_<company>.tex`
|
|
**Compile with:** **lualatex** on MiKTeX/TeX Live. pdflatex often fails on modern MiKTeX installs with `fontawesome5` font-expansion errors; lualatex handles the same sources cleanly.
|
|
**Master reference:** `cv/main_example.tex` (comprehensive CV with all competencies, experience, and achievements - use as source when building targeted CVs)
|
|
|
|
### Compile command
|
|
|
|
```bash
|
|
cd cv && lualatex -interaction=nonstopmode main_<company>.tex
|
|
```
|
|
|
|
Expected output: `Output written on main_<company>.pdf (2 pages, ...)`. Any page count other than 2 is a failure that must be fixed before presenting to the user.
|
|
|
|
## Document Structure
|
|
|
|
```latex
|
|
\documentclass[11pt,a4paper,sans]{moderncv}
|
|
\moderncvstyle{banking}
|
|
\moderncvcolor{blue}
|
|
|
|
% Force both first and last name AND section headings to render in moderncv
|
|
% blue (color1). Default banking on lualatex+MiKTeX leaves these black, which
|
|
% looks inconsistent with the rest of the blue accent scheme.
|
|
\renewcommand*{\firstnamestyle}[1]{{\fontsize{34}{36}\bfseries\upshape\color{color1}#1}}
|
|
\renewcommand*{\lastnamestyle}[1]{{\fontsize{34}{36}\bfseries\upshape\color{color1}#1}}
|
|
\renewcommand*{\sectionstyle}[1]{{\sectionfont\color{color1}#1}}
|
|
|
|
\usepackage[utf8]{inputenc}
|
|
\usepackage{hyperref}
|
|
\hypersetup{
|
|
colorlinks=true,
|
|
linkcolor=blue,
|
|
filecolor=magenta,
|
|
urlcolor=blue,
|
|
pdftitle={[YOUR_NAME] - CV},
|
|
pdfpagemode=FullScreen,
|
|
}
|
|
\usepackage[scale=0.77]{geometry}
|
|
\usepackage{import}
|
|
|
|
% Personal data
|
|
\name{[FIRST_NAME]}{[LAST_NAME]}
|
|
\address{[YOUR_ADDRESS]}{}{}
|
|
\phone[mobile]{[YOUR_PHONE]}
|
|
\email{[YOUR_EMAIL]}
|
|
\extrainfo{\href{[YOUR_LINKEDIN_URL]}{LinkedIn}, \href{[YOUR_GITHUB_URL]}{GitHub}}
|
|
|
|
\begin{document}
|
|
\makecvtitle
|
|
|
|
% 1. Profile statement (1-3 sentences, tailored per role)
|
|
% 2. Skills section
|
|
% 3. Education section
|
|
% 4. Professional Experience section
|
|
% 5. Selected Publications (if applicable)
|
|
% 6. Honors and Awards (if applicable)
|
|
% 7. References
|
|
|
|
\end{document}
|
|
```
|
|
|
|
### Color overrides
|
|
|
|
The three `\renewcommand*` lines in the preamble are required on lualatex+MiKTeX. Without them the firstname, lastname, and section headings render in black even though `\moderncvcolor{blue}` is set, which looks inconsistent with the rest of the blue accent scheme (links, bullet markers, contact icons). The override forces all three to use `color1` (moderncv's accent colour, which becomes blue under `\moderncvcolor{blue}`). Both names render bold; if you prefer the firstname in regular weight, change the firstnamestyle override from `\bfseries` to `\mdseries`. Don't drop the override - on most modern installs the defaults render visibly wrong.
|
|
|
|
### Spacing inside itemize lists (important)
|
|
|
|
**Do not place `\vspace{...}` between `\item` entries in an `itemize` list.** Even though the source looks symmetric, this pattern occasionally produces a noticeably oversized gap before a single item: the inter-item `\vspace` creates a paragraph break that interacts unpredictably with the list's internal `\itemsep`, so LaTeX renders one of the gaps wider than the rest. Remove the inter-item `\vspace` and let `itemize` use its native uniform spacing.
|
|
|
|
```latex
|
|
% WRONG - intermittently produces an oversized gap before one bullet
|
|
\begin{itemize}
|
|
\item \textbf{Foo}: ...
|
|
\vspace{1pt}
|
|
\item \textbf{Bar}: ...
|
|
\vspace{1pt}
|
|
\item \textbf{Baz}: ...
|
|
\end{itemize}
|
|
|
|
% RIGHT - uniform spacing using the list's native itemsep
|
|
\begin{itemize}
|
|
\item \textbf{Foo}: ...
|
|
\item \textbf{Bar}: ...
|
|
\item \textbf{Baz}: ...
|
|
\end{itemize}
|
|
```
|
|
|
|
Two related patterns are fine and should be kept:
|
|
- `\vspace{1pt}` immediately after `\section{...}` (between section heading and first item) - this is between the heading and the list, not between list items.
|
|
- `\vspace{3pt}` between top-level `\cventry` blocks in Professional Experience or Education - this gives breathing room between roles and renders consistently.
|
|
|
|
## Section-by-Section Tailoring
|
|
|
|
### Profile Statement / Elevator Pitch (Best Practice)
|
|
This is the most important section to customize. It appears right after `\makecvtitle`.
|
|
|
|
Write 5-7 lines that function as an "elevator pitch": a concise, compelling introduction explaining why you're qualified for *this specific role*. Focus on what the employer gains from hiring you.
|
|
|
|
**Create 2-3 profile statement templates for your main role types:**
|
|
|
|
<!-- SETUP: These are populated based on your background -->
|
|
**For [YOUR_PRIMARY_ROLE_TYPE] roles:**
|
|
> [YOUR_PROFILE_STATEMENT_TEMPLATE_1]
|
|
|
|
**For [YOUR_SECONDARY_ROLE_TYPE] roles:**
|
|
> [YOUR_PROFILE_STATEMENT_TEMPLATE_2]
|
|
|
|
### Core Competencies / Skills Section (Best Practice)
|
|
Reorder and emphasize based on the role. Use bold category labels.
|
|
|
|
List **5-7 key competencies** in bullet format, tailored to the specific job. For each competency, briefly explain how it adds value to the position.
|
|
|
|
### Education
|
|
- Always include your highest degrees
|
|
- For senior roles, keep education brief (dates and titles only)
|
|
- Include thesis topics when relevant to the target role
|
|
|
|
### Professional Experience
|
|
- Rewrite bullet points to emphasize aspects most relevant to the target role
|
|
- Use 4-6 bullets for most recent role, 3-4 for previous, 2-3 for older
|
|
- **Emphasize measurable results** where possible: "Reduced processing time by X%", "Model adopted by the team"
|
|
|
|
### Handling Employment Gaps (Best Practice)
|
|
If there is a gap in your employment history:
|
|
- The gap should be explained matter-of-factly if needed
|
|
- Describe how professional development continued during the gap
|
|
- Frame as deliberate skill-building and career repositioning
|
|
|
|
### Publications
|
|
- Include Google Scholar link if applicable
|
|
- Select 3-4 most relevant publications (not always all of them)
|
|
- For non-academic roles, keep brief
|
|
|
|
### Honors and Awards
|
|
- Keep format brief, one line each
|
|
|
|
### References
|
|
- List 2-4 references with name, title, company, and contact
|
|
- End with: "More references are available upon request."
|
|
- **Do not attach reference letters** - employers typically contact references directly
|
|
|
|
## Compile-and-Inspect Loop (MANDATORY)
|
|
|
|
After writing the CV and before presenting to the user, always compile and visually inspect the PDF. Iterate until the layout is clean. Workflow:
|
|
|
|
1. Run `lualatex -interaction=nonstopmode main_<company>.tex`
|
|
2. Check the output page count: must be exactly 2
|
|
3. Read the PDF via the Read tool and visually inspect both pages
|
|
4. Check for **orphaned entries**: a `\cventry` title line must never sit alone at the bottom of page 1 with its bullets on page 2
|
|
|
|
### Fixing common page-break problems
|
|
|
|
**Problem: entry title on page 1, bullets orphaned to page 2**
|
|
Add `\needspace{5\baselineskip}` immediately before the problematic `\cventry`:
|
|
```latex
|
|
\needspace{5\baselineskip}
|
|
\item{\cventry{YEAR--YEAR}{Role Title}{Organization}{Location}{}{...}}
|
|
```
|
|
Include `\usepackage{needspace}` in the preamble.
|
|
|
|
**Problem: one trailing section spills to page 3 (e.g., References alone on page 3)**
|
|
Add `\enlargethispage{2-3\baselineskip}` before a late section (e.g., before `\section{Honors and Awards}`) to stretch page 2 by a few lines. This is the standard LaTeX rescue for near-miss overflows.
|
|
|
|
**Problem: 3 pages with significant content on page 3**
|
|
Cut content — do not compress geometry or `\vspace`. See "Relevance-weighted cutting" below for the rule.
|
|
|
|
**Problem: content finishes early on page 2 (feels thin)**
|
|
Restore the highest-relevance item that was previously cut — a CV that ends mid-page 2 looks incomplete.
|
|
|
|
## ATS Parseability
|
|
|
|
Most employers run CVs through an ATS before a human sees them, and the ATS reads the PDF's embedded **text layer**, not the rendered page. A CV can pass visual inspection and still extract as garbage. After the layout passes the compile-and-inspect loop, verify the text layer:
|
|
|
|
```bash
|
|
cd cv && pdftotext -layout main_<company>.pdf main_<company>.txt
|
|
```
|
|
|
|
`pdftotext` comes from [poppler](https://poppler.freedesktop.org/), not the TeX distribution - it is an **optional** dependency. If it is not installed, skip the mechanical check with a warning and rely on the visual PDF read for keyword coverage.
|
|
|
|
What to check in the extraction:
|
|
|
|
- **Contact details as literal text.** The stock template's fontawesome contact icons extract as glyph names (`MOBILE-ALT`, `Envelope`) - harmless noise, because the actual address and number are printed beside them. The failure mode is a contact detail carried *only* by an icon or a hyperlink (like the `LinkedIn` link text, whose URL is not in the text layer): invisible to an ATS. The email address must always appear as printed text.
|
|
- **No garbled output.** `(cid:NNN)` markers or `�` characters mean a font is embedded without a Unicode mapping - an ATS sees the same garbage. This shows up with unusual fonts in custom templates, not with the stock moderncv setup under lualatex.
|
|
- **Reading order.** The stock banking style is single-column, so extraction order matches visual order. Custom templates (via `/add-template`) with sidebars or multi-column layouts can interleave unrelated lines; if extraction order is scrambled, the user is trading ATS compatibility for looks and should be told.
|
|
- **Keyword coverage.** Match the posting's required/preferred terms against the extracted text, in the posting's language. Prefer the posting's exact term over a synonym when it is truthfully applicable - ATS matching is often literal. Never add a keyword the profile does not support.
|
|
|
|
## Page Budget - Hard 2-Page Limit
|
|
|
|
The CV **must** fit on exactly 2 pages when compiled. Use these content limits as a guide:
|
|
|
|
| Section | Max budget |
|
|
|---------|-----------|
|
|
| Profile statement | 3-4 lines |
|
|
| Skills | 5 items, each 1-2 lines |
|
|
| Most recent role | 4-5 bullets |
|
|
| Previous role | 2-3 bullets |
|
|
| Older roles | 2 bullets (1 line each) |
|
|
| Education | 2-3 entries |
|
|
| Publications | 2-3 entries |
|
|
| Awards | 3 entries, single line each |
|
|
| References | "Available upon request." (single line) |
|
|
|
|
**If in doubt, cut rather than squeeze.** Reducing `\vspace` or geometry scale to force-fit content makes the CV look cramped.
|
|
|
|
## Relevance-weighted cutting (the right way to shrink a CV)
|
|
|
|
**Cut by signal, not by section.** Static priority lists ("remove oldest education first, then shorten the earliest role...") are wrong when a relevant "lower-priority" item is competing with an irrelevant "higher-priority" item. An older-role bullet that speaks directly to the posting is worth more than a recent-role bullet that does not.
|
|
|
|
For every candidate line, score three things:
|
|
|
|
1. **Relevance to THIS posting** — does the line hit a named tool, keyword, or stated responsibility in the job ad?
|
|
2. **Uniqueness** — is it the only place this claim appears, or is it duplicated elsewhere in the CV?
|
|
3. **Narrative load** — does the cover letter depend on it? If cutting the line would force you to rewrite a cover-letter paragraph, it is load-bearing.
|
|
|
|
Cut the lowest-total-score line first, regardless of which section it sits in.
|
|
|
|
### Practical order of cuts (easiest → last resort)
|
|
|
|
1. **Redundancy.** If an achievement appears in both Core Competencies AND a role bullet, the Core Competencies version is usually the cleaner cut (the experience bullet is more concrete evidence).
|
|
2. **Profile-statement fluff.** A sentence that just restates what Publications or Skills will show. ("Peer-reviewed publications on X..." is already a Publications entry — profile can claim it once and stop.)
|
|
3. **Low-relevance experience bullets.** A bullet about work that does not touch posting keywords, wherever it sits. This cuts across sections before touching the structural list.
|
|
4. **Low-relevance supporting content.** An older-role bullet that does not speak to the target role. A certification that does not touch the posting's stack. A language entry that can be condensed to one line.
|
|
5. **Low-relevance publications.** Keep 1-2 publications that best match the posting. Cut the rest before touching experience bullets.
|
|
6. **Last-resort structural cuts.** Oldest education entry, tightening an older role to 2 bullets, collapsing Certifications into a single line. These only happen if the relevance-weighted cuts above have already been exhausted.
|
|
|
|
### Pitfalls to avoid
|
|
|
|
- Do not mechanically cut from the bottom of a static section list without checking relevance. "Cut the oldest role first" is wrong if that role is literally about the skill the posting asks for.
|
|
- Do not cut the one concrete example the cover letter leans on. Relevance is measured against the cover letter you wrote, not just the job posting — interviewers will have read both.
|
|
- Do not cut to fit if the fit is borderline (2.02 pages). Prefer `\enlargethispage{2-3\baselineskip}` on a late section for near-misses; reserve content cuts for genuine overflow (content on page 3 that is more than a single trailing section).
|
|
|
|
## Recommended Section Order
|
|
|
|
The section order varies by role type:
|
|
|
|
**For technical / data science / ML roles:**
|
|
1. Profile statement / elevator pitch
|
|
2. Core competencies / Skills
|
|
3. Professional Experience (reverse chronological)
|
|
4. Education (reverse chronological)
|
|
5. Languages
|
|
6. Publications & Awards
|
|
7. References
|
|
|
|
**For domain-specific / specialist roles:**
|
|
1. Profile statement / elevator pitch
|
|
2. Core competencies / Skills
|
|
3. Education (reverse chronological) - credentials are a key qualifier
|
|
4. Professional Experience (reverse chronological)
|
|
5. Publications & Awards
|
|
6. References
|