CV Parser Schema Design
Source: plans/step-5.1.1-cv-parser-design.md · synced 2026-06-12
# CV Parser Schema Design
Date: 2026-05-19
Phase: A — Underlying logic. 9-step coverage: Step 5a (CV parsed).
Status: Approved + shipped (see calibre-app/src/lib/cv-parser/).
Note (2026-05-25): This doc was written when the reference set was 65 CVs across 3 sub-functions. Current state: ~107 CVs across 5 sub-functions. Only 1 file is unreadable (
Chloe Wong (Market Risk, Metals).pdf) — earlier list of 4 was wrong. See SOURCE-OF-TRUTH.md §6 for canonical reference data inventory. Schema itself is unchanged and still valid.
Context
65 real candidate CVs across 3 sub-functions (Cargo Ops, Ship Ops, Risk/Analytics) serve as the training and reference set for Sprint 1. The parser is the first step in a 6-phase matching workflow. Its output feeds three downstream filter layers.
The parser’s sole responsibility is raw extraction. It does not normalise titles or score candidates against JDs. Those are separate systems.
Architecture
CV file (PDF / DOCX / DOC)
↓
Text extractor ↠handles all three formats; flags unreadable files
↓
Claude parser ↠single API call per CV, structured JSON output
↓
Parsed CV record ↠stored in Supabase candidates table
↓
[Title normaliser] ↠separate system, reads raw_title, writes normalised_titleOne Claude API call per CV. Input: raw extracted text. Output: structured JSON conforming to the schema below.
Architectural Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Parser vs normaliser | Two separate systems | Taxonomy evolves independently of extraction logic. Each is updatable without touching the other. |
| Uncertainty handling | Confidence scores on every field (0–1) | Enables UI flagging and human review without blocking record creation. |
| Employment history depth | Full history — all roles | Layer 2 (red flags, career trajectory) requires complete history. Short tenures and gaps hide in older roles. |
Output Schema
{
"identity": {
"name": { "value": "string", "confidence": 0.0–1.0 },
"email": { "value": "string", "confidence": 0.0–1.0 },
"phone": { "value": "string", "confidence": 0.0–1.0 },
"location": { "value": "string", "confidence": 0.0–1.0 },
"linkedin_url": { "value": "string|null", "confidence": 0.0–1.0|null }
},
"current_role": {
"raw_title": { "value": "string", "confidence": 0.0–1.0 },
"employer": { "value": "string", "confidence": 0.0–1.0 },
"start_date": { "value": "YYYY-MM", "confidence": 0.0–1.0 },
"end_date": { "value": "YYYY-MM|null", "confidence": 0.0–1.0|null }
},
"employment_history": [
{
"raw_title": { "value": "string", "confidence": 0.0–1.0 },
"employer": { "value": "string", "confidence": 0.0–1.0 },
"start_date": { "value": "YYYY-MM", "confidence": 0.0–1.0 },
"end_date": { "value": "YYYY-MM|null", "confidence": 0.0–1.0|null },
"description_summary": { "value": "string", "confidence": 0.0–1.0 }
}
],
"signals": {
"total_years_experience": { "value": "integer", "confidence": 0.0–1.0 },
"seniority_signal": { "value": "IC|Manager|Director|C-suite", "confidence": 0.0–1.0 },
"commodity_specialisations": { "value": ["string"], "confidence": 0.0–1.0 },
"vertical_signals": { "value": ["string"], "confidence": 0.0–1.0 },
"tools_and_systems": { "value": ["string"], "confidence": 0.0–1.0 },
"certifications": { "value": ["string"], "confidence": 0.0–1.0 },
"education": [
{
"qualification": { "value": "string", "confidence": 0.0–1.0 },
"field": { "value": "string", "confidence": 0.0–1.0 },
"institution": { "value": "string", "confidence": 0.0–1.0 },
"year": { "value": "YYYY|null", "confidence": 0.0–1.0|null }
}
],
"career_gaps": { "value": [{ "from": "YYYY-MM", "to": "YYYY-MM", "months": "integer" }], "confidence": 0.0–1.0 },
"short_tenures": { "value": [{ "employer": "string", "months": "integer" }], "confidence": 0.0–1.0 }
},
"parse_metadata": {
"source_file": "string",
"file_type": "pdf|docx|doc",
"parse_status": "success|partial|failed",
"unreadable_reason": "string|null",
"overall_confidence": 0.0–1.0,
"parsed_at": "ISO 8601 timestamp"
}
}Confidence Score Thresholds
| Score | Meaning | UI treatment |
|---|---|---|
| ≥ 0.85 | Reliable | Display as-is |
| 0.60–0.84 | Uncertain | Show with “unverified” flag |
| < 0.60 | Low confidence | Flag for human review |
null | Field not found | Blank; flag if required field |
overall_confidence = mean of all non-null field confidences.
Records with overall_confidence < 0.70 are flagged in the UI as “needs review.”
Parse Status Definitions
| Status | Condition | Behaviour |
|---|---|---|
success | Full text extracted, all required fields present | Record created, no flags |
partial | Text extracted but ≥1 required field missing or confidence < 0.60 | Record created, review flag set |
failed | Text extraction failed (scanned image, corrupted file) | Record created with identity from filename if parseable; all fields null |
Known failed files at time of writing:
Sandeep Singh (Ship Ops, Tanker).pdf— scanned imageChloe Wong (Market Risk, Metals).pdf— scanned imageAnand Upretic (Risk, Oil).docx— empty/corruptedJonathan Lam (Middle Office, Oil).doc— corrupted DOC
Filter Layer Mapping
| Parser field | Filter layer | Dimension |
|---|---|---|
total_years_experience | Layer 1 | Experience fit |
commodity_specialisations, vertical_signals | Layer 1 | Industry fit |
seniority_signal | Layer 1 | Seniority fit |
tools_and_systems, certifications, education | Layer 1 | General fit |
employment_history, description_summary | Layer 2 | Experience alignment |
career_gaps, short_tenures | Layer 2 | Red flags + signals |
| All signals combined | Layer 3 | Final recommendation |
What This Spec Does NOT Cover
- Title normalisation — separate spec (
plans/step-5-phase-2-cv-normaliser-design.md, to be written) - Supabase table schema — covered in infrastructure setup
- Parser prompt engineering — covered in implementation plan
- UI for review/flagging — covered in Sprint 4
Next Step
Invoke writing-plans to produce the implementation plan for this spec.