{"record":{"id":"8813ae98ae640390","repo":"santifer/career-ops","slug":"keep-a-num-and-b-num-exact-title-matc","errorCode":null,"errorMessage":"⚠️  Keep #${a.num} and #${b.num}: exact-title match but advanced status requires exact report identity","messagePattern":"⚠️  Keep #(.+?) and #(.+?): exact-title match but advanced status requires exact report identity","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"dedup-tracker.mjs","lineNumber":230,"sourceCode":" *\n * @param {object} a - First parsed applications.md row.\n * @param {object} b - Second parsed applications.md row.\n * @returns {boolean} True when dedup may cluster the two rows as duplicates.\n */\nfunction roleMatch(a, b) {\n  if (sameReportIdentity(a, b)) return true;\n  if (normalizeRole(a.role) !== normalizeRole(b.role)) return false;\n\n  // Exact-title duplicates that have entered the real application pipeline are\n  // kept separate. A user may already have applied to one row; deleting it\n  // because a higher-scored exact-title sibling exists would lose status,\n  // report, and notes. Keep both unless the rows point to the exact same\n  // report identity.\n  if (isAdvancedStatus(a.status) || isAdvancedStatus(b.status)) {\n    const key = pairKey(a, b);\n    if (!protectedTitlePairs.has(key)) {\n      protectedTitlePairs.add(key);\n      console.warn(`⚠️  Keep #${a.num} and #${b.num}: exact-title match but advanced status requires exact report identity`);\n    }\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Parse a tracker score cell into a numeric value for keeper selection.\n *\n * Scores may include Markdown bolding or a `/5` suffix. Dedup only needs the\n * numeric part so it can keep the highest-scored duplicate row in a cluster.\n *\n * @param {string} s - Raw score cell such as `4.3/5` or `**4.3/5**`.\n * @returns {number} Parsed score, or 0 when no number is present.\n */\nfunction parseScore(s) {\n  const m = s.replace(/\\*\\*/g, '').match(/([\\d.]+)/);","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/dedup-tracker.mjs#L212-L248","documentation":"dedup-tracker.mjs is telling you it deliberately did NOT merge two tracker rows whose normalized titles match exactly, because at least one row has an advanced status (Applied, Responded, Interview, Offer, Hired). Deleting a lower-scored exact-title sibling could destroy real application state — status, report link, notes — so both rows are kept unless they point at the exact same report identity (sameReportIdentity). The warning fires once per pair via the protectedTitlePairs set.","triggerScenarios":"The same company+role appears twice — e.g. a re-scan re-evaluated a posting you already applied to, or a backfilled row sits beside an original now in Interview — and node dedup-tracker.mjs runs over them.","commonSituations":"Re-scans after applying; reposted requisitions; leveled variants sharing one title; periodic tracker-hygiene sessions.","solutions":["If both rows are the SAME application, merge by hand: keep the advanced-status row, copy the report link and notes onto it, then delete the other with a careful manual edit.","If they are genuinely different requisitions, add the req/job IDs to both rows' notes (e.g. 'req JR-10423') so dedup and other tooling can tell them apart permanently.","Otherwise do nothing — keeping both is intentional data-safety behavior."],"exampleFix":"# before — two rows, exact same title, one already applied\n| 42 | 2026-08-01 | Acme | Data Engineer | 4.2/5 | Applied | ❌ | [42](reports/042-acme-2026-08-01.md) | |\n| 87 | 2026-08-18 | Acme | Data Engineer | 4.5/5 | Evaluated | ❌ | [87](reports/087-acme-2026-08-18.md) | |\n# after — different requisitions: disambiguate both rows with req ids in notes\n| 42 | 2026-08-01 | Acme | Data Engineer | 4.2/5 | Applied | ❌ | [42](reports/042-acme-2026-08-01.md) | req JR-10423 |\n| 87 | 2026-08-18 | Acme | Data Engineer | 4.5/5 | Evaluated | ❌ | [87](reports/087-acme-2026-08-18.md) | req JR-10987 |","handlingStrategy":"type-guard","validationCode":"const ADVANCED = new Set(['Applied', 'Responded', 'Interview', 'Offer', 'Hired']);\nconst rowsSafeToAutoMerge = (a, b) => !ADVANCED.has(a.status) && !ADVANCED.has(b.status);","typeGuard":"const isAdvancedStatus = (s) =>\n  ['Applied', 'Responded', 'Interview', 'Offer', 'Hired'].includes(String(s ?? '').trim());","tryCatchPattern":null,"preventionTips":["Never bulk-delete 'duplicate' rows that carry Applied-or-later statuses — status, report link, and notes are lost with them","Record req/job IDs in the notes of same-title requisitions; it is the only durable disambiguator","Review protected pairs manually after every dedup run instead of forcing a merge"],"tags":["dedup","tracker","data-safety","advanced-status"],"backgroundTag":"duplicate-record-detection","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}