tab5.py 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. #!/usr/bin/env python3
  2. """
  3. Tab5内容生成器 - 实质与形式的双向支撑关系图
  4. 展示:选题点(来自实质) ← 实质点 → 形式点 → 选题点(来自形式)
  5. """
  6. import html as html_module
  7. import json
  8. from typing import Dict, Any, List
  9. def get_intent_support_data(element: Dict[str, Any]) -> Dict[str, Any]:
  10. """
  11. 获取元素的意图支撑数据(兼容新旧数据结构)
  12. 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
  13. 这两个字段内部存储的都是意图支撑数据
  14. Args:
  15. element: 元素数据
  16. Returns:
  17. 意图支撑数据字典,格式:{"灵感点": [...], "目的点": [...], "关键点": [...]}
  18. """
  19. # 优先使用"意图支撑"字段
  20. intent_support = element.get('意图支撑')
  21. if intent_support and isinstance(intent_support, dict):
  22. return intent_support
  23. # 如果没有"意图支撑",则使用"多维度评分"字段(兼容旧数据)
  24. multi_scores = element.get('多维度评分')
  25. if multi_scores and isinstance(multi_scores, dict):
  26. return multi_scores
  27. # 都没有则返回空字典
  28. return {}
  29. def generate_tab5_content(data: Dict[str, Any]) -> str:
  30. """生成Tab5内容:实质与形式的双向支撑关系图(4列布局)"""
  31. html = '<div class="tab-content" id="tab5">\n'
  32. # 提取数据
  33. script_data = data.get('脚本理解', {})
  34. form_list = script_data.get('形式列表', [])
  35. substance_list = script_data.get('实质列表', [])
  36. inspiration_points = data.get('灵感点', [])
  37. purpose_points = data.get('目的点', [])
  38. key_points = data.get('关键点', [])
  39. if not substance_list and not form_list:
  40. html += '<div class="empty-state">暂无实质点和形式点数据</div>\n'
  41. html += '</div>\n'
  42. return html
  43. # 分类实质点
  44. concrete_elements = []
  45. concrete_concepts = []
  46. implicit_concepts = []
  47. abstract_concepts = []
  48. for substance in substance_list:
  49. dimension_2 = substance.get('维度', {}).get('二级', '')
  50. elem_type = substance.get('类型', '')
  51. # 隐含概念:优先通过类型判断(因为维度二级可能是"隐含概念"或"抽象概念")
  52. if elem_type == '隐含概念':
  53. implicit_concepts.append(substance)
  54. elif dimension_2 == '具体元素':
  55. concrete_elements.append(substance)
  56. elif dimension_2 == '具象概念': # 修改:从"具体概念"改为"具象概念"
  57. concrete_concepts.append(substance)
  58. elif dimension_2 == '抽象概念':
  59. abstract_concepts.append(substance)
  60. # 分类形式点
  61. concrete_element_forms = []
  62. concrete_concept_forms = []
  63. overall_forms = []
  64. for form in form_list:
  65. dimension_2 = form.get('维度', {}).get('二级', '')
  66. if dimension_2 == '具体元素形式':
  67. concrete_element_forms.append(form)
  68. elif dimension_2 == '具象概念形式': # 修改:从"具体概念形式"改为"具象概念形式"
  69. concrete_concept_forms.append(form)
  70. elif dimension_2 == '整体形式':
  71. overall_forms.append(form)
  72. # 构建关系数据
  73. relationships = build_bidirectional_relationships(
  74. concrete_elements, concrete_concepts, implicit_concepts, abstract_concepts,
  75. concrete_element_forms, concrete_concept_forms, overall_forms,
  76. inspiration_points, purpose_points, key_points
  77. )
  78. # 添加标题和说明
  79. html += '<div class="tab5-header">\n'
  80. html += '<h2 class="tab5-title">实质与形式的双向支撑关系图</h2>\n'
  81. html += '<div class="tab5-description">\n'
  82. html += '<p>4列展示:<strong>选题点(实质支撑)</strong> ← <strong>实质点</strong> | <strong>形式点</strong> → <strong>选题点(形式支撑)</strong></p>\n'
  83. html += '<ul class="relationship-rules">\n'
  84. html += '<li>左侧选题点:显示实质点对灵感点、关键点、目的点的支撑关系</li>\n'
  85. html += '<li>右侧选题点:显示形式点对灵感点、关键点、目的点的支撑关系</li>\n'
  86. html += '</ul>\n'
  87. html += '</div>\n'
  88. html += '</div>\n'
  89. # SVG 连线容器(放在4列布局之前,作为背景层)
  90. html += '<div class="tab5-svg-container">\n'
  91. html += '<svg id="tab5-connection-svg" width="100%" height="100%">\n'
  92. html += '<!-- 连线将在这里绘制 -->\n'
  93. html += '</svg>\n'
  94. html += '</div>\n'
  95. # 主要内容区域(4列布局)
  96. html += '<div class="tab5-four-column-container">\n'
  97. # 第1列:左侧选题点(来自实质点的支撑关系)
  98. html += '<div class="tab5-column tab5-left-targets">\n'
  99. html += '<h3 class="panel-title">选题点<br/><span style="font-size:0.8em;font-weight:normal;color:#6c757d;">(实质支撑)</span></h3>\n'
  100. # 灵感点(左)
  101. if inspiration_points:
  102. html += '<div class="target-group">\n'
  103. html += '<h4 class="group-title">灵感点</h4>\n'
  104. html += '<div class="target-items">\n'
  105. for idx, point in enumerate(inspiration_points, 1):
  106. # 使用提取的特征中的特征名称,每个特征名称一个卡片
  107. features = point.get('提取的特征', [])
  108. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  109. if feature_names:
  110. # 为每个特征名称创建一个独立ID的卡片
  111. for feature_idx, feature_name in enumerate(feature_names, 1):
  112. feature_id = f'inspiration-{idx}-{feature_idx}'
  113. html += f'<div class="target-card inspiration-card" data-type="inspiration-substance" data-id="{feature_id}" onclick="selectLeftTarget(' + f"'inspiration', '{idx}-{feature_idx}'" + ')">\n'
  114. html += f'<div class="card-number">#{idx}-{feature_idx}</div>\n'
  115. html += f'<div class="card-text">{html_module.escape(feature_name)}</div>\n'
  116. html += '</div>\n'
  117. else:
  118. # 如果没有特征,使用原始的灵感点文本
  119. display_text = point.get('灵感点', '')
  120. html += f'<div class="target-card inspiration-card" data-type="inspiration-substance" data-id="inspiration-{idx}" onclick="selectLeftTarget(' + f"'inspiration', {idx}" + ')">\n'
  121. html += f'<div class="card-number">#{idx}</div>\n'
  122. html += f'<div class="card-text">{html_module.escape(display_text)}</div>\n'
  123. html += '</div>\n'
  124. html += '</div>\n'
  125. html += '</div>\n'
  126. # 关键点(左)
  127. if key_points:
  128. html += '<div class="target-group">\n'
  129. html += '<h4 class="group-title">关键点</h4>\n'
  130. html += '<div class="target-items">\n'
  131. for idx, point in enumerate(key_points, 1):
  132. # 使用提取的特征中的特征名称,每个特征名称一个卡片
  133. features = point.get('提取的特征', [])
  134. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  135. if feature_names:
  136. # 为每个特征名称创建一个独立ID的卡片
  137. for feature_idx, feature_name in enumerate(feature_names, 1):
  138. feature_id = f'keypoint-{idx}-{feature_idx}'
  139. html += f'<div class="target-card keypoint-card" data-type="keypoint-substance" data-id="{feature_id}" onclick="selectLeftTarget(' + f"'keypoint', '{idx}-{feature_idx}'" + ')">\n'
  140. html += f'<div class="card-number">#{idx}-{feature_idx}</div>\n'
  141. html += f'<div class="card-text">{html_module.escape(feature_name)}</div>\n'
  142. html += '</div>\n'
  143. else:
  144. # 如果没有特征,使用原始的关键点文本
  145. display_text = point.get('关键点', '')
  146. html += f'<div class="target-card keypoint-card" data-type="keypoint-substance" data-id="keypoint-{idx}" onclick="selectLeftTarget(' + f"'keypoint', {idx}" + ')">\n'
  147. html += f'<div class="card-number">#{idx}</div>\n'
  148. html += f'<div class="card-text">{html_module.escape(display_text)}</div>\n'
  149. html += '</div>\n'
  150. html += '</div>\n'
  151. html += '</div>\n'
  152. # 目的点(左)
  153. if purpose_points:
  154. html += '<div class="target-group">\n'
  155. html += '<h4 class="group-title">目的点</h4>\n'
  156. html += '<div class="target-items">\n'
  157. for idx, point in enumerate(purpose_points, 1):
  158. # 使用提取的特征中的特征名称,每个特征名称一个卡片
  159. features = point.get('提取的特征', [])
  160. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  161. if feature_names:
  162. # 为每个特征名称创建一个独立ID的卡片
  163. for feature_idx, feature_name in enumerate(feature_names, 1):
  164. feature_id = f'purpose-{idx}-{feature_idx}'
  165. html += f'<div class="target-card purpose-card" data-type="purpose-substance" data-id="{feature_id}" onclick="selectLeftTarget(' + f"'purpose', '{idx}-{feature_idx}'" + ')">\n'
  166. html += f'<div class="card-number">#{idx}-{feature_idx}</div>\n'
  167. html += f'<div class="card-text">{html_module.escape(feature_name)}</div>\n'
  168. html += '</div>\n'
  169. else:
  170. # 如果没有特征,使用原始的目的点文本
  171. display_text = point.get('目的点', '')
  172. html += f'<div class="target-card purpose-card" data-type="purpose-substance" data-id="purpose-{idx}" onclick="selectLeftTarget(' + f"'purpose', {idx}" + ')">\n'
  173. html += f'<div class="card-number">#{idx}</div>\n'
  174. html += f'<div class="card-text">{html_module.escape(display_text)}</div>\n'
  175. html += '</div>\n'
  176. html += '</div>\n'
  177. html += '</div>\n'
  178. html += '</div>\n'
  179. # 第2列:实质点
  180. html += '<div class="tab5-column tab5-substances">\n'
  181. html += '<h3 class="panel-title">实质点</h3>\n'
  182. # 具体元素
  183. if concrete_elements:
  184. html += '<div class="substance-group">\n'
  185. html += '<h4 class="group-title">具体元素</h4>\n'
  186. html += '<div class="substance-items">\n'
  187. for substance in concrete_elements:
  188. html += render_substance_card(substance, 'concrete-element')
  189. html += '</div>\n'
  190. html += '</div>\n'
  191. # 具象概念
  192. if concrete_concepts:
  193. html += '<div class="substance-group">\n'
  194. html += '<h4 class="group-title">具象概念</h4>\n'
  195. html += '<div class="substance-items">\n'
  196. for substance in concrete_concepts:
  197. html += render_substance_card(substance, 'concrete-concept')
  198. html += '</div>\n'
  199. html += '</div>\n'
  200. # 隐含概念
  201. if implicit_concepts:
  202. html += '<div class="substance-group">\n'
  203. html += '<h4 class="group-title">隐含概念</h4>\n'
  204. html += '<div class="substance-items">\n'
  205. for substance in implicit_concepts:
  206. html += render_substance_card(substance, 'implicit-concept')
  207. html += '</div>\n'
  208. html += '</div>\n'
  209. # 抽象概念
  210. if abstract_concepts:
  211. html += '<div class="substance-group">\n'
  212. html += '<h4 class="group-title">抽象概念</h4>\n'
  213. html += '<div class="substance-items">\n'
  214. for substance in abstract_concepts:
  215. html += render_substance_card(substance, 'abstract-concept')
  216. html += '</div>\n'
  217. html += '</div>\n'
  218. html += '</div>\n'
  219. # 第3列:形式点
  220. html += '<div class="tab5-column tab5-forms">\n'
  221. html += '<h3 class="panel-title">形式点</h3>\n'
  222. # 具体元素形式
  223. if concrete_element_forms:
  224. html += '<div class="form-group">\n'
  225. html += '<h4 class="group-title">具体元素形式</h4>\n'
  226. html += '<div class="form-items">\n'
  227. for form in concrete_element_forms:
  228. html += render_form_card(form, 'concrete-element-form')
  229. html += '</div>\n'
  230. html += '</div>\n'
  231. # 具象概念形式
  232. if concrete_concept_forms:
  233. html += '<div class="form-group">\n'
  234. html += '<h4 class="group-title">具象概念形式</h4>\n'
  235. html += '<div class="form-items">\n'
  236. for form in concrete_concept_forms:
  237. html += render_form_card(form, 'concrete-concept-form')
  238. html += '</div>\n'
  239. html += '</div>\n'
  240. # 整体形式
  241. if overall_forms:
  242. html += '<div class="form-group">\n'
  243. html += '<h4 class="group-title">整体形式</h4>\n'
  244. html += '<div class="form-items">\n'
  245. for form in overall_forms:
  246. html += render_form_card(form, 'overall-form')
  247. html += '</div>\n'
  248. html += '</div>\n'
  249. html += '</div>\n'
  250. # 第4列:右侧选题点(来自形式点的支撑关系)
  251. html += '<div class="tab5-column tab5-right-targets">\n'
  252. html += '<h3 class="panel-title">选题点<br/><span style="font-size:0.8em;font-weight:normal;color:#6c757d;">(形式支撑)</span></h3>\n'
  253. # 灵感点(右)
  254. if inspiration_points:
  255. html += '<div class="target-group">\n'
  256. html += '<h4 class="group-title">灵感点</h4>\n'
  257. html += '<div class="target-items">\n'
  258. for idx, point in enumerate(inspiration_points, 1):
  259. # 使用提取的特征中的特征名称,每个特征名称一个卡片
  260. features = point.get('提取的特征', [])
  261. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  262. if feature_names:
  263. # 为每个特征名称创建一个独立ID的卡片
  264. for feature_idx, feature_name in enumerate(feature_names, 1):
  265. feature_id = f'inspiration-{idx}-{feature_idx}'
  266. html += f'<div class="target-card inspiration-card" data-type="inspiration-form" data-id="{feature_id}" onclick="selectRightTarget(' + f"'inspiration', '{idx}-{feature_idx}'" + ')">\n'
  267. html += f'<div class="card-number">#{idx}-{feature_idx}</div>\n'
  268. html += f'<div class="card-text">{html_module.escape(feature_name)}</div>\n'
  269. html += '</div>\n'
  270. else:
  271. # 如果没有特征,使用原始的灵感点文本
  272. display_text = point.get('灵感点', '')
  273. html += f'<div class="target-card inspiration-card" data-type="inspiration-form" data-id="inspiration-{idx}" onclick="selectRightTarget(' + f"'inspiration', {idx}" + ')">\n'
  274. html += f'<div class="card-number">#{idx}</div>\n'
  275. html += f'<div class="card-text">{html_module.escape(display_text)}</div>\n'
  276. html += '</div>\n'
  277. html += '</div>\n'
  278. html += '</div>\n'
  279. # 关键点(右)
  280. if key_points:
  281. html += '<div class="target-group">\n'
  282. html += '<h4 class="group-title">关键点</h4>\n'
  283. html += '<div class="target-items">\n'
  284. for idx, point in enumerate(key_points, 1):
  285. # 使用提取的特征中的特征名称,每个特征名称一个卡片
  286. features = point.get('提取的特征', [])
  287. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  288. if feature_names:
  289. # 为每个特征名称创建一个独立ID的卡片
  290. for feature_idx, feature_name in enumerate(feature_names, 1):
  291. feature_id = f'keypoint-{idx}-{feature_idx}'
  292. html += f'<div class="target-card keypoint-card" data-type="keypoint-form" data-id="{feature_id}" onclick="selectRightTarget(' + f"'keypoint', '{idx}-{feature_idx}'" + ')">\n'
  293. html += f'<div class="card-number">#{idx}-{feature_idx}</div>\n'
  294. html += f'<div class="card-text">{html_module.escape(feature_name)}</div>\n'
  295. html += '</div>\n'
  296. else:
  297. # 如果没有特征,使用原始的关键点文本
  298. display_text = point.get('关键点', '')
  299. html += f'<div class="target-card keypoint-card" data-type="keypoint-form" data-id="keypoint-{idx}" onclick="selectRightTarget(' + f"'keypoint', {idx}" + ')">\n'
  300. html += f'<div class="card-number">#{idx}</div>\n'
  301. html += f'<div class="card-text">{html_module.escape(display_text)}</div>\n'
  302. html += '</div>\n'
  303. html += '</div>\n'
  304. html += '</div>\n'
  305. # 目的点(右)
  306. if purpose_points:
  307. html += '<div class="target-group">\n'
  308. html += '<h4 class="group-title">目的点</h4>\n'
  309. html += '<div class="target-items">\n'
  310. for idx, point in enumerate(purpose_points, 1):
  311. # 使用提取的特征中的特征名称,每个特征名称一个卡片
  312. features = point.get('提取的特征', [])
  313. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  314. if feature_names:
  315. # 为每个特征名称创建一个独立ID的卡片
  316. for feature_idx, feature_name in enumerate(feature_names, 1):
  317. feature_id = f'purpose-{idx}-{feature_idx}'
  318. html += f'<div class="target-card purpose-card" data-type="purpose-form" data-id="{feature_id}" onclick="selectRightTarget(' + f"'purpose', '{idx}-{feature_idx}'" + ')">\n'
  319. html += f'<div class="card-number">#{idx}-{feature_idx}</div>\n'
  320. html += f'<div class="card-text">{html_module.escape(feature_name)}</div>\n'
  321. html += '</div>\n'
  322. else:
  323. # 如果没有特征,使用原始的目的点文本
  324. display_text = point.get('目的点', '')
  325. html += f'<div class="target-card purpose-card" data-type="purpose-form" data-id="purpose-{idx}" onclick="selectRightTarget(' + f"'purpose', {idx}" + ')">\n'
  326. html += f'<div class="card-number">#{idx}</div>\n'
  327. html += f'<div class="card-text">{html_module.escape(display_text)}</div>\n'
  328. html += '</div>\n'
  329. html += '</div>\n'
  330. html += '</div>\n'
  331. html += '</div>\n'
  332. html += '</div>\n'
  333. # 嵌入关系数据
  334. html += '<script>\n'
  335. html += f'const tab5Relationships = {json.dumps(relationships, ensure_ascii=False)};\n'
  336. html += '</script>\n'
  337. html += '</div>\n'
  338. return html
  339. def render_substance_card(substance: Dict[str, Any], css_class: str) -> str:
  340. """渲染实质点卡片"""
  341. substance_id = substance.get('id', '')
  342. substance_name = substance.get('名称', '')
  343. description = substance.get('描述', '')
  344. html = f'<div class="substance-card {css_class}" data-id="{html_module.escape(substance_id)}" onclick="selectSubstance(' + f"'{substance_id}'" + ')">\n'
  345. html += f'<div class="card-header">\n'
  346. html += f'<div class="card-id">#{html_module.escape(substance_id)}</div>\n'
  347. html += f'<div class="card-name">{html_module.escape(substance_name)}</div>\n'
  348. html += '</div>\n'
  349. if description:
  350. html += f'<div class="card-description">{html_module.escape(description[:50])}{"..." if len(description) > 50 else ""}</div>\n'
  351. html += '</div>\n'
  352. return html
  353. def render_form_card(form: Dict[str, Any], css_class: str) -> str:
  354. """渲染形式点卡片"""
  355. form_id = form.get('id', '')
  356. form_name = form.get('名称', '')
  357. description = form.get('描述', '')
  358. weight_score = form.get('权重分')
  359. html = f'<div class="form-card {css_class}" data-id="{html_module.escape(form_id)}" onclick="selectForm(' + f"'{form_id}'" + ')">\n'
  360. html += f'<div class="card-header">\n'
  361. html += f'<div class="card-id">#{html_module.escape(form_id)}</div>\n'
  362. html += f'<div class="card-name">{html_module.escape(form_name)}</div>\n'
  363. if weight_score is not None:
  364. html += f'<div class="card-weight" style="font-size: 11px; color: #666; margin-top: 2px;">权重分: {weight_score:.1f}</div>\n'
  365. html += '</div>\n'
  366. if description:
  367. html += f'<div class="card-description">{html_module.escape(description[:50])}{"..." if len(description) > 50 else ""}</div>\n'
  368. html += '</div>\n'
  369. return html
  370. def build_bidirectional_relationships(
  371. concrete_elements: List[Dict],
  372. concrete_concepts: List[Dict],
  373. implicit_concepts: List[Dict],
  374. abstract_concepts: List[Dict],
  375. concrete_element_forms: List[Dict],
  376. concrete_concept_forms: List[Dict],
  377. overall_forms: List[Dict],
  378. inspiration_points: List[Dict],
  379. purpose_points: List[Dict],
  380. key_points: List[Dict]
  381. ) -> Dict[str, Any]:
  382. """
  383. 构建双向支撑关系数据
  384. 返回结构:
  385. {
  386. "substance_to_target": {
  387. "substance_id": {
  388. "inspiration": [{target_id, score, ...}],
  389. "purpose": [...],
  390. "keypoint": [...]
  391. }
  392. },
  393. "form_to_target": {
  394. "form_id": {
  395. "inspiration": [{target_id, score, ...}],
  396. "purpose": [...],
  397. "keypoint": [...]
  398. }
  399. },
  400. "target_from_substance": {
  401. "inspiration-1": [substance_ids with scores],
  402. "keypoint-1": [...],
  403. "purpose-1": [...]
  404. },
  405. "target_from_form": {
  406. "inspiration-1": [form_ids with scores],
  407. ...
  408. },
  409. "form_to_substance": {
  410. "form_id": [substance_ids],
  411. ...
  412. },
  413. "substance_from_form": {
  414. "substance_id": [form_ids],
  415. ...
  416. }
  417. }
  418. """
  419. relationships = {
  420. "substance_to_target": {},
  421. "form_to_target": {},
  422. "target_from_substance": {},
  423. "target_from_form": {},
  424. "form_to_substance": {}, # 新增:形式点→实质点
  425. "substance_from_form": {} # 新增:实质点←形式点(反向)
  426. }
  427. # 合并所有实质点和形式点
  428. all_substances = concrete_elements + concrete_concepts + implicit_concepts + abstract_concepts
  429. all_forms = concrete_element_forms + concrete_concept_forms + overall_forms
  430. # 注意:优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段(兼容旧数据)
  431. # 这两个字段内部存储的都是意图支撑数据,每个项目就是一个支撑点
  432. # 不再需要阈值过滤,所有支撑点都会显示连线
  433. # 1. 构建实质点到选题点的关系(基于意图支撑数据)
  434. for substance in all_substances:
  435. substance_id = substance.get('id', '')
  436. if not substance_id:
  437. continue
  438. dimension_2 = substance.get('维度', {}).get('二级', '')
  439. intention_support = get_intent_support_data(substance)
  440. relationships["substance_to_target"][substance_id] = {
  441. "name": substance.get('名称', ''),
  442. "type": dimension_2,
  443. "inspiration": [],
  444. "purpose": [],
  445. "keypoint": []
  446. }
  447. # 处理灵感点(意图支撑)
  448. for support_item in intention_support.get('灵感点', []):
  449. point_name = support_item.get('点')
  450. if not point_name:
  451. continue
  452. # 在灵感点列表中查找匹配的点,以便复用Tab1中的卡片id/特征结构
  453. idx = next(
  454. (i for i, p in enumerate(inspiration_points, 1)
  455. if isinstance(p, dict) and p.get('灵感点') == point_name),
  456. None
  457. )
  458. if idx is None:
  459. continue
  460. point = inspiration_points[idx - 1]
  461. features = point.get('提取的特征', [])
  462. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  463. if feature_names:
  464. # 为每个特征创建独立的关系
  465. for feature_idx in range(1, len(feature_names) + 1):
  466. target_id = f'inspiration-{idx}-{feature_idx}'
  467. relationships["substance_to_target"][substance_id]["inspiration"].append({
  468. "target_id": target_id,
  469. "point": point_name,
  470. "support_reason": support_item.get('支撑理由', '')
  471. })
  472. # 反向关系
  473. if target_id not in relationships["target_from_substance"]:
  474. relationships["target_from_substance"][target_id] = []
  475. relationships["target_from_substance"][target_id].append({
  476. "substance_id": substance_id,
  477. "name": substance.get('名称', ''),
  478. "type": dimension_2,
  479. "support_reason": support_item.get('支撑理由', '')
  480. })
  481. else:
  482. # 没有特征,使用主索引
  483. target_id = f'inspiration-{idx}'
  484. relationships["substance_to_target"][substance_id]["inspiration"].append({
  485. "target_id": target_id,
  486. "point": point_name,
  487. "support_reason": support_item.get('支撑理由', '')
  488. })
  489. # 反向关系
  490. if target_id not in relationships["target_from_substance"]:
  491. relationships["target_from_substance"][target_id] = []
  492. relationships["target_from_substance"][target_id].append({
  493. "substance_id": substance_id,
  494. "name": substance.get('名称', ''),
  495. "type": dimension_2,
  496. "support_reason": support_item.get('支撑理由', '')
  497. })
  498. # 处理目的点(意图支撑)
  499. for support_item in intention_support.get('目的点', []):
  500. point_name = support_item.get('点')
  501. if not point_name:
  502. continue
  503. idx = next(
  504. (i for i, p in enumerate(purpose_points, 1)
  505. if isinstance(p, dict) and p.get('目的点') == point_name),
  506. None
  507. )
  508. if idx is None:
  509. continue
  510. point = purpose_points[idx - 1]
  511. features = point.get('提取的特征', [])
  512. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  513. if feature_names:
  514. for feature_idx in range(1, len(feature_names) + 1):
  515. target_id = f'purpose-{idx}-{feature_idx}'
  516. relationships["substance_to_target"][substance_id]["purpose"].append({
  517. "target_id": target_id,
  518. "point": point_name,
  519. "support_reason": support_item.get('支撑理由', '')
  520. })
  521. if target_id not in relationships["target_from_substance"]:
  522. relationships["target_from_substance"][target_id] = []
  523. relationships["target_from_substance"][target_id].append({
  524. "substance_id": substance_id,
  525. "name": substance.get('名称', ''),
  526. "type": dimension_2,
  527. "support_reason": support_item.get('支撑理由', '')
  528. })
  529. else:
  530. target_id = f'purpose-{idx}'
  531. relationships["substance_to_target"][substance_id]["purpose"].append({
  532. "target_id": target_id,
  533. "point": point_name,
  534. "support_reason": support_item.get('支撑理由', '')
  535. })
  536. if target_id not in relationships["target_from_substance"]:
  537. relationships["target_from_substance"][target_id] = []
  538. relationships["target_from_substance"][target_id].append({
  539. "substance_id": substance_id,
  540. "name": substance.get('名称', ''),
  541. "type": dimension_2,
  542. "support_reason": support_item.get('支撑理由', '')
  543. })
  544. # 处理关键点(意图支撑)
  545. for support_item in intention_support.get('关键点', []):
  546. point_name = support_item.get('点')
  547. if not point_name:
  548. continue
  549. idx = next(
  550. (i for i, p in enumerate(key_points, 1)
  551. if isinstance(p, dict) and p.get('关键点') == point_name),
  552. None
  553. )
  554. if idx is None:
  555. continue
  556. point = key_points[idx - 1]
  557. features = point.get('提取的特征', [])
  558. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  559. if feature_names:
  560. for feature_idx in range(1, len(feature_names) + 1):
  561. target_id = f'keypoint-{idx}-{feature_idx}'
  562. relationships["substance_to_target"][substance_id]["keypoint"].append({
  563. "target_id": target_id,
  564. "point": point_name,
  565. "support_reason": support_item.get('支撑理由', '')
  566. })
  567. if target_id not in relationships["target_from_substance"]:
  568. relationships["target_from_substance"][target_id] = []
  569. relationships["target_from_substance"][target_id].append({
  570. "substance_id": substance_id,
  571. "name": substance.get('名称', ''),
  572. "type": dimension_2,
  573. "support_reason": support_item.get('支撑理由', '')
  574. })
  575. else:
  576. target_id = f'keypoint-{idx}'
  577. relationships["substance_to_target"][substance_id]["keypoint"].append({
  578. "target_id": target_id,
  579. "point": point_name,
  580. "support_reason": support_item.get('支撑理由', '')
  581. })
  582. if target_id not in relationships["target_from_substance"]:
  583. relationships["target_from_substance"][target_id] = []
  584. relationships["target_from_substance"][target_id].append({
  585. "substance_id": substance_id,
  586. "name": substance.get('名称', ''),
  587. "type": dimension_2,
  588. "support_reason": support_item.get('支撑理由', '')
  589. })
  590. # 2. 构建形式点到选题点的关系
  591. for form in all_forms:
  592. form_id = form.get('id', '')
  593. if not form_id:
  594. continue
  595. dimension_2 = form.get('维度', {}).get('二级', '')
  596. # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
  597. intent_support_data = get_intent_support_data(form)
  598. relationships["form_to_target"][form_id] = {
  599. "name": form.get('名称', ''),
  600. "type": dimension_2,
  601. "inspiration": [],
  602. "purpose": [],
  603. "keypoint": []
  604. }
  605. # 处理灵感点(每个项目就是一个支撑点)
  606. if '灵感点' in intent_support_data:
  607. for support_point in intent_support_data['灵感点']:
  608. if not isinstance(support_point, dict):
  609. continue
  610. # 每个项目就是一个支撑点,直接使用
  611. point_name = support_point.get('点', '')
  612. if not point_name:
  613. continue
  614. # 根据点名称找到对应的灵感点索引
  615. point_idx = None
  616. for idx, point in enumerate(inspiration_points, 1):
  617. if point.get('灵感点') == point_name or point.get('名称') == point_name:
  618. point_idx = idx
  619. break
  620. if point_idx and point_idx <= len(inspiration_points):
  621. point = inspiration_points[point_idx - 1]
  622. features = point.get('提取的特征', [])
  623. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  624. if feature_names:
  625. # 为每个特征创建独立的关系
  626. for feature_idx in range(1, len(feature_names) + 1):
  627. target_id = f'inspiration-{point_idx}-{feature_idx}'
  628. relationships["form_to_target"][form_id]["inspiration"].append({
  629. "target_id": target_id,
  630. "point": point_name,
  631. "support_reason": support_point.get('支撑理由', '')
  632. })
  633. # 反向关系
  634. if target_id not in relationships["target_from_form"]:
  635. relationships["target_from_form"][target_id] = []
  636. relationships["target_from_form"][target_id].append({
  637. "form_id": form_id,
  638. "name": form.get('名称', ''),
  639. "type": dimension_2,
  640. "support_reason": support_point.get('支撑理由', '')
  641. })
  642. else:
  643. # 没有特征,使用主索引
  644. target_id = f'inspiration-{point_idx}'
  645. relationships["form_to_target"][form_id]["inspiration"].append({
  646. "target_id": target_id,
  647. "point": point_name,
  648. "support_reason": support_point.get('支撑理由', '')
  649. })
  650. # 反向关系
  651. if target_id not in relationships["target_from_form"]:
  652. relationships["target_from_form"][target_id] = []
  653. relationships["target_from_form"][target_id].append({
  654. "form_id": form_id,
  655. "name": form.get('名称', ''),
  656. "type": dimension_2,
  657. "support_reason": support_point.get('支撑理由', '')
  658. })
  659. # 处理目的点(每个项目就是一个支撑点)
  660. if '目的点' in intent_support_data:
  661. for support_point in intent_support_data['目的点']:
  662. if not isinstance(support_point, dict):
  663. continue
  664. # 每个项目就是一个支撑点,直接使用
  665. point_name = support_point.get('点', '')
  666. if not point_name:
  667. continue
  668. # 根据点名称找到对应的目的点索引
  669. point_idx = None
  670. for idx, point in enumerate(purpose_points, 1):
  671. if point.get('目的点') == point_name or point.get('名称') == point_name:
  672. point_idx = idx
  673. break
  674. if point_idx and point_idx <= len(purpose_points):
  675. point = purpose_points[point_idx - 1]
  676. features = point.get('提取的特征', [])
  677. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  678. if feature_names:
  679. for feature_idx in range(1, len(feature_names) + 1):
  680. target_id = f'purpose-{point_idx}-{feature_idx}'
  681. relationships["form_to_target"][form_id]["purpose"].append({
  682. "target_id": target_id,
  683. "point": point_name,
  684. "support_reason": support_point.get('支撑理由', '')
  685. })
  686. if target_id not in relationships["target_from_form"]:
  687. relationships["target_from_form"][target_id] = []
  688. relationships["target_from_form"][target_id].append({
  689. "form_id": form_id,
  690. "name": form.get('名称', ''),
  691. "type": dimension_2,
  692. "support_reason": support_point.get('支撑理由', '')
  693. })
  694. else:
  695. target_id = f'purpose-{point_idx}'
  696. relationships["form_to_target"][form_id]["purpose"].append({
  697. "target_id": target_id,
  698. "point": point_name,
  699. "support_reason": support_point.get('支撑理由', '')
  700. })
  701. if target_id not in relationships["target_from_form"]:
  702. relationships["target_from_form"][target_id] = []
  703. relationships["target_from_form"][target_id].append({
  704. "form_id": form_id,
  705. "name": form.get('名称', ''),
  706. "type": dimension_2,
  707. "support_reason": support_point.get('支撑理由', '')
  708. })
  709. # 处理关键点(每个项目就是一个支撑点)
  710. if '关键点' in intent_support_data:
  711. for support_point in intent_support_data['关键点']:
  712. if not isinstance(support_point, dict):
  713. continue
  714. # 每个项目就是一个支撑点,直接使用
  715. point_name = support_point.get('点', '')
  716. if not point_name:
  717. continue
  718. # 根据点名称找到对应的关键点索引
  719. point_idx = None
  720. for idx, point in enumerate(key_points, 1):
  721. if point.get('关键点') == point_name or point.get('名称') == point_name:
  722. point_idx = idx
  723. break
  724. if point_idx and point_idx <= len(key_points):
  725. point = key_points[point_idx - 1]
  726. features = point.get('提取的特征', [])
  727. feature_names = [f.get('特征名称', '') for f in features if f.get('特征名称')]
  728. if feature_names:
  729. for feature_idx in range(1, len(feature_names) + 1):
  730. target_id = f'keypoint-{point_idx}-{feature_idx}'
  731. relationships["form_to_target"][form_id]["keypoint"].append({
  732. "target_id": target_id,
  733. "point": point_name,
  734. "support_reason": support_point.get('支撑理由', '')
  735. })
  736. if target_id not in relationships["target_from_form"]:
  737. relationships["target_from_form"][target_id] = []
  738. relationships["target_from_form"][target_id].append({
  739. "form_id": form_id,
  740. "name": form.get('名称', ''),
  741. "type": dimension_2,
  742. "support_reason": support_point.get('支撑理由', '')
  743. })
  744. else:
  745. target_id = f'keypoint-{point_idx}'
  746. relationships["form_to_target"][form_id]["keypoint"].append({
  747. "target_id": target_id,
  748. "point": point_name,
  749. "support_reason": support_point.get('支撑理由', '')
  750. })
  751. if target_id not in relationships["target_from_form"]:
  752. relationships["target_from_form"][target_id] = []
  753. relationships["target_from_form"][target_id].append({
  754. "form_id": form_id,
  755. "name": form.get('名称', ''),
  756. "type": dimension_2,
  757. "support_reason": support_point.get('支撑理由', '')
  758. })
  759. # 3. 构建形式点到实质点的支撑关系
  760. for form in all_forms:
  761. form_id = form.get('id', '')
  762. if not form_id:
  763. continue
  764. dimension_2 = form.get('维度', {}).get('二级', '')
  765. support_data = form.get('支撑', [])
  766. if not support_data:
  767. continue
  768. # 初始化形式点的支撑关系
  769. if form_id not in relationships["form_to_substance"]:
  770. relationships["form_to_substance"][form_id] = []
  771. # 支撑字段可能是列表或字典
  772. support_items = []
  773. if isinstance(support_data, list):
  774. # 列表格式:[{id, 名称}, ...]
  775. support_items = support_data
  776. elif isinstance(support_data, dict):
  777. # 字典格式:{'具体元素': [...], '具象概念': [...]}
  778. for _category, items in support_data.items():
  779. if isinstance(items, list):
  780. support_items.extend(items)
  781. for support_item in support_items:
  782. if not isinstance(support_item, dict):
  783. continue
  784. substance_id = support_item.get('id', '')
  785. substance_name = support_item.get('名称', '')
  786. if not substance_id:
  787. continue
  788. # 形式点 → 实质点
  789. relationships["form_to_substance"][form_id].append({
  790. "substance_id": substance_id,
  791. "name": substance_name
  792. })
  793. # 实质点 ← 形式点(反向)
  794. if substance_id not in relationships["substance_from_form"]:
  795. relationships["substance_from_form"][substance_id] = []
  796. relationships["substance_from_form"][substance_id].append({
  797. "form_id": form_id,
  798. "name": form.get('名称', ''),
  799. "type": dimension_2
  800. })
  801. return relationships