{"record":{"id":"60f501af545e1fe9","repo":"santifer/career-ops","slug":"possible-duplicates-group-map-e-e-num","errorCode":null,"errorMessage":"Possible duplicates: ${group.map(e => `#${e.num}`).join(', ')} (${group[0].company} — ${group[0].role})","messagePattern":"Possible duplicates: (.+?)`\\)\\.join\\(', '\\)\\} \\((.+?) — (.+?)\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"verify-pipeline.mjs","lineNumber":167,"sourceCode":"    badStatuses++;\n  }\n}\nif (badStatuses === 0) ok('All statuses are canonical');\n\n// --- Check 2: Duplicates ---\nconst companyRoleMap = new Map();\nlet dupes = 0;\nfor (const e of entries) {\n  // Unicode-aware (#2393): an [a-z0-9] strip erases non-Latin scripts outright,\n  // so every Japanese company and every Japanese role keyed to '' and unrelated\n  // rows were reported as \"possible duplicates\".\n  const key = normalizeTextKey(e.company) + '::' + normalizeTextKey(e.role);\n  if (!companyRoleMap.has(key)) companyRoleMap.set(key, []);\n  companyRoleMap.get(key).push(e);\n}\nfor (const [key, group] of companyRoleMap) {\n  if (group.length > 1) {\n    warn(`Possible duplicates: ${group.map(e => `#${e.num}`).join(', ')} (${group[0].company} — ${group[0].role})`);\n    dupes++;\n  }\n}\nif (dupes === 0) ok('No exact duplicates found');\n\n// --- Check 3: Report links ---\n// Markdown links resolve relative to the file that contains them, so report\n// links must resolve against the tracker's own directory (see #760). For the\n// transition we also accept legacy root-relative links: try the tracker dir\n// first, then fall back to the repo root before flagging a link broken.\nconst TRACKER_DIR = dirname(APPS_FILE);\nlet brokenReports = 0;\nfor (const e of entries) {\n  const match = e.report.match(/\\]\\(([^)]+)\\)/);\n  if (!match) continue;\n  const link = match[1];\n  if (!existsSync(join(TRACKER_DIR, link)) && !existsSync(join(CAREER_OPS, link))) {\n    error(`#${e.num}: Report not found: ${link}`);","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/verify-pipeline.mjs#L149-L185","documentation":"A warning from verify-pipeline.mjs's duplicate check (Check 2): two or more tracker entries in applications.md share the same normalized company+role key (normalizeTextKey on both fields). Exact-string duplicates usually mean the same opening was added twice — the AGENTS.md rule forbids new entries when company+role already exists; update the existing row instead.","triggerScenarios":"Running `node verify-pipeline.mjs` when the tracker contains two rows whose lowercased/normalized company and role strings are identical, e.g. after hand-editing applications.md or merging without merge-tracker.mjs dedup.","commonSituations":"Manually appending rows instead of writing TSVs to batch/tracker-additions/; two parallel batch workers both adding the same offer; same role posted at different URLs not distinguished by req ID in notes; copy-pasting an existing row to change its status.","solutions":["Run `node dedup-tracker.mjs` to remove exact duplicates automatically.","Decide which row is canonical and update it via `node set-status.mjs <report#> <State> --note ...`; delete the redundant row.","For genuinely distinct openings with near-identical titles, add the req/posting ID to the notes of both rows (`req JR-10423`) so merge-tracker.mjs treats them as distinct.","Prevent recurrence: never hand-edit applications.md; write batch/tracker-additions/*.tsv and run `node merge-tracker.mjs` after each batch."],"exampleFix":"// before: second hand-added row duplicates row 42\n| 43 | 2026-09-16 | Acme | Senior Backend Eng | ... |\n// after: update the existing row instead\nnode set-status.mjs 42 Applied --note \"applied 2026-09-16\"","handlingStrategy":"validation","validationCode":"const key = normalizeTextKey(company) + '::' + normalizeTextKey(role);\nif (existingKeys.has(key)) updateExistingRow(existingRowNum) else appendRow();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-edit applications.md; always merge via merge-tracker.mjs.","Add req/posting IDs to notes for same-title sibling openings.","Run verify-pipeline.mjs at the end of every batch to catch dupes early."],"tags":["duplicate-records","data-integrity","tracker","pipeline"],"backgroundTag":"duplicate-record","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}