fix(工作台): 视频拆解/视频分析字段映射修正闭环
- server.js 入库映射对齐 dsh 语义:content.json→source.content_json、analysis.json→source.analysis_json(视频拆解 tab)、*拆解分析.md→account_video_analysis(视频分析 tab) - 清理 6 条俊希错位 account_video_analysis + 白昼小熊 id13 归位 source47.analysis_json - 重跑入库验证:俊希 6 条 analysis_json 均写入、account_video_analysis 12 条=王微斯拆解分析(语义正确) - 王微斯视频三 tab 验证通过(视频脚本/视频拆解/视频分析各就其位) - 临时脚本已清理
This commit is contained in:
1 parent
b34929e7db
commit
818d00a28c
4 files changed
+26
-12
No files matched your search
@@ -50,3 +50,7 @@
|
|||||||
- **修复(治本)**:①export_video_map.py 增加 aweme_id 纯数字校验(`str(aweme_id).isdigit()`)过滤表头/非数据行;②data-pages.js fmtDuration 重写:兼容纯数字秒(648→10分48秒)与 mm:ss/hh:mm:ss 文本(01:31→1分31秒、13:48→13分48秒);③清理库内表头假记录(DELETE aweme_id='视频ID'/空/null,1条)
|
- **修复(治本)**:①export_video_map.py 增加 aweme_id 纯数字校验(`str(aweme_id).isdigit()`)过滤表头/非数据行;②data-pages.js fmtDuration 重写:兼容纯数字秒(648→10分48秒)与 mm:ss/hh:mm:ss 文本(01:31→1分31秒、13:48→13分48秒);③清理库内表头假记录(DELETE aweme_id='视频ID'/空/null,1条)
|
||||||
- **验证**:导出 136 条无非数字;重跑入库 xlsx_rows=136(表头不再计入)、videos_upserted=0;库 total=136/表头0/无时长0;/api/dsh/account-videos 返回 total=136、duration='13:48' 等 mm:ss → fmtDuration 正确显示
|
- **验证**:导出 136 条无非数字;重跑入库 xlsx_rows=136(表头不再计入)、videos_upserted=0;库 total=136/表头0/无时长0;/api/dsh/account-videos 返回 total=136、duration='13:48' 等 mm:ss → fmtDuration 正确显示
|
||||||
- 待提交 git;页面待重开验证
|
- 待提交 git;页面待重开验证
|
||||||
|
|
||||||
|
## 09-01 10:20 账号详情视频列表 UI 微调(data-pages.js)
|
||||||
|
- ①操作列按钮「AI改写」→「AI写作」;②点赞列 `fmt`→`fmtWan`(过万转万:81934→8.2万;like_display 为 null、like_count 纯数字,fmtWan 直接可用)。语法检查通过,页面已重开
|
||||||
|
- **10:22 修复 fmtWan 回归**:like_display 并非恒 null——**大部分视频有抖音原生「x.x万」文本**(如 7.8万/14万),`fmtWan('7.8万')`=Number→NaN→'-' 导致点赞列大面积变 '-'。修复:fmtWan 先判断含「万」则**直接透传**。全量验证 100/100 正常显示、0 条 '-'。教训:数据字段格式要全量核查,勿以抽查样本下结论
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
- **信息链路缺口识别**:Step 有方法论≠Step 能承接账号级信息;通用方法论管"怎么做",账号级信息管"怎么做才像它",不可互替
|
- **信息链路缺口识别**:Step 有方法论≠Step 能承接账号级信息;通用方法论管"怎么做",账号级信息管"怎么做才像它",不可互替
|
||||||
|
|
||||||
## 工作方式约定
|
## 工作方式约定
|
||||||
|
- **治本原则(09-01 用户明确)**:如无明确要求,解决问题一律从根因入手——定位根因→修复机制→**重新执行验证效果**;禁止补数据/临时绕过(补数据仅限用户明确要求时)
|
||||||
- **改完必重开页面(08-28)**:MCN工作台等网页改动完成后,最后一步必须用 present_files 重新打开对应页面(带 hash 定位);内置浏览器无 CDP 调试端口无法模拟 F5,重开页面是唯一刷新方式
|
- **改完必重开页面(08-28)**:MCN工作台等网页改动完成后,最后一步必须用 present_files 重新打开对应页面(带 hash 定位);内置浏览器无 CDP 调试端口无法模拟 F5,重开页面是唯一刷新方式
|
||||||
- **逐条修复**:审计修复必须读一条→改一条→验证一条→再进下一条,禁止批量读取
|
- **逐条修复**:审计修复必须读一条→改一条→验证一条→再进下一条,禁止批量读取
|
||||||
- **技能版本标签(08-26)**:frontmatter 存 `updated_at`+`last_change`+`deployment`;源=`source`+`created_at`+`updated_at`,副本=`dsh`+`updated_at`+`deployed_at`;副本 deployed_at<源updated_at=滞后
|
- **技能版本标签(08-26)**:frontmatter 存 `updated_at`+`last_change`+`deployment`;源=`source`+`created_at`+`updated_at`,副本=`dsh`+`updated_at`+`deployed_at`;副本 deployed_at<源updated_at=滞后
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fmt = (v) => (v == null || v === '') ? '-' : String(v);
|
const fmt = (v) => (v == null || v === '') ? '-' : String(v);
|
||||||
/* 过万数字以"万"为单位,保留一位小数(如 57141 → 5.7万) */
|
/* 过万数字以"万"为单位,保留一位小数(如 57141 → 5.7万);已是"x.x万"文本(like_display 抖音原生格式)直接透传(09-01 修复:旧实现 Number('7.8万')=NaN→'-') */
|
||||||
const fmtWan = (v) => {
|
const fmtWan = (v) => {
|
||||||
const n = Number(v);
|
if (v == null || v === '') return '-';
|
||||||
|
const s = String(v).trim();
|
||||||
|
if (s.includes('万')) return s;
|
||||||
|
const n = Number(s);
|
||||||
if (!Number.isFinite(n) || n <= 0) return '-';
|
if (!Number.isFinite(n) || n <= 0) return '-';
|
||||||
return n >= 10000 ? (n / 10000).toFixed(1) + '万' : String(n);
|
return n >= 10000 ? (n / 10000).toFixed(1) + '万' : String(n);
|
||||||
};
|
};
|
||||||
@@ -295,11 +298,11 @@ async function renderAccountDetail(view, id) {
|
|||||||
<td>${fmt(v.publish_time)}</td>
|
<td>${fmt(v.publish_time)}</td>
|
||||||
<td class="td-id"><a class="link" href="#/video/${v.id}" title="查看视频分析详情">${esc(v.aweme_id || '-')}</a></td>
|
<td class="td-id"><a class="link" href="#/video/${v.id}" title="查看视频分析详情">${esc(v.aweme_id || '-')}</a></td>
|
||||||
<td class="cell-content">${v.video_url ? `<a class="title-plain" data-ext="${esc(v.video_url)}" title="在新标签打开视频">${esc(cleanTitle(v.video_title) || '-')}</a>` : esc(cleanTitle(v.video_title) || '-')}</td>
|
<td class="cell-content">${v.video_url ? `<a class="title-plain" data-ext="${esc(v.video_url)}" title="在新标签打开视频">${esc(cleanTitle(v.video_title) || '-')}</a>` : esc(cleanTitle(v.video_title) || '-')}</td>
|
||||||
<td>${fmt(v.like_display || v.like_count)}</td>
|
<td>${fmtWan(v.like_display || v.like_count)}</td>
|
||||||
<td>${fmtWan(v.comment_count)}</td>
|
<td>${fmtWan(v.comment_count)}</td>
|
||||||
<td>${fmtWan(v.share_count)}</td>
|
<td>${fmtWan(v.share_count)}</td>
|
||||||
<td>${fmtDuration(v.duration)}</td>
|
<td>${fmtDuration(v.duration)}</td>
|
||||||
<td class="cell-act"><button class="btn btn-sm ${(v.has_source || v.has_analysis) ? 'btn-view' : ''}" data-rewrite='${esc(JSON.stringify({ name: acc.account_name, title: cleanTitle(v.video_title), aweme: v.aweme_id, parsed: !!(v.has_source || v.has_analysis) }))}'>AI改写</button></td>
|
<td class="cell-act"><button class="btn btn-sm ${(v.has_source || v.has_analysis) ? 'btn-view' : ''}" data-rewrite='${esc(JSON.stringify({ name: acc.account_name, title: cleanTitle(v.video_title), aweme: v.aweme_id, parsed: !!(v.has_source || v.has_analysis) }))}'>AI写作</button></td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
const tabBtns = [['info', '账号信息'], ['persona', '账号设定'], ['analysis', '数据分析']].map(([k, label]) => `<button class="tab ${tab === k ? 'tab-active' : ''}" data-tab="${k}">${label}</button>`).join('');
|
const tabBtns = [['info', '账号信息'], ['persona', '账号设定'], ['analysis', '数据分析']].map(([k, label]) => `<button class="tab ${tab === k ? 'tab-active' : ''}" data-tab="${k}">${label}</button>`).join('');
|
||||||
let panel = '';
|
let panel = '';
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ function importAccountIntoDb(db, account, dir) {
|
|||||||
const stats = { video_analysis: 0, video_source: 0, persona: 0, account_analysis: 0, videos_upserted: 0, skipped: [] };
|
const stats = { video_analysis: 0, video_source: 0, persona: 0, account_analysis: 0, videos_upserted: 0, skipped: [] };
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const insAnalysis = db.prepare(`INSERT INTO account_video_analysis (video_id, aweme_id, analysis_time, content_json, summary) VALUES (?,?,?,?,?)`);
|
const insAnalysis = db.prepare(`INSERT INTO account_video_analysis (video_id, aweme_id, analysis_time, content_json, summary) VALUES (?,?,?,?,?)`);
|
||||||
const insSource = db.prepare(`INSERT INTO account_video_source (video_id, aweme_id, analysis_time, content_json, summary) VALUES (?,?,?,?,?)`);
|
const insSource = db.prepare(`INSERT INTO account_video_source (video_id, aweme_id, analysis_time, content_json, summary, analysis_json) VALUES (?,?,?,?,?,?)`);
|
||||||
const insPersona = db.prepare(`INSERT INTO account_persona (account_id, analysis_time, content_json, summary) VALUES (?,?,?,?)`);
|
const insPersona = db.prepare(`INSERT INTO account_persona (account_id, analysis_time, content_json, summary) VALUES (?,?,?,?)`);
|
||||||
const insAccAna = db.prepare(`INSERT INTO account_analysis (account_id, analysis_time, content_json, summary) VALUES (?,?,?,?)`);
|
const insAccAna = db.prepare(`INSERT INTO account_analysis (account_id, analysis_time, content_json, summary) VALUES (?,?,?,?)`);
|
||||||
const acc = db.prepare('SELECT id FROM hot_accounts WHERE account_name=? LIMIT 1').get(account);
|
const acc = db.prepare('SELECT id FROM hot_accounts WHERE account_name=? LIMIT 1').get(account);
|
||||||
@@ -123,6 +123,9 @@ function importAccountIntoDb(db, account, dir) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 1) 视频分析目录(功能三 Step5 产出;功能五产出在 视频对标/ 目录,由 dsh 导入机制处理)
|
// 1) 视频分析目录(功能三 Step5 产出;功能五产出在 视频对标/ 目录,由 dsh 导入机制处理)
|
||||||
|
// 字段映射(09-01 修正,对齐 dsh 语义):content.json→account_video_source.content_json(视频脚本);
|
||||||
|
// analysis.json→account_video_source.analysis_json(视频拆解,MCP 结构化分析);*拆解分析.md→account_video_analysis(视频分析,功能四产物)。
|
||||||
|
// 原实现把 analysis.json 误写 account_video_analysis 导致「拆解内容进了视频分析」+「视频拆解 tab 空」
|
||||||
const vaDir = path.join(dir, '视频分析');
|
const vaDir = path.join(dir, '视频分析');
|
||||||
if (fs.existsSync(vaDir)) {
|
if (fs.existsSync(vaDir)) {
|
||||||
for (const folder of fs.readdirSync(vaDir)) {
|
for (const folder of fs.readdirSync(vaDir)) {
|
||||||
@@ -131,19 +134,22 @@ function importAccountIntoDb(db, account, dir) {
|
|||||||
if (!st.isDirectory()) continue;
|
if (!st.isDirectory()) continue;
|
||||||
const awemeId = matchAweme(folder);
|
const awemeId = matchAweme(folder);
|
||||||
const anPath = path.join(fdir, 'analysis.json');
|
const anPath = path.join(fdir, 'analysis.json');
|
||||||
if (fs.existsSync(anPath)) {
|
|
||||||
const content = fs.readFileSync(anPath, 'utf8');
|
|
||||||
if (awemeId) db.prepare('DELETE FROM account_video_analysis WHERE aweme_id=?').run(awemeId);
|
|
||||||
insAnalysis.run(null, awemeId, now, content, content.slice(0, 500));
|
|
||||||
stats.video_analysis++;
|
|
||||||
}
|
|
||||||
const coPath = path.join(fdir, 'content.json');
|
const coPath = path.join(fdir, 'content.json');
|
||||||
if (fs.existsSync(coPath)) {
|
if (fs.existsSync(coPath)) {
|
||||||
const content = fs.readFileSync(coPath, 'utf8');
|
const content = fs.readFileSync(coPath, 'utf8');
|
||||||
|
const analysisJson = fs.existsSync(anPath) ? fs.readFileSync(anPath, 'utf8') : null;
|
||||||
if (awemeId) db.prepare('DELETE FROM account_video_source WHERE aweme_id=?').run(awemeId);
|
if (awemeId) db.prepare('DELETE FROM account_video_source WHERE aweme_id=?').run(awemeId);
|
||||||
insSource.run(null, awemeId, now, content, '');
|
insSource.run(null, awemeId, now, content, '', analysisJson);
|
||||||
stats.video_source++;
|
stats.video_source++;
|
||||||
}
|
}
|
||||||
|
// 拆解分析.md → account_video_analysis(功能四逐条拆解产物,与解析并存于同一文件夹;无则不入)
|
||||||
|
const decon = fs.readdirSync(fdir).find((f) => f.endsWith('拆解分析.md'));
|
||||||
|
if (decon) {
|
||||||
|
const content = fs.readFileSync(path.join(fdir, decon), 'utf8');
|
||||||
|
if (awemeId) db.prepare('DELETE FROM account_video_analysis WHERE aweme_id=?').run(awemeId);
|
||||||
|
insAnalysis.run(null, awemeId, now, content, content.slice(0, 500));
|
||||||
|
stats.video_analysis++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2) 人设卡 + 账号分析(去重,不删旧)
|
// 2) 人设卡 + 账号分析(去重,不删旧)
|
||||||
|
|||||||
Reference in new issue
Block a user