{"record":{"id":"0b5f38b71b8a743b","repo":"jackwener/OpenCLI","slug":"semanticscholar-label-must-be-a-number-when-pre","errorCode":null,"errorMessage":"semanticscholar ${label} must be a number when present","messagePattern":"semanticscholar (.+?) must be a number when present","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/semanticscholar/utils.js","lineNumber":157,"sourceCode":"        return tldr.text.trim();\n    }\n    return '';\n}\n\n/** First author display name, or '' when authors is missing. */\nexport function firstAuthorName(authors) {\n    if (!Array.isArray(authors) || !authors.length) return '';\n    const first = authors[0];\n    if (first && typeof first === 'object' && typeof first.name === 'string') {\n        return first.name.trim();\n    }\n    return '';\n}\n\nexport function optionalNumber(value, label) {\n    if (value == null) return null;\n    if (typeof value !== 'number' || !Number.isFinite(value)) {\n        throw new CommandExecutionError(`semanticscholar ${label} must be a number when present`);\n    }\n    return value;\n}\n\nexport function normalizePaperRow(paper, label, { rank } = {}) {\n    if (!paper || typeof paper !== 'object') {\n        throw new CommandExecutionError(`semanticscholar ${label} row is not an object`);\n    }\n    if (typeof paper.paperId !== 'string' || !paper.paperId.trim()) {\n        throw new CommandExecutionError(`semanticscholar ${label} row is missing paperId`);\n    }\n    if (typeof paper.title !== 'string' || !paper.title.trim()) {\n        throw new CommandExecutionError(`semanticscholar ${label} row is missing title`);\n    }\n    if (paper.authors != null && !Array.isArray(paper.authors)) {\n        throw new CommandExecutionError(`semanticscholar ${label} row has malformed authors`);\n    }\n    const row = {","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/semanticscholar/utils.js#L139-L175","documentation":"optionalNumber validates that an optional numeric field (year, citationCount) on a Semantic Scholar paper row is either null/undefined or a finite number. If the field is present but is a string, NaN, or Infinity, it throws this CommandExecutionError naming the field via `label`. It enforces the adapter's typed-row contract before output.","triggerScenarios":"normalizePaperRow passes paper.year or paper.citationCount to optionalNumber and the API returned a non-numeric value — e.g. year as a string \"2023\", citationCount as null-coalesced garbage, or a dataset row with corrupted fields.","commonSituations":"Semantic Scholar API schema changes returning strings for numeric fields; third-party/proxied responses with stringified numbers; hand-built test fixtures with string values.","solutions":["Coerce numeric strings before passing through: Number(value) and re-check Number.isFinite.","Check which field the label names (e.g. `${label} year`) and inspect the raw API response for that field.","If the API changed shape, pin/upgrade the adapter version that matches the current schema.","Sanitize upstream data sources feeding normalizePaperRow."],"exampleFix":"// before: strict pass-through\nyear: optionalNumber(paper.year, `${label} year`),\n// after: coerce numeric strings first\nconst year = paper.year == null ? null : (typeof paper.year === 'string' && paper.year.trim() !== '' ? Number(paper.year) : paper.year);\nyear: optionalNumber(year, `${label} year`),","handlingStrategy":"validation","validationCode":"function toFiniteNumberOrNull(v) {\n  if (v == null) return null;\n  const n = typeof v === 'number' ? v : Number(v);\n  return Number.isFinite(n) ? n : null;\n}\n// apply before calling the API-consuming code path\nconst year = toFiniteNumberOrNull(rawPaper.year);","typeGuard":"function isFiniteNumber(v) {\n  return typeof v === 'number' && Number.isFinite(v);\n}","tryCatchPattern":"try {\n  const row = normalizePaperRow(paper, 'citation');\n} catch (err) {\n  if (/must be a number when present/.test(err.message)) {\n    return null; // skip malformed numeric field\n  }\n  throw err;\n}","preventionTips":["Coerce numeric strings from upstream sources before normalization.","Pin the adapter version against the S2 API schema you test with.","Sanitize fixtures used in scripts to use real numbers."],"tags":["validation","type-error","schema","semantic-scholar"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}