#!/usr/bin/env python3 """ Tab3内容生成器 - 脚本点(元素列表) """ import html as html_module from typing import Dict, Any, List def calculate_intent_support_count(element: Dict[str, Any]) -> int: """ 计算元素的意图支撑数量 统计元素支撑的意图点总数 支撑的意图点越多,说明该元素与意图的关联越强 Args: element: 元素数据 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 similarity_results = item.get('相似度结果') # 新结构:在相似度结果列表里取最大值 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 else: # 实质元素:按意图支撑的数量统计 intent_support = element.get('意图支撑') or {} 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]) return total_support_count def determine_dominant_factor(element: Dict[str, Any], all_elements: List[Dict[str, Any]]) -> str: """ 判断元素排序的主导因素 排序规则:覆盖率 > 频次 > 意图支撑数 主导因素判断:哪个指标在当前元素中相对最显著 Args: element: 当前元素 all_elements: 同组所有元素 Returns: 主导因素: 'coverage' | 'frequency' | 'intent_support' """ if not all_elements: return 'coverage' # 获取当前元素的指标 commonality = element.get('共性分析') or {} coverage = commonality.get('段落覆盖率', 0.0) frequency = commonality.get('出现频次', 0) intent_count = calculate_intent_support_count(element) # 将所有指标归一化到同一量级,然后比较 # 覆盖率已经是0-1范围 # 频次归一化:假设最大频次为10 normalized_frequency = min(frequency / 10.0, 1.0) # 意图支撑数归一化:假设最大支撑数为10 normalized_intent = min(intent_count / 10.0, 1.0) # 比较归一化后的值,取最大的作为主导因素 scores = { 'coverage': coverage, 'frequency': normalized_frequency, 'intent_support': normalized_intent } return max(scores, key=scores.get) def sort_elements_by_coverage_and_frequency(elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ 按照覆盖率、频次和意图支撑数对元素排序 排序规则: 1. 第一优先级:共性(段落覆盖率)- 倒序 2. 第二优先级:共性(出现频次)- 倒序 3. 第三优先级:意图支撑数 - 倒序 Args: elements: 元素列表 Returns: 排序后的元素列表 """ def get_sort_key(elem): # 获取共性分析,防止为None commonality = elem.get('共性分析') or {} # 获取段落覆盖率 coverage = commonality.get('段落覆盖率', 0.0) # 获取出现频次 frequency = commonality.get('出现频次', 0) # 计算意图支撑数 intent_count = calculate_intent_support_count(elem) # 返回排序键(负数用于倒序) return (-coverage, -frequency, -intent_count) return sorted(elements, key=get_sort_key) def get_element_category(element: Dict[str, Any]) -> str: """ 获取元素的分类名称(支持新旧两种数据结构) Args: element: 元素数据 Returns: 分类名称字符串 """ category = element.get('分类', '未分类') if isinstance(category, dict): # 新结构:分类是对象,包含一级分类和二级分类 level1 = category.get('一级分类', '') level2 = category.get('二级分类', '') if level1 and level2: return f"{level1} - {level2}" elif level1: return level1 else: return '未分类' else: # 旧结构:分类是字符串或列表 if isinstance(category, list): return ' - '.join(category) if category else '未分类' return category if category else '未分类' def group_elements_by_hierarchical_category(elements: List[Dict[str, Any]]) -> Dict[str, Any]: """ 按树形分类结构组织元素(一级分类 → 二级分类 → 元素) 优化规则:同一个父节点下的所有子节点采用统一的分类格式展示 - 如果一级分类下既有元素又有分类,将元素归入"未分类"二级分类 Args: elements: 元素列表 Returns: 树形分类结构字典 """ # 1. 按一级分类和二级分类分组 level1_groups = {} for elem in elements: category_data = elem.get('分类', {}) if isinstance(category_data, dict): level1 = category_data.get('一级分类', '未分类') level2 = category_data.get('二级分类', '') elif isinstance(category_data, list): # 列表格式:第一个元素作为一级分类,第二个作为二级分类 level1 = category_data[0] if len(category_data) > 0 else '未分类' level2 = category_data[1] if len(category_data) > 1 else '' else: # 旧结构:分类是字符串 level1 = str(category_data) if category_data else '未分类' level2 = '' # 初始化一级分类 if level1 not in level1_groups: level1_groups[level1] = { 'elements': [], 'level2_groups': {} } # 如果有二级分类,放入二级分类组;否则放入一级分类的直接元素列表(临时) if level2: if level2 not in level1_groups[level1]['level2_groups']: level1_groups[level1]['level2_groups'][level2] = [] level1_groups[level1]['level2_groups'][level2].append(elem) else: level1_groups[level1]['elements'].append(elem) # 1.5 优化:仅当一级分类下既有直接元素又有二级分类时,才将直接元素移到"未分类"二级分类中 # 如果一级分类下只有直接元素,没有二级分类,则保持原样(不需要"未分类"概念) for level1_name, level1_data in level1_groups.items(): if level1_data['elements'] and level1_data['level2_groups']: # 将直接元素移到"未分类"分类 if '未分类' not in level1_data['level2_groups']: level1_data['level2_groups']['未分类'] = [] level1_data['level2_groups']['未分类'].extend(level1_data['elements']) level1_data['elements'] = [] # 如果只有直接元素,没有二级分类,则保持level1_data['elements']不变 # 2. 对每个分类内的元素排序 for level1_data in level1_groups.values(): # 排序一级分类直接包含的元素 if level1_data['elements']: level1_data['elements'] = sort_elements_by_coverage_and_frequency(level1_data['elements']) # 排序每个二级分类的元素 for level2_name in level1_data['level2_groups']: level1_data['level2_groups'][level2_name] = sort_elements_by_coverage_and_frequency( level1_data['level2_groups'][level2_name] ) # 3. 计算每个一级分类的统计信息用于排序 level1_scores = {} for level1_name, level1_data in level1_groups.items(): # 收集该一级分类下的所有元素(包括二级分类下的) all_elements = level1_data['elements'][:] for level2_elements in level1_data['level2_groups'].values(): all_elements.extend(level2_elements) if not all_elements: level1_scores[level1_name] = (0.0, 0, 0.0) continue # 计算统计指标 avg_coverage = sum((e.get('共性分析') or {}).get('段落覆盖率', 0.0) for e in all_elements) / len(all_elements) avg_frequency = sum((e.get('共性分析') or {}).get('出现频次', 0) for e in all_elements) / len(all_elements) avg_intent_count = sum(calculate_intent_support_count(e) for e in all_elements) / len(all_elements) level1_scores[level1_name] = (avg_coverage, avg_frequency, avg_intent_count) # 4. 对一级分类排序 sorted_level1 = sorted( level1_scores.keys(), key=lambda c: (-level1_scores[c][0], -level1_scores[c][1], -level1_scores[c][2]) ) # 5. 对每个一级分类内的二级分类排序 for level1_name in sorted_level1: level1_data = level1_groups[level1_name] level2_groups = level1_data['level2_groups'] if not level2_groups: continue # 计算二级分类的统计信息 level2_scores = {} for level2_name, level2_elements in level2_groups.items(): if not level2_elements: level2_scores[level2_name] = (0.0, 0, 0.0) continue avg_coverage = sum((e.get('共性分析') or {}).get('段落覆盖率', 0.0) for e in level2_elements) / len(level2_elements) avg_frequency = sum((e.get('共性分析') or {}).get('出现频次', 0) for e in level2_elements) / len(level2_elements) avg_intent_count = sum(calculate_intent_support_count(e) for e in level2_elements) / len(level2_elements) level2_scores[level2_name] = (avg_coverage, avg_frequency, avg_intent_count) # 排序二级分类 sorted_level2_names = sorted( level2_scores.keys(), key=lambda c: (-level2_scores[c][0], -level2_scores[c][1], -level2_scores[c][2]) ) # 重新组织为有序字典 sorted_level2_groups = {name: level2_groups[name] for name in sorted_level2_names} level1_data['level2_groups'] = sorted_level2_groups # 6. 返回排序后的结构 return {level1_name: level1_groups[level1_name] for level1_name in sorted_level1} def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, Any]] = None) -> str: """渲染单个元素项的HTML(支持详情展开,兼容新旧数据结构) Args: element: 元素数据 all_elements: 同组所有元素(用于计算主导因素) """ elem_id = element.get('id', '') name = element.get('名称') or '' # 处理None的情况 description = element.get('描述') or '' # 处理None的情况 # 获取类型和维度(兼容新旧结构) dimension = element.get('维度', {}) if isinstance(dimension, dict): elem_type = dimension.get('一级', '') elem_type_level2 = dimension.get('二级', '') else: elem_type = element.get('类型', '') elem_type_level2 = '' # 获取分类(兼容新旧结构) category_data = element.get('分类', '') if isinstance(category_data, dict): category_level1 = category_data.get('一级分类', '') category_level2 = category_data.get('二级分类', '') category = get_element_category(element) elif isinstance(category_data, list): category_level1 = category_data[0] if len(category_data) > 0 else '' category_level2 = category_data[1] if len(category_data) > 1 else '' category = get_element_category(element) else: category = category_data category_level1 = '' category_level2 = '' category_def = element.get('分类定义', '') # 获取共性分析(防止为None) commonality = element.get('共性分析') or {} coverage = commonality.get('段落覆盖率', 0.0) frequency = commonality.get('出现频次', 0) paragraphs_list = commonality.get('出现段落列表', []) source = element.get('来源', []) intent_count = calculate_intent_support_count(element) intent_support = element.get('意图支撑', {}) # 检查是否有详细信息 has_details = bool(elem_type or category or category_def or paragraphs_list or source or intent_support) # 计算主导因素 dominant_factor = 'coverage' # 默认 if all_elements: dominant_factor = determine_dominant_factor(element, all_elements) # 根据主导因素确定边框颜色 border_color_class = f'dominant-{dominant_factor}' html = f'
  • \n' html += '
    \n' # 添加展开/收起图标 if has_details: html += '\n' # 显示ID和名称 if elem_id: html += f'#{elem_id}\n' html += f'{html_module.escape(name)}\n' # 显示统计指标(根据主导因素高亮) # 判断是否为形式元素 is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式' html += '
    \n' if is_form: # 形式元素:显示最高相似度(基于多维度评分) html += f'最高相似度: {intent_count:.2f}\n' else: # 实质元素显示全部三个指标 coverage_highlight = 'stat-highlight' if dominant_factor == 'coverage' else '' frequency_highlight = 'stat-highlight' if dominant_factor == 'frequency' else '' intent_highlight = 'stat-highlight' if dominant_factor == 'intent_support' else '' html += f'覆盖率: {coverage:.2%}\n' html += f'频次: {frequency}\n' html += f'意图支撑: {intent_count}\n' html += '
    \n' html += '
    \n' # 描述(始终显示) if description: html += f'
    {html_module.escape(description)}
    \n' # 详细信息(可展开) if has_details: html += '\n' html += '
  • \n' return html def generate_tab3_content(data: Dict[str, Any]) -> str: """生成Tab3内容:按层次展示(实质/形式 → 具体元素/具体概念/抽象概念 → 树形展示)""" html = '\n' return html