Browse Source

权重分迭代

jihuaqiang 1 tuần trước cách đây
mục cha
commit
b8331eacd5
29 tập tin đã thay đổi với 1720 bổ sung7980 xóa
  1. BIN
      .DS_Store
  2. 23 3
      examples/demo.json
  3. BIN
      examples/html 2.zip
  4. BIN
      examples/html.zip
  5. 0 3604
      examples/html/script_result_61626151.html
  6. 67 23
      examples/html/visualize/script.js
  7. 0 2305
      examples/output_demo_1.json
  8. 450 308
      examples/output_demo_script.json
  9. 0 684
      examples/run_script_single.py
  10. 0 193
      examples/run_single.py
  11. 0 99
      examples/run_test.py
  12. BIN
      examples/static/visualize/__pycache__/tab3.cpython-313.pyc
  13. BIN
      examples/static/visualize/__pycache__/tab5.cpython-313.pyc
  14. 273 84
      examples/static/visualize/tab3.py
  15. 178 132
      examples/static/visualize/tab5.py
  16. 0 264
      examples/test.py
  17. BIN
      src/components/agents/__pycache__/script_form_extraction_agent.cpython-313.pyc
  18. BIN
      src/components/agents/__pycache__/script_section_division_agent.cpython-313.pyc
  19. BIN
      src/components/agents/__pycache__/script_substance_extraction_agent.cpython-313.pyc
  20. 610 222
      src/components/agents/script_form_extraction_agent.py
  21. 14 0
      src/components/agents/script_section_division_agent.py
  22. 47 9
      src/components/agents/script_substance_extraction_agent.py
  23. 51 45
      src/components/functions/video_upload_function.py
  24. BIN
      src/states/__pycache__/script_state.cpython-313.pyc
  25. 5 4
      src/states/script_state.py
  26. BIN
      src/workflows/__pycache__/script_workflow.cpython-313.pyc
  27. BIN
      src/workflows/__pycache__/what_deconstruction_workflow.cpython-313.pyc
  28. 1 0
      src/workflows/script_workflow.py
  29. 1 1
      src/workflows/what_deconstruction_workflow.py

BIN
.DS_Store


+ 23 - 3
examples/demo.json

@@ -1,7 +1,27 @@
 [
     {
-        "channel_content_id": "58840748",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250929/ce8968e2f346103b83b75c0c8100028e.mp4",
-        "title": "🔴😂笑死了!让狗咬了,还要粘住嘴"
+        "channel_content_id": "56898272",
+        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250813/8ea91f5604305a1b61ca1daeab376061.mp4",
+        "title": "🔴中国12个奇怪村庄,最后一个许多人都想去"
+    },
+    {
+        "channel_content_id": "49285161",
+        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250311/66055634YSXA75e4qHx0H806l.mp4",
+        "title": "🔴太厉害了!这一家人是隐藏在民间的绝世高人吧!"
+    },
+    {
+        "channel_content_id": "47290992",
+        "video": "https://rescdn.yishihui.com/jq_oss/video/2025012115234475983.mp4",
+        "title": "🔴曾经的同学情,回忆满满,群友们,你们想起谁了?"
+    },
+    {
+        "channel_content_id": "54450908",
+        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250717/14500202Axaxn1AwyAUGxhA4mU.mp4",
+        "title": "🔴这就是我们的好领导!!!"
+    },
+    {
+        "channel_content_id": "50691930",
+        "video": "https://rescdn.yishihui.com/jq_oss/video/2024122618451715484.mp4",
+        "title": "‌🔴你说怪不怪?"
     }
 ]

BIN
examples/html 2.zip


BIN
examples/html.zip


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 3604
examples/html/script_result_61626151.html


+ 67 - 23
examples/html/visualize/script.js

@@ -310,17 +310,21 @@ function drawAllConnections() {
 
         // 绘制到灵感点的连线
         relations.inspiration.forEach(rel => {
-            drawConnection(substanceId, rel.target, 'inspiration', rel.avg_score);
+            // 兼容新结构(支撑)和旧结构(相似度分数)
+            const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
+            drawConnection(substanceId, rel.target, 'inspiration', score);
         });
 
         // 绘制到目的点的连线
         relations.purpose.forEach(rel => {
-            drawConnection(substanceId, rel.target, 'purpose', rel.avg_score);
+            const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
+            drawConnection(substanceId, rel.target, 'purpose', score);
         });
 
         // 绘制到关键点的连线
         relations.keypoint.forEach(rel => {
-            drawConnection(substanceId, rel.target, 'keypoint', rel.avg_score);
+            const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
+            drawConnection(substanceId, rel.target, 'keypoint', score);
         });
     });
 }
