{"record":{"id":"068b8255fb18d80c","repo":"santifer/career-ops","slug":"skipping-filename-the-column-labelled-score-reads-score","errorCode":null,"errorMessage":"⚠️  Skipping ${filename}: the column labelled \"score\" reads \"${score}\", which is not a score or a documented sentinel (X.X/5, N/A, —, -) — check the header labels match the value order","messagePattern":"⚠️  Skipping (.+?): the column labelled \"score\" reads \"(.+?)\", which is not a score or a documented sentinel \\(X\\.X/5, N/A, —, -\\) — check the header labels match the value order","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":877,"sourceCode":"      .map(([k]) => k);\n    if (misplacedUrl.length) {\n      console.warn(`⚠️  Skipping ${filename}: a URL sits under \"${misplacedUrl.join('\", \"')}\" while the \"url\" cell is absent — the optional cells are shifted; write an empty cell for each optional value you omit`);\n      return null;\n    }\n  }\n\n  // Write-canonical: the tracker stores scores unbolded (verify-pipeline\n  // rejects bold scores), so strip any markdown bold from the incoming cell.\n  const score = at('score').replace(/\\*\\*/g, '').trim();\n  if (!looksLikeScoreCell(score)) {\n    // Corroboration, not disambiguation: the header already said which column\n    // this is, so this can no longer pick an order — it only asks whether the\n    // labelled cell holds what the label promises. It stays a hard gate because\n    // the value it catches is an emitter that wrote its VALUES in one order and\n    // its LABELS in another, which is the silent swap in a new costume. The\n    // headerless path already refuses a score cell of this shape (neither cell\n    // score-like → undecidable), so this is the same strictness, not new.\n    console.warn(`⚠️  Skipping ${filename}: the column labelled \"score\" reads \"${score}\", which is not a score or a documented sentinel (X.X/5, N/A, —, -) — check the header labels match the value order`);\n    return null;\n  }\n\n  return {\n    num: parseTrackerNum(at('num')),\n    date: at('date'),\n    company: at('company'),\n    role: at('role'),\n    status: validateStatus(at('status')),\n    score,\n    pdf: at('pdf'),\n    report: at('report'),\n    notes: optional('notes'),\n    // A `via` COLUMN carries the agency name plainly; tolerate a writer that\n    // also brings the headerless form's `via=` tag along.\n    via: optional('via').replace(/^via=/i, '').trim(),\n    location: optional('location'),\n    url: optional('url'),","sourceCodeStart":859,"sourceCodeEnd":895,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/merge-tracker.mjs#L859-L895","documentation":"In a headed TSV the header already declares which column holds the score, so the parser no longer picks an order — it only verifies the labelled cell holds what the label promises: a score like 'X.X/5' or a sentinel ('N/A', '—', '-'). A cell that matches neither is a hard reject, because the value it catches is an emitter that wrote its VALUES in one order and its LABELS in another — the silent score/status swap in a new costume. Merging would store garbage (or a status) as the score.","triggerScenarios":"parseHeadedAddition (merge-tracker.mjs:869-878): looksLikeScoreCell(score) returns false for the cell under the 'score' label (after stripping markdown bold). E.g. the header says score then status, but the row's cells were written status-first, so the score label reads 'Applied'.","commonSituations":"A writer used the legacy headerless order (status before score) under the headed template's labels; hand-editing swapped two cells; a generator emitted columns alphabetically while the header stayed canonical; the score was written as a bare number like '4.2' without '/5' in a format the pattern doesn't accept.","solutions":["Align the data row's cell order with the header labels — put the score value ('X.X/5' or sentinel) under the 'score' label and the canonical status under 'status'.","If the emitter writes status-first, change either its cell order or its header order so they agree.","Format the score as 'X.X/5' (or use a sentinel 'N/A' / '—' / '-') if it currently holds a bare number or prose.","Verify by re-running node merge-tracker.mjs and confirming the row merges with the correct score."],"exampleFix":"// before (values written status-first under score-first labels)\nscore\tstatus\nEvaluated\t4.2/5\n\n// after\nscore\tstatus\n4.2/5\tEvaluated","handlingStrategy":"validation","validationCode":"const scoreCell = cells[header.indexOf('score')].replace(/\\*\\*/g, '').trim();\nif (!/^\\d\\.\\d\\/5$/.test(scoreCell) && !['N/A','—','-'].includes(scoreCell)) {\n  throw new Error(`${file}: cell labelled \"score\" reads \"${scoreCell}\" — not a score or sentinel; check label/value order`);\n}","typeGuard":"const looksLikeScore = (v) => /^\\d\\.\\d\\/5$/.test(String(v ?? '').replace(/\\*\\*/g, '').trim());","tryCatchPattern":null,"preventionTips":["Keep the data row's cell order identical to the header's label order — never mix the legacy status-first order into a headed file.","Format scores as 'X.X/5' and use 'N/A', '—' or '-' when there is no score.","Strip markdown bold before writing (the parser tolerates it, but canonical output is unbolded).","Generate the row from the same array you generate the header from, so they can never disagree."],"tags":["tsv","parsing","merge-tracker","column-swap","score"],"backgroundTag":"invalid-argument-format","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}