{"record":{"id":"19e5c1737fd669cc","repo":"santifer/career-ops","slug":"non-canonical-status-status-defaulting","errorCode":null,"errorMessage":"⚠️  Non-canonical status \"${status}\" → defaulting to \"Evaluated\"","messagePattern":"⚠️  Non-canonical status \"(.+?)\" → defaulting to \"Evaluated\"","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":181,"sourceCode":"    // Spanish → English\n    'evaluada': 'Evaluated', 'condicional': 'Evaluated', 'hold': 'Evaluated', 'evaluar': 'Evaluated', 'verificar': 'Evaluated',\n    'aplicado': 'Applied', 'enviada': 'Applied', 'aplicada': 'Applied', 'applied': 'Applied', 'sent': 'Applied',\n    'respondido': 'Responded',\n    'entrevista': 'Interview',\n    'oferta': 'Offer',\n    'rechazado': 'Rejected', 'rechazada': 'Rejected',\n    'contratado': 'Hired', 'contratada': 'Hired', 'accepted': 'Hired', 'accept': 'Hired',\n    'descartado': 'Discarded', 'descartada': 'Discarded', 'cerrada': 'Discarded', 'cancelada': 'Discarded',\n    'no aplicar': 'SKIP', 'no_aplicar': 'SKIP', 'skip': 'SKIP', 'monitor': 'SKIP',\n    'geo blocker': 'SKIP',\n  };\n\n  if (aliases[lower]) return aliases[lower];\n\n  // DUPLICADO/Repost → Discarded\n  if (/^(duplicado|dup|repost)/i.test(lower)) return 'Discarded';\n\n  console.warn(`⚠️  Non-canonical status \"${status}\" → defaulting to \"Evaluated\"`);\n  return 'Evaluated';\n}\n\n// normalizeVia (Unicode-aware Via/agency key, #1596/#1603) lives in\n// tracker-parse.mjs so merge-tracker and analyze-patterns share ONE normalizer\n// and agency identity can't drift between scripts. (normalizeCompany lives in\n// tracker-utils.mjs since #1460 so every tracker writer shares one company key.)\n\n/**\n * Extract the bracketed report number from a Markdown report link.\n *\n * Report-number equality is an exact duplicate signal, but only after company\n * equality is confirmed by the caller. This helper reads links such as\n * `[123](../reports/123-company-role-date.md)` and returns the numeric id.\n *\n * Layouts whose header has no dedicated Report column (e.g. a customized\n * `… | Materials | Apply Link | Follow-up | Notes` shape) embed the link in\n * Notes prose instead (see buildRow), so the Notes cell is scanned as a","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/merge-tracker.mjs#L163-L199","documentation":"validateStatus() in merge-tracker.mjs normalizes tracker status cells: it lowercases, maps known aliases (English, Spanish forms, DUPLICADO/repost patterns) to canonical states from templates/states.yml. Anything unrecognized falls back to 'Evaluated' with this warning — a deliberately conservative default that keeps the row mergeable while flagging the typo.","triggerScenarios":"A TSV/tracker cell in the status column containing a value outside the alias table — e.g. 'Interveiw', 'offer!', 'rejected (no fit)' — reaches the end of validateStatus() and is coerced to 'Evaluated'.","commonSituations":"Hand-written batch TSVs with typos; statuses copied from a company email ('withdrawn'); locale variants not in the alias map; trailing punctuation or markdown bold breaking the match.","solutions":["Correct the status cell to a canonical state: Evaluated, Applied, Responded, Interview, Offer, Hired, Rejected, Discarded, or SKIP","If the value is a legitimate new synonym, add it to the aliases map in merge-tracker.mjs and normalize existing rows with node normalize-statuses.mjs","Run node verify-pipeline.mjs after merging to catch rows that landed on the wrong default"],"exampleFix":"# before (batch/tracker-additions/042-acme.tsv, status column)\n042\t2026-08-20\tAcme\tSRE\tInterveiw\t4.2/5\t...\n\n# after\n042\t2026-08-20\tAcme\tSRE\tInterview\t4.2/5\t...","handlingStrategy":"validation","validationCode":"const CANONICAL_STATES = new Set(['Evaluated','Applied','Responded','Interview','Offer','Hired','Rejected','Discarded','SKIP']);\nfunction isCanonicalStatus(s) {\n  return CANONICAL_STATES.has(String(s).trim());\n}","typeGuard":"/** @param {unknown} s */\nfunction isCanonicalTrackerStatus(s) {\n  return typeof s === 'string' && CANONICAL_STATES.has(s.trim());\n}","tryCatchPattern":null,"preventionTips":["Use `node set-status.mjs <selector> <State>` for every tracker status change — it validates against states.yml","Run node normalize-statuses.mjs after importing rows from external sources","Never add punctuation or free text to status cells; put commentary in Notes"],"tags":["tracker","status-normalization","merge","warning"],"backgroundTag":"non-canonical-status-value","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}