From 89d59f6f03e1f718d75d9342c5dabe3903d959b5 Mon Sep 17 00:00:00 2001 From: simonkoson <28867558@qq.com> Date: Fri, 12 Jun 2026 17:13:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E6=88=90=20doco=20src=20layout?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84,=E6=96=87=E4=BB=B6=E7=A7=BB=E8=87=B3=20sr?= =?UTF-8?q?c/doco/=20=E5=AD=90=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doco/src/{ => doco}/__init__.py | 0 doco/src/{ => doco}/asr_adapter.py | 0 doco/src/{ => doco}/cli.py | 0 doco/src/{ => doco}/video_split.py | 0 doco/tests/fixtures/mini_test.mp4 | Bin 0 -> 11454 bytes docs/Claude协作原则.md | 11 + ...og_md_TPS_工作台待办池_不丢视野_非排期_.md | 4 + .../分钟收视回放_主project答复_v1.md | 364 ++++++++++++++ .../分钟收视回放/分钟收视回放_立项备忘_v1.md | 119 +++++ .../main/寄存条_看板升级已外迁.md | 65 +++ .../看板升级分析/main2slave/INDEX.md | 50 ++ .../main2slave/看板升级子项目_Brief.md | 143 ++++++ .../main2slave/迁移清单_主project操作指南.md | 95 ++++ .../slave/F3_横轴量化方法_PRD_v1.0.md | 151 ++++++ .../slave/F3_横轴量化方法_PRD_v1.0草稿.md | 151 ++++++ docs/cross_project/跨project协作提示单_v1.md | 224 +++++++++ docs/doco/Doco子项目_Brief.md | 144 ++++++ docs/doco/PRD_doco_文稿整理子项目_v2.md | 295 ++++++++++++ docs/doco/doco_handoff_to_opus_chat.md | 103 ++++ docs/doco/doco_project_design.md | 451 ++++++++++++++++++ docs/doco/doco_xfyun_integration_notes.md | 181 +++++++ .../golden test/现代防空反导大对决 定稿.docx | Bin 0 -> 32677 bytes .../golden test/现代防空反导大对决_终版.docx | Bin 0 -> 46389 bytes docs/doco/xfyun_asr_standard.py | 345 ++++++++++++++ docs/doco/主project对Doco_PRDv2的回复.md | 214 +++++++++ docs/doco/实测复盘.md | 151 ++++++ docs/跨子项目协作规则.md | 38 ++ ...设计讨论.md => 续接快照_看板分析升级_设计讨论_v1.md} | 0 note/续接快照_看板分析升级_设计讨论_v4.md | 377 +++++++++++++++ 29 files changed, 3676 insertions(+) rename doco/src/{ => doco}/__init__.py (100%) rename doco/src/{ => doco}/asr_adapter.py (100%) rename doco/src/{ => doco}/cli.py (100%) rename doco/src/{ => doco}/video_split.py (100%) create mode 100644 doco/tests/fixtures/mini_test.mp4 create mode 100644 docs/Claude协作原则.md create mode 100644 docs/cross_project/分钟收视回放/分钟收视回放_主project答复_v1.md create mode 100644 docs/cross_project/分钟收视回放/分钟收视回放_立项备忘_v1.md create mode 100644 docs/cross_project/看板升级分析/main/寄存条_看板升级已外迁.md create mode 100644 docs/cross_project/看板升级分析/main2slave/INDEX.md create mode 100644 docs/cross_project/看板升级分析/main2slave/看板升级子项目_Brief.md create mode 100644 docs/cross_project/看板升级分析/main2slave/迁移清单_主project操作指南.md create mode 100644 docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0.md create mode 100644 docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0草稿.md create mode 100644 docs/cross_project/跨project协作提示单_v1.md create mode 100644 docs/doco/Doco子项目_Brief.md create mode 100644 docs/doco/PRD_doco_文稿整理子项目_v2.md create mode 100644 docs/doco/doco_handoff_to_opus_chat.md create mode 100644 docs/doco/doco_project_design.md create mode 100644 docs/doco/doco_xfyun_integration_notes.md create mode 100644 docs/doco/golden test/现代防空反导大对决 定稿.docx create mode 100644 docs/doco/golden test/现代防空反导大对决_终版.docx create mode 100644 docs/doco/xfyun_asr_standard.py create mode 100644 docs/doco/主project对Doco_PRDv2的回复.md create mode 100644 docs/doco/实测复盘.md create mode 100644 docs/跨子项目协作规则.md rename note/{续接快照_看板分析升级_设计讨论.md => 续接快照_看板分析升级_设计讨论_v1.md} (100%) create mode 100644 note/续接快照_看板分析升级_设计讨论_v4.md diff --git a/doco/src/__init__.py b/doco/src/doco/__init__.py similarity index 100% rename from doco/src/__init__.py rename to doco/src/doco/__init__.py diff --git a/doco/src/asr_adapter.py b/doco/src/doco/asr_adapter.py similarity index 100% rename from doco/src/asr_adapter.py rename to doco/src/doco/asr_adapter.py diff --git a/doco/src/cli.py b/doco/src/doco/cli.py similarity index 100% rename from doco/src/cli.py rename to doco/src/doco/cli.py diff --git a/doco/src/video_split.py b/doco/src/doco/video_split.py similarity index 100% rename from doco/src/video_split.py rename to doco/src/doco/video_split.py diff --git a/doco/tests/fixtures/mini_test.mp4 b/doco/tests/fixtures/mini_test.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1f0b69eb199f7e34fc4b8ee6750ade49cac67048 GIT binary patch literal 11454 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

