{"record":{"id":"6030c195b76c1a3b","repo":"santifer/career-ops","slug":"duplicate-reports-for-same-company-role-group-j","errorCode":null,"errorMessage":"Duplicate reports for same company+role: ${group.join(', ')}","messagePattern":"Duplicate reports for same company\\+role: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"verify-pipeline.mjs","lineNumber":317,"sourceCode":"\nlet dupReports = 0;\nconst reportsByRole = new Map();\nfor (const name of reportFiles) {\n  const companySlug = name.match(REPORT_FILE_RE)[2];\n  let role = null;\n  try {\n    role = extractRole(readFileSync(join(REPORTS_DIR, name), 'utf-8'));\n  } catch {\n    // Unreadable report — the orphan check below still sees it.\n  }\n  if (!role) continue;\n  const key = normalizeKey(companySlug) + '::' + normalizeKey(role);\n  if (!reportsByRole.has(key)) reportsByRole.set(key, []);\n  reportsByRole.get(key).push(name);\n}\nfor (const group of reportsByRole.values()) {\n  if (group.length > 1) {\n    warn(`Duplicate reports for same company+role: ${group.join(', ')}`);\n    dupReports++;\n  }\n}\nif (dupReports === 0) ok('No duplicate reports for the same company+role');\n\n// --- Check 10: Orphan reports with no tracker row (#1425) ---\n// Every reports/NNN-*.md should be referenced by a tracker row — by the\n// [NNN] link text(s), the NNN- prefix of the linked filename(s), or (only when\n// the cell carries no markdown link at all) the row's own number.\n//\n// The row's own number is a LAST RESORT, not a standing signal. Tracker row\n// numbers and report numbers are independent counters that diverge in normal\n// operation — #1733 established that a reserved report number is discarded\n// when it is <= the tracker max, permanently desynchronising the two. Treating\n// a row's number as a reference whenever it merely coexists with an unrelated\n// link therefore masks real orphans: a row numbered 950 that legitimately\n// links to report 955 also silently \"references\" an unrelated orphaned\n// report 950. Only when the cell has no link is the row number the only signal","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/verify-pipeline.mjs#L299-L335","documentation":"A warning from verify-pipeline.mjs (Check 9) that two or more report files in reports/ normalize to the same company+role key (slugified company + role). Unlike #593 this checks report filenames, not tracker rows; duplicates suggest the same offer was evaluated multiple times, wasting report numbers and possibly creating divergent evaluations.","triggerScenarios":"Running `node verify-pipeline.mjs` when reports/ holds e.g. `042-acme-senior-backend-2026-08-01.md` and `051-acme-senior-backend-2026-09-10.md` — same slugified company::role key — typically from re-evaluating an offer instead of updating the original report, or from slug variations that normalize identically.","commonSituations":"Re-pasting the same JD weeks later and generating a fresh report instead of reusing #42; different batch workers evaluating the same URL concurrently without URL-based dedup; reposts detected by detect-reposts.mjs that were then re-evaluated wholesale.","solutions":["Decide which report is canonical; update its tracker row via set-status.mjs and mark the duplicate row Discarded.","Before re-evaluating a known company+role, check the tracker/report first and only refresh the existing report's content.","Run `node detect-reposts.mjs` to distinguish a genuine repost from a duplicate evaluation before merging.","Rely on merge-tracker.mjs's URL-first dedup so a re-evaluation updates the existing row rather than adding a new one."],"exampleFix":"// before: full re-evaluation of a known offer\npaste JD → new report 051-acme-senior-backend.md\n// after: refresh the existing report, keep one per company+role\nnode set-status.mjs 42 Evaluated --note \"re-verified posting 2026-09-10\"","handlingStrategy":"validation","validationCode":"const key = normalizeKey(companySlug) + '::' + normalizeKey(role);\nif (reportsByRole.has(key)) refreshExistingReport(reportsByRole.get(key)[0]) else writeNewReport();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check the tracker for an existing company+role report before evaluating a JD.","Run detect-reposts.mjs to decide repost vs duplicate.","Let merge-tracker.mjs's URL-first dedup keep one row per posting."],"tags":["duplicate-records","reports","pipeline","deduplication"],"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"}