From 30931d444625d42e715c47a676ce26c933116b50 Mon Sep 17 00:00:00 2001 From: simonkoson <28867558@qq.com> Date: Wed, 24 Jun 2026 17:53:05 +0800 Subject: [PATCH] =?UTF-8?q?ai-labeling:=20ground-truth=20v0.3.0=20+=20?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E6=89=A9=E5=B1=95=20--field=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20Prompt=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ground-truth.json 新增 4 分类字段骨架(program_format/equipment_domain/scene_tags/tech_tags) - equipment_domain 类型修正为数组(对齐 v2 快照多选定义) - run_labeling.py 新增 --field 参数,文件名含 field 标记 - summarize.py 新增 --field 参数 + 老文件向后兼容 - CLAUDE.md 补入 v1-v4 快照精华:象限图规格、6 字段枚举、三层交付策略 Co-Authored-By: Claude Opus 4.6 --- ai-labeling/CLAUDE.md | 96 ++++++++++++++++++--- ai-labeling/benchmark-set/ground-truth.json | 25 +++--- ai-labeling/scripts/run_labeling.py | 40 ++++++--- ai-labeling/scripts/summarize.py | 75 ++++++++++------ 4 files changed, 174 insertions(+), 62 deletions(-) diff --git a/ai-labeling/CLAUDE.md b/ai-labeling/CLAUDE.md index ac87035..c15f10e 100644 --- a/ai-labeling/CLAUDE.md +++ b/ai-labeling/CLAUDE.md @@ -38,18 +38,72 @@ **质量标杆**:`example/` 目录下的 PPT(14 页诊断报告)和 Excel(8 个分析 Sheet)是制片人与 Opus 4.8 手工做出的 21 期阶段性分析成果。看板升级要达到**同等分析深度**,并把"一次性手工分析"变成"累积持续自动分析"。 -### 1.2 核心理论框架——"双引擎模型" +### 1.2 核心理论框架——"双引擎 + 开篇钩子" -**收视 = 题材热度 × 叙事结构** +> 收视 ≈ 题材热度(横轴)× 叙事结构(纵轴)+ 开篇钩子(独立维度,叠加显示) - **引擎 1 题材热度(地基)**:热点/新装备/大舰/节日天然有人看。即使叙事并列,热点也能撑住收视(如防空网 0.875、X-76 0.873)。 - **引擎 2 叙事结构(放大器)**:有没有贯穿全片的主线悬念。强主线能把冷题材拉到高收视(如仿生潜艇 0.92、组装武器 0.85)。 +- **开篇钩子(独立维度)**:前 1-2 分钟决定观众换不换台。功能与叙事结构不同(开篇=留住,结构=留住之后的体验),AI 打标也分开判。 **两条腿至少占一条**:冷题材 + 并列结构 = 必然低分(如枪械图鉴 0.533)。 +**"话题性"已拆解消化**(v2 快照确认):题材稳态热度→横轴承接,时机加成→scene_tags 承接,切口话题感→program_format"横切类比"部分承接。话题性不独立立维。 + 这个模型是所有 AI 打标、分析计算、诊断结论的理论基础。开发任何功能都要回到这个公式上来。 -### 1.3 系统四层架构 +### 1.3 招牌视觉——双引擎象限图(v2 快照定稿) + +| 元素 | 编码 | +|------|------| +| 横轴 | 题材热度(= 该题材类别窗口内均值 − 栏目均值,零点=栏目自身平均线) | +| 纵轴 | 叙事结构 2 档 | +| 气泡大小 | 收视份额 | +| 气泡颜色 | 收视判定三色(红 `#c0584f` 优秀 / 蓝 `#5b8db8` 达标 / 绿 `#7aa874` 待提升) | +| 气泡边框 | 开篇钩子(粗实线=强 / 细实线=中 / 虚线=弱) | +| 悬停 tooltip | 节目名 + 期次 + 份额 + 钩子档 | +| 重叠处理 | 横轴 jitter + 垂直 jitter;同点聚集本身就是诊断信号 | + +这张图是整个看板升级的**核心视觉交付物**。制片人已认可视觉方向。 + +### 1.4 6 字段完整枚举定义(v2 快照锁定,v3 修订叙事结构) + +**字段一:`program_format` 主题材(单选)** — 节目内容切口,不是播出形态 + +| 枚举值 | 含义 | +|--------|------| +| `装备深解` | 围绕一款/一型具体装备深度剖析 | +| `横切类比` | 用某切口(趣味/功能/技术)跨装备类比 | +| `历史纵深` | 沿时间线讲装备/战术演进 | +| `前沿科技` | 新技术在军事装备上的应用 | +| `事件战例` | 围绕某次行动、战役、冲突 | +| `人物牵引` | 用人物作叙事入口和牵引线 | + +**字段二:`equipment_domain` 装备域(多选,至少 1 项)** + +`陆战装备` / `海军舰船` / `航空航天` / `导弹与反导` / `信息化` / `后勤保障` / `轻武器` / `跨域` / `其他` + +注:原"枪械"升级为"轻武器";原"空军防空"改为"导弹与反导";"无人作战"从装备域剥离到 tech_tags。 + +**字段三:`scene_tags` 场景标签(多选,可空)** — 回答"为什么这周播" + +`节点策划`(纪念日/周年/节庆/装备节点)/ `热点跟进`(突发新闻)/ `装备首发`(新装备亮相)/ `展会` + +注:原"节日策划"从 program_format 移到 scene_tags 并改名"节点策划"。 + +**字段四:`tech_tags` 技术线标签(多选,可空)** — 新质新域追踪 + +`无人作战` / `智能化`(AI/算法/智能决策)/ `有人无人协同` + +**字段五:`narrative_structure` 叙事结构(单选,2 档)** + +`主线演进` / `并列结构`(v3 从 3 档收为 2 档) + +**字段六:`opening_hook` 开篇钩子强度(单选,3 档)** + +`强`(新闻/反差/画面震撼/悬念)/ `中`(背景铺陈/装备亮相/历史溯源)/ `弱`(直接讲技术/概念/平铺直叙) + +### 1.5 系统四层架构 | 层 | 名称 | 做什么 | 当前状态 | |----|------|--------|----------| @@ -58,19 +112,29 @@ | L3 | **分析引擎** | 基于双引擎模型,计算走势、编导对比、题材对比、高低样本对照等多维分析 | 待设计 | | L4 | **诊断报告** | 调用 LLM 生成文字诊断结论 + 改进建议(对标 PPT 第 8-10 页水平) | 待设计 | -### 1.4 分析周期策略 +### 1.6 三层分步交付策略(v1 快照定稿) + +| 层 | 解锁条件 | 交付物 | +|----|---------|--------| +| 第一层"纯算的" | 只需题材字段(program_format + equipment_domain) | 核心指标卡、走势图(含基础/摸高目标线+滚动3期均值)、阶段对比、题材对比、编导维度 | +| 第二层"打标后才能算的" | 叙事结构 + 开篇钩子标完 | **双引擎象限图**(招牌视觉)+ 高低样本下钻 | +| 第三层"算不出只能写的" | L3 数据就绪 + LLM API 配通 | AI 季度诊断小结(制片人审改后发布)+ 一句话诊断 + 病因 + 提振方案 | + +**编导视角:能力地图,不是排名榜**(v1 §2.7)。团队视角看"谁擅长哪种结构/哪类题材",把强项做成全组可复用模板。制片人视角可看完整排名+下钻。底层同一套数据,锁可后开。 + +### 1.7 分析周期策略 - **默认轨:季度报告**(Q1/Q2/Q3/Q4)。对标台里考核节奏,每季约 12-13 期,数据量合适。 - **灵活轨:自定义周期**。所有栏目人员均可自由选择时间范围查看诊断(如"暑期档 7-8 月"、"最近 10 期")。 - 系统按季度自动生成,用户也能手动选范围——两不误。 -### 1.5 受众与权限 +### 1.8 受众与权限 - **不只给制片人看**——全栏目人员(制片人、责编、6 名编导)都能查看分析结果、自由选择周期、调用 LLM 查看诊断。 - AI 打标结果仍须制片人审核后才正式落库(设计哲学红线:AI 给方向不给答案)。 - 但分析报告(L4)是基于已审核数据生成的,可直接展示。 -### 1.6 样板文件参照表(`example/` 目录) +### 1.9 样板文件参照表(`example/` 目录) | 文件 | 内容 | 对标系统层 | |------|------|-----------| @@ -82,7 +146,7 @@ | PPT 第 12-13 页 | 选题日历、复盘会议程 | 超出系统范围,供人工参考 | | Excel 8 个 Sheet | 核心指标卡、数据明细、走势图、编导对比、题材对比、阶段对比、高低样本、诊断结论 | L3 分析引擎的完整数据底稿 | -### 1.7 范围边界 +### 1.10 范围边界 - ✅ 做:Prompt 撰写与迭代、模型选型基准测试、ground-truth 维护、打标脚本、分析引擎设计与实现、诊断报告生成、前端看板页面 - ❌ 不做:选题日历自动编排、复盘会自动组织、标题自动改写(这些是人的工作) @@ -163,14 +227,16 @@ python scripts/summarize.py --model mimo-v2.5-pro ## 3. 当前进度(动态,核心交接区) -- **已完成**:L2 层 Prompt 2(叙事结构)锁定 + ground-truth v0.2.1 锁定 + 生产模型 mimo-v2.5-pro 确定 -- **正在做**:全貌对齐完成,准备进入 Prompt 1 撰写 -- **卡点/待解**:Prompt 1 的 4 分类字段枚举需收敛;L3/L4 架构设计尚未启动;诊断报告多 LLM 共识机制待选型测试 +- **已完成**:L2 层 Prompt 2(叙事结构)锁定 + ground-truth v0.3.0(4 分类字段骨架预留)+ 脚本扩展支持 --field classification + 生产模型 mimo-v2.5-pro 确定 +- **正在做**:Prompt 1(4 分类字段)撰写中 +- **卡点/待解**:L3/L4 架构设计尚未启动;诊断报告多 LLM 共识机制待选型测试 --- ## 4. 已完成(只追加,最新在上) +- [2026-06-24 | Opus+Cline] ground-truth v0.3.0 结构扩展(4 分类字段骨架预留)+ run_labeling.py / summarize.py 扩展 --field 参数支持 classification。脚本可直接跑 Prompt 1,等 prompt 文件就位即用。 +- [2026-06-24 | Opus] CLAUDE.md 二次更新:精读 v1-v4 四份续接快照,补入双引擎象限图视觉规格、6 字段完整枚举定义、三层分步交付策略、编导能力地图设计、v1-v4 关键决策。 - [2026-06-24 | Opus] 全貌对齐:精读 PPT+Excel 样板、明确四层架构(数据沉淀→AI打标→分析引擎→诊断报告)、确认多 LLM 共识机制方向、确认季度+自定义双轨周期、确认全员可见。CLAUDE.md 从打标沙盒文档升级为全局指导文件。 - [2026-06-24 | Opus] 模型配置统一更正:M3 配置移除,生产模型改为 mimo-v2.5-pro,更新 run_labeling.py / summarize.py / CLAUDE.md / README.md / .env.example。 - [2026-06-11 | Opus+Cline] AI 打标流水线跑通:Prompt 2 v0.2 锁定、ground-truth v0.2.1 锁定、M3 100% 命中率、模型选型完成。Git commit `38f3728` + `edfb298`。 @@ -220,7 +286,7 @@ python scripts/summarize.py --model mimo-v2.5-pro ## 6. 关键决策(跨 session 最易丢) -### 项目全貌(2026-06-24 对齐) +### 项目全貌(v1-v4 快照 + 2026-06-24 对齐) - [2026-06-24] **看板升级 ≠ AI 打标**。AI 打标只是 L2 层,完整系统是四层:数据沉淀 → AI 打标 → 分析引擎 → 诊断报告。目标是把 PPT+Excel 样板里的阶段性手工分析变成累积持续自动分析。 - [2026-06-24] **双引擎模型是理论基础**:收视 = 题材热度 × 叙事结构。所有打标维度、分析计算、诊断结论都围绕这个公式展开。 @@ -228,6 +294,12 @@ python scripts/summarize.py --model mimo-v2.5-pro - [2026-06-24] **分析周期双轨**:默认季度 + 自定义范围。 - [2026-06-24] **不用旧 21 期冷启动**:数据已重新整理清洗,拿新数据重新做。 - [2026-06-24] **诊断报告用多 LLM 共识机制**:因合规限制无法用顶级模型(Opus),改为多个国产模型(MiMo / GLM 5.2 / 待定)分析相同数据取共识,弥补单模型洞察力不足。 +- [2026-06-09] **配色走浅色**:沿用 App 米色底 `#fbf9f1` + 深绿 `#6b8e6b`,不做深色大屏(留 2.0 独立数据大屏蓝本)。 +- [2026-06-09] **DataEase 不进 1.0**:栈冲突 + 资源不够。图表用 ECharts / @ant-design/charts 在 React 栈直接复刻。 +- [2026-06-09] **编导板块不做排名**(团队视角走"能力地图"),制片人视角可看完整排名+下钻。 +- [2026-06-09] **"活的边界"**:实时脉搏(走势/滚动均值)永远滚动;周期复盘(象限/病因/提振)按周期生成。每张图都要有显式时间选择器。 +- [2026-06-10] **话题性不独立立维**:稳态热度→横轴,时机→scene_tags,切口→横切类比形态。 +- [2026-06-10] **6 字段枚举锁定**(见 §1.4):program_format 6 值 / equipment_domain 9 值多选 / scene_tags 4 值 / tech_tags 3 值 / narrative_structure 2 档 / opening_hook 3 档。 ### 叙事结构判别 @@ -274,7 +346,7 @@ python scripts/summarize.py --model mimo-v2.5-pro ## 8. 待确认 / 开放问题(需制片人拍板) -- [ ] Prompt 1 的 4 分类字段枚举最终口径(v1 快照 §三.1 已列草案,需收敛) +- [x] ~~Prompt 1 的 4 分类字段枚举最终口径~~ → 已在 v2 快照锁定(见 §1.4) - [ ] 多 LLM 共识机制的模型阵容(MiMo + GLM 5.2 + ?)待选型测试后定 - [ ] L3 分析引擎是否需要超出 Excel 8 Sheet 的额外分析维度 - [ ] 下一步优先级:先推 Prompt 1 还是先推 schema 加字段方案? diff --git a/ai-labeling/benchmark-set/ground-truth.json b/ai-labeling/benchmark-set/ground-truth.json index 91a8dc5..c5f069f 100644 --- a/ai-labeling/benchmark-set/ground-truth.json +++ b/ai-labeling/benchmark-set/ground-truth.json @@ -1,19 +1,20 @@ { - "version": "v0.2", + "version": "v0.3.0", "locked_at": "2026-06-11", "locked_by": "制片人刘通 + Opus 复议(二次审定)", "fields_covered": ["narrative_structure", "opening_hook"], - "changelog": "v0.1→v0.2: ep10(射速决定论)严格命中性能阶梯主线、ep15(空中坚盾)符合层级承接到体系困局递进,两期从并列结构改为主线演进。ep05/ep13/ep14 经讨论后维持并列结构(共同原因: 段内有承接但全片骨架为并列,已写入 Prompt v0.2 示例 4/5 作为反例教学)。v0.2.1: ep12 三家独立模型一致判并列结构,经制片人复读稿确认后改为并列结构", + "fields_reserved": ["program_format", "equipment_domain", "scene_tags", "tech_tags"], + "changelog": "v0.1→v0.2: ep10(射速决定论)严格命中性能阶梯主线、ep15(空中坚盾)符合层级承接到体系困局递进,两期从并列结构改为主线演进。ep05/ep13/ep14 经讨论后维持并列结构(共同原因: 段内有承接但全片骨架为并列,已写入 Prompt v0.2 示例 4/5 作为反例教学)。v0.2.1: ep12 三家独立模型一致判并列结构,经制片人复读稿确认后改为并列结构。v0.3.0: 为 Prompt 1 预留 4 个分类字段位置(program_format/equipment_domain/scene_tags/tech_tags),枚举值待定,暂填 null/[]。未改动现有 narrative_structure/opening_hook 标注结果。v0.3.0补丁: equipment_domain 从 null 改为 [],对齐 v2 快照"多选"定义。", "episodes": [ - {"ep": 3, "title": "硅基大脑", "share": 0.85, "narrative_structure": "并列结构", "opening_hook": "中"}, - {"ep": 4, "title": "潜艇仿生", "share": 0.92, "narrative_structure": "主线演进", "opening_hook": "强"}, - {"ep": 5, "title": "舰证不凡", "share": 0.72, "narrative_structure": "并列结构", "opening_hook": "中"}, - {"ep": 7, "title": "马年军事图鉴", "share": 0.95, "narrative_structure": "并列结构", "opening_hook": "强"}, - {"ep": 10, "title": "射速决定论", "share": 0.703, "narrative_structure": "主线演进", "opening_hook": "弱"}, - {"ep": 11, "title": "空战颠覆者", "share": 0.859, "narrative_structure": "主线演进", "opening_hook": "中"}, - {"ep": 12, "title": "逆袭战局", "share": 0.85, "narrative_structure": "并列结构", "opening_hook": "强"}, - {"ep": 13, "title": "长短智慧", "share": 0.533, "narrative_structure": "并列结构", "opening_hook": "中"}, - {"ep": 14, "title": "X-76 飞行器", "share": 0.873, "narrative_structure": "并列结构", "opening_hook": "中"}, - {"ep": 15, "title": "空中坚盾", "share": 0.875, "narrative_structure": "主线演进", "opening_hook": "强"} + {"ep": 3, "title": "硅基大脑", "share": 0.85, "narrative_structure": "并列结构", "opening_hook": "中", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 4, "title": "潜艇仿生", "share": 0.92, "narrative_structure": "主线演进", "opening_hook": "强", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 5, "title": "舰证不凡", "share": 0.72, "narrative_structure": "并列结构", "opening_hook": "中", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 7, "title": "马年军事图鉴", "share": 0.95, "narrative_structure": "并列结构", "opening_hook": "强", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 10, "title": "射速决定论", "share": 0.703, "narrative_structure": "主线演进", "opening_hook": "弱", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 11, "title": "空战颠覆者", "share": 0.859, "narrative_structure": "主线演进", "opening_hook": "中", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 12, "title": "逆袭战局", "share": 0.85, "narrative_structure": "并列结构", "opening_hook": "强", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 13, "title": "长短智慧", "share": 0.533, "narrative_structure": "并列结构", "opening_hook": "中", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 14, "title": "X-76 飞行器", "share": 0.873, "narrative_structure": "并列结构", "opening_hook": "中", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []}, + {"ep": 15, "title": "空中坚盾", "share": 0.875, "narrative_structure": "主线演进", "opening_hook": "强", "program_format": null, "equipment_domain": [], "scene_tags": [], "tech_tags": []} ] } \ No newline at end of file diff --git a/ai-labeling/scripts/run_labeling.py b/ai-labeling/scripts/run_labeling.py index 0be5502..c12cb08 100644 --- a/ai-labeling/scripts/run_labeling.py +++ b/ai-labeling/scripts/run_labeling.py @@ -41,11 +41,16 @@ MODEL_CONFIG = { ALL_EPISODES = [3, 4, 5, 7, 10, 11, 12, 13, 14, 15] +FIELD_PROMPT_MAP = { + "narrative": "prompt2_narrative.md", + "classification": "prompt1_classification.md", +} + def load_prompt(field): - if field == "narrative": - return (PROMPTS_DIR / "prompt2_narrative.md").read_text(encoding="utf-8") - raise ValueError(f"Unknown field: {field}") + if field not in FIELD_PROMPT_MAP: + raise ValueError(f"Unknown field: {field}, valid: {list(FIELD_PROMPT_MAP.keys())}") + return (PROMPTS_DIR / FIELD_PROMPT_MAP[field]).read_text(encoding="utf-8") def load_transcript(ep): @@ -65,8 +70,19 @@ def load_ground_truth(ep): def parse_prompt(template, transcript): + """按 ## SYSTEM / ## USER 分隔符拆解 prompt。 + 自动剥离 ## SYSTEM 标签之前的标题行。 + """ parts = template.split("## USER") - system_prompt = parts[0].replace("# Prompt 2:叙事结构判别", "").strip() + # parts[0] 是 system 部分,可能包含标题行 + ## SYSTEM 标签 + system_raw = parts[0] + # 如果有 ## SYSTEM 标签,取它之后的内容;否则去除标题行 + if "## SYSTEM" in system_raw: + system_prompt = system_raw.split("## SYSTEM", 1)[1].strip() + else: + # 没有 ## SYSTEM 标签时,去掉第一行(标题行)作为降级处理 + lines = system_raw.strip().splitlines() + system_prompt = "\n".join(lines[1:]).strip() if len(lines) > 1 else system_raw.strip() user_prompt = parts[1].strip().replace("{transcript}", transcript) return system_prompt, user_prompt @@ -108,19 +124,19 @@ def call_model(model_key, system_prompt, user_prompt): return extract_json_from_response(raw) -def run_labeling(ep, model_key): +def run_labeling(ep, model_key, field="narrative"): transcript, fname = load_transcript(ep) - template = load_prompt("narrative") + template = load_prompt(field) system_prompt, user_prompt = parse_prompt(template, transcript) result = call_model(model_key, system_prompt, user_prompt) gt = load_ground_truth(ep) ts = datetime.now().strftime("%Y%m%d_%H%M%S") - out = EXPERIMENTS_DIR / f"{ts}_{model_key}_ep{ep:02d}.json" + out = EXPERIMENTS_DIR / f"{ts}_{model_key}_{field}_ep{ep:02d}.json" out.write_text( - json.dumps({"episode": ep, "filename": fname, "result": result, "ground_truth": gt}, ensure_ascii=False, indent=2), + json.dumps({"episode": ep, "filename": fname, "field": field, "result": result, "ground_truth": gt}, ensure_ascii=False, indent=2), encoding="utf-8", ) - print(f"完成 ep{ep:02d} -> {out.name}") + print(f"完成 ep{ep:02d} [{field}] -> {out.name}") return result @@ -129,12 +145,14 @@ def main(): parser.add_argument("--ep", type=int, help="单期编号") parser.add_argument("--all", action="store_true", help="跑全部") parser.add_argument("--model", default="mimo-v2.5-pro", help="模型键名") + parser.add_argument("--field", default="narrative", choices=["narrative", "classification"], + help="打标字段: narrative(叙事结构) / classification(4分类)") args = parser.parse_args() if args.all: for ep in ALL_EPISODES: - run_labeling(ep, args.model) + run_labeling(ep, args.model, args.field) elif args.ep: - run_labeling(args.ep, args.model) + run_labeling(args.ep, args.model, args.field) else: parser.print_help() diff --git a/ai-labeling/scripts/summarize.py b/ai-labeling/scripts/summarize.py index f348478..bc1dd4b 100644 --- a/ai-labeling/scripts/summarize.py +++ b/ai-labeling/scripts/summarize.py @@ -39,11 +39,21 @@ def latest_per_ep(files): return {ep: info[0] for ep, info in latest.items()} -def run(model): - pattern = str(EXPERIMENTS_DIR / f"*_{model}_*.json") - files = sorted(Path(p) for p in glob.glob(pattern)) +def run(model, field="narrative"): + # 文件匹配:新格式有 field 标记,老格式(无 field)向后兼容 + pattern_new = str(EXPERIMENTS_DIR / f"*_{model}_{field}_*.json") + files_new = sorted(Path(p) for p in glob.glob(pattern_new)) + # 如果是 narrative 且新格式文件为空,尝试匹配老格式(无 field 标记) + if field == "narrative" and not files_new: + pattern_old = str(EXPERIMENTS_DIR / f"*_{model}_ep*.json") + files_old = [f for f in sorted(Path(p) for p in glob.glob(pattern_old)) + if len(f.name.replace(".json", "").split("_")) == 3] # 老格式: ts_model_epNN.json + files = files_old + else: + files = files_new + if not files: - print(f"未找到 {pattern}") + print(f"未找到 {pattern_new}") return ep_files = latest_per_ep(files) @@ -61,36 +71,47 @@ def run(model): result = data.get("result") title = gt.get("title", "?") - gt_val = gt.get("narrative_structure", "?") - pred_val = result.get("narrative_structure") if result else None - conf = result.get("confidence", "?") if result else "?" - hit = pred_val == gt_val if pred_val is not None else False - rows.append({"ep": ep, "title": title, "gt": gt_val, "pred": pred_val or "解析失败", "hit": hit, "conf": conf}) + if field == "narrative": + gt_val = gt.get("narrative_structure", "?") + pred_val = result.get("narrative_structure") if result else None + conf = result.get("confidence", "?") if result else "?" + hit = pred_val == gt_val if pred_val is not None else False + rows.append({"ep": ep, "title": title, "gt": gt_val, "pred": pred_val or "解析失败", "hit": hit, "conf": conf}) + elif field == "classification": + # 骨架预留:等 ground-truth 标注就绪后实现具体比对逻辑 + rows.append({"ep": ep, "title": title, "gt": "?", "pred": "classification 待实现", "hit": False, "conf": "?"}) - # 打印每行 - for r in rows: - mark = "✓" if r["hit"] else "✗" - conf_str = f'置信度:{r["conf"]}' if r["conf"] != "?" else "" - print(f' ep{r["ep"]:02d} {r["title"]:<10} | 标准:{r["gt"]:<8} | {model}:{r["pred"]:<8} | {mark} {conf_str}') + if field == "narrative": + # 打印每行 + for r in rows: + mark = "✓" if r["hit"] else "✗" + conf_str = f'置信度:{r["conf"]}' if r["conf"] != "?" else "" + print(f' ep{r["ep"]:02d} {r["title"]:<10} | 标准:{r["gt"]:<8} | {model}:{r["pred"]:<8} | {mark} {conf_str}') - # 汇总 - total = len(rows) - hits = sum(1 for r in rows if r["hit"]) - hi_conf = [r for r in rows if r["conf"] == "高"] - mid_low = [r for r in rows if r["conf"] in ("中", "低")] - hi_hit = sum(1 for r in hi_conf if r["hit"]) - ml_hit = sum(1 for r in mid_low if r["hit"]) + # 汇总 + total = len(rows) + hits = sum(1 for r in rows if r["hit"]) + hi_conf = [r for r in rows if r["conf"] == "高"] + mid_low = [r for r in rows if r["conf"] in ("中", "低")] + hi_hit = sum(1 for r in hi_conf if r["hit"]) + ml_hit = sum(1 for r in mid_low if r["hit"]) - print(f"\n ===== {model} 命中情况 =====") - print(f" narrative_structure 命中: {hits}/{total} = {hits*100//total}%") - print(f" 自评\"高\"置信的命中率: {hi_hit}/{len(hi_conf)}") - print(f" 自评\"中/低\"置信的命中率: {ml_hit}/{len(mid_low)}") - print(f" 解析失败: {parse_fail} 期") + print(f"\n ===== {model} 命中情况 =====") + print(f" narrative_structure 命中: {hits}/{total} = {hits*100//total}%") + print(f" 自评\"高\"置信的命中率: {hi_hit}/{len(hi_conf)}") + print(f" 自评\"中/低\"置信的命中率: {ml_hit}/{len(mid_low)}") + print(f" 解析失败: {parse_fail} 期") + elif field == "classification": + print(f"\n ===== {model} classification =====") + print(f" classification 比对逻辑待实现(等 ground-truth 标注就绪)") + print(f" 解析失败: {parse_fail} 期") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--model", required=True, help="模型键名,如 mimo-v2.5-pro / deepseek-v4-pro") + parser.add_argument("--field", default="narrative", choices=["narrative", "classification"], + help="打标字段: narrative(叙事结构) / classification(4分类)") args = parser.parse_args() - run(args.model) \ No newline at end of file + run(args.model, args.field)