{"record":{"id":"8c66da11e71271b0","repo":"jackwener/OpenCLI","slug":"eastmoney-convertible-returned-malformed-field","errorCode":null,"errorMessage":"eastmoney convertible returned malformed ${field} for ${bondCode || 'unknown bond'}","messagePattern":"eastmoney convertible returned malformed (.+?) for (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/convertible.js","lineNumber":33,"sourceCode":"  value:         { fid: 'f236', order: 'desc' }, // 转股价值\n  // #2109: f239 is the putback trigger price (= convPrice × 0.7), not YTM.\n  // Renamed so `--sort` no longer claims to order by a value it doesn't hold.\n  'put-trigger': { fid: 'f239', order: 'desc' }, // 回售触发价\n};\n\nconst NUMERIC_FIELDS = [\n  'f2', 'f3', 'f229', 'f230', 'f235', 'f236', 'f237', 'f238', 'f239',\n];\n\nfunction isEastmoneyScalar(value) {\n  return typeof value === 'string' || typeof value === 'number';\n}\n\nfunction normalizeEastmoneyNumeric(value, field, bondCode) {\n  if (typeof value === 'number' && Number.isFinite(value)) return value;\n  // Eastmoney uses \"-\" for temporarily unavailable quote metrics.\n  if (value === '-') return value;\n  throw new CommandExecutionError(`eastmoney convertible returned malformed ${field} for ${bondCode || 'unknown bond'}`);\n}\n\nfunction normalizeEastmoneyString(value, field, bondCode) {\n  if (isEastmoneyScalar(value)) return String(value);\n  throw new CommandExecutionError(`eastmoney convertible returned malformed ${field} for ${bondCode || 'unknown bond'}`);\n}\n\nfunction normalizeEastmoneyIdentityString(value, field, bondCode) {\n  if (typeof value === 'string' && value.trim()) return value;\n  throw new CommandExecutionError(`eastmoney convertible returned malformed ${field} for ${bondCode || 'unknown bond'}`);\n}\n\nexport function parseConvertibleLimit(value) {\n  if (value === undefined || value === null || value === '') return 20;\n  if (typeof value === 'number') {\n    if (Number.isInteger(value) && value >= 1 && value <= 100) return value;\n    throw new ArgumentError('eastmoney convertible --limit must be an integer between 1 and 100');\n  }","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/convertible.js#L15-L51","documentation":"normalizeEastmoneyNumeric() validates quote metrics from the convertible-bond API. It accepts finite numbers and the literal '-' (Eastmoney's marker for temporarily unavailable data), and throws CommandExecutionError for anything else — meaning a field the code expects to be numeric came back as a string, null, undefined, NaN, or an object.","triggerScenarios":"A row field (e.g. price, premium rate, volume) is null/undefined, a numeric-looking string like '123.45', NaN, or a nested object — anything not a finite number or '-'. Called from mapConvertibleRow for each numeric column.","commonSituations":"Eastmoney changes field semantics or units and returns strings; suspended bonds return null instead of '-'; a schema drift makes a column an object after a site update.","solutions":["Inspect the raw API row for the offending field and confirm its current type.","If Eastmoney now sends numeric strings, preprocess with Number(value) before mapping, or relax the check.","Treat the throw as data-quality signal: catch CommandExecutionError, log the bond code + field, and skip that row.","Pin/verify against the current Eastmoney convertible API response format."],"exampleFix":"// before\nconst price = normalizeEastmoneyNumeric(row.PRICE, 'price', code);\n// after\nconst rawPrice = row.PRICE === '-' ? row.PRICE : (row.PRICE == null ? row.PRICE : Number(row.PRICE));\nconst price = normalizeEastmoneyNumeric(rawPrice, 'price', code);","handlingStrategy":"type-guard","validationCode":"const isOkNumeric = (v) => (typeof v === 'number' && Number.isFinite(v)) || v === '-';\nif (!isOkNumeric(row.PRICE)) console.warn('price unavailable for', row.SECURITY_CODE);","typeGuard":"const isEastmoneyNumeric = (v) => (typeof v === 'number' && Number.isFinite(v)) || v === '-';","tryCatchPattern":"try {\n  const mapped = mapConvertibleRow(row);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /malformed/.test(e.message)) {\n    console.warn(`Skipping row with bad numeric field: ${e.message}`);\n  } else throw e;\n}","preventionTips":["Pre-coerce numeric-looking strings with Number() before mapping","Map null to '-' when the API uses '-' for unavailable values","Dump one raw API row to pin down current field types","Catch per-row errors so one bad field doesn't kill the whole batch"],"tags":["api","schema","data-quality","eastmoney"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}