| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const { buildLocalSeedMaterialIndex, sanitizeCell, sanitizePublicUrl } = require('./local-seed-material-index');
- const ROOT = path.resolve(__dirname, '..');
- const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'local-seed-to-intake-worklist-latest');
- function main() {
- const args = parseArgs(process.argv.slice(2));
- const workspaceRoot = path.resolve(args.workspaceRoot || path.join(ROOT, '..'));
- const outputsDir = path.resolve(args.outputs || path.join(ROOT, 'outputs'));
- const outputDir = path.resolve(args.output || DEFAULT_OUTPUT);
- const seedIndex = buildLocalSeedMaterialIndex({ root: ROOT, workspaceRoot, outputsDir });
- const summary = buildLocalSeedToIntakeWorklist({ root: ROOT, seedIndex });
- fs.mkdirSync(outputDir, { recursive: true });
- const summaryPath = path.join(outputDir, 'local-seed-to-intake-worklist-summary.json');
- const reportPath = path.join(outputDir, 'local-seed-to-intake-worklist.md');
- const csvPath = path.join(outputDir, 'local-seed-to-intake-worklist.csv');
- const historyDraftPath = path.join(outputDir, 'history-intake-draft.csv');
- const reviewDraftPath = path.join(outputDir, 'manual-review-draft.csv');
- const videoWorklistPath = path.join(outputDir, 'video-resource-worklist.csv');
- const candidateWorklistPath = path.join(outputDir, 'candidate-seed-worklist.csv');
- fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf8');
- fs.writeFileSync(reportPath, withBom(renderReport(summary)), 'utf8');
- fs.writeFileSync(csvPath, withBom(renderUnifiedCsv(summary)), 'utf8');
- fs.writeFileSync(historyDraftPath, withBom(renderHistoryDraftCsv(summary)), 'utf8');
- fs.writeFileSync(reviewDraftPath, withBom(renderReviewDraftCsv(summary)), 'utf8');
- fs.writeFileSync(videoWorklistPath, withBom(renderVideoWorklistCsv(summary)), 'utf8');
- fs.writeFileSync(candidateWorklistPath, withBom(renderCandidateWorklistCsv(summary)), 'utf8');
- console.log(JSON.stringify({
- outputDir,
- summary: summaryPath,
- report: reportPath,
- csv: csvPath,
- historyDraft: historyDraftPath,
- reviewDraft: reviewDraftPath,
- videoWorklist: videoWorklistPath,
- candidateWorklist: candidateWorklistPath,
- historyDraftRowCount: summary.historyDraftRows.length,
- reviewDraftRowCount: summary.reviewDraftRows.length,
- referenceSeedRowCount: summary.referenceSeedRows.length,
- candidateSeedRowCount: summary.candidateSeedRows.length,
- canCloseProofGap: summary.canCloseProofGap
- }, null, 2));
- if (args.strict && summary.directCustomerProof !== false) process.exitCode = 1;
- }
- function buildLocalSeedToIntakeWorklist({ root, seedIndex }) {
- const references = extractReferences(seedIndex.materials || []);
- const candidates = extractCandidates(seedIndex.materials || []);
- const videoCandidates = candidates.filter(item => /视频/i.test(item.suggestedFormat || ''));
- const briefSeed = buildDhaBriefSeed(seedIndex, references);
- const videoWorklistRows = buildVideoWorklistRows({ briefSeed, references, videoCandidates });
- const candidateSeedRows = candidates.slice(0, 12).map((item, index) => ({
- order: index + 1,
- briefId: briefSeed?.briefId || 'dha-xhs-mom-baby-seed-001',
- platform: '小红书',
- creatorName: item.creatorName,
- xhsId: item.xhsId,
- sourceSheet: item.sourceSheet,
- sourceWorkbook: item.sourceWorkbook,
- recommendationLevel: item.recommendationLevel,
- region: item.region,
- fans: item.fans,
- imageQuote: item.imageQuote,
- videoQuote: item.videoQuote,
- suggestedFormat: item.suggestedFormat,
- suggestedQuote: item.suggestedQuote,
- readMedian: item.readMedian,
- interactionMedian: item.interactionMedian,
- readUnitCost: item.readUnitCost,
- targetFile: 'outputs/data-intake-pack-latest/manual-review-template.csv',
- missingForProof: [
- '主页链接或真实可访问 profile URL',
- '商务人工复核标签',
- '客户最终选中/拒绝',
- '拒绝原因或负样本归因',
- '本轮人工补号量'
- ]
- }));
- const historyDraftRows = buildHistoryDraftRows({ briefSeed, candidateSeedRows });
- const reviewDraftRows = buildReviewDraftRows({ briefSeed, candidateSeedRows });
- const proofGapMaterialBridge = buildProofGapMaterialBridge({
- historyDraftRows,
- reviewDraftRows,
- referenceSeedRows: references,
- candidateSeedRows,
- videoWorklistRows,
- videoCandidates
- });
- return {
- generatedAt: new Date().toISOString(),
- root,
- proofLevel: 'not_business_proof',
- materialType: 'seed_to_intake_worklist',
- directCustomerProof: false,
- canCloseProofGap: false,
- complete: false,
- sourceSeedIndex: {
- proofLevel: seedIndex.proofLevel || 'not_business_proof',
- directCustomerProof: Boolean(seedIndex.directCustomerProof),
- canCloseProofGap: Boolean(seedIndex.canCloseProofGap),
- materialCount: Number(seedIndex.materialCount || 0),
- existingMaterialCount: Number(seedIndex.existingMaterialCount || 0)
- },
- counts: {
- historyDraftRows: historyDraftRows.length,
- reviewDraftRows: reviewDraftRows.length,
- referenceSeedRows: references.length,
- candidateSeedRows: candidateSeedRows.length,
- videoWorklistRows: videoWorklistRows.length,
- videoCandidateNeedsResourceRows: videoCandidates.length
- },
- historyDraftRows,
- reviewDraftRows,
- referenceSeedRows: references,
- candidateSeedRows,
- videoWorklistRows,
- proofGapMaterialBridge,
- missingProofRequirements: [
- '至少 5 个真实历史 Brief',
- '历史人工补号量基线',
- '人工最终名单中的真实博主名称和主页链接',
- '客户最终选中/拒绝记录',
- '拒绝原因或商务复核标签',
- '至少 1 个真实候选视频 URL、封面、ASR/字幕、帧图或正文证据',
- '本轮人工补号量',
- 'history:audit readyForCustomerEffectProof=true',
- 'video:resource-readiness readyForVideoAbPreflight=true',
- 'customer-effect:audit overallPass=true'
- ],
- targetFiles: [
- 'outputs/data-intake-pack-latest/history-data-template.csv',
- 'outputs/video-intake-pack-latest/video-resource-template.csv',
- 'outputs/data-intake-pack-latest/manual-review-template.csv'
- ],
- guardrails: [
- '本 worklist 只把本地 DHA Brief、PoC 候选和参考账号解析转成补表草稿,不写入正式 intake 模板。',
- '空字段表示仍需商务/投放补真实材料;不得用“未知”“0”或示例值补齐客户选择、拒绝原因或人工补号量。',
- '候选账号来自 PoC/参考分析种子,不等同于人工最终名单或客户选中名单。',
- '参考笔记链接和候选账号只能作为线索;缺真实候选视频 URL、封面、ASR/字幕或帧图时,不得启动视频 A/B 证明。',
- '本 worklist 的 proofLevel 固定为 not_business_proof,不能关闭 proof-gap。'
- ],
- nextActions: [
- {
- owner: '商务',
- title: '把 history-intake-draft.csv 扩成 5-10 个真实 Brief',
- action: '保留 DHA 行作为本地种子草稿,同时补齐其他真实历史 Brief、历史人工补号量基线、人工最终名单和客户选择/拒绝。',
- acceptance: 'intake:readiness historyReady=true,history:audit readyForCustomerEffectProof=true。'
- },
- {
- owner: '商务/投放',
- title: '按 video-resource-worklist.csv 补真实候选视频',
- action: '优先为建议形式为视频的候选补视频链接、封面、ASR/字幕、帧图或正文证据。',
- acceptance: 'video:resource-readiness readyForVideoAbPreflight=true,realCandidateRows>=1。'
- },
- {
- owner: '商务',
- title: '按 candidate-seed-worklist.csv 做人工复核',
- action: '只把候选种子作为复核池,补真实主页链接、人工复核标签、客户选择、拒绝原因和本轮人工补号量。',
- acceptance: 'review:metrics 与 customer-effect:audit 在真实标注后通过。'
- }
- ]
- };
- }
- function buildDhaBriefSeed(seedIndex, references) {
- const briefMaterial = (seedIndex.materials || []).find(item => item.id === 'dha-brief-workbook' && item.exists);
- if (!briefMaterial) return null;
- const referenceLinks = references
- .filter(item => item.finalLink && !/\/404\b/.test(item.finalLink))
- .slice(0, 4)
- .map(item => item.finalLink);
- return {
- briefId: 'dha-xhs-mom-baby-seed-001',
- projectName: 'DHA 小红书母婴提号',
- category: '母婴',
- customerBrief: [
- '平台:小红书',
- '产品:DHA',
- '账号类型:干货科普(营养师/育婴师/妈妈为主)、618合集(6个月-7岁孩子妈妈博主)',
- '预算:1000-5000',
- '粉丝画像:女性为主,25-44岁占比70%以上;兴趣需命中母婴/萌娃/记录生活中至少 2 个'
- ].join(';'),
- historicalManualSupplementBaseline: '',
- referenceAccountOrVideo: referenceLinks.join('|'),
- platform: '小红书',
- manualFinalCreatorName: '',
- manualFinalProfileUrl: '',
- manualReviewLabel: '',
- customerDecision: '',
- rejectReason: '',
- sourcePath: briefMaterial.path,
- canWritePartialFields: true,
- missingForProof: [
- '历史人工补号量基线',
- '人工最终名单博主名称',
- '人工最终名单主页链接',
- '客户最终选中/拒绝',
- '拒绝原因或商务复核标签',
- '仍需至少 4 个额外真实 Brief 才达到 minBriefs=5'
- ]
- };
- }
- function buildHistoryDraftRows({ briefSeed, candidateSeedRows }) {
- if (!briefSeed) return [];
- const rows = (candidateSeedRows || []).slice(0, 12).map(candidate => ({
- ...briefSeed,
- manualFinalCreatorName: candidate.creatorName || '',
- manualFinalProfileUrl: profileHint(candidate.xhsId),
- manualReviewLabel: '',
- customerDecision: '',
- rejectReason: '',
- sourcePath: `${candidate.sourceWorkbook} / ${candidate.sourceSheet}`,
- candidateSeedLevel: candidate.recommendationLevel || '',
- missingForProof: [
- '客户最终选中/拒绝记录',
- '拒绝原因或商务复核标签',
- '历史人工补号量基线',
- '真实主页链接复核',
- '仍需至少 5 个真实历史 Brief 才能满足 minBriefs=5'
- ]
- }));
- return rows.length ? rows : [briefSeed];
- }
- function buildReviewDraftRows({ briefSeed, candidateSeedRows }) {
- const briefId = briefSeed?.briefId || 'dha-xhs-mom-baby-seed-001';
- return (candidateSeedRows || []).map((candidate, index) => ({
- briefId,
- strategy: strategyFromCandidate(candidate),
- rank: index + 1,
- platform: candidate.platform || '小红书',
- creatorName: candidate.creatorName || '',
- totalScore: '',
- briefMatchScore: '',
- referenceStyleScore: '',
- homepageEvidenceScore: '',
- visualQualityScore: '',
- toneFitScore: '',
- evidenceBonus: '',
- evidenceRiskPenalty: '',
- reason: `PoC候选:${candidate.recommendationLevel || '待复核'};建议形式:${candidate.suggestedFormat || '待补'};建议报价:${candidate.suggestedQuote || '待补'}`,
- risk: '需商务复核真实主页链接、报价有效期、内容调性、客户最终选择和本轮人工补号量。',
- profileUrl: profileHint(candidate.xhsId),
- manualReviewLabel: '',
- customerDecision: '',
- attributionType: '',
- feedbackReason: '',
- currentManualSupplementCount: '',
- source: `${candidate.sourceWorkbook} / ${candidate.sourceSheet}`,
- proofBoundary: 'seed_only_not_customer_effect_proof'
- }));
- }
- function buildProofGapMaterialBridge({ historyDraftRows, reviewDraftRows, referenceSeedRows, candidateSeedRows, videoWorklistRows, videoCandidates }) {
- return [
- {
- id: 'historical-dataset',
- materialFound: historyDraftRows.length > 0,
- connectedDraft: 'history-intake-draft.csv',
- draftRowCount: historyDraftRows.length,
- canCloseProofGap: false,
- whyNotClosed: [
- '当前只接入 DHA/PoC 种子,未达到 5 个真实历史 Brief。',
- '客户选择/拒绝、拒绝原因和历史人工补号量基线仍为空,不能通过 readyForCustomerEffectProof。'
- ]
- },
- {
- id: 'video-ab-live-proof',
- materialFound: referenceSeedRows.length > 0 || videoWorklistRows.length > 0,
- connectedDraft: 'video-resource-worklist.csv',
- referenceSeedRowCount: referenceSeedRows.length,
- videoCandidateSeedRowCount: videoCandidates.length,
- canCloseProofGap: false,
- whyNotClosed: [
- '资源线索不等于 acceptance:video-ab 的 live proofContext。',
- '仍需要 live runtime credential、company、VOC social provider token、video analysis token 后运行 npm run acceptance:video-ab。'
- ]
- },
- {
- id: 'manual-review-and-customer-effect',
- materialFound: candidateSeedRows.length > 0 || reviewDraftRows.length > 0,
- connectedDraft: 'manual-review-draft.csv',
- draftRowCount: reviewDraftRows.length,
- canCloseProofGap: false,
- whyNotClosed: [
- '候选种子已转成商务复核草表,但人工复核标签、客户选择、归因、反馈原因和本轮人工补号量仍需真实填写。',
- 'customer-effect:audit 还必须绑定通过的历史审计 JSON。'
- ]
- }
- ];
- }
- function profileHint(xhsId) {
- return xhsId ? `小红书号:${xhsId}` : '';
- }
- function strategyFromCandidate(candidate) {
- const source = `${candidate.sourceWorkbook || ''} ${candidate.sourceSheet || ''}`.toLowerCase();
- if (source.includes('参考分析')) return 'reference-account';
- if (/主推|main|strong/i.test(candidate.recommendationLevel || candidate.sourceSheet || '')) return 'result-first-broad';
- return 'candidate-seed';
- }
- function extractReferences(materials) {
- const rows = [];
- for (const material of materials || []) {
- for (const sheet of material.workbook?.sheets || []) {
- if (!/参考.*(解析|链接)/.test(sheet.name || '')) continue;
- for (const row of sheet.sampleRows || []) {
- const referenceType = clean(row[0]);
- const shortLink = sanitizePublicUrl(clean(row[1]));
- const noteId = clean(row[2]);
- const authorName = clean(row[3]);
- const xhsId = clean(row[5]);
- const status = clean(row[8]);
- const finalLink = sanitizePublicUrl(clean(row[9]));
- if (!referenceType && !shortLink && !finalLink) continue;
- rows.push({
- referenceType,
- shortLink,
- noteId,
- authorName,
- xhsId,
- status,
- finalLink,
- sourceWorkbook: material.path,
- sourceSheet: sheet.name,
- usableFor: /已解析/.test(status) && finalLink && !/\/404\b/.test(finalLink)
- ? '可作为参考账号/参考内容线索转写'
- : '需人工重新打开短链或替换为可访问参考资源',
- missingForProof: [
- '是否真实参考视频需投放复核',
- '不能替代真实候选视频',
- '不能替代客户选择或人工补号量'
- ]
- });
- }
- }
- }
- return uniqueBy(rows, item => item.finalLink || item.shortLink || `${item.referenceType}:${item.authorName}`);
- }
- function extractCandidates(materials) {
- const rows = [];
- for (const material of materials || []) {
- for (const sheet of material.workbook?.sheets || []) {
- if (!/主推账号|备选/.test(sheet.name || '')) continue;
- for (const row of sheet.sampleRows || []) {
- const creatorName = clean(row[1]);
- if (!creatorName) continue;
- rows.push({
- recommendationLevel: clean(row[0]),
- creatorName,
- xhsId: clean(row[2]),
- region: clean(row[3]),
- fans: clean(row[4]),
- imageQuote: clean(row[5]),
- videoQuote: clean(row[6]),
- suggestedFormat: clean(row[7]),
- suggestedQuote: clean(row[8]),
- readMedian: clean(row[9]),
- interactionMedian: clean(row[10]),
- readUnitCost: clean(row[11]),
- sourceWorkbook: material.path,
- sourceSheet: sheet.name
- });
- }
- }
- }
- return uniqueBy(rows, item => item.xhsId || item.creatorName);
- }
- function buildVideoWorklistRows({ briefSeed, references, videoCandidates }) {
- const briefId = briefSeed?.briefId || 'dha-xhs-mom-baby-seed-001';
- const referenceRows = references
- .filter(item => /video|视频/i.test(item.referenceType || '') || /视频/.test(item.usableFor || ''))
- .map((item, index) => ({
- order: index + 1,
- briefId,
- projectName: briefSeed?.projectName || 'DHA 小红书母婴提号',
- platform: '小红书',
- role: '参考视频',
- creatorName: item.authorName,
- profileOrHomepage: item.xhsId ? `小红书号:${item.xhsId}` : '',
- videoLink: item.finalLink || item.shortLink,
- source: `${item.sourceWorkbook} / ${item.sourceSheet}`,
- resourceStatus: item.finalLink && !/\/404\b/.test(item.finalLink) ? '参考线索可访问,需投放复核是否为真实视频资源' : '短链或最终链接不可用,需替换',
- requiredFill: '补封面、ASR/字幕、帧图、标题、发布时间、内容摘要和真实资源确认',
- canMarkRealResourceNow: false
- }));
- const candidateRows = videoCandidates.map((item, index) => ({
- order: referenceRows.length + index + 1,
- briefId,
- projectName: briefSeed?.projectName || 'DHA 小红书母婴提号',
- platform: '小红书',
- role: '候选视频',
- creatorName: item.creatorName,
- profileOrHomepage: item.xhsId ? `小红书号:${item.xhsId}` : '',
- videoLink: '',
- source: `${item.sourceWorkbook} / ${item.sourceSheet}`,
- resourceStatus: '候选账号种子,缺真实候选视频 URL',
- requiredFill: '补真实候选视频 URL、封面、ASR/字幕、帧图或正文证据后,才能在 video-resource-template.csv 标为真实资源',
- canMarkRealResourceNow: false
- }));
- return [...referenceRows, ...candidateRows];
- }
- function renderReport(summary) {
- return [
- '# 本地种子转 intake 补表 worklist',
- '',
- `- 生成时间:${summary.generatedAt}`,
- `- proofLevel:${summary.proofLevel}`,
- `- materialType:${summary.materialType}`,
- `- directCustomerProof:${summary.directCustomerProof}`,
- `- canCloseProofGap:${summary.canCloseProofGap}`,
- `- 历史草稿行:${summary.counts.historyDraftRows}`,
- `- 商务复核草表行:${summary.counts.reviewDraftRows}`,
- `- 参考线索:${summary.counts.referenceSeedRows}`,
- `- 候选种子:${summary.counts.candidateSeedRows}`,
- `- 视频补表行:${summary.counts.videoWorklistRows}`,
- '',
- '## 边界',
- '',
- ...summary.guardrails.map(item => `- ${item}`),
- '',
- '## 目标文件',
- '',
- ...summary.targetFiles.map(item => `- ${item}`),
- '',
- '## 历史数据草稿',
- '',
- '| brief编号 | 项目 | 类目 | 参考链接 | 可直接转写 | 仍缺证明 |',
- '| --- | --- | --- | --- | --- | --- |',
- ...summary.historyDraftRows.map(item => tableRow([
- item.briefId,
- item.projectName,
- item.category,
- item.referenceAccountOrVideo || '缺失',
- item.canWritePartialFields,
- item.missingForProof.join(';')
- ])),
- '',
- '## 参考账号/内容线索',
- '',
- '| 类型 | 作者 | 状态 | 最终链接 | 来源 | 用途 | 仍缺证明 |',
- '| --- | --- | --- | --- | --- | --- | --- |',
- ...summary.referenceSeedRows.map(item => tableRow([
- item.referenceType,
- item.authorName || '缺失',
- item.status || '缺失',
- item.finalLink || item.shortLink || '缺失',
- `${item.sourceWorkbook} / ${item.sourceSheet}`,
- item.usableFor,
- item.missingForProof.join(';')
- ])),
- '',
- '## 候选种子',
- '',
- '| 序号 | 账号 | 红书号 | 推荐等级 | 建议形式 | 报价 | 来源 | 仍缺证明 |',
- '| ---: | --- | --- | --- | --- | --- | --- | --- |',
- ...summary.candidateSeedRows.map(item => tableRow([
- item.order,
- item.creatorName,
- item.xhsId,
- item.recommendationLevel,
- item.suggestedFormat,
- item.suggestedQuote,
- `${item.sourceWorkbook} / ${item.sourceSheet}`,
- item.missingForProof.join(';')
- ])),
- '',
- '## 视频资源补表清单',
- '',
- '| 序号 | 角色 | 博主 | 视频链接 | 当前状态 | 需补字段 | 可标真实资源 |',
- '| ---: | --- | --- | --- | --- | --- | --- |',
- ...summary.videoWorklistRows.map(item => tableRow([
- item.order,
- item.role,
- item.creatorName || '缺失',
- item.videoLink || '缺失',
- item.resourceStatus,
- item.requiredFill,
- item.canMarkRealResourceNow
- ])),
- '',
- '## Proof Gap Material Bridge',
- '',
- '| Gap | Material found | Connected draft | Rows | Can close | Why not closed |',
- '| --- | --- | --- | ---: | --- | --- |',
- ...summary.proofGapMaterialBridge.map(item => tableRow([
- item.id,
- item.materialFound,
- item.connectedDraft,
- item.draftRowCount ?? item.referenceSeedRowCount ?? 0,
- item.canCloseProofGap,
- item.whyNotClosed.join('; ')
- ])),
- '',
- '## 仍缺证明',
- '',
- ...summary.missingProofRequirements.map(item => `- ${item}`),
- '',
- '## 下一步',
- '',
- '| 负责人 | 动作 | 验收 |',
- '| --- | --- | --- |',
- ...summary.nextActions.map(item => tableRow([item.owner, `${item.title}:${item.action}`, item.acceptance])),
- ''
- ].join('\n');
- }
- function renderUnifiedCsv(summary) {
- const rows = [
- ['类型', '负责人', '目标文件', '字段/对象', '建议值/动作', '来源', '可直接转写', '仍缺证明', '边界'],
- ...summary.historyDraftRows.flatMap(item => [
- ['history-draft', '商务', 'outputs/data-intake-pack-latest/history-data-template.csv', '客户原始Brief', item.customerBrief, item.sourcePath, '是', item.missingForProof.join(';'), '不是业务证明'],
- ['history-draft', '商务', 'outputs/data-intake-pack-latest/history-data-template.csv', '历史人工补号量基线', '补真实历史人工补号量基线', item.sourcePath, '否', '历史人工补号量基线', '不得用 0 或示例值代替'],
- ['history-draft', '商务', 'outputs/data-intake-pack-latest/history-data-template.csv', '客户选择/拒绝原因', '补客户最终选中/拒绝和拒绝原因', item.sourcePath, '否', '客户最终选择和拒绝原因', '不得用主观判断代替客户反馈']
- ]),
- ...summary.reviewDraftRows.map(item => [
- 'manual-review-draft',
- '商务',
- 'outputs/data-intake-pack-latest/manual-review-template.csv',
- item.creatorName,
- '补人工复核标签、客户选择、归因类型、反馈原因和本轮人工补号量',
- item.source,
- '部分字段可转写',
- '客户选择、归因、反馈原因、本轮人工补号量',
- '候选草表不是客户效果证明'
- ]),
- ...summary.videoWorklistRows.map(item => [
- 'video-resource-worklist',
- item.role === '候选视频' ? '商务/投放' : '商务/投放',
- 'outputs/video-intake-pack-latest/video-resource-template.csv',
- `${item.role}:${item.creatorName}`,
- item.requiredFill,
- item.source,
- item.videoLink ? '部分字段可转写' : '否',
- '真实视频 URL、封面、ASR/字幕、帧图或正文证据',
- '补齐前不得标为真实候选视频'
- ]),
- ...summary.candidateSeedRows.map(item => [
- 'candidate-seed',
- '商务',
- 'outputs/data-intake-pack-latest/manual-review-template.csv',
- item.creatorName,
- `复核候选:${item.recommendationLevel},建议形式 ${item.suggestedFormat || '缺失'}`,
- `${item.sourceWorkbook} / ${item.sourceSheet}`,
- '部分字段可转写',
- item.missingForProof.join(';'),
- '候选种子不是人工最终名单或客户选择'
- ])
- ];
- return rows.map(row => row.map(csvCell).join(',')).join('\n');
- }
- function renderHistoryDraftCsv(summary) {
- const rows = [
- ['brief编号', '项目名称', '类目', '客户原始Brief', '历史人工补号量基线', '参考账号或视频', '平台', '博主名称', '主页链接', '人工复核标签', '客户选择', '拒绝原因'],
- ...summary.historyDraftRows.map(item => [
- item.briefId,
- item.projectName,
- item.category,
- item.customerBrief,
- item.historicalManualSupplementBaseline,
- item.referenceAccountOrVideo,
- item.platform,
- item.manualFinalCreatorName,
- item.manualFinalProfileUrl,
- item.manualReviewLabel,
- item.customerDecision,
- item.rejectReason
- ])
- ];
- return rows.map(row => row.map(csvCell).join(',')).join('\n');
- }
- function renderReviewDraftCsv(summary) {
- const rows = [
- ['brief编号', '策略', '排名', '平台', '博主名称', '综合分', 'brief匹配分', '参考风格分', '主页证据分', '视觉质感分', '调性一致分', '证据加分', '证据风险扣分', '推荐理由', '风险提示', '主页链接', '人工复核标签', '客户选择', '归因类型', '反馈原因', '本轮人工补号量', '来源', '证明边界'],
- ...summary.reviewDraftRows.map(item => [
- item.briefId,
- item.strategy,
- item.rank,
- item.platform,
- item.creatorName,
- item.totalScore,
- item.briefMatchScore,
- item.referenceStyleScore,
- item.homepageEvidenceScore,
- item.visualQualityScore,
- item.toneFitScore,
- item.evidenceBonus,
- item.evidenceRiskPenalty,
- item.reason,
- item.risk,
- item.profileUrl,
- item.manualReviewLabel,
- item.customerDecision,
- item.attributionType,
- item.feedbackReason,
- item.currentManualSupplementCount,
- item.source,
- item.proofBoundary
- ])
- ];
- return rows.map(row => row.map(csvCell).join(',')).join('\n');
- }
- function renderVideoWorklistCsv(summary) {
- const rows = [
- ['序号', 'brief编号', '项目名称', '平台', '资源角色', '博主名称', '主页或红书号', '视频链接', '当前状态', '需补字段', '来源', '是否可直接标真实资源'],
- ...summary.videoWorklistRows.map(item => [
- item.order,
- item.briefId,
- item.projectName,
- item.platform,
- item.role,
- item.creatorName,
- item.profileOrHomepage,
- item.videoLink,
- item.resourceStatus,
- item.requiredFill,
- item.source,
- item.canMarkRealResourceNow ? '是' : '否'
- ])
- ];
- return rows.map(row => row.map(csvCell).join(',')).join('\n');
- }
- function renderCandidateWorklistCsv(summary) {
- const rows = [
- ['序号', 'brief编号', '平台', '账号', '红书号', '推荐等级', '地区', '粉丝数', '图文报价', '视频报价', '建议形式', '建议报价', '阅读中位', '互动中位', '阅读单价', '来源', '仍缺证明'],
- ...summary.candidateSeedRows.map(item => [
- item.order,
- item.briefId,
- item.platform,
- item.creatorName,
- item.xhsId,
- item.recommendationLevel,
- item.region,
- item.fans,
- item.imageQuote,
- item.videoQuote,
- item.suggestedFormat,
- item.suggestedQuote,
- item.readMedian,
- item.interactionMedian,
- item.readUnitCost,
- `${item.sourceWorkbook} / ${item.sourceSheet}`,
- item.missingForProof.join(';')
- ])
- ];
- return rows.map(row => row.map(csvCell).join(',')).join('\n');
- }
- function clean(value) {
- return sanitizeCell(value == null ? '' : String(value));
- }
- function uniqueBy(rows, keyFn) {
- const seen = new Set();
- const result = [];
- for (const row of rows) {
- const key = keyFn(row);
- if (!key || seen.has(key)) continue;
- seen.add(key);
- result.push(row);
- }
- return result;
- }
- function parseArgs(argv) {
- const result = {};
- for (let index = 0; index < argv.length; index += 1) {
- const arg = argv[index];
- if (!arg.startsWith('--')) continue;
- const key = arg.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
- const next = argv[index + 1];
- if (!next || next.startsWith('--')) result[key] = true;
- else {
- result[key] = next;
- index += 1;
- }
- }
- return result;
- }
- function withBom(text) {
- return `\uFEFF${text}`;
- }
- function tableRow(values) {
- return `| ${values.map(value => escapeCell(value)).join(' | ')} |`;
- }
- function escapeCell(value) {
- return String(value ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, '<br>');
- }
- function csvCell(value) {
- return `"${String(value ?? '').replace(/"/g, '""')}"`;
- }
- if (require.main === module) main();
- module.exports = {
- buildLocalSeedToIntakeWorklist,
- extractReferences,
- extractCandidates,
- buildVideoWorklistRows
- };
|