tab5.py 40 KB

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