{
  "methodVersion": "1.0.0",
  "measuredAt": "2026-11-04T18:55:28.579Z",
  "question": "When one layout variable is repaired, how much more of a resume does Luceria's own extractor recover?",
  "environment": {
    "node": "v24.16.0",
    "platform": "win32 x64",
    "generators": [
      {
        "name": "docx",
        "version": "9.6.1",
        "license": "MIT"
      },
      {
        "name": "pdfkit",
        "version": "0.18.0",
        "license": "MIT"
      },
      {
        "name": "fontkit",
        "version": "2.0.4",
        "license": "MIT"
      },
      {
        "name": "jszip",
        "version": "3.10.1",
        "license": "(MIT OR GPL-3.0-or-later)"
      }
    ],
    "parsers": [
      {
        "name": "pdf-parse",
        "version": "2.4.5",
        "license": "Apache-2.0"
      },
      {
        "name": "pdfjs-dist",
        "version": "5.4.296",
        "license": "Apache-2.0"
      },
      {
        "name": "mammoth",
        "version": "1.12.0",
        "license": "BSD-2-Clause"
      }
    ],
    "font": {
      "file": "node_modules/pdfjs-dist/standard_fonts/LiberationSans-Regular.ttf",
      "sha256": "f8ace1f892b2bd9dc1792ba7f097fa7588f84fed48321480e04de5390828221f",
      "license": "SIL Open Font License 1.1"
    },
    "extractor": "lib/extract.ts (pdf-parse for PDF, mammoth for DOCX); measures scripts/research/shared/measure.ts; flags lib/tools/parser-preview.ts"
  },
  "metrics": {
    "extracted": "Whether lib/extract.ts returned text (it refuses files with almost no text rather than guessing).",
    "noTextLayer": "The extraction was refused because the file has effectively no text layer.",
    "tokenRecall": "Share of distinct expected content tokens (3+ characters) present anywhere in the extracted text.",
    "lineOrder": "Share of expected lines recovered in the expected reading order (longest increasing subsequence).",
    "contact": "Whether the fictional email address and phone number were readable.",
    "flags": "Layout artefacts the parser-preview heuristics raise."
  },
  "fixtures": [
    {
      "id": "control-docx",
      "file": "control-docx.docx",
      "kind": "docx",
      "role": "control",
      "label": "Word, single column, contact line in the body",
      "bytes": 9033,
      "sha256": "adba30f38b3daf123997bd6c4e14325de0cbcb3948fba0c49b482c2e3231bf9a",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1117,
      "tokenRecall": 1,
      "lineOrder": 1,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "control-pdf",
      "file": "control-pdf.pdf",
      "kind": "pdf",
      "role": "control",
      "label": "PDF, single column, contact line in the body",
      "bytes": 2725,
      "sha256": "089a0aefdae0aa98a3ceda344a249e12113fc83f6cfc478619fdb0ef1a9b44e3",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1120,
      "tokenRecall": 1,
      "lineOrder": 1,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r1-before",
      "file": "r1-before-two-column.pdf",
      "kind": "pdf",
      "role": "before",
      "label": "PDF, two columns drawn as two text streams (sidebar first)",
      "bytes": 2797,
      "sha256": "e26a9e2e18b1bcd4615b8692c0abc4524baa016ebee47f93b8c6ae60507a8dad",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1116,
      "tokenRecall": 1,
      "lineOrder": 0.5455,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r1-after",
      "file": "r1-after-single-column.pdf",
      "kind": "pdf",
      "role": "after",
      "label": "PDF, the same content in one column",
      "bytes": 2730,
      "sha256": "1f812df5eab2d232c34231a9fa5bb9814ad2982f47359cd8608e532ea93c2a16",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1116,
      "tokenRecall": 1,
      "lineOrder": 0.9545,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r2-before",
      "file": "r2-before-table.docx",
      "kind": "docx",
      "role": "before",
      "label": "Word, sidebar and main column built as a table",
      "bytes": 9178,
      "sha256": "93c6be019f71313a0542160c58327c4b44b1718afcc47071f327f001d92c0cab",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1115,
      "tokenRecall": 1,
      "lineOrder": 0.5455,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r2-after",
      "file": "r2-after-single-column.docx",
      "kind": "docx",
      "role": "after",
      "label": "Word, the same paragraphs in one column",
      "bytes": 9028,
      "sha256": "ebfaa47a2e9591edf383c64cfb2365bf6f5aea6f97b66aedd9e3e3e26c5f782e",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1115,
      "tokenRecall": 1,
      "lineOrder": 0.9545,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r3-before",
      "file": "r3-before-header-contact.docx",
      "kind": "docx",
      "role": "before",
      "label": "Word, contact line only in the page header",
      "bytes": 9936,
      "sha256": "55e47ffdef7242f5d806ea04b835124da8299452f7018c114589f93e2bd94e16",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1061,
      "tokenRecall": 0.955,
      "lineOrder": 0.9545,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": false,
        "phone": false
      },
      "flags": []
    },
    {
      "id": "r3-after",
      "file": "r3-after-body-contact.docx",
      "kind": "docx",
      "role": "after",
      "label": "Word, the contact line in the body",
      "bytes": 9033,
      "sha256": "adba30f38b3daf123997bd6c4e14325de0cbcb3948fba0c49b482c2e3231bf9a",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1117,
      "tokenRecall": 1,
      "lineOrder": 1,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r4-before",
      "file": "r4-before-running-header.pdf",
      "kind": "pdf",
      "role": "before",
      "label": "PDF, two pages with a running contact header",
      "bytes": 3636,
      "sha256": "2c25030556a09413289472bd4079104e9609217c2b099ff4f7add1b2e78aff3c",
      "extracted": true,
      "noTextLayer": false,
      "chars": 3575,
      "tokenRecall": 1,
      "lineOrder": 1,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": [
        "repeated-contact"
      ]
    },
    {
      "id": "r4-after",
      "file": "r4-after-no-running-header.pdf",
      "kind": "pdf",
      "role": "after",
      "label": "PDF, the same two pages without the running header",
      "bytes": 3538,
      "sha256": "2c3ff223cc6d2b83bc6b874221da79eecd7bf40398000a6dd176a90dde3dad4d",
      "extracted": true,
      "noTextLayer": false,
      "chars": 3459,
      "tokenRecall": 1,
      "lineOrder": 1,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    },
    {
      "id": "r5-before",
      "file": "r5-before-outlined-text.pdf",
      "kind": "pdf",
      "role": "before",
      "label": "PDF, text drawn as outlines (no text layer)",
      "bytes": 51832,
      "sha256": "8def9a235951a48c0d7da912e93657f2bbb19fddd7d75b91e4143bbce7180cf6",
      "extracted": false,
      "noTextLayer": true,
      "reason": "Extracted very little text — this PDF might be a scan or image-only. Open it, copy the text, and paste it into the box instead.",
      "chars": 0,
      "tokenRecall": 0,
      "lineOrder": 0,
      "bullets": {
        "found": 0,
        "total": 5
      },
      "headings": {
        "found": 0,
        "total": 4
      },
      "contact": {
        "email": false,
        "phone": false
      },
      "flags": []
    },
    {
      "id": "r5-after",
      "file": "r5-after-real-text.pdf",
      "kind": "pdf",
      "role": "after",
      "label": "PDF, the same lines as real text",
      "bytes": 21873,
      "sha256": "62ee08b2c4e914c3f32f7dbd07008e4658d6a2b78d94dddc61f354fc0e91bb46",
      "extracted": true,
      "noTextLayer": false,
      "chars": 1120,
      "tokenRecall": 1,
      "lineOrder": 1,
      "bullets": {
        "found": 5,
        "total": 5
      },
      "headings": {
        "found": 4,
        "total": 4
      },
      "contact": {
        "email": true,
        "phone": true
      },
      "flags": []
    }
  ],
  "repairs": [
    {
      "id": "r1",
      "symptom": "order-jumbled",
      "variable": "Column layout: two columns drawn as two text streams → one column in reading order",
      "howTo": "Rebuild the page as one column: move the sidebar (name, contact, skills, education) into the main flow in reading order, then export the PDF again.",
      "before": "r1-before",
      "after": "r1-after",
      "extracted": {
        "before": true,
        "after": true
      },
      "tokenRecall": {
        "before": 1,
        "after": 1,
        "change": 0
      },
      "lineOrder": {
        "before": 0.5455,
        "after": 0.9545,
        "change": 0.409
      },
      "contact": {
        "before": {
          "email": true,
          "phone": true
        },
        "after": {
          "email": true,
          "phone": true
        }
      },
      "flagsRemoved": [],
      "flagsAdded": []
    },
    {
      "id": "r2",
      "symptom": "order-jumbled",
      "variable": "Document structure: a two-cell layout table → the same paragraphs in one column in reading order",
      "howTo": "Copy the table's contents out into ordinary paragraphs in reading order (in Word: select the table, Table Layout, Convert to Text, then move sections), and delete the empty table.",
      "before": "r2-before",
      "after": "r2-after",
      "extracted": {
        "before": true,
        "after": true
      },
      "tokenRecall": {
        "before": 1,
        "after": 1,
        "change": 0
      },
      "lineOrder": {
        "before": 0.5455,
        "after": 0.9545,
        "change": 0.409
      },
      "contact": {
        "before": {
          "email": true,
          "phone": true
        },
        "after": {
          "email": true,
          "phone": true
        }
      },
      "flagsRemoved": [],
      "flagsAdded": []
    },
    {
      "id": "r3",
      "symptom": "contact-missing",
      "variable": "Contact location: page header → document body",
      "howTo": "Cut the contact line from the page header (double-click the header area) and paste it under your name in the body.",
      "before": "r3-before",
      "after": "r3-after",
      "extracted": {
        "before": true,
        "after": true
      },
      "tokenRecall": {
        "before": 0.955,
        "after": 1,
        "change": 0.045
      },
      "lineOrder": {
        "before": 0.9545,
        "after": 1,
        "change": 0.0455
      },
      "contact": {
        "before": {
          "email": false,
          "phone": false
        },
        "after": {
          "email": true,
          "phone": true
        }
      },
      "flagsRemoved": [],
      "flagsAdded": []
    },
    {
      "id": "r4",
      "symptom": "duplicate-text",
      "variable": "Running header: contact line repeated on every page → none (contact once in the body)",
      "howTo": "Delete the running header (in Word: Insert, Header, Remove Header); keep the contact line once under your name.",
      "before": "r4-before",
      "after": "r4-after",
      "extracted": {
        "before": true,
        "after": true
      },
      "tokenRecall": {
        "before": 1,
        "after": 1,
        "change": 0
      },
      "lineOrder": {
        "before": 1,
        "after": 1,
        "change": 0
      },
      "contact": {
        "before": {
          "email": true,
          "phone": true
        },
        "after": {
          "email": true,
          "phone": true
        }
      },
      "flagsRemoved": [
        "repeated-contact"
      ],
      "flagsAdded": []
    },
    {
      "id": "r5",
      "symptom": "empty-or-image",
      "variable": "Text layer: glyph outlines with no text objects → real text at the same positions",
      "howTo": "Export the document again from the word processor as a text PDF (Save as or Export, not Print to image, and without an 'outline text' or 'flatten' option), or upload the .docx.",
      "before": "r5-before",
      "after": "r5-after",
      "extracted": {
        "before": false,
        "after": true
      },
      "tokenRecall": {
        "before": 0,
        "after": 1,
        "change": 1
      },
      "lineOrder": {
        "before": 0,
        "after": 1,
        "change": 1
      },
      "contact": {
        "before": {
          "email": false,
          "phone": false
        },
        "after": {
          "email": true,
          "phone": true
        }
      },
      "flagsRemoved": [],
      "flagsAdded": []
    }
  ],
  "limitations": [
    "One fictional resume and twelve synthetic files. Each repair changes one stated variable; real documents combine several problems and vary in fonts and authoring software.",
    "Measures Luceria's own extractor only. It does not test any employer's applicant tracking system, which may use a different parser, and it cannot predict hiring outcomes.",
    "The text-layer pair draws text as vector outlines; a photographed or scanned page behaves the same way for a parser without optical character recognition, which Luceria does not run.",
    "In the column and table repairs the sidebar sections move into reading order, because that is what rebuilding in one column means; the measured change is the repair as a whole.",
    "Reading order credits a line the extractor split in two when a fragment holds more than 30 characters of it (the parser benchmark's measure), and the stacked contact details (location, phone and email on separate lines) never match the single expected contact line, which caps the after scores of the column repairs.",
    "Hashes are reproducible with the recorded library versions; a different version of docx, pdfkit, fontkit or JSZip may produce different bytes with the same text."
  ]
}
