{"record":{"id":"1d0adca61a467541","repo":"santifer/career-ops","slug":"skipping-filename-cannot-tell-score-from-s","errorCode":null,"errorMessage":"⚠️  Skipping ${filename}: cannot tell score from status in columns 5–6 (\"${parts[4]}\" | \"${parts[5]}\") — refusing to merge a possible column swap","messagePattern":"⚠️  Skipping (.+?): cannot tell score from status in columns 5–6 \\(\"(.+?)\" \\| \"(.+?)\"\\) — refusing to merge a possible column swap","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":955,"sourceCode":"\n  let parts;\n  let addition;\n\n  // Detect pipe-delimited (markdown table row)\n  if (content.startsWith('|')) {\n    parts = content.split('|').map(s => s.trim());\n    if (parts[0] === '') parts.shift();\n    if (parts[parts.length - 1] === '') parts.pop();\n    if (parts.length < 8) {\n      console.warn(`⚠️  Skipping malformed pipe-delimited ${filename}: ${parts.length} fields`);\n      return null;\n    }\n    // Format: num | date | company | role | score | status | pdf | report | notes [| location]\n    // Identify score vs status by content, not position, so a swapped row can't\n    // merge silently (#1427).\n    const resolved = resolveScoreStatus(parts[4], parts[5]);\n    if (!resolved) {\n      console.warn(`⚠️  Skipping ${filename}: cannot tell score from status in columns 5–6 (\"${parts[4]}\" | \"${parts[5]}\") — refusing to merge a possible column swap`);\n      return null;\n    }\n    addition = {\n      num: parseTrackerNum(parts[0]),\n      date: parts[1],\n      company: parts[2],\n      role: parts[3],\n      // Write-canonical: the tracker stores scores unbolded (verify-pipeline\n      // rejects bold scores), so strip any markdown bold from the incoming cell.\n      score: resolved.score.replace(/\\*\\*/g, '').trim(),\n      status: validateStatus(resolved.status),\n      pdf: parts[6],\n      report: parts[7],\n      notes: parts[8] || '',\n    };\n    const extras = parseTsvExtras(parts, filename);\n    if (!extras) return null;\n    Object.assign(addition, extras);","sourceCodeStart":937,"sourceCodeEnd":973,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/merge-tracker.mjs#L937-L973","documentation":"In the headerless positional forms, columns 5 and 6 hold score and status — but the two orders in circulation disagree (batch TSVs write status/score, applications.md writes score/status), so resolveScoreStatus identifies each cell by content: a score matches the X.X/5 (or sentinel) pattern, a status never does. When neither cell is recognizably a score (e.g. both look like statuses, or neither does), the pair is undecidable and the row is refused rather than merged with swapped columns.","triggerScenarios":"parseTsvContent (merge-tracker.mjs:953-956 for pipe rows): resolveScoreStatus(parts[4], parts[5]) returns null — neither cell matches the score pattern nor a sentinel. Classic case: a discarded, never-scored row carrying '—' in both cells, or two status-looking strings ('Applied' | 'Evaluated').","commonSituations":"A discarded row used the '—' sentinel for both score and status (the known undecidable case — use the headed form, which has no such case); the writer emitted two status values; a non-standard score format ('four point two', '85%') that matches neither pattern; columns 5-6 were shifted by an earlier dropped cell.","solutions":["Switch this row to the headed TSV form (labels row + one data row) — the header disambiguates score/status by name, so a '—'/'—' discarded row merges correctly.","If staying headerless, write a recognizable score ('4.2/5') or score sentinel 'N/A' in one of columns 5-6 so the pair becomes decidable.","Verify both cells actually belong in columns 5-6 — an earlier missing cell shifts everything left and can produce this warning.","Check the status values against templates/states.yml; a typo'd status that matches no pattern contributes to the ambiguity."],"exampleFix":"// before (headerless: '—' in both cells — undecidable)\n42\t2026-09-16\tAcme\tBackend\t—\t—\t❌\t[042](reports/042.md)\n\n// after (headed form resolves by name)\nscore\tstatus\n—\tDiscarded","handlingStrategy":"validation","validationCode":"const scoreLike = (v) => /^\\d\\.\\d\\/5$/.test(String(v ?? '').replace(/\\*\\*/g, '').trim()) || ['N/A','—','-'].includes(String(v ?? '').trim());\nconst isStatusLike = (v) => /^[A-Za-z][A-Za-z ]*$/.test(String(v ?? '').trim());\nif (scoreLike(a) === scoreLike(b)) {\n  throw new Error(`${file}: cannot tell score from status in columns 5-6 — use the headed form`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the headed TSV form for any row that cannot carry a recognizable score (e.g. Discarded with '—' in both cells) — the header resolves score/status by name with no undecidable case.","In headerless rows, always write a pattern-recognizable score ('X.X/5' or 'N/A') so the content sniffing succeeds.","Validate status strings against templates/states.yml before emitting.","Count cells first — a shifted row can push a status into columns 5-6 and trigger this warning."],"tags":["tsv","parsing","merge-tracker","column-swap","ambiguity"],"backgroundTag":"shape-mismatch","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"}