{"record":{"id":"1238916181e19947","repo":"santifer/career-ops","slug":"skipping-filename-required-cell-s-present-but-empty","errorCode":null,"errorMessage":"⚠️  Skipping ${filename}: required cell(s) present but empty: ${blankRequiredCells.join(', ')} — use the documented sentinel (— / N/A) rather than a blank cell","messagePattern":"⚠️  Skipping (.+?): required cell\\(s\\) present but empty: (.+?) — use the documented sentinel \\(— / N/A\\) rather than a blank cell","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":844,"sourceCode":"    console.warn(`⚠️  Skipping ${filename}: data row has ${parts.length} field(s), missing the required cell(s): ${missingRequiredCells.join(', ')}`);\n    return null;\n  }\n\n  const at = (k) => (map[k] != null ? String(parts[map[k]] ?? '').trim() : '');\n  const optional = (k) => {\n    const v = at(k);\n    return PLACEHOLDER_CELL.test(v) ? '' : v;\n  };\n\n  // A required cell that is PRESENT but blank is a writer that did not supply\n  // the value, and blank is never the way to say \"none\" here: every required\n  // field has a documented value, and the \"no data\" cases have sentinels\n  // (`—` / `N/A` for score, `—` for report, `❌` for pdf). Left unchecked, an\n  // empty status reaches validateStatus(''), which warns and returns\n  // \"Evaluated\" — recording a real evaluation state the row never claimed.\n  const blankRequiredCells = TSV_REQUIRED_FIELDS.filter(k => at(k) === '');\n  if (blankRequiredCells.length) {\n    console.warn(`⚠️  Skipping ${filename}: required cell(s) present but empty: ${blankRequiredCells.join(', ')} — use the documented sentinel (— / N/A) rather than a blank cell`);\n    return null;\n  }\n\n  // The optional tail can only be trusted when a short row's missing cells are\n  // genuinely its LAST ones. Position cannot tell \"notes omitted, url written\"\n  // from \"notes written, url omitted\" — both are one cell short — so the one\n  // TYPED optional column is the corroboration. A cell that IS a URL, sitting\n  // under a label that is not `url` while the `url` label has no cell at all,\n  // means every optional value is one column left of its label. Merging that\n  // stores the posting URL as the row's NOTES and leaves the dedup key empty,\n  // which is precisely the mapping the header exists to protect.\n  if (map.url != null && shortLabels.includes('url')) {\n    const misplacedUrl = Object.entries(map)\n      .filter(([k, i]) => k !== 'url' && i < parts.length && /^https?:\\/\\/\\S*$/i.test(String(parts[i] ?? '').trim()))\n      .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;","sourceCodeStart":826,"sourceCodeEnd":862,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/merge-tracker.mjs#L826-L862","documentation":"A required cell that is present in the data row but blank (empty after trimming) is rejected: blank is never the way to say 'none' in this format. Every required field has a documented value or sentinel ('—' / 'N/A' for score, '—' for report, '❌' for pdf). Left unchecked, an empty status would reach validateStatus(''), which coerces to 'Evaluated' — recording an evaluation state the row never claimed.","triggerScenarios":"parseHeadedAddition (merge-tracker.mjs:843) finds TSV_REQUIRED_FIELDS entries where at(k) === '' — the cell exists at the right column index but holds only whitespace or nothing (e.g. a row like 42\\t\\tAcme\\t... with an empty date cell).","commonSituations":"An LLM writer left a placeholder it then stripped; a spreadsheet export wrote empty cells for unknown values; a script interpolated an unset shell variable (\\t${SCORE}\\t producing an empty cell); a human hand-edited the row and deleted a value instead of substituting the sentinel.","solutions":["Replace each blank required cell named in the warning with its documented value or sentinel: score → 'X.X/5', 'N/A', '—' or '-'; status → a canonical state from templates/states.yml; pdf → '✅' or '❌'; report → the markdown link or '—'; num/date/company/role → real values.","For a backfilled row with no evaluation, use the score sentinel 'N/A', '—' or '-' explicitly rather than leaving the cell empty.","Fix the emitter to substitute sentinels instead of empty strings for unknown values.","Re-run node merge-tracker.mjs after editing and confirm the row merges."],"exampleFix":"// before (blank status and score cells)\n42\t2026-09-16\tAcme\tBackend Engineer\t\t\t❌\t[042](reports/042.md)\n\n// after\n42\t2026-09-16\tAcme\tBackend Engineer\t4.2/5\tEvaluated\t❌\t[042](reports/042.md)\n// or for a never-scored backfill:\n42\t2026-09-16\tAcme\tBackend Engineer\tN/A\tApplied\t❌\t—","handlingStrategy":"validation","validationCode":"const REQUIRED = ['num','date','company','role','score','status','pdf','report'];\nconst blank = REQUIRED.filter(k => String(cells[header.indexOf(k)] ?? '').trim() === '');\nif (blank.length) throw new Error(`${file}: blank required cell(s) ${blank.join(', ')} — use sentinels (— / N/A / ❌)`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Substitute documented sentinels ('—'/'N/A' for score, '—' for report, '❌' for pdf) whenever a value is unknown — never an empty string.","For backfilled rows with no evaluation, always write the score sentinel N/A, — or -.","Default optional values in the emitter to sentinels instead of ''.","Grep your additions directory for rows with consecutive tabs before merging: grep -P '\\t\\t' batch/tracker-additions/*."],"tags":["tsv","parsing","merge-tracker","sentinel"],"backgroundTag":"empty-required-field","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"}