{"record":{"id":"16f4f8c950872c6f","repo":"jackwener/OpenCLI","slug":"bilibili-creator-comparison-omitted-metric-defin","errorCode":null,"errorMessage":"Bilibili creator comparison omitted metric ${definition.key}","messagePattern":"Bilibili creator comparison omitted metric (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/creator-stats.js","lineNumber":120,"sourceCode":"        throw new CommandExecutionError(`Bilibili creator comparison returned duplicate rows for ${bvid}`);\n    }\n    if (matches.length === 0) {\n        throw new EmptyResultError(\n            `bilibili creator-stats ${bvid}`,\n            'The manuscript was not present in the latest 100 creator analytics rows; it may be older, not owned by this account, or not analyzed yet.',\n        );\n    }\n    const target = matches[0];\n    if (!isRecord(target.stat)) {\n        throw new CommandExecutionError(`Bilibili creator comparison returned malformed stat data for ${bvid}`);\n    }\n    return target;\n}\n\nfunction metricValue(target, definition) {\n    const source = definition.source === 'target' ? target : target.stat;\n    if (!Object.prototype.hasOwnProperty.call(source, definition.key)) {\n        throw new CommandExecutionError(`Bilibili creator comparison omitted metric ${definition.key}`);\n    }\n    const raw = source[definition.key];\n    if (raw === null) return null;\n    if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) {\n        throw new CommandExecutionError(`Bilibili creator comparison returned malformed metric ${definition.key}`);\n    }\n    if (definition.unit === 'percent' && raw > 10_000) {\n        throw new CommandExecutionError(`Bilibili creator comparison returned out-of-range percentage ${definition.key}`);\n    }\n    if ((definition.unit === 'count' || definition.unit === 'seconds') && !Number.isSafeInteger(raw)) {\n        throw new CommandExecutionError(`Bilibili creator comparison returned non-integer metric ${definition.key}`);\n    }\n    return definition.divisor ? raw / definition.divisor : raw;\n}\n\ncli({\n    site: 'bilibili',\n    name: 'creator-stats',","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/creator-stats.js#L102-L138","documentation":"metricValue checks that the metric key defined in the METRICS whitelist (e.g. `play`, `full_play_ratio`) actually exists on the source object (the row itself for target-sourced metrics like `duration`, otherwise `target.stat`). If the key is missing entirely, the library throws instead of silently emitting a wrong/empty value. null values are allowed and pass through, only total absence throws.","triggerScenarios":"Bilibili's compare endpoint omits a whitelisted key from `target.stat` (or from the row for `durationSeconds`), e.g. after an API schema change or for archive types that don't carry certain counters.","commonSituations":"Undocumented API contract drift after Bilibili renames/removes fields; new archives where analytics fields haven't been populated yet; region-specific A/B rollouts of the creator center that drop fields.","solutions":["Wait and retry — newly analyzed manuscripts often gain fields once analytics are computed.","Verify the actual response body of the compare endpoint and update the METRICS key mapping in creator-stats.js if Bilibili renamed the field.","Try a different, older manuscript to confirm the endpoint still returns all metrics.","Update the CLI to a version whose METRICS whitelist matches the current Bilibili API."],"exampleFix":"// before (throws on missing key)\nconst v = metricValue(target, def);\n// after (caller tolerates omission)\nconst source = def.source === 'target' ? target : target.stat;\nconst v = Object.prototype.hasOwnProperty.call(source, def.key) ? metricValue(target, def) : null;","handlingStrategy":"validation","validationCode":"const KEYS = ['play','like','comment','dm','fav','coin','share','total_new_attention_cnt','full_play_ratio','active_fans_rate','tm_rate','crash_rate','interact_rate','play_trans_fan_rate'];\nconst source = def.source === 'target' ? row : row.stat;\nif (!KEYS.every((k) => Object.prototype.hasOwnProperty.call(source ?? {}, k))) { /* treat metrics as unavailable */ }","typeGuard":"function hasMetric(source, key) {\n    return Boolean(source) && typeof source === 'object' && Object.prototype.hasOwnProperty.call(source, key);\n}","tryCatchPattern":"try {\n    const rows = await creatorStats(bvid);\n} catch (e) {\n    if (/omitted metric/.test(e.message)) {\n        // degrade: render partial stats or nulls instead of aborting\n    } else throw e;\n}","preventionTips":["Prefer newer manuscripts when sampling creator stats","Pin the CLI to a version tested against the current Bilibili API","Spot-check the raw compare endpoint after Bilibili creator-center updates","Wrap batch jobs so one missing field doesn't abort the run"],"tags":["bilibili","api-schema","whitelist","missing-field"],"backgroundTag":"api-schema-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}