Quellcode durchsuchen

refactor: 简化how解构输出结构和可视化

1. match_inspiration_features.py:
   - 输出结构简化:解构结果 替代 what/how解构结果
   - 点直接包含 匹配人设结果 数组(移除 how步骤列表/特征列表 嵌套)
   - 匹配结果按相似度降序排列

2. visualize_how_results.py:
   - 适配新的简化数据结构
   - 调整相似度阈值:<0.5无关,0.5-0.8相似,≥0.8相同
   - 简化状态标签:直接使用 相同/相似/无关
   - 添加 showPointView 函数支持TOC点击跳转

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yangxiaohui vor 1 Tag
Ursprung
Commit
a55ba8d04b

+ 91 - 115
script/data_processing/match_inspiration_features.py

@@ -43,21 +43,19 @@ async def process_single_point(
         model_name: 使用的模型名称
 
     Returns:
-        包含 how 步骤列表的点数据
+        包含匹配人设结果的点数据
     """
     global progress_bar
 
     point_name = point.get("名称", "")
-    feature_list = point.get("特征列表", [])
 
-    # 如果没有特征,直接返回
-    if not feature_list or not persona_features:
+    # 如果没有人设特征,直接返回
+    if not persona_features:
         result = point.copy()
-        result["how步骤列表"] = []
+        result["匹配人设结果"] = []
         return result
 
-    # 提取特征名称和人设名称列表
-    feature_names = [f.get("特征名称", "") for f in feature_list]
+    # 提取人设名称列表
     persona_names = [pf["特征名称"] for pf in persona_features]
 
     # 定义进度回调函数
@@ -66,94 +64,71 @@ async def process_single_point(
         if progress_bar:
             progress_bar.update(count)
 
-    # 核心优化:使用混合模型笛卡尔积一次计算M×N
-    # max_concurrent 控制的是底层 LLM 的全局并发数
+    # 核心优化:使用混合模型笛卡尔积一次计算1×N(点名称 vs 所有人设)
     similarity_results = await compare_phrases_cartesian(
-        feature_names,      # M个特征
+        [point_name],       # 1个点名称
         persona_names,      # N个人设
         max_concurrent=100,  # LLM最大并发数(全局共享)
         progress_callback=on_llm_progress  # 传递进度回调
     )
-    # similarity_results[i][j] = {"相似度": float, "说明": str}
-
-
-    # 构建匹配结果(使用模块返回的完整结果)
-    feature_match_results = []
-
-    for i, feature_item in enumerate(feature_list):
-        feature_name = feature_item.get("特征名称", "")
-        feature_weight = feature_item.get("权重", 1.0)
-
-        # 该特征与所有人设的匹配结果
-        match_results = []
-        for j, persona_feature in enumerate(persona_features):
-            persona_name = persona_feature["特征名称"]
-            persona_level = persona_feature["人设特征层级"]
-
-            # 直接使用模块返回的完整结果
-            similarity_result = similarity_results[i][j]
-
-            # 判断特征类型和分类
-            feature_type = "分类"  # 默认为分类
-            categories = []
-
-            if category_mapping:
-                # 先在标签特征中查找
-                is_tag_feature = False
+    # similarity_results[0][j] = {"相似度": float, "说明": str}
+
+    # 构建匹配结果
+    match_results = []
+    for j, persona_feature in enumerate(persona_features):
+        persona_name = persona_feature["特征名称"]
+        persona_level = persona_feature["人设特征层级"]
+
+        # 直接使用模块返回的完整结果
+        similarity_result = similarity_results[0][j]
+
+        # 判断特征类型和分类
+        feature_type = "分类"  # 默认为分类
+        categories = []
+
+        if category_mapping:
+            # 先在标签特征中查找
+            is_tag_feature = False
+            for ft in ["灵感点", "关键点", "目的点"]:
+                if ft in category_mapping:
+                    type_mapping = category_mapping[ft]
+                    if persona_name in type_mapping:
+                        feature_type = "标签"
+                        categories = type_mapping[persona_name].get("所属分类", [])
+                        is_tag_feature = True
+                        break
+
+            # 如果不是标签特征,检查是否是分类特征
+            if not is_tag_feature:
+                all_categories = set()
                 for ft in ["灵感点", "关键点", "目的点"]:
                     if ft in category_mapping:
-                        type_mapping = category_mapping[ft]
-                        if persona_name in type_mapping:
-                            feature_type = "标签"
-                            categories = type_mapping[persona_name].get("所属分类", [])
-                            is_tag_feature = True
-                            break
-
-                # 如果不是标签特征,检查是否是分类特征
-                if not is_tag_feature:
-                    all_categories = set()
-                    for ft in ["灵感点", "关键点", "目的点"]:
-                        if ft in category_mapping:
-                            for _fname, fdata in category_mapping[ft].items():
-                                cats = fdata.get("所属分类", [])
-                                all_categories.update(cats)
-
-                    if persona_name in all_categories:
-                        feature_type = "分类"
-                        categories = []
-
-            # 去重分类
-            unique_categories = list(dict.fromkeys(categories))
-
-            match_result = {
-                "人设特征名称": persona_name,
-                "人设特征层级": persona_level,
-                "特征类型": feature_type,
-                "特征分类": unique_categories,
-                "匹配结果": similarity_result  # 直接使用模块返回的结果
-            }
-            match_results.append(match_result)
-
-        feature_match_results.append({
-            "特征名称": feature_name,
-            "权重": feature_weight,
-            "匹配结果": match_results
-        })
-
-    # 构建 how 步骤(保持不变)
-    step_name_mapping = {
-        "灵感点": "灵感特征分别匹配人设特征",
-        "关键点": "关键特征分别匹配人设特征",
-        "目的点": "目的特征分别匹配人设特征"
-    }
-
-    how_step = {
-        "步骤名称": step_name_mapping.get(point_type, f"{point_type}特征分别匹配人设特征"),
-        "特征列表": list(feature_match_results)
-    }
+                        for _fname, fdata in category_mapping[ft].items():
+                            cats = fdata.get("所属分类", [])
+                            all_categories.update(cats)
+
+                if persona_name in all_categories:
+                    feature_type = "分类"
+                    categories = []
+
+        # 去重分类
+        unique_categories = list(dict.fromkeys(categories))
+
+        match_result = {
+            "人设特征名称": persona_name,
+            "人设特征层级": persona_level,
+            "特征类型": feature_type,
+            "特征分类": unique_categories,
+            "相似度": similarity_result.get("相似度", 0),
+            "说明": similarity_result.get("说明", "")
+        }
+        match_results.append(match_result)
+
+    # 按相似度降序排列
+    match_results.sort(key=lambda x: x.get("相似度", 0), reverse=True)
 
     result = point.copy()
-    result["how步骤列表"] = [how_step]
+    result["匹配人设结果"] = match_results
 
     return result
 
@@ -178,7 +153,7 @@ async def process_single_task(
         model_name: 使用的模型名称
 
     Returns:
-        包含 how 解构结果的任务
+        包含解构结果的任务(简化结构)
     """
     global progress_bar
 
@@ -187,13 +162,11 @@ async def process_single_task(
     # 获取 what 解构结果
     what_result = task.get("what解构结果", {})
 
-    # 计算当前帖子的总匹配任务数
+    # 计算当前帖子的总匹配任务数(每个点匹配所有人设特征)
     current_task_match_count = 0
     for point_type in ["灵感点", "关键点", "目的点"]:
         point_list = what_result.get(f"{point_type}列表", [])
-        for point in point_list:
-            feature_count = len(point.get("特征列表", []))
-            current_task_match_count += feature_count * len(all_persona_features)
+        current_task_match_count += len(point_list) * len(all_persona_features)
 
     # 创建当前帖子的进度条
     progress_bar = tqdm(
@@ -203,8 +176,8 @@ async def process_single_task(
         ncols=100
     )
 
-    # 构建 how 解构结果
-    how_result = {}
+    # 构建解构结果(简化结构:直接在点上添加匹配人设结果)
+    result_data = {}
 
     # 串行处理灵感点、关键点和目的点
     for point_type in ["灵感点", "关键点", "目的点"]:
@@ -224,18 +197,20 @@ async def process_single_task(
                 )
                 updated_point_list.append(result)
 
-            # 添加到 how 解构结果
-            how_result[point_list_key] = updated_point_list
+            result_data[point_list_key] = updated_point_list
 
     # 关闭当前帖子的进度条
     if progress_bar:
         progress_bar.close()
 
-    # 更新任务
-    updated_task = task.copy()
-    updated_task["how解构结果"] = how_result
+    # 构建简化的输出结构
+    output = {
+        "帖子id": task.get("帖子id", ""),
+        "帖子详情": task.get("帖子详情", {}),
+        "解构结果": result_data
+    }
 
-    return updated_task
+    return output
 
 
 async def process_task_list(
@@ -298,7 +273,9 @@ async def process_task_list(
         for point_type in ["灵感点", "关键点", "目的点"]:
             point_list = what_result.get(f"{point_type}列表", [])
             for point in point_list:
-                feature_count = len(point.get("特征列表", []))
+                # 新结构:点本身就是一个特征;旧结构:使用特征列表
+                feature_list = point.get("特征列表", None)
+                feature_count = len(feature_list) if feature_list else 1
                 total_match_count += feature_count * len(all_features)
 
     print(f"处理灵感点、关键点和目的点特征")
@@ -364,8 +341,11 @@ async def main():
     with open(category_mapping_file, "r", encoding="utf-8") as f:
         category_mapping = json.load(f)
 
-    # 获取任务列表
-    task_list = task_list_data.get("解构任务列表", [])
+    # 获取任务列表(支持列表格式和字典格式)
+    if isinstance(task_list_data, list):
+        task_list = task_list_data
+    else:
+        task_list = task_list_data.get("解构任务列表", [])
     print(f"总任务数: {len(task_list)}")
 
     # 处理任务列表(每个帖子处理完立即保存)
@@ -383,39 +363,35 @@ async def main():
     total_inspiration_points = 0
     total_key_points = 0
     total_purpose_points = 0
-    total_inspiration_features = 0
-    total_key_features = 0
-    total_purpose_features = 0
+    total_matches = 0
 
     for task in updated_task_list:
-        how_result = task.get("how解构结果", {})
+        result = task.get("解构结果", {})
 
         # 统计灵感点
-        inspiration_list = how_result.get("灵感点列表", [])
+        inspiration_list = result.get("灵感点列表", [])
         total_inspiration_points += len(inspiration_list)
         for point in inspiration_list:
-            total_inspiration_features += len(point.get("特征列表", []))
+            total_matches += len(point.get("匹配人设结果", []))
 
         # 统计关键点
-        key_list = how_result.get("关键点列表", [])
+        key_list = result.get("关键点列表", [])
         total_key_points += len(key_list)
         for point in key_list:
-            total_key_features += len(point.get("特征列表", []))
+            total_matches += len(point.get("匹配人设结果", []))
 
         # 统计目的点
-        purpose_list = how_result.get("目的点列表", [])
+        purpose_list = result.get("目的点列表", [])
         total_purpose_points += len(purpose_list)
         for point in purpose_list:
-            total_purpose_features += len(point.get("特征列表", []))
+            total_matches += len(point.get("匹配人设结果", []))
 
     print(f"\n统计:")
     print(f"  处理的帖子数: {len(updated_task_list)}")
     print(f"  处理的灵感点数: {total_inspiration_points}")
-    print(f"  处理的灵感点特征数: {total_inspiration_features}")
     print(f"  处理的关键点数: {total_key_points}")
-    print(f"  处理的关键点特征数: {total_key_features}")
     print(f"  处理的目的点数: {total_purpose_points}")
-    print(f"  处理的目的点特征数: {total_purpose_features}")
+    print(f"  总匹配数: {total_matches}")
 
 
 if __name__ == "__main__":

+ 143 - 365
script/data_processing/visualize_how_results.py

@@ -24,7 +24,7 @@ from script.data_processing.path_config import PathConfig
 
 # ============ 相似度阈值配置 ============
 SIMILARITY_THRESHOLD_SAME = 0.8      # >= 此值为"相同"
-SIMILARITY_THRESHOLD_SIMILAR = 0.6  # >= 此值为"相似",< SAME阈值
+SIMILARITY_THRESHOLD_SIMILAR = 0.5  # >= 此值为"相似",< SAME阈值
 # < SIMILAR阈值 为"无关"
 
 # 相似度对应的颜色
@@ -209,68 +209,25 @@ def generate_post_detail_html(post_data: Dict, post_idx: int) -> str:
     return html
 
 
-def generate_inspiration_detail_html(inspiration_point: Dict, feature_status_map: Dict[str, str] = None, point_type: str = "灵感点") -> str:
-    """生成点详情HTML
+def generate_inspiration_detail_html(point: Dict, point_type: str = "灵感点") -> str:
+    """生成点详情HTML(简化版,适配新结构)
 
     Args:
-        inspiration_point: 点数据
-        feature_status_map: 特征名称到状态的映射 {特征名称: "相同"|"相似"|"无关"}
+        point: 点数据(包含匹配人设结果)
         point_type: 点类型(灵感点/关键点/目的点)
     """
-    name = inspiration_point.get("名称", "")
-    desc = inspiration_point.get("描述", "")
-    features = inspiration_point.get("特征列表", [])
+    name = point.get("名称", "")
+    desc = point.get("描述", "")
+    match_results = point.get("匹配人设结果", [])
 
-    if feature_status_map is None:
-        feature_status_map = {}
+    # 根据最高相似度确定结论
+    max_similarity = 0.0
+    if match_results:
+        max_similarity = max(m.get("相似度", 0) for m in match_results)
 
-    # 计算该灵感点的整体结论
-    feature_statuses = []
-    features_html_list = []
-    for f in features:
-        feature_name = f if isinstance(f, str) else f.get("特征名称", "")
-        weight = f.get("权重", 1.0) if isinstance(f, dict) else 1.0
-
-        # 获取该特征的状态
-        status = feature_status_map.get(feature_name, "无关")
-        feature_statuses.append(status)
-
-        if status == "相同":
-            status_class = "feature-same"
-            status_label = "相同"
-        elif status == "相似":
-            status_class = "feature-similar"
-            status_label = "相似"
-        else:
-            status_class = "feature-unrelated"
-            status_label = "无关"
-
-        features_html_list.append(
-            f'<span class="feature-tag {status_class}">'
-            f'<span class="feature-status-label">{status_label}</span> '
-            f'{html_module.escape(feature_name)}'
-            f'</span>'
-        )
-
-    features_html = "".join(features_html_list)
-
-    # 计算灵感点结论
-    has_same = "相同" in feature_statuses
-    has_similar = "相似" in feature_statuses
-    has_unrelated = "无关" in feature_statuses
-
-    if not has_unrelated:
-        # 没有无关的 -> 找到
-        insp_conclusion = "找到"
-        insp_conclusion_class = "insp-conclusion-found"
-    elif has_same or has_similar:
-        # 有相同或相似,但也有无关 -> 部分找到
-        insp_conclusion = "部分找到"
-        insp_conclusion_class = "insp-conclusion-partial"
-    else:
-        # 都是无关 -> 都找不到
-        insp_conclusion = "都找不到"
-        insp_conclusion_class = "insp-conclusion-not-found"
+    status_label, status_color, status_suffix = get_similarity_status(max_similarity)
+    insp_conclusion = status_label
+    insp_conclusion_class = f"insp-conclusion-{status_suffix}"
 
     # 根据点类型设置图标
     point_icons = {
@@ -291,9 +248,9 @@ def generate_inspiration_detail_html(inspiration_point: Dict, feature_status_map
             <div class="desc-label">描述:</div>
             <div class="desc-text">{html_module.escape(desc)}</div>
         </div>
-        <div class="inspiration-features">
-            <div class="features-label">特征列表:</div>
-            <div class="features-tags">{features_html}</div>
+        <div class="inspiration-stats">
+            <div class="stats-label">最高相似度: <span class="similarity-value">{max_similarity:.2f}</span></div>
+            <div class="stats-label">匹配人设数: <span class="match-count">{len(match_results)}</span></div>
         </div>
     </div>
     '''
@@ -333,34 +290,32 @@ def load_feature_source_mapping(config: PathConfig) -> Dict:
         return {}
 
 
-def generate_single_match_html(match: Dict, match_idx: int, post_idx: int, insp_idx: int, feature_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, current_point_type: str = "灵感点") -> str:
+def generate_single_match_html(match: Dict, match_idx: int, post_idx: int, point_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, point_type: str = "灵感点") -> str:
     """生成单个匹配项的HTML
 
     Args:
         match: 单个匹配数据
         match_idx: 匹配项索引
         post_idx: 帖子索引
-        insp_idx: 灵感点索引
-        feature_idx: 特征索引
+        point_idx: 点索引
         category_mapping: 特征分类映射
         source_mapping: 特征来源映射
-        current_point_type: 当前点的类型(灵感点/关键点/目的点)
+        point_type: 当前点的类型(灵感点/关键点/目的点)
     """
     persona_name = match.get("人设特征名称", "")
     feature_type = match.get("特征类型", "")
     feature_categories = match.get("特征分类", [])
     persona_level = match.get("人设特征层级", "")
-    match_result = match.get("匹配结果", {})
-    similarity = match_result.get("相似度", 0.0)
-    explanation = match_result.get("说明", "")
+    similarity = match.get("相似度", 0.0)
+    explanation = match.get("说明", "")
 
     # 根据相似度确定颜色和标签
     label, color, _ = get_similarity_status(similarity)
 
-    match_id = f"post-{post_idx}-insp-{insp_idx}-feat-{feature_idx}-match-{match_idx}"
+    match_id = f"post-{post_idx}-{point_type}-{point_idx}-match-{match_idx}"
 
     # 判断是否同层级匹配
-    is_same_level = (persona_level == current_point_type)
+    is_same_level = (persona_level == point_type)
     same_level_class = "match-same-level" if is_same_level else ""
 
     # 生成合并的层级-类型标签
@@ -454,43 +409,36 @@ def generate_single_match_html(match: Dict, match_idx: int, post_idx: int, insp_
     return html
 
 
-def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_idx: int, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, current_point_type: str = "灵感点", feature_number: int = 1) -> str:
-    """生成可折叠的匹配结果HTML
+def generate_match_results_html(point: Dict, point_idx: int, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, point_type: str = "灵感点") -> str:
+    """生成可折叠的匹配结果HTML(适配新结构)
 
     Args:
-        how_steps: how步骤列表
-        feature_idx: 特征索引
-        insp_idx: 灵感点索引
+        point: 点数据(包含匹配人设结果)
+        point_idx: 点索引
         post_idx: 帖子索引
         category_mapping: 特征分类映射
         source_mapping: 特征来源映射
-        current_point_type: 当前点的类型(灵感点/关键点/目的点)
-        feature_number: 特征序号(从1开始)
+        point_type: 点类型(灵感点/关键点/目的点)
     """
-    if not how_steps or len(how_steps) == 0:
-        return ""
-
-    step = how_steps[0]
-    features = step.get("特征列表", [])
+    point_name = point.get("名称", "")
+    match_results = point.get("匹配人设结果", [])
 
-    if feature_idx >= len(features):
+    if not match_results:
         return ""
 
-    feature_data = features[feature_idx]
-    feature_name = feature_data.get("特征名称", "")
-    feature_weight = feature_data.get("权重", 1.0)
-    match_results = feature_data.get("匹配结果", [])
-
     if category_mapping is None:
         category_mapping = {}
 
-    # 按相似度排序
-    sorted_matches = sorted(match_results, key=lambda x: x.get("匹配结果", {}).get("相似度", 0), reverse=True)
+    # 用于显示的序号(点索引+1)
+    feature_number = point_idx + 1
+
+    # 匹配结果已经按相似度降序排列(在match脚本中处理过)
+    sorted_matches = match_results
 
     # 找出最高相似度,确定状态
     max_similarity = 0.0
     if match_results:
-        max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
+        max_similarity = max(match.get("相似度", 0) for match in match_results)
 
     # 根据最高相似度确定状态
     status, _, status_suffix = get_similarity_status(max_similarity)
@@ -504,7 +452,7 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
     unrelated_label = f"无关 (<{SIMILARITY_THRESHOLD_SIMILAR})"
     similarity_ranges = {same_label: 0, similar_label: 0, unrelated_label: 0}
     for match in match_results:
-        similarity = match.get("匹配结果", {}).get("相似度", 0)
+        similarity = match.get("相似度", 0)
         status_label, _, _ = get_similarity_status(similarity)
         if status_label == "相同":
             similarity_ranges[same_label] += 1
@@ -553,14 +501,14 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
             continue
 
         # 生成该层级的折叠区域
-        level_section_id = f"post-{post_idx}-{current_point_type}-{insp_idx}-feat-{feature_idx}-level-{level_name}"
+        level_section_id = f"post-{post_idx}-{point_type}-{point_idx}-level-{level_name}"
 
         # 找出该层级的最高分匹配
         all_level_matches = level_data["标签"] + level_data["分类"]
         top_match = None
         max_similarity = 0
         for _, match in all_level_matches:
-            similarity = match.get("匹配结果", {}).get("相似度", 0)
+            similarity = match.get("相似度", 0)
             if similarity > max_similarity:
                 max_similarity = similarity
                 top_match = match
@@ -584,7 +532,7 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
         # 计算该层级的相似度分布
         level_stats = {"相同": 0, "相似": 0, "无关": 0}
         for _, match in all_level_matches:
-            similarity = match.get("匹配结果", {}).get("相似度", 0)
+            similarity = match.get("相似度", 0)
             stat_label, _, _ = get_similarity_status(similarity)
             level_stats[stat_label] += 1
 
@@ -613,21 +561,21 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
         # 该层级下的标签分组
         subgroup_index = 1
         if level_data["标签"]:
-            group_id = f"post-{post_idx}-{current_point_type}-{insp_idx}-feat-{feature_idx}-level-{level_name}-label"
+            group_id = f"post-{post_idx}-{point_type}-{point_idx}-level-{level_name}-label"
             group_matches_html = ""
 
             # 找出标签中的最高分
             tag_top_match = None
             tag_max_similarity = 0
             for i, match in level_data["标签"]:
-                similarity = match.get("匹配结果", {}).get("相似度", 0)
+                similarity = match.get("相似度", 0)
                 if similarity > tag_max_similarity:
                     tag_max_similarity = similarity
                     tag_top_match = match
 
                 match_html = generate_single_match_html(
-                    match, i, post_idx, insp_idx, feature_idx,
-                    category_mapping, source_mapping, current_point_type
+                    match, i, post_idx, point_idx,
+                    category_mapping, source_mapping, point_type
                 )
                 group_matches_html += match_html
 
@@ -669,21 +617,21 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
 
         # 该层级下的分类分组
         if level_data["分类"]:
-            group_id = f"post-{post_idx}-{current_point_type}-{insp_idx}-feat-{feature_idx}-level-{level_name}-category"
+            group_id = f"post-{post_idx}-{point_type}-{point_idx}-level-{level_name}-category"
             group_matches_html = ""
 
             # 找出分类中的最高分
             cat_top_match = None
             cat_max_similarity = 0
             for i, match in level_data["分类"]:
-                similarity = match.get("匹配结果", {}).get("相似度", 0)
+                similarity = match.get("相似度", 0)
                 if similarity > cat_max_similarity:
                     cat_max_similarity = similarity
                     cat_top_match = match
 
                 match_html = generate_single_match_html(
-                    match, i, post_idx, insp_idx, feature_idx,
-                    category_mapping, source_mapping, current_point_type
+                    match, i, post_idx, point_idx,
+                    category_mapping, source_mapping, point_type
                 )
                 group_matches_html += match_html
 
@@ -730,13 +678,13 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
 
         level_index += 1
 
-    section_id = f"post-{post_idx}-insp-{insp_idx}-feat-{feature_idx}-section"
+    section_id = f"post-{post_idx}-{point_type}-{point_idx}-section"
 
     # 找出所有匹配中的最高分
     overall_top_match = None
     overall_max_similarity = 0
     for match in match_results:
-        similarity = match.get("匹配结果", {}).get("相似度", 0)
+        similarity = match.get("相似度", 0)
         if similarity > overall_max_similarity:
             overall_max_similarity = similarity
             overall_top_match = match
@@ -763,7 +711,7 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
         <div class="match-section-header collapsible-header" onclick="toggleFeatureSection(event, '{section_id}')">
             <div class="header-left">
                 <span class="expand-icon" id="{section_id}-icon">▼</span>
-                <h4>{feature_number}. 匹配结果: {html_module.escape(feature_name)}</h4>
+                <h4>{feature_number}. 匹配结果: {html_module.escape(point_name)}</h4>
                 {found_status_html}
             </div>
             <div class="match-stats">{stats_html}</div>
@@ -786,7 +734,7 @@ def generate_toc_html(post_data: Dict, post_idx: int, feature_status_map: Dict[s
         feature_status_map: 特征名称到状态的映射 {特征名称: "相同"|"相似"|"无关"}
         overall_conclusion: 整体结论
     """
-    how_result = post_data.get("how解构结果", {})
+    how_result = post_data.get("解构结果", {})
 
     if feature_status_map is None:
         feature_status_map = {}
@@ -816,68 +764,29 @@ def generate_toc_html(post_data: Dict, post_idx: int, feature_status_map: Dict[s
             name = point.get("名称", f"{point_name} {point_idx + 1}")
             name_short = name[:18] + "..." if len(name) > 18 else name
 
-            # 计算该点的整体状态
-            how_steps = point.get("how步骤列表", [])
-            point_status = "无关"
-            has_same = False
-            has_similar = False
-
-            if how_steps:
-                features = how_steps[0].get("特征列表", [])
-                for feature_data in features:
-                    feature_name = feature_data.get("特征名称", "")
-                    status = feature_status_map.get(feature_name, "无关")
-                    if status == "相同":
-                        has_same = True
-                        break
-                    elif status == "相似":
-                        has_similar = True
-
-                if has_same:
-                    point_status = "找到"
-                    point_status_class = "toc-point-found"
-                elif has_similar:
-                    point_status = "部分找到"
-                    point_status_class = "toc-point-partial"
-                else:
-                    point_status = "都找不到"
-                    point_status_class = "toc-point-notfound"
-            else:
-                point_status_class = "toc-point-notfound"
-
-            toc_items.append(f'<div class="toc-item toc-level-2 {point_status_class}" onclick="scrollToSection(\'post-{post_idx}-point-{list_key}-{point_idx}\')"><span class="toc-badge {badge_class}">{point_name}</span> {html_module.escape(name_short)} <span class="toc-point-status">[{point_status}]</span></div>')
-
-            # 特征列表
-            if how_steps:
-                features = how_steps[0].get("特征列表", [])
-                for feat_idx, feature_data in enumerate(features):
-                    feature_name = feature_data.get("特征名称", f"特征 {feat_idx + 1}")
-
-                    # 获取状态
-                    status = feature_status_map.get(feature_name, "无关")
-                    if status == "相同":
-                        status_class = "toc-feature-same"
-                        status_label = "相同"
-                    elif status == "相似":
-                        status_class = "toc-feature-similar"
-                        status_label = "相似"
-                    else:
-                        status_class = "toc-feature-unrelated"
-                        status_label = "无关"
-
-                    toc_items.append(f'<div class="toc-item toc-level-3 {status_class}" onclick="scrollToSection(\'post-{post_idx}-feat-{point_idx}-{feat_idx}\')"><span class="toc-badge toc-badge-feature">特征</span> {html_module.escape(feature_name)} <span class="toc-feature-status">[{status_label}]</span></div>')
+            # 计算该点的整体状态(根据匹配人设结果的最高相似度)
+            match_results = point.get("匹配人设结果", [])
+            max_similarity = 0.0
+            if match_results:
+                max_similarity = max(m.get("相似度", 0) for m in match_results)
+
+            status_label, _, status_suffix = get_similarity_status(max_similarity)
+            point_status = status_label
+            point_status_class = f"toc-point-{status_suffix}"
+
+            toc_items.append(f'<div class="toc-item toc-level-2 {point_status_class}" onclick="scrollToSection(\'post-{post_idx}-point-{list_key}-{point_idx}\')"><span class="toc-badge {badge_class}">{point_name}</span> {html_module.escape(name_short)} <span class="toc-point-status">[{point_status}]</span> <span class="toc-similarity">({max_similarity:.2f})</span></div>')
 
     # 整体结论HTML
     conclusion_html = ""
     if overall_conclusion:
-        if overall_conclusion == "找到":
-            conclusion_class = "conclusion-found"
+        if overall_conclusion == "相同":
+            conclusion_class = "conclusion-same"
             conclusion_icon = "✓"
-        elif overall_conclusion == "部分找到":
-            conclusion_class = "conclusion-partial"
+        elif overall_conclusion == "相似":
+            conclusion_class = "conclusion-similar"
             conclusion_icon = "~"
-        else:  # 都找不到
-            conclusion_class = "conclusion-not-found"
+        else:  # 无关
+            conclusion_class = "conclusion-unrelated"
             conclusion_icon = "✗"
 
         conclusion_html = f'''
@@ -899,81 +808,49 @@ def generate_toc_html(post_data: Dict, post_idx: int, feature_status_map: Dict[s
 
 
 def generate_post_content_html(post_data: Dict, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None) -> str:
-    """生成单个帖子的完整内容HTML"""
-    # 2. 灵感点详情和匹配结果
-    how_result = post_data.get("how解构结果", {})
-    inspiration_list = how_result.get("灵感点列表", [])
-
-    # 先计算所有特征的状态(基于最高相似度)
-    feature_status_map = {}  # {特征名称: "相同"|"相似"|"无关"}
-    for inspiration_point in inspiration_list:
-        how_steps = inspiration_point.get("how步骤列表", [])
-        if how_steps:
-            features = how_steps[0].get("特征列表", [])
-            for feature_data in features:
-                feature_name = feature_data.get("特征名称", "")
-                match_results = feature_data.get("匹配结果", [])
-
-                # 找出最高相似度
-                max_similarity = 0.0
-                if match_results:
-                    max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
-
-                # 根据最高相似度确定状态
-                status_label, _, _ = get_similarity_status(max_similarity)
-                feature_status_map[feature_name] = status_label
+    """生成单个帖子的完整内容HTML(适配新结构:点直接包含匹配人设结果)"""
+    how_result = post_data.get("解构结果", {})
 
     # 1. 帖子详情
     post_detail_html = generate_post_detail_html(post_data, post_idx)
 
-    # 生成所有灵感点的详情HTML(传入状态映射)
-    inspirations_detail_html = ""
-    for insp_idx, inspiration_point in enumerate(inspiration_list):
-        inspiration_detail = generate_inspiration_detail_html(inspiration_point, feature_status_map)
-        inspirations_detail_html += f'''
-        <div id="post-{post_idx}-insp-{insp_idx}" class="inspiration-detail-item content-section">
-            {inspiration_detail}
-        </div>
-        '''
+    # 2. 生成所有点的详情HTML(灵感点、关键点、目的点)
+    all_points_detail_html = ""
+    point_types = [
+        ("灵感点列表", "灵感点"),
+        ("关键点列表", "关键点"),
+        ("目的点列表", "目的点")
+    ]
+
+    for list_key, point_type in point_types:
+        point_list = how_result.get(list_key, [])
+        for point_idx, point in enumerate(point_list):
+            point_detail = generate_inspiration_detail_html(point, point_type)
+            all_points_detail_html += f'''
+            <div id="post-{post_idx}-{point_type}-{point_idx}" class="inspiration-detail-item content-section">
+                {point_detail}
+            </div>
+            '''
 
-    # 生成所有匹配结果HTML,按照how步骤分组
+    # 3. 生成所有匹配结果HTML
     all_matches_html = ""
-    for insp_idx, inspiration_point in enumerate(inspiration_list):
-        inspiration_name = inspiration_point.get("名称", f"灵感点 {insp_idx + 1}")
-        how_steps = inspiration_point.get("how步骤列表", [])
-
-        if how_steps:
-            # 为每个灵感点创建一个区域
-            for step_idx, step in enumerate(how_steps):
-                step_name = step.get("步骤名称", f"步骤 {step_idx + 1}")
-                features = step.get("特征列表", [])
-
-                # 生成该步骤下所有特征的匹配结果
-                features_html = ""
-                for feat_idx, feature_data in enumerate(features):
-                    match_html = generate_match_results_html([step], feat_idx, insp_idx, post_idx, category_mapping, source_mapping, "灵感点", feat_idx + 1)
-                    features_html += f'<div id="post-{post_idx}-feat-{insp_idx}-{feat_idx}" class="feature-match-wrapper">{match_html}</div>'
-
-                # 生成步骤区域(可折叠)
-                step_section_id = f"post-{post_idx}-step-{insp_idx}-{step_idx}"
+    for list_key, point_type in point_types:
+        point_list = how_result.get(list_key, [])
+        for point_idx, point in enumerate(point_list):
+            match_html = generate_match_results_html(
+                point, point_idx, post_idx,
+                category_mapping, source_mapping, point_type
+            )
+            if match_html:
                 all_matches_html += f'''
-                <div class="step-section">
-                    <div class="step-header collapsible-header" onclick="toggleStepSection('{step_section_id}')">
-                        <div class="header-left">
-                            <span class="expand-icon" id="{step_section_id}-icon">▼</span>
-                            <h3 class="step-name">{html_module.escape(step_name)}</h3>
-                        </div>
-                        <span class="step-inspiration-name">来自: {html_module.escape(inspiration_name)}</span>
-                    </div>
-                    <div class="step-features-list" id="{step_section_id}-content">
-                        {features_html}
-                    </div>
+                <div id="post-{post_idx}-match-{point_type}-{point_idx}" class="point-match-wrapper">
+                    {match_html}
                 </div>
                 '''
 
     html = f'''
     <div class="post-content-wrapper">
-        <!-- 第一个框:左右分栏(帖子详情 + 灵感点详情) -->
+        <!-- 第一个框:左右分栏(帖子详情 + 点详情) -->
         <div class="top-section-box">
             <div class="two-column-layout">
                 <div class="left-column">
@@ -983,7 +860,7 @@ def generate_post_content_html(post_data: Dict, post_idx: int, category_mapping:
                 </div>
                 <div class="right-column">
                     <div class="inspirations-detail-wrapper">
-                        {inspirations_detail_html}
+                        {all_points_detail_html}
                     </div>
                 </div>
             </div>
@@ -1029,25 +906,7 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
         <div class="toc-children hidden" id="toc-post-{post_idx}-children">
         ''')
 
-        # 获取特征状态映射
-        feature_status_map = {}
-        how_result = post.get("how解构结果", {})
-
-        # 先收集所有特征状态
-        for point_list_key in ["灵感点列表", "目的点列表", "关键点列表"]:
-            point_list = how_result.get(point_list_key, [])
-            for point in point_list:
-                how_steps = point.get("how步骤列表", [])
-                if how_steps:
-                    features = how_steps[0].get("特征列表", [])
-                    for feature_data in features:
-                        feature_name = feature_data.get("特征名称", "")
-                        match_results = feature_data.get("匹配结果", [])
-                        max_similarity = 0.0
-                        if match_results:
-                            max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
-                        status_label, _, _ = get_similarity_status(max_similarity)
-                        feature_status_map[feature_name] = status_label
+        how_result = post.get("解构结果", {})
 
         # 生成点类型目录
         point_types = [
@@ -1061,7 +920,7 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
             if not point_list:
                 continue
 
-            # 点类型分组标题(可折叠),去掉"列表"两字
+            # 点类型分组标题(可折叠)
             group_id = f"post-{post_idx}-{list_key}"
             all_toc_items.append(f'''
             <div class="toc-item toc-level-1 toc-group-header collapsed" onclick="toggleTocGroup(event, '{group_id}')">
@@ -1077,87 +936,29 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
                 name = point.get("名称", f"{point_name} {point_idx + 1}")
                 name_short = name[:18] + "..." if len(name) > 18 else name
 
-                # 计算该点的状态
-                how_steps = point.get("how步骤列表", [])
-                point_status = "无关"
-                has_same = False
-                has_similar = False
-
-                if how_steps:
-                    features = how_steps[0].get("特征列表", [])
-                    has_unrelated = False
-                    for feature_data in features:
-                        feature_name = feature_data.get("特征名称", "")
-                        status = feature_status_map.get(feature_name, "无关")
-                        if status == "相同":
-                            has_same = True
-                        elif status == "相似":
-                            has_similar = True
-                        elif status == "无关":
-                            has_unrelated = True
-
-                    # 统一使用与详情页相同的逻辑
-                    if not has_unrelated:
-                        # 没有无关的 -> 找到
-                        point_status = "找到"
-                        point_status_class = "toc-point-found"
-                    elif has_same or has_similar:
-                        # 有相同或相似,但也有无关 -> 部分找到
-                        point_status = "部分找到"
-                        point_status_class = "toc-point-partial"
-                    else:
-                        # 都是无关 -> 都找不到
-                        point_status = "都找不到"
-                        point_status_class = "toc-point-notfound"
-                else:
-                    point_status = "都找不到"
-                    point_status_class = "toc-point-notfound"
-
-                # 点项(可折叠,点击切换到该点的视图)
-                # 确保ID唯一:包含list_key以区分不同类型的点
+                # 计算该点的状态(基于匹配人设结果的最高相似度)
+                match_results = point.get("匹配人设结果", [])
+                max_similarity = 0.0
+                if match_results:
+                    max_similarity = max(m.get("相似度", 0) for m in match_results)
+
+                status_label, _, status_suffix = get_similarity_status(max_similarity)
+                point_status = status_label
+                point_status_class = f"toc-point-{status_suffix}"
+
+                # 点项(点击切换到该点的视图)
                 point_item_id = f"post-{post_idx}-{list_key}-point-{point_idx}"
                 point_view_id = f"view-post-{post_idx}-point-{list_key}-{point_idx}"
                 all_toc_items.append(f'''
-                <div class="toc-item toc-level-2 {point_status_class} collapsed" onclick="toggleTocPoint(event, '{point_item_id}', '{point_view_id}')">
-                    <span class="toc-expand-icon">▼</span>
+                <div class="toc-item toc-level-2 {point_status_class}" onclick="showPointView(event, '{point_view_id}')">
                     <div class="toc-item-content">
                         [{point_name}] {html_module.escape(name_short)}
                         <span class="toc-point-status">[{point_status}]</span>
+                        <span class="toc-similarity-score">{max_similarity:.2f}</span>
                     </div>
                 </div>
-                <div class="toc-children hidden" id="toc-{point_item_id}-children">
                 ''')
 
-                # 特征列表
-                if how_steps:
-                    features = how_steps[0].get("特征列表", [])
-                    for feat_idx, feature_data in enumerate(features):
-                        feature_name = feature_data.get("特征名称", f"特征 {feat_idx + 1}")
-                        status = feature_status_map.get(feature_name, "无关")
-
-                        if status == "相同":
-                            status_class = "toc-feature-same"
-                            status_label = "相同"
-                        elif status == "相似":
-                            status_class = "toc-feature-similar"
-                            status_label = "相似"
-                        else:
-                            status_class = "toc-feature-unrelated"
-                            status_label = "无关"
-
-                        # 特征项(点击直接跳转,不展开子项)
-                        all_toc_items.append(f'''
-                    <div class="toc-item toc-level-3 {status_class}" onclick="clickFeature(event, '{point_view_id}', 'post-{post_idx}-feat-{point_idx}-{feat_idx}')">
-                        <div class="toc-item-content">
-                            [特征] {html_module.escape(feature_name)}
-                            <span class="toc-feature-status">[{status_label}]</span>
-                        </div>
-                    </div>
-                        ''')
-
-                # 关闭点项的children
-                all_toc_items.append('</div>')
-
             # 关闭点类型分组的children
             all_toc_items.append('</div>')
 
@@ -1169,7 +970,7 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
 
     for post_idx, post in enumerate(posts_data):
         post_detail = post.get("帖子详情", {})
-        how_result = post.get("how解构结果", {})
+        how_result = post.get("解构结果", {})
 
         # 1. 生成"仅帖子详情"的视图
         post_detail_html = generate_post_detail_html(post, post_idx)
@@ -1193,51 +994,14 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
             point_type = point_list_key.replace("列表", "")
 
             for point_idx, point in enumerate(point_list):
-                # 计算该点的特征状态映射
-                point_feature_status_map = {}
-                how_steps = point.get("how步骤列表", [])
-                if how_steps:
-                    features = how_steps[0].get("特征列表", [])
-                    for feature_data in features:
-                        feature_name = feature_data.get("特征名称", "")
-                        match_results = feature_data.get("匹配结果", [])
-                        max_similarity = 0.0
-                        if match_results:
-                            max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
-                        status_label, _, _ = get_similarity_status(max_similarity)
-                        point_feature_status_map[feature_name] = status_label
-
-                # 生成点的详情HTML,传入特征状态映射和点类型
-                point_detail_html = generate_inspiration_detail_html(point, point_feature_status_map, point_type)
-
-                # 生成该点的所有特征匹配结果
-                point_name = point.get("名称", f"点 {point_idx + 1}")
-                matches_html = ""
-                if how_steps:
-                    for step_idx, step in enumerate(how_steps):
-                        step_name = step.get("步骤名称", f"步骤 {step_idx + 1}")
-                        features = step.get("特征列表", [])
-
-                        features_html = ""
-                        for feat_idx, feature_data in enumerate(features):
-                            match_html = generate_match_results_html([step], feat_idx, point_idx, post_idx, category_mapping, source_mapping, point_list_key.replace("列表", ""), feat_idx + 1)
-                            features_html += f'<div id="post-{post_idx}-feat-{point_idx}-{feat_idx}" class="feature-match-wrapper">{match_html}</div>'
-
-                        step_section_id = f"post-{post_idx}-step-{point_idx}-{step_idx}"
-                        matches_html += f'''
-                        <div class="step-section">
-                            <div class="step-header collapsible-header" onclick="toggleStepSection('{step_section_id}')">
-                                <div class="header-left">
-                                    <span class="expand-icon" id="{step_section_id}-icon">▼</span>
-                                    <h3 class="step-name">{html_module.escape(step_name)}</h3>
-                                </div>
-                                <span class="step-inspiration-name">来自: {html_module.escape(point_name)}</span>
-                            </div>
-                            <div class="step-features-list" id="{step_section_id}-content">
-                                {features_html}
-                            </div>
-                        </div>
-                        '''
+                # 生成点的详情HTML
+                point_detail_html = generate_inspiration_detail_html(point, point_type)
+
+                # 生成该点的匹配结果
+                matches_html = generate_match_results_html(
+                    point, point_idx, post_idx,
+                    category_mapping, source_mapping, point_type
+                )
 
                 # 组合成完整视图
                 point_view_id = f"view-post-{post_idx}-point-{point_list_key}-{point_idx}"
@@ -3019,6 +2783,20 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
                 children.classList.toggle('hidden');
             }}
 
+            // 直接显示点的内容视图(新结构:点没有子级,直接跳转)
+            function showPointView(event, viewId) {{
+                event.stopPropagation();
+
+                // 切换到该点的视图
+                showContentView(viewId, event.currentTarget);
+
+                // 滚动到右侧内容区域的顶部
+                var rightContent = document.querySelector('.right-content');
+                if (rightContent) {{
+                    rightContent.scrollTo({{ top: 0, behavior: 'smooth' }});
+                }}
+            }}
+
             // 折叠/展开点项(同时切换到该点的内容视图)
             function toggleTocPoint(event, pointItemId, viewId) {{
                 event.stopPropagation();
@@ -3724,7 +3502,7 @@ def main():
     print(f"\n压缩HTML...")
     minified_html = minify_html(html_content)
 
-    minified_file = output_file.parent / "当前帖子_how解构结果_可视化.min.html"
+    minified_file = output_file.parent / "当前帖子_解构结果_可视化.min.html"
     print(f"保存压缩HTML到: {minified_file}")
     with open(minified_file, "w", encoding="utf-8") as f:
         f.write(minified_html)
@@ -3735,7 +3513,7 @@ def main():
     # Gzip压缩
     import gzip
     print(f"\n生成Gzip压缩版本...")
-    gzip_file = output_file.parent / "当前帖子_how解构结果_可视化.html.gz"
+    gzip_file = output_file.parent / "当前帖子_解构结果_可视化.html.gz"
     with gzip.open(gzip_file, "wb") as f:
         f.write(minified_html.encode('utf-8'))