\n'
if elem_id:
html += f'#{elem_id}\n'
html += f'
{html_module.escape(name)}
\n'
html += '\n'
html += '
\n'
html += '
\n'
# 描述
if description:
html += '
\n'
html += '描述:\n'
html += f'
{html_module.escape(description)}
\n'
html += '
\n'
# 分类定义
if category_def:
html += '
\n'
html += '分类定义:\n'
html += f'
{html_module.escape(category_def)}
\n'
html += '
\n'
# 针对"形式"维度,显示"支撑"、"推理"和"支撑关系"
if isinstance(dimension, dict) and dimension.get('一级') == '形式':
# 支撑
zhicheng = element.get('支撑')
if zhicheng:
html += '
\n'
html += '支撑:\n'
html += '
\n'
if isinstance(zhicheng, list):
for item in zhicheng:
if isinstance(item, dict):
item_id = item.get('id', '')
item_name = item.get('名称', '')
html += f'{html_module.escape(f"{item_id}: {item_name}")}\n'
else:
html += f'{html_module.escape(str(item))}\n'
elif isinstance(zhicheng, dict):
# 支撑可能是对象(包含具体元素、具象概念等)
for key, values in zhicheng.items():
if isinstance(values, list):
html += f'
{html_module.escape(key)}
\n'
for item in values:
if isinstance(item, dict):
item_id = item.get('id', '')
item_name = item.get('名称', '')
html += f'{html_module.escape(f"{item_id}: {item_name}")}\n'
else:
html += f'{html_module.escape(str(item))}\n'
else:
html += f'{html_module.escape(str(values))}\n'
else:
html += f'{html_module.escape(str(zhicheng))}\n'
html += '
\n'
html += '
\n'
# 推理
tuili = element.get('推理')
if tuili:
html += '
\n'
html += '推理:\n'
html += f'
{html_module.escape(tuili)}
\n'
html += '
\n'
# 支撑关系(形式元素专用,从意图支撑数据中显示支撑点)
# 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
intent_support_data = get_intent_support_data(element)
if intent_support_data:
html += '
\n'
html += '支撑关系:\n'
for point_type in ['灵感点', '目的点', '关键点']:
support_points = intent_support_data.get(point_type) or []
if not support_points:
continue
html += f'
{point_type}
\n'
html += '
\n'
# 每个项目就是一个支撑点
for support_point in support_points:
if not isinstance(support_point, dict):
continue
point = support_point.get('点', '')
point_intention = support_point.get('点的意图', '')
support_reason = support_point.get('支撑理由', '')
html += '
\n'
html += f'
{html_module.escape(point)}
\n'
# 显示点的意图
if point_intention:
html += '
\n'
html += f'点的意图:{html_module.escape(point_intention)}\n'
html += '
\n'
# 显示支撑理由
if support_reason:
html += '
\n'
html += f'支撑理由:\n'
html += f'
{html_module.escape(support_reason)}
\n'
html += '
\n'
html += '
\n'
html += '
\n' # end score-list
html += '
\n' # end detail-section
# 针对"隐含概念",显示"来源"(声音特征、语气语调、BGM、音效等)和"时间范围"
elem_type = element.get('类型', '')
if elem_type == '隐含概念':
# 来源(隐含概念的来源包含声音特征、语气语调、背景音乐、音效等)
laiyuan = element.get('来源')
if laiyuan and isinstance(laiyuan, dict):
html += '
\n'
html += '来源:\n'
html += '
\n'
for key, values in laiyuan.items():
if isinstance(values, list) and values:
html += f'
{html_module.escape(key)}
\n'
for item in values:
html += f'{html_module.escape(str(item))}\n'
elif values:
html += f'{html_module.escape(str(values))}\n'
html += '
\n'
html += '
\n'
# 时间范围
time_range = element.get('时间范围')
if time_range:
html += '
\n'
html += '时间范围:\n'
html += '
\n'
if isinstance(time_range, list):
for tr in time_range:
html += f'{html_module.escape(str(tr))}\n'
else:
html += f'{html_module.escape(str(time_range))}\n'
html += '
\n'
html += '
\n'
# 针对"抽象概念"(实质-抽象概念),显示"类型"、"来源"和"推理过程"
# 注意:排除隐含概念(隐含概念有自己的显示逻辑)
elif isinstance(dimension, dict) and dimension.get('二级') == '抽象概念':
# 类型
leixing = element.get('类型')
if leixing:
html += '
\n'
html += '类型:\n'
html += f'
{html_module.escape(str(leixing))}
\n'
html += '
\n'
# 来源(抽象概念的来源可能是复杂对象)
laiyuan = element.get('来源')
if laiyuan and isinstance(laiyuan, dict):
html += '
\n'
html += '来源:\n'
html += '
\n'
for key, values in laiyuan.items():
if isinstance(values, list):
html += f'
{html_module.escape(key)}
\n'
for item in values:
if isinstance(item, dict):
item_id = item.get('id', '')
item_name = item.get('名称', '')
html += f'{html_module.escape(f"{item_id}: {item_name}")}\n'
else:
html += f'{html_module.escape(str(item))}\n'
else:
html += f'{html_module.escape(str(values))}\n'
html += '
\n'
html += '
\n'
# 推理过程
tuili_guocheng = element.get('推理过程')
if tuili_guocheng:
html += '
\n'
html += '推理过程:\n'
html += f'
{html_module.escape(tuili_guocheng)}
\n'
html += '
\n'
else:
if source:
html += '
\n'
html += '来源:\n'
html += '
\n'
for src in source:
html += f'{html_module.escape(str(src))}\n'
html += '
\n'
html += '
\n'
# 上下文验证(适用于具象概念)
context_verification = element.get('上下文验证')
if context_verification:
html += '
\n'
html += '上下文验证:\n'
html += '
\n'
# 原文位置
original_position = context_verification.get('原文位置', '')
if original_position:
html += '
\n'
html += '原文位置:\n'
html += f'
{html_module.escape(original_position)}
\n'
html += '
\n'
# 语法成分
grammar_component = context_verification.get('语法成分', '')
if grammar_component:
html += '
\n'
html += '语法成分:\n'
html += f'{html_module.escape(grammar_component)}\n'
html += '
\n'
# 语境判断
context_judgment = context_verification.get('语境判断', '')
if context_judgment:
html += '
\n'
html += '语境判断:\n'
html += f'
{html_module.escape(context_judgment)}
\n'
html += '
\n'
html += '
\n'
html += '
\n'
# 出现段落
if paragraphs_list:
html += '
\n'
html += '出现段落:\n'
html += '
\n'
for para in paragraphs_list:
if isinstance(para, dict):
# 新结构:对象包含段落ID和如何体现
para_id = para.get('段落ID', '')
how = para.get('如何体现', '')
html += '
\n'
html += f'{html_module.escape(para_id)}\n'
if how:
html += f'
{html_module.escape(how)}
\n'
html += '
\n'
else:
# 旧结构:字符串
html += f'{html_module.escape(str(para))}\n'
html += '
\n'
html += '
\n'
# 意图支撑
if intent_support:
html += '
\n'
html += '意图支撑:\n'
for point_type in ['灵感点', '目的点', '关键点']:
if point_type in intent_support and intent_support[point_type]:
html += f'
{point_type}
\n'
html += '
\n'
for item in intent_support[point_type]:
point = item.get('点', '')
point_intention = item.get('点的意图', '')
support_reason = item.get('支撑理由', '')
html += '
\n'
html += f'
{html_module.escape(point)}
\n'
# 显示点的意图
if point_intention:
html += '
\n'
html += f'点的意图:{html_module.escape(point_intention)}\n'
html += '
\n'
# 显示支撑理由
if support_reason:
html += '
\n'
html += f'支撑理由:\n'
html += f'
{html_module.escape(support_reason)}
\n'
html += '
\n'
html += '
\n'
html += '
\n'
html += '
\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 += '
\n'
html += '权重得分明细:\n'
html += '
\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 += '
\n'
html += '
\n'
# 共性维度得分
if is_form:
# 形式元素:显示频次分、覆盖段落数分、覆盖率分
html += f'频次分 {wd.get("频次分", 0):.1f}\n'
if "覆盖段落数分" in wd:
html += f'覆盖段落数分 {wd.get("覆盖段落数分", 0):.1f}\n'
html += f'覆盖率分 {wd.get("覆盖率分", 0):.1f}\n'
else:
# 实质元素:显示频次分、覆盖率分
html += f'频次分 {wd.get("频次分", 0):.1f} / 30\n'
html += f'覆盖率分 {wd.get("覆盖率分", 0):.1f} / 30\n'
# 支撑维度得分
inspiration_count = ss.get('灵感点数量', 0)
if is_form:
html += f'灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f}({inspiration_count} 个)\n'
else:
html += f'灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f} / 25({inspiration_count} 个)\n'
purpose_count = ss.get('目的点数量', 0)
if is_form:
html += f'目的点支撑分 {wd.get("目的点支撑分", 0):.1f}({purpose_count} 个)\n'
else:
html += f'目的点支撑分 {wd.get("目的点支撑分", 0):.1f} / 15({purpose_count} 个)\n'
keypoint_count = ss.get('关键点数量', 0)
if is_form:
html += f'关键点支撑分 {wd.get("关键点支撑分", 0):.1f}({keypoint_count} 个)\n'
else:
html += f'关键点支撑分 {wd.get("关键点支撑分", 0):.1f} / 10({keypoint_count} 个)\n'
html += '
\n'
html += '
\n'
html += '
\n'
html += '\n'
return html
def render_element_modal(element: Dict[str, Any]) -> str:
"""渲染元素详情的弹窗内容
Args:
element: 元素数据
"""
elem_id = element.get('id', '')
name = element.get('名称') or ''
description = element.get('描述') or ''
# 获取类型和维度
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('分类', '')
category_def = element.get('分类定义', '')
# 获取共性分析
commonality = element.get('共性分析') or {}
coverage = commonality.get('段落覆盖率', 0.0)
frequency = commonality.get('出现频次', 0)
paragraphs_list = commonality.get('出现段落列表', [])
source = element.get('来源', [])
intent_support = get_intent_support_data(element)
weight_info = compute_weight_scores(element)
html = f'
\n'
html += '
\n'
if elem_id:
html += f'#{elem_id}\n'
html += f'
{html_module.escape(name)}
\n'
html += '\n'
html += '
\n'
html += '
\n'
# 描述
if description:
html += '
\n'
html += '描述:\n'
html += f'
{html_module.escape(description)}
\n'
html += '
\n'
# 分类定义
if category_def:
html += '
\n'
html += '分类定义:\n'
html += f'
{html_module.escape(category_def)}
\n'
html += '
\n'
# 针对"形式"维度,显示"支撑"、"推理"和"支撑关系"
if isinstance(dimension, dict) and dimension.get('一级') == '形式':
# 支撑
zhicheng = element.get('支撑')
if zhicheng:
html += '
\n'
html += '支撑:\n'
html += '
\n'
if isinstance(zhicheng, list):
for item in zhicheng:
if isinstance(item, dict):
item_id = item.get('id', '')
item_name = item.get('名称', '')
html += f'{html_module.escape(f"{item_id}: {item_name}")}\n'
else:
html += f'{html_module.escape(str(item))}\n'
elif isinstance(zhicheng, dict):
for key, values in zhicheng.items():
if isinstance(values, list):
html += f'
{html_module.escape(key)}
\n'
for item in values:
if isinstance(item, dict):
item_id = item.get('id', '')
item_name = item.get('名称', '')
html += f'{html_module.escape(f"{item_id}: {item_name}")}\n'
else:
html += f'{html_module.escape(str(item))}\n'
else:
html += f'{html_module.escape(str(values))}\n'
else:
html += f'{html_module.escape(str(zhicheng))}\n'
html += '
\n'
html += '
\n'
# 推理
tuili = element.get('推理')
if tuili:
html += '
\n'
html += '推理:\n'
html += f'
{html_module.escape(tuili)}
\n'
html += '
\n'
# 支撑关系
intent_support_data = get_intent_support_data(element)
if intent_support_data:
html += '
\n'
html += '支撑关系:\n'
for point_type in ['灵感点', '目的点', '关键点']:
support_points = intent_support_data.get(point_type) or []
if not support_points:
continue
html += f'
{point_type}
\n'
html += '
\n'
for support_point in support_points:
if not isinstance(support_point, dict):
continue
point = support_point.get('点', '')
point_intention = support_point.get('点的意图', '')
support_reason = support_point.get('支撑理由', '')
html += '
\n'
html += f'
{html_module.escape(point)}
\n'
if point_intention:
html += '
\n'
html += f'点的意图:{html_module.escape(point_intention)}\n'
html += '
\n'
if support_reason:
html += '
\n'
html += f'支撑理由:\n'
html += f'
{html_module.escape(support_reason)}
\n'
html += '
\n'
html += '
\n'
html += '
\n'
html += '
\n'
# 针对"隐含概念"
elem_type = element.get('类型', '')
if elem_type == '隐含概念':
laiyuan = element.get('来源')
if laiyuan and isinstance(laiyuan, dict):
html += '
\n'
html += '来源:\n'
html += '
\n'
for key, values in laiyuan.items():
if isinstance(values, list) and values:
html += f'
{html_module.escape(key)}
\n'
for item in values:
html += f'{html_module.escape(str(item))}\n'
elif values:
html += f'{html_module.escape(str(values))}\n'
html += '
\n'
html += '
\n'
time_range = element.get('时间范围')
if time_range:
html += '
\n'
html += '时间范围:\n'
html += '
\n'
if isinstance(time_range, list):
for tr in time_range:
html += f'{html_module.escape(str(tr))}\n'
else:
html += f'{html_module.escape(str(time_range))}\n'
html += '
\n'
html += '
\n'
# 针对"抽象概念"
elif isinstance(dimension, dict) and dimension.get('二级') == '抽象概念':
leixing = element.get('类型')
if leixing:
html += '
\n'
html += '类型:\n'
html += f'
{html_module.escape(str(leixing))}
\n'
html += '
\n'
laiyuan = element.get('来源')
if laiyuan and isinstance(laiyuan, dict):
html += '
\n'
html += '来源:\n'
html += '
\n'
for key, values in laiyuan.items():
if isinstance(values, list):
html += f'
{html_module.escape(key)}
\n'
for item in values:
if isinstance(item, dict):
item_id = item.get('id', '')
item_name = item.get('名称', '')
html += f'{html_module.escape(f"{item_id}: {item_name}")}\n'
else:
html += f'{html_module.escape(str(item))}\n'
else:
html += f'{html_module.escape(str(values))}\n'
html += '
\n'
html += '
\n'
tuili_guocheng = element.get('推理过程')
if tuili_guocheng:
html += '
\n'
html += '推理过程:\n'
html += f'
{html_module.escape(tuili_guocheng)}
\n'
html += '
\n'
else:
if source:
html += '
\n'
html += '来源:\n'
html += '
\n'
for src in source:
html += f'{html_module.escape(str(src))}\n'
html += '
\n'
html += '
\n'
# 上下文验证
context_verification = element.get('上下文验证')
if context_verification:
html += '
\n'
html += '上下文验证:\n'
html += '
\n'
original_position = context_verification.get('原文位置', '')
if original_position:
html += '
\n'
html += '原文位置:\n'
html += f'
{html_module.escape(original_position)}
\n'
html += '
\n'
grammar_component = context_verification.get('语法成分', '')
if grammar_component:
html += '
\n'
html += '语法成分:\n'
html += f'{html_module.escape(grammar_component)}\n'
html += '
\n'
context_judgment = context_verification.get('语境判断', '')
if context_judgment:
html += '
\n'
html += '语境判断:\n'
html += f'
{html_module.escape(context_judgment)}
\n'
html += '
\n'
html += '
\n'
html += '
\n'
# 出现段落
if paragraphs_list:
html += '
\n'
html += '出现段落:\n'
html += '
\n'
for para in paragraphs_list:
if isinstance(para, dict):
para_id = para.get('段落ID', '')
how = para.get('如何体现', '')
html += '
\n'
html += f'{html_module.escape(para_id)}\n'
if how:
html += f'
{html_module.escape(how)}
\n'
html += '
\n'
else:
html += f'{html_module.escape(str(para))}\n'
html += '
\n'
html += '
\n'
# 意图支撑
if intent_support:
html += '
\n'
html += '意图支撑:\n'
for point_type in ['灵感点', '目的点', '关键点']:
if point_type in intent_support and intent_support[point_type]:
html += f'
{point_type}
\n'
html += '
\n'
for item in intent_support[point_type]:
point = item.get('点', '')
point_intention = item.get('点的意图', '')
support_reason = item.get('支撑理由', '')
html += '
\n'
html += f'
{html_module.escape(point)}
\n'
if point_intention:
html += '
\n'
html += f'点的意图:{html_module.escape(point_intention)}\n'
html += '
\n'
if support_reason:
html += '
\n'
html += f'支撑理由:\n'
html += f'
{html_module.escape(support_reason)}
\n'
html += '
\n'
html += '
\n'
html += '
\n'
html += '
\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 += '
\n'
html += '权重得分明细:\n'
html += '
\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 += '
\n'
html += '
\n'
if is_form:
html += f'频次分 {wd.get("频次分", 0):.1f}\n'
if "覆盖段落数分" in wd:
html += f'覆盖段落数分 {wd.get("覆盖段落数分", 0):.1f}\n'
html += f'覆盖率分 {wd.get("覆盖率分", 0):.1f}\n'
else:
html += f'频次分 {wd.get("频次分", 0):.1f} / 30\n'
html += f'覆盖率分 {wd.get("覆盖率分", 0):.1f} / 30\n'
inspiration_count = ss.get('灵感点数量', 0)
if is_form:
html += f'灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f}({inspiration_count} 个)\n'
else:
html += f'灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f} / 25({inspiration_count} 个)\n'
purpose_count = ss.get('目的点数量', 0)
if is_form:
html += f'目的点支撑分 {wd.get("目的点支撑分", 0):.1f}({purpose_count} 个)\n'
else:
html += f'目的点支撑分 {wd.get("目的点支撑分", 0):.1f} / 15({purpose_count} 个)\n'
keypoint_count = ss.get('关键点数量', 0)
if is_form:
html += f'关键点支撑分 {wd.get("关键点支撑分", 0):.1f}({keypoint_count} 个)\n'
else:
html += f'关键点支撑分 {wd.get("关键点支撑分", 0):.1f} / 10({keypoint_count} 个)\n'
html += '
\n'
html += '
\n'
html += '
\n' # modal-body
html += '
\n' # element-modal-content
return html
def generate_tab3_content(data: Dict[str, Any]) -> str:
"""生成Tab3内容:按层次展示(实质/形式 → 具体元素/具体概念/抽象概念 → 树形展示)"""
html = '
\n'
# 添加全局控制按钮(移除展开/收起按钮)
html += '
\n'
html += '
\n'
html += ' 颜色图例(主导因素):\n'
html += ' 覆盖率\n'
html += ' 频次\n'
html += ' 意图支撑\n'
html += '
\n'
html += '
\n'
# 收集所有元素用于弹窗
all_elements_dict = {}
if '脚本理解' in data:
script = data['脚本理解']
# 尝试获取元素列表,如果不存在则合并实质列表和形式列表
elements = script.get('元素列表', [])
if not elements:
substance_list = script.get('实质列表', [])
form_list = script.get('形式列表', [])
elements = substance_list + form_list
# 收集所有元素到字典中(用于弹窗)
for elem in elements:
elem_id = elem.get('id', '')
if elem_id:
all_elements_dict[elem_id] = elem
# 第一层:按维度.一级分组(实质 vs 形式)
level1_groups = {}
for elem in elements:
dimension = elem.get('维度', {})
if isinstance(dimension, dict):
level1 = dimension.get('一级', '实质')
else:
# 兼容旧结构
level1 = elem.get('类型', '实质')
if level1 not in level1_groups:
level1_groups[level1] = []
level1_groups[level1].append(elem)
# 按顺序渲染:实质、形式
for level1_name in ['实质', '形式']:
if level1_name not in level1_groups:
continue
level1_elements = level1_groups[level1_name]
html += '
\n'
html += f'
\n'
html += '▼\n'
html += f'
{level1_name} ({len(level1_elements)}个)
\n'
html += '
\n'
html += '
\n'
# 第二层:按维度.二级分组
level2_groups = {}
for elem in level1_elements:
dimension = elem.get('维度', {})
elem_type = elem.get('类型', '')
# 隐含概念:优先通过类型判断(因为维度二级可能是"隐含概念"或"抽象概念")
if elem_type == '隐含概念':
level2 = '隐含概念'
elif isinstance(dimension, dict):
level2 = dimension.get('二级', '具体元素')
else:
# 兼容旧结构
elem_type_old = elem.get('类型', '实质')
if elem_type_old == '实质':
level2 = '具体元素'
elif elem_type_old == '具象概念':
level2 = '具体概念'
else:
level2 = '抽象概念'
if level2 not in level2_groups:
level2_groups[level2] = []
level2_groups[level2].append(elem)
# 根据一级维度确定二级维度遍历顺序
if level1_name == '实质':
level2_order = ['具体元素', '具象概念', '隐含概念', '抽象概念']
else: # 形式
level2_order = ['具体元素形式', '具象概念形式', '整体形式']
# 按顺序渲染二级维度
for level2_name in level2_order:
if level2_name not in level2_groups:
continue
level2_elements = level2_groups[level2_name]
html += '
\n'
html += f'
\n'
html += '▼\n'
html += f'
{level2_name} ({len(level2_elements)}个)
\n'
html += '
\n'
html += '
\n'
# 第三层:按树形分类结构组织
hierarchical_categories = group_elements_by_hierarchical_category(level2_elements)
# 渲染树形分类结构
for cat_level1_name, cat_level1_data in hierarchical_categories.items():
# 收集该一级分类下的所有元素
all_cat_elements = cat_level1_data['elements'][:]
for level2_elems in cat_level1_data['level2_groups'].values():
all_cat_elements.extend(level2_elems)
if not all_cat_elements:
continue
# 计算一级分类的统计信息
avg_coverage = sum(get_element_coverage(e) for e in all_cat_elements) / len(all_cat_elements)
avg_intent_count = sum(calculate_intent_support_count(e) for e in all_cat_elements) / len(all_cat_elements)
html += '
\n'
html += f'平均覆盖率: {avg_coverage:.2%}\n'
html += f'平均意图支撑: {avg_intent_count:.1f}\n'
html += '
\n'
html += '
\n'
html += '
\n'
# 渲染一级分类直接包含的元素
if cat_level1_data['elements']:
html += '
\n'
for elem in cat_level1_data['elements']:
html += render_element_item(elem, all_cat_elements)
html += '
\n'
# 渲染二级分类
for cat_level2_name, cat_level2_elements in cat_level1_data['level2_groups'].items():
if not cat_level2_elements:
continue
# 计算二级分类的统计信息
avg_coverage_l2 = sum(get_element_coverage(e) for e in cat_level2_elements) / len(cat_level2_elements)
avg_intent_count_l2 = sum(calculate_intent_support_count(e) for e in cat_level2_elements) / len(cat_level2_elements)
html += '