Skip to Content
PlansCV Parser Schema Design

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_title

One Claude API call per CV. Input: raw extracted text. Output: structured JSON conforming to the schema below.


Architectural Decisions

DecisionChoiceRationale
Parser vs normaliserTwo separate systemsTaxonomy evolves independently of extraction logic. Each is updatable without touching the other.
Uncertainty handlingConfidence scores on every field (0–1)Enables UI flagging and human review without blocking record creation.
Employment history depthFull history — all rolesLayer 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

ScoreMeaningUI treatment
≥ 0.85ReliableDisplay as-is
0.60–0.84UncertainShow with “unverified” flag
< 0.60Low confidenceFlag for human review
nullField not foundBlank; 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

StatusConditionBehaviour
successFull text extracted, all required fields presentRecord created, no flags
partialText extracted but ≥1 required field missing or confidence < 0.60Record created, review flag set
failedText 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 image
  • Chloe Wong (Market Risk, Metals).pdf — scanned image
  • Anand Upretic (Risk, Oil).docx — empty/corrupted
  • Jonathan Lam (Middle Office, Oil).doc — corrupted DOC

Filter Layer Mapping

Parser fieldFilter layerDimension
total_years_experienceLayer 1Experience fit
commodity_specialisations, vertical_signalsLayer 1Industry fit
seniority_signalLayer 1Seniority fit
tools_and_systems, certifications, educationLayer 1General fit
employment_history, description_summaryLayer 2Experience alignment
career_gaps, short_tenuresLayer 2Red flags + signals
All signals combinedLayer 3Final 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.

Last updated on