{"record":{"id":"4ace6a61b21d2813","repo":"jackwener/OpenCLI","slug":"malformed-toutiao-recommend-row-group-id-source-u","errorCode":null,"errorMessage":"Malformed toutiao recommend row: group_id/source_url mismatch for ${JSON.stringify(rawGroupId)}.","messagePattern":"Malformed toutiao recommend row: group_id/source_url mismatch for (.+?)\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/toutiao/utils.js","lineNumber":234,"sourceCode":"}\n\nfunction articleIdFromToutiaoPath(pathname) {\n    return String(pathname || '').match(/^\\/(?:group|article)\\/([A-Za-z0-9_-]+)\\/?$/)?.[1] || '';\n}\n\nfunction buildGroupIdentity(sourceUrl, rawGroupId) {\n    const source = firstPartyToutiaoUrl(sourceUrl);\n    const sourceId = source ? articleIdFromToutiaoPath(source.pathname) : '';\n    const explicitId = trimOrNull(rawGroupId);\n    const groupId = explicitId || trimOrNull(sourceId);\n    if (!groupId) {\n        throw new CommandExecutionError('Malformed toutiao recommend row: missing group_id and article source_url.');\n    }\n    if (!/^[A-Za-z0-9_-]+$/.test(groupId)) {\n        throw new CommandExecutionError(`Malformed toutiao recommend row: invalid group_id ${JSON.stringify(rawGroupId)}.`);\n    }\n    if (sourceId && explicitId && explicitId !== sourceId) {\n        throw new CommandExecutionError(`Malformed toutiao recommend row: group_id/source_url mismatch for ${JSON.stringify(rawGroupId)}.`);\n    }\n    return {\n        groupId,\n        url: `https://www.toutiao.com/group/${groupId}/`,\n    };\n}\n\nfunction formatBehotTime(value) {\n    const seconds = Number(value);\n    if (!Number.isFinite(seconds) || seconds <= 0) return null;\n    return `${new Date(seconds * 1000).toISOString().slice(0, 19)}Z`;\n}\n\n/**\n * Project a row from the public toutiao channel feed into stable shape.\n *\n * Sponsored rows are dropped so an agent never reads an ad as editorial\n * content; absent counts stay null rather than being coerced to 0.","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/toutiao/utils.js#L216-L252","documentation":"buildGroupIdentity cross-checks the explicit group_id against the article id extracted from the source_url path. When both exist and differ, the row is internally inconsistent — trusting either value could produce a wrong canonical link — so it throws this CommandExecutionError naming the conflicting group_id.","triggerScenarios":"A row where group_id='123' but source_url='https://www.toutiao.com/article/456/'; copying a group_id from a different row than its URL; merging datasets where ids and URLs were not kept in sync.","commonSituations":"Faulty ETL joins mapping ids to the wrong rows; upstream feed republishing an article under a new id while the old group_id remains cached; hand-edited fixtures.","solutions":["Determine which value is correct (fetch both ids and compare content) and fix the row's data.","Re-derive group_id from source_url by omitting the explicit group_id argument, letting the URL id win.","Flag mismatched rows for review instead of silently picking one side.","Add an upstream-consistency check in your ingestion pipeline that runs before buildGroupIdentity."],"exampleFix":"// before\nbuildGroupIdentity(row.source_url, row.group_id); // stale group_id\n// after\nbuildGroupIdentity(row.source_url, null); // trust source_url-derived id","handlingStrategy":"validation","validationCode":"const fromUrl = String(row.source_url || '').replace(/^https?:\\/\\/[^/]+/, '').match(/^\\/(?:group|article)\\/([A-Za-z0-9_-]+)\\/?$/)?.[1];\nif (row.group_id && fromUrl && row.group_id !== fromUrl) flagMismatch(row);","typeGuard":"const idsConsistent = (row) => { const fromUrl = String(row?.source_url || '').replace(/^https?:\\/\\/[^/]+/, '').match(/^\\/(?:group|article)\\/([A-Za-z0-9_-]+)\\/?$/)?.[1]; return !row?.group_id || !fromUrl || row.group_id === fromUrl; };","tryCatchPattern":"try {\n  const identity = buildGroupIdentity(row.source_url, row.group_id);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('mismatch')) return quarantineRow(row, e.message);\n  throw e;\n}","preventionTips":["Prefer deriving the id from source_url (pass null group_id) when the two might disagree.","Add an ETL assertion that group_id matches the id embedded in source_url.","Quarantine mismatched rows for manual review instead of guessing."],"tags":["data-integrity","consistency-check","toutiao"],"backgroundTag":"data-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}