{"record":{"id":"bf1734712550e56b","repo":"santifer/career-ops","slug":"name-must-be-a-percentage-e-g-70-or-70-g","errorCode":null,"errorMessage":"--${name} must be a percentage (e.g. 70 or 70%), got \"${v}\"","messagePattern":"--(.+?) must be a percentage \\(e\\.g\\. 70 or 70%\\), got \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"assessment-log.mjs","lineNumber":125,"sourceCode":"}\n\n// --- Append (`add` subcommand) ---\nexport function buildRow(fields, today) {\n  const req = (name) => {\n    const v = String(fields[name] ?? '').trim();\n    if (!v) throw new Error(`--${name} is required`);\n    if (v.includes('\\t') || v.includes('\\n')) throw new Error(`--${name} must not contain tabs or newlines`);\n    return v;\n  };\n  const opt = (name) => {\n    const v = String(fields[name] ?? '').trim();\n    if (v.includes('\\t') || v.includes('\\n')) throw new Error(`--${name} must not contain tabs or newlines`);\n    return v || '-';\n  };\n  const optPct = (name) => {\n    const v = String(fields[name] ?? '').trim();\n    if (!v) return '-';\n    if (parsePct(v) === null) throw new Error(`--${name} must be a percentage (e.g. 70 or 70%), got \"${v}\"`);\n    return v;\n  };\n  return [\n    today, req('company'), opt('report'), req('platform'), req('subject'),\n    optPct('threshold'), optPct('score'), opt('stale') === '-' ? '' : opt('stale'),\n  ].join('\\t');\n}\n\nfunction addEntry(args) {\n  const fields = {};\n  for (let i = 0; i < args.length; i++) {\n    const m = args[i].match(/^--(company|report|platform|subject|threshold|score|stale)$/);\n    if (m) { fields[m[1]] = args[i + 1] ?? ''; i++; }\n  }\n  const today = new Date().toISOString().slice(0, 10);\n  let row;\n  try {\n    row = buildRow(fields, today);","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/assessment-log.mjs#L107-L143","documentation":"Validation in the `optPct` helper of `buildRow`: optional percentage fields (`threshold`, `score`) must parse via `parsePct` if non-empty. Accepts forms like `70` or `70%`; anything else (e.g. `high`, `7/10`, `0.7`, `70 percent`) is rejected to keep the column numeric and comparable.","triggerScenarios":"Passing `--threshold high`, `--score 7/10`, `--threshold 0.7`, `--score 70 percent`, `--score B+`; any non-empty value for which `parsePct` returns `null`.","commonSituations":"User thinks in fractions (0.7) or letter grades; agent emits a qualitative label; copy-pasting from a UI that shows '70%'; locale using comma decimals (70,5); confusion between raw score and percentage.","solutions":["Pass an integer percentage optionally suffixed with `%`: `--threshold 70` or `--score 85%`.","Convert fractions upstream: `Math.round(frac * 100)` → then pass as `--score <n>`.","Convert letter grades upstream via your own mapping before invoking.","Use a comma-decimal? convert to integer first: `parseFloat('70,5'.replace(',', '.'))` → 70 or 71.","Leave the flag off entirely if you don't have a value — `optPct` returns `-` for empty input."],"exampleFix":"// before\nnode assessment-log.mjs add --company acme --platform leetcode --subject arrays --score 0.85\n// after\nnode assessment-log.mjs add --company acme --platform leetcode --subject arrays --score 85","handlingStrategy":"validation","validationCode":"function normalizePct(v) {\n  if (!v) return '';\n  const m = String(v).trim().match(/^(\\d+(?:\\.\\d+)?)%?$/);\n  if (!m) throw new Error(`Not a percentage: ${v}`);\n  return String(Math.round(parseFloat(m[1])));\n}\n// convert fractions upstream: Math.round(frac * 100)","typeGuard":"function isPct(v) {\n  return /^(\\d+(?:\\.\\d+)?)%?$/.test(String(v ?? '').trim());\n}","tryCatchPattern":null,"preventionTips":["Pass integer percentages, optionally with a trailing `%`.","Convert fractions (0.85 → 85) and letter grades upstream.","Leave threshold/score flags off when you have no value.","Test rejection of `0.7`, `7/10`, `high`."],"tags":["validation","cli","assessment-log","percentage","numeric"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}