5I)ZW0{7k&MAI@)lW?7t7ys2i+ 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 版本:v1 +> 日期:2026-06-11 +> 作者:TPS 主 project 顾问 (Claude Opus 4.7) + 制片人 刘通 +> 状态:已采纳,待秒级 chat 接收并按本文件推进实现 +> 上一版:无 +> 对应文档:秒级收视 project《军事科技_分钟收视回放_子板块需求规格.md》v1(2026-06-10) + +--- + +## 一、本文件用途 + +回复秒级 chat 那份 PRD 第八节的 6 个对接问题,并把主 project 这边的**红线、修订建议、交付物期待**全部白纸黑字写下来。本文件是子板块继续推进的**最终输入**,秒级 chat 这边可基于此开干。 + +子板块结束前,要按本文件第九节的清单产出**五份交付物**给主 project。主 project 这边的 Cline 拿着五份交付物落代码,完成最终整合。 + +--- + +## 二、对 6 个对接问题的最终答复 + +### 1. TPS 工作台有没有"每期节目详情页"? + +**没有。子板块需要附带把详情页骨架建起来。** + +现状:仪表盘 (Dashboard) 是聚合视角,责编录入页 (EditorDesk) 是表单 + 列表,**没有"看单期 + 该期所有维度"的页面**。分钟收视回放需要这个容器,整个工作台早晚也需要这个页面,借这次一起做掉。 + +入口规划: +- 仪表盘柱图每根柱子点进去 → 详情页 +- EditorDesk 列表行点进去 → 详情页 +- 路由建议:`/episodes/:id`,`:id` 用 `episodes.id`(SERIAL 主键) + +详情页骨架要预留位置承载未来其他维度(审片意见、文稿、外拍信息等),分钟收视回放是其中**第一个模块**。 + +### 2. 数据库技术栈 + +**PostgreSQL 16 + pgvector(1536 维)**,ORM 是 **SQLModel(同步,非 async)**。 + +手写 SQL 走 `backend/sql/00X_*.sql`,不用 Alembic。 + +第五节那两张表的接入方式见第四节"数据模型修改建议"——简短版:**别新建"节目期次表",复用现有 `episodes` 表**;分钟数据表新建,命名 `episode_minute_ratings`,外键挂到 `episodes.id`。 + +### 3. 视频和截图存储方案 + +**最终决策:视频不入工作台。** + +- 视频本体由制片人在本地/百度网盘/极空间多渠道自存 +- 工作台只接收**截图**(27 张 jpg) +- 不再讨论服务端 ffmpeg、视频压缩、转码删源片等服务端方案 + +截图处理走**本地切图工具**(子板块产出,详见第五节): + +- 责编本地双击工具 → 拖入源片 → 自动切 27 张 480p 截图 + 命名好 +- 责编打开工作台 → 一键多文件上传截图(~5 MB 一次传完) + +截图存储:服务器文件系统 `/screenshots/{episode_id}/{分钟序号_两位数}.jpg`,数据库只存 URL。 + +### 4. 前端技术栈 + +**React 18 + Vite + Ant Design 5 + Zustand**,JavaScript(不用 TS),项目宪法锁定。 + +图表库选型: + +- **优先 @ant-design/charts**:与现有 AntD 5 视觉血缘最近,配色和圆角自然贴合 +- **次选 ECharts**:自定义 tooltip 模板灵活成熟 +- **已用过 Recharts**:仪表盘柱图已采用 + +**不要再引第三方图表库**。三选一即可,最终选哪个由 Cline 在 Plan 阶段定。 + +### 5. 后台异步任务怎么跑 + +**在"视频不入工作台 + 本地切图工具"决策下,服务端没有重型异步任务。** + +工作台后端只接收截图入库 + 跑数据校验(见第四节建议 E),都是毫秒级同步操作,**FastAPI BackgroundTasks 都用不上**。 + +如果未来某个新场景确实需要异步任务,再启用 FastAPI BackgroundTasks。**不引 Celery、不引 Redis 队列**——8 人栏目用这些是过度工程。 + +### 6. 录入身份/权限 + +**有完整用户系统**:Session + Cookie 认证,三角色拼音枚举锁定(`zhipianren` / `zebian` / `biandao`)。 + +写权限规则参照 `yearly_targets` 既有口径平移: + +- **POST/PATCH/DELETE 录入分钟数据 + 上传截图**:`zhipianren` + `zebian` 可写,`biandao` 返回 403 +- **GET 查看分钟回放**:三角色均可读 + +**实现细节**:FastAPI 后端直接从 session 拿 user_id 写入 `created_by` 字段,**前端不要把"录入人"作为字段提交**。 + +字段命名用 `created_by INTEGER REFERENCES users(id)`,**不要用 string 类型的"录入人姓名"**——主 project backlog 里有一条 "操作留痕 schema"(待办池 B2),分钟收视这张新表作为落地先例。 + +--- + +## 三、视频/截图最终方案(决策落地) + +### 责编一周一期的标准流程(最终版) + +``` +周二 21:20-21:47 节目播出 + ↓ +周二 22:00 后 责编到总编室账号抄 27 个份额数字(保留原方案) + ↓ +次日 责编打开本地切图工具 + ↓ + 拖入当期源片 mp4 → 工具自动切 27 张 480p jpg + + 自动命名为 01.jpg ~ 27.jpg + ↓ + 责编登录工作台 → 进入"节目期次"管理 + → 选择当期 episode(基于已录入的 episodes 记录) + → 进入分钟收视模块 + ↓ + 上传 27 张截图(多文件选择 / 拖拽) + → 服务器存到 /screenshots/{episode_id}/ + → 数据库写入 27 条 episode_minute_ratings 记录 + ↓ + 录入 27 个份额数字 + (推荐:"批量粘贴一列数字"输入框, + 从 Excel 复制 27 个数字粘贴后系统按行切分) + → 系统软校验(数据齐全?范围合理?均值与已录全期均值吻合?) + → 保存 + ↓ + 该期详情页里"分钟收视回放"模块即刻可看 +``` + +### 主 project 这边不做的事 + +- 服务端 ffmpeg(不需要) +- 视频上传接口(不需要) +- 视频压缩转码(不需要) +- 异步任务队列(不需要) +- 视频删除/清理脚本(不需要) + +--- + +## 四、数据模型修改建议(主 project 在此明确给出) + +> 任何 schema 改动**必须经主 project 顾问 (Opus) 审、配回补方案,Cline 不得自行动手**。这是项目红线。下面是建议方案,最终落地前还会过一遍审查。 + +### 建议 A:复用现有 `episodes` 表,**不新建"节目期次表"** + +现有 episodes 表已涵盖你需要的全部"节目期次"信息: + +| 你的字段 | 对应现有 episodes 字段 | +|---|---| +| 期次主键 | `id`(SERIAL) | +| 期数显示 | 前端从 `air_date` + `episode_number` 拼,不入库 | +| 节目名称 | `program_name` | +| 播出日期 | `air_date` | +| 播出时段 | 不入库,前端常量 "21:20-21:47" | +| 备注 | `notes` | + +理由:新建一张冗余表会割裂 episodes 与其他模块(收视成绩、外拍、未来选题、审片意见)的关联,破坏单一事实源。 + +### 建议 B:用 `episodes.id`(INTEGER)做外键,**不用"播出日期 string"做主键** + +秒级 chat v1 文档建议"期次主键用 `2026-06-09` 字符串"。这与 `episodes.id SERIAL` 体系冲突。 + +最终方案:分钟数据表用 `episode_id INTEGER REFERENCES episodes(id)` 外键,所有展示字符串("2026 年第 23 期 · 2026-06-09")由前端从 episodes 数据拼出,不入库。 + +### 建议 C:`收视份额` 用 `NUMERIC(6,4)`,**不用 float** + +与现有 `episodes.audience_share NUMERIC(6,4)` 类型对齐,避免浮点比较的怪事。 + +### 建议 D:新表 `episode_minute_ratings` 建议 schema + +```sql +CREATE TABLE IF NOT EXISTS episode_minute_ratings ( + id SERIAL PRIMARY KEY, + episode_id INTEGER NOT NULL REFERENCES episodes(id) ON DELETE CASCADE, + minute_index INTEGER NOT NULL CHECK (minute_index BETWEEN 1 AND 27), + audience_share NUMERIC(6,4) NOT NULL, + screenshot_url VARCHAR(500), + created_by INTEGER NOT NULL REFERENCES users(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(episode_id, minute_index) +); + +CREATE INDEX idx_minute_ratings_episode ON episode_minute_ratings (episode_id); +``` + +P1+ 扩展(多期对比、批注/标签):1.0 不建表,到 P1 触发时再说。当前 UNIQUE 复合键和外键设计已留好扩展空间。 + +### 建议 E:工作流补一个"软校验"环节 + +录入 27 个分钟份额时,系统应做**非阻塞**的合理性提示: + +- 27 个数字是否齐? +- 最大值/最小值是否在合理区间(如 0-3)? +- 27 个数字的平均值是否与已录的 `episodes.audience_share`(该期全期均值)大致吻合?误差 > 30% 给"数据可能录错"提示 + +不阻塞保存,只是温柔提醒。理由:分钟数据是栏目考核依据,权重最高,录错一个小数点不发现就一直错。 + +### 建议 F:批量导入接口复用现有 ExcelService + +23 期历史数据一次性导入:复用主 project 已有的 ExcelService 框架(Phase 2 Task 2 跑通),但单开一个 endpoint: + +- 上传一份"期次 × 27 列分钟份额"的 Excel +- 按行展开成 23 × 27 = 621 条 `episode_minute_ratings` 入库 +- 截图 URL 字段第一次导入时留空,后续走本地工具补传 + +### 建议 G:episodes 表是否要加视频 URL 字段? + +**不加。** + +因为视频不入工作台。如果未来某天想记录"这一期视频存在哪个网盘 / 哪个本地路径",可以走 `episodes.notes` 字段写自由文本,不需要专门字段。 + +--- + +## 五、本地切图工具规格(子板块产出物之一) + +子板块的 Claude 负责设计与实现这个工具。 + +### 5.1 形态 + +- 一个 Windows 上能跑的可执行文件(py 源码 + pyinstaller 打包成 exe) +- 双击即用,无安装步骤 +- 不依赖网络,不依赖工作台,完全离线 +- 不需要责编装 Python 环境 + +### 5.2 用户界面 + +简洁到极致: + +- 一个窗口,拖文件进去即可(或"选择文件"按钮) +- 接收 mp4 / mov / mkv 等常见格式 +- 进度条显示切图进度 +- 完成后弹"已生成 27 张截图于 [输出文件夹]" +- 自动打开输出文件夹 + +### 5.3 切图规则(与秒级 chat v1 文档保持一致) + +```bash +# 27 张截图,每分钟第 30 秒位置,480p (854 × 480) +for i in $(seq 0 26); do + sec=$((i*60+30)) # 0:30, 1:30, 2:30, ..., 26:30 + ffmpeg -i 源片.mp4 -ss $sec -vframes 1 -vf scale=854:480 \ + "$(printf "%02d" $((i+1))).jpg" -y +done +``` + +输出文件名规则:**`01.jpg` ~ `27.jpg`**(两位数,零填充,便于排序)。 + +### 5.4 输出文件夹组织 + +工具自动新建一个文件夹,命名建议:`截图_{源片文件名去后缀}_{日期}/`,里面 27 张 jpg。 + +### 5.5 ffmpeg 依赖 + +打包时把 ffmpeg.exe 一并塞进 exe,或者用 `imageio-ffmpeg` 包自动下载。**绝对不要让责编手动装 ffmpeg**。 + +### 5.6 错误处理 + +- 源片读不到 → 弹窗"文件无法读取,请检查格式" +- 源片时长 < 26 分 30 秒 → 弹窗"视频时长不足 27 分钟,是否继续?"(允许跳过缺失分钟) +- 输出文件夹已存在 → 自动加时间戳后缀 + +### 5.7 给责编的使用说明 + +子板块产出一份 `使用说明.md`,傻瓜级,要有截图。预期长度 200-400 字 + 3-5 张截图。 + +--- + +## 六、主 project 红线(不可越) + +子板块设计与实现过程中,下面这几条是**主 project 宪法**,任何方案不得违反: + +1. **改 schema 须主 project 顾问 (Opus) 审**:包括新建表、改字段、加字段。Cline / 子板块 Claude 不得自行动手。 +2. **角色枚举锁定拼音值**:`zhipianren` / `zebian` / `biandao`,不许新增枚举,不许用英文别名。 +3. **AI 给方向不给答案**:分钟收视回放的 P1+ 功能(多期对比找规律、批注 AI 建议等)若涉及 AI,必须保持"建议草稿 + 制片人审核"的三档置信度,不得 AI 直接拍板。 +4. **编导关联用"软引用 + 快照"**:本子板块如需展示编导信息,沿用 `episodes.editor_id` + `episodes.editor_name_snapshot` 双字段,不许新增编导关联字段。 +5. **收视颜色判定**:红=优秀(>摸高目标)/ 蓝=达标 / 绿=待提升(<基础目标)。反直觉规约,分钟收视回放折线图如果上色,按此规则。 +6. **AI 输出必须有"忽略 / 我来改写"否决权**:任何 AI 建议(如本子板块 P1+ 的 AI 找规律功能)必须给编导否决入口。 +7. **不引新依赖**:图表库三选一(@ant-design/charts / ECharts / Recharts),不引第四个;后端不引 Celery / Redis 队列。 + +--- + +## 七、子板块产出物清单(最终交付包) + +子板块结束前,请凝结成以下**五份交付物**,作为整体交付给主 project: + +### 产物 1:决策摘要 md + +记录子板块全程的关键决策。每条决策三句话: + +``` +决策 X:[一句话决策内容] + - 背景:为什么需要这个决策 + - 拍板理由:选这条不选那条的理由 + - 拍板人:[制片人 / 主 project 顾问 / 子 project Claude 建议+制片人采纳] +``` + +10-20 条决策足以讲清整个子板块"为什么这么做"。 + +### 产物 2:接口约定 md(最技术、最精确) + +把子板块与主 project 接驳的所有"接口"白纸黑字写出来: + +- **数据接口**:新建表 / 字段 / 类型 / 约束(细化到 SQL DDL 级别) +- **API 接口**:新增 endpoint URL / 方法 / 请求体 / 响应体 / 错误码 +- **UI 接口**:在主 project 哪个页面挂载、入口、路由 +- **权限接口**:哪些角色可以做什么操作 +- **存储接口**:截图存到哪个目录、命名规则、URL 拼接规则 + +要具体到"主 project 的 Cline 拿着这份就能写代码"的程度。**这是子 project 给主 project 的"合同"**。 + +### 产物 3:本地切图工具完整包 + +- `screenshot_tool.py`:源码,注释齐全 +- `screenshot_tool.exe`:打包好的可执行文件,责编双击即用 +- `使用说明.md`:傻瓜级文档 + 截图 + +工具规格见本文件第五节。 + +### 产物 4:未尽事项 backlog + +诚实写出"子板块讨论中提到但没做的事"。每条三行: + +- 是什么 +- 为什么没做(暂缓 / P1+ / 等条件成熟) +- 触发时机(什么时候应该捡起来) + +放进主 project 的 backlog 池子里。 + +### 产物 5:给主 project Claude 的"接手提示词" + +一两段话,告诉主 project 顾问"如果你是接手这件事的 Claude,最重要的三件事是 X、Y、Z"。 + +这次至少要包含: +- 复用 episodes 表,不新建节目期次表 +- 权限沿用 yearly_targets 口径 +- 本地切图工具与工作台零耦合,不在工作台 UI 里给它留接口 + +--- + +## 八、跨 project 协作规矩(子板块这边也要遵守) + +1. **不动主 project 代码**:子板块产出文档 + 独立工具,不写主 project 的实际代码。代码合入由主 project 的 Cline 完成。 +2. **遇到吃不准的事回主 project 问**:本次 v1 文档第八节就是好榜样,继续保持。 +3. **每份跨 project 文档顶部带元信息块**:版本、日期、作者、状态、上一版、主要变化(v2 以上必填)。 +4. **一份文档只谈一件事**:不要在分钟收视回放交付包里夹带其他议题。 + +--- + +## 九、给秒级 chat Claude 的明确动作清单 + +收到本文件后,请按下面顺序推进: + +1. **吸收本文件所有决策与红线**,回到秒级 chat 的 PRD v1,把不一致处标注出来(如果有) +2. **如有需要修订的地方,出 PRD v2**,顶部元信息块写明跟 v1 的变化点 +3. **设计本地切图工具**:与制片人讨论用户体验细节,定稿后凭工具规格让秒级 chat 的 Cline 实现(如果有)或制片人自己找 v0/Lovable 等工具生成 demo +4. **凝结成最终五份交付物**(第七节清单) +5. **交付给制片人**,由制片人带回主 project,主 project 顾问 (Opus) 审一遍,无冲突后主 project 的 Cline 落实施 + +--- + +## 十、附:版本演进规约 + +本文件未来如修订(v2+),顶部元信息块"主要变化"必须写清楚改了什么。 + +历史版本不覆盖,旧文件保留在 `docs/cross_project/` 目录(主 project 这边)以便回看决策史。 + +--- + +*本文件由主 project 顾问 (Claude Opus 4.7) 于 2026-06-11 基于与制片人刘通的两轮对话整理生成,供秒级收视项目接收使用。* diff --git a/docs/cross_project/分钟收视回放/分钟收视回放_立项备忘_v1.md b/docs/cross_project/分钟收视回放/分钟收视回放_立项备忘_v1.md new file mode 100644 index 0000000..3beb3c0 --- /dev/null +++ b/docs/cross_project/分钟收视回放/分钟收视回放_立项备忘_v1.md @@ -0,0 +1,119 @@ +# 子项目立项备忘 — 分钟收视回放 + +> 文档类型:子项目立项备忘 +> 子项目名称:分钟收视回放 +> 立项日期:2026-06-11 +> 子项目所在:秒级收视 project(Claude.ai 网页版的另一个 project) +> 当前状态:**推进中**(设计已收敛,进入子板块内部实现阶段) +> 主 project 联系人:制片人刘通 + Claude Opus 4.7 顾问 + +--- + +## 一、文档用途 + +未来主 project 这边新开 chat 时(必然会发生 Claude 失忆),新 Claude 通过此文件快速恢复"分钟收视回放子项目"的上下文。**它不重复 v1 答复 md 里的细节,只留指针 + 关键决策摘要**。 + +--- + +## 二、立项缘由 + +制片人在另一个 project(秒级收视)里讨论分钟收视数据怎么进工作台、怎么呈现。那边 Claude 写了一份 PRD(需求规格 v1, 2026-06-10),向主 project 这边提了 6 个对接问题。 + +主 project 这边讨论两轮后采纳"以子项目模式独立推进",并把答复凝结成正式 md 带回子项目。**这次是项目里第一次完整使用"子项目模式"**——既是分钟收视回放本身的立项,也是这套跨 project 协作工作流的首次实操。 + +--- + +## 三、关键决策摘要(已采纳,不要重复讨论) + +1. **视频不入工作台**:制片人多渠道自存(极空间/百度网盘/本地),工作台只接收截图 +2. **本地切图工具**:子板块产出,py + exe + 使用说明,与工作台零耦合 +3. **复用现有 episodes 表**:不新建"节目期次表",分钟数据表 `episode_minute_ratings` 外键挂到 `episodes.id` +4. **录入人字段用 `created_by INTEGER REFERENCES users(id)`**:不用 string,顺手落地 backlog B2 操作留痕 +5. **后端不上 Celery/Redis**:异步任务在视频不入库后基本消失,必要时用 FastAPI BackgroundTasks +6. **图表库三选一**:@ant-design/charts / ECharts / Recharts,不引第四个,最终由 Cline Plan 阶段定 +7. **详情页 `/episodes/:id` 骨架由本子板块附带建起来**:早晚要做,借这次一起 +8. **权限沿用 yearly_targets 口径**:zhipianren + zebian 可写,biandao 只读 + +--- + +## 四、子板块要交付什么(五份产物) + +子项目结束时,那边凝结以下交付包带回主 project: + +1. 决策摘要 md +2. 接口约定 md(含 SQL DDL、API endpoint、UI 接入点、权限规则、存储路径规则) +3. 本地切图工具完整包(py 源码 + exe + 使用说明) +4. 未尽事项 backlog +5. 给主 project Claude 顾问的"接手提示词" + +详见《分钟收视回放_主project答复_v1.md》第七节。 + +--- + +## 五、主 project 要做的事 + +**现在(推进期)**:不做事,不接收过程文档,等收交付包。 + +**收到交付包后**: + +1. 主 project 顾问 (Opus) 审一遍,看跟主项目红线有没有冲突 +2. 无冲突 → 主 project workspace 里让 Cline 落代码 +3. 有冲突 → 标出冲突点,回子项目修,循环 + +--- + +## 六、相关文档(指针) + +**外部输入**(子项目发来的): + +- 秒级 chat 的 PRD v1:《军事科技_分钟收视回放_子板块需求规格.md》(2026-06-10) + - 已在主 project files 中(制片人 2026-06-11 上传) + +**主 project 输出**(咱们这边给子项目的): + +- 《分钟收视回放_主project答复_v1.md》(2026-06-11) + - 建议归档位置:`docs/cross_project/分钟收视回放/分钟收视回放_主project答复_v1.md` + +**配套文档**: + +- 《跨project协作提示单_v1.md》(2026-06-11,制片人随身保存) + - 不在主 project 仓库内,是制片人个人工作流手册 + +--- + +## 七、预计回归时间 + +未明确锁定。子项目那边的 Claude 表示"没有什么不清楚的了",已在内部推进。 + +**触发主 project 这边介入的信号**: + +- 子项目带交付包过来(正常路径) +- 子项目过程中遇到吃不准的事 → 单点咨询主 project 顾问(不要整份过程稿上传) +- 超过 4 周还没收敛 → 主动 ping 一下,看是不是要重新切分 + +--- + +## 八、给未来失忆接手 Claude 的提示 + +如果你是几周后接手主 project 的 Claude,看到这份备忘,请记住: + +1. **分钟收视回放是一个独立推进的子项目**,主 project 不掺合其内部讨论 +2. **复用 episodes 表,不要新建节目期次表**——这是已经定死的决策,不重新讨论 +3. **改 schema 必须经 Opus 顾问审**,子项目交付包带 schema 修改建议时严守这条 +4. **本地切图工具与工作台零耦合**,工作台 UI 里不要给它留接口 +5. **视频不入工作台**——任何提议加视频上传接口的方案都已被否决 +6. **审查交付包是必经环节**,发现子项目设计违反主项目红线(如角色枚举、收视颜色判定)时直接打回 + +--- + +## 九、版本演进 + +本文件未来如需更新(v2+): + +- 子项目交付包接收后:更新"当前状态"为"已交付,整合中" +- Cline 实施完成后:更新"当前状态"为"已完成",加一节"实施回顾" +- 实施过程中如有重大决策变更:v2 顶部"主要变化"写明 + +--- + +*本立项备忘由主 project 顾问 (Claude Opus 4.7) 于 2026-06-11 制片人立项动作后随手生成。轻量、精简,未来失忆接手时由此恢复上下文。* diff --git a/docs/cross_project/看板升级分析/main/寄存条_看板升级已外迁.md b/docs/cross_project/看板升级分析/main/寄存条_看板升级已外迁.md new file mode 100644 index 0000000..0bfdc70 --- /dev/null +++ b/docs/cross_project/看板升级分析/main/寄存条_看板升级已外迁.md @@ -0,0 +1,65 @@ +# 寄存条:看板升级已外迁 + +> 留在主 project,让未来的 Claude 一眼知道:看板分析升级已经拉出去单独立项了。 +> 外迁时间:2026-06-12 + +--- + +## 外迁了什么 + +**TPS 看板分析升级**整体作为子项目,搬到独立 Claude project。 + +子项目聚焦:三层信息架构、双引擎象限图、AI 打标流水线、编导能力地图、本期诊断小结、看板 UI 设计。 + +子项目持有的核心资产:主项目签发的 Brief + 三份续接快照 + ground-truth 基准集 + 23 期文稿等。 + +--- + +## 主 project 不再讨论的话题 + +聊到下面这些词,**先问一句"这是子项目的事还是主 project 的事?"**——是子项目的事,提醒制片人切到子项目 chat。 + +关键词:看板升级 / 双引擎象限图 / 题材热度 / 叙事结构(作为字段)/ AI 打标 / Prompt 1/2/3 / ground-truth / episodes 加字段 / 23 期回填 / 能力地图 / 置信度三档 / 本期诊断小结 + +--- + +## 主 project 仍在管什么 + +- Phase 0-3 已落地的主干代码(episodes、editors、收视诊断基础) +- backend 全部 schema、API、迁移脚本 +- 前端全部 React 代码实施 +- 主干 Bug、性能优化、新需求(看板升级以外的) +- Cline 的全部 Plan + Act 操作 + +--- + +## 接收子项目交付物的接口 + +子项目交付物的具体接口约定,见**子项目主 Brief §六**(交付什么)。简要回顾: + +- **Schema 变更**:子项目出字段定义文档 → 主 project Cline 切 Plan + Opus 审 + 制片人批准 Act → `pg_dump` 备份 → 跑迁移 +- **前端实施**:子项目出设计 token + HTML mock + 视觉规范 → 主 project Cline 落 React +- **AI 打标**:沙盒 `ai-labeling/` 由子项目自治,主 project 不碰 +- **回填**:子项目出脚本设计 → 主 project 跑 + 制片人审 staging + +**纪律**: +- 只接稳定版,草稿不实施 +- 实施前 `pg_dump` 备份 +- 实施完成后回访子项目登记状态 + +--- + +## 开局口径 + +进主 project 新 chat: +> "续接 TPS 主项目。读 `寄存条_看板升级已外迁.md`。当前要做 [具体任务]。" + +--- + +## 万一子项目记录丢了 + +降级方案: +1. 主 project 这边还保留资产副本(xlsx + 11 份 docx + 部分 UI 参考图) +2. 决策史在 Gitea 有 git 历史(关键 commit `38f3728` 等) +3. 制片人本地应有副本 +4. **主 project 这边不要凭记忆重建子项目内容**——找制片人或重建子项目 diff --git a/docs/cross_project/看板升级分析/main2slave/INDEX.md b/docs/cross_project/看板升级分析/main2slave/INDEX.md new file mode 100644 index 0000000..c4011a8 --- /dev/null +++ b/docs/cross_project/看板升级分析/main2slave/INDEX.md @@ -0,0 +1,50 @@ +# 看板升级子项目 — 入口 + +> 这是 TPS 主 project 拆出来的子项目,聚焦"看板分析升级"。 +> 立项时间:2026-06-12 +> 顾问:Claude Opus 4.7 | 制片人:刘通 + +--- + +## 开局必读(顺序) + +1. `看板升级子项目_Brief.md` — 主项目签发的"交接宪法"(红线、技术栈、出入口接口)。**不在子项目内修改。** +2. `续接快照_看板分析升级_设计讨论_v4.md` — 最新决策史(往前可回溯 v3 / v1) +3. 上面两份读完,就有完整上下文了 + +--- + +## 已有资产 + +**决策史**: +- 续接快照 v1 / v3 / v4 — 整个看板升级讨论的完整记录 + +**数据基础**: +- `军事科技栏目收视诊断分析_v2.xlsx` — 双引擎模型源数据(主项目也留一份) +- 11 份节目文稿 `.docx` — AI 打标基准集源稿(主项目也留一份) + +**UI 参考**: +- `dashiboard首页原版.jpg` — 首屏改造起点(主项目也留一份) +- `dashiboard首页_拷贝.jpg` — 制片人批注稿 +- `色调字体倒角.webp` — 整体视觉资产(主项目也留一份) +- `简洁风_清晰.jpg` — DataEase 风格参考 + +**协作纪律**: +- `Claude协作原则.md` — 与主项目同源,沿用 + +**沙盒**: +- `ai-labeling/` 工作区(物理上仍在 `E:\tps-dashboard\ai-labeling\`) +- Prompt 2 v0.2 已锁,MiniMax M3 选定,ground-truth v0.2.1 锁定 + +--- + +## 子项目自由度 + +读完 Brief 你会注意到 §八"未决项"——那是子项目可自由发挥的部分。 +Brief 里没明确禁止、没在续接快照里拍板的事,都是讨论空间。 + +--- + +## 新 chat 开局第一句 + +> "续接 TPS 看板分析升级子项目。先读主 Brief 和续接快照 v4。本 project 是设计+PRD 产出,不动主 project 的 backend 代码。当前推进到 [具体任务]。" diff --git a/docs/cross_project/看板升级分析/main2slave/看板升级子项目_Brief.md b/docs/cross_project/看板升级分析/main2slave/看板升级子项目_Brief.md new file mode 100644 index 0000000..d972967 --- /dev/null +++ b/docs/cross_project/看板升级分析/main2slave/看板升级子项目_Brief.md @@ -0,0 +1,143 @@ +# 看板升级子项目 Brief + +> 主项目 → 子项目的"交接宪法":红线、技术栈、出入口接口 +> 起草日期:2026-06-12 +> 状态:子项目立项时由主项目顾问(Opus)签发,子项目内部不修改 + +--- + +## 一、为什么做 + +**痛点**:制片人和编导团队拿到的"收视成绩单"只能看到结果(均值、走势、单期高低), +看不到结构性原因。每次复盘要靠人工拼诊断(21 期那次花了若干周),无法持续。 + +**升级目标**:把仪表盘从"看成绩"升级为"能分析"—— +让"双引擎模型(题材热度 × 叙事结构)"这个一次性人工产出的分析框架, +变成一台**持续运转的诊断仪**。 + +--- + +## 二、做什么(功能边界,只列名) + +| 模块 | 简述 | +|---|---| +| 三层信息架构 | 纯算 / 打标后 / AI 起草,分层呈现 | +| 双引擎象限图 | 招牌视觉,题材热度 × 叙事结构 | +| AI 打标流水线 | Prompt 1/2/3 + MiniMax M3 + ground-truth | +| 编导能力地图 | 反排名,正反馈 | +| 本期诊断小结 | AI 起草 + 制片人审改后发布 | +| 时间选择器 | 实时脉搏 vs 周期复盘,边界永远可调 | + +**具体怎么做,子项目内部讨论,本 Brief 不预设方案。** + +--- + +## 三、怎么用(目标流程) + +- **每周**:制片人看"实时脉搏"——核心指标卡 + 近 9 期走势 +- **每月**:制片人看"题材分析"——题材汇总,关注异动 +- **每季**:制片人看"双引擎象限图 + 本期诊断小结"——周期复盘 +- **新节目入库**:AI 打标草稿 → staging 表 → 制片人审 → 落库 +- **编导维度**:制片人查能力地图,把强项做成全组可复用模板 +- **离职编导**:历史保留,面向当下视图可筛掉 + +--- + +## 四、不做什么(红线) + +子项目设计任何方案,**绝不**触碰以下: + +- ❌ **不做深色大屏**(留给 2.0 数据大屏蓝本) +- ❌ **不做编导排名**(团队视角走能力地图) +- ❌ **AI 不自动盖章下结论**(永远是"AI 出草稿,人拍板") +- ❌ **不引入新图表库**(继续 ECharts / @ant-design/charts) +- ❌ **不引入主 project 没有的技术栈** +- ❌ **不动主 project 已有的 schema**(改字段必须走 §六交付协议) +- ❌ **不做"未审字段直接上看板"**(置信度三档必须可辨) +- ❌ **不写 / 不改 AI 打标 Prompt 由 Cline 完成**(Cline 是执行者不是作者) + +--- + +## 五、技术栈约束(继承主 project) + +子项目所有设计必须能落在以下栈上,不引入新依赖: + +- **前端**:React 18 + Vite + AntD 5 + Zustand + ECharts / @ant-design/charts +- **后端**:FastAPI + SQLModel + PG 16 + pgvector +- **配色**:米色底 `#fbf9f1` + 深绿主色 `#6b8e6b` +- **三色色板**:红 `#c0584f` / 蓝 `#5b8db8` / 绿 `#7aa874` +- **基准线**:基础线 0.6448 / 摸高线 0.8989(2026) +- **AI 打标**:沙盒 `ai-labeling/`,生产模型 MiniMax M3,openai==1.55.0 锁版本 +- **部署**:腾讯云 4 核 8G(性能上限,凡是吃不动的方案直接否决) + +--- + +## 六、交付什么(出口接口) + +子项目产出最终要回到主 project 实施,**交付物必须满足以下接口**: + +### 6.1 Schema 变更类 +- 子项目出:**字段定义文档**(命名 / 类型 / 枚举 / nullable / 约束 / 回滚方案) +- 主 project 实施:**Cline 切 Plan → Opus 审 → 制片人批准 Act → `pg_dump` 备份 → 跑迁移** +- 不接受:子项目自己改主 project schema 或 `backend/` 任何代码 + +### 6.2 前端实施类 +- 子项目出:**设计 token JSON + HTML mock + 视觉规范文档** +- 主 project 实施:**Cline 按设计稿落 React 组件** +- 不接受:子项目自己写主 project 的 React 代码 + +### 6.3 AI 打标类 +- 子项目在 `ai-labeling/` 沙盒自治,**沙盒内 Cline 可直接 Act** +- 子项目出:**Prompt 文件 + ground-truth.json + 模型选型结论** +- 主 project 实施:**episodes_staging 表 schema 落库 + 审稿 UI 落地** + +### 6.4 回填类 +- 子项目出:**回填脚本设计文档 + 23 期回填策略** +- 主 project 实施:**Cline 跑脚本 + 制片人审 staging** + +### 6.5 通用约束 +- 子项目**不主动 push** 文件到主 project,**所有交付物由制片人决定时机** +- 主 project 只接**稳定版**,子项目内部草稿不交付 + +--- + +## 七、入口接口(子项目要知道的主 project 现状) + +- **Phase 0-3 已落地**:episodes / editors / 收视诊断基础数据,23 期 2026 年数据已入库 +- **现有视觉语言**:米色 + 深绿,卡片圆角 12px,AntD 5 默认行高 +- **现有 backlog**:主 project 这边的 backlog 已标注"看板升级条目已外迁" +- **现有沙盒**:`ai-labeling/` 工作区已搭建,Prompt 2 v0.2 已锁,MiniMax M3 已选定 +- **现有 git**:Gitea 落库,关键 commit `38f3728` + +--- + +## 八、未决项(归子项目自己拍板) + +以下不在本 Brief 内预设,**由子项目和制片人讨论决定**: + +- 横轴量化方法的具体公式 +- AI 打标 Prompt 1 / Prompt 3 的具体写法 +- 看板各屏幕的具体布局和交互 +- 双引擎象限图的具体视觉细节 +- 能力地图的具体可视化方案 +- episodes 新字段的最终命名 / 类型 / 是否多对多关联表 +- 置信度是单字段还是分字段 +- 时间窗口取多大 / 小样本怎么兜底等具体口径 + +--- + +## 九、决策史指向(不重复内容) + +子项目讨论前应该读的: +- `续接快照_看板分析升级_设计讨论.md`(v1,2026-06-09) +- `续接快照_看板分析升级_设计讨论_v3.md`(v3,2026-06-11 上午) +- `续接快照_看板分析升级_设计讨论_v4.md`(v4,2026-06-11 晚) +- `军事科技栏目收视诊断分析_v2.xlsx`(双引擎模型源数据) + +--- + +## 十、本 Brief 自身的修订规则 + +- 本 Brief 不在子项目内修改 +- 红线 / 技术栈如有变更,**主 project 这边发新版**,通过寄存条同步 +- 子项目可在自己的 chat 里讨论"建议主 project 调整某条红线",但调整动作只能在主 project 发生 diff --git a/docs/cross_project/看板升级分析/main2slave/迁移清单_主project操作指南.md b/docs/cross_project/看板升级分析/main2slave/迁移清单_主project操作指南.md new file mode 100644 index 0000000..d9ecfa4 --- /dev/null +++ b/docs/cross_project/看板升级分析/main2slave/迁移清单_主project操作指南.md @@ -0,0 +1,95 @@ +# 迁移清单 — 主 project 操作指南 + +> 用途:看板升级子项目立项时,主 project 这边的文件操作清单 +> 操作完即可丢弃 +> 制定时间:2026-06-12 + +--- + +## 资产分两类处理 + +### 🔵 子项目独有(主 project 这边删) + +纯决策记录性质,主 project 不再需要: + +1. `续接快照_看板分析升级_设计讨论.md`(v1) +2. `续接快照_看板分析升级_设计讨论_v3.md` +3. `续接快照_看板分析升级_设计讨论_v4.md` +4. `dashiboard首页_拷贝.jpg`(批注稿) +5. `简洁风_清晰.jpg`(参考图) + +### 🟢 两边都留(各保留一份) + +资产类性质,主 project 将来也可能用得到: + +1. `军事科技栏目收视诊断分析_v2.xlsx` — 数据基础 +2. 11 份节目文稿 `.docx`(硅基大脑、潜艇仿生 等) — 节目原始资料 +3. `dashiboard首页原版.jpg` — 主 project 实施 React 时要看 +4. `色调字体倒角.webp` — 整体视觉资产 + +--- + +## 主 project 这边的其他动作 + +### 新增 + +- 上传 `寄存条_看板升级已外迁.md` + +### 更新 + +- `backlog_md_TPS_工作台待办池_不丢视野_非排期_.md` 顶部加一段: + +```markdown +> ⚠️ 更新(2026-06-12):看板分析升级相关待办(原 F1-F8)已整体迁移到 +> **TPS 看板分析升级子项目**。主 project 这边不再维护这部分待办。 +> 子项目状态见 `寄存条_看板升级已外迁.md`。 +``` + +### 保留(不动) + +主干文档(README、Claude协作原则、phase 日志、doco 系列、git_workflow、_clinerules、_gitignore、001_init.sql、dev_plan、project_plan、Phase 3 续接快照系列)全部保留。 + +--- + +## 操作步骤 + +**Step 1 ─ 建子项目** +1. 新建 Claude project,名为 `TPS看板分析升级` +2. 上传 4 份新文件:`INDEX.md` + `看板升级子项目_Brief.md` + 拷贝一份 `Claude协作原则.md` +3. 上传 🔵 5 份子项目独有的文件 +4. 上传 🟢 14 份资产文件(主 project 也保留同名副本) + +**Step 2 ─ 清主 project + 寄存** +5. 删除 🔵 5 份文件 +6. 上传 `寄存条_看板升级已外迁.md` +7. 更新 `backlog` 顶部标注 + +**Step 3 ─ 验证** +8. 进主 project 新 chat:"读寄存条" → 看 Claude 是否懂"这部分外迁了,不在我这里讨论" +9. 进子项目新 chat:按 INDEX 开局口径 → 看 Claude 是否能正确进入顾问角色 + +--- + +## 操作后的拓扑 + +``` +TPS 主 project TPS 看板升级子项目 +───────────────────── ───────────────────── +├─ 主干文档(README/协作原则/phase 等) ├─ INDEX.md ★ +├─ doco/migration/git/init/dev/plan ├─ 看板升级子项目_Brief.md ★ +├─ Phase 3 续接快照系列 ├─ Claude协作原则.md(拷贝) +├─ backlog(顶部加标注) ├─ 续接快照 v1/v3/v4 +├─ 寄存条_看板升级已外迁.md ★ ├─ xlsx + 11 份 docx(副本) +├─ xlsx + 11 份 docx(副本) ├─ dashboard 首页原版(副本) +├─ dashboard 首页原版(副本) ├─ 色调字体倒角(副本) +└─ 色调字体倒角(副本) ├─ dashboard 首页_拷贝.jpg + └─ 简洁风_清晰.jpg +``` + +★ = 新增 + +--- + +## 拿不准的就先不动 + +任何一份文件去留拿不准的,**先保留**。文件保留比误删容易处理。 diff --git a/docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0.md b/docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0.md new file mode 100644 index 0000000..928b404 --- /dev/null +++ b/docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0.md @@ -0,0 +1,151 @@ +# F3 横轴量化方法 PRD v1.0 + +> 子项目交付物 · 看板分析升级 +> 状态:**v1.0 已锁**(2026-06-12 通哥审过 §2/§3/§4 决策) +> 起草时间:2026-06-12 +> 顾问:Claude Opus 4.7 | 制片人:刘通 +> 类型:薄 PRD(只解 v2 之后留下的开放点,已锁部分仅引用) + +--- + +## §1 引用已锁结论(不重述) + +本 PRD 是对已有结论的**补丁/收敛**,不替换。已锁部分直接参见以下来源: + +| 来源 | 已锁内容 | +|---|---| +| 续接快照 v2 §3 | 横轴公式 `X = 该题材在分析窗口内的均值份额 − 栏目均值份额`,零点 = 栏目均值,单位 = 收视份额绝对差 | +| 续接快照 v2 §4 | 双引擎象限图视觉规格:气泡大小 = 收视份额、气泡颜色 = 收视判定三色、气泡边框 = 开篇钩子三档、重叠用 jitter 解 | +| 主 project Claude 示意图(2026-06-10) | 23 期实际数据 + 模拟纵轴/钩子已可视化,制片人认可"不挤、不堆叠" | +| 续接快照 v3 §1.1 / v4 §3 | 纵轴从 3 档收为 2 档(主线演进 / 并列结构),Prompt 2 v0.2 + ground-truth v0.2.1 M3 100% 锁定 | +| 续接快照 v2 §2 字段二 | `equipment_domain` 多选 9 类:陆战装备 / 海军舰船 / 航空航天 / 导弹与反导 / 信息化 / 后勤保障 / 轻武器 / 跨域 / 其他 | + +本 PRD 解决 v2 之后留下的三个开放点:**多标签横轴取值规则、小样本兜底、视觉规格刷新到 2 档纵轴**。 + +--- + +## §2 决策一:多标签 → 横轴单点取值规则(Path A 全额法) + +`equipment_domain` 字段为多选(至少 1 项),但横轴一期只能落一个点。 + +### 2.1 规则 + +**每个 `equipment_domain` 标签独立维护自己的累计均值;一期节目标了 N 个标签,就把该期完整 share 直接记进**每一个**沾上的标签的均值池(全额、不瓜分、不加权)。** + +### 2.2 数学定义 + +``` +对每个 equipment_domain 标签 i: + base_i = 所有沾标签 i 的节目的 share 的算术平均 + = Σ(share_j, ∀ j 沾标签 i) / count(j 沾标签 i) + +栏目均值: + base_全栏目 = 所有节目的 share 的算术平均(单期单算) + +每期 ep 的横轴位置: + X(ep) = mean(base_i, ∀ i ∈ ep.equipment_domain) − base_全栏目 +``` + +### 2.3 示例 + +设当前 `海军舰船` 桶均值 = 0.69,`跨域` 桶均值 = 0.79,栏目均值 = 0.72。 + +- 一期标 `海军舰船 + 跨域`,share = 0.71 +- 该期 X 位置 = (0.69 + 0.79) / 2 − 0.72 = **+0.02**(略偏热) +- 这期 share = 0.71 同时记入 `海军舰船` 均值池(下次 base 重算用)和 `跨域` 均值池 + +**两点说明**: + +1. **均值更新方向自洽**:0.71 < 0.72(栏目均值),也低于"跨域 0.79"。记入两个桶后,两个桶的均值都会被这期略往下拉,符合"低于均值的样本拉低均值"的直觉。 +2. **不存在 ×N 放大**:之前讨论过的"按基值加权 × 标签数"方案因方向 bug(低于均值的样本可能反向拉高均值)被否决,详见 2026-06-12 续接讨论。 + +### 2.4 适用范围 + +本规则**仅用于横轴 X 值计算**。其他统计(能力地图、编导对比、scene_tags 分布等)的多标签处理另议。 + +--- + +## §3 决策二:小样本兜底规则 + +某 `equipment_domain` 桶若在当前分析窗口内样本量 n 较小,均值统计意义弱。视觉上需可辨。 + +### 3.1 兜底分档 + +| 桶样本量 | 视觉处理 | +|---|---| +| **n ≥ 3** | 正常显示,base 值直接入横轴坐标 | +| **n = 1 或 2** | 桶名右侧显示 `(n=X)` 灰色小字;桶内气泡描边改为虚线浅灰;tooltip 警示"样本不足,横轴位置参考价值有限" | +| **n = 0** | 桶不出现在横轴坐标系上(如 `陆战装备` 在 21 期场景下属此类,预留枚举值但暂不画) | + +### 3.2 阈值出处 + +n = 3 阈值的取法:统计上方差估计起码需 2 自由度,记录方便取 3。**这是初值,后续看实际分布可调**。 + +### 3.3 当前 21 期场景的小样本桶预测 + +按 v2 §2 九类 + 我对 21 期的初步映射(见配套回填提案表): + +- `导弹与反导` n=1(ep15 一期),**小样本桶** +- `信息化` n=1 或 2(取决于 ep03 是否归这里 vs 跨域),**可能小样本** +- `陆战装备` n=0,**不出现在坐标系** +- `后勤保障` n=0,**不出现在坐标系** +- `其他` n=0,**不出现在坐标系** + +其余 4 个桶(`海军舰船` / `航空航天` / `轻武器` / `跨域`)预计 n ≥ 3,正常显示。 + +--- + +## §4 决策三:视觉规格刷新(纵轴 3 档 → 2 档) + +v2 §4 视觉规格除以下差异外,**全部沿用**: + +| 元素 | v2 §4(3 档,旧) | 本版(2 档,新) | +|---|---|---| +| 纵轴档位 | 强主线悬念 / 主线演进 / 并列结构 | **主线演进 / 并列结构**(v3 §1.1 收为 2 档) | +| 纵轴坐标 | 三个水平基线 | 两个水平基线 | +| 同档内 jitter | ±0.3 | ±0.4(档少了,jitter 可适当加大避免聚拢) | +| 中线视觉 | 两条次级分隔 | **一条主分隔线显著加粗**,强调"主线 vs 并列"对照 | +| 上下区视觉重点 | 三层等权 | **两区中性等权**——纵轴不暗示偏好方向,呈现客观对照(2026-06-12 通哥拍板) | + +横轴部分全部沿用 v2 §4,无变化。 + +--- + +## §5 配套交付清单(子项目本轮其余产出) + +- [✅ 本文件] `F3_横轴量化方法_PRD_v1.0草稿.md` +- [⏳ 下批] `双引擎象限图_HTML_mock_v1.0.html`——基于本 PRD 的纵轴 2 档版可视化,可浏览器直开看交互 +- [⏳ 下批] `F3_算法验证脚本.py`——在子项目沙盒里跑通完整计算流程,输入 23 期 equipment_domain 多选 + share,输出各桶 base + 23 期 X 值表 + 数据校验报告 +- [⏳ 下批] `equipment_domain_字段定义文档.md`——按 Brief §6.1 格式,主 project Cline 拿去做 schema 迁移 +- [⏳ 下批] `23期_equipment_domain_回填提案表.md`——给制片人审,或转 Prompt 1a 的 ground-truth 输入 + +--- + +## §6 落地依赖(主 project Cline 工作清单) + +子项目这边的 5 份交付物全部锁定后,主 project Cline 可接手以下工作(均需切 Plan + Opus 审): + +1. **Schema 迁移**:`backend/sql/00X_add_equipment_domain.sql`(基于字段定义文档) +2. **23 期回填**:Prompt 1a 跑批结果落库 + 制片人审过的 8 期标题推断低置信度样本 +3. **后端 API endpoint**:横轴聚合数据接口 `GET /api/v1/episodes/quadrant-data`(或类似) +4. **前端 React/ECharts 组件**:基于本 PRD 的 HTML mock 1:1 翻译 + +--- + +## §7 后续可能调整项(本版不解,留入 backlog) + +- **时间窗口选择**:滚动窗口 vs 全部历史 vs 季度,默认设"全部历史",后续 UI 加切换器 +- **n ≥ 3 阈值的实际验证**:用 23 期 + 新片陆续入库 6 个月后看分布,必要时调阈值 +- **多标签规则是否升级**:本版用全额路 A,运行半年若发现不妥(如某桶均值长期被高频跨域期数刷高)再议升级到带权或 1/N 守恒版 +- **n=1/2 视觉表达细节**:虚线浅灰 vs 描边淡化 vs 加图例,等 HTML mock 出来后定 + +--- + +## §8 修订规则 + +本 PRD 锁定 v1.0 后: + +- v1.x 微调(视觉细节、阈值小动)在本子项目内修改 +- v2.0 重大变更(规则换路 B、取消纵轴 2 档等)需主 project 同步审,通过新一版续接快照固化 + +修订必留 changelog,谁、何时、改了什么、为什么改。 diff --git a/docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0草稿.md b/docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0草稿.md new file mode 100644 index 0000000..edf2a3b --- /dev/null +++ b/docs/cross_project/看板升级分析/slave/F3_横轴量化方法_PRD_v1.0草稿.md @@ -0,0 +1,151 @@ +# F3 横轴量化方法 PRD v1.0(草稿) + +> 子项目交付物 · 看板分析升级 +> 状态:**草稿**,等制片人审 + 锁定后转正式 v1.0 +> 起草时间:2026-06-12 +> 顾问:Claude Opus 4.7 | 制片人:刘通 +> 类型:薄 PRD(只解 v2 之后留下的开放点,已锁部分仅引用) + +--- + +## §1 引用已锁结论(不重述) + +本 PRD 是对已有结论的**补丁/收敛**,不替换。已锁部分直接参见以下来源: + +| 来源 | 已锁内容 | +|---|---| +| 续接快照 v2 §3 | 横轴公式 `X = 该题材在分析窗口内的均值份额 − 栏目均值份额`,零点 = 栏目均值,单位 = 收视份额绝对差 | +| 续接快照 v2 §4 | 双引擎象限图视觉规格:气泡大小 = 收视份额、气泡颜色 = 收视判定三色、气泡边框 = 开篇钩子三档、重叠用 jitter 解 | +| 主 project Claude 示意图(2026-06-10) | 23 期实际数据 + 模拟纵轴/钩子已可视化,制片人认可"不挤、不堆叠" | +| 续接快照 v3 §1.1 / v4 §3 | 纵轴从 3 档收为 2 档(主线演进 / 并列结构),Prompt 2 v0.2 + ground-truth v0.2.1 M3 100% 锁定 | +| 续接快照 v2 §2 字段二 | `equipment_domain` 多选 9 类:陆战装备 / 海军舰船 / 航空航天 / 导弹与反导 / 信息化 / 后勤保障 / 轻武器 / 跨域 / 其他 | + +本 PRD 解决 v2 之后留下的三个开放点:**多标签横轴取值规则、小样本兜底、视觉规格刷新到 2 档纵轴**。 + +--- + +## §2 决策一:多标签 → 横轴单点取值规则(Path A 全额法) + +`equipment_domain` 字段为多选(至少 1 项),但横轴一期只能落一个点。 + +### 2.1 规则 + +**每个 `equipment_domain` 标签独立维护自己的累计均值;一期节目标了 N 个标签,就把该期完整 share 直接记进**每一个**沾上的标签的均值池(全额、不瓜分、不加权)。** + +### 2.2 数学定义 + +``` +对每个 equipment_domain 标签 i: + base_i = 所有沾标签 i 的节目的 share 的算术平均 + = Σ(share_j, ∀ j 沾标签 i) / count(j 沾标签 i) + +栏目均值: + base_全栏目 = 所有节目的 share 的算术平均(单期单算) + +每期 ep 的横轴位置: + X(ep) = mean(base_i, ∀ i ∈ ep.equipment_domain) − base_全栏目 +``` + +### 2.3 示例 + +设当前 `海军舰船` 桶均值 = 0.69,`跨域` 桶均值 = 0.79,栏目均值 = 0.72。 + +- 一期标 `海军舰船 + 跨域`,share = 0.71 +- 该期 X 位置 = (0.69 + 0.79) / 2 − 0.72 = **+0.02**(略偏热) +- 这期 share = 0.71 同时记入 `海军舰船` 均值池(下次 base 重算用)和 `跨域` 均值池 + +**两点说明**: + +1. **均值更新方向自洽**:0.71 < 0.72(栏目均值),也低于"跨域 0.79"。记入两个桶后,两个桶的均值都会被这期略往下拉,符合"低于均值的样本拉低均值"的直觉。 +2. **不存在 ×N 放大**:之前讨论过的"按基值加权 × 标签数"方案因方向 bug(低于均值的样本可能反向拉高均值)被否决,详见 2026-06-12 续接讨论。 + +### 2.4 适用范围 + +本规则**仅用于横轴 X 值计算**。其他统计(能力地图、编导对比、scene_tags 分布等)的多标签处理另议。 + +--- + +## §3 决策二:小样本兜底规则 + +某 `equipment_domain` 桶若在当前分析窗口内样本量 n 较小,均值统计意义弱。视觉上需可辨。 + +### 3.1 兜底分档 + +| 桶样本量 | 视觉处理 | +|---|---| +| **n ≥ 3** | 正常显示,base 值直接入横轴坐标 | +| **n = 1 或 2** | 桶名右侧显示 `(n=X)` 灰色小字;桶内气泡描边改为虚线浅灰;tooltip 警示"样本不足,横轴位置参考价值有限" | +| **n = 0** | 桶不出现在横轴坐标系上(如 `陆战装备` 在 21 期场景下属此类,预留枚举值但暂不画) | + +### 3.2 阈值出处 + +n = 3 阈值的取法:统计上方差估计起码需 2 自由度,记录方便取 3。**这是初值,后续看实际分布可调**。 + +### 3.3 当前 21 期场景的小样本桶预测 + +按 v2 §2 九类 + 我对 21 期的初步映射(见配套回填提案表): + +- `导弹与反导` n=1(ep15 一期),**小样本桶** +- `信息化` n=1 或 2(取决于 ep03 是否归这里 vs 跨域),**可能小样本** +- `陆战装备` n=0,**不出现在坐标系** +- `后勤保障` n=0,**不出现在坐标系** +- `其他` n=0,**不出现在坐标系** + +其余 4 个桶(`海军舰船` / `航空航天` / `轻武器` / `跨域`)预计 n ≥ 3,正常显示。 + +--- + +## §4 决策三:视觉规格刷新(纵轴 3 档 → 2 档) + +v2 §4 视觉规格除以下差异外,**全部沿用**: + +| 元素 | v2 §4(3 档,旧) | 本版(2 档,新) | +|---|---|---| +| 纵轴档位 | 强主线悬念 / 主线演进 / 并列结构 | **主线演进 / 并列结构**(v3 §1.1 收为 2 档) | +| 纵轴坐标 | 三个水平基线 | 两个水平基线 | +| 同档内 jitter | ±0.3 | ±0.4(档少了,jitter 可适当加大避免聚拢) | +| 中线视觉 | 两条次级分隔 | **一条主分隔线显著加粗**,强调"主线 vs 并列"对照 | +| 上下区视觉重点 | 三层等权 | 上区(主线演进)略亮、下区(并列结构)略灰,**视觉上暗示偏好方向**(可选,等通哥决) | + +横轴部分全部沿用 v2 §4,无变化。 + +--- + +## §5 配套交付清单(子项目本轮其余产出) + +- [✅ 本文件] `F3_横轴量化方法_PRD_v1.0草稿.md` +- [⏳ 下批] `双引擎象限图_HTML_mock_v1.0.html`——基于本 PRD 的纵轴 2 档版可视化,可浏览器直开看交互 +- [⏳ 下批] `F3_算法验证脚本.py`——在子项目沙盒里跑通完整计算流程,输入 23 期 equipment_domain 多选 + share,输出各桶 base + 23 期 X 值表 + 数据校验报告 +- [⏳ 下批] `equipment_domain_字段定义文档.md`——按 Brief §6.1 格式,主 project Cline 拿去做 schema 迁移 +- [⏳ 下批] `23期_equipment_domain_回填提案表.md`——给制片人审,或转 Prompt 1a 的 ground-truth 输入 + +--- + +## §6 落地依赖(主 project Cline 工作清单) + +子项目这边的 5 份交付物全部锁定后,主 project Cline 可接手以下工作(均需切 Plan + Opus 审): + +1. **Schema 迁移**:`backend/sql/00X_add_equipment_domain.sql`(基于字段定义文档) +2. **23 期回填**:Prompt 1a 跑批结果落库 + 制片人审过的 8 期标题推断低置信度样本 +3. **后端 API endpoint**:横轴聚合数据接口 `GET /api/v1/episodes/quadrant-data`(或类似) +4. **前端 React/ECharts 组件**:基于本 PRD 的 HTML mock 1:1 翻译 + +--- + +## §7 后续可能调整项(本版不解,留入 backlog) + +- **时间窗口选择**:滚动窗口 vs 全部历史 vs 季度,默认设"全部历史",后续 UI 加切换器 +- **n ≥ 3 阈值的实际验证**:用 23 期 + 新片陆续入库 6 个月后看分布,必要时调阈值 +- **多标签规则是否升级**:本版用全额路 A,运行半年若发现不妥(如某桶均值长期被高频跨域期数刷高)再议升级到带权或 1/N 守恒版 +- **n=1/2 视觉表达细节**:虚线浅灰 vs 描边淡化 vs 加图例,等 HTML mock 出来后定 + +--- + +## §8 修订规则 + +本 PRD 锁定 v1.0 后: + +- v1.x 微调(视觉细节、阈值小动)在本子项目内修改 +- v2.0 重大变更(规则换路 B、取消纵轴 2 档等)需主 project 同步审,通过新一版续接快照固化 + +修订必留 changelog,谁、何时、改了什么、为什么改。 diff --git a/docs/cross_project/跨project协作提示单_v1.md b/docs/cross_project/跨project协作提示单_v1.md new file mode 100644 index 0000000..84bc744 --- /dev/null +++ b/docs/cross_project/跨project协作提示单_v1.md @@ -0,0 +1,224 @@ +# 跨 project 协作 — 制片人随身提示单 + +> 用途:每次想在主项目之外单开子 project 讨论某个模块时,照此提示单走一遍,避免漏环节、避免版本错位、避免子项目设计违反主项目宪法。 +> 适用:刘通在 Claude 网页版多 project 工作流下的所有"主项目 + 子项目"场景。 +> 维护人:刘通 + 主 project 顾问 (Claude Opus 4.7) +> 首版日期:2026-06-11 + +--- + +## 〇、什么是 PRD? + +**PRD = Product Requirements Document,产品需求文档**。 + +把"我想要个东西"翻译成"工程师可以照着做的具体规格"。一份合格的 PRD 回答: + +- 为什么做:解决谁的什么痛点 +- 做什么:功能边界、必做/不做 +- 怎么用:用户走完流程的样子 +- 数据形态:输入什么、输出什么、字段怎样 +- 不做什么:明确划红线,防止范围蔓延 + +子项目立项时,请那边的 Claude 写一份 PRD 跟主项目对接——这就是工程界的标准操作。 + +--- + +## 一、先判断:这事适不适合分子 project? + +分之前先想三个问题。三条都满足才适合分: + +1. **这个模块与主项目耦合深不深?** 耦合深的(改用户体系、改权限)就别分,分了沟通成本比省下的还高。 +2. **这个模块边界清不清?** 输入输出明确的(如分钟收视回放)分得开;边界模糊的(如"优化整体视觉风格")分不开。 +3. **工作量够不够大?** 特别小的事(改个字段名)直接在主项目谈完即可。三五次对话讨论不完的,才值得分。 + +**反例**:未来想做"AI 自动生成报题单"——这件事跟主项目的 TPS 模块、知识库、收视数据、编导画像全打通,**强耦合**,分子 project 反而割裂上下文。这种就在主项目里推进。 + +**正例**:分钟收视回放、本地切图工具、外拍资源管理子模块——边界清晰,可独立设计。 + +--- + +## 二、流程(四步) + +### 步骤 1:在主项目立"项"——先打招呼 + +子项目开干**之前**,先在主项目 chat 里跟 Claude 顾问说一声: + +> "我要开一个子 project 讨论 X 模块,目标是 Y。" + +主项目 Claude 会做两件事: + +- 检查 X 是否真的适合分出去(参照第一节三问) +- 把主项目跟 X 相关的红线、技术栈、宪法摘出来给你 + +**实操**:把这些"开局材料"打包好,作为子项目的 files 上传过去(至少包括 `.clinerules`、`project_plan.md`、`dev_plan.md`,必要时加相关的 phase log)。子项目 Claude 能读到这些,就能少犯错。 + +**这一步不能省**。子项目 Claude 看不到主项目宪法时,会本能地按自己的最佳实践设计——很可能违反主项目某条红线。 + +### 步骤 2:在子项目讨论、迭代、收敛 + +要点: + +- 子项目**不写主项目的代码**,只输出文档、demo、独立小工具 +- 子项目**不改主项目的 schema**,所有涉及主项目数据库结构的提议,回主项目让 Claude 顾问审 +- 子项目**遇到吃不准的事,回主项目问**——以提问列表的形式(参考秒级 chat 那份 PRD 第八节的 6 问) + +### 步骤 3:子项目凝结成"交付包" + +讨论完不能就这么散了。要凝结成可以跨 chat、跨 project、被未来任何人接手的产物。**详见下面第四节五份交付物**。 + +**关键认知**:Claude 的 chat 越长,判断力越下降(上下文太满);而且新 chat 一开就丢了。**md 文档才是真正的产物,chat 只是过程**。讨论一收敛,赶紧让子项目 Claude 凝结成 md。 + +### 步骤 4:回主项目整合 + +带着交付包回主项目: + +- 主项目 Claude 顾问审一遍交付包,看跟主项目红线有没有冲突 +- 没冲突 → 在主项目 workspace 里让 Cline 落代码 +- 有冲突 → 标出冲突点,回子项目修,循环 + +**主项目顾问对子项目交付物有否决权**。听起来霸道,但必要——子项目 Claude 不知道主项目全部红线。这一步省不掉。 + +--- + +## 三、规范:跨 project 文档怎么写 + +做对四件事,文档传递不会乱。 + +### 规范 1:文件名带版本号 + +❌ `分钟收视需求规格.md` +✅ `分钟收视需求规格_v1.md` + +下次改了就 v2。**绝对不要原地覆盖**——历史版本要留着,方便回看"我们当时为什么这么定"。 + +### 规范 2:文档顶部固定"元信息块" + +每份跨 project 文档第一段必须有: + +``` +> 版本:v1 +> 日期:2026-06-11 +> 作者:[谁写的] +> 状态:[草稿 / 待主项目审 / 已采纳 / 已作废] +> 上一版:[v0 或"无"] +> 主要变化:[v2 才需要写] +``` + +这块 6 行,写起来不累,但是救命。**没有这个块的跨 project 文档,主项目这边可以拒收**。 + +### 规范 3:一份文档只谈一件事 + +不要在"分钟收视需求规格"里顺带讨论"用户管理优化"。串了就乱。 + +### 规范 4:主项目这边定期回收 + +子项目讨论完、文档传过来、整合完成后,把最终采纳的那份 md 放到主项目的 `docs/cross_project/` 目录里。这是主项目的"对外接口档案库"。 + +--- + +## 四、最终交付物:子项目结束时该产出什么(五份) + +子项目结束时,告诉那边的 Claude: + +> "请把所有讨论凝结成最终交付包,包含以下五份产物。" + +### 产物 1:决策摘要 md + +记录子项目全程的关键决策。每条三句话: + +``` +决策 X:[一句话决策内容] + - 背景:为什么需要这个决策 + - 拍板理由:选这条不选那条的理由 + - 拍板人:[制片人 / 主项目顾问 / 子项目 Claude 建议+制片人采纳] +``` + +不需要"我们讨论了 47 轮"的过程,只要最终结论。10-20 条就把整个子项目的"为什么这么做"讲清。 + +### 产物 2:接口约定 md(最技术、最精确) + +把子板块与主项目接驳的所有"接口"白纸黑字写出来。"接口"是广义的,包括: + +- **数据接口**:要新建哪些表、哪些字段、字段类型、约束 +- **API 接口**:要新增哪些后端 endpoint、URL、方法、请求体、响应体、错误码 +- **UI 接口**:要在主项目哪个页面挂载这个模块、入口在哪、路由怎么走 +- **权限接口**:哪些角色可以做什么操作 +- **存储接口**:文件存到哪个目录、命名规则、URL 怎么拼 + +每一条都要具体到"主项目的 Cline 拿着这份就能写代码"的程度。**这份是子项目给主项目的"合同"**。 + +### 产物 3:实物交付(独立工具、demo、设计稿) + +子项目特有的产物。视模块性质不同: + +- 分钟收视回放子项目 → 本地切图工具 (py + exe + 使用说明) +- 视觉重设计子项目 → 设计稿(Figma / HTML demo) +- 数据分析子项目 → 离线分析脚本 + 示例输出 + +这些东西**与主项目零耦合**,自己能独立运行。 + +### 产物 4:未尽事项 backlog + +诚实写出"子项目讨论中提到但没做的事"。每条三行: + +- 是什么 +- 为什么没做(暂缓 / P1+ / 等条件成熟) +- 触发时机 + +放进主项目的 backlog 池子里,未来某个 Phase 收尾时回看,决定哪条捞进下个 Phase。 + +### 产物 5:给主项目 Claude 顾问的"接手提示词" + +一两段话,告诉主项目顾问"如果你是接手这件事的 Claude,最重要的三件事是 X、Y、Z"。 + +这段相当于一份"防止主项目这边犯傻"的备忘,往往是最有价值的一份。 + +--- + +## 五、操作过程中的实操注意点 + +### 注意 1:永远以 md 为准,不要口头转述 + +❌ "那边 Claude 说 XX",然后跟那边说"主项目这边说 YY" +✅ 所有跨项目沟通**以 md 文档为准** + +口头转述会丢细节、会失真,连续转两次就跟最初的意思差很远。 + +### 注意 2:chat 是过程,md 是产物 + +子项目 chat 长了之后,Claude 自己的判断力会下降(上下文太满),换新 chat 就丢了。**讨论一收敛,赶紧让子项目 Claude 凝结成 md。chat 可以扔,md 不能扔**。 + +### 注意 3:主项目顾问的"审查"环节不能省 + +子项目 Claude 不知道主项目的全部红线,可能输出违规方案。**拿到子项目交付包后,先来主项目让顾问审一遍,再让 Cline 落实施**。这一步省不掉。 + +### 注意 4:每个子项目最长不要超过 3-4 周 + +时间越长,跟主项目脱节越严重(主项目可能在这期间已经迭代)。如果一个子项目超过 4 周还没收敛,应该停下来重新评估:是不是分得太大?要不要切成更小的子子项目? + +### 注意 5:交付包归档到主项目仓库 + +交付完成后,五份交付物放到主项目的 `docs/cross_project/{子项目名}/` 目录里。**版本号文件全部保留**,未来回看决策史时会用到。 + +--- + +## 六、一句话总结 + +> **子项目专心做事不出主项目;主项目负责审查与整合,不掺合子项目讨论;md 文档是跨项目通信的唯一介质;版本号 + 元信息块永远不省。** + +--- + +## 七、复盘提示(每次子项目结束后回看一次) + +子项目结束,回过头来问自己: + +1. 这次分子项目是不是真的提高了效率?还是反而增加了沟通成本? +2. 主项目顾问审查时发现了多少个违规设计?说明子项目立项时给的"开局材料"够不够? +3. 五份交付物齐了吗?哪份最有用?哪份没人看? +4. 这次的经验,要不要加进本提示单的下一版? + +**本提示单本身也按版本管理**。每次复盘有沉淀就出 v2。 + +--- + +*v1 由主 project 顾问 (Claude Opus 4.7) 协助刘通于 2026-06-11 整理成稿。下次修订时机:第一次完整跑完一个子项目(分钟收视回放)之后。* diff --git a/docs/doco/Doco子项目_Brief.md b/docs/doco/Doco子项目_Brief.md new file mode 100644 index 0000000..b8ef565 --- /dev/null +++ b/docs/doco/Doco子项目_Brief.md @@ -0,0 +1,144 @@ +# Doco 文稿整理子项目 Brief + +> 主项目 → 子项目的"交接宪法":红线、技术栈、出入口接口 +> 起草日期:2026-06-12 +> 状态:主项目签发,子项目内部不修改 + +--- + +## 一、为什么做 + +**痛点**:《军事科技》每期节目播出后,产出一份贴近实际播出的终版文稿,过去靠人工对照 A 稿(编导定稿)+ 录音转写 + OCR 字幕反复核对,**单期 4-6 小时**,且容易遗漏错字、整段重写、编导笔误。 + +**升级目标**:把"三方文本 → 终版文稿 + 差异报告"做成可自动化的流水线模块。 + +**现状起点**:demo 已用《现代防空反导大对决》一期跑通,效果达标。栏目组改变了素材输出方式——直接产出"黑底白字+干净人声"的特殊视频——使物理输入从 3 路(A 稿+B 稿+ASR)收敛到 2 路(A 稿+视频),但**逻辑上仍然三方融合**(A 稿+B 路视频字幕 OCR+音轨 ASR)。 + +--- + +## 二、做什么(功能边界,只列名) + +| 模块 | 简述 | +|---|---| +| 视频双路拆分 | ffmpeg 抽帧+字幕变化检测+OCR → B 稿;ffmpeg 抽音轨 → 16k WAV | +| 讯飞 ASR 适配层 | 复用 demo 跑通的 `xfyun_asr_standard.py`,替换凭证 | +| 三方融合引擎 | 规则层(Python)+ AI 层(Claude Sonnet 4.6),demo 已锁 | +| 单期处理接口 | CLI + Python API,契约见主项目回复 §Q3 | +| Golden test | 用 demo 那期视频做零回归验证 | +| 23 期批量调度入口 | 单期接口的批量调用方式,具体编排不在本子项目 | + +**具体怎么做,子项目内部讨论,本 Brief 不预设方案。** + +--- + +## 三、怎么用(目标流程) + +**单期处理**: +1. 责编/编排层在 episodes 表落库该期,拿到 INT 主键 +2. 拼出 episode_id:`ep{NNN}_{YYYYMMDD}_{slug}` +3. 把视频和 A 稿放进 `programs/{episode_id}/source/` +4. 调 doco:`doco.process_episode(episode_id, video_path, a_draft_path, output_dir)` +5. 产物落 `programs/{episode_id}/output/`:终版 docx + 差异报告 + needs_review JSON +6. 编导/制片人审 needs_review 队列,决定接受 / 拒绝 / 编辑 + +**23 期批量**:用同一接口循环调用 23 次,失败重试 + 进度跟踪由调用方负责。 + +**降级回退**:若某期视频不符合"黑底白字+干净人声"条件,通过 feature flag 切换到旧流程(独立 mp3 + 上游 OCR)。 + +--- + +## 四、不做什么(红线) + +子项目设计任何方案,**绝不**触碰以下: + +- ❌ **不动 demo 已锁的算法层**(三方融合优先级 / 时间戳容忍 ±5s / 口语清理三档 / 改动确信度阈值 0.85/0.6 三段) +- ❌ **不引入 `ffmpeg-python` 等 wrapper**(用 subprocess 调系统 ffmpeg) +- ❌ **不替换 AI 融合层模型**(Claude Sonnet 4.6 已锁,不临时换 DeepSeek 等) +- ❌ **不复用主 project 那把 DeepSeek key**(Cline 工具用的 key 跟生产业务分开) +- ❌ **不把凭证写进主 project 的 `backend/.env`**(子模块自治,各自 .env) +- ❌ **不假设 episode_id 命名规则**(由调用方给定,doco 只当字符串用) +- ❌ **不引入主 project 没有的技术栈**(继续 Python 3.x + Claude API + 讯飞 API) +- ❌ **不自己实现批量调度**(失败重试 / 并发控制 / 进度跟踪属于编排层职责) +- ❌ **不在子项目里写中台密钥服务**(那不属于 doco 范围) + +--- + +## 五、技术栈约束(继承主 project) + +- **运行环境**:Python 3.x(版本对齐 backend),Windows / Linux 都可跑 +- **系统依赖**:ffmpeg ≥ 4.x(子模块文档明确列出) +- **AI 融合**:Anthropic Claude API,模型 `claude-sonnet-4-6` +- **OCR**:DeepSeek Vision API(主项目已批,Q1) +- **ASR**:讯飞开放平台 录音文件转写标准版(**不要用大模型版**——`language` 参数被阉割) +- **音频规格**:16kHz / 单声道 / 16bit WAV(讯飞规格) +- **数据格式**:句子级时间戳统一 `[Nm Ns] 句子` 格式 + +--- + +## 六、交付什么(出口接口) + +子项目最终交付物以下面形式回到主 project,**必须满足以下接口**: + +### 6.1 模块交付物 +- `doco/` Python 包,可被主 project 编排层 import +- CLI 入口:`doco process ...` +- 单元测试 + golden test(demo 那期零回归) +- `README.md`:系统依赖、安装、使用、配置 + +### 6.2 配置与凭证 +- doco 自己的 `.env`(讯飞 + DeepSeek + Claude 三套 key 各自管) +- 主项目仓库根目录的 `docs/api_credentials_inventory.md` 登记一行 doco 的凭证元信息(不存真实 key) + +### 6.3 数据资产沉淀(可入 TPS 中台共享) +- A 稿术语表(每期处理时从 A 稿提取,可入主 project 数据库的 terms 维度) +- OCR 错字映射表(demo 已整理,固定字形混淆对) + +### 6.4 不接受 +- 子项目自己改主 project 的 backend 代码、schema、迁移 +- 子项目自己写主 project 的前端 UI(编导确认 UI 属于上层,不在 doco 范围) +- 子项目自己定 episode_id 命名规则(由主项目给定) + +--- + +## 七、入口接口(子项目要知道的主 project 现状) + +- **主项目没有 OCR 子模块**(已确认,Q1) +- **主项目没有视频处理服务**(已确认,Q2) +- **主项目没有节目素材批量编排层**(已确认,Q3) +- **主项目没有中台密钥服务**(已确认,Q4) +- **主项目没有节目素材目录规约**(已确认,Q5,本子项目带头定 `programs/{episode_id}/...`) +- **主项目有 episodes 表**(INT 自增主键),doco 用 episode_id 时假定调用方已落库 + +--- + +## 八、未决项(归子项目自己拍板) + +以下不在本 Brief 内预设,**由子项目和制片人讨论决定**: + +- 视频抽帧的具体策略(关键帧检测算法 / 抽帧密度 / 字幕变化阈值) +- DeepSeek Vision 调用的具体 prompt 设计 +- 规则层 OCR 错字映射表的具体扩展机制 +- AI 融合层的具体提示词迭代 +- needs_review JSON 的具体字段(在 demo 基础上演化) +- 单元测试覆盖率目标 +- 子项目内部的 git 分支策略 / 版本号约定 + +--- + +## 九、决策史指向(不重复内容) + +子项目讨论前必读: +- `doco_project_design.md / .docx`(三方融合算法层,demo 沉淀,已锁) +- `doco_xfyun_integration_notes.md / .docx`(讯飞 ASR 接入 15 个坑) +- `doco_handoff_to_opus_chat.md / .docx`(子项目立项前的 opus 接手提示) +- `PRD_doco_文稿整理子项目_v2.md`(当前子项目的请求稿) +- `主project对Doco_PRDv2的回复.md`(主项目的批复) + +--- + +## 十、本 Brief 自身的修订规则 + +- 本 Brief 不在子项目内修改 +- 红线 / 技术栈如有变更,**主 project 这边发新版**,通过寄存条同步 +- 子项目可在自己的 chat 里讨论"建议主 project 调整某条红线",调整动作只能在主 project 发生 +- 子项目每次出新版 PRD,主项目顾问发新版回复;两者形成 PRD vN ↔ 回复 vN 的对照关系 diff --git a/docs/doco/PRD_doco_文稿整理子项目_v2.md b/docs/doco/PRD_doco_文稿整理子项目_v2.md new file mode 100644 index 0000000..c6ab926 --- /dev/null +++ b/docs/doco/PRD_doco_文稿整理子项目_v2.md @@ -0,0 +1,295 @@ +# PRD · Doco 文稿整理模块 · 视频源改造方案 + +> 版本:v2 +> 日期:2026-06-12 +> 作者:子 project Claude(Opus 4.7) +> 状态:草稿 / 待主项目审 +> 上一版:v1(2026-06-12 同日,更早一稿) +> 主要变化: +> 1. **子模块正式定名为「Doco 文稿整理模块」**(v1 沿用旧称"TPS 三方融合子模块",本版起全部改用 Doco;Q6 已由制片人拍板,不再列为待决问题) +> 2. **Q1(OCR 选型)方向更新**:制片人确认"软件离线刚需"已不成立,云端方案可接受。补充查清的关键事实:**讯飞 OCR 与 STT 共享鉴权凭证但套餐/余额各自独立**——按制片人原判据"如果能通用 token 订阅和余额就选讯飞"不成立。**新推荐方案:DeepSeek API**(制片人手上已有),并向主项目确认是否已有 OCR 能力或 LLM 视觉调用基础设施 +> 3. 其他 Q(Q2/Q3/Q4/Q5)保持不变 + +--- + +## 〇、一段话摘要(给主 project 顾问的最短摘要) + +Doco 文稿整理模块(前称"TPS 三方融合子模块")此前的物理输入是 A 稿 docx + B 稿 OCR txt + ASR txt(mp3→讯飞)。栏目组现在改了素材输出方式:责编一次性输出 23 期"特殊视频"——画面是黑底白字唱词字幕、声音是无 BGM/音效的干净人声(极少同期声杂音)。**B 稿与 ASR 现在同源派生于一个视频文件**,物理输入从 3 个收敛到 2 个(A 稿 + 视频)。 + +**核心判断**:下游"三方融合引擎"(规则层 + AI 层)算法**零侵入**——只要新增的"视频双路拆分"模块输出与旧版 B 稿/ASR txt 格式完全一致(`[Nm Ns] 句子`),下游所有逻辑不动。**逻辑上仍然是三方融合**——A 稿术语权威 + B 路视频字幕 OCR + 音轨 ASR,三路独立验证的关系没破。 + +子项目这边需要主 project 在开工前对 5 件影响接口/数据形态的事拍板。详见 §4。 + +--- + +## 一、变更前后对照 + +### 旧设计(demo 已验证) + +``` +[A 稿 docx] ──────────────────────┐ + │ +[上游既有 OCR 流程] ── B 稿 txt ──┤ + ├─► [三方融合引擎] ─► 终版.docx + 差异报告.docx +[节目纯净人声 mp3] │ + │ │ + └─► [讯飞 ASR] ── ASR txt ─┘ +``` + +### 新设计(本 PRD 提出) + +``` +[A 稿 docx] ─────────────────────────────────────┐ + │ +[节目特殊视频文件] │ + 画面:黑底白字字幕 │ + 声音:干净人声 │ + │ │ + ├─► [视频双路拆分子模块 · NEW] │ + │ │ │ + │ ├─► ffmpeg 抽帧+字幕变化检测 │ + │ │ │ │ + │ │ └─► [OCR] ── B 稿 txt ──────┤ + │ │ (待 Q1 拍板用 DeepSeek) ├─► [三方融合引擎] ─► 终版 + 差异 + │ └─► ffmpeg 抽音轨 → 16k/mono WAV │ (复用) + │ │ │ + │ └─► [讯飞 ASR · 复用] │ + │ └────── ASR txt ──────┘ + │ + └─ (后续接 23 期批量调度) +``` + +### 关键变化清单 + +| 项 | 旧 | 新 | +|---|---|---| +| 子模块名称 | TPS 三方融合子模块 | **Doco 文稿整理模块** | +| 物理输入 | A 稿 + B 稿 + ASR | A 稿 + 视频 | +| 逻辑融合方 | 三方 | **仍然三方**(A / B 路 / ASR 路) | +| B 稿来源 | 上游既有 OCR 流程 | 视频抽帧 → OCR | +| ASR 来源 | 节目 mp3 | 视频音轨 | +| 算法层 | 规则层 + AI 层 | **不变** | +| 新增模块 | — | 视频双路拆分(ffmpeg + OCR) | +| 处理粒度 | 单期 | 单期接口 + 23 期批量调度 | + +--- + +## 二、子项目这边已经拍板的事(不需要主 project 决策,仅告知) + +### 2.1 制片人已明确的决策 + +1. **子模块定名**:**Doco 文稿整理模块**("Doco" 取意"Documentation Consolidation"——文稿整合)。本 PRD 起所有引用旧称("TPS 三方融合子模块"、"三方融合")的地方都用新名替换。 +2. **离线不再是刚需**:原 v1 推荐 PaddleOCR 本地的核心理由(离线刚需)失效;OCR 选型转向云端。 + +### 2.2 demo 已验证或设计文档已定义的事 + +不变: + +1. **三方融合优先级**(A 稿术语权威 / ASR 口吻 / B 稿验证)—— 见 project_design.md §5.1 +2. **时间戳容忍度 ±5 秒** —— project_design.md §5.2 +3. **口语清理三档**(keep_all / medium / clean)—— project_design.md §5.3 +4. **改动确信度阈值**(0.85 自动 / 0.6–0.85 黄标 / <0.6 待确认)—— project_design.md §5.4 +5. **AI 融合层模型**:Claude Sonnet 4.6 起步 +6. **差异报告格式**:demo 黄金对照已落定 +7. **A 稿术语表数据结构**:project_design.md §9.1 +8. **OCR 错字映射表**:复用 demo 整理的字形混淆对 + +--- + +## 三、子项目这边的开发计划草案(待主 project 审) + +| Phase | 内容 | 产物 | 主依赖 | +|---|---|---|---| +| P1 | 视频双路拆分预处理(单期)| `video_split.py`:视频 → 抽帧+字幕变化检测+OCR → B稿.txt;视频 → 音轨 → 16k WAV | ffmpeg、OCR(待 Q1 拍板,倾向 DeepSeek API) | +| P2 | 讯飞 ASR 适配层 | 复用 `xfyun_asr_standard.py`,替换新凭证、做成可被 P1 流水线调用的模块 | 讯飞开发端密钥(待 Q4 拍板) | +| P3 | Doco 融合引擎 | 规则层 Python + AI 层 Claude API;输入 A/B/ASR 三方 txt,输出终版 docx + 差异报告 + needs_review JSON | Claude API | +| P4 | 单期端到端 golden test | 用 demo 那期《现代防空反导大对决》的视频跑一遍,与 demo 产物比对,确认零回归 | 上面三 phase | +| P5 | 23 期批量 | 单期模块包成可被批量调度调用的接口(CLI / Python API 二选一,待 Q3 拍板) | — | + +--- + +## 四、需要主项目决策的 5 个问题 + +> 每条都给了子项目这边的推荐答案 + 理由。主项目顾问可以照单批,也可以推翻。 + +### Q1. OCR 方案选型? + +**问题**:B 路 OCR 用什么?这是新引入的依赖,绕不开。 + +#### Q1.A 关键事实(更新自 v1) + +子项目这边查清了几件事,供主项目决策参考: + +1. **讯飞 OCR 与 STT 的共享情况**: + - 鉴权凭证(开发者账号、APPID、APIKey、APISecret)**可共享**,一组凭证可同时调用多个服务 + - 但**套餐/计费/余额各自独立**:STT 是"5 小时免费试用包"独立购买;OCR 是另一套计费体系(参考 SaaS 零售价 4.9 元/10 张、9.9 元/50 张)。**不存在用 STT 的余额跑 OCR 这种事** + - **按制片人原判据("如果能通用 token 订阅和余额就选讯飞")此条件不成立** + +2. **制片人手上已有的 API**: + - **DeepSeek**:有专门的 `DeepSeek-OCR` 模型(OCR 2.0 方案,对中文文档识别准确率行业领先)+ DeepSeek Vision 识图模式(官方 API endpoint)。**推荐** + - **MiniMax**:有 MiniMax-VL 视觉模型,理论上可 OCR,但 OCR 专项优化不如 DeepSeek-OCR + - **小米 MiMo**:主推推理模型,未见明确的视觉/OCR 能力 + +3. **本场景的 OCR 难度评估**: + - 输入图像是**黑底白字纯字幕画面**——OCR 最简单的子集(高对比度、印刷体、无图案干扰、无版面分析) + - LLM 视觉模型在此场景几乎不会出错;用 LLM 做 OCR 在这里不是杀鸡用牛刀,因为成本极低且无新增依赖 + +4. **成本估算(23 期总量)**: + - 单期约 300 条字幕(demo 数据),23 期 ≈ 6900 张图 + - DeepSeek Vision API 单张成本约几厘到一分,**总成本预估 50–150 元** + - 对比讯飞 OCR SaaS 零售价:6900 张 × 0.198 元/张 ≈ 1370 元 + - 对比 PaddleOCR 本地:0 元但需要维护本地环境(已不是刚需) + +#### Q1.B 选项与推荐 + +**选项**: +- A. **DeepSeek API(视觉模型)**——制片人已有 API,无新增订阅 +- B. **讯飞 OCR**——与 STT 同账号但余额独立 +- C. **PaddleOCR 本地**——零成本但需本地环境 +- D. **主 project 已有的 OCR 服务**——如果存在 + +**子项目推荐**:A(DeepSeek API) + +**理由**: +- 满足制片人"用现有 API 资源"的方向 +- 黑底白字字幕是 LLM 视觉模型的舒适区,识别质量有保障 +- 与讯飞 STT 解耦,单点故障范围小 +- 成本最低(除 PaddleOCR)且无本地环境维护成本 + +**请主项目确认**: +- 主 project 是否已有 OCR 子模块/能力?(制片人初步判断"主 project 应该没有 OCR 配置",请顾问最终确认) +- 主 project 的密钥管理体系是否已经接入 DeepSeek?还是需要 Doco 子模块自行管理 DeepSeek 凭证? +- 6900 张图调用 DeepSeek 是否会影响主 project 其他模块的 DeepSeek 配额/限流? + +--- + +### Q2. 视频处理依赖(ffmpeg)的入口? + +**问题**:抽帧与抽音轨都依赖 ffmpeg。 + +**选项**: +- A. Doco 子模块自带 ffmpeg 系统依赖 + Python 封装(`ffmpeg-python` 或 subprocess) +- B. 主 project 有统一的视频处理服务/适配层 + +**子项目推荐**:A,使用 subprocess 调用系统 ffmpeg + +**理由**:ffmpeg 是标准工具,独立部署足够简单。 + +**请主项目确认**:是否已经有视频处理子模块/服务在规划中?如有,应改走中台接口。 + +--- + +### Q3. 23 期批量 vs 单期接口? + +**问题**:本次实际场景是 23 期,Doco 子模块的接口应该长什么样? + +**选项**: +- A. 子模块只负责单期,输入 = 1 视频 + 1 A 稿;批量调度交给主 project 编排层 +- B. 子模块内部支持批量,输入 = 视频目录 + A 稿目录 +- C. 两者都提供 + +**子项目推荐**:A(子模块只对单期负责) + +**理由**: +- 单一职责原则。批量调度涉及失败重试、进度跟踪、并发控制——属于编排层职责 +- 单期接口在出错时定位简单(哪期失败、失败在哪个 phase 一目了然) +- 主 project 未来可能有节目档案库流水线、报题单流水线等,统一在编排层做并发更合理 + +**请主项目确认**: +- 主 project 是否计划做"节目素材批量处理编排层"?如有,Doco 的单期接口应满足什么样的契约(CLI?Python API?返回值结构?) +- 23 期这次是先单期跑通再批量,还是要求一次性 batch? + +--- + +### Q4. 讯飞密钥更新/续费策略? + +**问题**:demo 凭证可能已过期(制片人提示"讯飞试用可能也过了"),且需要区分**消费端**(讯飞听见 App)与**开发端**(开放平台)。 + +**选项**: +- A. Doco 子模块各自申请新凭证、各自管理 +- B. 接入中台统一密钥服务(如果有) +- C. 申请新凭证 + 写入"中台密钥资产文档"作为登记 + +**子项目推荐**:C + +**理由**:现阶段中台密钥服务可能未完成,但密钥归属、过期日期、所属子模块应该有统一登记入口。 + +**请主项目确认**: +- 中台是否已有密钥管理子模块? +- 新申请的讯飞开发端密钥(包括 APP_ID、SECRET_KEY、5 小时免费试用包激活状态)应该登记在哪里? +- 如果 Q1 拍板用 DeepSeek,DeepSeek API key 是否也按同样规则登记? + +--- + +### Q5. 目录结构与命名规约? + +**问题**:23 期素材、抽帧产物、wav、终版 docx 的存储路径需要规约,否则后期归档混乱。 + +**子项目草案**: + +``` +programs/ +└── {episode_id}/ # 例:20260612_防空反导 + ├── source/ + │ ├── video.mp4 # 原始特殊视频(黑底白字+干净人声) + │ └── a_draft.docx # A 稿(编导定稿) + ├── work/ # 中间产物(可清理) + │ ├── frames/ # 抽出的字幕关键帧 + │ ├── audio_16k.wav # 抽音轨标准化产物 + │ ├── b_draft.txt # OCR 产物(带 [Nm Ns] 时间戳) + │ ├── asr_result.txt # 讯飞 ASR 产物 + │ └── asr_result_raw.json # 讯飞原始返回 + └── output/ # 主产物(入档案库) + ├── final.docx # 终版文稿 + ├── diff_report.docx # 差异报告 + └── needs_review.json # 待编导确认队列 +``` + +**请主项目确认**: +- TPS 中台是否对节目素材的目录有统一规约? +- `episode_id` 命名规则是什么?(日期+栏目+期号?UUID?编导给定?) +- `work/` 中间产物保留多久?是否要自动清理? + +--- + +## 五、不向主项目提问、但需要告知的事 + +1. **demo 的 5 份 fixture 仍然有效**:A 稿 / B 稿 / ASR / 终版 / 差异报告 在新流程下都可作为单元测试与集成测试的黄金对照 +2. **讯飞 ASR 脚本仍然有效**:`xfyun_asr_standard.py` 只需替换凭证、把"音频文件路径"参数从"独立 mp3"改为"视频抽音轨产物 wav" +3. **23 期之后的常态化问题**:栏目组以后是否每期都按"黑底白字+干净人声"特殊视频出素材?还是只这一批 23 期是特例?这影响 Doco 子模块要不要保留旧的 mp3+独立 OCR 流程作为回退路径。**建议主项目顾问直接跟栏目组确认** + +--- + +## 六、给主项目顾问的"接手提示词" + +如果主 project 顾问要审这份 PRD,最重要的三件事: + +1. **算法层零侵入是 demo 验证过的硬保证**。任何改动如果会让 P3(Doco 融合引擎)必须改算法,需要警惕——大概率是输入数据形态没对齐。 +2. **OCR 选型(Q1)和目录规约(Q5)是会广泛影响其他子模块的事**,主项目顾问视野更大,应该是这两条的最终拍板人。如果主 project 有 OCR 能力或 DeepSeek 集成基础设施,Q1 的子项目推荐随时可被覆盖。 +3. **23 期是一次性还是常态化(§5 第 3 条)是个产品问题,不是技术问题**,建议主项目顾问直接跟栏目组确认后再批 PRD。 + +--- + +## 七、回复期望 + +子项目这边等以下确认才正式开干: +- Q1(OCR 选型)—— 必须先答,否则 P1 不能开始 +- Q4(讯飞密钥)—— 必须先答,否则 P2 不能开始 +- Q5(目录规约)—— 必须先答,否则 P1 输出无处可放 +- Q3(接口契约)—— P5 之前必须答 +- Q2 —— Doco 子模块可以先按推荐方案开干,主项目随后批 + +收到主项目 Claude 顾问的回复 md 后,Doco 子项目这边出 PRD v3(含元信息块标注"上一版:v2"),再启动 P1。 + +--- + +## 八、版本变更摘要 + +| 版本 | 日期 | 主要变化 | +|---|---|---| +| v1 | 2026-06-12 | 首版。6 个待决问题(含子模块命名 Q6)。OCR 选型推荐 PaddleOCR 本地 | +| **v2** | **2026-06-12** | 子模块定名"Doco 文稿整理模块";Q1 改推荐 DeepSeek API(基于讯飞 OCR/STT 余额独立的事实查清 + 制片人手上已有 API 资源);Q6 已拍板移出待决列表;剩余 5 个待决问题 | + +--- + +*v2 由 Doco 子 project Claude(Opus 4.7)于 2026-06-12 拟稿,遵照《跨 project 协作提示单 v1》§三的规范。* diff --git a/docs/doco/doco_handoff_to_opus_chat.md b/docs/doco/doco_handoff_to_opus_chat.md new file mode 100644 index 0000000..442e6dc --- /dev/null +++ b/docs/doco/doco_handoff_to_opus_chat.md @@ -0,0 +1,103 @@ +# Handoff to opus_chat · TPS三方融合子模块 + +> 这份是发给中台 opus_chat 的最短交接说明。完整设计见 `project_design.md`。 + +--- + +## 这是什么 + +《军事科技》TPS(终版文稿生成中台)下的一个子模块,叫"三方融合"。 + +**它做的事**:把三份资料合并成一份贴近实际播出的终版 docx 文稿,并生成一份差异报告。 + +三份资料是: +1. **A 稿** — 编导手写定稿(docx,有【主持人N】【解说N】【专家N】【动画N】结构标签) +2. **B 稿** — OCR 扒词(txt,带 `[Nm Ns]` 时间戳,有 OCR 错字) +3. **ASR** — 讯飞录音文件转写结果(txt,带 `[Nm Ns]` 时间戳,有同音误识) + +--- + +## 你需要记住的 5 件事 + +### 1. 三方权威优先级(**最重要,不要搞反**) + +| 看什么 | 信谁 | +|---|---| +| 术语 / 人名 / 装备型号 / 事实 | **A 稿** | +| 标点 / 语序 / 口吻 | **ASR** | +| OCR 字形错(肘/时、娈/变) | 用 **ASR 或 A 稿** 覆盖 B 稿 | +| 整段是否存在 | **B 稿 + ASR 联合**判定 | + +为什么 A 稿术语优先?demo 中 ASR 把"美以两国"识别成了"美伊两国"——句子通顺、语义荒谬。无 A 稿无法发现。 + +### 2. 架构是双层(不是单层 AI) + +``` +规则层(Python) AI 层(Claude API) +├ OCR 错字映射 ├ 段落对齐 +├ A 稿术语字典覆盖 ├ 改动类型判定 +├ 时间戳对齐 ├ 整段改写边界 +├ 数字 / 标点规范化 └ 编导笔误识别 +└ 口语词清理(可配置) +``` + +确定性的事走规则层,需要语义判断的事走 AI 层。**不要全塞给 AI**。 + +### 3. 输入有严格规范 + +- **A 稿**:自然段开头 `【…】` 是结构标签;`隔断:【…】` 是章节分隔 +- **B 稿 / ASR**:每行格式 `[Nm Ns] 句子内容` +- **音频**(如果走完整流程从音频开始):必须 **16kHz / 单声道 / 16bit WAV**,否则讯飞拒收 + +### 4. 讯飞 ASR 有坑(开发前必读) + +- 用"**录音文件转写(标准版)**",**不要**用"大模型版"(免费包阉割了 `language` 参数) +- 领域参数 `pd=mil`(军事节目专用) +- 顺滑参数 `eng_smoothproc=true` + `eng_colloqproc=true` +- **热词机制**是关键:每期从 A 稿提 200 字以内的专业术语作为 `hotWord` +- 讯飞**消费端**(讯飞听见 App)和**开发端**(开放平台)密钥**不通用** +- "5 小时免费试用"必须走 0 元购买流程激活 +- 签名:`signa = base64(HmacSHA1(MD5(appid + ts), secretKey))` + +详见 demo 中的 `xfyun_asr_standard.py`。 + +### 5. 输出有两份,不只一份 + +- **终版.docx** — 主产物,给档案库 +- **差异报告.docx** — 副产物,给编导回顾。**差异报告的长期价值可能比终版更高**(能反向分析编导删改偏好) + +--- + +## 我和这个模块的关系 + +- **你(opus_chat)**:中台对话入口,可能会被用户问到本模块的边界 / 接口 / 状态 +- **本模块**:被 TPS 主流程调用,输入三份文本,输出两份 docx + 一份 JSON +- **不归我管**:音频转码、OCR 识别本身、档案入库、编导确认 UI + +如果用户问以下内容,**统一口径**: + +| 问题 | 答 | +|---|---| +| "能不能不用 A 稿?" | 不能。会引入 ASR 的同音事实错误,且术语全乱。 | +| "能不能不用 B 稿?" | 可以,但失去独立交叉验证;不推荐。 | +| "能不能不用 ASR?" | demo 证明 A+B 也能跑,但效果显著下降;不推荐。 | +| "ASR 选讯飞还是别家?" | 当前讯飞。已踩坑、已跑通;阿里云是规划中的备份。 | +| "AI 用哪个模型?" | Claude Sonnet 4.6 起步,单期 26 分钟节目约 16k 字上下文。 | +| "处理一期要多久?" | 正式版预估 15–30 分钟(含人工确认);demo 实测 2 小时但近一半花在踩讯飞坑上。 | + +--- + +## demo 状态 + +- 已完整跑通一期《现代防空反导大对决》(26 分钟) +- 关键术语识别率接近 100%(开启 35 条热词后) +- 主要修复:OCR 错字 10+ 类、ASR 同音误识 4 类、编导笔误 1 段、整段删除 2 段 +- 复盘文档:`实测复盘.md` +- 设计文档:`project_design.md` + +--- + +## 不一致时怎么办 + +如果你(opus_chat)发现本备忘与 `project_design.md` 不一致,**以 project_design.md 为准**。 +如果你发现 project_design.md 本身需要更新,请同步刘统制片组。 diff --git a/docs/doco/doco_project_design.md b/docs/doco/doco_project_design.md new file mode 100644 index 0000000..07da4c7 --- /dev/null +++ b/docs/doco/doco_project_design.md @@ -0,0 +1,451 @@ +# TPS · 三方融合子模块 设计文档(project_design.md) + +> 归属:《军事科技》中台 TPS · 终版文稿生成(Three-source Pipeline Synthesis) +> 文档目的:把 demo 阶段的实测结论沉淀为可被中台其他子模块(含 opus_chat)调用的明确接口与约束 +> 当前状态:demo 已跑通一期完整节目(《现代防空反导大对决》),进入正式开发阶段 +> 文档版本:v0.1(2026-05-15) +> 维护:刘统制片组 / Claude + +--- + +## 0. TL;DR(给 opus_chat 的最短摘要) + +- 本子模块的输入是三份资料,输出是一份贴近实际播出的 docx 文稿 + 一份逐段差异报告。 +- **三份输入:** + 1. **A 稿**:编导手写定稿(docx,带【主持人N】【解说N】【专家N】【动画N】【隔断:…】结构标签) + 2. **B 稿**:OCR 扒词文档(txt,带 `[Nm Ns]` 句级时间戳,存在 OCR 错字) + 3. **ASR 结果**:讯飞录音文件转写(txt,带 `[Nm Ns]` 句级时间戳,存在同音误识) +- **核心信念**:三方各有短板,单源不可信;融合后才是"播出真相"。 + - A 稿 → 术语 / 人名 / 装备型号 / 事实 的**权威** + - ASR → 播出语序与口吻的**骨架** + - B 稿 → 独立**交叉验证**(同时验证 A 与 ASR) +- **架构两层:** + - **规则层**(Python):术语字典纠正、OCR 错字映射、时间戳对齐、口语词清理、数字规范化 + - **AI 层**(Claude API):段落对齐、改动类型判定、整段改写边界、编导笔误识别 +- **请 opus_chat 注意:** 本模块依赖讯飞 ASR 与 Claude API,**两套密钥**互不复用;调用本模块前应确认 A 稿术语表已入库。 + +--- + +## 1. 业务背景与定位 + +### 1.1 上游 / 下游 + +``` +[节目编导] ──A稿(docx)──┐ + │ +[OCR 服务(旧流程)]──B稿(txt+时间戳)──┐ + ├──► [本模块 三方融合] ──► [终版docx] ──► [节目档案库] + │ └► [差异报告docx] ──► [编导回顾 / 训练素材] +[节目纯净声音文件]──[讯飞 ASR]──ASR(txt+时间戳)─┘ +``` + +### 1.2 在 TPS 中台的位置 + +本子模块是 TPS(终版文稿生成中台)的**核心算法子模块**,位于上游"素材接收"和下游"档案入库 / 编导确认 UI"之间。 + +- 它**不**负责音频转码(由 ASR 适配层处理) +- 它**不**负责 OCR 识别(B 稿来自既有 OCR 流程) +- 它**不**负责最终入库(由档案子模块负责) +- 它**只**负责"三方文本 → 一份贴近播出的终版文稿 + 差异报告" + +### 1.3 为什么不能省略某一方? + +| 缺哪一方 | 后果 | 实例 | +|---|---|---| +| 缺 A 稿 | 术语 / 人名 / 装备型号易错;ASR 同音错难发现 | "美以两国"被 ASR 识别成"美伊两国"——句子通顺、语义荒谬,无 A 稿无法纠正 | +| 缺 ASR | 无法知道实际播出语序与口吻;专家口语词全丢 | A 稿用"观众朋友们,你们好",播出实际是"各位观众你们好" | +| 缺 B 稿 | 失去独立交叉验证;ASR/A 稿冲突时无第三方仲裁 | A 稿与 ASR 在某段不一致时,B 稿与谁一致就以谁为准 | + +**结论**:B 稿可以视为弱必需(demo 证明 A+ASR 二方融合也能跑),但建议保留 B 稿做 sanity check。 + +--- + +## 2. 输入规范 + +### 2.1 A 稿(编导定稿) + +- **格式**:`.docx`(实际为 markdown 风格的纯文本,也兼容真 docx) +- **结构标签**(出现在自然段开头,方括号包裹): + - `【导视】` `【主持人N】` `【解说N】` `【动画N】` `【专家N】` + - `隔断:【…】` 用于章节分隔 +- **特点**: + - 内容偏书面化;可能存在编导笔误(如本期【专家7】【专家8】内容字符完全相同 = 复制粘贴失误) + - 术语严格、人名 / 装备型号完整("爱国者-3"、"霍拉姆沙赫尔-4"、"见证者-136") + - 标点 / 顿号 / 引号规范 + +### 2.2 B 稿(OCR 扒词) + +- **格式**:`.txt` +- **行格式**:`[Nm Ns] 句子内容`,每行一句 +- **特点**: + - 句子时间戳精度到秒 + - **存在 OCR 错字**:字形相近字混淆(肘↔时、娈↔变、夭↔天、井↔并、末↔未、纺↔防、观↔现、忽↔您、岳↔越、改命↔致命) + - 标点弱(多为空白分隔,缺逗号 / 句号) + - 句子切分较碎(一句话常被切成 2–3 行) + +### 2.3 ASR(讯飞录音文件转写) + +- **格式**:`.txt` +- **行格式**:`[Nm Ns] 句子内容`,每行一句(与 B 稿对齐) +- **特点**: + - 标点合理(讯飞已自动加句号、逗号、问号) + - 经 `eng_smoothproc + eng_colloqproc` 处理,已**部分**清理"嗯/呃" + - **存在同音误识**:"中端↔中段"、"末端↔末段"、"中诚↔中程"、"美伊↔美以"、"高超声速↔高超音速" + - 数字 / 百分比识别准确 + - 句子级时间戳精确,可与 B 稿做交叉对齐 + +### 2.4 三方时间戳的对齐口径 + +A 稿无时间戳;B 稿与 ASR 都有 `[Nm Ns]` 但**不严格一致**(B 稿来自 OCR 字幕显示时点,ASR 来自语音波形起点;常相差 1–3 秒)。融合时**容忍 ±5 秒**的时间漂移作为同段判定的辅助条件。 + +--- + +## 3. 输出规范 + +### 3.1 终版文稿(主产物) + +- **格式**:`.docx` +- **结构**:保留 A 稿的段落标签(【导视】【主持人1】…) +- **内容来源策略**: + - 默认以 ASR 文本为骨架 + - A 稿术语 / 人名 / 装备型号回填 + - B 稿 OCR 错字修复(用 ASR / A 稿覆盖) + - 标点采用 ASR 的版本(最自然) + - 口语词处理强度由"清理档位"参数控制(见 §5.3) + +### 3.2 差异报告(副产物) + +- **格式**:`.docx` +- **内容**: + 1. 改动总览(段落数 / 改动类型分布) + 2. 逐段对照(A 稿原文 vs 终版,标注改动类型与依据) + 3. 关键差异专题:整段删除 / 整段改写 / 术语变化 / 编导笔误 / OCR 错字修复 / ASR 错字修复 / 口语词处理 + 4. 依据图例(三方一致 / ASR 为主 / A 稿权威 / …) +- **价值**:差异报告本身就是编导工作回顾,长期累积可分析编导删改偏好。 + +### 3.3 改动类型枚举 + +| 枚举值 | 含义 | 处置 | +|---|---|---| +| `unchanged` | 三方一致或语义等价 | 直接采用 A 稿 | +| `minor_edit` | 标点 / 顿号 / 简称差异 | 自动以 ASR 版本输出 | +| `term_normalize` | 术语 / 字形 / 同音修正 | 规则层处理 | +| `rewrite_large` | 整段重写(A 稿与播出版差异较大) | AI 层处理 + **建议编导确认** | +| `segment_delete` | A 稿有但播出无 | AI 层判定 + **建议编导确认** | +| `segment_add` | A 稿无但播出有 | AI 层判定 + **建议编导确认** | +| `editor_typo` | A 稿编导笔误(整段重复 / 明显错位) | AI 层提示 + **必须编导确认** | + +--- + +## 4. 模块架构 + +### 4.1 顶层流程 + +``` + ┌───────────────────────────────────────────────────┐ + │ 三方融合引擎(本子模块) │ + │ │ +A 稿 ──────►│ 解析层 ──► 段落对齐层 ──► 规则纠错层 ──► AI 融合 │──► 终版docx +B 稿 ──────►│ ↓ │──► 差异报告 +ASR ──────►│ [Claude API] │ + └───────────────────────────────────────────────────┘ + ▲ + │ + [A 稿术语表 / OCR 错字字典 / 口语词字典] +``` + +### 4.2 各层职责 + +#### 解析层(parser) +- A 稿:按 `【…】` 切段,识别隔断标签,输出 `[(label, text)]` +- B 稿 / ASR:按 `[Nm Ns]` 行切句,输出 `[(start_ms, end_ms, text)]` + +#### 段落对齐层(aligner) +- 目标:把 B 稿 / ASR 的句子聚合并归到 A 稿的段落标签下 +- 算法: + - **强信号**:通过 A 稿首末锚点句做语义匹配(demo 用了人工锚点列表,正式版应用 Claude API 做语义匹配) + - **弱信号**:时间戳的单调推进 +- 容错:允许 ±5 秒漂移、允许中间出现专家发言被识别为"解说" + +#### 规则纠错层(rule-based corrector) +- **A 稿术语字典**:从 A 稿提取专有名词,扫描 ASR / B 稿,发现字形不同但拼音相同的同音字 → 用 A 稿覆盖 +- **OCR 错字字典**:维护固定映射表(肘→时、娈→变、夭→天…),扫描 B 稿 +- **数字规范化**:`百%` → `100%`、`数10` → `数十`、`马赫数10` → `10马赫` +- **标点规范化**:去重逗号、补齐句号 + +#### AI 融合层(Claude API) +- 输入:经规则层处理后的三方对齐结果 +- 任务: + 1. 段落对齐的最终校验(解决规则层算不准的边界) + 2. 改动类型标记(`unchanged` / `minor_edit` / `rewrite_large` / `segment_delete` / `segment_add` / `editor_typo`) + 3. 高把握改动直接产出终版 + 4. 低把握改动标记原因 + 给编导待确认列表 +- 提示词要点:见 §6 + +### 4.3 双层划分的依据 + +| 任务 | 走哪层 | 理由 | +|---|---|---| +| OCR 错字纠正 | 规则层 | 字形错误是确定性映射 | +| 术语统一 | 规则层 | A 稿术语表是封闭集 | +| 时间戳对齐 | 规则层 | 数值比较 | +| 段落归属 | AI 层 | 需要语义判断("这句专家发言到底归【专家3】还是【解说5】") | +| 整段删除判定 | AI 层 | 需要判断"A 稿这一段,是否在 B 稿 + ASR 都找不到对应内容" | +| 编导笔误识别 | AI 层 | 需要"【专家7】【专家8】内容相同" + "ASR 第 N 句到第 M 句讲了完全不同的内容"的联合判断 | + +--- + +## 5. 关键决策与参数 + +### 5.1 三方冲突时的优先级 + +| 冲突类型 | 优先采用 | 例 | +|---|---|---| +| 术语 / 人名 / 装备型号 | **A 稿** | "美以两国"(ASR 错识"美伊") | +| 标点 / 句式 / 口吻 | **ASR** | "各位观众你们好"(A 稿"观众朋友们,你们好") | +| 字形错字 | **ASR / A 稿** 覆盖 B 稿 | "肘→时""纺空→防空" | +| 整段存在性 | **B 稿 + ASR 联合** 优先 | A 稿"雷达被誉为眼睛..."B 稿/ASR 都没有 → 删 | +| 数字 / 百分比 | **ASR**(已确认 ASR 100% 准确) | "5%"、"100%" | +| 同音 / 字形错 | **A 稿术语字典** | "中端→中段""高超声速→高超音速" | + +### 5.2 时间戳容忍度 + +- 同段判定:B 稿 / ASR 时间戳差 ≤ **5 秒** 视为同一播出位置 +- 锚点匹配:A 稿首句对应的 ASR 时间位置 ± **10 秒** 为搜索窗 + +### 5.3 口语词清理档位(可配置) + +| 档位 | 行为 | 适用场景 | +|---|---|---| +| `keep_all` | 完全保留"那么""呢""啊""这个" | 反映播出真相、用于事后复盘 | +| `medium`(**默认**) | 保留专家段口语词,清理解说段 | 平衡可读性与真实性 | +| `clean` | 全部清理,按 A 稿书面化 | 用于外部传播 / 文稿出版 | + +### 5.4 改动确信度阈值 + +- AI 判定结果带 `confidence ∈ [0,1]` +- `confidence ≥ 0.85`:自动应用,记入差异报告 +- `0.6 ≤ confidence < 0.85`:自动应用 + **黄色标记**,编导可在 UI 中翻看 +- `confidence < 0.6`:**不自动应用**,进入编导待确认队列 + +--- + +## 6. AI 融合层提示词设计纲要 + +提示词应包含以下要素(正式实现时按需扩展): + +``` +[系统角色] +你是《军事科技》节目终版文稿生成助手。已知: +- A 稿是编导手写定稿,术语 / 人名 / 装备型号权威 +- ASR 是讯飞录音转写,标点与口吻接近播出真相,但存在同音误识 +- B 稿是 OCR 扒词,存在字形错字,用作交叉验证 +三方冲突时按"§5.1 三方冲突优先级"裁决。 + +[输入] +A 稿全文(含 【…】 标签):{a_doc} +ASR 结果(带时间戳):{asr_sentences} +B 稿 OCR(带时间戳):{b_sentences} +A 稿术语表:{terms_dict} +OCR 错字字典:{ocr_typos} +口语清理档位:{level} + +[任务] +1. 把 ASR / B 稿的句子按时间顺序聚合并归到 A 稿的段落标签下 +2. 对每段标记改动类型(unchanged / minor_edit / rewrite_large / segment_delete / segment_add / editor_typo) +3. 对每段给出 confidence ∈ [0,1] +4. 对 confidence ≥ 0.85 的段落直接产出终版文本 +5. 对 confidence < 0.85 的段落产出"建议版本 + 待确认原因" +6. 识别 A 稿与 ASR 的术语 / 同音 / 字形差异,自动应用 A 稿权威 +7. 识别 A 稿的编导笔误(如整段复制粘贴)并标记 + +[输出] +JSON: +{ + "segments": [ + { + "label": "【主持人1】", + "change_type": "rewrite_large", + "confidence": 0.92, + "final_text": "...", + "evidence": {"asr_lines": [...], "b_lines": [...]}, + "reasons": ["A 稿首句被改写为口语化"] + } + ], + "needs_review": [...] +} +``` + +--- + +## 7. 外部依赖 + +### 7.1 讯飞 ASR(必需) + +- **服务**:讯飞开放平台「**录音文件转写(标准版)**」 +- **不要用**:讯飞「大模型版」——免费包阉割了 `language` 参数,会报误导性的 `language verify fail` +- **认证**:APP_ID + SECRET_KEY;本期 demo 已申请到一组测试凭证(见 `xfyun_asr_standard.py`,正式上线需替换) +- **音频规格**:**16kHz / 单声道 / 16bit WAV**;其他规格讯飞会拒收 +- **领域参数**:`pd=mil`(军事节目专用,提升术语识别率) +- **顺滑参数**:`eng_smoothproc=true` + `eng_colloqproc=true` +- **热词机制(关键)**: + - 每期节目从 A 稿提取专业术语(武器装备 / 技术概念 / 人名 / 军方机构),上限 200 个,每个 2–16 字 + - 用 `|` 分隔传入 `hotWord` 参数 + - demo 实测 35 个热词后,关键术语识别率接近 100% +- **计费**:0元购买"5 小时免费试用包"(注意:看到额度但未走 0 元购买流程**不会激活**),1 年有效期 +- **签名算法**:`signa = base64(HmacSHA1(MD5(appid + ts), secretKey))` + +详见 `xfyun_asr_standard.py`(demo 已跑通的本地脚本,可直接作为正式适配层的起点)。 + +### 7.2 Claude API(必需) + +- 用于 AI 融合层 +- 调用模型:建议 `claude-sonnet-4-6` 或更高(涉及长文本理解与语义对齐) +- 单期节目(26 分钟)的预估上下文:A 稿 ~6k 字 + ASR ~5k 字 + B 稿 ~5k 字 ≈ 16k 字,远在 Sonnet 上下文窗口内 +- 调用频次:每期节目 1–3 次(首次全量融合 + 若干次低确信段重判) + +### 7.3 OCR 服务(已有) + +- B 稿来自上游既有 OCR 流程 +- 本模块只消费 B 稿 txt,不调用 OCR 服务 + +### 7.4 密钥管理 + +> **opus_chat 注意**:以下是常见混淆点,请勿用错。 + +| 密钥 | 用途 | 来源 | +|---|---|---| +| 讯飞 **消费端** | 讯飞听见 App(人工录音转写) | 讯飞听见 App 内 | +| 讯飞 **开发端** | 录音文件转写 API(本模块用) | 讯飞**开放平台** console | +| Claude API | AI 融合层 | Anthropic console | + +三套密钥**互不相通**。 + +--- + +## 8. 编导确认 UI(属于上层 UI 子模块,本模块只提供数据) + +本模块按以下结构输出供 UI 子模块消费的 JSON: + +```json +{ + "program_id": "xxx", + "segments": [...], + "needs_review": [ + { + "segment_label": "【解说6】", + "reason": "整段改写边界不确定", + "candidates": [ + {"text": "...", "source": "A稿"}, + {"text": "...", "source": "ASR"} + ] + } + ], + "diff_report_path": "...", + "final_docx_path": "..." +} +``` + +UI 子模块应至少提供(参考 demo 复盘 §3.4): + +- **左中右三栏 diff 视图**:A 稿原文 | 终版 | ASR/B 稿(带时间戳,可点击播放对应音频) +- **改动类型颜色编码**:删除红 / 改写黄 / 术语修正蓝 / 无变化白 +- **逐段确认按钮**:接受 / 拒绝 / 编辑 / 标记疑问 +- **口语清理强度滑杆**:切换 `keep_all / medium / clean` +- **导出按钮**:终版 docx / 差异报告 / 复盘 CSV + +--- + +## 9. 数据资产 + +### 9.1 应入库的"中台共享资产" + +1. **A 稿术语表**(核心资产) + - 每期节目处理时从 A 稿提取后入库 + - 同时作为讯飞热词来源 + 规则层纠错字典 + - 字段:`term`, `domain`(武器 / 技术 / 人名 / 机构 / 栏目),`first_seen_episode`, `frequency` +2. **OCR 错字映射表** + - 固定字形混淆对(肘↔时、娈↔变、夭↔天…) + - 随版本演进可扩展 +3. **节目编导风格档案** + - 长期累积每位编导的删改偏好(口语词保留度、术语简称习惯…) + - 用于参数自适应 + +### 9.2 demo 阶段产物的处置建议 + +| 文件 | 处置 | +|---|---| +| `xfyun_asr_standard.py` | 作为 ASR 适配层的起点代码 | +| `asr_result_timed.txt` | 单元测试 fixture | +| `B稿_..._扒词.txt` | 单元测试 fixture(含典型 OCR 错字) | +| `A稿_..._定稿.docx` | 单元测试 fixture | +| `现代防空反导大对决_终版.docx` | 集成测试**期望输出**(黄金对照) | +| `差异报告.docx` | 集成测试**期望输出**(差异报告格式参考) | +| `实测复盘.md` | 设计文档背书 + 长期归档 | + +--- + +## 10. 性能与耗时基线(实测) + +来自 demo(26 分钟节目): + +| 阶段 | 耗时 | 占比 | +|---|---|---| +| 讯飞 ASR 转写 | ~5 分钟 | — | +| 三方解析 | ~5 分钟 | — | +| 段落对齐 | ~15 分钟(demo 手工) | 正式版预计 ≤2 分钟(AI 调用) | +| 术语规范化 + 口语清理 | ~5 分钟 | — | +| 终版 docx 生成 | ~5 分钟 | — | +| 差异报告生成 | ~15 分钟(demo 手工) | 正式版预计 ≤3 分钟 | +| **正式版预估单期** | **~15–30 分钟** | 取决于改动幅度与人工确认次数 | + +--- + +## 11. 已知风险与未决事项 + +| 项 | 当前态 | 建议 | +|---|---|---| +| ASR 第三方依赖 | 讯飞单源 | 接入阿里云 ASR 做备份 / 交叉验证 | +| 整段改写的边界 | demo 中默认取 ASR 短版 | 必须接入编导确认 UI,不能全自动 | +| 编导风格差异 | 硬编码"中度清理" | 需配置文件或编导画像 | +| 隔断标签的处置 | demo 直接保留 A 稿 | 待确认是否需要根据音频静音段重新切分 | +| A 稿与 ASR 都缺失的旁白 | 不处理 | 极小概率事件,靠 B 稿兜底;记录用例 | + +--- + +## 12. 给 opus_chat 的提示(重要) + +如果 opus_chat 在其他场景中被问到本模块,请按以下口径回答: + +1. **本模块的边界**:只做"三方文本 → 终版文稿 + 差异报告",不负责音频转码、不负责 OCR、不负责档案入库、不负责编导 UI。 +2. **依赖与约束**:依赖讯飞 ASR(标准版)和 Claude API;A 稿术语表必须先入库;音频规格必须 16kHz/单声道/16bit WAV。 +3. **三方权威优先级**:A 稿(术语)> ASR(口吻)> B 稿(验证);任何与此相反的处理都是 bug。 +4. **未来扩展**:编导风格画像、第二 ASR 供应商、口语清理档位是后续路标。 + +如遇到与本文档不一致的描述,**以本文档为准**;如发现本文档需要更新,请同步刘统制片组。 + +--- + +## 附录 A:项目文件清单(demo 阶段) + +``` +project/ +├── A稿_现代防空反导大对决_定稿.docx # 输入1:编导定稿 +├── B稿_现代防空反导大对决_扒词.txt # 输入2:OCR扒词 +├── asr_result_timed.txt # 输入3:讯飞ASR输出 +├── xfyun_asr_standard.py # ASR调用脚本 +├── 现代防空反导大对决_终版.docx # 输出1:终版文稿 +├── 差异报告.docx # 输出2:差异报告 +├── 实测复盘.md # demo复盘(设计文档原料) +└── project_design.md # 本文档 +``` + +## 附录 B:与"实测复盘.md"的对应关系 + +| 复盘章节 | 本文档对应位置 | +|---|---| +| 一、ASR环节实测数据 | §7.1 讯飞 ASR | +| 二、三方融合关键决策点 | §5 关键决策与参数 | +| 三、对正式开发的启示 | §4 模块架构 / §6 提示词设计 / §8 UI | +| 四、实测耗时分解 | §10 性能基线 | +| 五、给制片人的建议 | §9 数据资产 / §11 风险 | diff --git a/docs/doco/doco_xfyun_integration_notes.md b/docs/doco/doco_xfyun_integration_notes.md new file mode 100644 index 0000000..48a3b07 --- /dev/null +++ b/docs/doco/doco_xfyun_integration_notes.md @@ -0,0 +1,181 @@ +# 讯飞 ASR 集成踩坑笔记 + +> demo 期间累积的所有讯飞接入坑点。建议入 TPS 知识库,作为未来接入新 ASR 供应商时的对照。 + +--- + +## 选型坑 + +### 坑 1:不要用"大模型版" + +- 讯飞开放平台有两个录音转写产品: + - 「录音文件转写(**标准版**)」← **用这个** + - 「录音文件转写(**大模型版**)」← 不要用 +- **大模型版的免费包阉割了 `language` 参数**,调用时会报 `language verify fail` +- 这个错误信息**严重误导**:看上去是语种没设对,实际是免费包没开通该参数权限 +- 排查耗时:约 30 分钟 + +### 坑 2:消费端和开发端密钥不通 + +- 「讯飞听见 App」属于**消费端**,给最终用户做人工转写用 +- 「讯飞开放平台」属于**开发端**,给开发者调 API 用 +- **两套密钥互不相通**——开发端的 APP_ID 在消费端没用,反之亦然 +- 如果只在讯飞听见 App 注册了账号,**没有** APP_ID 和 SecretKey,必须再去开放平台注册一次 + +### 坑 3:免费额度不会自动激活 + +- 开放平台首页显示"5 小时免费试用包",但**只有走 0 元购买流程**才会激活 +- 看到额度但没下单 → 调用时会报余额不足 +- 流程:商品页 → 0 元下单 → 支付(实际免费)→ 额度到账 + +--- + +## 音频规格坑 + +### 坑 4:必须是 16kHz / 单声道 / 16bit WAV + +- 其他规格讯飞会报错(且错误信息可能误导成"language verify fail"或"audio decode error") +- MP3 在标准版**也支持**,但保险起见统一转 WAV +- 转码命令(ffmpeg): + ```bash + ffmpeg -i input.mp3 -ac 1 -ar 16000 -sample_fmt s16 output_16k_mono.wav + ``` + - `-ac 1` = 单声道 + - `-ar 16000` = 16kHz 采样率 + - `-sample_fmt s16` = 16bit 位深 + +### 坑 5:时长字段不严格校验但建议传准 + +- API 要求传 `duration`(毫秒) +- 实测哪怕传 0 也能上传成功,但官方文档建议传准确值 +- WAV 从文件头读:`n_frames / sample_rate * 1000` +- MP3 用 `mutagen.mp3.MP3` 读 + +--- + +## 接口坑 + +### 坑 6:签名算法是老式 HmacSHA1,不是常见的 HmacSHA256 + +```python +# signa = base64(HmacSHA1(MD5(appid + ts), secretKey)) +md5_str = hashlib.md5((appid + ts).encode("utf-8")).hexdigest() # 32 位小写 hex +mac = hmac.new(secret_key.encode("utf-8"), md5_str.encode("utf-8"), + digestmod=hashlib.sha1) +signa = base64.b64encode(mac.digest()).decode("utf-8") +``` + +注意: +- MD5 输出是**32 位小写 hex 字符串**,不是 16 字节 raw bytes +- HMAC 的 key 是 `secretKey`,data 是 MD5 字符串本身(不是其字节) +- 最后做 base64 编码 + +### 坑 7:参数全部走 URL query,不是 form-data 或 JSON body + +- 上传接口:`POST https://raasr.xfyun.cn/v2/api/upload?appId=...&signa=...&ts=...&...` +- `Content-Type: application/json`,但**请求体是音频原始字节流**(不是 JSON) +- 所有业务参数(`fileSize` `fileName` `duration` `language` `pd` `hotWord` …)都在 URL query 里 +- 中文热词必须做 URL encode:`quote(hot_word_str, safe='')` + +### 坑 8:热词用 `|` 分隔,不是逗号 + +```python +hot_word_str = "|".join(HOT_WORDS) # 萨德|爱国者|标准-6|... +params["hotWord"] = hot_word_str +``` + +限制: +- 最多 200 个热词 +- 每个热词 2–16 字 +- 整个 hotWord 参数 URL encode 后不能超过某个长度(实测 35 个常规军事术语没问题) + +--- + +## 异步查询坑 + +### 坑 9:轮询用 POST 不是 GET + +- 查询结果接口:`POST https://raasr.xfyun.cn/v2/api/getResult?...` +- 即便没有 body 也是 POST +- 用 GET 会报方法不允许 + +### 坑 10:status 状态码语义 + +| status | 含义 | +|---|---| +| 0 | 待处理 | +| 1 | 处理中 | +| 4 | **完成** | +| -1 | 失败 | + +完成是 `4`,不是 `2` 也不是 `200`。 + +### 坑 11:轮询间隔与超时 + +- 26 分钟音频实测转写耗时约 5 分钟 +- 推荐轮询间隔 30 秒(taskEstimateTime 返回的预估值仅供参考) +- 超时上限设 30 分钟比较保险 + +--- + +## 结果解析坑 + +### 坑 12:返回结果是双层嵌套 JSON 字符串 + +```python +# 外层 +data = response.json() +order_result_str = data["content"]["orderResult"] # 这是 JSON 字符串 + +# 中间还有一层反斜杠转义 +cleaned = re.sub(r"\\\\", r"\\", order_result_str) +outer = json.loads(cleaned) + +# 句子在 outer["lattice"] 里 +for item in outer["lattice"]: + inner_str = item["json_1best"] # 又是 JSON 字符串 + inner = json.loads(inner_str) + # 句子时间在 inner["st"]["bg"] / inner["st"]["ed"] + # 句子内容在 inner["st"]["rt"][..]["ws"][..]["cw"][..]["w"] +``` + +每个 `cw` 是一个字 / 词;要拼接所有 `cw["w"]`(跳过 `wp == "g"` 的空白标记)。 + +### 坑 13:时间戳单位是毫秒(不是秒、不是百分秒) + +- `bg` `ed` 都是毫秒(int) +- 与 B 稿的 `[Nm Ns]` 对齐时记得换算 + +--- + +## 业务参数坑 + +### 坑 14:`pd=mil` 对军事术语显著加分 + +- 不开 `pd`:默认通用领域,军事术语会被识别成同音常见词 +- 开 `pd=mil`:军事领域专用模型介入,配合热词效果叠加 + +### 坑 15:`eng_smoothproc` + `eng_colloqproc` 让 ASR 接近书面 + +- `eng_smoothproc=true`:去掉"嗯""那个""呃" +- `eng_colloqproc=true`:口语规整("咱们"→"我们"等) +- 两个都开后,输出已经非常接近书面文稿 +- 但**不会清理"那么""呢""啊""这个"**——这些保留与否需要在融合层决定 + +--- + +## 凭证与配额(demo 阶段使用) + +``` +APP_ID: 84eff996 +SECRET_KEY: d37fb0c77e7f4b4f4398e6a8834d1017 +免费包: 5 小时(已激活),有效期至 2027 年 5 月 +``` + +> ⚠️ **正式上线时替换为生产凭证**,并把 SECRET_KEY 从代码移到环境变量 / 密钥管理服务。 + +--- + +## 一句话总结 + +讯飞 ASR 标准版**能用、好用**,但接入文档稀少且错误信息误导性强;以上 15 个坑全部踩完后,单次调用的成功率接近 100%。后续接入新 ASR 供应商时,建议按本笔记的结构(选型 / 音频 / 接口 / 异步 / 解析 / 业务参数 / 凭证)做同样的整理。 diff --git a/docs/doco/golden test/现代防空反导大对决 定稿.docx b/docs/doco/golden test/现代防空反导大对决 定稿.docx new file mode 100644 index 0000000000000000000000000000000000000000..cce8e45e61c65b8bb9d44d97fe7ae85c94f2244d GIT binary patch literal 32677 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 L{^a%g`QQHm=Nr?_ literal 0 HcmV?d00001 diff --git a/docs/doco/golden test/现代防空反导大对决_终版.docx b/docs/doco/golden test/现代防空反导大对决_终版.docx new file mode 100644 index 0000000000000000000000000000000000000000..9708fbfd2dfa53a6572193d5eb8173cb9e4df8c0 GIT binary patch literal 46389 zcmZ6xV{|4#*EX7DV%xSev2EM7F|lo%cWiTFn-kl1GQl0=%=5kHJL`SUpYC3}_TF`^ zjqd8IOG)+zI64Rj2s8-0Z>?UfN@-FWC)DTRvx&dgx0O;?RP0(`AD~%G`lH;pc)N)4#)9)$Jc&elRqBvnX#}#N9pH;I%K`fH zuwjVD5H;*pq(CP)P3sjTo~kxM1C?kr3|fVM5+bs`c%HMco6Jc~u zI>3YURWwjM?{kfjQX|(k2evbaD>KUIuewCIQA_f$OQZV?U9+>L`md(Vfl@@zQ8-8B zg(LfZMvo~?)(gm{%GgI$ogGx0u%l~7+fd4e#j_uIn%@q*1(%x|bXMIu+VN_B))ygC zxZcmAh=9xfU>d4s68%)jY?khXTHny;q336s(gbpW*R@E4=cPq?`)T#Sainl|b-_lJ zF5!gq307{-?_U?9q<+AHP~CNIF$KSFF++apAfaKOmpjDw6Jv-C>=*c;x@W16p0Z=N z&EO(aIr?-O-On_F5|q9q<9DAoxbkWoF@b@P)$;9Q!O3URqIYz80}!SE%AyygtkBE9 z?CpSofWZB`8#!guBFRx3pyR9JBY+n?I2X! zE;|NDhA3oC`OIOVK`cPGgEpc|(|j{8Tqh!CNNX>JSP@#8g7OIGO0DVU$FS%K=_n!B z${t-_+i_&)5&a_A>MCUvD0GqNgrb_IV8)EaRe~t}3++)<`gAtFDzO8A+4v&>gm$#eBVa-S)#YEIEuQ zBI!2BEE-N5<7?9dbL%}%oR+&bEp+g<|84P}(vrt?vOh){7aq1D?}u~_v%cI)BoEne z6Fb{d_#cwoc9pWiGZ9r3`SoxOp=9b#G6dpdbei*^Ai}5=vgBrz%C<5>5CBR=A!t+&Ue_gR>o7X57tIgiY6m&NtJ0<^X>J9fZ&;$q^%!6M#8& z10)OnwfidsJQ66YkaCuaN4o~gUi7t#^RL}m_aFMz@NE(Bi0L?m^VbzMLcxX5Te|K$ zj>DymV@pd1TMRA~fr74P?W_%GpRH3|R2jzpfbBxWF%N#r2$*J-hb!iadQ*v@?T!;z(GKe z|94)Dot*wpTGXZ-Hh&^_zte&1yGC0>2`ghK2d@avi*DmKBm>%(CP`(Iz&n@^{QX`d zF}TB-fKj~7yzecO@2kQVP%R2p>KIe6MdM1pwqbwQM~|Is&YY6EfLB3OVrYz@=>IaD zovdAkWPowa(p0LO^dW6FnZ6^0a&l3mn|0kl6xwO=DeF>j@}2uI7ZSg6Z+sd4o2K-8 zZ6{Otr($&VkI#CmYOjh}es;-2;DvU28UmsOnDHko_>@<*HT8O=1eQE89_7{L3)x5teDmo{;|>P{OG?_l9~C1S`7bvYA-5 zsZulYKJGfPPfK{LfSditrzmyo!~$8Gjj`jvBTBwj(AKZS_NV4b-b6&%Cq(c!UXisA zOflLGQ79dP#FW-7w1CD*$A>FUF8Gw%`yX1M(f3F1C%Fx1sY2HH>zyd%et+f##2cp? zs$~usYe@L(Mybft3EoYJ@aRlwZZuC%MOSt76?Ee;^4W1w+$r|M{5wF89jPEvZTYd4 zB;9omh@P!*Kam;l?3qvZUfuZNE<7N){M(yh2s5r>>^>)ArwD`E?RGQL1ZFgM$$29H zSn57@ib)y6o6>@%ijqCuIZPi+gnwajULWwjmw{JA|6Lf_0+2VX)F2?$R{ztm9*)jt zO#iyp&HmrV=|7b*-m6K}!ifXDj{bc7cJtV<3+AJuPZm!Fk zTv1@=<3q#)lH8sQ0*%@YM(zm;V*r{6F1&STxKhCbrT7MyV`z~(NFvpBegmU0>pG6^ zsZ>r&$7JZArT!KX@F6&x-vc~Mnb1_EGVuMndGRs5?FAl<97Z|j^}gIZczi$ieeQeY zcfYSxuO=AyKAf!&$57?>y_Zwf?|hCieQgSUJg#%1B?NeW9gSSJS%qTN3%s8nAI|Q6 zeE}~s@_W9&s>gN(y}r(teP;6o@&KOfch{HCgo=Ct0SB9o`2zm;Uy5icyWQVLcD}Iz z-rU@GFB8?8kDa@M-gk$^nvOa9d5T;1EvE^DK7s<+c<-TKUyoq~hbOU=1_EBfeIM>7 z_pimPkE;{6XLu>!cSBRV^O;OUJ^pXNMN37)Zx~blmZ6WA*G)mW-k0gvJ6>KS-&5A? zeoH}tfD?YcJKNbwv6+BwN4XvILcm9C^=!k?Pz0Ui``HdV)Cr*B;LC zdd9W3wS0s4fR{G=H$g>#x2P{x{J)Hg-x9r#A6e5MtCILcT|@Ck(C=|wa{F(*Jy|bl zx8;2bZ2tTH&utS=2`OlT-T3iOkB)fPh6W!I1TpvnjyO|SNdeaNU#%0qz|Z0gGoe|c zfG4Kip3F?&M<8o9iu4DA)zPM5qQ{%}L5|>lAJc0B6Vua+6q+yS@=va+9-)ULpxpdGd1JhOcJu-%PB2zV}gFbddH2%O{%Zn+};2S!(G7o z?bzP{PKzfaR|`R?6KOpM<_{O*LBB+-WJaGADcm2HISj-K(HCqR7Ng`l}2$4>-q6V-L{M} z!R5imDmI{ue43T@gF{KxLasl*P(tM+VqCoo6;FodzFi;pCHf){p;B5y)$L_vh_I1C z{!Pb-eh>HUs&sjZyAF4D9xeIfDEcsJtugnE3zg&`Qs;32{Bk`NP)9W)nypdTD5lq> z#5Ena=9GACsZv!Bh{UKu`s0759F5BTZ#rXzjkp;1)wjKaHEkG_HY*JHSl+V z=w43U7b_wmA0xzxtL5)D*C4yK7TZ&FZ|Q~^cV3o~P{B4dj56~1?LDd+%-#$28=n${ zIvsfzoEF;#8Z!)w&f^VNo96lFF2#~&2({e?+-jPqY%KB^WrM4Nr zjkAXVnKS_`Ca`nyQ|l^nU%w6K7`T_oi;j4#VHIPFDU9FTJ~Ls{5Vgt7dd6}Zb#JXn5z zqYMTVxVOo#jrt6f*5hSLmOcR4iqD(Ba&?s}*{qAT;@s@kR=gFCX>IDb*KUNbi>np> zu2~x(xN|b^J%zL8YF_hi3xreFluPN+`rhC}i2JDf%8o=Qetf`-*9<6ZSc9v7^iSX? z?!yPimw;Z&6B-WQ!htORH$vm;alVO;)MQvSMtCXk^iC+MGzY@vQubw&74zQorbVE);8QB=}tyr%{F(?k_)B1WS+z zsk&F#XA&_i+f@FNwygV1UXA5>NKb})t|t_RtN$0KCptgdoR^J1_ubVPWbN?S{|(};yz5i904mH1 zD`{r9>KI18=J)qB!HO~@jIY`ZLB?B5*=UtpifcyJIVz+da+ZOWn7{OOuGgG*4R!-+ z_ONnk(&(-K9Dp`F+FIjL2v;iKvxVa=zOm-HBQ0T-Kapw0(xhU_*uydc$LU;1s!NNa zR)`i2JgNU^4)qZO#|+!NMnqKHnlkNCD?I)FFp3ox8Q0b+Rs69-?b#Z2tlER}aKe4Xe@>z`IJ49vOLbbCxvx*Xb^>!CLX-UYy)o zykB+d4oyv)sN)BJ3C+krzLYn}8GxolS-Qm}Vk9P5 z#Z3D)EER_(B+NTsm(AB~8w^bVfR~=lHcazv;shYO(r}1>S$B4c+r8N$huzBPWXY*0 zs*m{bz3FhrYV|9?$PB$6{>1upB7vrA9K~VJY;FpZ2TKtO7T`-W*^$mlr%||{>$o;( z5MK!mVkScD*QPsmQr*k{@zqi^JfnbZ=6L(OG!n_RD?ick4G;?1480ac1PpPz)77$$!F$* zEmU#MEp2zV;F>fCFSY}dqohu4qNpJglB*7{%PlIhESyclLy9Zr1@{UUiZnUUNp}QQ z8=Yky48E3RDK&DAv;4yy{OqbOQG5dFUYr1~zc3Xq6_9NuMeJ+})!-voF_*;Y;#)so2RuUqoG@ay3dyh*Fo=t9`oSjZJ!t}s)@9-J9 z+7umjWWp8zmUM5^;}&KYp;Ggfq`N`c)nc8NXs@uax1hdZ) z_1cg&o@=Wt$eUeM$rF6s+C$^@maT{<72qAzJeWwDl$x99OOLS{x#LVUtt|GhXG1oX zWYxu@Oib5l)G^;U_7*K1Lfm0AX9K~FayHIJfUa>1TSvrd%VR{B9C4(g1DY|**ZFD@n znU&N&`x42JsT9e z0+Hl=cy>-#TP$UV8Y7$>4Xb2 z2?!_HA-*@X$-yJzPzj!H1Ivp_99O0gflhVO>o7C!XJ6~~ASo%mzv1Bfat5hWYTq(K z_9VQh6xXQ9UYCqUd%71IwMn`gM<49nEppyWCc1N}ysEpB&%~4-2#kA)XCQv)g7y|$ zx2XxeNho^;XonR=9&)b0dr*O9n4!a+E%6;AY4NS&QT%M{G3X~|(6!iW3+RJ_v^^JB zNqJ~lZHzerv#`Nt?G-n?8^WF@X(%@tNopT`@mHT=*oC)QHS7HOEXz~Usazu0Hqul$ ztN;h?8@k@DPS(lZ{PmmlJwxy-m!8>r zE?m1TN6OKKV4U)lbWn;8&?)KUto%l;rTP@;62io0gTs>s(tLkMU8t#P-dteLnET~E zqDCt*f<@;tUzOT3EQi1g!WHY!u@?fNF}FzJQBwRQA%v8dE2hg7dpW<*C%ZzTWV~ag`y#_F%pF6vv=+F{ z+_cuI?N52X{4KU60~u(*`Hsi(l>+E$?zU=L14$Wp4*F0@w~E29GTcAL%d}+^(=?E2 z;RccoQOt{_o_(99obJ(m=|wyS#I{BuH|Oh#%_xbmC{<-WEkQ0ymf=#lg!TeLm^f@1 zal^XU-X+ZPT7-7e#`*%rSZ+2lf_bONN5}PMCM+K%8(J9$wYf-Dc)g`byVq@j$JQGq zA+-5wK$>FL)bwN8Ki2S%3YRhv8zxC$8s~AzW$#nXLcWcJ)VW@$<2B}_08H)^uc)km z47T0?S>Z#hIb~uQ`b*?2;fpZQby1<0I5(C-AM<;`ZoE$(y|{e%wtTHwM~r#t^lh#) zHH@l>H#O3_PUuL7$Iy=jN#bq3jrfTV5*W|!s_gd?X*0f};la7>r8QrJ3Rf!3q`r$q zeL<`h$I;<0Qtxg+H2xTg+k~ab3i3NOW{7U)Rn;?gMa9FOe1*=(o>4hUUCg5_G zcHQuffmOq{Gop_J17ZNTrNvqOK!+{CkyJ`Ug!R6%emU(cTB{?gT-?I!Op*$_1!8mFn!%-ha#8ro;4a*pkjyPCS zSw(7FX=d(cBjg$Q3F=4Z&*=vZ#P?clj02O3`u88UmhEEMl$%GpyjQ7xT6%^g=%(<1 zV;K93Fsty8ejZ_wDYf`^N@o;W2}p^bB-R*nP9!cfKJxQ7=tC%JJ(OTEv%}HkKXp}- zyD)t3%OX0EL2-VTlU>a=7TOJ%G@39d7xhX>?r`$&?TW*86 zmk5fb<*qJb|1JVoDaN2_2$yp-g*wX$v}OiPoQy$5k%ruItdBISnRXoY%_-a=&Dvug z(X~kF_^YrY2kEVk{^>nftFNVO)M_vZo>NAgB~v{Kk2{zdbc}y8esZRwkJ82yN|IcV zl21pn-})LQ-NFa)aDOjPq7@-my{aC5S`UGA6O;VPdEWv~gEPQHIoX7KhF$-5(s{!~ z@9!$fONm5Bfo=_2I1TY@XO?WMe=|HPd1*&-6b7<0s&kkm+Ck^aoR)(IQFCjHSRbAG zektOUO=TaWNK79^!*OFeS=fQz5M$+&o&sj%%8d~r)Z4ef>hb>kLAU5svx-m>*~Ef;c{> zv-$q;921jU8CZs7B+JVkJ`hp9kBAvu`_#wUGILP8)^GVQU}(VLB$5^fvR+wCtA7iw zJckb7`lLGrp+#5`%PwLkrh3<6Ids{A_ts zFf0|dL%lKDaIeQuzA9zD_#K;qpDNrxuJskqyd&wFC|e3OHZF}^F7JHe^pFBla@V(n zoUM(6<|^7C!3NtI76k%HpSVkJI#} zEF8{}FS4!+N(^>b;|yksQYM~#HUo>ISep{wErR^<%AQ4e(Ef5yiP}mrnCTd5Oc5Ho zBJ5T#s>h0g4*aVSx(eQZ@9dA3KY3*6N_89S^An<+i^3Ter*aIGo}%GFnr~rJ-&=1< z6U;qM#8Lauf6y8nPFjVN>!c$KwfMIhm&N76-xcPV0)i|+8!_SWDw8p_{DKgcETL>+ zCghNDlA=o_iR)9iiw!%DPJUh{kSB;W(`$(mR>ukDn0 zK*suH4aS*%%!vtr1;W*W2Cu)^H;+l9@#yvlocWN43G{9bq=XyLDO)HAMKs|9___D; zz;{vWfo3b04$P61)5~=!w$GP1ZP@NH%oqZa0jhj4mK!t114YfI(UeDnG>=Kf<4JKJ zm1r?%$CE~-rH4L|+;A=jzqe4=M^X1E`fS%i(Jq2{3UTfSK-AB9STTAUj&2kXbgb3K z*Sj$tlk~W8-ODa0v0z5jT7(zps==XTL;d9+%7;aRcky_!Tb02576Be)lALazar`D^ z%q1+MNOL<2&g1~xo1V=@n7kuW+^a2vNE6&iW^zlJCgtct$jI zO8UL@PVX!qR;`>lPXhaMEihw2f8sbWY{9NDQD`eIeQ=A?)a)&P6KN`wm+N`3AiJb; znop3w({wbclSXz*k)5NonhfR5u_KvS zEQ9tFv{HETiAd8~!IA^fXZeD@POtj2SB2LKLj`}vA7b!R$>=f-^ekY8x0}$F%)`Ff|G;6vI@pf&QOTmVbT9gf2;tF2lyx4>1+nN3#xo_X<% zqes*T5!KLPD?A@A+mvWoJX*En#%wPvB+nV}EG6X8v0Ygq4QSF;&WL?JN419SgK6Tm zE*i#qQ5)63--}y?UOI9#nd%l{y^8gZHq30%7hiFQD{z$e(doBnKEJ_tne1EANXj_# zU8;But~j=RoslQdm1FjW$zhl=sUv&=CM2sa#0c=2CQ6%>!bZ@hFQ} zDP=61?{pe~#ZG+tf%PZM_kNx?;LG)jKoELqTcd4ab$X%=Tx zBPX>2^3d+)#jS6vD}rBaS>?A?bd$>pmb2CFq;<%^ z^8r6dwGWnrj}Af_N$ksuG(+|0O_F3Q6hxOq=5TvtJKb|nQ1*Ak+lc1D z4;)acuEblzfUoQ>RM(1PE_k2qc6%TMmxuE7qy}Mi>dg^={yCCg-e8Qe|^T&Q_Y&LA)g1uUc`)?Q*@J}QKo8pv4 zi#VgS$^20Fsly!7TO09F!efd!<`rra+YSA5E3RwPr2_TBBk=6Pq1zT@)Ceso_O%OT@*MrLl zPU(bCXmiN-rkvbP*Cl~!{{g1YR2@nsi|XKTFJv_tNQDqi-s{u8l4~bik*P#*yKv-O z4gF*)#r|9lji>KylsB$cN2?OQ|1oF3*%jPkRc`dj6%Ni4*6RShu>mx$Wff0-1|x)5;6hOp|%0bzzt!IWwC7fVCahYPet|BV~j zwMtwhf8h_!0!P>Ms?Q4!Bjf%9%*iW``gx*8jBAi~8}~D&LM(0*X(DyHNlP zzWJYiIWa~#D#erOomXQchm+WqKU7aiCl!yN{f)QI=y?*si3CbQbP5v)@Ig!_Nt?uS z5P7T^F-s7aFxmZV&ds0hn^D(4!&FCKOD?ws3b0BqGpdQ&HOST2-Dqw~ECqYY&!l^^ zq=>l&_LJPJdLq}5gCU3}JI#3t&p4ZCVZ-zxKSvKzs<`WqLkH#yizxL}Kc9~h^x1K%~* zW0p$y-9){?F;UXBm4I?l59v9qKxL4Ba7v9bFSVw)ABcCZT>=pd@FT= zblsm-A^Vi55Rp?2Wg0qoxD8)dWpyMs7u;!%+4a1$LJtJlMmQHv-cyva5|U)w%JBLp zAmX~51$D$(wr*DVS+;Q|7a6u5xTrGSubM+yg|ETNoAG@D9}@+hsu7rV3x{vH@LE>F z2Erci^9D}n7l*`i%#Uuc2>BOT5(%KRi&IN5A%Td+79Mq}*eTlyfRR`BjDS^Ja(}w^ zMq6s>)?toq+K;ocC1qmQHruytGBX3^TZWOoY;Vh(C*s{psu13_#v$1XA!@7=z)&f@yJR%`i*?DP!?LG#7fg= zgzGF;U<`^*@hg4-DJ`cEPR%)z+*~i4B9AO#Je%;X=bC#^+ivz4E$r(@BjRdbWFbEpa~7`BCk`HIb?bHZtI?S%n(N_3yCJz6~=%{a)PZsiCRxIi3@S z(%I1<{4ofrQ3V#TkRhEmefS3vtU50796AF(-3Zz68kFIB!i&hoJ{kFTi=a`AaNa;W zM82<^bgbT1nX|#<`@-S2QfWLH51cN@ADSHi5xV)i1@cPmPg?WeV+FBjwL>DlZZA`8 zjrm-R#B#;Xqh43GV{{nplj%6Rb5R0aH{Nz}OV8>|z!g<_i@Lc3C^q6uiYC2*2xmpe z5@Zm=5#KfXuJdAvndnDeUC96!dyGQUF})mbyH*V>LHpXu=@|I=<9&AC@z8-U%yeQ| zhO9rB7yO_;{MR`!$7N3R@ziNXFTOKllTrG27xUU$^7Gf-RJ~CiUZkl4O@SsB&^4=7 zYr}#b9C>37KWuh|+7OIi3JiddQYb|Chv(5?NlAtsHXMICh5C=9^PGYTOJXQvBfgm6 zelTe{TR>J+ha@ckLOK`^E~MBV6uYxQ|e{ zi{=x`fs&1%uP7xyLXZyPrvW|eg@VQe-}{TtPSn)%}q|>_gdf zE}t0EiTJEgWwUxDGqSfS(u#LGvd4l!{_F2}Zw}QfOOuH|UJYGksB3*lW24g&@%lO4 z8$3l$(aF{fVk?LL&Q?H=aSdlxV~7RlFc5Hm|L3^=*#sX>k>cCtiAtd*j`LyfKv{XLnM zr-OVKhFwW)EhVX)MdIq%GuppOUt?+m^%RnqFaPj=isLUPTk24HO(&}>8Mg1aBM2J9 zCl6=NkH^#RRK6!33jeYDiVbig%NWq)--#x4;Ky@ljMoq2UdWlLUaDch!@KB6Q`1y; z^F2GdpIbrROWk+jKhUCR1-j#M8j(`qxYNCpFwL1!S^GgMtT_zRC{!G!+C~2|ODY?- zaJiqrdtgGod*smtn*06DkT{Bu;=Ja=CCL0Ekw0J$MCIK#yywT{Z?@4V%PoB^*c}Gi zpf*Fq#*-thmf5^I3fC6TdffO)%3)*3`RhE({B1*-B1IQM^44jr{i*S{zszBP5x zaSclaRbghJ{;BOl9R&ItALrhHr(C4k>!aeE!jRM{<2+C;iA)F>f|<_cHG-vQ+Axxa zNsYM~DPpr@-0lmxv0bUfI3Q(!I0HBJAbgrR6mTQ&`+aqupfhZlH7yb8@X@?&Cvbj` z9TK4pQ;pFCipDP&|7zr%Z3`$-H^CJ!aGj|3`sw?q4;(~W!7^L&**k)Ep%?o;K=BuW z|2*lW#Rs*@Lw5t>Blx8HQp%hN(QuL;#0GHiweQ@6j0P5{`=Dgl`G93u+F2`wR-!R` zFd=<&K#V+9RK44u`d=#axN_!nTpLqNyn)Q~<*n_=JdWD>JrYGxB~3;+-@ALFma>&+ zRE3Px?d&xLdf>F+U!A^7tvadoiVAn}ubni7OTr&MYV6OQmqfHo&&2pWmqXG?f>Upw zMLDTD`eyXYPLfZABw8d3fG##S_De7(u+wG4FzN1+p~TIE&=S$JR(QX^uA>sVEE(L~i&R$A24vjXt*M%rN%bbbD}L zM=y37eI@p29D%`*cuY)+grj%eYVhOY3$vo0bh@Sc-*~9#&1ns=Zi&$6CPjWZui7Fq zM1Ao@J(8)vV_PWWVNzFXzQepds!`n+L5@2KUxq`Z+57`08 zLr4w|=Qtfr%TeBd+CQHkwx|Z=BWMA{#l;!^AB~&m#IO2WPOaIr>0cvY(|?KBUuK4% z4T1H{dxnO4sT?8}K>;!QE7m>U_bE0ffrz8{d$5}t_{*mjxxn3XfVm}{8Qke34{pFfJ_6n)^k|4jS zzx~JFhXO3lV!o07n_=h>h=55toc`ZD!~Y>srwS{E{KO8w z{em3~OF=Jlh{3rkqU@rTs4w*mlh{ap5vR|BeQeD~M#inbK z3Fe9gLqcB+Q!+>8vqZ|c>~EG*7q+b>hT&-#;OJT76Oo_4ZcMYPIh9G3|A1}mpcrm~ zCI4OMAk=|T7gus7p_&}t#HjdN)(oe&17LyC=Av?J}^Z(5w+W zhyvcYwrr$N#!g{$aCcc!Hi17U5_?du3@e6p{*JpR@uS*o4 z^Lew4HPi>++pc@7@m@Z2v)IMt|Ap+!{d#cYsdJN;zWM3!c@rbJ@{gza)fVu6d0HP| zF4%f`sQ>c1eOH~a-1UCCcn++Nh&a6P`fTF+GXL(}31jl#A~HxX|2sQW9+7dpzuLCS zr1_q)dTjmBzV4t9z^4@w&b&FzXH>JqJkN|orVW;dF z-|egbesAa3%eFu8^6mVS#&>_)kDjjk<)KZGkmFtSto-(-eDUbu9XNH8Gqbqp{e0u^ z>2-MH_0aP5d36$fv$3ImdqXEJ`kIslg(g+93t!s_Q$zQQ#;+;WVm0`~xtmAHE2 z;f!NUuOR__{u|vrdTL~W5_-DIq3R5SpwW@=#P`r-X?GAeyAbh2>sm)&W3-2GTi;^l zzFpqF=W>T(-Rkt+qYI5)YmUzm368q^k3ZAn_o|^AB@+=3E?-2XP3Q>*vL3xSb!$aV zC=beEz0^daMMTmPivZdbW;EqmFxoa98X}3V3!m1XnPNLgTVt6$VFb(DFlxf8!{LByOltlRyhlMZ+oQ<5QSb z&LpD-FO|u{2y;D@fqaQ%azd04Pv?NC%$>-CYFj;6T;q$2B;sl(A%~`PLxAs--)?xt zh7tph^cldJyx_(MrFX!Bos!!Ei2+9pLJlbZp3p;&7>FSl{tb%A?tnG@-$7z%Dla(b z|5x9mcA5GYiRXl_5T?RC=KuBWSf2DBQxY#YDdGQ^&i(U7|IhD#nf}jI#8UiTns5XE z{|ORs|KU+V{tt?le|B*HLGfP@|Cg(!6p!D?-T%@0U%=WnrS35kgA=c z4-r(D>z@euKd4Lcx~s$X{-gE(iO6&DiriW^fw_P0mq-?>An$+;RhK@qNH2?Thi?r# zwcr{m)Ue%rg`r|2zegTEzgBr0!?robghR#c3EOh~x~ESP2Ga@{&2gVZ-?yox9mL|r{~>UeA;aIeRrGXm#ukcL-X=m6nGWMQ+GX- zDu)3?@x~Ptw>a>@VZ6UX9=bB|hIh8VcLTmpzrSV!#7PhR3|HaqEbsBURD2o_@M=6u zE=9#;bctY*?tFXkW4!gkH9p4TfxeqFH>*>?2dS7-^ZUje1jgG;N*|=AuK}g_TSTe{ zG46?*#>M-)%#ZK;UTW*GXY8-nPQl(Tk1grsD3x1oF{{hB^vY{QRCE1Nx+|HOT=w;p z6TR=f8|g!8AB-yF4jBhWpgZ5Sp^4_yyONBx7aG8@vx|n!GQtdP*iB!j8zR^=jW*`* z64RypfPa9A=i=CdIgTSwg4hoEu{=%bvAmPN^m0ycD#_(@8F?X6*7|vON5N#N`@g0p zjiN(Uy|80><@?oSxO%xtB=U-sA=nEiAK*LwHgWiN_=RlKpEbPm%Pr@{{=H50h;jF` zwFg#)S8inG;J8nw6;qPQxee1&z-vcT;H#*eNHmJk23OAwp(%2 zI;=S69;+<`Cg-@nq?X8^Cp~BA7Y8SzL>$VB#oXa=^6iXl1FnVAoO)I@32-z{b)X?G z2*|$HlfY7%GA8-1eOWX=Ul?WrV{VlbfNwTh*n2@}s=N zFTT=Ux$P0h%Bp=0-k0-VIp~W-ZM!$5-(P^Gu33+_$M^fM`_U2au1|k2|L3o?@0IJf z!}E^|r=P~%U+sdqPfQ=PV-Vh7AV}*uUhM+lhPz#D8Dl__SDmDHaigv9;))v=A4GBa zDat9Lrwl@=SiH_|wVOPZ4%raLxw_@|53~T6*CLQZ+m@H=$a`zyd)}QVIwJ;+3kn3g zG~6{2mWE~aADkKt8EobHHVSwX0QcKzn6R}|AitLnpx(sj(zArch#w#1ByaU~kt77} zpiRO)KV?KFK!WS#hEH02w`tTykWOAKE#nT(1;CG-bxf+=1wGZv&^9q$jX*iuzU+jr z01lxq=Hc**DyUkwuz%CWzTc|H>7M-829Gk~zfiHFr1GB!eh;>0qm z8Gpc9j!gve_U4>rp|iEJ@P3iW_+5@-V%>rAr1>ck3GAT&j<%B;~uV%(+sJ)0)@kay*YIf(p~L+sg>ov#9oH#o1fxZR+Px z6iJj=u~sX$2`jxzaW7#eGfIqe-lAhu)Z^S%0-6-Hia6;l_cEYu?!ymMnm1L|nK(@O zMP93#aDROBtZrLGk`$l(6szMl%kL7)(GnP}=T&=wZVG-;rFJ<1pU$A&NzUWAewYkD zJ-$(ORa*9IY9;i*jmvAy)J>thVi)ML?-+1Zg2lo0dNQGm%J1QcT6(@38Yn4@$nUdn zvu)korKo4G4yVI_|Gv8h)^amyWe{>1JNY->Ss7Ii+DKpkDx}orU7Z@#jaO@b6o-L7;rRgm4It+mJj;hl0aTHZq_^~$Fxq1z`2tg^XPNFykhijeE--OkQDm$7~~CwxRkJxBX3wqY;ge$P-7l)?xvWvu>q zmdDzkiUWmQCwKY#!`Di>UW3TakZuAFOO;$#!gf9B$TN%rV}^dZ{Tpq~@pr`!TAAA0 zlVX)6{%c+PsuTjx(;@wP8D7I$89`FRlo7JDkqV06zj&05COl9>ULtz*(3h_r6i1ix zs@h8B`Zcprq9?C}r!Oy>^2UU9W)Gc0Sfc<}Ix_ry&OUtKZAmj>$r|`+I=@2AMwGE? zw7ys9r8hfDTV0auPTdFGbBi~fw!l3tISpau5HlpB-9BjOQ>+$}M>RHB4Y%Z9{29>ImUJ4MS!QnfiB1?K zI!c&GVumzszrJ(cU}ERVw&D;r>+BreAir?Jpn$c+C61}T>C_s{WJtyeP^uQo7Oc#L!n(o7lh5F zTo=kZUT}M-po$8wsN6L3y^Tc3)XIVx8v#Gwg7K2oB};d^_T`QC=}1E(lU7Puoyeyd zsYs=`?%+y3qSF6x(xxS8d0Yj~bLvL*t+Ro6gJw7G=PiEyu%Qv6lP2P^*M`!~N}97h^*}Q6kQWLmIXl{zVL?65dT&xV?;;1tn)wODru4O^x5) zBHplT_N~7=BpHtl#gCo6Zr+$WE|GBj`1D#H6FJDoYBnjffBR^}g|K&5zr*51dP6TL zB^YlIeKiRHG#NEA^R4N9d%~amdKKEMuf4s-hjRYTCgj-5Jr?a&FNeICxTnX~<=Qg2 zJEc*S<~cq^2>R3$QnS4~r>DBekZc@Bay$i5Wt*}KB`~wjXZNpKBrY+iK#~uaZErE| zAy_qc1=5Syn`w(Uj0&O6K7sAd3a5Mk)-A8XtBA!PM>oL6gdDGOowAGDDowjuhu-Td zM9<{+WSOPF>haU(a^hXO133$3?!cO$dy>pydT@Ryd=Q6HwXHKFC^TulNAD2y%hP*|sEE5{&+K{fXC+m4LW0pej~haFTmgp=-JtgISc&3iarClh zGx$yiZ@ru*!DzfLzuV3bRE49@YCT6e(0Uq?wpQ?a;=n9<@*2_RYROkYA31Q^|4bk7 zQ*#ArBFdi0k8vCH8?8$$?paHnDs{O8j=Gc492npEOWMFaw>47+`ymVrzg;U&18$$J7{1uP&7qW%h zKnSytvx&Zt)3jfD?=?PSS1Bu>^*#1qh{)r1xS#|j-u4K;slgWiAHu#eERJPe7Z2|4 z8r&hcOCY!e4estRxVyVM!QI_mgS)#s37R`B*=Oy2nl#3c9MAo*o*x=E2Ld ze4f&{mE|fM)|eS6fjQj*yRBq0YN40-#k*bm`_rPnH~R4ec#Sy8))Az16-qfa3-)dn&Z&5$x$Qy0+}kzH^Dp73w=DY zG-@1CRu9V4tSEk%I7)#sqF)8rn;}m3eLoRvGvpy(Ey>Z=u1aM)3FJuA%i9?6*lLy4 zQK&NPg6>G4@M+J|SaL+Ue=qO(VKVn;x!Ygm9eOU2xmWClgXGQCP7}GaEpby|Tt*ymrYTNdT&0S1+m4KczAt^CoNjAD89psB zQp8fQnJisSBJvdPK)(_Ol`Bp@K<4{asy!#wC{mjVd$NBY@FiaxAVMYFQR=C6XAKLf zxhPUm$Q|P5pfb_;<(T#4yAKvDK)8b~_-I>^z3P-=b$dw`^8Uun?7ISEdn{(I_PiIT z8^4|0Ua-`BxZ8-cL}R>ohYryvIsAIOE(pk$ zWQ<7&`x2qh;gpsv@8bM7KW-XOJ1#+2=!dphX578z6-$0m8c&zswJ(crR}*w>a%`jA z{n&N8#;IP@a+P9L0-tRRz3iQMzcWm6$fb+MhEXkyhY+6N&$NHavU2%u9;AYU13BkX z#VFLRAFoKKMIo(8%-z{lPKnhIvB-x2BDv zWON#W5~tze2d!`e<+c1R3l{9+?~N*r@l?HlMyy|NPJ3t5Ajf5CNzcO^8sFV;pTBv2 z7r5b)@u-X_M0eqE$=SQAp=0sf!ZVQSD zB3yP6#m5@=6j`CfSW=qtr20jiK5rax+dpg#-+h${_u0d%P~?mBtn4Hue$4pDulgA! z#)VT4=t>|XCd>yieRf;)zt{*0CH|c)OX>6f#TM!pfdxSGwkP0nk8*x0Ed)A>R4K_C zXv$8wDh2$EUX~B;Ab21+1}J;`Z!c-_G|22mF~Qo*f^$PymLvhV+RPD5We8C!u$k%kf0--!p|Gk9aR!*~TRS?y z1a*S3X($br2PmRcXa28ghWbeZuW%gAQ5g)w&8h#x;34Ki|9X8gyM4HMbA3eL(7QI> zx*c;FIfXq0(|OQVdc0J*ArS3ywJ?c>5-9R<1*xo%8UsmO`Tkvy=5j_@_w)nBfc$`5 zWpC^OPv{II6zooR`w3BUnT`O#gAGYUYn4kb#DGEm^)Rh9`C`v6bL8K9SM!gZhQ7+I zVqHO)+p+GaryHXU9Q`cVj>V-kA+{ilP}7X8KC_EyxqAo{6(6(IQ&1PSM{E`2=;Vq& zQYMSR_A4T*j{^Ad=$eDS>dW9GQ^%BmlYU@w73}v}3Tr3cq1D=^MNd5!HP%8VcA>L< z=sx0S#j4(V?VRiTAb8}^AYWkqvEI@8xvLy$r` z&fo|l?j5oEU@xIu%=m>2I0g>L8#bb)-ea(^9^x9ywV$zulYHjro(>&2a@VyNfsw8w zb8j$Y9*8$^rGxVFqN_;}{*J@Ra~1Izi3jg>Y8V$cH(T?76 zvq`@DZVLwbru>B*PF-1*Ne7kHHyK3shwpkS9O4hPzynkxqqqigO-f}ocG$QxXbn1gJ8am>5&(jFTf1X)wd`C{Kn2nL5YX8-y$7!5f6R z+5o-)5+(c?HavIOl9J^V476nY$DrvE+4Vnz;agZznp1ol{!6r?qZ-+xlbv2Amm+fV8X<%T1a z_AvFu?N_^UoQxc;)jh>}(8li@An8jVYh?7(wWK6j!Yjrtgrz;u#(wp4M6%+zwWIEq zi#7Cfg*rIn86qFf$^9J7sCx>Rxp&@wKZ$w>+{VPElp|Q;$Yi`1as63z_}`0aTiU;G ze9Pjbw3FS*08*jGH~1oShC1luWOEkTeH4jS-N|SuP6D=1TKmKAWIP>fr#h$*Lh6sg41#CBI5`wameRZrF=b}ZjuAW1( zCZ3dhKfkCLH~#VCZQ3E)VLuDMo$Ri35Mfn6mxTiTyOMP%oPMtR*$$S&a$e&;MFC^~ ze<@_C?`zLT3~|W&Q(<4!e^;30;MK%+lRhubNW)f4z%Ig|j+l5Vn8m5w3hBC%$YxN; z8(j*ZS|U-KPdgzFmx=KHV}`zI+cmFyMCSY5_ME)m^$=`htcNa+x=13LM!+$47w%Ee9?@G~1V~-oZ>#oB-52*xT@9q(?Ll4q*@5Cv+2rI;k@5#M!E!B-Ix6xH>ITP= z{ygyig}R;54$NlfFquQ=LV;1u&#lb7I$*V)5>9H1z#Te?xQc${;%TaX2bypPxIm_3 z#!rG)Z(gk~m{>YyrDK-A1XTCmmGG`vCqEDtVVa`$WcSkx(HYR0mFPRlwgS7I$kvj)9d5^dDY=CMWz9=Ogwwn^^X$Sw2wGHMQ7Ra z%)lC1zurd9vK^O{<}=uE%2;&C#UHR$ctN7bfKS)oXsai=Rng^{5#*SGO<3<8b~bV! z`3TB&xrgG2&KgeG@H&>p7fKHDO|wOsrum#p&wK2$$+(rC6py9L&T3q07V*fGX$gtW z3bIwSyX1V|;E6HS_+XkQc+2?MQdJS9PyA!{aw80Cnoa!{4=MAcz%0&Aj!lLv?V~P_ z68k2nOng=;_sLDrZ2iMCz+86rL=}0O4Oxcv^1FG-PeqEf1<B0( z$fvV7E(xpzObTvfzuIl!MX?H7WI4++>+A6|tuzDClpF##&1YRIPFh@jw{R{KI6V*{ zJ*0FXiVzZB6W|JQClV}I@aya$E4jSQlk`#!^40F6W%X8LOuz64WNKs&kXUd*Xk=#M z2oNmtP}DuhdkD(nhO9FtMITpqJ;=i3iNV`$w4b$~rom1sO1B}ce@@eB`?wHMw9B6v zVMZJpOn!6?$9c$!G6Mllli9#&(%21znYjHO4eK3^vG`u_lt~+iwhcMCk+TdqfOS_- z2Y}wI%wK^uA!uVVcCT)sXj28eu4&VK%vewR#Ck>o040U=!y>i^0sa6LfVJgEe(l$u z)42ut-I$kP5RNIDq`mvCaqPBzS$Jt|r5_G>EepbGeod(bfNn&GUJ2L0=rE;DaIy91 z1@RTn6A8i=%g_rV7Qm4U!b3B5LrU&Ix(f02z?o~2a7SS25{dwPkBx41`S;3`3|;?A z9J2&a9G*Y2HkB6wmpVLeFu?GakaNDc!VSDy&pzso!?-5{!_#sC zhDWds3=bjsKf*hY|2w?KH1JY07RZ~%e`mM0Kw7)X#pjEN$` zIbZg3K)xw*eS{8xj-mbs$W5K{*fwEj>VmsppV_{z&KRGJ2c1Q@NyUb0!m)rx!8%8@ z-miQ05fEphtz1}_*tk%7ErNO-Ld0)pv`t|SUqL`YHXN1^V$MVt0rwf;gus0U&xLa2 zDq7`Y@51A!r7SEpPCVAp);Z2$-FeszTcyj_c~iXiSoB{AwPyBAO1EFk~%g8hV{`NGO_kl~O_SGPJYF zx4rZ1(-+g-V=DK*aCg5H-adNtFOOM^q-J;LV$4~u1KGJ8BK@LIGKO|D)V`ifvSvOu}TY-|1kOj0Tsw3;Hw8ZE~l%Ee2Ye?8u$1$ zY8CBZQP`mOQ%d&!2hg_O5w|b;UQj-o`d(UWV9Wye2OKwG*$8K;W*uS&i(a#eP6tI{ ziru?@1O;AyP_6cAoLK4oLG{O7sai5e2qP~rf4_&vuSjsqhpK!umf6`y&9%=jrGw{i zw-%iPmORfdYq~Tq3H>kdAWi)*t)>wAUR2K4#+^h2 zl}@v%IG3MYA-jR6|eq`C{t$CVThT=`&j@mVJI#%=dS`G~dQM{pMS{XH1X zeJ*Z#m}jA^K&f@XvYGLoW<}lOMdGu(=eLS2RWFJ33NHyfj29{~`JSp>$S##A;aSMb zbfP=uE0L5rhYyHG20pM}Pi|e*O4>FV*|1)Pm@hvZayqpIAqA8TlzOJ-jz716$7=_x z#o07W9<-@miQL;Y_~{I6Xx-Owm27>c_Jj4(V*J%$2Uf@cco*zP)LW3nW5NIweDz;~ zuUH$l{u2BPC>Y2iD=_M0&@svIann3=!lh~}_+-}{vg?K{)#{!s^)FxoAh7*a&ELRZ zlB342^Omu5{IWYYY(^6n@eYgpvP&QZ?(mDNu;hD?=9iSOSe>2}oX94k$|?U1I99Fa z(w>DI_iFpqa z--nY^NMKm(pDjC!Co2nlG#u z!Cgfq)(db%3%3sGk-C68j4ar%okxLfzc6(5YmT$Nk|gKg7}CQ3pjP;r3^q6mev)*==Xw5mdt-*+M};%0DTiIn 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