@@ -556,17 +560,21 @@ function drawSelectedSubstanceConnections(substanceId, relations) {
 
     // 绘制到灵感点的连线
     relations.inspiration.forEach(rel => {
-        drawConnection(substanceId, rel.target, 'inspiration', rel.avg_score);
+        // 兼容新结构(支撑)和旧结构(相似度分数)
+        const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
+        drawConnection(substanceId, rel.target, 'inspiration', score);
     });
 
     // 绘制到目的点的连线
     relations.purpose.forEach(rel => {
-        drawConnection(substanceId, rel.target, 'purpose', rel.avg_score);
+        const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
+        drawConnection(substanceId, rel.target, 'purpose', score);
     });
 
     // 绘制到关键点的连线
     relations.keypoint.forEach(rel => {
-        drawConnection(substanceId, rel.target, 'keypoint', rel.avg_score);
+        const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
+        drawConnection(substanceId, rel.target, 'keypoint', score);
     });
 
     // 绘制到其他实质点的连线
@@ -746,10 +754,15 @@ function drawSupportRelationships(substanceId, relations) {
         html += '<div class="flow-target-group">\n';
         html += '<div class="flow-target-label">灵感点</div>\n';
         relations.inspiration.forEach(rel => {
-            const score = (rel.avg_score * 100).toFixed(0);
             html += `<div class="flow-target-item inspiration-flow">\n`;
             html += `<div class="flow-target-text">${rel.point}</div>\n`;
-            html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            // 兼容新结构(支撑理由)和旧结构(相似度分数)
+            if (rel.support_reason !== undefined) {
+                html += `<div class="flow-target-score">支撑</div>\n`;
+            } else if (rel.avg_score !== undefined) {
+                const score = (rel.avg_score * 100).toFixed(0);
+                html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            }
             html += '</div>\n';
         });
         html += '</div>\n';
@@ -759,10 +772,15 @@ function drawSupportRelationships(substanceId, relations) {
         html += '<div class="flow-target-group">\n';
         html += '<div class="flow-target-label">目的点</div>\n';
         relations.purpose.forEach(rel => {
-            const score = (rel.avg_score * 100).toFixed(0);
             html += `<div class="flow-target-item purpose-flow">\n`;
             html += `<div class="flow-target-text">${rel.point}</div>\n`;
-            html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            // 兼容新结构(支撑理由)和旧结构(相似度分数)
+            if (rel.support_reason !== undefined) {
+                html += `<div class="flow-target-score">支撑</div>\n`;
+            } else if (rel.avg_score !== undefined) {
+                const score = (rel.avg_score * 100).toFixed(0);
+                html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            }
             html += '</div>\n';
         });
         html += '</div>\n';
@@ -772,10 +790,15 @@ function drawSupportRelationships(substanceId, relations) {
         html += '<div class="flow-target-group">\n';
         html += '<div class="flow-target-label">关键点</div>\n';
         relations.keypoint.forEach(rel => {
-            const score = (rel.avg_score * 100).toFixed(0);
             html += `<div class="flow-target-item keypoint-flow">\n`;
             html += `<div class="flow-target-text">${rel.point}</div>\n`;
-            html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            // 兼容新结构(支撑理由)和旧结构(相似度分数)
+            if (rel.support_reason !== undefined) {
+                html += `<div class="flow-target-score">支撑</div>\n`;
+            } else if (rel.avg_score !== undefined) {
+                const score = (rel.avg_score * 100).toFixed(0);
+                html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            }
             html += '</div>\n';
         });
         html += '</div>\n';
@@ -885,7 +908,7 @@ let selectedFormId = null;           // 选中的形式点ID
 let selectedRightTargetType = null;  // 右侧选题点类型:inspiration/keypoint/purpose
 let selectedRightTargetIdx = null;   // 右侧选题点索引
 
-// 选择左侧选题点(来自实质点评分
+// 选择左侧选题点(来自实质点支撑关系
 function selectLeftTarget(targetType, targetIdx) {
     if (typeof tab5Relationships === 'undefined') {
         console.error('tab5Relationships not defined');
@@ -1135,7 +1158,7 @@ function selectForm(formId) {
     }, 50);
 }
 
-// 选择右侧选题点(来自形式点评分
+// 选择右侧选题点(来自形式点支撑关系
 function selectRightTarget(targetType, targetIdx) {
     if (typeof tab5Relationships === 'undefined') {
         console.error('tab5Relationships not defined');
@@ -1340,10 +1363,15 @@ function drawFormSupportRelationships(formId, relations) {
         html += '<div class="flow-target-group">\n';
         html += '<div class="flow-target-label">灵感点</div>\n';
         relations.inspiration.forEach(rel => {
-            const score = (rel.avg_score * 100).toFixed(0);
             html += `<div class="flow-target-item inspiration-flow">\n`;
             html += `<div class="flow-target-text">${rel.point}</div>\n`;
-            html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            // 兼容新结构(支撑理由)和旧结构(相似度分数)
+            if (rel.support_reason !== undefined) {
+                html += `<div class="flow-target-score">支撑</div>\n`;
+            } else if (rel.avg_score !== undefined) {
+                const score = (rel.avg_score * 100).toFixed(0);
+                html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            }
             html += '</div>\n';
         });
         html += '</div>\n';
@@ -1353,10 +1381,15 @@ function drawFormSupportRelationships(formId, relations) {
         html += '<div class="flow-target-group">\n';
         html += '<div class="flow-target-label">目的点</div>\n';
         relations.purpose.forEach(rel => {
-            const score = (rel.avg_score * 100).toFixed(0);
             html += `<div class="flow-target-item purpose-flow">\n`;
             html += `<div class="flow-target-text">${rel.point}</div>\n`;
-            html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            // 兼容新结构(支撑理由)和旧结构(相似度分数)
+            if (rel.support_reason !== undefined) {
+                html += `<div class="flow-target-score">支撑</div>\n`;
+            } else if (rel.avg_score !== undefined) {
+                const score = (rel.avg_score * 100).toFixed(0);
+                html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            }
             html += '</div>\n';
         });
         html += '</div>\n';
@@ -1366,10 +1399,15 @@ function drawFormSupportRelationships(formId, relations) {
         html += '<div class="flow-target-group">\n';
         html += '<div class="flow-target-label">关键点</div>\n';
         relations.keypoint.forEach(rel => {
-            const score = (rel.avg_score * 100).toFixed(0);
             html += `<div class="flow-target-item keypoint-flow">\n`;
             html += `<div class="flow-target-text">${rel.point}</div>\n`;
-            html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            // 兼容新结构(支撑理由)和旧结构(相似度分数)
+            if (rel.support_reason !== undefined) {
+                html += `<div class="flow-target-score">支撑</div>\n`;
+            } else if (rel.avg_score !== undefined) {
+                const score = (rel.avg_score * 100).toFixed(0);
+                html += `<div class="flow-target-score">相似度: ${score}%</div>\n`;
+            }
             html += '</div>\n';
         });
         html += '</div>\n';
@@ -1604,6 +1642,8 @@ function drawFormToRightTargetConnections(svg) {
             const targetCard = document.querySelector(`.tab5-right-targets .target-card[data-id="${rel.target_id}"]`);
             
             if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
+                // 兼容新结构(支撑)和旧结构(相似度分数)
+                const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
                 drawTab5Connection(
                     svg,
                     sourceCard,
@@ -1612,7 +1652,7 @@ function drawFormToRightTargetConnections(svg) {
                     formId,
                     rel.target_id,
                     {
-                        score: rel.avg_score,
+                        score: score,
                         point: rel.point,
                         formName: relations.name
                     }
@@ -1626,6 +1666,8 @@ function drawFormToRightTargetConnections(svg) {
             const targetCard = document.querySelector(`.tab5-right-targets .target-card[data-id="${rel.target_id}"]`);
             
             if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
+                // 兼容新结构(支撑)和旧结构(相似度分数)
+                const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
                 drawTab5Connection(
                     svg,
                     sourceCard,
@@ -1634,7 +1676,7 @@ function drawFormToRightTargetConnections(svg) {
                     formId,
                     rel.target_id,
                     {
-                        score: rel.avg_score,
+                        score: score,
                         point: rel.point,
                         formName: relations.name
                     }
@@ -1648,6 +1690,8 @@ function drawFormToRightTargetConnections(svg) {
             const targetCard = document.querySelector(`.tab5-right-targets .target-card[data-id="${rel.target_id}"]`);
             
             if (sourceCard && targetCard && !sourceCard.classList.contains('hidden') && !targetCard.classList.contains('hidden')) {
+                // 兼容新结构(支撑)和旧结构(相似度分数)
+                const score = rel.avg_score !== undefined ? rel.avg_score : (rel.support_reason !== undefined ? 1.0 : 0.5);
                 drawTab5Connection(
                     svg,
                     sourceCard,
@@ -1656,7 +1700,7 @@ function drawFormToRightTargetConnections(svg) {
                     formId,
                     rel.target_id,
                     {
-                        score: rel.avg_score,
+                        score: score,
                         point: rel.point,
                         formName: relations.name
                     }

+ 0 - 2305
examples/output_demo_1.json

@@ -1,2305 +0,0 @@
-{
-  "timestamp": "20251128_161337",
-  "total": 17,
-  "success_count": 8,
-  "fail_count": 0,
-  "results": [
-    {
-      "video_data": {
-        "channel_content_id": "53009047",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250517/93cac5eae2b371328a163f3208a22d10.mp4",
-        "title": "人到晚年,看看咱年轻时的模样!😊"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250517/93cac5eae2b371328a163f3208a22d10.mp4",
-          "标题": "人到晚年,看看咱年轻时的模样!😊",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 2,
-              "分类": "人生感悟",
-              "灵感点": "对年华老去与情谊不改的深思",
-              "描述": "创作者可能在亲身经历或目睹他人步入老年,感受到时光的无情流逝和容颜的改变时,同时深刻体悟到同学情谊能够跨越岁月,始终如一的珍贵和不变。",
-              "推理": "视频中反复出现的“人到晚年”、“岁月悠悠催白了头”、“现如今那一张张稚嫩的小脸上渐渐刻上了皱纹”等文字,表达了对衰老和时间流逝的直接感叹。而“同学情是永远割不断的”、“青山在人渐老,同学情更浓”则强调了友谊的恒久。这种对人生阶段变化与不变情谊的对比性思考,是激发创作者传递这一深刻主题的内在灵感。",
-              "推导说明": "从“对年华老去与情谊不改的深思”这个灵感点出发,可以完美推导出整个视频的内容。视频标题“人到晚年”直接点明了思考的起点。内容中反复提及同学情谊的永恒性(“永远割不断的情”、“永远不能散的缘”),时间的流逝(“岁月悠悠,催白了头”、“数十年聚散两茫茫”),以及尽管各自忙碌、联系甚少,但情谊依然亲密不计较的特点。这都体现了在人生晚年回顾青春,感慨时光飞逝但情谊长存的深层思考。优美的风景画面也恰好为这种怀旧和感悟提供了宁静而富有诗意的背景。",
-              "scoring": {
-                "人设契合度": 5,
-                "触发可能性": 9,
-                "内容解释力": 10,
-                "总分": 24,
-                "评分说明": "该灵感点完美契合视频的主题和内容。视频标题“人到晚年”直接点明了创作者对年华老去的感受。视频中大量描述了岁月流逝对个人面貌和生活状态的改变(如“催白了头”、“稚嫩的笑脸上渐渐刻上了皱纹”),同时强烈对比和赞美了同学情谊的持久不变、纯真和珍贵(如“永远割不断的情”、“不逊于亲情”、“愈久愈纯正、愈久愈珍贵、愈久愈甘甜”)。这种对时光流逝和情谊不改的深思,是贯穿视频始终的核心情感线索,因此触发创作的可能性极高,且能完整解释视频的所有核心内容。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "颂扬和珍视同学情谊",
-                "描述": "创作者希望通过视频表达对同学情谊的深厚情感,强调其在人生晚年依然纯粹、珍贵和不可替代的价值,让受众感受到这份情谊的美好。",
-                "推理": "视频中的文字描述反复强调“同学情是永远割不断的情,愈久愈纯正,愈久愈珍贵,愈久愈甘甜”、“同学情虽不是亲情却不逊于亲情”,以及“同学情最实在,最珍贵,最美好”等,这些都是直接对同学情谊的赞美和高度肯定,旨在分享并升华这种情感的价值。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "促进老同学间的情感链接与回忆重温",
-                "描述": "创作者旨在通过鼓励视频的分享,促使老同学们重新建立或加强联系,共同回顾曾经的校园时光,以此加深彼此之间的情感连接和认同。",
-                "推理": "视频在结尾部分明确呼吁“由衷希望你能动动手指,将这个视频分享出去,分享到同学群里,让更多的老同学看到”、“让大家都回忆起那段美好的校园时光,想起那些无忧无虑的日子”,这直接体现了创作者希望通过视频作为媒介,促进特定社群(老同学)之间的互动和情感交流。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 1,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "传统中式建筑",
-                "描述": "视频开头出现的多栋具有传统中式风格的房屋,周围环绕着精心打理的园林景观,背景是连绵的山脉和多变的天空(晴朗、日落),营造出一种宁静、雅致的氛围。",
-                "children": []
-              },
-              {
-                "候选编号": 2,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "诗意江南水乡",
-                "描述": "视频中出现的古镇小巷,石板路湿润,小桥流水,柳树依依,红灯笼点缀,充满江南水乡的诗情画意,暗示岁月的痕迹和沉淀的友情。",
-                "children": []
-              },
-              {
-                "候选编号": 3,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "乡村田园风光",
-                "描述": "视频展示了梯田、村落、油菜花海、翠绿山林等自然风光,场景从宁静的山谷小屋到生机勃勃的田野,再到被云雾缭绕的山峦,传递出回归自然、纯真质朴的美好意境。",
-                "children": []
-              },
-              {
-                "候选编号": 4,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "现代别墅景观",
-                "描述": "视频中穿插了设计现代、带有宽阔草坪和露台的别墅场景,与传统风格形成对比,展现了不同生活环境下的友情延续,暗示无论境遇如何,友情都在。",
-                "children": []
-              },
-              {
-                "候选编号": 5,
-                "维度大类": "实质",
-                "维度细分": "分类",
-                "关键点": "晚年思念同学",
-                "描述": "视频以“人到晚年”开篇,直指年长者对老同学的深厚思念,引发目标受众的共鸣,强调人生阶段的特定情感。",
-                "children": []
-              },
-              {
-                "候选编号": 7,
-                "维度大类": "实质",
-                "维度细分": "分类",
-                "关键点": "同学情谊纯粹",
-                "描述": "描述同学情“愈久愈纯正,愈久愈珍贵,愈久愈甘甜”,以及“不分贵与贱”、“没有富和穷”,凸显友谊的纯真无瑕和超越世俗。",
-                "children": [
-                  {
-                    "候选编号": 10,
-                    "维度大类": "实质",
-                    "维度细分": "分类",
-                    "关键点": "重逢后的亲密",
-                    "描述": "描述重逢时“不管你喊我的绰号还是我叫你的乳名,对方都不会计较反而笑的那么开心”,以及“不计较,心里感觉很舒坦”,展现了老同学之间无需顾忌的真挚与自在。",
-                    "children": [],
-                    "作为子节点的原因": "编号10“重逢后的亲密”描述的是同学情谊纯粹性(编号7)在重逢场景下的具体表现,即无需顾忌的真挚与自在,是编号7所描述的“纯粹”的直接结果和具体化。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 12,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "文字诗意表达",
-                "描述": "视频中所有文字都以诗意的语言描述同学情谊,如“酿成了陈年的老酒”、“窖藏了珍贵的古董”、“青葱岁月”、“最温暖的港湾”等,增强了情感的深度和艺术感染力。",
-                "children": []
-              },
-              {
-                "候选编号": 13,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "柔和治愈系画面",
-                "描述": "视频选取了大量色彩柔和、光线明亮且富有自然美感的风景画面,包括日出、晴空、绿色山林、花田、流水等,视觉上给人带来平静、舒适和治愈的感觉。",
-                "children": []
-              },
-              {
-                "候选编号": 14,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "情感共鸣式发问",
-                "描述": "视频末尾通过“老同学你在远方还好吗?工作顺利吗?生活幸福吗?”等一系列问题,直接与观众(老同学)进行情感互动,促使观众反思和共鸣。",
-                "children": []
-              },
-              {
-                "候选编号": 15,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "舒缓背景音乐",
-                "描述": "视频全程播放舒缓、温馨的背景音乐,与画面和文字内容相得益彰,烘托出怀旧、温暖、深情的主题氛围,增强了情感传递。",
-                "children": []
-              },
-              {
-                "候选编号": 17,
-                "维度大类": "形式",
-                "维度细分": "关系",
-                "关键点": "画面与文字紧密呼应",
-                "描述": "视频中的画面场景(如中式建筑、水乡、田园风光)与文字内容(如对同学情谊的纯真、美好、永恒的描述)相互补充,共同强化主题表达,形成一致的情感基调。",
-                "children": []
-              }
-            ],
-            "total_count": 12,
-            "root_count": 11
-          }
-        },
-        "选题理解": {
-          "主题": "晚年同学情谊的诗意颂扬与重温回忆",
-          "描述": "创作者受到年华老去而同学情谊不变的感触,通过呈现唯美的中式园林、江南水乡、田园风光以及现代别墅等多样化场景,搭配诗意的文字和舒缓的音乐,营造出怀旧温暖的氛围,旨在颂扬和珍视同学情谊,并促进老同学间的情感链接与回忆重温。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "对年华老去与情谊不改的深思",
-                "是否覆盖": true,
-                "覆盖说明": "视频以“人到晚年”开头,并多次提及“岁月悠悠,催白了头”、“稚嫩的小脸上渐渐刻上了皱纹”,同时强调“同学情是永远割不断的”,充分体现了对时光流逝和情谊永恒的深思。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "颂扬和珍视同学情谊",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过一系列赞美同学情谊的诗意化描述,如“愈久愈纯正,愈久愈珍贵,愈久愈甘甜”、“不分贵与贱,没有富和穷”、“最温暖的港湾”,以及画面中宁静美好的景色,表达了对这份情谊的珍视和颂扬。"
-              },
-              {
-                "目的点": "促进老同学间的情感链接与回忆重温",
-                "是否覆盖": true,
-                "覆盖说明": "视频结尾直接呼吁观众分享给老同学,并询问“你在远方还好吗?工作顺利吗?生活幸福吗?”,旨在唤起共同回忆,促进老同学间的互动与情感连接。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "传统中式建筑",
-                "是否覆盖": true,
-                "覆盖说明": "视频开头展示了具有传统中式屋顶和庭院的建筑,与宁静的氛围相符。"
-              },
-              {
-                "关键点": "诗意江南水乡",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了石板路、小河、柳树和红灯笼的古镇场景,充满江南水乡的诗意,营造出怀旧氛围。"
-              },
-              {
-                "关键点": "乡村田园风光",
-                "是否覆盖": true,
-                "覆盖说明": "视频中包含梯田、村落、油菜花海和翠绿山林的画面,展现了质朴自然的田园风光,与纯真情谊的主题相呼应。"
-              },
-              {
-                "关键点": "现代别墅景观",
-                "是否覆盖": true,
-                "覆盖说明": "视频穿插有带有大草坪和露台的现代别墅,暗示不同生活境遇下友谊的延续性。"
-              },
-              {
-                "关键点": "晚年思念同学",
-                "是否覆盖": true,
-                "覆盖说明": "视频标题和文字明确指出“人到晚年”对同学的思念,直接触及目标受众的情感核心。"
-              },
-              {
-                "关键点": "同学情谊纯粹",
-                "是否覆盖": true,
-                "覆盖说明": "文字反复强调“愈久愈纯正,愈久愈珍贵,愈久愈甘甜”、“不分贵与贱,没有富和穷”,凸显同学情谊的纯粹性。"
-              },
-              {
-                "关键点": "重逢后的亲密",
-                "是否覆盖": true,
-                "覆盖说明": "文字描述了重逢时呼唤绰号、乳名不计较,反而开心舒坦的场景,展现了同学间超越形式的亲密无间。"
-              },
-              {
-                "关键点": "文字诗意表达",
-                "是否覆盖": true,
-                "覆盖说明": "所有文字都采用富有情感和意象的诗意语言,如“酿成了陈年的老酒”、“窖藏了珍贵的古董”、“最温暖的港湾”,提升了视频的艺术感染力。"
-              },
-              {
-                "关键点": "柔和治愈系画面",
-                "是否覆盖": true,
-                "覆盖说明": "视频选取了大量色彩柔和、光线明亮的自然风光和建筑,视觉上带来平静和治愈感,与怀旧温馨的主题相得益彰。"
-              },
-              {
-                "关键点": "情感共鸣式发问",
-                "是否覆盖": true,
-                "覆盖说明": "视频末尾提出“老同学你在远方还好吗?工作顺利吗?生活幸福吗?”,直接与观众进行情感交流,引发共鸣和思考。"
-              },
-              {
-                "关键点": "舒缓背景音乐",
-                "是否覆盖": true,
-                "覆盖说明": "视频全程播放的背景音乐旋律舒缓、温馨,有效烘托了怀旧、深情的主题氛围。"
-              },
-              {
-                "关键点": "画面与文字紧密呼应",
-                "是否覆盖": true,
-                "覆盖说明": "视频中的多变风景画面(如古镇、田园、山水)与文字内容(如情谊的沉淀、纯真、美好)相互映衬,共同强化了对同学情谊的表达。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "对年华老去与情谊不改的深思"
-            ],
-            "目的点列表": [
-              "颂扬和珍视同学情谊",
-              "促进老同学间的情感链接与回忆重温"
-            ],
-            "关键点列表": [
-              "传统中式建筑",
-              "诗意江南水乡",
-              "乡村田园风光",
-              "现代别墅景观",
-              "晚年思念同学",
-              "同学情谊纯粹",
-              "重逢后的亲密",
-              "文字诗意表达",
-              "柔和治愈系画面",
-              "情感共鸣式发问",
-              "舒缓背景音乐",
-              "画面与文字紧密呼应"
-            ]
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "年华老去 同学情",
-              "组合逻辑": "提取了[年华老去]这一人生阶段的强钩子,结合[同学情]这一核心主题的不变属性。"
-            },
-            {
-              "搜索词": "岁月无情 友情不变",
-              "组合逻辑": "提取了[岁月无情]这一时间流逝的感叹,结合[友情不变]这一情感反差的钩子。"
-            },
-            {
-              "搜索词": "江南水乡 怀旧情",
-              "组合逻辑": "提取了[江南水乡]这一具象的核心场景,结合[怀旧情]这一主题语境和情感氛围。"
-            },
-            {
-              "搜索词": "中式园林 同学聚会",
-              "组合逻辑": "提取了[中式园林]这一唯美具体元素,结合[同学聚会]这一社交场景作为内容属性。"
-            },
-            {
-              "搜索词": "田园风光 晚年生活",
-              "组合逻辑": "提取了[田园风光]这一自然场景元素,结合[晚年生活]这一目标受众的生活状态。"
-            },
-            {
-              "搜索词": "同学情谊 诗意文案",
-              "组合逻辑": "提取了[同学情谊]这一核心主题,结合[诗意文案]这一内容表现形式。"
-            },
-            {
-              "搜索词": "老同学 纯真友谊",
-              "组合逻辑": "提取了[老同学]这一具体人物关系,结合[纯真友谊]这一情感属性来表达核心内容。"
-            },
-            {
-              "搜索词": "治愈系风景 同学",
-              "组合逻辑": "提取了[治愈系风景]这一视频风格元素,结合[同学]这一核心情感主体。"
-            },
-            {
-              "搜索词": "老同学 重温回忆",
-              "组合逻辑": "提取了[老同学]这一受众标签,结合[重温回忆]这一受众痛点和需求。"
-            },
-            {
-              "搜索词": "晚年生活 友情链接",
-              "组合逻辑": "提取了[晚年生活]这一受众场景,结合[友情链接]这一情感维系的需求。"
-            },
-            {
-              "搜索词": "同学聚会 感人瞬间",
-              "组合逻辑": "提取了[同学聚会]这一具体活动,结合[感人瞬间]这一能引发情感共鸣的内容属性。"
-            },
-            {
-              "搜索词": "岁月沉淀 友谊长存",
-              "组合逻辑": "提取了[岁月沉淀]这一时间维度,结合[友谊长存]这一美好愿景和主题。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "58840748",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250929/ce8968e2f346103b83b75c0c8100028e.mp4",
-        "title": "🔴😂笑死了!让狗咬了,还要粘住嘴"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250929/ce8968e2f346103b83b75c0c8100028e.mp4",
-          "标题": "🔴😂笑死了!让狗咬了,还要粘住嘴",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 1,
-              "分类": "社会议题",
-              "灵感点": "狂犬病及其造成的普遍恐惧",
-              "描述": "创作者可能在新闻报道、医学知识普及或日常交流中,接触到关于狂犬病的严重性、传染性以及社会大众对此类疾病普遍存在的恐慌心理。",
-              "推理": "视频的核心恶搞主题围绕“狂犬病发作”展开,博主通过模仿狂犬病症状,成功引发了多位路人和店主强烈的恐惧和逃跑行为。这表明创作者认识到狂犬病是一个能迅速触及人们深层恐惧的题材,可以作为恶搞的强大刺激源。",
-              "推导说明": "该灵感点直接捕捉了视频的核心驱动力。狂犬病在中国社会中普遍存在恐惧心理,人们对其发病后的行为有着深刻印象。创作者正是利用了这种普遍的社会心理:首先通过询问“狂犬疫苗偏方”制造一种非正常状态下的求助情境,然后突然模拟狂犬病发作的症状(如狗叫、咬人暗示、牙痛、精神错乱等),直接触发受害者的深层恐惧。视频中,无论是大妈、大爷还是店主,他们从最初的困惑、提供常识性建议,到看到“发病”后表现出的极度惊恐、逃跑、甚至放弃财物,都是这种普遍恐惧心理的真实体现。因此,狂犬病及其造成的普遍恐惧可以推导出整个视频的创作构思和内容呈现。",
-              "scoring": {
-                "人设契合度": 5,
-                "触发可能性": 9,
-                "内容解释力": 10,
-                "总分": 24,
-                "评分说明": "狂犬病因其严重性和传染性在社会中引发普遍恐慌,这为创作提供了强烈的戏剧冲突和情绪触发点。创作者可能从这种普遍恐惧中获得灵感,利用其作为核心元素来设计一个具有冲击力和喜剧效果的恶作剧。视频中所有行为,无论是寻求偏方、表演发病症状,还是最终吓跑受害者,都直接围绕“狂犬病”这一概念展开,因此该灵感点能完美解释视频的核心内容和表现手法。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "通过恶搞制造喜剧效果",
-                "描述": "创作者希望通过扮演狂犬病人,引发路人惊慌失措的反应,从而达到制造幽默效果,让观众感到开心和好笑的目的。",
-                "推理": "视频内容主体是创作者向路人询问狂犬病偏方,随后假装发病并发出狗叫声追赶路人。视频中,大妈、两位大爷和两位店员均被吓跑或摔倒。这些夸张和出人意料的互动是典型的恶搞喜剧手法,旨在通过路人的真实反应来逗乐观众。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "鼓励用户分享,扩大视频传播",
-                "描述": "创作者希望观众在看完视频后,能够主动将内容分享给朋友和群组,以增加视频的曝光度和传播量,达到更广泛的受众触达。",
-                "推理": "视频结尾出现了明确的文字提示(“老朋友们,刚才这个视频真是太逗了...赶紧点击下方黄色的分享到群按钮,分享到您的三个微信群里...好东西就是要一起分享...快乐分享越多,福气自然也就越多了”),直接引导和鼓励观众进行分享操作,以期通过社交传播来提升视频的影响力。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 9,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "幽默字幕增强",
-                "描述": "视频中穿插“笑死人,不偿命”的幽默字幕,直接点出视频的喜剧属性,增加了观看乐趣。",
-                "children": []
-              },
-              {
-                "候选编号": 10,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "夸张表演风格",
-                "描述": "扮演狂犬病人的男子通过高分贝的狗叫声、突然的冲撞和面部表情,展现出极度夸张的表演,增强喜剧效果。",
-                "children": [
-                  {
-                    "候选编号": 3,
-                    "维度大类": "形式",
-                    "维度细分": "风格",
-                    "关键点": "追逐式肢体喜剧",
-                    "描述": "视频包含多段男子追逐路人的画面,路人跑动、鞋子脱落等,形成具有视觉冲击力的肢体喜剧效果。",
-                    "children": [],
-                    "作为子节点的原因": "“追逐式肢体喜剧”是“夸张表演风格”的一种具体表现形式,侧重于通过肢体动作和追逐场景来展现夸张和喜剧效果,是其下属的一种具体风格。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 6,
-                "维度大类": "形式",
-                "维度细分": "关系",
-                "关键点": "多场景多角色互动",
-                "描述": "视频在不同地点(街边、商店内)选择不同年龄段的路人(大姨、大爷、店主)进行同样的恶作剧,展现多样化的互动情境。",
-                "children": [
-                  {
-                    "候选编号": 2,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "老年人的惊恐反应",
-                    "描述": "视频中被男子“威胁”的老年路人(包括大姨和大爷)从疑惑到惊恐,最终奔跑躲避。",
-                    "children": [],
-                    "作为子节点的原因": "“老年人的惊恐反应”是“多场景多角色互动”中特定角色(老年人)的具体反应,是整体互动中的一个具体局部。"
-                  },
-                  {
-                    "候选编号": 8,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "店主激烈防卫",
-                    "描述": "商店女店主在被“威胁”后,因恐惧摔倒在地,并向男子投掷商品,试图自卫。",
-                    "children": [],
-                    "作为子节点的原因": "“店主激烈防卫”是“多场景多角色互动”中特定角色(店主)的具体反应,是整体互动中的一个具体局部。"
-                  },
-                  {
-                    "候选编号": 5,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "金钱诱惑情节",
-                    "描述": "男子为让路人配合其“狂犬病”行为,向路人提出给予金钱(一万块、两块钱),以换取被咬的机会。",
-                    "children": [],
-                    "作为子节点的原因": "“金钱诱惑情节”是“多场景多角色互动”中男子与路人之间发生的具体互动内容,是整体互动中的一个具体局部。"
-                  },
-                  {
-                    "候选编号": 7,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "胶带绑嘴提议",
-                    "描述": "在商店场景中,男子要求店主撕开胶带,并帮他缠住嘴巴和头部,以防止自己“咬人”。",
-                    "children": [],
-                    "作为子节点的原因": "“胶带绑嘴提议”是“多场景多角色互动”中男子与店主之间发生的具体互动内容,是整体互动中的一个具体局部。"
-                  }
-                ]
-              }
-            ],
-            "total_count": 8,
-            "root_count": 3
-          }
-        },
-        "选题理解": {
-          "主题": "男子假扮狂犬病人恶搞路人,通过夸张表演和互动制造喜剧效果并鼓励分享",
-          "描述": "该选题源于对狂犬病普遍恐惧的洞察,创作者通过扮演狂犬病发作的男子,以夸张的狗叫声、突然冲撞及追逐路人的肢体表演,结合提出金钱诱惑或要求用胶带绑嘴的荒诞情节,在多场景与不同年龄路人(包括老年人)及店主互动,引发其惊恐和自卫的激烈反应,从而制造出强烈的喜剧效果,旨在娱乐观众并鼓励其将这份快乐分享到社交群组,以扩大视频的传播。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "狂犬病及其造成的普遍恐惧",
-                "是否覆盖": true,
-                "覆盖说明": "视频的核心恶搞手段是男子模仿狂犬病症状,成功利用了社会大众对狂犬病普遍存在的恐惧心理,引发路人恐慌反应。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "通过恶搞制造喜剧效果",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过男子夸张的表演、追逐路人以及路人惊慌失措的反应,成功制造了多段幽默场景,旨在让观众感到开心和好笑。"
-              },
-              {
-                "目的点": "鼓励用户分享,扩大视频传播",
-                "是否覆盖": true,
-                "覆盖说明": "视频结尾明确呼吁观众将视频分享到微信群,并强调“独乐乐不如众乐乐”的理念,直接体现了扩大视频传播的目的。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "幽默字幕增强",
-                "是否覆盖": true,
-                "覆盖说明": "视频中穿插“笑死人,不偿命”等字幕,明确了视频的喜剧属性,增强了观看乐趣。"
-              },
-              {
-                "关键点": "夸张表演风格",
-                "是否覆盖": true,
-                "覆盖说明": "男子通过高分贝的狗叫声、突然冲撞以及面部表情变化,极度夸张地模仿狂犬病症状,增强了喜剧效果。"
-              },
-              {
-                "关键点": "追逐式肢体喜剧",
-                "是否覆盖": true,
-                "覆盖说明": "视频包含男子追逐路人、路人跑动甚至鞋子脱落的画面,形成了具有视觉冲击力的肢体喜剧效果。"
-              },
-              {
-                "关键点": "多场景多角色互动",
-                "是否覆盖": true,
-                "覆盖说明": "视频在街边和商店内等不同地点,选择了大姨、大爷和商店女店主等不同年龄和身份的人物进行恶作剧,展现了多样化的互动情境。"
-              },
-              {
-                "关键点": "老年人的惊恐反应",
-                "是否覆盖": true,
-                "覆盖说明": "视频中被男子“威胁”的大姨和大爷,从疑惑到惊恐,最终奔跑躲避,这些真实的反应被捕捉,增加了喜剧张力。"
-              },
-              {
-                "关键点": "店主激烈防卫",
-                "是否覆盖": true,
-                "覆盖说明": "商店女店主在被男子“威胁”后,因恐惧摔倒在地,并向男子投掷商品,试图自卫,这一互动增加了戏剧性。"
-              },
-              {
-                "关键点": "金钱诱惑情节",
-                "是否覆盖": true,
-                "覆盖说明": "男子为让路人配合其“狂犬病”行为,向路人提出给予金钱(一万块、两块钱)以换取被咬的机会,这一荒诞情节增强了幽默感。"
-              },
-              {
-                "关键点": "胶带绑嘴提议",
-                "是否覆盖": true,
-                "覆盖说明": "在商店场景中,男子要求店主撕开胶带,并帮他缠住嘴巴和头部,以防止自己“咬人”,这一细节进一步夸大了恶搞的荒诞性。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "狂犬病及其造成的普遍恐惧"
-            ],
-            "目的点列表": [
-              "通过恶搞制造喜剧效果",
-              "鼓励用户分享,扩大视频传播"
-            ],
-            "关键点列表": [
-              "幽默字幕增强",
-              "夸张表演风格",
-              "追逐式肢体喜剧",
-              "多场景多角色互动",
-              "老年人的惊恐反应",
-              "店主激烈防卫",
-              "金钱诱惑情节",
-              "胶带绑嘴提议"
-            ]
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "狂犬病 恶搞",
-              "组合逻辑": "提取了“狂犬病”这一社会普遍恐惧的灵感元素作为核心实体,结合“恶搞”这一视频内容体裁。"
-            },
-            {
-              "搜索词": "狂犬病人 吓人",
-              "组合逻辑": "提取了“狂犬病人”这一具象角色作为核心实体,结合“吓人”这一视频制造的情感效果和视觉冲击。"
-            },
-            {
-              "搜索词": "街头 恶作剧",
-              "组合逻辑": "提取了“多场景”中的“街头”这一具体地点作为核心实体,结合“恶作剧”这一内容类型。"
-            },
-            {
-              "搜索词": "路人 惊恐反应",
-              "组合逻辑": "提取了“路人”这一核心互动对象作为核心实体,结合其“惊恐反应”这一主要看点。"
-            },
-            {
-              "搜索词": "店主 防卫",
-              "组合逻辑": "提取了“店主”这一特定互动角色作为核心实体,结合其“激烈防卫”这一具体行为。"
-            },
-            {
-              "搜索词": "金钱诱惑 整蛊",
-              "组合逻辑": "提取了“金钱诱惑”这一荒诞情节作为核心实体,结合“整蛊”这一视频内容属性。"
-            },
-            {
-              "搜索词": "胶带绑嘴 搞笑",
-              "组合逻辑": "提取了“胶带绑嘴”这一具体荒诞道具和行为作为核心实体,结合“搞笑”这一内容目的。"
-            },
-            {
-              "搜索词": "肢体喜剧 追逐",
-              "组合逻辑": "提取了“肢体喜剧”这一表演风格作为核心实体,结合“追逐”这一具体的动作元素。"
-            },
-            {
-              "搜索词": "恶搞视频 爆笑",
-              "组合逻辑": "提取了“恶搞视频”这一内容体裁作为核心实体,结合“爆笑”这一强烈的情感效果,满足观众娱乐需求。"
-            },
-            {
-              "搜索词": "整蛊 挑战",
-              "组合逻辑": "提取了“整蛊”这一具体行为作为核心实体,结合“挑战”这一在抖音/TikTok上常见的内容形式。"
-            },
-            {
-              "搜索词": "搞笑视频 分享",
-              "组合逻辑": "提取了“搞笑视频”这一具体内容类型作为核心实体,结合“分享”这一视频鼓励用户采取的行为目的。"
-            },
-            {
-              "搜索词": "整人 视频",
-              "组合逻辑": "提取了“整人”这一视频的核心行为作为核心实体,结合“视频”这一内容载体。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "31877786",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20241009/57463791OoDiuHeqBZlhyJJ4vt.mp4",
-        "title": "⭕九九重阳节,将出现“双月同天”的天文景象‌"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20241009/57463791OoDiuHeqBZlhyJJ4vt.mp4",
-          "标题": "⭕九九重阳节,将出现“双月同天”的天文景象‌",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 1,
-              "分类": "网络热议",
-              "灵感点": "网络流传重阳节双月异象",
-              "描述": "看到或听说网上流传的关于今年农历九月初九重阳节期间,天空可能同时出现两个月亮,并持续20天的说法。",
-              "推理": "视频开头以强烈视觉和文字提示“九月初九 重阳节 天上将有可能同时出现两个月亮”,此信息在社交媒体上常以奇闻异事形式传播,很可能激发创作者对此现象进行探讨的冲动。",
-              "推导说明": "视频以重阳节可能出现“两个月亮”的异象为开篇,这是一个极具网络热议潜力的奇闻。从这个灵感点出发,创作者可以自然地引出:1. 强调异象的出现及其持续时间(20天)。2. 引入古代的预言和历史记载(汉书五行志),以增加内容的权威性和神秘感,并将其与灾难联系起来。3. 为支撑“今年不一般”的主题,进一步补充其他生活观察到的反常现象(桂花未开、蚊虫稀少),并引用更多民间谚语和刘伯温碑文以强化危机感。4. 在营造出“不一般”的氛围后,顺理成章地引出针对这种特殊年份的养生建议,即“秋后四不动”,使其显得尤为重要和及时。5. 视频结尾的强烈转发呼吁,也符合网络热议话题的传播模式。因此,这个灵感点能够很好地贯穿并推导出整个视频的所有核心内容。",
-              "scoring": {
-                "人设契合度": 8,
-                "触发可能性": 7,
-                "内容解释力": 9,
-                "总分": 24,
-                "评分说明": "这个灵感点与视频创作者的人设高度契合。创作者明显定位为向中老年群体分享生活智慧和健康养生建议,经常使用传统说法和一些民间流传的现象来引发关注。网络上流传的‘重阳节双月异象’作为一个带有神秘色彩和传统文化关联的话题,非常能够吸引目标受众的眼球。\n\n触发可能性较高,因为该话题具有一定的轰动性和时效性(与特定节日相关),能够为创作者提供一个强大的开头引子,迅速抓住观众注意力,并为后续的健康警示和养生建议制造紧迫感和合理性。视频一开场就聚焦于此,表明这是内容的核心触发点之一。\n\n内容解释力非常强。视频的开头部分几乎全部围绕这个‘双月异象’展开,包括视觉呈现、历史记载的引用(汉书五行志)、以及古人对此异象的解读(必有大难)。随后,视频又通过将这一异象与桂花不开、蚊虫消失等其他‘古怪’现象联系起来,进一步强化了今年秋冬‘不一般’的论调,从而引出了核心的‘秋后四不动’养生建议。因此,这个灵感点是整个视频叙事逻辑的起点和核心驱动力。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "分享秋冬养生与季节警示",
-                "描述": "创作者旨在向受众分享关于今年秋冬季节的异常现象(如两月同天、桂花不开等)和古代预测,同时提供具体的秋季养生保健建议,以帮助受众应对季节变化,维护健康。",
-                "推理": "视频开头渲染了“九月十九重阳节天上将有可能同时出现两个月亮”的异常现象,并引用《汉书五行志》和“霜叶同天,必有大难”的古人言论,以及“今年的桂花没有开”、“苍蝇蚊子去哪了”等反常自然现象,制造了紧迫感和警示。随后,视频详细讲解了秋季“四不动”的具体做法和原因(不动脖子、不动脚、不动头、不要动关节),并建议多吃清淡润燥的食物,这些都表明创作者在分享关于秋冬的警示信息和实用养生知识。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "促进内容传播与粉丝互动",
-                "描述": "创作者通过直接呼吁观众将视频转发到群组,并承诺转发带来“吉祥美满”等美好祝愿,旨在激励受众主动分享内容,从而扩大视频的传播范围和影响力,并增强与粉丝的互动。",
-                "推理": "视频末尾出现蓝色背景文案,明确提示“点击下方按钮即可转发”,并强调“这个视频实在是太好了,如果你能把视频转发到群里,祝福就传递一次,今天不管多忙,千万不要让这个视频在您的手里终止了,快行动起来,尽可能多的转发吧,愿您和家人吉祥美满一辈子,顺顺利利一辈子,转发分享福气多多”。这直接表达了创作者希望观众积极转发和分享视频以扩大传播的目的。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 2,
-                "维度大类": "实质",
-                "维度细分": "分类",
-                "关键点": "古代灾难预言",
-                "描述": "视频引用《汉书五行志》中公元前37年两月亮出现后引发洪灾地震的记载,以及多个古代谚语,暗示异象预示着灾难。",
-                "children": []
-              },
-              {
-                "候选编号": 3,
-                "维度大类": "实质",
-                "维度细分": "分类",
-                "关键点": "今秋自然反常",
-                "描述": "视频列举了当前年份秋季出现的反常现象,如各地桂花未开、苍蝇蚊子和蜻蜓蝴蝶不见踪影,用以支持“今年不一般”的论点。",
-                "children": []
-              },
-              {
-                "候选编号": 4,
-                "维度大类": "实质",
-                "维度细分": "分类",
-                "关键点": "秋冬养生重要性",
-                "描述": "视频强调了今年秋冬季节养生的特殊重要性,特别是对于年过四十的人群,指出重视养生能帮助“无病一身轻”度过2024年。",
-                "children": [
-                  {
-                    "候选编号": 5,
-                    "维度大类": "实质",
-                    "维度细分": "分类",
-                    "关键点": "老祖宗四不动",
-                    "描述": "视频核心内容是介绍“老祖宗传承千年的智慧结晶”——“秋后四不动”的养生原则,吸引观众了解具体方法。",
-                    "children": [],
-                    "作为子节点的原因": "“老祖宗四不动”是“秋冬养生重要性”这一大类下的具体养生原则,是从抽象的“重要性”到具体的“方法论”的细分。"
-                  },
-                  {
-                    "候选编号": 10,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "秋季饮食润燥",
-                    "描述": "视频根据寒露将至、秋燥四起的气候特征,提供具体的饮食建议,即多吃清淡和润燥的食物。",
-                    "children": [],
-                    "作为子节点的原因": "“秋季饮食润燥”是“秋冬养生重要性”这一大类下的具体养生实践(饮食方面),是从整体的“重要性”到局部的“具体建议”的细化。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 12,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "定向退休人群",
-                "描述": "视频左上方醒目地标注“退休群”,将内容明确指向特定中老年受众,增强了内容的针对性和专属感,提高目标群体的兴趣。",
-                "children": []
-              },
-              {
-                "候选编号": 13,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "女专家讲解",
-                "描述": "一位身穿白大褂的女性作为主讲人,其形象给人以专业和权威的印象,有助于提升内容的信任度。",
-                "children": []
-              },
-              {
-                "候选编号": 14,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "多样视觉呈现",
-                "描述": "视频通过穿插多种视觉内容,如模拟异象、灾害片段、自然景象、古装与现代着装对比、养生操作演示等,使信息传达更生动直观。",
-                "children": []
-              },
-              {
-                "候选编号": 15,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "营造稀缺紧迫感",
-                "描述": "通过“只说给有缘人听”、“价值千金”等措辞,营造信息珍贵和机会难得的氛围,刺激观众的观看和传播欲望。",
-                "children": []
-              }
-            ],
-            "total_count": 9,
-            "root_count": 7
-          }
-        },
-        "选题理解": {
-          "主题": "",
-          "描述": "",
-          "覆盖情况": {
-            "灵感点覆盖情况": [],
-            "目的点覆盖情况": [],
-            "关键点覆盖情况": []
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "网络流传重阳节双月异象"
-            ],
-            "目的点列表": [
-              "分享秋冬养生与季节警示",
-              "促进内容传播与粉丝互动"
-            ],
-            "关键点列表": [
-              "古代灾难预言",
-              "今秋自然反常",
-              "秋冬养生重要性",
-              "老祖宗四不动",
-              "秋季饮食润燥",
-              "定向退休人群",
-              "女专家讲解",
-              "多样视觉呈现",
-              "营造稀缺紧迫感"
-            ]
-          },
-          "错误": "无法获取视频文件对象"
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "重阳节 双月异象",
-              "组合逻辑": "提取了[重阳节、双月异象]的核心实体,结合[网络热议、奇闻]的主题语境。"
-            },
-            {
-              "搜索词": "今年气候反常 预兆",
-              "组合逻辑": "提取了[今年气候反常、桂花不开]的核心实体,结合[古代预兆、异常现象]的主题语境。"
-            },
-            {
-              "搜索词": "老祖宗智慧 养生秘诀",
-              "组合逻辑": "提取了[老祖宗智慧、养生秘诀]的核心实体,结合[稀缺、价值千金]的主题语境。"
-            },
-            {
-              "搜索词": "秋后四不动 原则",
-              "组合逻辑": "提取了[秋后四不动]的核心实体,结合[养生原则、禁忌]的主题语境。"
-            },
-            {
-              "搜索词": "寒露节气 饮食",
-              "组合逻辑": "提取了[寒露节气、秋燥]的核心实体,结合[饮食建议、润燥]的主题语境。"
-            },
-            {
-              "搜索词": "秋冬养生 2024",
-              "组合逻辑": "提取了[秋冬养生、2024年]的核心实体,结合[季节警示、健康]的主题语境。"
-            },
-            {
-              "搜索词": "中医专家 养生讲解",
-              "组合逻辑": "提取了[女专家、白大褂]的核心实体(暗示专业性),结合[中医、养生讲解]的主题语境。"
-            },
-            {
-              "搜索词": "汉书五行志 异象",
-              "组合逻辑": "提取了[汉书五行志、古代记载]的核心实体,结合[异象、预言]的主题语境。"
-            },
-            {
-              "搜索词": "退休养生 建议",
-              "组合逻辑": "提取了[退休人群、年过四十]的核心实体,结合[养生建议、健康]的主题语境。"
-            },
-            {
-              "搜索词": "中老年人 秋季健康",
-              "组合逻辑": "提取了[中老年人、年过四十]的核心实体,结合[秋季健康、应对变化]的主题语境。"
-            },
-            {
-              "搜索词": "无病一身轻 秘诀",
-              "组合逻辑": "提取了[无病一身轻]的核心实体,结合[健康目标、养生秘诀]的主题语境。"
-            }
-          ],
-          "总数": 11
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "52088667",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20240810/66085350uUqFZIg77FJLAzJJpY.mp4",
-        "title": "退休人员收到告示书"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20240810/66085350uUqFZIg77FJLAzJJpY.mp4",
-          "标题": "退休人员收到告示书",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 5,
-              "分类": "心理状态",
-              "灵感点": "积极情绪对健康的重要性",
-              "描述": "视频通过对比“笑一笑十年少”与“愁一愁白了头”,强调了乐观、宽容、不急躁等积极情绪对于延缓衰老、保持身心健康的关键作用。",
-              "推理": "“心态篇”中,多处强调“笑一笑”、“乐淘淘”、“心胸宽”、“不惧老心乐观”等积极情绪的益处,并与消极情绪的危害形成对比,这表明创作者认识到心理状态在影响老年人健康和幸福方面的核心地位。",
-              "推导说明": "“积极情绪对健康的重要性”是视频贯穿始终的核心主旨之一,特别是在“总则”和“心态篇”中得到了淋漓尽致的体现。从“别失落 顺自然”、“快乐过 每一天”,到“笑一笑 十年少”、“不惧老 心乐观”,都强调了保持乐观心态的巨大益处。这种对情绪价值的重视,作为创作的内在驱动,决定了视频充满正能量、劝慰和鼓励的整体基调和大量相关内容。",
-              "scoring": {
-                "人设契合度": 9,
-                "触发可能性": 9,
-                "内容解释力": 9,
-                "总分": 27,
-                "评分说明": "“心态篇”作为视频的压轴部分,通过对比手法(笑一笑十年少 vs 愁一愁白了头)明确强调了积极情绪的巨大价值。这表明创作者可能深知心态对老年生活质量的决定性影响,并以此作为核心信息进行创作。积极乐观、宽容不计较的建议,贯穿了视频的终极目标——让老年生活更幸福,是视频深层价值观的体现,极大地解释了视频为何将心态作为独立且重要的章节进行阐述。"
-              }
-            },
-            {
-              "候选编号": 1,
-              "分类": "文化载体",
-              "灵感点": "传统三字经的叙事结构",
-              "描述": "视频采用中国传统蒙学读物《三字经》的独特韵律、三字一句、偶句押韵的叙事结构。",
-              "推理": "视频开篇以竹简和毛笔字形式呈现“告全国离退休人员 三字经”的标题,并以三字一句的固定格式朗读后续内容,这表明创作者受到《三字经》这种深入人心的传统叙事结构启发,认为其适合传达易于记忆和传播的生活哲理。",
-              "推导说明": "从“传统三字经的叙事结构”这个灵感点出发,创作者选择使用三字一句、押韵对仗的传统形式来编写内容,并将其分章节呈现(总则、生活篇、健康篇等)。这种结构本身就构成了视频的骨架,使得针对退休人群的多元生活建议能够以一种易于理解和记忆的方式贯穿整个视频,从而推导出全部内容的呈现形式和风格。",
-              "scoring": {
-                "人设契合度": 8,
-                "触发可能性": 9,
-                "内容解释力": 9,
-                "总分": 26,
-                "评分说明": "该灵感点完美契合了视频的整体形式和内容呈现方式。视频的标题直接点明了是“三字经”,整个视频内容也严格遵循了三字一句、偶句押韵的传统《三字经》格式,这种结构的选择是创作的基石。对于一个旨在向中老年群体传递建议的创作者来说,采用他们熟悉且易于接受的传统文化载体,是非常直接且有效的创作触发点,能够解释视频为何采用这种独特的韵律和分段方式。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "为退休人员提供生活指导",
-                "描述": "创作者希望通过系统性的“三字经”形式,向退休人员提供关于如何度过老年生活的实用建议和积极心态的指引,帮助他们享受充实健康的晚年。",
-                "推理": "视频以“告全国离退休人员三字经”为主题(0:00-0:06),并分设“总则”、“生活篇”、“健康篇”、“家庭篇”、“上网篇”、“心态篇”等多个章节(0:07-3:54),详细阐述了退休人员在面对身体变化、人际交往、数字生活、心理调适等方面应采取的态度和具体做法。例如“多聚会少窝家”、“常锻炼意志坚”、“家庭和福寿天”、“年再高潮流赶”、“笑一笑十年少”等具体内容,都旨在向受众分享如何更好地生活和保持积极心态。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "促进内容传播与社群互动",
-                "描述": "创作者明确鼓励观看视频的退休人员将内容分享给他们的家人和朋友,尤其是在群组中分享,以扩大内容的传播范围,并加强受众之间的互动,形成社区效益。",
-                "推理": "视频在结尾部分(3:55-4:14)通过画面(“同意地转一转”)和配音直接呼吁:“老友们,这个视频对咱们非常实用。为了家人朋友一定要转发分享到群,群里的家人都会感谢你的。转发传递,让每一个人都能从中受益!”这明确表达了创作者希望受众进行分享和传播,利用社交网络扩大影响力和互动,从而服务于更广泛的退休人群体的意图。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 1,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "传统竹简翻页动画",
-                "描述": "视频以金色竹简作为主要视觉载体,通过翻页动画展示内容,具有浓郁的中国传统文化风格。",
-                "children": []
-              }
-            ],
-            "total_count": 2,
-            "root_count": 1
-          }
-        },
-        "选题理解": {
-          "主题": "以三字经形式为退休人员提供实用生活指南。",
-          "描述": "该视频受积极情绪对健康的重要性以及传统《三字经》叙事结构的启发,采用竹简翻页动画作为核心呈现手法,通过生活、健康、家庭、上网和心态五个篇章,提供易于记忆和传播的退休生活建议,旨在为退休人员提供实用的生活指导,并促进内容在社群中广泛传播与互动,帮助他们享受充实健康的晚年。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "积极情绪对健康的重要性",
-                "是否覆盖": true,
-                "覆盖说明": "视频的“心态篇”部分,通过“笑一笑十年少”、“乐淘淘情绪高”等内容,直接体现了积极情绪对老年人健康长寿的促进作用,并与负面情绪的危害形成对比,呼应了该灵感点。"
-              },
-              {
-                "灵感点": "传统三字经的叙事结构",
-                "是否覆盖": true,
-                "覆盖说明": "视频开篇明确指出“告全国离退休人员三字经”,且整个视频内容都以三字一句、偶句押韵的朗读形式呈现,并通过竹简翻页动画展现,完美复刻了传统三字经的结构和风格,直接覆盖了该灵感点。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "为退休人员提供生活指导",
-                "是否覆盖": true,
-                "覆盖说明": "视频的核心内容就是围绕退休人员的日常展开,分为“总则”、“生活篇”、“健康篇”、“家庭篇”、“上网篇”、“心态篇”,提供了全方位的实用建议和积极引导,旨在帮助退休人员更好地规划和享受晚年生活,直接覆盖了该目的点。"
-              },
-              {
-                "目的点": "促进内容传播与社群互动",
-                "是否覆盖": true,
-                "覆盖说明": "视频结尾明确呼吁“老友们,这个视频对咱们非常实用,为了家人朋友一定要转发分享到群,群里的家人都会感谢你的”,直接表达了创作者希望内容被广泛传播和促进社群互动的目的。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "传统竹简翻页动画",
-                "是否覆盖": true,
-                "覆盖说明": "视频采用金色的竹简作为背景,通过竹简逐片翻开的形式展示文字内容,并伴有翻页的音效,营造了浓厚的传统文化氛围,这种独特的视觉呈现形式是吸引受众观看并记住内容的重要关键点,直接覆盖了该关键点。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "积极情绪对健康的重要性",
-              "传统三字经的叙事结构"
-            ],
-            "目的点列表": [
-              "为退休人员提供生活指导",
-              "促进内容传播与社群互动"
-            ],
-            "关键点列表": [
-              "传统竹简翻页动画"
-            ]
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "竹简 三字经",
-              "组合逻辑": "提取了[竹简]视觉元素,结合[三字经]传统形式,作为强钩子。"
-            },
-            {
-              "搜索词": "退休 三字经",
-              "组合逻辑": "提取了[退休]受众标签,结合[三字经]内容结构,作为强钩子。"
-            },
-            {
-              "搜索词": "老年人 竹简动画",
-              "组合逻辑": "提取了[老年人]受众,结合[竹简动画]独特视觉风格,作为强钩子。"
-            },
-            {
-              "搜索词": "退休生活 指南",
-              "组合逻辑": "提取了[退休生活]核心主题,结合[指南]内容属性,作为核心内容。"
-            },
-            {
-              "搜索词": "老年养生 秘诀",
-              "组合逻辑": "提取了[老年养生]具体领域,结合[秘诀]内容形式,作为核心内容。"
-            },
-            {
-              "搜索词": "积极心态 养老",
-              "组合逻辑": "提取了[积极心态]核心理念,结合[养老]生活语境,作为核心内容。"
-            },
-            {
-              "搜索词": "退休日常 建议",
-              "组合逻辑": "提取了[退休日常]具体场景,结合[建议]实用功能,作为核心内容。"
-            },
-            {
-              "搜索词": "家庭关系 退休后",
-              "组合逻辑": "提取了[家庭关系]具体领域,结合[退休后]时间语境,作为核心内容。"
-            },
-            {
-              "搜索词": "老年人 上网教学",
-              "组合逻辑": "提取了[老年人]受众,结合[上网教学]解决方案,针对受众痛点。"
-            },
-            {
-              "搜索词": "退休群 必看",
-              "组合逻辑": "提取了[退休群]社群标签,结合[必看]传播钩子,针对受众痛点和传播目的。"
-            },
-            {
-              "搜索词": "晚年生活 规划",
-              "组合逻辑": "提取了[晚年生活]目标场景,结合[规划]解决痛点,针对受众痛点。"
-            },
-            {
-              "搜索词": "空巢老人 心理健康",
-              "组合逻辑": "提取了[空巢老人]特定群体,结合[心理健康]核心需求,针对受众痛点。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "24076827",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250811/57463792oH5RnCGJ9AIZQSHPLl.mp4",
-        "title": "💖参观这个纪念馆,永远怀念!"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20250811/57463792oH5RnCGJ9AIZQSHPLl.mp4",
-          "标题": "💖参观这个纪念馆,永远怀念!",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 7,
-              "分类": "民族悲恸",
-              "灵感点": "“十里长街送总理”的灵车",
-              "描述": "视频展示了曾载着周恩来总理骨灰、驶过“十里长街”接受人民送别的灵车实物,以及当年民众悲痛欲绝的送别画面。",
-              "推理": "视频2:31-2:40展示了在纪念馆内展出的灵车实物,并播放了历史影像资料,重现了周总理逝世时全国人民的巨大悲痛,触及了民族的共同记忆和情感。",
-              "推导说明": "“十里长街送总理”的灵车及其承载的民族悲恸,代表了人民对周恩来总理的深切怀念和无限爱戴。这种全民性的情感震动,会促使创作者去探究并呈现总理“何以如此被爱戴”的原因。为了解释这份深沉的爱戴,创作者需要回顾总理的一生,从他为国立志、投身革命、建设新中国、到晚年鞠躬尽瘁的所有奉献与牺牲。这一巨大的情感共鸣作为触发源,足以引导并推导出视频中对总理完整生平的叙述,以彰显他对国家和人民的伟大贡献,从而解释这份悲恸的源头。",
-              "scoring": {
-                "人设契合度": 5,
-                "触发可能性": 10,
-                "内容解释力": 9,
-                "总分": 24,
-                "评分说明": "人设契合度:按要求默认5分。触发可能性:“十里长街送总理”是中国人民对周恩来总理深切缅怀和爱戴的集体记忆,承载着巨大的民族情感和历史悲恸。实物灵车的展示,能带来极强的视觉和情感冲击,强烈触发创作者表达缅怀之情、展现总理崇高地位的冲动,触发可能性极高。内容解释力:视频将这一事件置于尾声,作为总理一生奉献的最终结局和人民情感的集中爆发点,很好地解释了视频高潮部分的民族悲恸,以及对总理永恒怀念的主题,并引出“盛世如您所愿”的展望,解释力很强。"
-              }
-            },
-            {
-              "候选编号": 2,
-              "分类": "少年立志",
-              "灵感点": "少年周恩来“为中华崛起而读书”",
-              "描述": "视频中通过一幅画作和旁白,讲述了年仅12岁的周恩来,在校长询问读书目的时,明确而坚定地回答“为中华之崛起而读书”的故事。",
-              "推理": "视频0:20-0:31展示了这幅描绘少年周恩来在课堂上立志的画作,并配以详细的旁白,生动再现了这一历史场景和其崇高志向,极具教育和激励意义。",
-              "推导说明": "“为中华崛起而读书”是周恩来总理少年时期立下的宏大志向,代表了他一生的奋斗目标和精神起点。从这一灵感点出发,创作者会自然而然地追溯他如何实践这一诺言:从求学报国、投身革命(五四运动、黄埔军校、南昌起义),到推动国家统一(西安事变)、建立新中国、外交建国,再到晚年为国家现代化鞠躬尽瘁,以及最终将骨灰撒遍祖国大地,所有这些内容都是他实现“中华崛起”的具体体现。因此,这个灵感点能够很好地推导出整个视频对总理一生光荣而伟大历程的展现。",
-              "scoring": {
-                "人设契合度": 5,
-                "触发可能性": 9,
-                "内容解释力": 9,
-                "总分": 23,
-                "评分说明": "人设契合度:按要求默认5分。触发可能性:少年周恩来“为中华崛起而读书”是其人生中最著名、最具启发性的志向宣言之一,极具历史意义和感染力,很容易成为创作者展现其伟大一生的起点,故触发可能性高。内容解释力:视频在开篇部分即详细描绘了这一场景,并明确指出这是周恩来少年时的志向,为他日后的革命和建设事业奠定了思想基础,能够很好地解释视频对周恩来光荣一生的叙述主线。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "弘扬伟人精神,激发爱国情怀",
-                "描述": "创作者希望通过讲述周恩来总理的光辉一生、无私奉献和崇高品格,让受众深入了解其为中华民族复兴所付出的巨大牺牲和卓越贡献,从而激发对祖国的热爱、民族自豪感和对先辈的崇敬之情。",
-                "推理": "视频通过多角度展示周恩来总理从少年立志“为中华之崛起而读书”到晚年“为人民服务,鞠躬尽瘁,死而后已”的历程。具体线索包括:总理年幼时立下宏愿的场景(0:29-0:32),他为国家团结和解放做出的努力(0:47-0:56),在开国大典上提出“飞机不够就飞两趟”的魄力(1:07-1:13),以及他晚年带病坚持工作,甚至在极度疲惫中睡着、体重仅61斤的感人细节(1:27-2:29)。这些叙述和画面旨在传递总理的伟大精神,并最终以“这盛世如您所愿,告慰您一个更好的中国”的感性呼唤,将观众的情感引向对祖国繁荣的自豪和对总理的深切缅怀。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "动员受众,扩大伟人事迹传播",
-                "描述": "创作者通过明确的号召和互动机制,希望观众积极参与视频的转发和传播,以实现对周恩来总理伟大事迹的更广泛宣传,确保其精神能被更多后人知晓并铭记。",
-                "推理": "视频的结尾部分(3:12-3:39)包含了一个非常直接和强烈的行动呼吁:'今天请所有看到这个视频的朋友,请花1分钟时间转发出去。一人转发,十人知道,百人支持,千人同心,万人铭记,让更多人知道他们为祖国人民的付出。' 这一段话清晰地表明了创作者希望借助现有观众的力量,将内容传播给更广泛的群体,以达到纪念和弘扬伟人精神的目的。这是一种典型的面向粉丝或观众的运营策略,旨在通过社群传播来扩大影响力。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 1,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "庄重鞠躬致敬",
-                "描述": "视频开头,一位女性在周恩来总理的白色坐像前进行庄重的鞠躬礼,表达敬意。",
-                "children": []
-              },
-              {
-                "候选编号": 2,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "伟人逝世48年",
-                "描述": "视频通过文字提示,自周恩来总理逝世至今已过去48年,作为时间背景。",
-                "children": []
-              },
-              {
-                "候选编号": 3,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "参观周总理纪念馆",
-                "描述": "视频记录了女性前往周恩来总理故乡的纪念馆进行实地参观,表达追忆之情。",
-                "children": [
-                  {
-                    "候选编号": 4,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "周恩来纪念馆建筑",
-                    "描述": "视频展示了位于江苏淮安的周恩来纪念馆,一座外观宏伟庄严的现代建筑。",
-                    "children": [
-                      {
-                        "候选编号": 25,
-                        "维度大类": "形式",
-                        "维度细分": "风格",
-                        "关键点": "纪念馆石柱象征现代化",
-                        "描述": "视频指出周恩来纪念馆外围的四根高大花纹岩石柱,象征着周恩来总理提出的“四个现代化”宏伟设想,体现建筑的文化寓意。",
-                        "children": [],
-                        "作为子节点的原因": "纪念馆石柱是周恩来纪念馆建筑外观的具体组成部分和风格特征。"
-                      }
-                    ],
-                    "作为子节点的原因": "周恩来纪念馆建筑是参观周总理纪念馆这一整体活动中的一个具体组成部分,是参观的对象之一。"
-                  },
-                  {
-                    "候选编号": 5,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "挺拔伟人铜像",
-                    "描述": "纪念馆前的广场上矗立着一座周恩来总理全身的青铜雕像,姿态挺拔,象征其高大形象。",
-                    "children": [],
-                    "作为子节点的原因": "挺拔伟人铜像也是参观周总理纪念馆这一整体活动中的一个具体组成部分,是纪念馆内的重要展示物。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 7,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "总理故居旧照片",
-                "描述": "视频展示了1898年周恩来总理在江苏淮安出生的故居老照片,还原其出生之地。",
-                "children": []
-              },
-              {
-                "候选编号": 8,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "12岁东北求学",
-                "描述": "视频讲述了周恩来总理12岁时,远赴东北地区求学的早期经历。",
-                "children": []
-              },
-              {
-                "候选编号": 9,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "山河破碎局势图",
-                "描述": "视频展示了一幅描绘19世纪末20世纪初中国被列强瓜分、内忧外患的政治地图,作为时代背景。",
-                "children": []
-              },
-              {
-                "候选编号": 12,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "15岁求学南开照片",
-                "描述": "视频展示了周恩来总理15岁时在南开学校求学期间的旧照片,记录了其青少年时期的学习轨迹。",
-                "children": []
-              },
-              {
-                "候选编号": 13,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "留学日本与五四运动",
-                "描述": "视频介绍了周恩来总理19岁留学日本,回国后积极投身五四运动的历史,展现其早期革命参与。",
-                "children": []
-              },
-              {
-                "候选编号": 14,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "黄埔军校主任照片",
-                "描述": "视频展示了周恩来总理26岁时担任黄埔军校政治部主任的旧照片,体现其在早期革命军队中的重要地位。",
-                "children": []
-              },
-              {
-                "候选编号": 15,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "南昌起义领导绘画",
-                "描述": "视频通过一幅绘画,描绘了周恩来总理在1927年南昌起义中担任重要领导人的历史事件。",
-                "children": []
-              },
-              {
-                "候选编号": 16,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "西安事变和平解决",
-                "描述": "视频展示了周恩来总理38岁时,推动西安事变和平解决的相关历史照片和关于民族团结的讲话内容。",
-                "children": []
-              },
-              {
-                "候选编号": 17,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "51岁开国大典照片",
-                "描述": "视频展示了周恩来总理51岁时,参加1949年新中国成立开国大典时的珍贵照片。",
-                "children": [
-                  {
-                    "候选编号": 18,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "开国阅兵飞机飞两趟",
-                    "描述": "视频讲述了开国大典时,因飞机数量不足,周恩来总理决定让有限的飞机飞两趟,以壮国威的故事,并对比了中国空军今非昔比的强大。",
-                    "children": [],
-                    "作为子节点的原因": "开国阅兵飞机飞两趟是开国大典这一宏大事件中一个具体的、富有故事性的细节,是对开国大典的具体化描述。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 19,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "总理日常中山装",
-                "描述": "视频展示了周恩来总理生前经常穿着的米色中山装,体现其朴素的生活作风。",
-                "children": [
-                  {
-                    "候选编号": 20,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "“为人民服务”胸章",
-                    "描述": "视频特写了周恩来总理中山装上佩戴的印有“为人民服务”字样的徽章,象征其政治信念和人生宗旨。",
-                    "children": [],
-                    "作为子节点的原因": "“为人民服务”胸章是总理日常中山装这一整体服饰上的一个具体细节和组成部分。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 22,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "75岁刮脸刀旁睡着",
-                "描述": "视频通过一幅油画和旁白,讲述了周恩来总理75岁时,因长时间工作疲惫不堪,在卫生间刮脸时靠墙睡着的感人场景,手中还紧握着满是肥皂泡的刮脸刀。",
-                "children": []
-              },
-              {
-                "候选编号": 23,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "病中坚持工作13次手术",
-                "描述": "视频展示了周恩来总理1974年住院后,在频繁进行13次手术的情况下,仍坚持处理公务、会见外宾的工作记录,体现其超人毅力。",
-                "children": []
-              },
-              {
-                "候选编号": 24,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "最后提出“四个现代化”",
-                "描述": "视频展示了周恩来总理1975年最后一次提出建设“四个现代化”宏伟目标的照片和背景介绍。",
-                "children": []
-              },
-              {
-                "候选编号": 26,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "77岁会外宾体重61斤",
-                "描述": "视频展示了周恩来总理77岁最后一次会见外宾的照片,并指出当时他体重仅61斤(约30.5公斤),凸显其晚年身体的极度虚弱和消瘦。",
-                "children": []
-              },
-              {
-                "候选编号": 29,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "骨灰撒江河体现为民",
-                "描述": "视频讲述了周恩来总理将其骨灰撒入江河大地的最后遗愿,并引用其“撒地做肥料,撒水喂鱼”的话,再次强调其至死不渝的“为人民服务”精神。",
-                "children": []
-              }
-            ],
-            "total_count": 23,
-            "root_count": 18
-          }
-        },
-        "选题理解": {
-          "主题": "铭记周恩来总理的崇高精神与丰功伟绩",
-          "描述": "视频以周恩来总理“为中华崛起而读书”的少年立志和“十里长街送总理”的民族悲恸为灵感,通过参观其纪念馆,展示历史照片、画作及文物,如“为人民服务”徽章、病中坚持工作的记录以及最后会见外宾时极度消瘦的照片,以此展现他为国家和人民鞠躬尽瘁的一生。此创作旨在弘扬伟人精神,激发受众的爱国情怀,并通过呼吁转发,动员受众扩大伟人事迹的传播,让更多人铭记其为祖国做出的巨大贡献。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "“十里长街送总理”的灵车",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了“十里长街送总理”的灵车实物及历史画面,并以此作为重要情感触发点。"
-              },
-              {
-                "灵感点": "少年周恩来“为中华崛起而读书”",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过画作和旁白详细讲述了少年周恩来“为中华之崛起而读书”的立志故事。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "弘扬伟人精神,激发爱国情怀",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过展现周恩来总理一生为国为民的奉献和牺牲,直接表达了弘扬伟人精神、激发爱国情怀的意图。"
-              },
-              {
-                "目的点": "动员受众,扩大伟人事迹传播",
-                "是否覆盖": true,
-                "覆盖说明": "视频结尾明确呼吁观众转发,以“一人转发,十人知道,百人支持,千人同心,万人铭记”的方式,实现对伟人事迹的广泛传播。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "庄重鞠躬致敬",
-                "是否覆盖": true,
-                "覆盖说明": "视频开头女性在总理坐像前鞠躬,体现了对伟人的崇敬和庄重氛围。"
-              },
-              {
-                "关键点": "伟人逝世48年",
-                "是否覆盖": true,
-                "覆盖说明": "视频开头提示“您走了48年”,交代了追忆的时间背景。"
-              },
-              {
-                "关键点": "参观周总理纪念馆",
-                "是否覆盖": true,
-                "覆盖说明": "视频主体内容即为女性参观周恩来纪念馆的过程,串联起总理一生的事迹。"
-              },
-              {
-                "关键点": "周恩来纪念馆建筑",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了纪念馆的宏伟建筑外观,作为参观的地点背景。"
-              },
-              {
-                "关键点": "纪念馆石柱象征现代化",
-                "是否覆盖": true,
-                "覆盖说明": "视频特意指出纪念馆的四根石柱象征总理提出的“四个现代化”,赋予建筑深层含义。"
-              },
-              {
-                "关键点": "挺拔伟人铜像",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了纪念馆前的总理铜像,作为庄严的象征。"
-              },
-              {
-                "关键点": "总理故居旧照片",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理出生故居的旧照片,回溯其起点。"
-              },
-              {
-                "关键点": "12岁东北求学",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过旁白介绍了总理12岁东北求学的经历。"
-              },
-              {
-                "关键点": "山河破碎局势图",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了当时中国山河破碎的地图,交代了少年总理立志救国的时代背景。"
-              },
-              {
-                "关键点": "15岁求学南开照片",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理15岁在南开学校求学时的照片。"
-              },
-              {
-                "关键点": "留学日本与五四运动",
-                "是否覆盖": true,
-                "覆盖说明": "视频提及总理留学日本并投身五四运动的经历,展现其早期革命参与。"
-              },
-              {
-                "关键点": "黄埔军校主任照片",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理26岁担任黄埔军校政治部主任的照片,体现其重要地位。"
-              },
-              {
-                "关键点": "南昌起义领导绘画",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过绘画描绘了总理在南昌起义中的领导角色。"
-              },
-              {
-                "关键点": "西安事变和平解决",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理38岁推动西安事变和平解决的历史照片,突显其在民族危难中的作用。"
-              },
-              {
-                "关键点": "51岁开国大典照片",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理51岁参加开国大典的照片,纪念新中国的诞生。"
-              },
-              {
-                "关键点": "开国阅兵飞机飞两趟",
-                "是否覆盖": true,
-                "覆盖说明": "视频讲述了开国大典飞机飞两趟的故事,展现了总理的智慧和新中国初期的艰辛,并与今日中国国力形成对比,激发自豪感。"
-              },
-              {
-                "关键点": "总理日常中山装",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理生前穿着的中山装,体现其简朴作风。"
-              },
-              {
-                "关键点": "“为人民服务”胸章",
-                "是否覆盖": true,
-                "覆盖说明": "视频特写中山装上的“为人民服务”徽章,是总理一生信念的象征,强化了其为民服务的核心精神。"
-              },
-              {
-                "关键点": "75岁刮脸刀旁睡着",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过油画和旁白,生动再现了总理因过度劳累而睡着的场景,体现其勤政与辛劳。"
-              },
-              {
-                "关键点": "病中坚持工作13次手术",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理病中仍坚持工作的记录,强调其对工作的极端负责和奉献精神。"
-              },
-              {
-                "关键点": "最后提出“四个现代化”",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了总理1975年最后一次提出“四个现代化”的宏伟目标,展现其对国家未来的深远思考和贡献。"
-              },
-              {
-                "关键点": "77岁会外宾体重61斤",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示总理最后会见外宾的照片并提及其消瘦的体重,直观地展现了其为国操劳至生命最后一刻的付出。"
-              },
-              {
-                "关键点": "骨灰撒江河体现为民",
-                "是否覆盖": true,
-                "覆盖说明": "视频讲述了总理骨灰撒入江河大地的遗愿及其“做肥料、喂鱼”的朴素愿望,再次深刻体现其“为人民服务”的崇高精神。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "“十里长街送总理”的灵车",
-              "少年周恩来“为中华崛起而读书”"
-            ],
-            "目的点列表": [
-              "弘扬伟人精神,激发爱国情怀",
-              "动员受众,扩大伟人事迹传播"
-            ],
-            "关键点列表": [
-              "庄重鞠躬致敬",
-              "伟人逝世48年",
-              "参观周总理纪念馆",
-              "周恩来纪念馆建筑",
-              "纪念馆石柱象征现代化",
-              "挺拔伟人铜像",
-              "总理故居旧照片",
-              "12岁东北求学",
-              "山河破碎局势图",
-              "15岁求学南开照片",
-              "留学日本与五四运动",
-              "黄埔军校主任照片",
-              "南昌起义领导绘画",
-              "西安事变和平解决",
-              "51岁开国大典照片",
-              "开国阅兵飞机飞两趟",
-              "总理日常中山装",
-              "“为人民服务”胸章",
-              "75岁刮脸刀旁睡着",
-              "病中坚持工作13次手术",
-              "最后提出“四个现代化”",
-              "77岁会外宾体重61斤",
-              "骨灰撒江河体现为民"
-            ]
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "十里长街送总理 历史影像",
-              "组合逻辑": "提取了“十里长街送总理”这一民族共同记忆的事件,结合了“历史影像”这一强烈的视觉钩子和内容形式,用于检索相关历史回顾视频。"
-            },
-            {
-              "搜索词": "周恩来 为中华崛起而读书",
-              "组合逻辑": "提取了周恩来总理最具代表性的少年立志名言,结合了其“立志”的主题语境,用于检索伟人少年时期励志故事类内容。"
-            },
-            {
-              "搜索词": "周恩来61斤 催泪瞬间",
-              "组合逻辑": "提取了周恩来总理晚年体重“61斤”这一触目惊心的具象细节,结合了“催泪瞬间”的情感语境,用于检索感人肺腑的伟人奉献事迹。"
-            },
-            {
-              "搜索词": "周恩来纪念馆 探访Vlog",
-              "组合逻辑": "提取了“周恩来纪念馆”这一核心实体和具象地点,结合了“探访Vlog”这一内容形式,用于检索实地参观纪念馆的视频。"
-            },
-            {
-              "搜索词": "周恩来中山装 为人民服务",
-              "组合逻辑": "提取了周恩来总理具象的衣着“中山装”及其上“为人民服务”的徽章,结合了其核心信念的主题,用于检索体现伟人朴素作风和奉献精神的内容。"
-            },
-            {
-              "搜索词": "周恩来病中 工作记录",
-              "组合逻辑": "提取了周恩来总理“病中”这一具体状态,结合了其“工作记录”这一体现其超人毅力和奉献精神的主题,用于检索伟人带病工作的感人事迹。"
-            },
-            {
-              "搜索词": "伟人精神 传承教育",
-              "组合逻辑": "提取了“伟人精神”这一抽象核心概念,结合了“传承教育”这一弘扬和教育的主题,用于检索旨在传承红色基因的教育类内容。"
-            },
-            {
-              "搜索词": "周恩来事迹 爱国情怀",
-              "组合逻辑": "提取了“周恩来事迹”这一内容主题,结合了“爱国情怀”这一激发受众情感的目的,用于检索激发民族自豪感的历史人物事迹。"
-            },
-            {
-              "搜索词": "周恩来骨灰 撒江河遗愿",
-              "组合逻辑": "提取了“周恩来骨灰”这一具象元素,结合了“撒江河遗愿”这一体现其至死不渝为民精神的特定事件,用于检索伟人高尚品格的解读。"
-            },
-            {
-              "搜索词": "开国大典 飞机飞两趟",
-              "组合逻辑": "提取了“开国大典”这一重大历史事件,结合了“飞机飞两趟”这一鲜为人知的历史细节,用于检索揭秘历史背后故事的内容。"
-            },
-            {
-              "搜索词": "周恩来 鞠躬尽瘁",
-              "组合逻辑": "提取了“周恩来”这一核心人物,结合了“鞠躬尽瘁”这一高度概括其一生奉献精神的主题语境,用于检索伟人一生的总结性内容。"
-            },
-            {
-              "搜索词": "周恩来少年 励志故事",
-              "组合逻辑": "提取了“周恩来少年”这一特定人生阶段,结合了“励志故事”这一主题语境,用于检索伟人青少年时期的成长与抱负。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "22847469",
-        "video": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20240725/66055630tfcs46gemRBilN8aZ.mp4",
-        "title": "⭕这个地方,竟如此好!一妻多夫的村"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/longvideo/transcode/video/vpc/20240725/66055630tfcs46gemRBilN8aZ.mp4",
-          "标题": "⭕这个地方,竟如此好!一妻多夫的村",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 1,
-              "分类": "社会习俗",
-              "灵感点": "一妻多夫的古老婚姻",
-              "描述": "发现中国境内一个村落,至今仍保留着一个女人可以同时拥有多个丈夫的古老婚姻制度,在当地被称为“伙婚”,这种制度在现代社会背景下显得尤为独特和罕见。",
-              "推理": "视频开篇直接点出“女人的天堂 俄亚大村”和“一妻多夫的村落”,并用多帧画面展示了婚礼仪式、妻子与多个丈夫的互动场景,以及对这种婚姻制度的详细解说,这无疑是触发创作者关注并深入了解其背后的文化与社会机制的核心刺激。",
-              "推导说明": "从“一妻多夫的古老婚姻”这一灵感点出发,创作者会自然而然地想深入了解这种婚姻制度的具体运作方式(如家庭成员的职责、亲缘关系的处理),其社会背景和历史成因(如人口结构、土地资源),以及这种制度在当地形成的独特村落文化。视频内容完美涵盖了这些方面,从展示这种婚姻的实际场景、解释其规则,到探讨其存在的深层原因和对当地生活的影响,都与这一核心灵感点高度契合。开篇即点出“女人的天堂 俄亚大村”和“一妻多夫的村落”,直接将这一婚姻制度作为核心看点。",
-              "scoring": {
-                "人设契合度": 8,
-                "触发可能性": 9,
-                "内容解释力": 9,
-                "总分": 26,
-                "评分说明": "该灵感点完美契合视频的核心主题和吸引力。视频开篇就点明“女人的天堂 俄亚大村”和“一妻多夫”这一独特习俗,并用大量画面和解说来展示其具体运作方式(如妻子地位最高,分工明确,孩子称呼等)。对于一个创作者而言,发现中国境内仍存在这种颠覆传统观念的古老婚姻制度,本身就具有极强的猎奇性和传播价值,是引发创作冲动的核心刺激源。因此,它具有极高的触发可能性和内容解释力。"
-              }
-            },
-            {
-              "候选编号": 4,
-              "分类": "社会背景",
-              "灵感点": "贫瘠环境下的生存策略",
-              "描述": "发现该村落长期存在男多女少的光棍问题以及土地资源极其贫瘠的困境,这种特殊的婚姻制度是为了维持家族血脉传承、集中财产并共同富裕而产生的一种适应性生存策略。",
-              "推理": "视频明确指出“当地男多女少光棍多土地又极为贫瘠”和“为了维持家族血脉的传承保持财产的集中并让大家共同富裕”,揭示了这种婚姻制度并非偶然,而是当地人民在恶劣自然和社会条件下,为生存和发展而演化出的独特智慧,这种深层原因的揭示为创作提供了更深刻的思考维度。",
-              "推导说明": "从“贫瘠环境下的生存策略”这一灵感点出发,创作者会去探究在资源匮乏、人口失衡(男多女少光棍多)的特定地理环境下,当地居民是如何通过独特的社会制度来维系家族生存和发展的。视频中明确指出,一妻多夫制度是为了“维持家族血脉的传承,保持财产的集中,并让大家共同富裕”。这一深层动机揭示了这种婚姻制度并非偶然,而是当地人民在特定历史和地理条件下的智慧选择和生存策略。因此,该灵感点能够很好地推导出视频对婚姻制度起源和功能的所有解释,以及它如何与当地的自然和社会环境相适应。",
-              "scoring": {
-                "人设契合度": 7,
-                "触发可能性": 7,
-                "内容解释力": 8,
-                "总分": 22,
-                "评分说明": "这个灵感点是视频内容深层原因的揭示,而非最初的触发源。视频在介绍了一妻多夫的婚姻制度后,才在后半段(1:45起)解释了其产生的社会背景和生存策略。虽然这是理解该制度至关重要的一部分,但创作者可能首先是被这种奇特的婚姻形式所吸引(如候选1),然后才深入挖掘其背后的经济和社会原因。因此,它更多是作为视频内容的深化和支撑,而非第一线的创作刺激。它能很好地解释制度的合理性和持续性,但在解释整个视频(包括最初的冲击感和‘女人天堂’的设定)方面略逊于直接揭示婚姻制度本身。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "普及奇特文化习俗",
-                "描述": "创作者旨在向观众介绍中国一个鲜为人知且独特的村落及其一妻多夫的婚姻制度,通过展示其历史、习俗和生活方式,拓展观众的认知视野。",
-                "推理": "视频标题“女人的天堂 俄亚大村”以及贯穿视频的文字提示“这样的生活,如果不是亲眼所见你肯定不敢相信”、“堪称女人的天堂,彻底颠覆了人们的三观!”、“接下来带您去开开眼”、“看完真的是开眼了”、“让更多人都瞧一瞧,长长见识!”都明确表达了介绍奇特风俗、增长见闻的意图。视频内容详细讲解了婚姻制度的由来(男多女少、土地贫瘠、血脉传承、财产集中)和家庭分工,提供了大量信息。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "促进视频传播和互动",
-                "描述": "创作者希望通过视频的独特性和呼吁,鼓励观众进行转发和分享,以扩大视频的传播范围和影响力,吸引更多潜在观众。",
-                "推理": "视频开头和结尾都出现的“转发分享积福气”标语,以及结尾的直接呼吁“请动动手指转发出去,让更多人都瞧一瞧,长长见识!”、“群多的就多转几个,千万别让这么难得的视频在你手里终止了!”这些明确的引导语和情感号召,都旨在鼓励观众积极转发和分享视频,从而提升视频的曝光率和互动量。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 2,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "俄亚大村",
-                "描述": "视频介绍的中国特殊村落名称,位于四川云南交界处,地理位置独特。",
-                "children": [
-                  {
-                    "候选编号": 10,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "蜂巢式村落建筑",
-                    "描述": "村庄房屋依山而建,层叠紧密,远看酷似巨大蜂巢,视觉壮观独特。",
-                    "children": [],
-                    "作为子节点的原因": "蜂巢式村落建筑是俄亚大村独特的物理景观,是村落整体的局部特征。"
-                  },
-                  {
-                    "候选编号": 4,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "纳西族文化",
-                    "描述": "村庄居民属于纳西族,展示了其独特的民族风情和传统生活方式。",
-                    "children": [],
-                    "作为子节点的原因": "纳西族文化是俄亚大村居民的民族特色,是村落整体的文化组成部分。"
-                  },
-                  {
-                    "候选编号": 7,
-                    "维度大类": "实质",
-                    "维度细分": "分类",
-                    "关键点": "兄弟共妻制",
-                    "描述": "丈夫们必须是亲兄弟才能组成家庭,强调血缘关系在婚姻中的作用。",
-                    "children": [
-                      {
-                        "候选编号": 5,
-                        "维度大类": "实质",
-                        "维度细分": "分类",
-                        "关键点": "女性家庭地位高",
-                        "描述": "在多夫婚姻中,妻子拥有最高家庭地位,负责管理和决策,颠覆传统认知。",
-                        "children": [],
-                        "作为子节点的原因": "女性家庭地位高是兄弟共妻制下家庭权力分配和角色定位的具体体现。"
-                      },
-                      {
-                        "候选编号": 8,
-                        "维度大类": "实质",
-                        "维度细分": "分类",
-                        "关键点": "共同抚养子女",
-                        "描述": "孩子出生后,所有丈夫共同抚养,不纠结生父,一视同仁,展现独特的育儿观。",
-                        "children": [],
-                        "作为子节点的原因": "共同抚养子女是兄弟共妻制下育儿方式的具体实践,反映了其独特的亲子关系处理。"
-                      },
-                      {
-                        "候选编号": 9,
-                        "维度大类": "实质",
-                        "维度细分": "元素",
-                        "关键点": "独特亲属称谓",
-                        "描述": "孩子按年龄称呼丈夫们为“大爸、二爸、三爸”等,反映特殊的家庭结构。",
-                        "children": [],
-                        "作为子节点的原因": "独特亲属称谓是兄弟共妻制家庭结构下,孩子对多位父亲称呼方式的具体细节,是该制度的直接产物。"
-                      }
-                    ],
-                    "作为子节点的原因": "兄弟共妻制是俄亚大村最核心、最独特的社会婚姻制度,是村落整体的社会结构特征。"
-                  },
-                  {
-                    "候选编号": 14,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "整体生活和谐",
-                    "描述": "尽管制度复杂,但家庭成员之间分工合作,生活气氛和谐融洽,展现特殊幸福。",
-                    "children": [],
-                    "作为子节点的原因": "整体生活和谐是俄亚大村在独特制度和文化背景下呈现出的整体氛围和结果,是村落整体生活状态的抽象总结。"
-                  }
-                ]
-              }
-            ],
-            "total_count": 8,
-            "root_count": 1
-          }
-        },
-        "选题理解": {
-          "主题": "揭秘中国四川云南交界处俄亚大村一妻多夫的古老婚姻习俗",
-          "描述": "视频以中国鲜为人知的俄亚大村及其延续四百多年的“伙婚”制度为灵感,通过展示当地独特的蜂巢式建筑、纳西族人民的生活方式以及女性主导、兄弟共妻、共同育儿的特殊家庭结构,旨在普及这一奇特文化习俗,并揭示其为应对男多女少和土地贫瘠困境而形成的生存智慧,同时通过呼吁转发互动,促进视频的广泛传播。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "一妻多夫的古老婚姻",
-                "是否覆盖": true,
-                "覆盖说明": "视频的核心内容是介绍俄亚大村至今保留的“伙婚”制度,包括其历史延续和具体运作方式,直接体现了这一灵感。"
-              },
-              {
-                "灵感点": "贫瘠环境下的生存策略",
-                "是否覆盖": true,
-                "覆盖说明": "视频明确解释了“伙婚”制度产生的深层原因,即当地男多女少、土地贫瘠,以及为维持家族血脉传承和集中财产的考量,完整呈现了这一生存策略的背景。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "普及奇特文化习俗",
-                "是否覆盖": true,
-                "覆盖说明": "视频详细介绍了俄亚大村的地理位置、独特的建筑风格、纳西族风情、婚姻制度的具体细节及家庭分工等,旨在向观众普及这一独特的文化习俗,并用“开开眼”的措辞强化了此目的。"
-              },
-              {
-                "目的点": "促进视频传播和互动",
-                "是否覆盖": true,
-                "覆盖说明": "视频在结尾明确呼吁观众“转发扩散”、“让更多人都瞧一瞧长长见识”,并强调“别让这么难得的视频在你手里终止了”,直接旨在促进视频的广泛传播和互动。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "俄亚大村",
-                "是否覆盖": true,
-                "覆盖说明": "视频明确指出介绍的村落名称为“俄亚大村”,并展示了其位于四川云南交界处的地理位置。"
-              },
-              {
-                "关键点": "蜂巢式村落建筑",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过多段航拍画面,展现了村庄房屋依山而建、层叠紧密的景象,并描述其“远看就像是一个巨大的蜂巢”,强调了其视觉壮观性。"
-              },
-              {
-                "关键点": "纳西族文化",
-                "是否覆盖": true,
-                "覆盖说明": "视频明确提及村里居住着“纳西族人”,并通过展示他们的传统服饰、婚礼仪式和日常生活场景,体现了其民族文化。"
-              },
-              {
-                "关键点": "兄弟共妻制",
-                "是否覆盖": true,
-                "覆盖说明": "视频反复强调该婚姻制度的核心特点是“一妻多夫”,并且“丈夫们必须是亲兄弟”,明确了血缘关系在此制度中的重要性。"
-              },
-              {
-                "关键点": "女性家庭地位高",
-                "是否覆盖": true,
-                "覆盖说明": "视频明确指出在多夫婚姻中“妻子地位最高”,并负责“管家,做决定分任务”,突出了女性在家庭中的核心地位。"
-              },
-              {
-                "关键点": "共同抚养子女",
-                "是否覆盖": true,
-                "覆盖说明": "视频说明在妻子生子后,丈夫们不会纠结生父,而是将孩子视作“自己的亲骨肉来对待”,体现了共同抚养的模式。"
-              },
-              {
-                "关键点": "独特亲属称谓",
-                "是否覆盖": true,
-                "覆盖说明": "视频明确介绍了孩子们会根据年龄大小称呼丈夫们为“大爸、二爸、三爸”,展现了这种特殊家庭结构下的称谓习惯。"
-              },
-              {
-                "关键点": "整体生活和谐",
-                "是否覆盖": true,
-                "覆盖说明": "视频在描述家庭分工和成员关系时,多次使用“生活十分和谐”等词语,营造了积极、融洽的氛围,展现了特殊制度下的和谐生活状态。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "一妻多夫的古老婚姻",
-              "贫瘠环境下的生存策略"
-            ],
-            "目的点列表": [
-              "普及奇特文化习俗",
-              "促进视频传播和互动"
-            ],
-            "关键点列表": [
-              "俄亚大村",
-              "蜂巢式村落建筑",
-              "纳西族文化",
-              "兄弟共妻制",
-              "女性家庭地位高",
-              "共同抚养子女",
-              "独特亲属称谓",
-              "整体生活和谐"
-            ]
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "俄亚大村 伙婚",
-              "组合逻辑": "提取了核心村落名称(俄亚大村)和当地特有的婚姻制度名称(伙婚)。"
-            },
-            {
-              "搜索词": "一妻多夫 揭秘",
-              "组合逻辑": "提取了最具强钩子属性的婚姻制度(一妻多夫),结合其内容属性为揭示真相(揭秘)。"
-            },
-            {
-              "搜索词": "兄弟共妻 纳西族",
-              "组合逻辑": "提取了具体的婚姻形式(兄弟共妻),结合其所属的民族文化背景(纳西族)。"
-            },
-            {
-              "搜索词": "蜂巢建筑 村落",
-              "组合逻辑": "提取了视频中独特的视觉标志性建筑(蜂巢建筑),结合其作为地理实体(村落)。"
-            },
-            {
-              "搜索词": "女性地位 独特家庭",
-              "组合逻辑": "提取了多夫婚姻中女性的核心角色(女性地位),结合其形成的特殊家庭结构(独特家庭)。"
-            },
-            {
-              "搜索词": "贫瘠山区 婚姻习俗",
-              "组合逻辑": "提取了视频背景中的环境痛点(贫瘠山区),结合因此形成的特殊文化(婚姻习俗)。"
-            },
-            {
-              "搜索词": "共同育儿 家族传承",
-              "组合逻辑": "提取了独特的育儿方式(共同育儿),结合其背后维系家族延续的目的(家族传承)。"
-            },
-            {
-              "搜索词": "中国奇闻 习俗",
-              "组合逻辑": "提取了内容本身的稀有性和奇特性(中国奇闻),结合其文化属性(习俗)。"
-            },
-            {
-              "搜索词": "大爸二爸 称谓",
-              "组合逻辑": "提取了视频中提及的特殊亲属称呼(大爸二爸),结合其作为称谓的属性(称谓)。"
-            },
-            {
-              "搜索词": "多夫婚姻 真实生活",
-              "组合逻辑": "提取了核心的婚姻制度类型(多夫婚姻),结合其在现实生活中的展现(真实生活)。"
-            },
-            {
-              "搜索词": "四川云南 边界村",
-              "组合逻辑": "提取了具体的地理位置信息(四川云南),结合其作为特殊地理实体(边界村)。"
-            },
-            {
-              "搜索词": "少数民族 婚姻观",
-              "组合逻辑": "提取了视频涉及的民族属性(少数民族),结合其独特的社会观念(婚姻观)。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "23698074",
-        "video": "https://rescdn.yishihui.com/jq_oss/video/2024081407060094959.mp4",
-        "title": "🔴中国大地原点竟藏在这!"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/jq_oss/video/2024081407060094959.mp4",
-          "标题": "🔴中国大地原点竟藏在这!",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 1,
-              "分类": "地理发现",
-              "灵感点": "中国大地原点位置与意义",
-              "描述": "发现中国国土核心地带存在一个名为“中华人民共和国大地原点”的标志性地点,它是一个国家地理坐标的基准点,其重要性却鲜为人知。",
-              "推理": "视频开篇(0:00-0:06)创作者兴奋地指出她身处“中国最中心的地方”,并展示刻有“中华人民共和国大地原点”字样的石碑。随后(0:07-0:15)她提到“99%的人都不知道中国的大地原点究竟在哪里”,表明了发现并分享此地重要性的冲动是创作的触发源。",
-              "推导说明": "视频以“中国最中心的地方”开篇,直接点明了大地原点的地理位置,并迅速引出其深远意义。内容围绕大地原点的定义、历史背景(包括自主建立的必要性)、具体位置(陕西泾阳县永乐镇北流村)、构成部分(主体建筑、仪器台、投影台、中心标志)以及对国家测绘事业、经济发展和国防建设的重要性展开。最后提及的古代测绘智慧与现代发现的巧合、以及新旧坐标系的过渡,都是在深化“大地原点”的“位置”所承载的“意义”。因此,这个灵感点能够完整地推导出视频的全部内容。",
-              "scoring": {
-                "人设契合度": 8,
-                "触发可能性": 9,
-                "内容解释力": 9,
-                "总分": 26,
-                "评分说明": "该灵感点与创作者的爱国、科普人设高度契合。视频以直接展示“中国最中心的地方”——大地原点开篇,并详细介绍了其作为国家地理坐标基准点的重要性、具体位置、构成部分以及它对地图测绘、交通、国防等领域的关键作用,这表明发现该地点及其未被广知的意义很可能是触发创作的首要刺激源,并能很好地解释视频的绝大部分内容。"
-              }
-            },
-            {
-              "候选编号": 2,
-              "分类": "历史进程",
-              "灵感点": "中国大地坐标系统自主建立",
-              "描述": "了解到新中国成立初期曾沿用前苏联坐标系统,导致测绘误差巨大,严重影响国家发展,进而促使中国自主探索并建立自己精准大地坐标系统的历史过程和国家决心。",
-              "推理": "创作者在视频中(0:31-1:13)详细讲述了“新中国刚成立时,我们只能沿用前苏联的大地原点”,以及这带来的“误差可不是一般的大”,直至“1975年中国决定一定要建立我们自己的大地坐标系统,寻觅了整整三年,1978年我们终于找到这个...独立坐标原点”,这一段关于国家自主建立坐标系统的历史和决心,是重要的创作触发点。",
-              "推导说明": "视频用了相当篇幅讲述新中国成立初期沿用前苏联大地原点带来的误差和不便,以及中国在1975年决定自主建立大地坐标系统的历史进程。这一灵感点直接驱动了视频对大地原点选址(筛选考察三年)、建设(1978年建成)过程的详细描述,并强调了其对国家主权和测绘事业独立自主的象征意义。虽然视频也包含了地理位置和建筑细节的介绍,但这些都被置于“自主建立”这一宏大历史叙事的框架下,作为展现国家实力的重要成果。因此,这个灵感点能够推导出视频的主要叙事线索和大部分内容。",
-              "scoring": {
-                "人设契合度": 8,
-                "触发可能性": 7,
-                "内容解释力": 8,
-                "总分": 23,
-                "评分说明": "该灵感点与创作者的爱国、科普人设同样高度契合,强调了国家自主建立核心基础设施的民族自豪感。视频用大量篇幅讲述了新中国初期沿用苏联坐标系统所带来的问题,以及中国独立自主建立大地坐标系统的历史进程,这确实是视频的重要组成部分。然而,视频的开篇和核心展示对象是“大地原点”这一具体地点和建筑,历史进程是为衬托其重要性而展开的背景故事。因此,发现这个“最中心的地方”本身(灵感点1)可能比单纯了解到历史进程(灵感点2)更直接地触发了对整个物理实体的探索和视频制作。灵感点2主要解释了“为什么”和“如何”建立,但对大地原点具体构成和未来坐标系统演变部分的解释力稍弱。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "分享中国大地原点知识",
-                "描述": "创作者希望通过视频向受众普及中国大地原点的历史、重要性及其组成部分。",
-                "推理": "视频开篇(0:00-0:05)即点明主题“中国最中心的地方”,随后详细介绍了大地原点的定义(0:21-0:31)、设立的背景和历史意义(0:31-1:13),并带领观众了解其主体建筑、仪器台、投影台和中心标志(1:48-2:37)。视频还提及了大地原点在地图绘制、交通规划、航空航海和国防建设中的重要作用(1:36-1:48),以及地心坐标系的应用(2:37-2:56)和具体的经纬度数据(3:02-3:10),这些都旨在传递和普及相关知识。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "促进粉丝互动与转化",
-                "描述": "创作者希望通过明确的号召,鼓励观众点赞、评论、转发视频,并关注账号以增加粉丝数量和互动率。",
-                "推理": "在视频的0:15-0:21处,创作者直接呼吁观众“点赞转发”,并配有“点赞 加关注”的动画图标和转发按钮提示,旨在提升视频的传播度和互动性。在视频结尾(3:42-3:48),再次明确邀请观众“欢迎点赞评论关注哦!视频下方告诉白娘子,关注白娘子,带你深度游终南”,这表明了创作者希望与现有粉丝建立更深联系并吸引新粉丝关注,从而实现粉丝增长和提高用户粘性的目的。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [],
-            "total_count": 0,
-            "root_count": 0
-          }
-        },
-        "选题理解": {
-          "主题": "揭秘中国大地原点的历史与国家意义",
-          "描述": "视频通过创作者实地探访中国大地原点,以其作为地理坐标基准点的灵感,向观众揭示了其鲜为人知的地理位置、从沿用前苏联坐标系统到自主建立的艰辛历程,以及对国家领土完整和现代导航的深远意义,旨在普及重要国家知识并激发民族自豪感,同时通过明确的互动号召提升粉丝参与和转化。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "中国大地原点位置与意义",
-                "是否覆盖": true,
-                "覆盖说明": "视频开篇即由创作者指出其身处“中国最中心的地方”,并展示“中华人民共和国大地原点”的标志碑,随后详细解释了大地原点作为国家地理坐标基准点的重要性和其鲜为人知的现状,直接回应了这一灵感点。"
-              },
-              {
-                "灵感点": "中国大地坐标系统自主建立",
-                "是否覆盖": true,
-                "覆盖说明": "视频用很大篇幅讲述了新中国成立初期沿用前苏联大地原点带来的误差问题,以及中国在1975年决定自主建立坐标系统,并经过三年寻觅于1978年找到独立坐标原点的历史过程,充分展现了国家自主发展的决心。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "分享中国大地原点知识",
-                "是否覆盖": true,
-                "覆盖说明": "视频全程都在详细介绍中国大地原点的定义、历史背景、组成结构、以及它在国家测绘、国防建设和日常导航中的实际应用,有效普及了相关知识。"
-              },
-              {
-                "目的点": "促进粉丝互动与转化",
-                "是否覆盖": true,
-                "覆盖说明": "视频中多次出现明确的互动引导,如鼓励观众“点赞转发”(0:17)、“打出中华崛起”(1:14)、“点赞评论关注转发”(3:43),以及引导关注账号以获得更多内容,旨在提高视频的传播和粉丝的增长。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "新奇地点揭秘",
-                "是否覆盖": true,
-                "覆盖说明": "视频以“中国最中心的地方”这一引人入胜的定位开场,揭示了位于一个小村庄内的“大地原点”,通过稀有性和地理中心的独特性吸引观众。"
-              },
-              {
-                "关键点": "历史知识普及",
-                "是否覆盖": true,
-                "覆盖说明": "视频深入讲解了中国大地原点从无到有、从依赖到自主的历史,包括沿用前苏联坐标系统造成的困境及我国自主建立坐标体系的历程,为观众提供了丰富且重要的历史知识。"
-              },
-              {
-                "关键点": "国家自豪感/爱国情怀",
-                "是否覆盖": true,
-                "覆盖说明": "视频强调了大地原点作为国家尊严的象征,以及我国在测绘事业上独立自主的成就,特别是呼吁打出“中华崛起”并强调其对领土坚定的底线,激发了观众的民族自豪感和爱国情怀。"
-              },
-              {
-                "关键点": "实用联系",
-                "是否覆盖": true,
-                "覆盖说明": "视频解释了大地原点不仅关乎地图绘制和交通规划,更与航空航海、国防建设以及日常导航(如北斗系统)息息相关,让抽象概念与观众的日常生活产生具体联系。"
-              },
-              {
-                "关键点": "视觉呈现",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过实地拍摄、航拍(如1:09、1:55、2:32)、历史图片、地图动画和太空视角(如2:49)等多维度视觉素材,丰富了信息呈现,增强了视频的吸引力与信息传达效果。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "中国大地原点位置与意义",
-              "中国大地坐标系统自主建立"
-            ],
-            "目的点列表": [
-              "分享中国大地原点知识",
-              "促进粉丝互动与转化"
-            ],
-            "关键点列表": []
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "中国大地原点 揭秘",
-              "组合逻辑": "提取了“中国大地原点”这一核心实体,结合“揭秘”这一强钩子属性,旨在寻找同类科普内容。"
-            },
-            {
-              "搜索词": "中国中心 冷知识",
-              "组合逻辑": "提取了“中国最中心的地方”这一具象描述,结合“冷知识”这一吸引人的内容属性,挖掘不为人知的地理信息。"
-            },
-            {
-              "搜索词": "国家坐标系统 历史",
-              "组合逻辑": "提取了“中国大地坐标系统”这一核心实体,结合“历史”这一主题语境,关注其发展历程。"
-            },
-            {
-              "搜索词": "大地原点 国家意义",
-              "组合逻辑": "提取了“大地原点”这一核心实体,结合其“对国家领土完整和现代导航的深远意义”的功能属性,寻找相关科普和解读。"
-            },
-            {
-              "搜索词": "地理科普 涨知识",
-              "组合逻辑": "提取了“地理”这一内容领域,结合“科普”和“涨知识”这一旨在普及重要国家知识的受众痛点。"
-            },
-            {
-              "搜索词": "中国测绘 艰辛",
-              "组合逻辑": "提取了“测绘”这一关键行为,结合“自主建立坐标系统”的“艰辛历程”这一主题语境,寻找相关奋斗史或幕后故事。"
-            },
-            {
-              "搜索词": "99%不知道 地理",
-              "组合逻辑": "提取了“99%的人都不知道”这一强钩子,结合“地理”这一内容领域,寻找引发好奇心的地理冷门知识。"
-            },
-            {
-              "搜索词": "领土完整 坐标基准",
-              "组合逻辑": "提取了“国家领土完整”这一核心意义,结合“坐标基准点”这一具体元素,寻找其关联性的内容。"
-            },
-            {
-              "搜索词": "苏联坐标 中国",
-              "组合逻辑": "提取了“前苏联坐标系统”这一历史实体,结合“中国”自主建立的语境,寻找关于中国坐标系统变迁的历史内容。"
-            },
-            {
-              "搜索词": "民族自豪感 地理",
-              "组合逻辑": "提取了“民族自豪感”这一情感目的,结合“地理”这一内容领域,寻找激发爱国情怀的地理主题视频。"
-            },
-            {
-              "搜索词": "中国地理 秘密",
-              "组合逻辑": "提取了“中国地理”这一大类,结合“秘密”这一揭示未知内容的语境,吸引对隐藏信息感兴趣的受众。"
-            },
-            {
-              "搜索词": "大地原点 实地探访",
-              "组合逻辑": "提取了“大地原点”这一核心实体,结合“实地探访”这一视频内容形式,寻找Vlog或探店类竞品。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    },
-    {
-      "video_data": {
-        "channel_content_id": "43766016",
-        "video": "https://rescdn.yishihui.com/jq_oss/video/2024112806543168430.mp4",
-        "title": "🔴习惯不拔充电器的朋友们,安全意识要加强哦!"
-      },
-      "success": true,
-      "error": null,
-      "result": {
-        "视频信息": {
-          "视频URL": "https://rescdn.yishihui.com/jq_oss/video/2024112806543168430.mp4",
-          "标题": "🔴习惯不拔充电器的朋友们,安全意识要加强哦!",
-          "正文": ""
-        },
-        "三点解构": {
-          "灵感点": [
-            {
-              "候选编号": 1,
-              "分类": "事件目击",
-              "灵感点": "亲历或看到重大火灾事故",
-              "描述": "视频开头展示的建筑物发生严重火灾的真实画面,浓烟滚滚、火光冲天,伴随着急促的警报声和人们的尖叫。",
-              "推理": "创作者可能亲眼目睹了类似火灾现场,或者从新闻、网络等渠道看到了这些触目惊心的画面,被火灾的破坏力与造成的损失所震撼,从而产生了强烈的警示和宣传消防安全的创作冲动。",
-              "推导说明": "从亲历或目击重大火灾事故这一灵感点出发,创作者会产生强烈的警示和分享冲动。视频以触目惊心的火灾画面开篇,直接展示了最严重的后果,随后通过讲解原因(充电器未拔)和展示具体案例(烧毁房子、充电引发的火灾场景),以及最终的呼吁(提高警惕,分享视频),都与因亲身经历或目睹灾难而产生的警示心理高度吻合,从而推导出整个视频的内容和传递的紧迫感。",
-              "scoring": {
-                "人设契合度": 8,
-                "触发可能性": 9,
-                "内容解释力": 10,
-                "总分": 27,
-                "评分说明": "这个灵感点与视频的开场画面和核心警告主题高度契合。视频以一场触目惊心的火灾事故开始,强调了其严重性,这非常可能是一个强烈的创作触发源。创作者可能亲历或从新闻、社交媒体上看到类似事件,产生了强烈的警示和分享冲动。该灵感点完美解释了视频为何要强调火灾的危害,并以此作为切入点引出不良充电习惯的讨论。"
-              }
-            },
-            {
-              "候选编号": 2,
-              "分类": "行为观察",
-              "灵感点": "发现普遍存在的充电不良习惯",
-              "描述": "观察到很多人在手机充完电后,只是拿走手机,却忘记拔掉充电器插头;或者喜欢躺在床上边充电边玩手机,充完电也不拔掉充电器。",
-              "推理": "创作者在日常生活中频繁看到或了解到身边人普遍存在的这些充电习惯,意识到这些看似无害的行为实际上蕴藏着巨大的安全隐患,这种对常见危险行为的发现促使他想要制作视频来提醒大众。",
-              "推导说明": "当创作者观察到身边普遍存在充电不良习惯(如手机充完电不拔充电器、边充边玩等),并意识到这些行为可能带来的潜在风险时,会萌生创作视频以纠正这些习惯的念头。视频中明确指出了“很多人习惯不拔充电器”这一现象,并通过展示这些习惯如何导致火灾的案例(烧毁的家、着火的床头),以及对充电器工作状态的科学解释,最终呼吁大家改变习惯,都能够从发现普遍不良习惯这一灵感点顺畅推导出来,旨在教育和改变观众的行为。",
-              "scoring": {
-                "人设契合度": 7,
-                "触发可能性": 8,
-                "内容解释力": 9,
-                "总分": 24,
-                "评分说明": "这个灵感点反映了创作者对日常生活细节的观察和对潜在危险的敏感。视频中花费大量篇幅展示和解释了各种不拔充电器的场景和危害,表明创作者对这些普遍存在的习惯有深入的认识和担忧。这种观察是视频具体安全建议的直接来源。虽然它可能不是唯一的触发源(结合火灾事故的严重性可能更强),但它非常有力地解释了视频的核心内容和具体警示点。"
-              }
-            }
-          ],
-          "目的点": {
-            "perspective": "创作者视角",
-            "purposes": [
-              {
-                "维度": {
-                  "一级分类": "个人",
-                  "二级分类": "分享"
-                },
-                "目的点": "提高受众安全意识",
-                "描述": "创作者希望通过展示充电器不拔插头可能引发的火灾案例和危害,提升受众对日常用电安全的警惕性,认识到充电器长时间不拔的潜在风险。",
-                "推理": "视频开头以火灾现场的画面和“敲响警钟!”的文字营造紧张气氛,随后通过具体案例(手机充完电忘记拔充电器导致房屋被毁、床上充电导致火灾)以及充电器持续工作导致温度升高引发火灾的原理说明危险。最后,视频呼吁“让大家都提高警惕,增强安全意识”,明确表达了提高安全意识的目的。"
-              },
-              {
-                "维度": {
-                  "一级分类": "创作者",
-                  "二级分类": "面向粉丝"
-                },
-                "目的点": "促使受众分享安全信息",
-                "描述": "创作者希望观众在了解充电安全隐患后,能够主动将此视频分享给身边的亲朋好友,共同传播安全知识,扩大信息覆盖面,以保护更多人的生命财产安全。",
-                "推理": "视频在结尾部分多次出现明确的呼吁:“赶快把这条视频转发给身边的亲朋好友吧”、“请把这个视频分享给你的朋友和家人吧”、“分享出去,让更多人受益”,这些都直接表明了创作者希望受众转发、分享视频的意图,旨在通过观众的行动来扩大安全信息的传播。"
-              }
-            ],
-            "total_count": 2
-          },
-          "关键点": {
-            "key_points": [
-              {
-                "候选编号": 10,
-                "维度大类": "形式",
-                "维度细分": "风格",
-                "关键点": "关键信息文字总结",
-                "描述": "视频在讲解员发言结束后,以黄色背景搭配简洁明了的文字,总结了“安全无小事”、“提高警惕”、“互相帮助”等核心安全理念,强化记忆和行动指导。",
-                "children": []
-              },
-              {
-                "候选编号": 2,
-                "维度大类": "实质",
-                "维度细分": "分类",
-                "关键点": "充电器火灾警示",
-                "描述": "视频在火灾画面上叠加了醒目文字“敲响警钟!充电器不拔插头导致严重火灾”,直接点明了视频的核心警告信息和火灾起因。",
-                "children": [
-                  {
-                    "候选编号": 5,
-                    "维度大类": "形式",
-                    "维度细分": "风格",
-                    "关键点": "充电器起火动画演示",
-                    "描述": "视频通过动画模拟了手机充电器插在插座上,因未拔除导致插头过热、冒烟并最终起火的过程,直观展示了危险的发生机制。",
-                    "children": [],
-                    "作为子节点的原因": "关键点5“充电器起火动画演示”是父关键点2的具体化,通过动画形式直观演示了充电器导致火灾的发生过程,是警示内容(火灾如何发生)的视觉呈现和细化。"
-                  },
-                  {
-                    "候选编号": 8,
-                    "维度大类": "实质",
-                    "维度细分": "分类",
-                    "关键点": "充电器过热原理说明",
-                    "描述": "讲解员解释了充电器即使不连接手机,只要插在插座上就会有微弱电流通过,长期处于工作状态会导致温度升高,从而引发火灾的科学原理。",
-                    "children": [],
-                    "作为子节点的原因": "关键点8“充电器过热原理说明”是父关键点2的具体化,从科学原理层面解释了充电器引发火灾的机制,是警示内容(火灾为何发生)的理论支撑和细化。"
-                  },
-                  {
-                    "候选编号": 4,
-                    "维度大类": "实质",
-                    "维度细分": "分类",
-                    "关键点": "疏忽导致财产损失",
-                    "描述": "讲解员讲述了一个因“一时疏忽”未拔充电器,导致屋主辛苦购置的房子被“烧毁”的案例,强调了小失误造成的巨大财产损失。",
-                    "children": [],
-                    "作为子节点的原因": "关键点4“疏忽导致财产损失”是父关键点2的具体化,通过描述因疏忽导致火灾造成的抽象财产损失,是警示内容(火灾的危害)的后果呈现和细化。"
-                  },
-                  {
-                    "候选编号": 6,
-                    "维度大类": "实质",
-                    "维度细分": "元素",
-                    "关键点": "火灾受损房屋内景",
-                    "描述": "视频展示了火灾过后房屋内部的惨状,家具、墙壁被烧得焦黑,物品散乱,直观呈现了火灾的破坏性后果和财产损失。",
-                    "children": [],
-                    "作为子节点的原因": "关键点6“火灾受损房屋内景”是父关键点2的具体化,通过展示火灾后房屋的惨状,是警示内容(火灾的危害)的具体视觉证据和细化。"
-                  }
-                ]
-              },
-              {
-                "候选编号": 3,
-                "维度大类": "实质",
-                "维度细分": "元素",
-                "关键点": "讲解员出镜",
-                "描述": "一位女性讲解员在室内场景中出镜,以亲切专业的形象对火灾事件进行讲解和安全提醒,增加了视频的权威性和吸引力。",
-                "children": []
-              }
-            ],
-            "total_count": 7,
-            "root_count": 3
-          }
-        },
-        "选题理解": {
-          "主题": "充电器不拔插头引发火灾的警示教育",
-          "描述": "该视频受真实火灾事故和普遍存在的充电不良习惯启发,通过展示触目惊心的火灾现场、充电器起火动画演示,并由讲解员出镜详细阐述充电器长时间不拔因微弱电流通过而引发火灾的原理及造成的财产损失,旨在直观提升受众对日常用电安全的警惕性,并促使他们将此安全信息分享给亲朋好友,共同提高安全意识。",
-          "覆盖情况": {
-            "灵感点覆盖情况": [
-              {
-                "灵感点": "亲历或看到重大火灾事故",
-                "是否覆盖": true,
-                "覆盖说明": "视频开头展示了真实的建筑物火灾画面,并在讲解中提及了烧毁房子的案例,直接体现了重大火灾事故的视觉冲击和危害。"
-              },
-              {
-                "灵感点": "发现普遍存在的充电不良习惯",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过讲解员描述和动画演示,展现了手机充电后不拔充电器、以及边充电边玩手机甚至睡觉时充电器不拔的常见不良习惯。"
-              }
-            ],
-            "目的点覆盖情况": [
-              {
-                "目的点": "提高受众安全意识",
-                "是否覆盖": true,
-                "覆盖说明": "视频通过警示性画面、动画演示和详细讲解充电器不拔的危险性,强调安全无小事,目的在于提高受众对日常用电安全的警惕性。"
-              },
-              {
-                "目的点": "促使受众分享安全信息",
-                "是否覆盖": true,
-                "覆盖说明": "视频在结尾明确呼吁观众“赶快把这条视频转发给身边的亲朋好友吧”,以实现安全信息的广泛传播。"
-              }
-            ],
-            "关键点覆盖情况": [
-              {
-                "关键点": "关键信息文字总结",
-                "是否覆盖": true,
-                "覆盖说明": "视频在结尾处通过黄色背景文字清晰总结了“安全无小事”、“提高警惕”、“互相帮助”等核心安全提示,强化了信息传达效果。"
-              },
-              {
-                "关键点": "充电器火灾警示",
-                "是否覆盖": true,
-                "覆盖说明": "视频的核心内容即是对充电器引发火灾的警示,通过开头的文字提示、真实的火灾画面和随后的动画演示,直接点明了这一主题。"
-              },
-              {
-                "关键点": "充电器起火动画演示",
-                "是否覆盖": true,
-                "覆盖说明": "视频中展示了充电器插头过热冒烟并最终起火的模拟动画,直观地展现了火灾发生的动态过程。"
-              },
-              {
-                "关键点": "充电器过热原理说明",
-                "是否覆盖": true,
-                "覆盖说明": "讲解员明确解释了充电器即使不连接手机也会有微弱电流通过,长时间工作导致温度升高从而引发火灾的科学原理。"
-              },
-              {
-                "关键点": "疏忽导致财产损失",
-                "是否覆盖": true,
-                "覆盖说明": "视频讲述了因“一时疏忽”未拔充电器,导致屋主辛苦购置的房子被烧毁的案例,强调了疏忽的严重后果。"
-              },
-              {
-                "关键点": "火灾受损房屋内景",
-                "是否覆盖": true,
-                "覆盖说明": "视频展示了火灾后房屋内部被烧毁、家具焦黑的惨状,以视觉冲击力展现了火灾造成的巨大财产损失。"
-              },
-              {
-                "关键点": "讲解员出镜",
-                "是否覆盖": true,
-                "覆盖说明": "视频中有一位女性讲解员在室内场景出镜,对火灾事件进行讲解和安全提醒,增加了信息的专业性和可信度。"
-              }
-            ]
-          },
-          "explicit_elements": {
-            "灵感点列表": [
-              "亲历或看到重大火灾事故",
-              "发现普遍存在的充电不良习惯"
-            ],
-            "目的点列表": [
-              "提高受众安全意识",
-              "促使受众分享安全信息"
-            ],
-            "关键点列表": [
-              "关键信息文字总结",
-              "充电器火灾警示",
-              "充电器起火动画演示",
-              "充电器过热原理说明",
-              "疏忽导致财产损失",
-              "火灾受损房屋内景",
-              "讲解员出镜"
-            ]
-          }
-        },
-        "搜索关键词": {
-          "搜索词列表": [
-            {
-              "搜索词": "充电器 火灾警示",
-              "组合逻辑": "提取了[充电器]核心实体,结合[火灾警示]主题语境,作为强钩子警醒。"
-            },
-            {
-              "搜索词": "插头不拔 后果",
-              "组合逻辑": "提取了[插头不拔]具象行为,结合[后果]主题语境,直指受众不良习惯的潜在危害。"
-            },
-            {
-              "搜索词": "房屋烧毁 真实案例",
-              "组合逻辑": "提取了[房屋烧毁]触目视觉,结合[真实案例]主题语境,增强内容真实性和冲击力。"
-            },
-            {
-              "搜索词": "充电器起火 原理科普",
-              "组合逻辑": "提取了[充电器起火]核心事件,结合[原理科普]内容属性,满足受众对知识的探究欲。"
-            },
-            {
-              "搜索词": "用电安全 避坑指南",
-              "组合逻辑": "提取了[用电安全]主题,结合[避坑指南]功能属性,解决受众痛点,提供实用方案。"
-            },
-            {
-              "搜索词": "手机充电 坏习惯",
-              "组合逻辑": "提取了[手机充电]常见场景,结合[坏习惯]受众痛点,精准定位目标人群。"
-            },
-            {
-              "搜索词": "消防安全 居家必备",
-              "组合逻辑": "提取了[消防安全]核心概念,结合[居家必备]受众需求,强调其重要性。"
-            },
-            {
-              "搜索词": "充电器自燃 预防",
-              "组合逻辑": "提取了[充电器自燃]具象风险,结合[预防]解决方案,提供行动指导。"
-            },
-            {
-              "搜索词": "微弱电流 危害",
-              "组合逻辑": "提取了[微弱电流]科学实体,结合[危害]警示语境,揭示隐性风险。"
-            },
-            {
-              "搜索词": "火灾动画 警示",
-              "组合逻辑": "提取了[火灾动画]视觉形式,结合[警示]目的,强调内容的直观性和教育意义。"
-            },
-            {
-              "搜索词": "安全用电 知识分享",
-              "组合逻辑": "提取了[安全用电]主题,结合[知识分享]目的点,鼓励用户传播信息。"
-            },
-            {
-              "搜索词": "充电器不拔 警钟",
-              "组合逻辑": "提取了[充电器不拔]具体行为,结合[警钟]警示语境,直接点明视频核心警告。"
-            }
-          ],
-          "总数": 12
-        }
-      }
-    }
-  ]
-}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 450 - 308
examples/output_demo_script.json


+ 0 - 684
examples/run_script_single.py

@@ -1,684 +0,0 @@
-"""
-脚本理解测试脚本
-
-功能:
-1. 从指定目录读取最新的 result_XXX.json 文件
-2. 提取选题描述和帖子内容
-3. 运行 ScriptUnderstandingAgent(step1 + step2)
-4. 保存结果到 script_result_xxx_xxx.json
-"""
-
-import json
-import sys
-import os
-import argparse
-import time
-from pathlib import Path
-from datetime import datetime
-
-# 添加项目根目录到路径
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-# 手动加载.env文件
-def load_env_file(env_path):
-    """手动加载.env文件"""
-    if not env_path.exists():
-        return False
-
-    with open(env_path, 'r') as f:
-        for line in f:
-            line = line.strip()
-            # 跳过注释和空行
-            if not line or line.startswith('#'):
-                continue
-            # 解析KEY=VALUE
-            if '=' in line:
-                key, value = line.split('=', 1)
-                os.environ[key.strip()] = value.strip()
-
-    return True
-
-env_path = project_root / ".env"
-if load_env_file(env_path):
-    print(f"✅ 已加载环境变量从: {env_path}")
-    # 验证API密钥
-    api_key = os.environ.get("GEMINI_API_KEY", "")
-    if api_key:
-        print(f"   GEMINI_API_KEY: {api_key[:10]}...")
-else:
-    print(f"⚠️  未找到.env文件: {env_path}")
-
-from src.components.agents.script_section_division_agent import ScriptSectionDivisionAgent
-from src.components.agents.script_element_extraction_agent import ScriptElementExtractionAgent
-from src.utils.logger import get_logger
-from src.utils.llm_invoker import LLMInvoker
-import requests
-import tempfile
-import os
-from urllib3.exceptions import IncompleteRead
-
-logger = get_logger(__name__)
-
-
-def find_latest_result_file(directory):
-    """
-    查找指定目录中最新的 result_XXX.json 文件
-
-    Args:
-        directory: 帖子目录名(如"阿里多多酱"或"G88818")
-
-    Returns:
-        Path: 最新result文件的路径,如果找不到则返回None
-    """
-    output_dir = Path(__file__).parent / directory / "output"
-
-    if not output_dir.exists():
-        print(f"⚠️  输出目录不存在: {output_dir}")
-        return None
-
-    # 查找所有result_*.json文件
-    result_files = list(output_dir.glob("result_*.json"))
-
-    if not result_files:
-        print(f"⚠️  未找到result_*.json文件")
-        return None
-
-    # 按修改时间排序,取最新的
-    latest_file = max(result_files, key=lambda p: p.stat().st_mtime)
-
-    return latest_file
-
-
-def find_post_file(directory):
-    """
-    查找指定目录中的视频详情.json文件
-
-    Args:
-        directory: 视频目录名(如"56898272")
-
-    Returns:
-        Path: 视频详情文件的路径,如果找不到则返回None
-    """
-    post_file = Path(__file__).parent / directory / "视频详情.json"
-
-    if not post_file.exists():
-        print(f"⚠️  视频详情文件不存在: {post_file}")
-        return None
-
-    return post_file
-
-
-def load_result_file(file_path):
-    """
-    加载result文件
-
-    Args:
-        file_path: result文件路径
-
-    Returns:
-        dict: 解析后的JSON数据
-    """
-    with open(file_path, 'r', encoding='utf-8') as f:
-        data = json.load(f)
-
-    return data
-
-
-def extract_topic_description(result_data):
-    """
-    从result数据中提取选题描述
-
-    Args:
-        result_data: result.json的数据
-
-    Returns:
-        dict: 选题描述字典
-    """
-    topic_understanding = result_data.get("选题理解", {})
-
-    # 返回结构化的选题描述
-    return {
-        "主题": topic_understanding.get("主题", ""),
-        "描述": topic_understanding.get("描述", "")
-    }
-
-
-def infer_content_category(result_data, post_data):
-    """
-    从result数据和帖子数据中推断内容品类
-
-    Args:
-        result_data: result.json的数据
-        post_data: 待解构帖子.json的数据
-
-    Returns:
-        str: 内容品类
-    """
-    # 尝试从选题理解中推断
-    topic_understanding = result_data.get("选题理解", {})
-    theme = topic_understanding.get("主题", "")
-    description = topic_understanding.get("描述", "")
-
-    # 基于关键词推断品类
-    content = f"{theme} {description} {post_data.get('title', '')} {post_data.get('body_text', '')}"
-    content_lower = content.lower()
-
-    # 常见品类关键词映射
-    category_keywords = {
-        "美妆教程": ["化妆", "眼妆", "底妆", "口红", "粉底"],
-        "美甲分享": ["美甲", "指甲", "甲油", "美甲设计"],
-        "美食教程": ["食谱", "做菜", "烹饪", "美食", "制作"],
-        "穿搭分享": ["穿搭", "搭配", "outfit", "服装", "衣服"],
-        "旅行vlog": ["旅行", "旅游", "打卡", "游玩", "景点"],
-        "健身教程": ["健身", "运动", "锻炼", "瑜伽", "训练"],
-        "手工DIY": ["手工", "diy", "制作", "手作"],
-        "护肤分享": ["护肤", "面膜", "精华", "皮肤"],
-        "摄影分享": ["摄影", "拍照", "相机", "照片"],
-    }
-
-    # 匹配品类
-    for category, keywords in category_keywords.items():
-        for keyword in keywords:
-            if keyword in content_lower or keyword in content:
-                return category
-
-    # 如果没有匹配到,使用通用描述
-    return "创意分享"
-
-
-def extract_post_content(post_data):
-    """
-    从视频详情数据中提取视频内容,并移除所有话题标签
-
-    Args:
-        post_data: 视频详情.json的数据
-
-    Returns:
-        tuple: (text_data, video_url)
-    """
-    import re
-
-    # 提取原始数据
-    title = post_data.get("title", "")
-    body = post_data.get("body_text", "")
-
-    # 移除body中的所有话题标签(格式:#xxx[话题]# 或 #xxx#)
-    # 匹配模式:# 开头,后面是任意字符,可能包含[话题],以 # 结尾
-    body_cleaned = re.sub(r'#[^#]+?(?:\[话题\])?\s*#', '', body)
-    # 清理多余的空白字符
-    body_cleaned = re.sub(r'\s+', ' ', body_cleaned).strip()
-
-    text_data = {
-        "title": title,
-        "body": body_cleaned
-    }
-
-    video_url = post_data.get("video", "")
-
-    return text_data, video_url
-
-
-def download_and_upload_video(video_url: str, directory: str):
-    """
-    下载视频并上传到Gemini
-    
-    Args:
-        video_url: 视频URL
-        directory: 目录名(用于查找本地文件)
-        
-    Returns:
-        Gemini文件对象,失败返回 None
-    """
-    if not video_url:
-        print("⚠️  未提供视频URL,跳过上传")
-        return None
-    
-    try:
-        # 1. 首先检查examples目录下是否有对应的mp4文件
-        examples_dir = Path(__file__).parent
-        local_video_path = examples_dir / directory / f"{directory}.mp4"
-        
-        if local_video_path.exists() and local_video_path.is_file():
-            print(f"✅ 在examples目录下找到现有文件: {local_video_path.name}")
-            video_file_path = str(local_video_path)
-            is_temp_file = False
-        else:
-            # 2. 如果没有找到,则下载到临时文件
-            print(f"📥 开始下载视频: {video_url}")
-            
-            # 创建临时文件
-            temp_file = tempfile.NamedTemporaryFile(
-                suffix=".mp4",
-                delete=False
-            )
-            temp_file_path = temp_file.name
-            temp_file.close()
-            
-            # 下载视频(带重试机制)
-            max_retries = 3
-            retry_count = 0
-            last_exception = None
-            video_file_path = None
-            is_temp_file = True
-            
-            while retry_count < max_retries:
-                try:
-                    if retry_count > 0:
-                        print(f"🔄 重试下载视频 (第 {retry_count}/{max_retries-1} 次)...")
-                    
-                    # 使用 Session 进行下载
-                    session = requests.Session()
-                    session.headers.update({
-                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
-                    })
-                    
-                    # 下载视频(增加超时时间)
-                    response = session.get(
-                        video_url,
-                        timeout=(30, 120),  # (连接超时, 读取超时)
-                        stream=True
-                    )
-                    response.raise_for_status()
-                    
-                    # 确保目录存在
-                    os.makedirs(os.path.dirname(temp_file_path), exist_ok=True)
-                    
-                    # 写入文件
-                    with open(temp_file_path, "wb") as f:
-                        for chunk in response.iter_content(chunk_size=8192):
-                            if chunk:
-                                f.write(chunk)
-                    
-                    # 验证文件大小
-                    file_size = os.path.getsize(temp_file_path)
-                    if file_size == 0:
-                        raise ValueError("下载的文件大小为0")
-                    
-                    print(f"✅ 视频下载完成,大小: {file_size / 1024 / 1024:.2f} MB")
-                    video_file_path = temp_file_path
-                    break  # 下载成功,退出重试循环
-                    
-                except (requests.exceptions.ChunkedEncodingError,
-                        requests.exceptions.ConnectionError,
-                        requests.exceptions.Timeout,
-                        requests.exceptions.RequestException,
-                        ConnectionError,
-                        IncompleteRead) as e:
-                    last_exception = e
-                    retry_count += 1
-                    
-                    # 清理不完整的文件
-                    if os.path.exists(temp_file_path):
-                        try:
-                            os.remove(temp_file_path)
-                        except:
-                            pass
-                    
-                    if retry_count < max_retries:
-                        wait_time = retry_count * 2  # 递增等待时间:2秒、4秒
-                        print(f"⚠️  下载失败 (尝试 {retry_count}/{max_retries}): {e}")
-                        print(f"   等待 {wait_time} 秒后重试...")
-                        time.sleep(wait_time)
-                    else:
-                        print(f"❌ 下载失败,已重试 {max_retries} 次")
-                        raise
-                except Exception as e:
-                    # 其他类型的异常直接抛出,不重试
-                    if os.path.exists(temp_file_path):
-                        try:
-                            os.remove(temp_file_path)
-                        except:
-                            pass
-                    raise
-            
-            # 如果所有重试都失败了
-            if not video_file_path:
-                if last_exception:
-                    raise last_exception
-                else:
-                    raise Exception("视频下载失败")
-        
-        # 3. 上传视频到Gemini
-        print(f"📤 上传视频到Gemini...")
-        video_file = LLMInvoker.upload_video_to_gemini(video_file_path)
-        
-        # 4. 清理临时文件
-        if is_temp_file:
-            try:
-                os.remove(video_file_path)
-                print(f"✅ 临时文件已删除")
-            except Exception as e:
-                print(f"⚠️  删除临时文件失败: {e}")
-        
-        if not video_file:
-            print(f"❌ 视频上传到Gemini失败")
-            return None
-        
-        # 5. 获取文件信息(用于日志)
-        file_name = None
-        if hasattr(video_file, 'name'):
-            file_name = video_file.name
-        elif hasattr(video_file, 'uri'):
-            # 从URI中提取文件名
-            file_uri = video_file.uri
-            if "/files/" in file_uri:
-                file_name = file_uri.split("/files/")[-1]
-        
-        print(f"✅ 视频上传成功")
-        if file_name:
-            print(f"   文件名称: {file_name}")
-        
-        # 直接返回文件对象
-        return video_file
-        
-    except Exception as e:
-        print(f"❌ 视频下载/上传失败: {e}")
-        import traceback
-        traceback.print_exc()
-        return None
-
-
-def main():
-    """主函数"""
-    # 解析命令行参数
-    parser = argparse.ArgumentParser(description='运行脚本理解Agent(视频分析版本)')
-    parser.add_argument('directory', type=str, help='视频目录名(如"56898272"),目录下需要有"视频详情.json"文件')
-    args = parser.parse_args()
-
-    directory = args.directory
-
-    print("=" * 80)
-    print(f"开始运行脚本理解Agent - 目录: {directory}")
-    print("=" * 80)
-
-    # 1. 查找视频详情文件
-    print("\n[1] 查找视频详情文件...")
-    try:
-        post_file = find_post_file(directory)
-        if not post_file:
-            print(f"❌ 未找到视频详情文件")
-            return
-
-        print(f"✅ 找到视频详情文件: {post_file.name}")
-        print(f"   文件路径: {post_file}")
-    except Exception as e:
-        print(f"❌ 查找视频详情文件失败: {e}")
-        return
-
-    # 2. 加载视频详情文件
-    print("\n[2] 加载视频详情文件...")
-    try:
-        post_data = load_result_file(post_file)
-        print(f"✅ 成功加载视频详情文件")
-    except Exception as e:
-        print(f"❌ 加载视频详情文件失败: {e}")
-        return
-
-    # 3. 提取视频内容
-    print("\n[3] 提取视频内容...")
-    try:
-        text_data, video_url = extract_post_content(post_data)
-        print(f"✅ 成功提取视频内容")
-        print(f"   标题: {text_data.get('title', '无')}")
-        print(f"   正文长度: {len(text_data.get('body', ''))}")
-        print(f"   视频URL: {'有' if video_url else '无'}")
-    except Exception as e:
-        print(f"❌ 提取视频内容失败: {e}")
-        return
-
-    # 4. 查找最新的result文件
-    print("\n[4] 查找最新的result文件...")
-    try:
-        result_file = find_latest_result_file(directory)
-        if not result_file:
-            print(f"❌ 未找到result文件")
-            return
-
-        print(f"✅ 找到最新result文件: {result_file.name}")
-        print(f"   文件路径: {result_file}")
-        print(f"   修改时间: {datetime.fromtimestamp(result_file.stat().st_mtime)}")
-    except Exception as e:
-        print(f"❌ 查找result文件失败: {e}")
-        return
-
-    # 5. 加载result文件
-    print("\n[5] 加载result文件...")
-    try:
-        result_data = load_result_file(result_file)
-        print(f"✅ 成功加载result文件")
-    except Exception as e:
-        print(f"❌ 加载result文件失败: {e}")
-        return
-
-    # 6. 提取选题描述
-    print("\n[6] 提取选题描述...")
-    try:
-        topic_description = extract_topic_description(result_data)
-
-        print(f"✅ 成功提取选题描述")
-        print(f"   选题描述:")
-        if topic_description.get("主题"):
-            print(f"     主题: {topic_description['主题']}")
-        if topic_description.get("描述"):
-            print(f"     描述: {topic_description['描述']}")
-    except Exception as e:
-        print(f"❌ 提取选题描述失败: {e}")
-        return
-
-    # 7. 下载并上传视频到Gemini
-    print("\n[7] 下载并上传视频到Gemini...")
-    video_file = None
-    if video_url:
-        try:
-            video_file = download_and_upload_video(video_url, directory)
-            if not video_file:
-                print(f"⚠️  视频上传失败,但继续执行(可能影响视频分析功能)")
-        except Exception as e:
-            print(f"⚠️  视频上传失败: {e},但继续执行(可能影响视频分析功能)")
-            import traceback
-            traceback.print_exc()
-    else:
-        print(f"⚠️  未提供视频URL,跳过上传")
-
-    # 8. 初始化两个Agent
-    print("\n[8] 初始化ScriptSectionDivisionAgent和ScriptElementExtractionAgent...")
-    try:
-        section_agent = ScriptSectionDivisionAgent(
-            model_provider="google_genai"
-        )
-        element_agent = ScriptElementExtractionAgent(
-            model_provider="google_genai"
-        )
-        print(f"✅ Agent初始化成功")
-    except Exception as e:
-        print(f"❌ Agent初始化失败: {e}")
-        import traceback
-        traceback.print_exc()
-        return
-
-    # 9. 组装state对象
-    print("\n[9] 组装state对象...")
-    try:
-        # 构建选题理解格式(模拟workflow中的格式)
-        topic_understanding = result_data.get("选题理解", {})
-
-        state = {
-            "text": text_data,
-            "video": video_url,
-            "topic_selection_understanding": topic_understanding
-        }
-        
-        # 添加视频文件对象(如果上传成功)
-        if video_file:
-            state["video_file"] = video_file
-        
-        print(f"✅ State对象组装成功")
-        print(f"   - 文本: {bool(text_data)}")
-        print(f"   - 视频URL: {'有' if video_url else '无'}")
-        print(f"   - 视频文件对象: {'有' if video_file else '无'}")
-        print(f"   - 选题理解: {bool(topic_understanding)}")
-    except Exception as e:
-        print(f"❌ 组装state对象失败: {e}")
-        return
-
-    # 10. 执行两个Agent
-    print("\n[10] 执行脚本段落划分Agent...")
-    try:
-        section_result = section_agent.process(state)
-        sections = section_result.get("段落列表", [])
-        content_category = section_result.get("内容品类", "未知品类")
-        print(f"✅ 段落划分执行成功")
-        print(f"   内容品类: {content_category}")
-        print(f"   划分出 {len(sections)} 个Section")
-    except Exception as e:
-        print(f"❌ 段落划分执行失败: {e}")
-        import traceback
-        traceback.print_exc()
-        return
-
-    print("\n[11] 执行脚本元素提取Agent...")
-    try:
-        # 更新state,添加段落划分结果和其他必需数据
-        state["section_division"] = {"段落列表": sections}
-
-        # 从result_data中提取灵感点、目的点、关键点(从"三点解构"中提取)
-        three_points = result_data.get("三点解构", {})
-        state["inspiration_points"] = three_points.get("灵感点", {})
-        state["purpose_points"] = three_points.get("目的点", {})
-        state["key_points"] = three_points.get("关键点", {})
-
-        element_result = element_agent.process(state)
-        elements = element_result.get("元素列表", [])
-        tendency_judgment = element_result.get("视频倾向判断", {})
-        print(f"✅ 元素提取执行成功")
-        print(f"   识别出 {len(elements)} 个元素")
-        if tendency_judgment:
-            print(f"   视频倾向: {tendency_judgment.get('判断结果', '未知')}")
-    except Exception as e:
-        print(f"❌ 元素提取执行失败: {e}")
-        import traceback
-        traceback.print_exc()
-        return
-
-    # 12. 组装最终结果
-    print("\n[12] 组装最终结果...")
-    try:
-        # 递归统计数量
-        def count_items(items_list):
-            count = len(items_list)
-            for item in items_list:
-                if item.get('子项'):
-                    count += count_items(item['子项'])
-            return count
-
-        total_sections = count_items(sections)
-        total_elements = count_items(elements)
-
-        # 组装脚本理解结果
-        script_understanding = {
-            "内容品类": content_category,
-            "段落列表": sections,
-            "元素列表": elements,
-            "视频URL": video_url,
-            "视频倾向判断": tendency_judgment  # 添加视频倾向判断
-        }
-
-        final_result = {
-            "选题描述": topic_description,
-            "脚本理解": script_understanding,
-            "元信息": {
-                "段落总数": total_sections,
-                "元素总数": total_elements,
-                "来源帖子文件": post_file.name,
-                "来源结果文件": result_file.name,
-                "执行时间": datetime.now().isoformat()
-            }
-        }
-        print(f"✅ 结果组装成功")
-    except Exception as e:
-        print(f"❌ 结果组装失败: {e}")
-        return
-
-    # 13. 保存结果
-    print("\n[13] 保存结果...")
-    try:
-        # 生成带时间戳的文件名
-        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
-        output_filename = f"script_result_{timestamp}.json"
-        output_path = Path(__file__).parent / directory / "output" / output_filename
-        output_path.parent.mkdir(parents=True, exist_ok=True)
-
-        with open(output_path, "w", encoding="utf-8") as f:
-            json.dump(final_result, f, ensure_ascii=False, indent=2)
-
-        print(f"✅ 结果已保存到: {output_path}")
-        print(f"   文件名: {output_filename}")
-    except Exception as e:
-        print(f"❌ 保存结果失败: {e}")
-        return
-
-    # 13. 显示结果摘要
-    print("\n" + "=" * 80)
-    print("结果摘要")
-    print("=" * 80)
-
-    print(f"\n选题描述:")
-    if topic_description.get("主题"):
-        print(f"   主题: {topic_description['主题']}")
-    if topic_description.get("描述"):
-        print(f"   描述: {topic_description['描述']}")
-
-    # 递归打印Section树状结构
-    def print_sections(sections_list, indent=0):
-        for idx, section in enumerate(sections_list, 1):
-            prefix = "   " + "  " * indent
-            print(f"{prefix}{idx}. {section.get('描述', 'N/A')}")
-            if section.get('子项'):
-                print_sections(section['子项'], indent + 1)
-
-    # 递归统计Section数量(只统计叶子节点)
-    def count_sections(sections_list):
-        count = 0
-        for section in sections_list:
-            if section.get('子项'):
-                # 有子项,递归统计子项
-                count += count_sections(section['子项'])
-            else:
-                # 无子项,是叶子节点
-                count += 1
-        return count
-
-    total_sections = count_sections(sections)
-    print(f"\nSection列表 ({total_sections} 个):")
-    print_sections(sections)
-
-    # 打印Element列表(只打印名称和类型,不打印树状结构)
-    def print_elements(elements_list):
-        for element in elements_list:
-            name = element.get('名称', 'N/A')
-            elem_type = element.get('类型', 'N/A')
-            classification = element.get('分类', {})
-
-            # 构建分类路径
-            if classification:
-                class_path = " > ".join([v for v in classification.values() if v])
-                print(f"   - [{elem_type}] {name} ({class_path})")
-            else:
-                print(f"   - [{elem_type}] {name}")
-
-    # 不再递归统计,直接使用列表长度
-    total_elements = len(elements)
-    print(f"\n元素列表 ({total_elements} 个):")
-    if elements:
-        print_elements(elements)
-    else:
-        print("   (无)")
-
-    print("\n" + "=" * 80)
-    print("测试完成!")
-    print("=" * 80)
-
-
-if __name__ == "__main__":
-    main()

+ 0 - 193
examples/run_single.py

@@ -1,193 +0,0 @@
-"""
-测试脚本:运行视频解构
-
-功能:
-1. 支持视频输入(视频详情.json)
-2. 运行 WhatDeconstructionWorkflow(视频分析版本)
-"""
-
-import json
-import sys
-import os
-import argparse
-from pathlib import Path
-from datetime import datetime
-
-# 添加项目根目录到路径
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-# 手动加载.env文件
-def load_env_file(env_path):
-    """手动加载.env文件"""
-    if not env_path.exists():
-        return False
-
-    with open(env_path, 'r') as f:
-        for line in f:
-            line = line.strip()
-            # 跳过注释和空行
-            if not line or line.startswith('#'):
-                continue
-            # 解析KEY=VALUE
-            if '=' in line:
-                key, value = line.split('=', 1)
-                os.environ[key.strip()] = value.strip()
-
-    return True
-
-env_path = project_root / ".env"
-if load_env_file(env_path):
-    print(f"✅ 已加载环境变量从: {env_path}")
-    # 验证API密钥
-    api_key = os.environ.get("GEMINI_API_KEY", "")
-    if api_key:
-        print(f"   GEMINI_API_KEY: {api_key[:10]}...")
-else:
-    print(f"⚠️  未找到.env文件: {env_path}")
-
-from src.workflows.what_deconstruction_workflow import WhatDeconstructionWorkflow
-from src.utils.logger import get_logger
-
-logger = get_logger(__name__)
-
-def load_test_data(directory):
-    """
-    加载测试数据(视频格式)
-
-    Args:
-        directory: 视频目录名(如"56898272")
-    """
-    # 加载视频详情文件
-    video_data_path = Path(__file__).parent / directory / "视频详情.json"
-    if not video_data_path.exists():
-        raise FileNotFoundError(f"未找到视频详情文件:{video_data_path}\n请确保目录下存在'视频详情.json'文件")
-    
-    with open(video_data_path, "r", encoding="utf-8") as f:
-        data = json.load(f)
-    
-    return data
-
-
-def convert_to_workflow_input(raw_data):
-    """
-    将原始数据转换为工作流输入格式(视频分析版本)
-
-    Args:
-        raw_data: 原始帖子数据(视频格式)
-    """
-    # 视频分析版本:直接使用视频URL和文本信息
-    input_data = {
-        "video": raw_data.get("video", ""),
-        "channel_content_id": raw_data.get("channel_content_id", ""),
-        "title": raw_data.get("title", ""),
-        "body_text": raw_data.get("body_text", ""),
-    }
-
-    return input_data
-
-
-def main():
-    """主函数"""
-    # 解析命令行参数
-    parser = argparse.ArgumentParser(description='运行单个视频的What解构工作流(视频分析版本)')
-    parser.add_argument('directory', type=str, help='视频目录名(如"56898272"),目录下需要有"视频详情.json"文件')
-    args = parser.parse_args()
-
-    directory = args.directory
-
-    print("=" * 80)
-    print(f"开始测试 What 解构工作流(视频分析版本)- 目录: {directory}")
-    print("=" * 80)
-
-    # 1. 加载测试数据(目标视频)
-    print("\n[1] 加载测试数据(目标视频)...")
-    try:
-        raw_data = load_test_data(directory)
-
-        print(f"✅ 成功加载测试数据")
-        print(f"   - 标题: {raw_data.get('title')}")
-        print(f"   - 内容类型: {raw_data.get('content_type', 'unknown')}")
-        video_url = raw_data.get('video', '')
-        if video_url:
-            print(f"   - 视频URL: {video_url[:50]}...")
-        else:
-            print(f"   - 视频URL: 未提供")
-    except Exception as e:
-        print(f"❌ 加载测试数据失败: {e}")
-        return
-
-    # 2. 转换数据格式
-    print("\n[2] 转换数据格式...")
-    try:
-        input_data = convert_to_workflow_input(raw_data)
-        print(f"✅ 数据格式转换成功")
-        print(f"   - 视频URL: {input_data.get('video', '')[:50]}...")
-        print(f"   - 标题: {input_data.get('title', '')}")
-    except Exception as e:
-        print(f"❌ 数据格式转换失败: {e}")
-        return
-
-    # 3. 初始化工作流
-    print("\n[3] 初始化工作流...")
-    try:
-        workflow = WhatDeconstructionWorkflow(
-            model_provider="google_genai",
-            max_depth=10
-        )
-        print(f"✅ 工作流初始化成功")
-    except Exception as e:
-        print(f"❌ 工作流初始化失败: {e}")
-        import traceback
-        traceback.print_exc()
-        return
-
-    # 4. 执行工作流
-    print("\n[4] 执行工作流...")
-    print("   注意:这可能需要几分钟时间...")
-    try:
-        result = workflow.invoke(input_data)
-        print(f"✅ 工作流执行成功")
-    except Exception as e:
-        print(f"❌ 工作流执行失败: {e}")
-        import traceback
-        traceback.print_exc()
-        return
-
-    # 5. 保存结果
-    print("\n[5] 保存结果...")
-    try:
-        # 生成带时间戳的文件名
-        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
-        output_filename = f"result_{timestamp}.json"
-        output_path = Path(__file__).parent / directory / "output" / output_filename
-        output_path.parent.mkdir(parents=True, exist_ok=True)
-
-        with open(output_path, "w", encoding="utf-8") as f:
-            json.dump(result, f, ensure_ascii=False, indent=2)
-
-        print(f"✅ 结果已保存到: {output_path}")
-        print(f"   文件名: {output_filename}")
-    except Exception as e:
-        print(f"❌ 保存结果失败: {e}")
-        return
-
-    # 6. 显示结果摘要
-    print("\n" + "=" * 80)
-    print("结果摘要")
-    print("=" * 80)
-
-    if result:
-        # 1. 视频信息
-        topic_selection_v2 = result.get("topic_selection_v2", {})
-        if topic_selection_v2:
-            print(f"✅ 选题结构分析 V2 完成")
-            print(f"   - 选题结构: {topic_selection_v2}")
-        else:
-            print(f"❌ 选题结构分析 V2 失败")
-            return
-        
-
-
-if __name__ == "__main__":
-    main()

+ 0 - 99
examples/run_test.py

@@ -1,99 +0,0 @@
-"""
-统一测试脚本:根据参数选择运行单个视频或批量处理
-
-功能:
-1. 支持 single 模式:处理单个视频
-2. 支持 batch 模式:批量处理作者历史视频
-3. 自动加载对应目录的数据(视频详情.json)
-"""
-
-import sys
-import subprocess
-from pathlib import Path
-import argparse
-
-
-def main():
-    """主函数"""
-    # 解析命令行参数
-    parser = argparse.ArgumentParser(
-        description='运行What解构工作流测试脚本',
-        formatter_class=argparse.RawDescriptionHelpFormatter,
-        epilog="""
-使用示例:
-  # 单个视频模式
-  python examples/run_test.py single 56898272  # 视频输入(使用视频详情.json)
-
-  # 批量处理模式
-  python examples/run_test.py batch <目录名>
-
-注意:
-  - 视频输入:目录下需要有"视频详情.json"文件
-  - 视频详情.json格式:包含video、title、body_text等字段
-        """
-    )
-
-    parser.add_argument(
-        'mode',
-        type=str,
-        choices=['single', 'batch'],
-        help='运行模式:single(单个帖子)或 batch(批量处理)'
-    )
-
-    parser.add_argument(
-        'directory',
-        type=str,
-        help='视频目录名(如"56898272"),目录下需要有"视频详情.json"文件'
-    )
-
-    args = parser.parse_args()
-
-    # 获取脚本目录
-    script_dir = Path(__file__).parent
-
-    # 根据模式选择对应的脚本
-    if args.mode == 'single':
-        script_path = script_dir / "run_single.py"
-        mode_name = "单个视频处理"
-    else:  # batch
-        script_path = script_dir / "run_batch.py"
-        mode_name = "批量处理"
-
-    # 检查脚本是否存在
-    if not script_path.exists():
-        print(f"❌ 错误:脚本不存在 {script_path}")
-        return 1
-
-    # 检查目录是否存在
-    data_dir = script_dir / args.directory
-    if not data_dir.exists():
-        print(f"❌ 错误:目录不存在 {data_dir}")
-        print(f"   请确保以下目录存在:")
-        print(f"   - {data_dir}")
-        return 1
-
-    # 显示运行信息
-    print("=" * 80)
-    print(f"运行模式: {mode_name}")
-    print(f"目录: {args.directory}")
-    print(f"脚本: {script_path.name}")
-    print("=" * 80)
-    print()
-
-    # 构建命令
-    cmd = [sys.executable, str(script_path), args.directory]
-
-    # 执行命令
-    try:
-        result = subprocess.run(cmd, check=False)
-        return result.returncode
-    except KeyboardInterrupt:
-        print("\n\n⚠️  用户中断执行")
-        return 130
-    except Exception as e:
-        print(f"\n❌ 执行失败: {e}")
-        return 1
-
-
-if __name__ == "__main__":
-    sys.exit(main())

BIN
examples/static/visualize/__pycache__/tab3.cpython-313.pyc


BIN
examples/static/visualize/__pycache__/tab5.cpython-313.pyc


+ 273 - 84
examples/static/visualize/tab3.py

@@ -6,6 +6,33 @@ import html as html_module
 from typing import Dict, Any, List
 
 
+def get_intent_support_data(element: Dict[str, Any]) -> Dict[str, Any]:
+    """
+    获取元素的意图支撑数据(兼容新旧数据结构)
+    
+    优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
+    这两个字段内部存储的都是意图支撑数据
+    
+    Args:
+        element: 元素数据
+        
+    Returns:
+        意图支撑数据字典,格式:{"灵感点": [...], "目的点": [...], "关键点": [...]}
+    """
+    # 优先使用"意图支撑"字段
+    intent_support = element.get('意图支撑')
+    if intent_support and isinstance(intent_support, dict):
+        return intent_support
+    
+    # 如果没有"意图支撑",则使用"多维度评分"字段(兼容旧数据)
+    multi_scores = element.get('多维度评分')
+    if multi_scores and isinstance(multi_scores, dict):
+        return multi_scores
+    
+    # 都没有则返回空字典
+    return {}
+
+
 def calculate_intent_support_count(element: Dict[str, Any]) -> int:
     """
     计算元素的意图支撑数量
@@ -19,48 +46,158 @@ def calculate_intent_support_count(element: Dict[str, Any]) -> int:
     Returns:
         支撑的意图点总数
     """
-    # 区分实质元素和形式元素的统计方式
-    dimension = element.get('维度') or {}
-    if isinstance(dimension, dict) and dimension.get('一级') == '形式':
-        # 形式元素:基于多维度评分,使用最高相似度作为"意图支撑强度"的代表
-        # 兼容两种结构:
-        # 1)新版:{"名称": "...", "相似度结果": [{"点","语义相似度","文本相似度",...}, ...]}
-        # 2)旧版:直接列表 [{"点","语义相似度","文本相似度",...}, ...]
-        multi_scores = element.get('多维度评分') or {}
-        best_score = 0.0
-
-        for point_type in ['灵感点', '目的点', '关键点']:
-            for item in multi_scores.get(point_type, []) or []:
-                if not isinstance(item, dict):
-                    continue
+    # 统一使用意图支撑数据统计(实质和形式都使用相同逻辑)
+    # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
+    intent_support_data = get_intent_support_data(element)
+    
+    total_support_count = 0
+    for point_type in ['灵感点', '目的点', '关键点']:
+        support_points = intent_support_data.get(point_type, []) or []
+        # 每个项目就是一个支撑点,直接统计数量
+        total_support_count += len(support_points)
 
-                similarity_results = item.get('相似度结果')
+    return total_support_count
 
-                # 新结构:在相似度结果列表里取最大值
-                if similarity_results:
-                    for sim in similarity_results or []:
-                        if not isinstance(sim, dict):
-                            continue
-                        semantic = float(sim.get('语义相似度', 0) or 0)
-                        text_sim = float(sim.get('文本相似度', 0) or 0)
-                        best_score = max(best_score, semantic, text_sim)
-                else:
-                    # 旧结构:当前item本身就带语义/文本相似度
-                    semantic = float(item.get('语义相似度', 0) or 0)
-                    text_sim = float(item.get('文本相似度', 0) or 0)
-                    best_score = max(best_score, semantic, text_sim)
 
-        return best_score
+def get_support_stats(element: Dict[str, Any]) -> Dict[str, int]:
+    """
+    获取元素的支撑统计信息(灵感点/目的点/关键点数量)
+
+    优先使用元素中已经预计算好的 support_stats 字段;
+    若不存在,则根据「意图支撑」或「多维度评分」字段动态统计。
+    """
+    # 优先使用预计算的支撑统计
+    support_stats = element.get('支撑统计')
+    if isinstance(support_stats, dict):
+        # 做一次安全拷贝并补齐缺失字段
+        return {
+            '灵感点数量': int(support_stats.get('灵感点数量', 0) or 0),
+            '目的点数量': int(support_stats.get('目的点数量', 0) or 0),
+            '关键点数量': int(support_stats.get('关键点数量', 0) or 0),
+        }
+
+    # 兼容旧字段名
+    support_stats_old = element.get('support_stats')
+    if isinstance(support_stats_old, dict):
+        return {
+            '灵感点数量': int(support_stats_old.get('灵感点数量', 0) or 0),
+            '目的点数量': int(support_stats_old.get('目的点数量', 0) or 0),
+            '关键点数量': int(support_stats_old.get('关键点数量', 0) or 0),
+        }
+
+    # 统一使用意图支撑数据统计(实质和形式都使用相同逻辑)
+    # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
+    intent_support_data = get_intent_support_data(element)
+    
+    return {
+        '灵感点数量': len(intent_support_data.get('灵感点', []) or []),
+        '目的点数量': len(intent_support_data.get('目的点', []) or []),
+        '关键点数量': len(intent_support_data.get('关键点', []) or []),
+    }
+
+
+def compute_weight_scores(element: Dict[str, Any]) -> Dict[str, Any]:
+    """
+    计算元素的权重相关得分。
+
+    对于形式元素:优先使用元素中预计算的权重分和权重明细
+    对于实质元素:根据共性分析和意图支撑动态计算
+
+    原始总分 = 各子项得分之和
+    权重分 = min(100, 原始总分 × 100 / 110)
+    """
+    # 判断是形式元素还是实质元素
+    dimension = element.get('维度') or {}
+    is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'
+
+    # 形式元素:优先使用预计算的权重信息
+    if is_form:
+        weight_score = element.get('权重分')
+        weight_details = element.get('权重明细')
+        support_stats = get_support_stats(element)
+
+        # 如果存在预计算的权重信息,直接使用
+        if weight_score is not None and weight_details is not None:
+            # 从权重明细中提取各项得分
+            freq_score = float(weight_details.get('频次分', 0) or 0)
+            coverage_count_score = float(weight_details.get('覆盖段落数分', 0) or 0)
+            coverage_rate_score = float(weight_details.get('覆盖率分', 0) or 0)
+            inspiration_score = float(weight_details.get('灵感点支撑分', 0) or 0)
+            purpose_score = float(weight_details.get('目的点支撑分', 0) or 0)
+            keypoint_score = float(weight_details.get('关键点支撑分', 0) or 0)
+
+            # 计算原始总分(共性总分 + 支撑总分)
+            commonality_total = weight_details.get('共性总分', 0) or 0
+            support_total = weight_details.get('支撑总分', 0) or 0
+            raw_total = float(commonality_total) + float(support_total)
+
+            return {
+                'weight_score': float(weight_score),
+                'raw_total': raw_total,
+                'details': {
+                    '频次分': freq_score,
+                    '覆盖段落数分': coverage_count_score,
+                    '覆盖率分': coverage_rate_score,
+                    '灵感点支撑分': inspiration_score,
+                    '目的点支撑分': purpose_score,
+                    '关键点支撑分': keypoint_score,
+                },
+                'support_stats': support_stats,
+            }
+
+    # 实质元素或形式元素没有预计算权重:使用旧的计算逻辑
+    commonality = element.get('共性分析') or {}
+    coverage = float(commonality.get('段落覆盖率', 0.0) or 0.0)
+    frequency = int(commonality.get('出现频次', 0) or 0)
+
+    support_stats = get_support_stats(element)
+    inspiration_count = support_stats.get('灵感点数量', 0) or 0
+    purpose_count = support_stats.get('目的点数量', 0) or 0
+    keypoint_count = support_stats.get('关键点数量', 0) or 0
+
+    # 1) 频次分(0–30分)
+    # 假定「高频」的参考上限为 12 次,超过即视为满分
+    # 频次分 = min(30, 出现频次 / 12 * 30)
+    if frequency <= 0:
+        freq_score = 0.0
     else:
-        # 实质元素:按意图支撑的数量统计
-        intent_support = element.get('意图支撑') or {}
+        freq_score = min(30.0, frequency * 30.0 / 12.0)
+
+    # 2) 覆盖率分(0–30分)
+    # 覆盖率分 = 段落覆盖率 × 30
+    coverage_score = max(0.0, min(30.0, coverage * 30.0))
+
+    # 3) 灵感点支撑分(0–25分)
+    # 按你的说明:支撑{灵感点数量}个灵感点 × 25分/个,封顶 25 分
+    inspiration_score = min(25.0, float(inspiration_count) * 25.0)
 
-        total_support_count = 0
-        for point_type in ['灵感点', '目的点', '关键点']:
-            if point_type in intent_support and intent_support[point_type]:
-                total_support_count += len(intent_support[point_type])
+    # 4) 目的点支撑分(0–15分)
+    # 说明:支撑{目的点数量}个目的点 × 5分/个,封顶 15 分
+    purpose_score = min(15.0, float(purpose_count) * 5.0)
 
-        return total_support_count
+    # 5) 关键点支撑分(0–10分)
+    # 说明:支撑{关键点数量}个关键点 × 1分/个,封顶 10 分
+    keypoint_score = min(10.0, float(keypoint_count) * 1.0)
+
+    raw_total = freq_score + coverage_score + inspiration_score + purpose_score + keypoint_score
+
+    if raw_total <= 0:
+        weight_score = 0.0
+    else:
+        weight_score = min(100.0, raw_total * 100.0 / 110.0)
+
+    return {
+        'weight_score': weight_score,
+        'raw_total': raw_total,
+        'details': {
+            '频次分': freq_score,
+            '覆盖率分': coverage_score,
+            '灵感点支撑分': inspiration_score,
+            '目的点支撑分': purpose_score,
+            '关键点支撑分': keypoint_score,
+        },
+        'support_stats': support_stats,
+    }
 
 
 def determine_dominant_factor(element: Dict[str, Any], all_elements: List[Dict[str, Any]]) -> str:
@@ -332,10 +469,13 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
     paragraphs_list = commonality.get('出现段落列表', [])
     source = element.get('来源', [])
     intent_count = calculate_intent_support_count(element)
-    intent_support = element.get('意图支撑', {})
+    intent_support = get_intent_support_data(element)
+
+    # 计算权重得分(如果有 weight_details)
+    weight_info = compute_weight_scores(element)
 
     # 检查是否有详细信息
-    has_details = bool(elem_type or category or category_def or paragraphs_list or source or intent_support)
+    has_details = bool(elem_type or category or category_def or paragraphs_list or source or intent_support or weight_info.get('raw_total', 0) > 0)
 
     # 计算主导因素
     dominant_factor = 'coverage'  # 默认
@@ -363,9 +503,14 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
 
     html += '<div class="element-stats">\n'
 
+    # 显示权重分(如有)
+    if weight_info.get('raw_total', 0) > 0:
+        html += f'<span class="stat-badge stat-weight">权重分: {weight_info["weight_score"]:.1f}</span>\n'
+
     if is_form:
-        # 形式元素:显示最高相似度(基于多维度评分)
-        html += f'<span class="stat-badge stat-intent">最高相似度: {intent_count:.2f}</span>\n'
+        # 形式元素:显示支撑数量和权重分
+        html += f'<span class="stat-badge stat-intent">支撑: {intent_count}</span>\n'
+        # 权重分已在上面显示
     else:
         # 实质元素显示全部三个指标
         coverage_highlight = 'stat-highlight' if dominant_factor == 'coverage' else ''
@@ -420,7 +565,7 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
             html += f'<div class="detail-text">{html_module.escape(category_def)}</div>\n'
             html += '</div>\n'
 
-        # 针对"形式"维度,显示"支撑"、"推理"和"多维度评分"
+        # 针对"形式"维度,显示"支撑"、"推理"和"支撑关系"
         if isinstance(dimension, dict) and dimension.get('一级') == '形式':
             # 支撑
             zhicheng = element.get('支撑')
@@ -463,58 +608,49 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
                 html += f'<div class="detail-text">{html_module.escape(tuili)}</div>\n'
                 html += '</div>\n'
 
-            # 多维度评分(形式元素专用,简化为分类列表展示)
-            multi_scores = element.get('多维度评分') or {}
-            if multi_scores:
+            # 支撑关系(形式元素专用,从意图支撑数据中显示支撑点)
+            # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
+            intent_support_data = get_intent_support_data(element)
+            if intent_support_data:
                 html += '<div class="detail-section">\n'
-                html += '<strong>多维度评分:</strong>\n'
+                html += '<strong>支撑关系:</strong>\n'
 
-                for score_type in ['灵感点', '目的点', '关键点']:
-                    score_items = multi_scores.get(score_type) or []
-                    if not score_items:
+                for point_type in ['灵感点', '目的点', '关键点']:
+                    support_points = intent_support_data.get(point_type) or []
+                    if not support_points:
                         continue
 
-                    html += f'<div class="score-type">{score_type}</div>\n'
-                    html += '<ul class="score-list simple-score-list">\n'
+                    html += f'<div class="score-type">{point_type}</div>\n'
+                    html += '<div class="score-list">\n'
 
-                    for score_item in score_items:
-                        if not isinstance(score_item, dict):
+                    # 每个项目就是一个支撑点
+                    for support_point in support_points:
+                        if not isinstance(support_point, dict):
                             continue
 
-                        # 兼容两种结构:
-                        # 1)新结构:{"名称": "...", "相似度结果": [ {...}, ... ]}
-                        # 2)旧结构:直接列表 [{"点","语义相似度","文本相似度",...}, ...]
-                        similarity_results = score_item.get('相似度结果')
-
-                        if similarity_results:
-                            # 新结构:对每个相似度结果生成一条列表项
-                            for sim in similarity_results or []:
-                                if not isinstance(sim, dict):
-                                    continue
-                                point = sim.get('点', '')
-                                semantic = sim.get('语义相似度', 0)
-                                text_sim = sim.get('文本相似度', 0)
-
-                                html += '<li class="score-item simple-score-item">\n'
-                                if point:
-                                    html += f'<span class="score-point-name">{html_module.escape(point)}</span>\n'
-                                html += f'<span class="score-badge">语义 {semantic:.2f}</span>\n'
-                                html += f'<span class="score-badge">文本 {text_sim:.2f}</span>\n'
-                                html += '</li>\n'
-                        else:
-                            # 旧结构:当前条目本身就是一个点的评分
-                            point = score_item.get('点', '')
-                            semantic = score_item.get('语义相似度', 0)
-                            text_sim = score_item.get('文本相似度', 0)
+                        point = support_point.get('点', '')
+                        point_intention = support_point.get('点的意图', '')
+                        support_reason = support_point.get('支撑理由', '')
 
-                            html += '<li class="score-item simple-score-item">\n'
-                            if point:
-                                html += f'<span class="score-point-name">{html_module.escape(point)}</span>\n'
-                            html += f'<span class="score-badge">语义 {semantic:.2f}</span>\n'
-                            html += f'<span class="score-badge">文本 {text_sim:.2f}</span>\n'
-                            html += '</li>\n'
+                        html += '<div class="score-item">\n'
+                        html += f'<div class="score-point">{html_module.escape(point)}</div>\n'
+
+                        # 显示点的意图
+                        if point_intention:
+                            html += '<div class="point-intention">\n'
+                            html += f'<strong style="color: #666;">点的意图:</strong>{html_module.escape(point_intention)}\n'
+                            html += '</div>\n'
 
-                    html += '</ul>\n'  # end score-list
+                        # 显示支撑理由
+                        if support_reason:
+                            html += '<div class="score-reasons">\n'
+                            html += f'<strong style="color: #666;">支撑理由:</strong>\n'
+                            html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
+                            html += '</div>\n'
+
+                        html += '</div>\n'
+
+                    html += '</div>\n'  # end score-list
 
                 html += '</div>\n'  # end detail-section
 
@@ -693,6 +829,59 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
 
             html += '</div>\n'
 
+        # 权重明细(放在展开内容最底部,简化为紧凑样式)
+        if weight_info.get('raw_total', 0) > 0:
+            wd = weight_info['details']
+            ss = weight_info['support_stats']
+
+            # 判断是形式元素还是实质元素
+            is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'
+
+            html += '<div class="detail-section weight-detail-section" style="margin-top: 12px; border-top: 1px dashed #e0e0e0; padding-top: 10px;">\n'
+            html += '<strong style="font-size: 13px; color: #555;">权重得分明细:</strong>\n'
+            html += '<div class="weight-summary-header" style="margin-top: 4px; font-size: 12px; color: #666;">\n'
+            if is_form:
+                html += f'权重分:{weight_info["weight_score"]:.1f}(形式元素使用新的权重计算逻辑)\n'
+            else:
+                html += f'原始总分:{weight_info["raw_total"]:.1f},权重分:{weight_info["weight_score"]:.1f}(min(100, 原始总分 × 100 / 110))\n'
+            html += '</div>\n'
+
+            html += '<div class="weight-summary-grid" style="margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px;">\n'
+            
+            # 共性维度得分
+            if is_form:
+                # 形式元素:显示频次分、覆盖段落数分、覆盖率分
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">频次分 {wd.get("频次分", 0):.1f}</span>\n'
+                if "覆盖段落数分" in wd:
+                    html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">覆盖段落数分 {wd.get("覆盖段落数分", 0):.1f}</span>\n'
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">覆盖率分 {wd.get("覆盖率分", 0):.1f}</span>\n'
+            else:
+                # 实质元素:显示频次分、覆盖率分
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">频次分 {wd.get("频次分", 0):.1f} / 30</span>\n'
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">覆盖率分 {wd.get("覆盖率分", 0):.1f} / 30</span>\n'
+
+            # 支撑维度得分
+            inspiration_count = ss.get('灵感点数量', 0)
+            if is_form:
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f}({inspiration_count} 个)</span>\n'
+            else:
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f} / 25({inspiration_count} 个)</span>\n'
+
+            purpose_count = ss.get('目的点数量', 0)
+            if is_form:
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">目的点支撑分 {wd.get("目的点支撑分", 0):.1f}({purpose_count} 个)</span>\n'
+            else:
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">目的点支撑分 {wd.get("目的点支撑分", 0):.1f} / 15({purpose_count} 个)</span>\n'
+
+            keypoint_count = ss.get('关键点数量', 0)
+            if is_form:
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">关键点支撑分 {wd.get("关键点支撑分", 0):.1f}({keypoint_count} 个)</span>\n'
+            else:
+                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">关键点支撑分 {wd.get("关键点支撑分", 0):.1f} / 10({keypoint_count} 个)</span>\n'
+
+            html += '</div>\n'
+            html += '</div>\n'
+
         html += '</div>\n'
 
     html += '</li>\n'

+ 178 - 132
examples/static/visualize/tab5.py

@@ -8,6 +8,33 @@ import json
 from typing import Dict, Any, List
 
 
+def get_intent_support_data(element: Dict[str, Any]) -> Dict[str, Any]:
+    """
+    获取元素的意图支撑数据(兼容新旧数据结构)
+    
+    优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
+    这两个字段内部存储的都是意图支撑数据
+    
+    Args:
+        element: 元素数据
+        
+    Returns:
+        意图支撑数据字典,格式:{"灵感点": [...], "目的点": [...], "关键点": [...]}
+    """
+    # 优先使用"意图支撑"字段
+    intent_support = element.get('意图支撑')
+    if intent_support and isinstance(intent_support, dict):
+        return intent_support
+    
+    # 如果没有"意图支撑",则使用"多维度评分"字段(兼容旧数据)
+    multi_scores = element.get('多维度评分')
+    if multi_scores and isinstance(multi_scores, dict):
+        return multi_scores
+    
+    # 都没有则返回空字典
+    return {}
+
+
 def generate_tab5_content(data: Dict[str, Any]) -> str:
     """生成Tab5内容:实质与形式的双向支撑关系图(4列布局)"""
     html = '<div class="tab-content" id="tab5">\n'
@@ -70,7 +97,7 @@ def generate_tab5_content(data: Dict[str, Any]) -> str:
     html += '<div class="tab5-header">\n'
     html += '<h2 class="tab5-title">实质与形式的双向支撑关系图</h2>\n'
     html += '<div class="tab5-description">\n'
-    html += '<p>4列展示:<strong>选题点(实质评分)</strong> ← <strong>实质点</strong> | <strong>形式点</strong> → <strong>选题点(形式评分)</strong></p>\n'
+    html += '<p>4列展示:<strong>选题点(实质支撑)</strong> ← <strong>实质点</strong> | <strong>形式点</strong> → <strong>选题点(形式支撑)</strong></p>\n'
     html += '<ul class="relationship-rules">\n'
     html += '<li>左侧选题点:显示实质点对灵感点、关键点、目的点的支撑关系</li>\n'
     html += '<li>右侧选题点:显示形式点对灵感点、关键点、目的点的支撑关系</li>\n'
@@ -88,7 +115,7 @@ def generate_tab5_content(data: Dict[str, Any]) -> str:
     # 主要内容区域(4列布局)
     html += '<div class="tab5-four-column-container">\n'
 
-    # 第1列:左侧选题点(来自实质点的评分
+    # 第1列:左侧选题点(来自实质点的支撑关系
     html += '<div class="tab5-column tab5-left-targets">\n'
     html += '<h3 class="panel-title">选题点<br/><span style="font-size:0.8em;font-weight:normal;color:#6c757d;">(实质支撑)</span></h3>\n'
 
@@ -260,7 +287,7 @@ def generate_tab5_content(data: Dict[str, Any]) -> str:
 
     html += '</div>\n'
 
-    # 第4列:右侧选题点(来自形式点的评分
+    # 第4列:右侧选题点(来自形式点的支撑关系
     html += '<div class="tab5-column tab5-right-targets">\n'
     html += '<h3 class="panel-title">选题点<br/><span style="font-size:0.8em;font-weight:normal;color:#6c757d;">(形式支撑)</span></h3>\n'
 
@@ -382,11 +409,14 @@ def render_form_card(form: Dict[str, Any], css_class: str) -> str:
     form_id = form.get('id', '')
     form_name = form.get('名称', '')
     description = form.get('描述', '')
+    weight_score = form.get('权重分')
 
     html = f'<div class="form-card {css_class}" data-id="{html_module.escape(form_id)}" onclick="selectForm(' + f"'{form_id}'" + ')">\n'
     html += f'<div class="card-header">\n'
     html += f'<div class="card-id">#{html_module.escape(form_id)}</div>\n'
     html += f'<div class="card-name">{html_module.escape(form_name)}</div>\n'
+    if weight_score is not None:
+        html += f'<div class="card-weight" style="font-size: 11px; color: #666; margin-top: 2px;">权重分: {weight_score:.1f}</div>\n'
     html += '</div>\n'
     if description:
         html += f'<div class="card-description">{html_module.escape(description[:50])}{"..." if len(description) > 50 else ""}</div>\n'
@@ -457,18 +487,18 @@ def build_bidirectional_relationships(
     all_substances = concrete_elements + concrete_concepts + implicit_concepts + abstract_concepts
     all_forms = concrete_element_forms + concrete_concept_forms + overall_forms
 
-    # 阈值设定
-    # 实质点侧已不再使用多维度评分,仅依赖意图支撑关系进行连线
-    FORM_THRESHOLD = 0.5  # 形式点和选题点的阈值
+    # 注意:优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段(兼容旧数据)
+    # 这两个字段内部存储的都是意图支撑数据,每个项目就是一个支撑点
+    # 不再需要阈值过滤,所有支撑点都会显示连线
 
-    # 1. 构建实质点到选题点的关系(基于「意图支撑」,不再使用多维度评分
+    # 1. 构建实质点到选题点的关系(基于意图支撑数据
     for substance in all_substances:
         substance_id = substance.get('id', '')
         if not substance_id:
             continue
 
         dimension_2 = substance.get('维度', {}).get('二级', '')
-        intention_support = substance.get('意图支撑', {}) or {}
+        intention_support = get_intent_support_data(substance)
 
         relationships["substance_to_target"][substance_id] = {
             "name": substance.get('名称', ''),
@@ -646,7 +676,8 @@ def build_bidirectional_relationships(
             continue
 
         dimension_2 = form.get('维度', {}).get('二级', '')
-        multi_scores = form.get('多维度评分', {})
+        # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
+        intent_support_data = get_intent_support_data(form)
 
         relationships["form_to_target"][form_id] = {
             "name": form.get('名称', ''),
@@ -656,46 +687,37 @@ def build_bidirectional_relationships(
             "keypoint": []
         }
 
-        # 处理灵感点
-        if '灵感点' in multi_scores:
-            for idx, score_data in enumerate(multi_scores['灵感点'], 1):
-                semantic_score = score_data.get('语义相似度', 0)
-                text_score = score_data.get('文本相似度', 0)
-                avg_score = (semantic_score + text_score) / 2
-
-                if avg_score > FORM_THRESHOLD:
-                    # 获取对应的灵感点,并为每个特征创建子索引关系
-                    if idx <= len(inspiration_points):
-                        point = inspiration_points[idx - 1]
-                        features = point.get('提取的特征', [])
-                        feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
-
-                        if feature_names:
-                            # 为每个特征创建独立的关系
-                            for feature_idx in range(1, len(feature_names) + 1):
-                                target_id = f'inspiration-{idx}-{feature_idx}'
-                                relationships["form_to_target"][form_id]["inspiration"].append({
-                                    "target_id": target_id,
-                                    "point": score_data.get('点', ''),
-                                    "avg_score": avg_score
-                                })
-
-                                # 反向关系
-                                if target_id not in relationships["target_from_form"]:
-                                    relationships["target_from_form"][target_id] = []
-                                relationships["target_from_form"][target_id].append({
-                                    "form_id": form_id,
-                                    "name": form.get('名称', ''),
-                                    "type": dimension_2,
-                                    "score": avg_score
-                                })
-                        else:
-                            # 没有特征,使用主索引
-                            target_id = f'inspiration-{idx}'
+        # 处理灵感点(每个项目就是一个支撑点)
+        if '灵感点' in intent_support_data:
+            for support_point in intent_support_data['灵感点']:
+                if not isinstance(support_point, dict):
+                    continue
+                
+                # 每个项目就是一个支撑点,直接使用
+                point_name = support_point.get('点', '')
+                if not point_name:
+                    continue
+                
+                # 根据点名称找到对应的灵感点索引
+                point_idx = None
+                for idx, point in enumerate(inspiration_points, 1):
+                    if point.get('灵感点') == point_name or point.get('名称') == point_name:
+                        point_idx = idx
+                        break
+                
+                if point_idx and point_idx <= len(inspiration_points):
+                    point = inspiration_points[point_idx - 1]
+                    features = point.get('提取的特征', [])
+                    feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
+
+                    if feature_names:
+                        # 为每个特征创建独立的关系
+                        for feature_idx in range(1, len(feature_names) + 1):
+                            target_id = f'inspiration-{point_idx}-{feature_idx}'
                             relationships["form_to_target"][form_id]["inspiration"].append({
                                 "target_id": target_id,
-                                "point": score_data.get('点', ''),
-                                "avg_score": avg_score
+                                "point": point_name,
+                                "support_reason": support_point.get('支撑理由', '')
                             })
 
                             # 反向关系
@@ -705,112 +727,136 @@ def build_bidirectional_relationships(
                                 "form_id": form_id,
                                 "name": form.get('名称', ''),
                                 "type": dimension_2,
-                                "score": avg_score
+                                "support_reason": support_point.get('支撑理由', '')
                             })
-
-        # 处理目的点
-        if '目的点' in multi_scores:
-            for idx, score_data in enumerate(multi_scores['目的点'], 1):
-                semantic_score = score_data.get('语义相似度', 0)
-                text_score = score_data.get('文本相似度', 0)
-                avg_score = (semantic_score + text_score) / 2
-
-                if avg_score > FORM_THRESHOLD:
-                    # 获取对应的目的点,并为每个特征创建子索引关系
-                    if idx <= len(purpose_points):
-                        point = purpose_points[idx - 1]
-                        features = point.get('提取的特征', [])
-                        feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
-
-                        if feature_names:
-                            # 为每个特征创建独立的关系
-                            for feature_idx in range(1, len(feature_names) + 1):
-                                target_id = f'purpose-{idx}-{feature_idx}'
-                                relationships["form_to_target"][form_id]["purpose"].append({
-                                    "target_id": target_id,
-                                    "point": score_data.get('点', ''),
-                                    "avg_score": avg_score
-                                })
-
-                                # 反向关系
-                                if target_id not in relationships["target_from_form"]:
-                                    relationships["target_from_form"][target_id] = []
-                                relationships["target_from_form"][target_id].append({
-                                    "form_id": form_id,
-                                    "name": form.get('名称', ''),
-                                    "type": dimension_2,
-                                    "score": avg_score
-                                })
-                        else:
-                            # 没有特征,使用主索引
-                            target_id = f'purpose-{idx}'
+                    else:
+                        # 没有特征,使用主索引
+                        target_id = f'inspiration-{point_idx}'
+                        relationships["form_to_target"][form_id]["inspiration"].append({
+                            "target_id": target_id,
+                            "point": point_name,
+                            "support_reason": support_point.get('支撑理由', '')
+                        })
+
+                        # 反向关系
+                        if target_id not in relationships["target_from_form"]:
+                            relationships["target_from_form"][target_id] = []
+                        relationships["target_from_form"][target_id].append({
+                            "form_id": form_id,
+                            "name": form.get('名称', ''),
+                            "type": dimension_2,
+                            "support_reason": support_point.get('支撑理由', '')
+                        })
+
+        # 处理目的点(每个项目就是一个支撑点)
+        if '目的点' in intent_support_data:
+            for support_point in intent_support_data['目的点']:
+                if not isinstance(support_point, dict):
+                    continue
+                
+                # 每个项目就是一个支撑点,直接使用
+                point_name = support_point.get('点', '')
+                if not point_name:
+                    continue
+                
+                # 根据点名称找到对应的目的点索引
+                point_idx = None
+                for idx, point in enumerate(purpose_points, 1):
+                    if point.get('目的点') == point_name or point.get('名称') == point_name:
+                        point_idx = idx
+                        break
+                
+                if point_idx and point_idx <= len(purpose_points):
+                    point = purpose_points[point_idx - 1]
+                    features = point.get('提取的特征', [])
+                    feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
+
+                    if feature_names:
+                        for feature_idx in range(1, len(feature_names) + 1):
+                            target_id = f'purpose-{point_idx}-{feature_idx}'
                             relationships["form_to_target"][form_id]["purpose"].append({
                                 "target_id": target_id,
-                                "point": score_data.get('点', ''),
-                                "avg_score": avg_score
+                                "point": point_name,
+                                "support_reason": support_point.get('支撑理由', '')
                             })
-
-                            # 反向关系
                             if target_id not in relationships["target_from_form"]:
                                 relationships["target_from_form"][target_id] = []
                             relationships["target_from_form"][target_id].append({
                                 "form_id": form_id,
                                 "name": form.get('名称', ''),
                                 "type": dimension_2,
-                                "score": avg_score
+                                "support_reason": support_point.get('支撑理由', '')
                             })
-
-        # 处理关键点
-        if '关键点' in multi_scores:
-            for idx, score_data in enumerate(multi_scores['关键点'], 1):
-                semantic_score = score_data.get('语义相似度', 0)
-                text_score = score_data.get('文本相似度', 0)
-                avg_score = (semantic_score + text_score) / 2
-
-                if avg_score > FORM_THRESHOLD:
-                    # 获取对应的关键点,并为每个特征创建子索引关系
-                    if idx <= len(key_points):
-                        point = key_points[idx - 1]
-                        features = point.get('提取的特征', [])
-                        feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
-
-                        if feature_names:
-                            # 为每个特征创建独立的关系
-                            for feature_idx in range(1, len(feature_names) + 1):
-                                target_id = f'keypoint-{idx}-{feature_idx}'
-                                relationships["form_to_target"][form_id]["keypoint"].append({
-                                    "target_id": target_id,
-                                    "point": score_data.get('点', ''),
-                                    "avg_score": avg_score
-                                })
-
-                                # 反向关系
-                                if target_id not in relationships["target_from_form"]:
-                                    relationships["target_from_form"][target_id] = []
-                                relationships["target_from_form"][target_id].append({
-                                    "form_id": form_id,
-                                    "name": form.get('名称', ''),
-                                    "type": dimension_2,
-                                    "score": avg_score
-                                })
-                        else:
-                            # 没有特征,使用主索引
-                            target_id = f'keypoint-{idx}'
+                    else:
+                        target_id = f'purpose-{point_idx}'
+                        relationships["form_to_target"][form_id]["purpose"].append({
+                            "target_id": target_id,
+                            "point": point_name,
+                            "support_reason": support_point.get('支撑理由', '')
+                        })
+                        if target_id not in relationships["target_from_form"]:
+                            relationships["target_from_form"][target_id] = []
+                        relationships["target_from_form"][target_id].append({
+                            "form_id": form_id,
+                            "name": form.get('名称', ''),
+                            "type": dimension_2,
+                            "support_reason": support_point.get('支撑理由', '')
+                        })
+
+        # 处理关键点(每个项目就是一个支撑点)
+        if '关键点' in intent_support_data:
+            for support_point in intent_support_data['关键点']:
+                if not isinstance(support_point, dict):
+                    continue
+                
+                # 每个项目就是一个支撑点,直接使用
+                point_name = support_point.get('点', '')
+                if not point_name:
+                    continue
+                
+                # 根据点名称找到对应的关键点索引
+                point_idx = None
+                for idx, point in enumerate(key_points, 1):
+                    if point.get('关键点') == point_name or point.get('名称') == point_name:
+                        point_idx = idx
+                        break
+                
+                if point_idx and point_idx <= len(key_points):
+                    point = key_points[point_idx - 1]
+                    features = point.get('提取的特征', [])
+                    feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
+
+                    if feature_names:
+                        for feature_idx in range(1, len(feature_names) + 1):
+                            target_id = f'keypoint-{point_idx}-{feature_idx}'
                             relationships["form_to_target"][form_id]["keypoint"].append({
                                 "target_id": target_id,
-                                "point": score_data.get('点', ''),
-                                "avg_score": avg_score
+                                "point": point_name,
+                                "support_reason": support_point.get('支撑理由', '')
                             })
-
-                            # 反向关系
                             if target_id not in relationships["target_from_form"]:
                                 relationships["target_from_form"][target_id] = []
                             relationships["target_from_form"][target_id].append({
                                 "form_id": form_id,
                                 "name": form.get('名称', ''),
                                 "type": dimension_2,
-                                "score": avg_score
+                                "support_reason": support_point.get('支撑理由', '')
                             })
+                    else:
+                        target_id = f'keypoint-{point_idx}'
+                        relationships["form_to_target"][form_id]["keypoint"].append({
+                            "target_id": target_id,
+                            "point": point_name,
+                            "support_reason": support_point.get('支撑理由', '')
+                        })
+                        if target_id not in relationships["target_from_form"]:
+                            relationships["target_from_form"][target_id] = []
+                        relationships["target_from_form"][target_id].append({
+                            "form_id": form_id,
+                            "name": form.get('名称', ''),
+                            "type": dimension_2,
+                            "support_reason": support_point.get('支撑理由', '')
+                        })
 
     # 3. 构建形式点到实质点的支撑关系
     for form in all_forms:

+ 0 - 264
examples/test.py

@@ -1,264 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-测试脚本:视频下载、上传到Gemini并进行分析
-"""
-
-import json
-import sys
-import os
-import time
-import requests
-import re
-from pathlib import Path
-from typing import Optional
-
-# 添加项目根目录到路径
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-# 手动加载.env文件
-def load_env_file(env_path):
-    """手动加载.env文件"""
-    if not env_path.exists():
-        return False
-    with open(env_path, 'r') as f:
-        for line in f:
-            line = line.strip()
-            if not line or line.startswith('#'):
-                continue
-            if '=' in line:
-                key, value = line.split('=', 1)
-                os.environ[key.strip()] = value.strip()
-    return True
-
-env_path = project_root / ".env"
-load_env_file(env_path)
-
-from google import genai
-
-client = genai.Client()
-
-# Gemini 文件处理相关常量
-FILE_PROCESS_TIMEOUT = 300
-API_RETRY_INTERVAL = 5
-
-
-def load_video_data(directory: str) -> dict:
-    """加载视频详情.json文件
-    
-    Args:
-        directory: 目录名(如"53009047")
-        
-    Returns:
-        视频详情字典
-    """
-    video_data_path = Path(__file__).parent / directory / "视频详情.json"
-    if not video_data_path.exists():
-        raise FileNotFoundError(f"未找到视频详情文件:{video_data_path}")
-    
-    with open(video_data_path, "r", encoding="utf-8") as f:
-        data = json.load(f)
-    
-    return data
-
-
-def download_video(video_url: str, output_dir: Path, filename: str = None) -> Optional[str]:
-    """下载视频到指定目录"""
-    try:
-        output_dir.mkdir(parents=True, exist_ok=True)
-        
-        if not filename:
-            filename = video_url.split('/')[-1].split('?')[0]
-            if not filename.endswith('.mp4'):
-                filename = 'video.mp4'
-        
-        output_path = output_dir / filename
-        print(f"下载视频: {output_path.name}")
-        
-        response = requests.get(video_url, stream=True, timeout=120)
-        response.raise_for_status()
-        
-        with open(output_path, 'wb') as f:
-            for chunk in response.iter_content(chunk_size=8192):
-                if chunk:
-                    f.write(chunk)
-        
-        return str(output_path)
-    except Exception as e:
-        print(f"❌ 下载视频失败: {e}")
-        return None
-
-
-def wait_for_file_processing(uploaded_file: "genai.types.File") -> Optional["genai.types.File"]:
-    """等待 Gemini 完成文件处理"""
-    start = time.time()
-    current = uploaded_file
-    
-    if not hasattr(current, 'state'):
-        return current
-    
-    state_name = getattr(current.state, 'name', None) if hasattr(current.state, 'name') else str(current.state)
-    
-    if state_name != "PROCESSING":
-        if state_name == "FAILED":
-            print(f"❌ 文件处理失败: {current.state}")
-            return None
-        elif state_name in ["ACTIVE", "COMPLETE", "READY"]:
-            return current
-    
-    print("等待文件处理...", end="", flush=True)
-    
-    while True:
-        elapsed = time.time() - start
-        if elapsed > FILE_PROCESS_TIMEOUT:
-            print(f"\n❌ 文件处理超时(超过 {FILE_PROCESS_TIMEOUT} 秒)")
-            return None
-        
-        time.sleep(API_RETRY_INTERVAL)
-        
-        if hasattr(current, 'name'):
-            try:
-                current = client.files.get(name=current.name)
-                if not hasattr(current, 'state'):
-                    break
-                
-                state_name = getattr(current.state, 'name', None) if hasattr(current.state, 'name') else str(current.state)
-                
-                if state_name == "FAILED":
-                    print(f"\n❌ 文件处理失败: {current.state}")
-                    return None
-                elif state_name in ["ACTIVE", "COMPLETE", "READY"]:
-                    break
-            except Exception as e:
-                print(f"\n❌ 获取文件状态失败: {e}")
-                return None
-        else:
-            return None
-    
-    print(" 完成")
-    return current
-
-
-def upload_to_gemini(local_file_path: str) -> Optional["genai.types.File"]:
-    """上传视频到Gemini"""
-    try:
-        print("上传视频到Gemini...", end=" ", flush=True)
-        uploaded_file = client.files.upload(file=local_file_path)
-        
-        processed_file = wait_for_file_processing(uploaded_file)
-        if not processed_file:
-            raise Exception("文件处理失败")
-        
-        print("完成")
-        return processed_file
-    except Exception as e:
-        print(f"\n❌ 上传失败: {e}")
-        return None
-
-
-def analyze_video_outline(file_obj: "genai.types.File", video_title: str) -> Optional[dict]:
-    """使用Gemini进行视频大纲理解"""
-    try:
-        system_prompt = """
-## 角色
-你是一个专业的视频内容分析助手。请分析视频内容,提取出视频的大纲信息,需要有明确的时间段分割和汇总。
-
-## 任务
-逐帧分析视频内容,提取出视频的大纲信息,需要有明确的时间段分割和汇总,每个时间段需要有内容摘要和最核心的内容点20个字概括,以及口播语音(需要有时间戳)。
-
-## 输出格式
-{
-    "title": "视频标题/主题",
-    "summary": "视频内容摘要(100-200字)",
-    "words": "视频口播文案",
-    "summaries": {
-        "时间段1": {
-            "summary": "时间段1的内容摘要和最核心的内容点20个字概括",
-            "words": "视频时间段1的口播语音(需要有时间戳)"
-        },
-        "时间段2": {
-            "summary": "时间段1的内容摘要和最核心的内容点20个字概括",
-            "words": "视频时间段2的口播语音(需要有时间戳)"
-        }
-    },
-    "duration_estimate": "视频时长估算(如果可识别)",
-    "category": "视频类别/分类"
-}
-"""
-        
-        print("分析视频大纲...", end=" ", flush=True)
-        response = client.models.generate_content(model="gemini-2.5-flash", contents=[file_obj, system_prompt])
-        
-        if hasattr(response, 'text'):
-            content = response.text.strip()
-        elif hasattr(response, 'content'):
-            if isinstance(response.content, str):
-                content = response.content.strip()
-            elif hasattr(response.content, 'text'):
-                content = response.content.text.strip()
-            else:
-                content = str(response.content).strip()
-        else:
-            content = str(response).strip()
-        
-        print("完成")
-        
-        try:
-            json_match = re.search(r'\{[\s\S]*\}', content)
-            if json_match:
-                return json.loads(json_match.group())
-            else:
-                return {"raw_response": content, "parsed": False}
-        except json.JSONDecodeError:
-            return {"raw_response": content, "parsed": False}
-        
-    except Exception as e:
-        print(f"\n❌ 分析失败: {e}")
-        return None
-
-
-def main():
-    """主函数"""
-    directory = "53009047"
-    current_dir = Path(__file__).parent
-    
-    # 读取视频详情
-    try:
-        video_data = load_video_data(directory)
-        video_url = video_data.get("video", "")
-        title = video_data.get("title", "")
-        
-        if not video_url:
-            print(f"❌ 视频详情文件中未找到video字段")
-            return
-    except Exception as e:
-        print(f"❌ 读取视频详情失败: {e}")
-        return
-    
-    # 下载视频
-    video_filename = f"{directory}.mp4"
-    video_path = download_video(video_url, current_dir, video_filename)
-    if not video_path:
-        return
-    
-    # 上传到Gemini
-    file_obj = upload_to_gemini(video_path)
-    if not file_obj:
-        return
-    
-    # 分析视频大纲
-    analysis_result = analyze_video_outline(file_obj, title)
-    
-    # 保存结果
-    if analysis_result:
-        output_path = current_dir / f"{directory}_analysis_result.json"
-        with open(output_path, "w", encoding="utf-8") as f:
-            json.dump(analysis_result, f, ensure_ascii=False, indent=2)
-        print(f"结果已保存: {output_path}")
-    else:
-        print("❌ 未能获取分析结果")
-
-
-if __name__ == "__main__":
-    main()

BIN
src/components/agents/__pycache__/script_form_extraction_agent.cpython-313.pyc


BIN
src/components/agents/__pycache__/script_section_division_agent.cpython-313.pyc


BIN
src/components/agents/__pycache__/script_substance_extraction_agent.cpython-313.pyc


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 610 - 222
src/components/agents/script_form_extraction_agent.py


+ 14 - 0
src/components/agents/script_section_division_agent.py

@@ -136,6 +136,10 @@ class ScriptSectionDivisionAgent(BaseLLMAgent):
 - 具象层是抽象层的具体展开
 - 每个抽象层下必须有至少1个具象层子项
 
+### 整体情绪价值(Emotion Value)
+**内容背后的情绪钩子或社会价值**。仅在有明显的升维价值时提取(如:民族自信、致青春、趋吉避凶、鞠躬尽瘁),否则留空。
+** 输出案例: "致青春", "友谊长存", "猎奇", "颠覆认知"等 6字以内的词语,可输出多个,用逗号分隔**
+
 ## Section字段
 
 - 描述: 段落描述(共性维度名称;具体内容概括)
@@ -149,6 +153,16 @@ class ScriptSectionDivisionAgent(BaseLLMAgent):
 
 {
   "内容品类": "内容品类",
+  "整体情绪分析": ["整体情绪分析1(6个字以内)", "整体情绪分析2(6个字以内)"],
+  "段落衔接机制": [
+    {
+      "from": "段落1",
+      "to": "段落2",
+      "衔接方式": "如何过渡到下一个段落",
+      "衔接语/画面特征": "衔接语或画面特征",
+      "设计意图": "设计意图"
+    }
+  ],
   "段落列表": [
     {
       "描述": "共性维度名称",

+ 47 - 9
src/components/agents/script_substance_extraction_agent.py

@@ -145,7 +145,9 @@ class ScriptSubstanceExtractionAgent(BaseLLMAgent):
 
         # Step 2: 提取具象概念(画面中的文字 + 口播内容中的文字)
         logger.info("▶ Step 2: 提取具象概念")
-        concrete_concepts = self._step2_extract_concrete_concepts(video_file, text_data)
+        concrete_concepts = self._step2_extract_concrete_concepts(
+            video_file, text_data, concrete_elements
+        )
 
         # 隐含概念相关逻辑已移除,不再单独提取
         implicit_concepts: List[dict] = []
@@ -323,26 +325,42 @@ class ScriptSubstanceExtractionAgent(BaseLLMAgent):
     def _step2_extract_concrete_concepts(
         self,
         video_file,
-        text_data: dict
+        text_data: dict,
+        concrete_elements: List[dict],
     ) -> List[dict]:
         """Step 2: 提取具象概念 - 文字中字面出现的名词"""
         if not self.is_initialized:
             self.initialize()
 
-        prompt = """# 任务
+        # 从第一步结果中提取已识别的具体元素名称,供本步骤排除使用
+        element_names = [
+            e.get("名称") for e in (concrete_elements or []) if e.get("名称")
+        ]
+        element_names_text = (
+            json.dumps(element_names, ensure_ascii=False, indent=2)
+            if element_names
+            else "[]"
+        )
+
+        prompt = f"""# 任务
 从视频中提取"具象概念"
 
 # 核心定义
 ## 具象概念
 - **定义**:视频画面内的文字或者口播内容中明确提到的完整名词
 
+## 排除的名称(来自第一步,仅用于排除)
+**禁止提取的名称**:{element_names_text}
+
 ## 判断标准
 - **视频画面内的文字或者口播内容**中实际出现的**完整名词**
+- **不能是视频画面中出现的元素的名称等归类词**
 - 去掉表达方式后,这个概念仍然存在
 
 # 约束
 - 禁止通过语义推导、联想、理解得出的名词
-- 禁止归类词(蔬菜、水果、人)
+- **禁止归类词(蔬菜、水果、人等)**
+- **禁止使用第一步中已提取的具体元素名称**
 - 禁止拆分复合词
 - 禁止提取形容词、动词
 - 禁止提取谓语、定语、状语、补语
@@ -362,11 +380,11 @@ class ScriptSubstanceExtractionAgent(BaseLLMAgent):
 
 # 输出json结构
 [
-    {
+    {{
       "id": "从1开始的自增序列",
       "名称": "字面原词(完整名词)",
       "描述": "说明这个概念是什么",
-      "维度": {"一级": "实质", "二级": "具象概念"},
+      "维度": {{"一级": "实质", "二级": "具象概念"}},
       "来源": "HH:MM:SS",
       "上下文验证": {{
         "原文位置": "该词在原视频画面内的文字或者口播内容中的具体句子",
@@ -374,7 +392,7 @@ class ScriptSubstanceExtractionAgent(BaseLLMAgent):
         "语境判断": "说明该词在此语境中确实作为名词使用的理由"
       }},
       "推理": "为什么这个名词被认为是具象概念"
-    }
+    }}
 ]
 
 注意:只输出同时满足"词性是名词"和"上下文中作为名词使用"两个条件的概念
@@ -1058,8 +1076,28 @@ class ScriptSubstanceExtractionAgent(BaseLLMAgent):
         rejected_ids = []
         for element_id, analyzed_data in analyzed_map.items():
             element_name = analyzed_data.get("名称", "N/A")
-            frequency = analyzed_data.get("出现频次", 0)
-            coverage_rate = analyzed_data.get("段落覆盖率", 0.0)
+            
+            # 声音类型的实质元素(背景音乐、音效声等)直接通过筛选,不参与后续判断
+            sound_type_names = ["背景音乐", "音效声"]
+            if element_name in sound_type_names:
+                filtered_ids.append(element_id)
+                logger.info(
+                    f"✅ 保留: id={element_id}, name={element_name}, 原因=声音类型元素,豁免筛选"
+                )
+                continue
+            
+            # 确保 frequency 是整数类型
+            frequency_raw = analyzed_data.get("出现频次", 0)
+            try:
+                frequency = int(frequency_raw) if frequency_raw is not None else 0
+            except (ValueError, TypeError):
+                frequency = 0
+            # 确保 coverage_rate 是浮点数类型
+            coverage_rate_raw = analyzed_data.get("段落覆盖率", 0.0)
+            try:
+                coverage_rate = float(coverage_rate_raw) if coverage_rate_raw is not None else 0.0
+            except (ValueError, TypeError):
+                coverage_rate = 0.0
 
             # 频次过滤:出现频次<=1 的直接过滤(不再继续做支撑和覆盖率判断)
             if frequency <= 1:

+ 51 - 45
src/components/functions/video_upload_function.py

@@ -67,8 +67,8 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
 
             logger.info(f"开始下载视频: {video_url}")
 
-            # 1. 下载视频到本地临时文件(或使用examples目录下的现有文件)
-            # 从input_data中获取channel_content_id,用于查找examples目录下的文件
+            # 1. 下载视频到本地(或使用examples/videos目录下的现有文件)
+            # 从input_data中获取channel_content_id,用于查找examples/videos目录下的文件
             channel_content_id = input_data.get("channel_content_id", "")
             local_video_path, is_temp_file = self._download_video(video_url, channel_content_id)
             
@@ -131,36 +131,45 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
             }
 
     def _download_video(self, video_url: str, channel_content_id: str = "") -> Tuple[Optional[str], bool]:
-        """下载视频到本地临时文件,或使用examples目录下的现有文件
+        """下载视频到本地,或使用examples/videos目录下的现有文件
         
         Args:
             video_url: 视频URL
-            channel_content_id: 频道内容ID,用于查找examples目录下的文件
+            channel_content_id: 频道内容ID,用于查找examples/videos目录下的文件
             
         Returns:
             (本地文件路径, 是否为临时文件) 的元组,失败返回 (None, True)
-            如果使用examples目录下的文件,返回 (文件路径, False)
-            如果下载到临时文件,返回 (临时文件路径, True)
+            如果使用examples/videos目录下的文件,返回 (文件路径, False)
+            如果下载到examples/videos目录,返回 (文件路径, False)
         """
         try:
-            # 1. 首先检查examples目录下是否有对应的mp4文件
+            # 1. 首先检查examples/videos目录下是否有对应的mp4文件
             existing_file = self._check_examples_directory(channel_content_id)
             if existing_file:
-                logger.info(f"在examples目录下找到现有文件,直接使用: {existing_file}")
+                logger.info(f"在examples/videos目录下找到现有文件,直接使用: {existing_file}")
                 return existing_file, False
             
-            # 2. 如果没有找到,则下载到临时文件
-            logger.info("未在examples目录下找到同名文件,开始下载...")
+            # 2. 如果没有找到,则下载到examples/videos目录
+            if not channel_content_id:
+                logger.warning("未提供channel_content_id,无法保存到examples/videos目录")
+                return None, True
+            
+            logger.info("未在examples/videos目录下找到同名文件,开始下载...")
+            
+            # 获取项目根目录
+            project_root = Path(__file__).parent.parent.parent.parent
+            videos_dir = project_root / "examples" / "videos"
             
-            # 创建临时文件
-            temp_dir = tempfile.gettempdir()
-            temp_file = tempfile.NamedTemporaryFile(
-                dir=temp_dir,
-                suffix=".mp4",
-                delete=False
-            )
-            temp_path = temp_file.name
-            temp_file.close()
+            # 确保目录存在
+            videos_dir.mkdir(parents=True, exist_ok=True)
+            
+            # 构建文件路径:examples/videos/{channel_content_id}.mp4
+            target_path = videos_dir / f"{channel_content_id}.mp4"
+            
+            # 如果文件已存在(并发情况),直接返回
+            if target_path.exists():
+                logger.info(f"文件已存在: {target_path}")
+                return str(target_path), False
 
             # 下载视频(带重试机制)
             max_retries = 3
@@ -172,10 +181,10 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
                     if retry_count > 0:
                         logger.info(f"重试下载视频 (第 {retry_count}/{max_retries-1} 次)...")
                     
-                    # 使用 Session 进行下载(参考提供的代码)
+                    # 使用 Session 进行下载
                     session = requests.Session()
                     session.headers.update({
-                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"# 禁用压缩,避免IncompleteRead问题
+                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
                     })
                     
                     # 下载视频(增加超时时间)
@@ -186,22 +195,19 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
                     )
                     response.raise_for_status()
 
-                    # 确保目录存在
-                    os.makedirs(os.path.dirname(temp_path), exist_ok=True)
-
                     # 写入文件
-                    with open(temp_path, "wb") as f:
+                    with open(target_path, "wb") as f:
                         for chunk in response.iter_content(chunk_size=8192):
                             if chunk:
                                 f.write(chunk)
                     
                     # 验证文件大小
-                    file_size = os.path.getsize(temp_path)
+                    file_size = os.path.getsize(target_path)
                     if file_size == 0:
                         raise ValueError("下载的文件大小为0")
                     
-                    logger.info(f"视频下载完成,大小: {file_size / 1024 / 1024:.2f} MB")
-                    return temp_path, True
+                    logger.info(f"视频下载完成,大小: {file_size / 1024 / 1024:.2f} MB,保存到: {target_path}")
+                    return str(target_path), False
                     
                 except (requests.exceptions.ChunkedEncodingError, 
                         requests.exceptions.ConnectionError,
@@ -211,9 +217,9 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
                     retry_count += 1
                     
                     # 清理不完整的文件
-                    if os.path.exists(temp_path):
+                    if target_path.exists():
                         try:
-                            os.remove(temp_path)
+                            os.remove(target_path)
                         except:
                             pass
                     
@@ -227,9 +233,9 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
                         raise
                 except Exception as e:
                     # 其他类型的异常直接抛出,不重试
-                    if os.path.exists(temp_path):
+                    if target_path.exists():
                         try:
-                            os.remove(temp_path)
+                            os.remove(target_path)
                         except:
                             pass
                     raise
@@ -243,12 +249,12 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
             return None, True
     
     def _check_examples_directory(self, channel_content_id: str) -> Optional[str]:
-        """检查examples目录下是否有对应的mp4文件
+        """检查examples/videos目录下是否有对应的mp4文件
         
-        文件路径格式:examples/{channel_content_id}/{channel_content_id}.mp4
+        文件路径格式:examples/videos/{channel_content_id}.mp4
         
         Args:
-            channel_content_id: 频道内容ID(目录名)
+            channel_content_id: 频道内容ID
             
         Returns:
             如果找到文件,返回文件路径;否则返回None
@@ -256,33 +262,33 @@ class VideoUploadFunction(BaseFunction[Dict[str, Any], Dict[str, Any]]):
         try:
             # 如果没有提供channel_content_id,无法查找
             if not channel_content_id:
-                logger.info("未提供channel_content_id,跳过examples目录检查")
+                logger.info("未提供channel_content_id,跳过examples/videos目录检查")
                 return None
             
             # 获取项目根目录
             # __file__ 是 src/components/functions/video_upload_function.py
             # 需要往上4层才能到项目根目录
             project_root = Path(__file__).parent.parent.parent.parent
-            examples_dir = project_root / "examples"
+            videos_dir = project_root / "examples" / "videos"
             
-            if not examples_dir.exists():
-                logger.info(f"examples目录不存在: {examples_dir}")
+            if not videos_dir.exists():
+                logger.info(f"examples/videos目录不存在: {videos_dir}")
                 return None
             
-            # 构建文件路径:examples/{channel_content_id}/{channel_content_id}.mp4
-            video_dir = examples_dir / channel_content_id
-            mp4_file = video_dir / f"{channel_content_id}.mp4"
+            # 构建文件路径:examples/videos/{channel_content_id}.mp4
+            mp4_file = videos_dir / f"{channel_content_id}.mp4"
             logger.info(f"构建文件路径: {mp4_file}")
+            
             # 检查文件是否存在
             if mp4_file.exists() and mp4_file.is_file():
-                logger.info(f"在examples目录下找到文件: {mp4_file}")
+                logger.info(f"在examples/videos目录下找到文件: {mp4_file}")
                 return str(mp4_file)
             
-            logger.debug(f"在examples目录下未找到文件: {mp4_file}")
+            logger.debug(f"在examples/videos目录下未找到文件: {mp4_file}")
             return None
             
         except Exception as e:
-            logger.warning(f"检查examples目录时出错: {e}", exc_info=True)
+            logger.warning(f"检查examples/videos目录时出错: {e}", exc_info=True)
             return None
 
 

BIN
src/states/__pycache__/script_state.cpython-313.pyc


+ 5 - 4
src/states/script_state.py

@@ -41,10 +41,11 @@ class ScriptState(TypedDict, total=False):
     concrete_element_forms: List[Dict[str, Any]]  # 具体元素形式
     overall_forms: List[Dict[str, Any]]  # 整体形式
     form_elements: List[Dict[str, Any]]  # 合并后的形式元素
-    form_analyzed_result: List[Dict[str, Any]]  # Step 3: 共性分析结果
-    form_scored_result: Dict[str, Any]  # Step 4: 评分结果
-    form_filtered_ids: List[int]  # Step 5: 筛选的元素ID
-    form_categorized_result: Dict[str, Any]  # Step 6: 分类结果
+    form_analyzed_result: List[Dict[str, Any]]  # Step 4: 共性分析结果
+    form_scored_result: Dict[str, Any]  # Step 5: 支撑判断结果
+    form_weighted_result: Dict[str, Any]  # Step 6: 权重计算结果
+    form_filtered_ids: List[int]  # Step 7: 筛选的元素ID
+    form_categorized_result: Dict[str, Any]  # Step 8: 分类结果
     form_final_elements: List[Dict[str, Any]]  # 形式元素最终结果
 
     # ========== 分离后的元素列表 ==========

BIN
src/workflows/__pycache__/script_workflow.cpython-313.pyc


BIN
src/workflows/__pycache__/what_deconstruction_workflow.cpython-313.pyc


+ 1 - 0
src/workflows/script_workflow.py

@@ -217,6 +217,7 @@ class ScriptWorkflow(BaseGraphAgent):
                 "form_elements": [],
                 "form_analyzed_result": [],
                 "form_scored_result": {},
+                "form_weighted_result": {},
                 "form_filtered_ids": [],
                 "form_categorized_result": {},
                 "form_final_elements": []

+ 1 - 1
src/workflows/what_deconstruction_workflow.py

@@ -482,7 +482,7 @@ class WhatDeconstructionWorkflow(BaseGraphAgent):
             "current_depth": 0,
             "max_depth": self.max_depth,
             "use_topic_agent_v2": input_data.get("use_topic_agent_v2", False),
-            "use_structure_agent": input_data.get("use_structure_agent", False)
+            "use_structure_agent": input_data.get("use_structure_agent", True)
         }
 
         # 执行工作流

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác