Files
tps-dashboard/doco/docs/doco_pipeline_flowchart.html
T
simonkoson d91c20a126 看板升级: GT v0.5.0(20期叙事结构全覆盖) + schema加字段方案 + git清理
- ground-truth v0.5.0: 补完10期narrative_structure标注(MiMo跑批+制片人审定)
- summarize.py: 修复无标注期次格式化崩溃
- 003_add_ai_labels.sql: episodes表加7列(6打标+1置信度)迁移文件
- episode.py: Model同步加7个Optional字段
- CLAUDE.md: 项目主控文件入库
- doco/deliverables: 20期终版文稿md入库
- doco/programs: 中间产物从git移除(本地保留), .gitignore统一管理
- note/寄存条: 两个子项目寄存条入库
- .gitignore: 补充doco中间产物/Excel锁文件/临时文件规则

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 09:14:29 +08:00

587 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Doco 文稿整理流程图 — 《军事科技》</title>
<style>
:root {
--blue: #2563eb;
--blue-light: #dbeafe;
--green: #16a34a;
--green-light: #dcfce7;
--orange: #ea580c;
--orange-light: #ffedd5;
--purple: #9333ea;
--purple-light: #f3e8ff;
--red: #dc2626;
--red-light: #fee2e2;
--gray: #64748b;
--gray-light: #f1f5f9;
--bg: #fafbfc;
--card-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
background: var(--bg);
color: #1e293b;
line-height: 1.6;
padding: 40px 20px 80px;
}
.container { max-width: 960px; margin: 0 auto; }
h1 {
text-align: center;
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
color: #0f172a;
}
.subtitle {
text-align: center;
color: var(--gray);
font-size: 15px;
margin-bottom: 40px;
}
/* ---- Legend ---- */
.legend {
display: flex;
justify-content: center;
gap: 24px;
flex-wrap: wrap;
margin-bottom: 36px;
font-size: 13px;
}
.legend-item { display: flex; align-items: center; gap: 6px; }
.legend-dot {
width: 14px; height: 14px; border-radius: 4px;
}
/* ---- Pipeline column ---- */
.pipeline {
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
}
/* ---- Arrow between stages ---- */
.arrow-down {
width: 3px; height: 36px;
background: #cbd5e1;
position: relative;
}
.arrow-down::after {
content: '';
position: absolute;
bottom: -6px; left: 50%; transform: translateX(-50%);
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 8px solid #cbd5e1;
}
.arrow-label {
font-size: 11px;
color: var(--gray);
margin-top: 4px;
text-align: center;
}
/* ---- Stage card ---- */
.stage {
width: 100%;
max-width: 820px;
border-radius: 14px;
padding: 24px 28px;
box-shadow: var(--card-shadow);
position: relative;
border-left: 5px solid;
}
.stage-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.stage-badge {
font-size: 13px;
font-weight: 700;
padding: 3px 10px;
border-radius: 6px;
color: #fff;
white-space: nowrap;
}
.stage-title {
font-size: 18px;
font-weight: 600;
}
.stage-body { font-size: 14px; color: #475569; }
.stage-body p { margin-bottom: 8px; }
.stage-body p:last-child { margin-bottom: 0; }
.io-row {
display: flex;
gap: 16px;
margin-top: 14px;
flex-wrap: wrap;
}
.io-box {
flex: 1;
min-width: 200px;
border-radius: 8px;
padding: 10px 14px;
font-size: 13px;
}
.io-box strong { display: block; margin-bottom: 4px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
.io-in { background: #f0f9ff; border: 1px solid #bae6fd; }
.io-in strong { color: #0369a1; }
.io-out { background: #f0fdf4; border: 1px solid #bbf7d0; }
.io-out strong { color: #15803d; }
.io-tool { background: #fdf4ff; border: 1px solid #e9d5ff; }
.io-tool strong { color: #7e22ce; }
/* Color themes per stage type */
.stage-prep { border-color: var(--blue); background: var(--blue-light); }
.stage-prep .stage-badge { background: var(--blue); }
.stage-core { border-color: var(--green); background: var(--green-light); }
.stage-core .stage-badge { background: var(--green); }
.stage-fuse { border-color: var(--orange); background: var(--orange-light); }
.stage-fuse .stage-badge { background: var(--orange); }
.stage-output { border-color: var(--purple); background: var(--purple-light); }
.stage-output .stage-badge { background: var(--purple); }
.stage-check { border-color: var(--red); background: var(--red-light); }
.stage-check .stage-badge { background: var(--red); }
/* ---- Parallel fork ---- */
.parallel-wrapper {
width: 100%;
max-width: 820px;
position: relative;
}
.parallel-label {
text-align: center;
font-size: 12px;
color: var(--gray);
margin-bottom: 10px;
font-weight: 600;
letter-spacing: 1px;
}
.parallel-row {
display: flex;
gap: 20px;
}
.parallel-row > .parallel-branch {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
}
.parallel-row .stage {
width: 100%;
}
/* ---- Merge connector ---- */
.merge-connector {
width: 100%;
max-width: 820px;
height: 40px;
position: relative;
}
.merge-connector::before {
content: '';
position: absolute;
top: 0; left: 25%; right: 25%;
height: 20px;
border-left: 3px solid #cbd5e1;
border-right: 3px solid #cbd5e1;
border-bottom: 3px solid #cbd5e1;
border-radius: 0 0 12px 12px;
}
.merge-connector::after {
content: '';
position: absolute;
bottom: 2px; left: 50%; transform: translateX(-50%);
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 8px solid #cbd5e1;
}
/* Fork connector */
.fork-connector {
width: 100%;
max-width: 820px;
height: 36px;
position: relative;
}
.fork-connector::before {
content: '';
position: absolute;
bottom: 0; left: 25%; right: 25%;
height: 20px;
border-left: 3px solid #cbd5e1;
border-right: 3px solid #cbd5e1;
border-top: 3px solid #cbd5e1;
border-radius: 12px 12px 0 0;
}
.fork-connector-center {
position: absolute;
top: 0; left: 50%; transform: translateX(-50%);
width: 3px; height: 16px;
background: #cbd5e1;
}
/* ---- Key principle banner ---- */
.principle {
width: 100%;
max-width: 820px;
background: linear-gradient(135deg, #fef3c7, #fde68a);
border: 2px solid #f59e0b;
border-radius: 10px;
padding: 16px 20px;
font-size: 14px;
color: #92400e;
text-align: center;
font-weight: 500;
}
.principle strong { color: #b45309; }
/* ---- Input materials row ---- */
.inputs-row {
display: flex;
gap: 20px;
width: 100%;
max-width: 820px;
margin-bottom: 0;
}
.input-card {
flex: 1;
border-radius: 12px;
padding: 18px 20px;
box-shadow: var(--card-shadow);
text-align: center;
border: 2px dashed;
}
.input-card.card-a {
background: #eff6ff;
border-color: #93c5fd;
}
.input-card.card-v {
background: #fef3c7;
border-color: #fcd34d;
}
.input-icon { font-size: 32px; margin-bottom: 6px; }
.input-label { font-size: 16px; font-weight: 600; }
.input-desc { font-size: 12px; color: var(--gray); margin-top: 4px; }
/* ---- Final output ---- */
.final-output {
width: 100%;
max-width: 820px;
border-radius: 14px;
padding: 24px;
background: linear-gradient(135deg, #7c3aed, #4f46e5);
color: #fff;
text-align: center;
box-shadow: 0 4px 20px rgba(79,70,229,0.3);
}
.final-output h3 { font-size: 20px; margin-bottom: 8px; }
.final-output p { font-size: 14px; opacity: 0.9; }
.output-cards {
display: flex;
gap: 16px;
margin-top: 16px;
justify-content: center;
}
.output-card {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.3);
border-radius: 10px;
padding: 14px 20px;
min-width: 200px;
}
.output-card h4 { font-size: 15px; margin-bottom: 4px; }
.output-card p { font-size: 12px; opacity: 0.8; }
/* ---- Footer ---- */
.footer {
text-align: center;
margin-top: 48px;
font-size: 12px;
color: #94a3b8;
}
@media (max-width: 700px) {
.parallel-row { flex-direction: column; }
.inputs-row { flex-direction: column; }
.output-cards { flex-direction: column; align-items: center; }
.merge-connector::before { left: 10%; right: 10%; }
.fork-connector::before { left: 10%; right: 10%; }
}
</style>
</head>
<body>
<div class="container">
<h1>Doco 文稿整理流程</h1>
<p class="subtitle">《军事科技》栏目 &middot; 从原始素材到终版文稿的六步流水线</p>
<!-- Legend -->
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--blue)"></div> 准备阶段</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--green)"></div> 核心处理</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--orange)"></div> 交叉融合</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div> 最终出稿</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--red)"></div> 质量校验</div>
</div>
<!-- Principle -->
<div class="pipeline">
<div class="principle">
<strong>核心原则:</strong>AI 辅助而不替代 &mdash; 正文来自原始素材,<strong>一个汉字都不改</strong>;AI 只做结构判断和标点优化,最终由制片人审核。
</div>
<div class="arrow-down"></div>
<!-- ======== INPUTS ======== -->
<div class="inputs-row">
<div class="input-card card-a">
<div class="input-icon">&#128196;</div>
<div class="input-label">A稿(编导定稿)</div>
<div class="input-desc">Word 文档 &middot; 含【导视】【主持人】【解说】【专家】等分段标记</div>
</div>
<div class="input-card card-v">
<div class="input-icon">&#127909;</div>
<div class="input-label">播出视频文件</div>
<div class="input-desc">MP4 成片 &middot; 含屏幕字幕 + 完整音轨</div>
</div>
</div>
<!-- Fork -->
<div class="fork-connector">
<div class="fork-connector-center"></div>
</div>
<!-- ======== PARALLEL: P1+P2 / C1 ======== -->
<div class="parallel-wrapper">
<div class="parallel-label">&#9660; 以下三条路可以同时进行 &#9660;</div>
<div class="parallel-row">
<!-- Left branch: P1 + P2 -->
<div class="parallel-branch">
<div class="stage stage-prep">
<div class="stage-header">
<span class="stage-badge">P1 抽帧</span>
<span class="stage-title">视频截图 + 音频分离</span>
</div>
<div class="stage-body">
<p>把视频<strong>每秒截一张图</strong>(约 1600 张),同时把<strong>音轨单独分离</strong>出来(16kHz 高清音频文件)。</p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>播出视频 MP4</div>
<div class="io-box io-out"><strong>产出</strong>截图文件夹 + 音频 WAV</div>
<div class="io-box io-tool"><strong>工具</strong>ffmpeg(本地)</div>
</div>
</div>
</div>
<div class="arrow-down"></div>
<div class="stage stage-prep">
<div class="stage-header">
<span class="stage-badge">P2 读字幕</span>
<span class="stage-title">OCR 文字识别 → B稿</span>
</div>
<div class="stage-body">
<p>用本地 AI 模型<strong>逐帧识别屏幕上的字幕文字</strong>,再<strong>自动去除重复行</strong>(相邻帧字幕相同),得到约 700 行带时间戳的 B稿。</p>
<p>B稿 = 播出时屏幕上真实打出的字,<strong>是最贴近播出版的文本</strong></p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>~1600 张截图</div>
<div class="io-box io-out"><strong>产出</strong>B稿v2~700行 带时间戳)</div>
<div class="io-box io-tool"><strong>工具</strong>Ollama 本地 AI4路并发)</div>
</div>
</div>
</div>
</div>
<!-- Middle branch: C1 -->
<div class="parallel-branch">
<div class="stage stage-core">
<div class="stage-header">
<span class="stage-badge">C1 取词</span>
<span class="stage-title">从 A稿 提取专业术语</span>
</div>
<div class="stage-body">
<p>从编导定稿中<strong>自动提取武器型号、人名、机构名</strong>等专业术语。先用规则扫(数字+字母组合等),再让 AI 补充。</p>
<p>产出的<strong>热词表</strong>会喂给下一步语音识别,帮它认准专业词汇(比如"萨德"而不是"撒的")。</p>
<p>术语会<strong>累积到词典</strong>,做过的期数越多,识别越准。</p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>A稿 Word 文档</div>
<div class="io-box io-out"><strong>产出</strong>本期热词表(60-70 条)<br>+ 累积词典(已 235 条)</div>
</div>
</div>
</div>
<div style="height: 36px;"></div>
<div class="stage stage-core">
<div class="stage-header">
<span class="stage-badge">C2 听声</span>
<span class="stage-title">语音识别 → ASR 文本</span>
</div>
<div class="stage-body">
<p>把分离出的音频上传<strong>讯飞语音识别</strong>,加载热词表,把主持人/解说/专家说的话<strong>转成带时间戳的文字</strong></p>
<p>ASR 是<strong>口语实况</strong>,与 B稿(屏幕字幕)内容重叠但形态不同——字幕是精简书面语,语音是完整口语。</p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>音频 WAV + 热词表</div>
<div class="io-box io-out"><strong>产出</strong>ASR 文本(~300-400 句 带时间戳)</div>
<div class="io-box io-tool"><strong>工具</strong>讯飞录音转写</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Merge -->
<div class="merge-connector"></div>
<!-- ======== C3 FUSE ======== -->
<div class="stage stage-fuse">
<div class="stage-header">
<span class="stage-badge">C3 交叉融合</span>
<span class="stage-title">B稿 + ASR 逐行比对验证 → 融合B稿</span>
</div>
<div class="stage-body">
<p>把 B稿(字幕文字)和 ASR(语音文字)<strong>逐行交叉比对</strong></p>
<p>&bull; <strong>B稿为主</strong>(屏幕实打字幕,与播出最一致)<br>
&bull; ASR 作为参照——如果字幕 OCR 有漏字/错字,ASR 能帮忙发现<br>
&bull; <strong>专业术语以 B稿/A稿为准</strong>,语音识别的同音异写不采纳(如"斯泰尔"不能被改成"斯太尔"</p>
<p>产出<strong>融合B稿</strong>:碎句+密集时间戳格式,行数与原 B稿完全一致。同时产出 CSV 留痕记录,标注哪些行做了修改。</p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>B稿v2 + ASR 文本</div>
<div class="io-box io-out"><strong>产出</strong>融合B稿(碎句版,给爱德华)<br>+ fusion_review.csv(留痕)</div>
</div>
</div>
</div>
<div class="arrow-down"></div>
<!-- ======== SKELETON (between C3 and C4) ======== -->
<div class="stage stage-core" style="border-color: #0891b2; background: #ecfeff;">
<div class="stage-header">
<span class="stage-badge" style="background: #0891b2;">骨架分析</span>
<span class="stage-title">A稿段落结构识别</span>
</div>
<div class="stage-body">
<p>让 AI 读 A稿的 Word 格式,<strong>识别出每一段是什么角色</strong>(导视 / 主持人1 / 解说2 / 专家3 / 隔断 等),但<strong>不碰正文内容</strong>,只输出结构骨架。</p>
<p>骨架生成后由<strong>制片人肉眼核验</strong>(确认段落归属无误、没有真人姓名泄露),核验通过才用于下一步。</p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>A稿 Word 文档</div>
<div class="io-box io-out"><strong>产出</strong>骨架 JSON(段落编号 + 角色标签)<br>+ 人类预览表</div>
</div>
</div>
</div>
<div class="arrow-down"></div>
<!-- ======== C4 COMPOSE ======== -->
<div class="stage stage-output">
<div class="stage-header">
<span class="stage-badge">C4 出稿</span>
<span class="stage-title">段落归拢 + 加标点 → 终版文稿</span>
</div>
<div class="stage-body">
<p><strong>第一步:段落归拢</strong> — 把融合B稿的 700 行碎句,按照 A稿的骨架结构,<strong>归到对应段落</strong>里(哪些行属于【解说1】、哪些属于【主持人2】)。AI 做语义对齐判断,但文字 100% 来自融合B稿。</p>
<p><strong>第二步:加标点</strong> — 碎句拼起来后可能全是逗号。让 AI <strong>按语义断句</strong>,在合适的位置加句号、问号等,让读起来更通顺。</p>
<p><strong>第三步:套格式</strong> — 按【导视】【主持人N】【解说N】【专家N】【隔断】格式,生成公文样式的 Word 文档。</p>
<div class="io-row">
<div class="io-box io-in"><strong>输入</strong>融合B稿 + A稿骨架</div>
<div class="io-box io-out"><strong>产出</strong>融合A稿.docx(公文格式 Word<br>+ 对齐表 CSV</div>
</div>
</div>
</div>
<div class="arrow-down"></div>
<!-- ======== Quality check ======== -->
<div class="stage stage-check">
<div class="stage-header">
<span class="stage-badge">质检红线</span>
<span class="stage-title">机器 + 人工双重校验</span>
</div>
<div class="stage-body">
<p><strong>机器守门员:</strong>每一段产出后,自动检查"去掉标点后的文字"是否和输入完全一致。不一致就<strong>自动重试</strong>,3 次都不过就回退到原始文本,标记"需人工"。<strong>绝不允许 AI 偷偷改字。</strong></p>
<p><strong>人工核验:</strong>制片人审核融合A稿 Word,重点看:段落归属对不对、有无空段(播出可能删了某段戏,属正常)、专业术语有无被同音字替换。</p>
</div>
</div>
<div class="arrow-down"></div>
<!-- ======== FINAL OUTPUT ======== -->
<div class="final-output">
<h3>最终交付</h3>
<p>经制片人审核通过后,一期节目产出两份文稿:</p>
<div class="output-cards">
<div class="output-card">
<h4>&#128209; 融合A稿 (.docx)</h4>
<p>给编导 &mdash; 公文格式 Word<br>按【导视/主持人/解说/专家/隔断】分段<br>段落结构与编导定稿一致</p>
</div>
<div class="output-card">
<h4>&#128221; 融合B稿 (.txt)</h4>
<p>给爱德华 &mdash; 碎句+时间戳<br>格式:[XmYs] 文本<br>约 700 行,与字幕一一对应</p>
</div>
</div>
</div>
</div><!-- /pipeline -->
<!-- ======== Summary section ======== -->
<div style="max-width:820px; margin: 40px auto 0;">
<h2 style="font-size:20px; margin-bottom:16px; text-align:center;">一句话概括每一步</h2>
<table style="width:100%; border-collapse:collapse; font-size:14px;">
<tr style="background:#f8fafc;">
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600; width:100px;">P1 抽帧</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">把视频拆成截图 + 音频</td>
</tr>
<tr>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600;">P2 读字幕</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">让 AI 认截图里的字 → B稿(屏幕字幕版)</td>
</tr>
<tr style="background:#f8fafc;">
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600;">C1 取词</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">从 A稿抓专业术语,给语音识别"提词"</td>
</tr>
<tr>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600;">C2 听声</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">讯飞把音频变成文字 → ASR(口语版)</td>
</tr>
<tr style="background:#f8fafc;">
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600;">C3 融合</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">字幕 + 语音交叉验证,互相纠错 → 融合B稿</td>
</tr>
<tr>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600;">骨架</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">AI 读 A稿格式,标出每段是谁的词(不碰内容)</td>
</tr>
<tr style="background:#f8fafc;">
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0; font-weight:600;">C4 出稿</td>
<td style="padding:10px 14px; border-bottom:1px solid #e2e8f0;">碎句归段 + 加标点 + 套公文格式 → 终版 Word</td>
</tr>
<tr>
<td style="padding:10px 14px; font-weight:600;">质检</td>
<td style="padding:10px 14px;">机器逐字比对 + 制片人肉眼审核</td>
</tr>
</table>
</div>
<div class="footer">
Doco 文稿整理模块 &middot; TPS 工作台 &middot; 《军事科技》栏目 &middot; 2026
</div>
</div>
</body>
</html>