cis;X>uU_M#@Yz3$hSQ}oO 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 bL+nrQP+1-ZJc53XDhz-TczASH|NQlTN+r1C literal 0 HcmV?d00001 diff --git a/docs/doco/xfyun_asr_standard.py b/docs/doco/xfyun_asr_standard.py new file mode 100644 index 0000000..cf85a96 --- /dev/null +++ b/docs/doco/xfyun_asr_standard.py @@ -0,0 +1,345 @@ +# -*- coding: utf-8 -*- +""" +讯飞 录音文件转写(标准版)- 本地运行脚本 +================================================= +用途:将节目音频转为带时间戳的文本,用于TPS中台三方融合demo。 + +接口:https://raasr.xfyun.cn/v2/api/upload / getResult +签名:signa = base64(HmacSHA1(MD5(appid + ts), secretKey)) + +特性: +- 支持热词列表(hotWord),提升专业术语识别率 +- 支持军事领域参数(pd=mil) +- 支持顺滑+口语规整(输出更接近书面语) +- 默认语种 cn(中文普通话),免费包标配,无需 autodialect + +依赖:pip install requests + +输出文件(与脚本同目录): + asr_result_raw.json - 讯飞原始返回 + asr_result_full.txt - 纯文本 + asr_result_timed.txt - 带时间戳的文本(格式同B稿) +""" + +import base64 +import hashlib +import hmac +import json +import os +import re +import time +import wave +from urllib.parse import quote + +import requests + +# ======================================================================== +# 配置区 —— 填写凭证 + 音频路径 +# ======================================================================== + +# 老版只用 APPID + SecretKey 两个字段 +APP_ID = "84eff996" +SECRET_KEY = "在此填入您的SecretKey" + +# 音频文件——可以用之前转好的WAV,也可以直接用mp3(讯飞标准版mp3也支持) +AUDIO_FILE = "现代防空反导大对决音频_16k_mono.wav" + +# 自动去除空白 +APP_ID = APP_ID.strip() +SECRET_KEY = SECRET_KEY.strip() + +# ======================================================================== +# 接口配置 +# ======================================================================== + +HOST = "https://raasr.xfyun.cn/v2/api" +UPLOAD_URL = HOST + "/upload" +RESULT_URL = HOST + "/getResult" + +# 业务参数 +LANGUAGE = "cn" # 中文普通话 +PD = "mil" # 军事领域优化 +ENG_SMOOTHPROC = "true" # 顺滑(去掉"嗯/那个") +ENG_COLLOQPROC = "true" # 口语规整 + +# 热词列表——节目里出现的关键专业术语(讯飞限制200个热词,每个2-16字) +# 我从A稿提取了下面这批,能显著提升识别准确率 +HOT_WORDS = [ + # 武器装备 + "萨德", "爱国者", "标准-6", "铁穹", "塔米尔", + "大卫投石索", "箭式", "箭-2", "箭-3", + "霍拉姆沙赫尔-4", "见证者-136", "卡桑", "泥石", + "C-17", "飞毛腿", "M270", + # 技术概念 + "高超音速", "弹道导弹", "巡航导弹", "无人机蜂群", + "中段反导", "饱和攻击", "动能撞击", "诱饵弹", + "相控阵雷达", "电子战", "外大气层", + # 事件军方 + "真实承诺-4", "伊斯兰革命卫队", "本·古里安", "拉斐尔", + "黎巴嫩真主党", "波斯湾", + # 节目栏目 + "军事科技", +] + +# 轮询配置 +POLL_INTERVAL_SECONDS = 30 +MAX_WAIT_MINUTES = 30 + + +# ======================================================================== +# 签名+工具 +# ======================================================================== + + +def make_signa(app_id, secret_key, ts): + """ + 讯飞老版签名:signa = base64(HmacSHA1(MD5(appid + ts), secretKey)) + """ + base_string = (app_id + ts).encode("utf-8") + md5_str = hashlib.md5(base_string).hexdigest() # 32位小写hex + mac = hmac.new( + secret_key.encode("utf-8"), + md5_str.encode("utf-8"), + digestmod=hashlib.sha1, + ) + signa = base64.b64encode(mac.digest()).decode("utf-8") + return signa + + +def get_audio_duration_ms(filepath): + """获取音频时长(毫秒)。WAV用内置,MP3用mutagen。""" + ext = os.path.splitext(filepath)[1].lower() + + if ext == ".wav": + with wave.open(filepath, "rb") as wf: + n_frames = wf.getnframes() + sample_rate = wf.getframerate() + duration_ms = int(round(n_frames / sample_rate * 1000)) + print(f"[info] WAV规格: {sample_rate}Hz, 声道={wf.getnchannels()}, 位深={wf.getsampwidth()*8}bit") + return duration_ms + + if ext == ".mp3": + try: + from mutagen.mp3 import MP3 + return int(MP3(filepath).info.length * 1000) + except ImportError: + print("[warn] mutagen未安装,时长按0传——讯飞文档说该字段不严格校验") + return 0 + + raise ValueError(f"不支持的音频格式: {ext}") + + +# ======================================================================== +# 上传 +# ======================================================================== + + +def upload_audio(filepath): + """上传音频,返回 orderId""" + if not os.path.exists(filepath): + raise FileNotFoundError(f"音频文件不存在: {filepath}") + + file_size = os.path.getsize(filepath) + file_name = os.path.basename(filepath) + duration_ms = get_audio_duration_ms(filepath) + ts = str(int(time.time())) + + signa = make_signa(APP_ID, SECRET_KEY, ts) + print(f"[info] ts={ts}, signa={signa}") + print(f"[info] 文件: {file_name}, {file_size/1024/1024:.2f}MB, {duration_ms/1000:.1f}秒") + + # 构建URL参数 + params = { + "appId": APP_ID, + "signa": signa, + "ts": ts, + "fileSize": str(file_size), + "fileName": file_name, + "duration": str(duration_ms), + "language": LANGUAGE, + "pd": PD, + "eng_smoothproc": ENG_SMOOTHPROC, + "eng_colloqproc": ENG_COLLOQPROC, + } + + # 热词,用 | 分隔 + if HOT_WORDS: + hot_word_str = "|".join(HOT_WORDS) + params["hotWord"] = hot_word_str + print(f"[info] 热词数量: {len(HOT_WORDS)} 个") + + url_parts = [f"{quote(k, safe='')}={quote(str(v), safe='')}" for k, v in params.items()] + url = f"{UPLOAD_URL}?{'&'.join(url_parts)}" + + headers = { + "Content-Type": "application/json", + } + + with open(filepath, "rb") as f: + audio_bytes = f.read() + + print(f"[info] 正在上传... ({file_size/1024/1024:.1f}MB)") + resp = requests.post(url, headers=headers, data=audio_bytes, timeout=300) + print(f"[info] HTTP状态: {resp.status_code}") + + data = resp.json() + print(f"[info] 响应: {json.dumps(data, ensure_ascii=False, indent=2)}") + + if data.get("code") != "000000": + raise RuntimeError(f"上传失败: code={data.get('code')}, desc={data.get('descInfo')}") + + order_id = data["content"]["orderId"] + estimate_ms = data["content"].get("taskEstimateTime", 0) + print(f"\n[ok] orderId = {order_id}") + print(f"[ok] 预估完成时间: {estimate_ms/1000:.1f}秒\n") + return order_id + + +# ======================================================================== +# 查询结果 +# ======================================================================== + + +def query_result(order_id): + """单次查询""" + ts = str(int(time.time())) + signa = make_signa(APP_ID, SECRET_KEY, ts) + + params = { + "appId": APP_ID, + "signa": signa, + "ts": ts, + "orderId": order_id, + "resultType": "transfer", + } + url_parts = [f"{quote(k, safe='')}={quote(str(v), safe='')}" for k, v in params.items()] + url = f"{RESULT_URL}?{'&'.join(url_parts)}" + + resp = requests.post(url, timeout=30) + return resp.json() + + +def poll_until_done(order_id): + """轮询直到完成""" + print(f"[info] 开始轮询(每 {POLL_INTERVAL_SECONDS} 秒一次)...") + start_time = time.time() + while True: + elapsed = time.time() - start_time + if elapsed > MAX_WAIT_MINUTES * 60: + raise TimeoutError(f"超过 {MAX_WAIT_MINUTES} 分钟未完成") + + data = query_result(order_id) + order_info = data.get("content", {}).get("orderInfo", {}) + status = order_info.get("status") + fail_type = order_info.get("failType", 0) + elapsed_min = elapsed / 60 + print(f"[poll] 已等 {elapsed_min:.1f} 分钟, status={status}, failType={fail_type}") + + if status == 4: + print(f"\n[ok] 转写完成!耗时 {elapsed_min:.1f} 分钟") + return data + if status == -1: + raise RuntimeError(f"转写失败: failType={fail_type}, 数据: {data}") + + time.sleep(POLL_INTERVAL_SECONDS) + + +# ======================================================================== +# 结果解析 +# ======================================================================== + + +def parse_order_result(order_result_str): + """ + 解析嵌套JSON,返回 [(sentence_start_ms, sentence_end_ms, text), ...] + """ + if not order_result_str: + return [] + + cleaned = re.sub(r"\\\\", r"\\", order_result_str) + outer = json.loads(cleaned) + + sentences = [] + for item in outer.get("lattice", []): + inner_str = item.get("json_1best", "") + if not inner_str: + continue + inner = json.loads(inner_str) + st = inner.get("st", {}) + bg = int(st.get("bg", 0)) + ed = int(st.get("ed", 0)) + + words = [] + for rt in st.get("rt", []): + for ws in rt.get("ws", []): + for cw in ws.get("cw", []): + w = cw.get("w", "").strip() + wp = cw.get("wp", "n") + if w and wp != "g": + words.append(w) + sentence = "".join(words).strip() + if sentence: + sentences.append((bg, ed, sentence)) + + return sentences + + +def format_timestamp(ms): + total_sec = ms // 1000 + return f"{total_sec // 60}m{total_sec % 60}s" + + +def write_outputs(sentences, raw_data): + with open("asr_result_raw.json", "w", encoding="utf-8") as f: + json.dump(raw_data, f, ensure_ascii=False, indent=2) + print(f"[ok] 已写 asr_result_raw.json") + + timed_lines = [f"[{format_timestamp(bg)}] {text}" for bg, _, text in sentences] + with open("asr_result_timed.txt", "w", encoding="utf-8") as f: + f.write("\n".join(timed_lines)) + print(f"[ok] 已写 asr_result_timed.txt ({len(timed_lines)} 句)") + + full_text = "".join(t for _, _, t in sentences) + with open("asr_result_full.txt", "w", encoding="utf-8") as f: + f.write(full_text) + print(f"[ok] 已写 asr_result_full.txt ({len(full_text)} 字)") + + +# ======================================================================== +# 入口 +# ======================================================================== + + +def main(): + print("=" * 60) + print("讯飞 录音文件转写(标准版) - 本地脚本") + print("=" * 60) + + if SECRET_KEY.startswith("在此填入"): + print("[error] 请先填写 SECRET_KEY") + return + + print(f"[diag] APP_ID = {APP_ID} (长度 {len(APP_ID)})") + print(f"[diag] SECRET_KEY 长度 {len(SECRET_KEY)}, 首4位={SECRET_KEY[:4]}, 末4位={SECRET_KEY[-4:]}") + print(f"[diag] AUDIO_FILE = {AUDIO_FILE}") + print() + + # 1. 上传 + order_id = upload_audio(AUDIO_FILE) + + # 2. 轮询 + final_data = poll_until_done(order_id) + + # 3. 解析+写文件 + order_result_str = final_data["content"]["orderResult"] + sentences = parse_order_result(order_result_str) + write_outputs(sentences, final_data) + + print("\n" + "=" * 60) + print(f"完成!共识别 {len(sentences)} 句") + print(f"请把 asr_result_timed.txt 发给 Claude 进行三方融合") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/docs/doco/主project对Doco_PRDv2的回复.md b/docs/doco/主project对Doco_PRDv2的回复.md new file mode 100644 index 0000000..cd6dd82 --- /dev/null +++ b/docs/doco/主project对Doco_PRDv2的回复.md @@ -0,0 +1,214 @@ +# 主 project 对 Doco PRD v2 的回复 + +> 来源:TPS 主 project 顾问(Claude Opus 4.7) +> 日期:2026-06-12 +> 针对:`PRD_doco_文稿整理子项目_v2.md` 第四章 5 个问题 +> 性质:正式批复,Doco 子项目据此出 v3 后启动 P1 + +--- + +## 总体意见 + +**5 个问题的子项目推荐**,**整体方向 4 个批准、1 个调整**: +- Q1 OCR 选型:**批准 DeepSeek API,但有附加条件**(见下) +- Q2 ffmpeg 入口:**批准,有补充约束** +- Q3 单期 vs 批量接口:**批准,加交付契约** +- Q4 密钥管理:**调整方案** — 走"自管 + 中台登记 inventory" +- Q5 目录规约:**批准草案,但 episode_id 命名规则需主项目定** + +5.3 §"23 期是否常态化":**这是一个产品问题,我代制片人答不了。子项目可以按"先按 23 期一次性做完、保留兜底回退路径"开干,常态化的事跟栏目组确认后再回头审。** + +--- + +## Q1. OCR 方案选型 — 批准 DeepSeek API + +### 主 project 现状 +- **主 project 没有 OCR 子模块/能力**(已确认)。旧设计里 B 稿来自"上游既有 OCR 流程",指的是栏目组那边人工跑的 OCR,不是 TPS 内部的服务。 +- 主 project `backend/.env` 里有 `DEEPSEEK_API_KEY`,但**只用作 Cline 工具的 Plan/Act 模型**——没有任何业务代码在调 DeepSeek。所以 doco 调 DeepSeek 视觉模型相当于**新增一条业务调用**,不是"复用现有基础设施"。 + +### 批复 + +**Doco 子模块新增 DeepSeek Vision 调用,自管 API key。** + +- API key 独立申请,**不复用 Cline 用的那把 key**。理由:Cline 的 key 是开发工具用的,doco 是生产服务,生命周期、配额、监控都该独立。 +- 6900 张图 × 几厘 / 张 ≈ 50-150 元,在制片人手上的 API 余额范围内,**不存在配额冲突问题**(主 project 业务代码侧没人在花)。 +- 黑底白字字幕场景对 LLM 视觉模型确实是舒适区,**实施前先在 demo 那期视频上跑 10 帧抽样,验证识别准确率达到目标后再扩到 23 期**。 +- **降级路径**:如果 DeepSeek Vision 在某些边角 case 上不达标(比如有同期声同步显示双行字幕、字号变化等),回退到 PaddleOCR 本地。**不要回退到讯飞 OCR**(余额独立 + 单期 ~13 元成本太高)。 + +--- + +## Q2. 视频处理依赖(ffmpeg)入口 — 批准方案 A + +### 主 project 现状 +- **没有视频处理子模块/服务**(已确认),dev_plan 也没规划。 + +### 批复 + +**Doco 自带 ffmpeg 系统依赖 + Python subprocess 调用。** + +附加约束: +- **不引入 `ffmpeg-python` wrapper**——subprocess 足够,不增加新依赖。 +- ffmpeg 命令封装在 `doco/video_split.py` 内,**对外只暴露 Python 函数接口**,不让上层调用方直接 shell 调 ffmpeg。 +- 在 doco 的 `README.md` 明确写"系统依赖:ffmpeg ≥ 4.x"——这是部署的前置条件。 +- 未来如果主 project 真做了统一的视频处理服务,**doco 的封装方式应该使迁移成本最低**(把 subprocess 调用换成 HTTP 调用即可,业务逻辑不动)。 + +--- + +## Q3. 23 期批量 vs 单期接口 — 批准方案 A,加契约 + +### 主 project 现状 +- **没有节目素材批量处理编排层**(已确认)。dev_plan 提到的"批量导入工具 v1/v2"是收视数据和报题单的批量,跟视频处理是两回事。 + +### 批复 + +**Doco 只对单期负责。同时提供 CLI 和 Python API 两套接口。** + +**接口契约(子项目正式实现时按这个出)**: + +```python +# Python API +def process_episode( + episode_id: str, # 调用方给定,doco 不管命名规则 + video_path: Path, # 输入视频 + a_draft_path: Path, # A 稿 docx + output_dir: Path, # 产物存放目录 + cleanup_level: str = "medium", # keep_all / medium / clean +) -> ProcessResult + +# ProcessResult 必含字段 +@dataclass +class ProcessResult: + episode_id: str + status: Literal["success", "partial", "failed"] + final_docx_path: Optional[Path] + diff_report_path: Optional[Path] + needs_review_json_path: Optional[Path] + errors: List[str] + stage_timings: Dict[str, float] # 哪个 phase 用了多久 +``` + +```bash +# CLI 等价形式 +doco process \ + --episode-id ep023 \ + --input-video /path/to/video.mp4 \ + --input-a-draft /path/to/a_draft.docx \ + --output-dir /path/to/output \ + --cleanup-level medium +``` + +**关于 23 期推进**:**先单期跑通再批量**——拿 demo 那期《现代防空反导大对决》视频做 golden test(P4),与 demo 已有产物比对零回归,再扩到 23 期。一次性 batch 23 期风险太大。 + +未来主 project 若开节目素材批量处理编排层,**会反过来调 doco 的单期接口**,不会改 doco。 + +--- + +## Q4. 讯飞密钥管理 — 调整方案:自管 + Inventory 登记 + +### 主 project 现状 +- **没有中台密钥管理子模块**(已确认),也不在 dev_plan 排期内。 +- 主 project 凭证都在 `backend/.env`,各模块走环境变量。 + +### 批复 + +不走子项目推荐的"申请新凭证 + 写入中台密钥资产文档"(因为中台密钥服务不存在,不能凭空捏一个)。改为: + +**1. Doco 自管凭证。** 讯飞、DeepSeek 各自的 API key 放 doco 自己的 `.env`,**不放进主 project 的 `backend/.env`**——保持子模块自治。 + +**2. 主 project 仓库根目录新增一份登记文档:`docs/api_credentials_inventory.md`** + +这份文档: +- **不存储真实凭证**(那些在各自子模块的 .env 里) +- 只存储**元信息**:每个外部 API 用了什么、所属子模块是谁、开通时间、激活状态、到期日、续费责任人 +- 子模块申请新 key、key 到期更换,都更新这份文档 +- 这是主 project 帮所有子模块做"凭证清单"的最小落地——比起"中台密钥服务",轻得多,但解决"忘了谁管哪把 key"的问题 + +**3. Inventory 文档的字段**(供子项目和未来其他子模块参考): + +```markdown +| 子模块 | API 服务 | Key 类型 | 开通日 | 激活状态 | 到期日 | 责任人 | 备注 | +|---|---|---|---|---|---|---|---| +| doco | 讯飞开放平台 - 录音文件转写 | APP_ID + SECRET_KEY | 2026-06-12 | 已激活 5h 试用包 | 2027-06-12 | 制片人 | demo 凭证已过期,新申请 | +| doco | DeepSeek Vision | API_KEY | 2026-06-12 | 已激活 | — | 制片人 | doco OCR 用 | +| (主) | Anthropic Claude API | API_KEY | ... | ... | ... | ... | AI 融合层用 | +``` + +**4. 制片人现在要做的事**(代答 Q4 里"新申请的讯飞开发端密钥应该登记在哪里"): +- 去讯飞开放平台 console 申请新的开发端凭证 +- 走 0 元购买流程激活 5 小时免费试用包 +- 把 APP_ID / SECRET_KEY 写到 doco 的 .env 里 +- 在 `docs/api_credentials_inventory.md` 登记一行 + +--- + +## Q5. 目录结构与命名规约 — 批准草案,episode_id 命名由主项目定 + +### 主 project 现状 +- **没有节目素材目录的统一规约**(已确认)。主 project 处理的是数据维度的 `episodes` 表,episode_id 是 INT 自增主键。 +- 但是,**素材文件名跟数据库 ID 是两层东西**,不能直接套。 + +### 批复 + +**子项目提的 `programs/{episode_id}/source|work|output/...` 结构批准。** + +**关于 episode_id 命名规则**——这是主项目要定的规约,定如下: + +**episode_id 格式:`ep{NNN}_{YYYYMMDD}_{slug}`** + +- `ep{NNN}` = episodes 表自增 INT 主键,零填充到 3 位(如 ep023) +- `{YYYYMMDD}` = 首播日期 +- `{slug}` = 节目名称的拼音缩写或简称中文(无空格、无特殊字符) + +举例: +- `ep023_20260612_haishang_xianfeng` +- `ep007_20260201_马年军事图鉴` + +**理由**: +- INT 主键稳定可关联到 episodes 表 +- 日期方便文件系统按时间排序 +- slug 让人眼可读 + +**约定**: +- episode_id **由调用方给定**(主项目编排层 / 责编 / CLI 调用者),doco 不生成 +- doco 只把它当一个字符串使用 +- 主项目责编/编排层在调 doco 前**先在 episodes 表落库该期、拿到 INT ID**,再拼出 episode_id + +**work/ 中间产物保留时长**: +- 节目入档案库后保留 **30 天**,然后可清理(由编排层负责,doco 不管) +- 出问题需要复查时,30 天窗口够定位 + +--- + +## §5 第 3 条:23 期是常态化吗? + +**这是产品问题,不是技术问题。我代制片人答不了。** + +**建议子项目的处理**: +- 按"23 期一次性"开干,先把这批做完 +- 算法层零侵入是硬保证,即使将来变常态化,流水线也能继续跑 +- **保留旧的 mp3+独立 OCR 流程作为回退路径**(代码不删,加 feature flag 或环境变量切换)。万一某期视频不符合"黑底白字+干净人声"特殊条件,降级到旧流程 +- 23 期完成 + 制片人跟栏目组确认后,再决定是否清理回退路径 + +--- + +## 子项目可以启动的部分 + +按 PRD v2 §七 的回复期望: +- ✅ **Q1 已答**(DeepSeek API + 附加条件) → P1 可开始 +- ✅ **Q2 已答**(subprocess + 不引入 wrapper)→ P1 可开始 +- ✅ **Q3 已答**(单期 + 契约) → P5 推进时按契约 +- ✅ **Q4 已答**(自管 + inventory 登记) → 制片人先申请讯飞新凭证 + 建 inventory,P2 可开始 +- ✅ **Q5 已答**(目录草案批准 + episode_id 规则) → P1 输出有处可放 + +**Doco 子项目现在可以出 PRD v3,启动 P1。** + +--- + +## 顺便给子项目的两条提醒 + +1. **demo 的 5 份 fixture 必须进 git**(transcripts / A 稿 / B 稿 / ASR / 终版 / 差异报告),作为单元测试 + golden test 黄金对照。这件事属于 Doco 内部自治,主项目不管,但提醒一下。 +2. **AI 融合层模型选 Sonnet 4.6**(PRD v2 §二 已锁),不要在子项目里临时换成别的模型。主项目这边也不会推翻这个选择。 + +--- + +*主 project 顾问(Claude Opus 4.7)签发,2026-06-12* diff --git a/docs/doco/实测复盘.md b/docs/doco/实测复盘.md new file mode 100644 index 0000000..69608ad --- /dev/null +++ b/docs/doco/实测复盘.md @@ -0,0 +1,151 @@ +# 《现代防空反导大对决》三方融合demo实测复盘 + +> 日期:2026年5月13日 | 报告人:Claude | 节目时长:26分钟 | 实施耗时:约2小时 + +--- + +## 一、ASR环节实测数据 + +### 1.1 服务选型最终结果 + +**最终采用:讯飞开放平台「录音文件转写(标准版)」** + +| 维度 | 实测结果 | +|---|---| +| 转写耗时 | 5分钟左右(26分钟音频) | +| 识别句子数 | 307句(含片尾彩蛋) | +| 主体节目识别 | 297句对齐节目主体 | +| 关键术语识别率 | **接近100%**(开启35条热词后) | +| 数字/百分比识别 | 100%准确 | +| 文档显示价格 | 5小时免费试用包,1年有效期 | + +### 1.2 ASR亮点 + +- 讯飞自动加入了合理的标点(句号、逗号、问号) +- 开启 `eng_smoothproc=true` 和 `eng_colloqproc=true` 后,已自动清理了大部分"嗯/那个/呃"等口语词 +- **热词机制效果显著**:`霍拉姆沙赫尔-4` `见证者-136` `塔米尔` `大卫投石索` `本古里安` `拉斐尔` 等生僻军事术语全部正确识别 +- 句子级时间戳精准,可直接与B稿做时间对齐 + +### 1.3 ASR短板(需要A稿术语表后处理修正) + +| 错误类型 | 实例 | 修复依据 | +|---|---|---| +| 同音误识 | "中端/末端" → 应为"中段/末段" | A稿术语 | +| 同音误识 | "中诚防御" → 应为"中程防御" | A稿术语 | +| 事实错误 | "美伊两国" → 应为"美以两国"(最严重) | A稿/语境 | +| 表述偏好 | "高超声速" → A稿用"高超音速" | A稿术语统一 | +| 百分比表达 | "百%" → 应为"100%" | 后处理规则 | +| 数字表达 | "数10" → 应为"数十" | 后处理规则 | +| 口语词残留 | 专家发言里仍有"啊呢这个" | 部分保留以反映播出状态 | + +### 1.4 关于'美伊→美以'这类事实错误的反思 + +ASR第18行把"美以两国"识别成了"美伊两国"。**这是最危险的一类错误**——拼音相同、字形不同,单独看ASR文本完全像是合法的事实陈述("美国和伊朗两国协同向伊朗发起军事打击"语义虽然荒谬但句子通顺),人工审校如果不熟悉上下文也可能漏过。 + +这印证了交接说明里的设计判断:**A稿的术语和事实必须作为权威源参与融合**,不能让ASR单独输出,否则会引入危险的事实错误。 + +--- + +## 二、三方融合环节关键决策点 + +### 2.1 把握大的判断(demo中AI判断高确信度) + +1. **整段删除的识别**:A稿【解说2】末尾'雷达被誉为中段反导体系的眼睛...'在B稿和ASR里完全找不到对应内容 → 信号非常明确,删除 +2. **整段重复笔误**:A稿【专家7】和【专家8】内容字符完全相同 → 编导笔误,按ASR的真实内容(讲效费比)替换【专家8】 +3. **术语事实错误的修复**:ASR'美伊→美以'类同音错误 → A稿权威覆盖 +4. **OCR错字的批量修复**:'肘→时''娈→变''夭→天'等字形混淆 → ASR提供正确版本 + +### 2.2 把握较小的判断(demo中AI判断中等确信度,未来需要编导确认) + +1. **解说6"整段改写"的边界**:A稿80字版本和ASR/B稿40字版本,**究竟保留哪个细节合并哪个细节**——目前demo直接采用了ASR的40字版本,但A稿那80字版本里有"信任危机""推迟采购方案"等A稿独有的内容,编导可能想保留。**未来应该让编导在UI上看diff后人工确认** +2. **专家发言的口语词处理**:'呢''啊'保留多少?太多影响阅读,太少违背播出真相。本次demo保留较多,倾向"播出真相"。**未来可做个可调档位("完全保留口语 / 中度清理 / 完全书面化"三档)** +3. **A稿首句被改写的情况**:A稿"观众朋友们,你们好!欢迎来到这一期的《军事科技》节目",播出时改为了"各位观众,你们好!欢迎收看《军事科技》"——句式接近但用词全变。demo靠手工锚点列表匹配上了,未来要让Claude做语义对齐而非字符匹配。 + +### 2.3 把握很小的判断(demo中AI暂时不处理,必须人工确认) + +1. **句子级的微小增删**:如A稿"在涉及远、中、近程"和ASR"在涉及远中近程"——只是顿号差异。这种差异融合时按ASR走、还是按A稿格式来?**未来UI应该展示句子级diff让编导决定** +2. **专有名词的简写问题**:A稿严格用"爱国者-3",播出时多数说"爱国者"。这是术语统一问题还是口语化问题?**取决于该期节目的风格定位** + +--- + +## 三、对正式开发TPS子模块的启示 + +### 3.1 模块架构建议 + +``` +[音频上传] → [ASR服务(讯飞)] → [OCR处理(B稿)] → [三方融合引擎] → [编导确认UI] → [终版docx] + ↓ + [大模型API(Claude)] +``` + +**核心是"三方融合引擎"应当分两层**: +- **规则层**:OCR错字纠正(固定映射表)、术语统一(A稿术语权威)、时间戳对齐 — 走Python代码 +- **AI层**:段落对齐、整段改写判定、'编导笔误'识别 — 走大模型API + +### 3.2 关于讯飞ASR的具体集成建议 + +**应该写进project_design.md【外部依赖】章节的内容**: + +1. 优先使用讯飞「录音文件转写(标准版)」API,**不要**用大模型版(免费包阉割了language参数) +2. 音频必须先转码为 **16kHz / 单声道 / 16bit WAV**,否则讯飞会报误导性的`language verify fail`错误 +3. **热词词库**是关键能力:每期节目从A稿提取专业术语作为热词上传给ASR +4. **领域参数** `pd=mil` 对军事节目有显著效果 +5. 开启 `eng_smoothproc=true` + `eng_colloqproc=true` 让ASR输出更接近书面文稿 +6. 讯飞密钥分两套:消费端(讯飞听见App)和开发端(讯飞开放平台),**互不相通** +7. "免费5小时"需要走0元购买流程才激活,不是看到额度就能用 + +### 3.3 关于大模型融合提示词的设计 + +未来正式开发时调Claude API做段落对齐,prompt应该包含: + +- **输入1**:A稿全文 + 标签序列 +- **输入2**:ASR结果(句子+时间戳) +- **输入3**:B稿OCR结果(句子+时间戳,作为交叉验证) +- **任务1**:把ASR句子按A稿标签序列归类 +- **任务2**:对每段标记改动类型(无变化/细微改写/整段改写/整段删除/整段新增) +- **任务3**:对'高把握'改动直接产出终版;对'低把握'改动标记原因,等编导确认 +- **任务4**:识别A稿术语和ASR结果的字形/同音差异,用A稿权威修正 + +### 3.4 关于编导确认UI的设计 + +借鉴本次demo发现的痛点,UI应该至少提供: + +- **左中右三栏diff视图**:A稿原文 | 终版 | ASR/B稿(带时间戳,可点击播放对应音频片段) +- **改动类型颜色编码**:删除红色 / 改写黄色 / 术语修正蓝色 / 无变化白色 +- **逐段确认按钮**:接受 / 拒绝 / 编辑 / 标记疑问 +- **口语清理强度滑杆**:让编导决定保留多少口语词 +- **导出按钮**:终版docx / 差异报告 / 复盘CSV + +--- + +## 四、实测耗时分解(用于未来效率评估) + +| 阶段 | 实测耗时 | 主要消耗 | +|---|---|---| +| 讯飞ASR申请+踩坑 | 约45分钟 | 大模型版语种权限问题、音频规格转码 | +| 老版讯飞ASR调通 | 约15分钟 | 申请+脚本+调用 | +| 三方解析 | 约5分钟 | A稿/B稿/ASR解析为统一结构 | +| 段落对齐 | 约15分钟 | 锚点匹配算法+单调推进 | +| 术语规范化+口语清理 | 约5分钟 | 字符级规则映射 | +| 终版docx生成 | 约5分钟 | python-docx写文档 | +| 差异报告+复盘 | 约15分钟 | 这份报告本身 | + +**总耗时约2小时**,其中近一半花在讯飞踩坑上——正式开发后这部分会清零,每期实际处理时间预计15-30分钟(取决于改动幅度)。 + +--- + +## 五、给您(刘统制片)的几点直接建议 + +1. **TPS正式开发时,不必把ASR作为强依赖**。本次demo证明,即便没有ASR,A稿+B稿做二方融合也能产出接近播出版的终版。ASR的价值在于"独立第三方仲裁"——值得做但不应阻塞主流程。 + +2. **A稿术语表应该入库管理**,作为TPS的核心知识资产之一。每期节目处理时都从该表查找"权威术语",自动覆盖ASR的同音错误。本次demo手工列出的35个热词清单是个起点。 + +3. **节目编导习惯的差异要预留可配置项**。比如有的编导喜欢保留口语词、有的喜欢全部规整化;有的喜欢保留专家发言的原始措辞、有的喜欢编辑过的书面版本。这些应该在配置文件里,而不是硬编码。 + +4. **差异报告的价值可能比终版docx更大**。每期节目处理完,差异报告其实是个"编导工作回顾"——能看到自己当初做了哪些删改、哪些是OCR错字。长期累积下来可以分析编导的删改偏好,作为节目优化的素材。 + +5. **本次踩的坑值得专门整理一个"讯飞集成踩坑笔记"文档**入TPS知识库。未来下次开发新供应商接入时(比如阿里云ASR),可以走更顺的路。 + +--- + +*本复盘为demo阶段产物,不代表正式开发的最终决策。所有结论以下次设计文档为准。* diff --git a/docs/跨子项目协作规则.md b/docs/跨子项目协作规则.md new file mode 100644 index 0000000..90e12aa --- /dev/null +++ b/docs/跨子项目协作规则.md @@ -0,0 +1,38 @@ +# 跨子项目协作规则 + +> 主项目维护的通用规则,所有外迁子项目都适用。 +> 起始日期:2026-06-12 + +--- + +## 规则 1:PRD 版本管理(立于 Doco 立项时) + +### 1.1 主 project 这边的规则 + +- **只保留最新版 PRD + 对应回复**,旧版本一律删 +- **PRD 与回复一一配对**:PRD v2 ↔ 回复 v2;PRD v3 ↔ 回复 v3 +- 任何时点,主 project 不允许同时存在同一子项目的两个 PRD 版本 + +### 1.2 子 project 那边的规则 + +- **保留所有版本**(包括 PRD v0 / v1 / v2 / v3...) +- 旧版本可移到 `archive/` 子目录或加 `_archived` 后缀,但不删 +- 每份新 PRD 在元信息块标注"上一版:vN" + +### 1.3 版本翻新的操作 + +子项目出 PRD vN+1 时: +1. 制片人在主 project 这边**先删** PRD vN + 回复 vN +2. **再传** PRD vN+1 + 主项目顾问写的回复 vN+1 +3. 一次性,不允许"先传再删"造成短暂双版本状态 + +### 1.4 适用范围 + +- 当前所有外迁子项目(看板升级 / Doco 等) +- 未来新立项的子项目沿用 + +--- + +## (未来其他通用规则在这里累加) + +> 后续规则按 `## 规则 2:xxx` `## 规则 3:xxx` 顺序追加。 diff --git a/note/续接快照_看板分析升级_设计讨论.md b/note/续接快照_看板分析升级_设计讨论_v1.md similarity index 100% rename from note/续接快照_看板分析升级_设计讨论.md rename to note/续接快照_看板分析升级_设计讨论_v1.md diff --git a/note/续接快照_看板分析升级_设计讨论_v4.md b/note/续接快照_看板分析升级_设计讨论_v4.md new file mode 100644 index 0000000..477b2fb --- /dev/null +++ b/note/续接快照_看板分析升级_设计讨论_v4.md @@ -0,0 +1,377 @@ +# 续接快照 — 看板分析升级 v4(AI 打标流水线落地 + 模型选型完成) + +> 用途:下次新 chat 接力用。承接 2026-06-09 v1 + 2026-06-10 v2 + 2026-06-11 上午 v3,包含 2026-06-11 一整天讨论。 +> 状态:AI 打标流水线跑通;Prompt 2 v0.2 锁定;ground-truth v0.2.1 锁定;模型选型完成 → **MiniMax M3 + Prompt v0.2 = 100% 命中率**;Git 已推 Gitea 落库。 +> 更新时间:2026-06-11(晚) +> 顾问:Claude Opus 4.7 | 制片人:刘通 + +--- + +## 零、本快照与 v1/v2/v3 的关系 + +v1/v2/v3 全部结论仍然有效,**v4 是增量,不替换**。今日新增 6 件大事: + +1. AI 打标流水线从纸面落到能跑(脚本 + 配置 + 文稿入库 + 汇总工具) +2. Prompt 2 升级到 v0.2(加"段内承接 ≠ 全片骨架"陷阱教学) +3. ground-truth 经两轮复议升级到 v0.2.1(三期改判) +4. 三家模型在同一份"试卷"上公平对比完成 +5. 模型选型拍板:**MiniMax M3** +6. Git 双 commit 推送 Gitea 落库(commit `38f3728` 是今天的里程碑) + +--- + +## 一、今天的时间线(2026-06-11 一日) + +| 阶段 | 关键动作 | 产出 | +|---|---|---| +| 上午 | 工作区目录搭建 + 10 期文稿导入 + ep04 单跑 | 9/10 自动导入成功,ep05 .doc 制片人手工另存补全 | +| 中段 | 发现 reasoning model 的 `` 标签 bug → 修 `extract_json_from_response` | JSON 解析链路稳定 | +| Prompt v0.1 全跑 | M3 跑 10 期 | **5/10 = 50%**,全部"高置信" | +| 复议 1 | ep05/13/14 维持并列,ep10/15 改主线演进 | ground-truth v0.2 | +| Prompt 升级 | 加"段内承接陷阱"+ `chapter_list` + `shuffle_test` 强制思考字段 | Prompt 2 v0.2 | +| M3 二跑 | 9/10 = 90%,唯一错 ep12 | 锁定 v0.2 不再调 | +| DeepSeek V4 Pro | 70%,错的全是"应主线判并列",偏保守 | — | +| MiMo v2.5 Pro | 60%,两向都错 | — | +| 复议 2 | ep12 三家独立都判并列 → ground-truth 改 | ground-truth v0.2.1 | +| 最终战绩 | **M3 100% / DeepSeek 80% / MiMo 70%** | 选 M3 | +| 收尾 | Git 双 commit 推 Gitea | commit 38f3728 + edfb298 | + +--- + +## 二、AI 打标流水线已落地资产 + +### 2.1 工作目录(`E:\tps-dashboard\ai-labeling\`) + +``` +ai-labeling/ +├── benchmark-set/ +│ ├── transcripts/ ← 10 期文稿 .md(基准卷子) +│ │ ├── ep03_硅基大脑.md +│ │ ├── ep04_潜艇仿生.md +│ │ ├── ep05_舰证不凡.md +│ │ ├── ep07_马年图鉴.md +│ │ ├── ep10_射速决定论.md +│ │ ├── ep11_空战颠覆者.md +│ │ ├── ep12_逆袭战局.md +│ │ ├── ep13_长短智慧.md +│ │ ├── ep14_X76飞机.md +│ │ └── ep15_空中坚盾.md +│ └── ground-truth.json ← 基准答案 v0.2.1 +├── prompts/ +│ └── prompt2_narrative.md ← 叙事结构判别 v0.2(5 示例,含两个陷阱反例) +├── experiments/ ← 跑批结果 .json(.gitignore 拦截,不入 git) +├── scripts/ +│ ├── import_transcripts.py ← docx/doc → md 解析 + 清洗 +│ ├── run_labeling.py ← 单期或批量调模型 + 写文件 +│ └── summarize.py ← 扫 experiments/ 汇总命中率 +├── .env.example ← 入 git 的 Key 占位模板 +├── .env ← 不入 git,真 Key +├── .gitignore ← 拦 .env / experiments / __pycache__ +└── README.md +``` + +### 2.2 三个关键脚本的分工 + +| 脚本 | 干什么 | 用法 | +|---|---|---| +| `import_transcripts.py` | 把源 docx/doc 拷+解析+清洗成 .md,落 transcripts/ | 已跑过一次,10 期都在;新文稿要扩源目录映射表 | +| `run_labeling.py` | 调模型,把 result + ground_truth 写进 experiments/ 一个 .json | `python scripts/run_labeling.py --ep N --model m3` | +| `summarize.py` | 扫 experiments/ 下指定 model 的最新 json,出 10 行命中表 + 汇总 | `python scripts/summarize.py --model m3` | + +PowerShell 批量套路(模型选型阶段用,以后可作为标准跑法): +```powershell +cd E:\tps-dashboard\ai-labeling +3, 4, 5, 7, 10, 11, 12, 13, 14, 15 | ForEach-Object { python scripts/run_labeling.py --ep $_ --model m3 } +python scripts/summarize.py --model m3 +``` + +### 2.3 MODEL_CONFIG 已配置的三家(`scripts/run_labeling.py`) + +```python +MODEL_CONFIG = { + "m3": { + "base_url": "https://api.minimaxi.com/v1", + "model_name": "MiniMax-M3", + "api_key_env": "MINIMAX_API_KEY", + }, + "deepseek-v4-pro": { + "base_url": "https://api.deepseek.com", # 注意:不带 /v1 + "model_name": "deepseek-v4-pro", + "api_key_env": "DEEPSEEK_API_KEY", + }, + "mimo-v2.5-pro": { + "base_url": "https://api.xiaomimimo.com/v1", + "model_name": "mimo-v2.5-pro", + "api_key_env": "MIMO_API_KEY", + }, +} +``` + +依赖锁定(`scripts/requirements.txt`): +``` +openai==1.55.0 # 不能升级,新版自动转 developer 角色,MiniMax 不支持 +python-dotenv>=1.0.0 +python-docx>=1.1.0 +``` + +--- + +## 三、Prompt 2 v0.2 核心设计 + +(完整文件见 `ai-labeling/prompts/prompt2_narrative.md`,以下是要点摘录) + +### 3.1 两档定义(收为 2 档,沿用 v3) + +- **主线演进**:全片由"贯穿主线"串起来。主线可以是 a) 内在概念 / b) 时间因果链 / c) 性能阶梯 +- **并列结构**:章节相互独立、可替换,共性只来自分类共性 + +### 3.2 关键边界(共 4 条 + 1 条核心新增) + +1. 命名共性不构成主线 +2. 装备域共性不构成主线 +3. 空间方位共性不构成主线 +4. **【v0.2 新增,最重要】段内承接 ≠ 全片骨架**——文稿里某段出现"递进""演进""阶梯""奠定基础"字样,**往往只是段内微观承接**,不是全片骨架。先识别全片大章节,再做章际打乱测试。 + +### 3.3 强制判断流程(v0.2 新增) + +模型必须先输出 `chapter_list`(全片 3-5 个大章节)+ `shuffle_test`(打乱测试结论),才能下判断。避免它一看到"递进"两字就秒判主线演进。 + +### 3.4 5 个示例(2 主线 + 3 并列,平衡分布) + +| 示例 | 类型 | 教学重点 | +|---|---|---| +| 潜艇仿生 | 主线演进-内在概念 | 同一命题贯穿全片 | +| 空战颠覆者 | 主线演进-时间线 | 严格按时间序推进 | +| 马年军事图鉴 | 并列结构-命名共性 | 高收视不靠结构,靠节点策划 | +| **舰证不凡** ⚠️ | **并列结构-警惕段内承接陷阱** | "递进式"字样在段内 ≠ 全片主线 | +| **X-76 飞行器** ⚠️ | **并列结构-警惕段内时间线陷阱** | 时间线只在"背景"段里 ≠ 全片骨架 | + +### 3.5 JSON 输出 schema(新增两个强制思考字段) + +```json +{ + "narrative_structure": "主线演进 或 并列结构", + "chapter_list": ["大章节1", "大章节2", ...], ← v0.2 新增 + "shuffle_test": "打乱后观众怎样的一句话", ← v0.2 新增 + "main_thread": "主线演进时点明主线,并列填 null", + "reasoning": "判断理由,如果文稿有'递进'等字样必须说明是段内还是全片", + "key_evidence": ["证据片段1", "证据片段2"], + "confidence": "高 或 中 或 低", + "confidence_reason": "为什么这个置信度" +} +``` + +--- + +## 四、ground-truth v0.2.1 锁定的 10 期分布 + +| ep | 节目 | share | 叙事结构(v0.2.1) | 开篇钩子 | v0.1 → v0.2.1 变迁 | +|---|---|---|---|---|---| +| 3 | 硅基大脑 | 0.85 | 并列结构 | 中 | 未变 | +| 4 | 潜艇仿生 | 0.92 | 主线演进 | 强 | 未变 | +| 5 | 舰证不凡 | 0.72 | 并列结构 | 中 | 未变(经讨论后维持) | +| 7 | 马年军事图鉴 | 0.95 | 并列结构 | 强 | 未变 | +| 10 | 射速决定论 | 0.703 | **主线演进** | 弱 | v0.1 并列 → v0.2 主线(性能阶梯) | +| 11 | 空战颠覆者 | 0.859 | 主线演进 | 中 | 未变 | +| 12 | 逆袭战局 | 0.85 | **并列结构** | 强 | v0.1 主线 → v0.2.1 并列(三家独立都判并列) | +| 13 | 长短智慧 | 0.533 | 并列结构 | 中 | 未变(经讨论后维持) | +| 14 | X-76 飞行器 | 0.873 | 并列结构 | 中 | 未变(经讨论后维持) | +| 15 | 空中坚盾 | 0.875 | **主线演进** | 强 | v0.1 并列 → v0.2 主线(层级承接) | + +**最终分布:5 主线 / 5 并列,平衡。** 此前 v0.1 是 3 主线 / 7 并列。 + +--- + +## 五、三家模型最终对比(基于 v0.2.1 修订版试卷) + +### 5.1 命中率总表 + +| 期次 | 修订后标准 | **M3** | **DeepSeek V4 Pro** | **MiMo v2.5 Pro** | +|---|---|---|---|---| +| ep03 | 并列 | ✓ | ✓ | ✓ | +| ep04 | 主线 | ✓ | **✗** | ✓ | +| ep05 | 并列 | ✓ | ✓ | **✗** | +| ep07 | 并列 | ✓ | ✓ | ✓ | +| ep10 | 主线 | ✓ | **✗** | **✗** | +| ep11 | 主线 | ✓ | ✓ | ✓ | +| ep12 | 并列 | ✓ | ✓ | ✓ | +| ep13 | 并列 | ✓ | ✓ | ✓ | +| ep14 | 并列 | ✓ | ✓ | ✓ | +| ep15 | 主线 | ✓ | ✓ | **✗** | +| **命中率** | | **10/10 = 100%** | 8/10 = 80% | 7/10 = 70% | +| **错的方向** | | 无 | 全部"应主线判并列",**偏保守** | 两个方向都错 | + +### 5.2 选型结论:MiniMax M3 完胜 + +理由不只在命中率,更在质量层: + +| 维度 | M3 | DeepSeek | MiMo | +|---|---|---|---| +| 命中率 | **100%** | 80% | 70% | +| 三种主线识别能力(内在概念/时间线/性能阶梯/层级承接) | **全识别** | 漏识别 ep04(示例 1 直接教过的潜艇仿生都没认出来) | 漏 2 期主线 | +| Few-shot 学习力 | 强 | **弱**(连示例 1 同型的 ep04 都漏) | 中 | +| reasoning 质量 | chapter_list + shuffle_test + 时序验证完整 | 一般 | 一般 | +| 系统偏差 | 无 | **偏保守** | 偏向不稳定 | + +**致命差距**:DeepSeek 连 Prompt 里"示例 1 直接教过的潜艇仿生"都没认出来——这说明它对 few-shot 示例的"听话度"较差,以后加再多反例也可能漏。 + +### 5.3 落地选型(冻结) + +- **生产模型**:MiniMax M3 +- **Prompt**:Prompt 2 v0.2(`ai-labeling/prompts/prompt2_narrative.md`) +- **基准卷子**:ground-truth v0.2.1(`ai-labeling/benchmark-set/ground-truth.json`) +- **回退方案**:如 M3 服务中断,DeepSeek V4 Pro 备选(虽然命中率较低,但稳定可用) + +--- + +## 六、关键经验教训(下次接力的我必读) + +### 6.1 reasoning model 的输出格式坑 + +MiniMax M3 是 reasoning model,输出格式是: +``` + +模型的内部推理过程(英文,几百字) + + +{真正的 JSON 输出} +``` + +直接 `json.loads(content)` 会失败。脚本里 `extract_json_from_response` 用正则剥掉 `...` 后再解析。**未来如果换其他 reasoning model(R1、o1 系列等)同样适用。** + +### 6.2 段内承接 vs 全片骨架(Prompt 2 v0.2 的灵魂) + +模型最容易栽的跟头是看到"递进""演进""阶梯"几个字就秒判主线演进。但这些字样常常**只在某一段内**(讲两件装备先后、某段背景的时间线等),不是全片骨架。 + +判别口诀(已写进 Prompt 2):**先看全片大章节(树干),再看段内细节(树叶)**。章节顺序能不能打乱才是主线判别的唯一依据。 + +### 6.3 ground-truth 是活的,不是写死的 + +v0.1 → v0.2(改两期)→ v0.2.1(改一期),共改三期。每次修改都因为发现 v0.1 标的判断与 v3 §1.1 自己定的判别规则有冲突,或者多家模型独立得出反向结论。 + +**纪律**:基准答案集每次升级,changelog 必须详细记录变更原因,不能"悄悄改"。未来用 pgvector 检索做动态示例集时,固化示例必须明确版本号 + 复议历史。 + +### 6.4 同一份卷子比三家 vs 优化单家 + +诱惑很大:M3 90% 后只差一题,继续调 Prompt 上 100% 应该不难。但**这样会过度适配 M3 的脾气**——同一个 Prompt 跑 DeepSeek/MiMo 反而下降。 + +实践证明:v0.2 Prompt 上 M3 100%,DeepSeek 也吃这套教学(从 70% 升 80%),MiMo 也吃(从 60% 升 70%)。**好的 Prompt 不是为某一家定制,而是把判别规则讲清楚,三家都受益。** + +### 6.5 MiniMax M2.7(Cline 的 Plan/Act 模型)写长文件不稳定 + +中段 Cline 改 run_labeling.py 时撞过: +- `write_to_file` 长内容截断 +- 改用辅助脚本生成主文件 +- 改用 base64 编码绕过 +- 触发 MiniMax 自己的 context 限制 + +**纪律**:涉及 ai-labeling/ 内文件改动,**老文件用 `replace_in_file` 局部改,新文件用 `write_to_file` 整体写**(<200 行才稳)。Prompt 和 ground-truth 这种长文件,**让制片人手工替换最稳**。 + +### 6.6 边界期是制片人 + 多家模型共审拍板 + +ep12 是个标志性案例:制片人 v0.1 标主线演进,M3/DeepSeek/MiMo 三家独立都判并列结构,经制片人复读稿后改并列。**这种"多家独立一致 + 人工复审"的双重证据机制,未来扩展示例集时是黄金流程。** + +--- + +## 七、待办池(追加到 `backlog.md`) + +继承 v3 §六的 4 条 + v4 新增 4 条: + +### 继承 v3 §六 + +- **F1. AI 打标示例集更新纪律【优先级:中,上线后启用】** + 示例集每月集中替换一次,不碎片化更新(吃 prompt 缓存红利) + +- **F2. Prompt 1(4 分类字段)+ Prompt 3(开篇钩子)撰写【优先级:中,Prompt 2 已验证通过,可启动】** + 按 Prompt 2 同样套路:Opus 写 → M3 跑基准 → 制片人审 → 锁定 + +- **F3. Decision 3 横轴量化方法落地【优先级:中】** + v2 §3 已基本定(栏目均值偏离),但还没真正算过 23 期具体数值 + +- **F4. 23 期回填确认(继承 v2 §5.5)** + ep15 空中坚盾原"空军防空"→确认改 `导弹与反导`? + ep16 启程远航装备域缺失,疑似 `海军舰船` + ep18 海上先锋·无人舰艇 → `海军舰船` + tech_tag=`无人作战` + +### v4 新增 + +- **F5. opening_hook 字段尚未跑过 AI 验证【优先级:中,等 Prompt 3】** + 10 期 ground-truth 里 opening_hook 是制片人读稿确认的,但还没让 AI 试着判过。Prompt 3 上线后跑一次,对答案看 M3 在开篇钩子上的命中率。 + +- **F6. 改 episodes schema 加 6 字段【优先级:高,但必须先切 Plan + Opus 审】** + 字段:`program_format / equipment_domain / scene_tags / tech_tags / narrative_structure / opening_hook` + 1 个 `confidence` 字段。**纪律**:Cline 不许直接 Act,必须先 Plan + 我审过迁移方案。回补方案包括 23 期已标注的回填脚本。 + +- **F7. UI 必须区分置信度三档【优先级:中,Phase 4a/4b 看板设计时落地】** + 特别警惕"AI 自评高置信但实际错判"的样本(模型不知道自己错了),UI 上要让制片人能一眼分辨"AI 草稿/制片人确认/标题推断"三档。 + +- **F8. prompt 缓存月度更新机制【优先级:低,上线后启用】** + v3 §3.7 已经写过纪律,执行时机是 AI 打标系统上线后第 1 个月起。 + +**追加方式**:你看完后告诉我,我给一段 Cline 指令把 F1-F8 这 8 条追加到 `backlog.md`(如果文件不存在就新建)。 + +--- + +## 八、纪律红线(沿用 v3 §七 + v4 增补) + +沿用: +- 改表须 Opus 审,Cline 不得自行加字段 +- AI 只出草稿、给方向,人拍板 +- 不做编导排名(团队视角走"能力地图") +- 未审核字段不上看板,编导只看已确认数据 +- AI 打标三个 prompt 独立,不合并 +- 示例集每月集中更新,不碎片化(吃缓存红利) +- Prompt 内容不让 Cline 创作——Cline 是执行者不是作者,Prompt 由 Opus 写、Cline 落文件 + +**v4 新增红线**: + +- **AI 打标实验工作区(`ai-labeling/`)与生产代码(`backend/`)严格隔离** +- **`ai-labeling/` 内改动允许 Cline 直接 Act**(沙盒,改坏重跑就是) +- **`backend/` 改动必须切 Plan + Opus 审**(生产代码,改 schema 加字段更要严) +- **长文件改动失败时,让制片人手工替换最稳**(避免 Cline 在 write_to_file 截断时套娃式 workaround) +- **改 ground-truth 必须留 changelog**(谁、何时、改了什么、为什么改) + +--- + +## 九、技术备忘(沿用 v3 §八 + 新增) + +沿用: +- 栈:React18 + Vite + AntD5 + Zustand | FastAPI + SQLModel + PG16 + pgvector +- 图表:ECharts / @ant-design/charts +- 配色固化:米色底 `#fbf9f1`、深绿主色 `#6b8e6b` +- 三色色板:红 `#c0584f` / 蓝 `#5b8db8` / 绿 `#7aa874` +- 基础线 0.6448 / 摸高线 0.8989(2026) + +v4 新增: +- AI 打标模型选型:**MiniMax M3** + `ai-labeling/prompts/prompt2_narrative.md` v0.2 +- `summarize.py` 是模型选型基准工具,未来切其他模型继续用 +- `experiments/*.json` 不进 git(.gitignore 拦截) +- `ground-truth.json` + `transcripts/*.md` 进 git(栏目核心资产) +- `requirements.txt` 锁 openai==1.55.0(不要升级) + +--- + +## 十、Git 状态 + +里程碑 commit(2026-06-11 晚): + +``` +edfb298 chore: TPS 主项目素材积累(海报 logo + 样本 md) +38f3728 feat(ai-labeling): AI 打标实验工作区初始化 + 模型选型完成 ← 今天的硬核成果 +ae04c7f chore: gitignore 忽略前端测试题图截图 +ae9581e docs: 新增 claude 协作原则文件,backlog 待办池入库 +b942907 docs: phase3_log 收尾,Task 3/4 记录归位 +``` + +`ai-labeling/` 已在 Gitea 落地,任意时点可回溯。`.env` / `experiments/` / `__pycache__/` 全部被 `.gitignore` 拦住,无敏感泄露。 + +--- + +## 十一、下次开局第一句 + +> "续接 v3 → v4。AI 打标流水线已跑通,**MiniMax M3 + Prompt 2 v0.2 在 ground-truth v0.2.1 上命中率 100%**,模型选型完成。下一步推 Prompt 1(4 分类字段)+ Prompt 3(开篇钩子),按 v4 同样的'Opus 写 Prompt → M3 跑 10 期基准 → 制片人审 → 锁定'三步走。**改 episodes schema 加 6 字段之前必须切 Plan + Opus 审**——这是宪法红线,不能直接 Act。" + +--- + +## 附:今日核心结论一句话总结 + +> AI 打标流水线从纸面落地;Prompt 2 v0.2 + ground-truth v0.2.1 经两轮复议锁定;三家模型公平对比(M3 100% / DeepSeek 80% / MiMo 70%)后选 MiniMax M3 为生产模型;Git 双 commit 推 Gitea 落库(里程碑 commit `38f3728`)。明天起可以推 Prompt 1 + Prompt 3,但改 episodes schema 必须先 Plan + Opus 审。