routes.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. """
  2. 数据指标API接口模块
  3. 该模块提供了数据指标的各种API接口,包括:
  4. - 指标新增、更新和查询
  5. - 指标列表展示
  6. - 指标图谱生成
  7. - 指标代码生成
  8. - 指标关系管理
  9. """
  10. from flask import request, jsonify
  11. import json
  12. from app.api.data_metric import bp
  13. from app.core.data_metric.metric_interface import (
  14. metric_list, handle_metric_relation, handle_data_metric,
  15. handle_meta_data_metric, handle_id_metric, metric_kinship_graph,
  16. metric_impact_graph, metric_all_graph, data_metric_edit, metric_check,
  17. metric_delete
  18. )
  19. from app.core.llm import code_generate_metric
  20. from app.core.meta_data import translate_and_parse
  21. from app.models.result import success, failed
  22. from app.core.graph.graph_operations import MyEncoder, connect_graph
  23. @bp.route('/relation', methods=['POST'])
  24. def data_metric_relation():
  25. """
  26. 处理数据指标血缘关系
  27. 请求参数:
  28. - id: 数据模型ID
  29. 返回:
  30. - 处理结果,包含血缘关系数据
  31. """
  32. try:
  33. # 传入请求参数
  34. receiver = request.get_json()
  35. model_ids = receiver['id'] # 给定一个数据模型的id
  36. response_data = handle_metric_relation(model_ids)
  37. res = success(response_data, "success")
  38. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  39. except Exception as e:
  40. res = failed({}, {"error": f"{e}"})
  41. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  42. @bp.route('/add', methods=['POST'])
  43. def data_metric_add():
  44. """
  45. 新增数据指标
  46. 请求参数:
  47. - name: 指标名称
  48. - 其他指标相关属性
  49. 返回:
  50. - 处理结果,成功或失败
  51. """
  52. # 传入请求参数
  53. receiver = request.get_json()
  54. metric_name_zh = receiver['name_zh'] # 数据指标的name
  55. try:
  56. result_list = translate_and_parse(metric_name_zh)
  57. id, id_list = handle_data_metric(metric_name_zh, result_list, receiver)
  58. handle_meta_data_metric(id, id_list)
  59. res = success({}, "success")
  60. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  61. except Exception as e:
  62. res = failed({}, {"error": f"{e}"})
  63. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  64. @bp.route('/code', methods=['POST'])
  65. def data_metric_code():
  66. """
  67. 生成指标计算的代码
  68. 请求参数:
  69. - content: 指标规则描述
  70. - relation: 映射关系
  71. 返回:
  72. - 生成的代码
  73. """
  74. # 传入请求参数
  75. receiver = request.get_json()
  76. content = receiver['content'] # 指标规则描述
  77. relation = receiver['relation'] # 映射关系
  78. try:
  79. id_list = [item["id"] for item in relation]
  80. cql = """
  81. MATCH (n:DataMetric) WHERE id(n) IN $Id_list
  82. WITH n.name_en AS name_en, n.name_zh AS name_zh
  83. WITH collect({name_en: name_en, name_zh: name_zh}) AS res
  84. WITH reduce(acc = {}, item IN res | apoc.map.setKey(acc, item.name_en, item.name_zh)) AS result
  85. RETURN result
  86. """
  87. # 修复:使用正确的session方式执行查询
  88. driver = connect_graph()
  89. if not driver:
  90. return json.dumps(failed({}, "无法连接到数据库"), ensure_ascii=False, cls=MyEncoder)
  91. with driver.session() as session:
  92. query_result = session.run(cql, Id_list=id_list)
  93. id_relation = query_result.single()[0]
  94. result = code_generate_metric(content, id_relation)
  95. res = success(result, "success")
  96. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  97. except Exception as e:
  98. res = failed({}, {"error": f"{e}"})
  99. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  100. @bp.route('/detail', methods=['POST'])
  101. def data_metric_detail():
  102. """
  103. 获取数据指标详情
  104. 请求参数:
  105. - id: 指标ID
  106. 返回:
  107. - 指标详情数据
  108. """
  109. try:
  110. # 传入请求参数
  111. receiver = request.get_json()
  112. id = int(receiver.get('id'))
  113. response_data = handle_id_metric(id)
  114. res = success(response_data, "success")
  115. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  116. except Exception as e:
  117. res = failed({}, {"error": f"{e}"})
  118. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  119. @bp.route('/list', methods=['POST'])
  120. def data_metric_list():
  121. """
  122. 获取数据指标列表
  123. 请求参数:
  124. - current: 当前页码,默认1
  125. - size: 每页大小,默认10
  126. - name_en: 英文名称过滤
  127. - name_zh: 名称过滤
  128. - category: 类别过滤
  129. - time: 时间过滤
  130. - tag: 标签过滤
  131. 返回:
  132. - 指标列表数据和分页信息
  133. """
  134. try:
  135. # 传入请求参数
  136. receiver = request.get_json()
  137. page = int(receiver.get('current', 1))
  138. page_size = int(receiver.get('size', 10))
  139. name_en_filter = receiver.get('name_en', None)
  140. name_zh_filter = receiver.get('name_zh', None)
  141. category = receiver.get('category', None)
  142. create_time = receiver.get('create_time', None)
  143. tag = receiver.get('tag', None)
  144. # 计算跳过的记录的数量
  145. skip_count = (page - 1) * page_size
  146. data, total = metric_list(skip_count, page_size, name_en_filter,
  147. name_zh_filter, category, create_time, tag)
  148. response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
  149. res = success(response_data, "success")
  150. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  151. except Exception as e:
  152. res = failed({}, {"error": f"{e}"})
  153. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  154. @bp.route('/graph/all', methods=['POST'])
  155. def data_metric_graph_all():
  156. """
  157. 获取数据指标图谱
  158. 请求参数:
  159. - id: 指标ID
  160. - type: 图谱类型,可选kinship/impact/all
  161. - meta: 是否返回元数据,true/false
  162. 返回:
  163. - 图谱数据
  164. """
  165. try:
  166. # 传入请求参数
  167. receiver = request.get_json()
  168. nodeid = receiver['id']
  169. type = receiver['type'] # kinship/impact/all
  170. meta = receiver['meta'] # true/false 是否返回元数据
  171. if type == 'kinship':
  172. result = metric_kinship_graph(nodeid, meta)
  173. elif type == 'impact':
  174. result = metric_impact_graph(nodeid, meta)
  175. else:
  176. result = metric_all_graph(nodeid, meta)
  177. return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
  178. except Exception as e:
  179. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  180. @bp.route('/list/graph', methods=['POST'])
  181. def data_metric_list_graph():
  182. """
  183. 获取数据指标列表图谱
  184. 请求参数:
  185. - tag: 标签ID
  186. 返回:
  187. - 图谱数据,包含节点和连线
  188. """
  189. try:
  190. # 传入请求参数
  191. receiver = request.get_json()
  192. if not receiver or 'tag' not in receiver:
  193. raise ValueError("Missing 'tag' parameter in request body")
  194. nodeid = receiver['tag']
  195. # 构建查询条件
  196. params = {}
  197. where_clause = ""
  198. if nodeid is not None:
  199. where_clause = "MATCH (n)-[:LABEL]->(la) WHERE id(la) = $nodeId"
  200. params['nodeId'] = nodeid
  201. query = f"""
  202. MATCH (n:DataMetric)
  203. OPTIONAL MATCH (n)-[:child]->(child)
  204. {where_clause}
  205. WITH
  206. collect(DISTINCT {{id: toString(id(n)), text: n.name_zh, type: split(labels(n)[0], '_')[1]}}) AS nodes,
  207. collect(DISTINCT {{id: toString(id(child)), text: child.name_zh, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
  208. collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
  209. RETURN nodes + nodes2 AS nodes, lines AS lines
  210. """
  211. # 修复:使用正确的session方式执行查询
  212. driver = connect_graph()
  213. if not driver:
  214. return json.dumps(failed({}, "无法连接到数据库"), ensure_ascii=False, cls=MyEncoder)
  215. with driver.session() as session:
  216. result = session.run(query, **params)
  217. res = {}
  218. for item in result:
  219. res = {
  220. "nodes": [record for record in item['nodes'] if record['id']],
  221. "lines": [record for record in item['lines'] if record['from'] and record['to']],
  222. }
  223. return json.dumps(success(res, "success"), ensure_ascii=False, cls=MyEncoder)
  224. except Exception as e:
  225. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  226. @bp.route('/update', methods=['POST'])
  227. def data_metric_update():
  228. """
  229. 更新数据指标
  230. 请求参数:
  231. - id: 指标ID
  232. - 其他需要更新的属性
  233. 返回:
  234. - 处理结果,成功或失败
  235. """
  236. try:
  237. # 传入请求参数
  238. receiver = request.get_json()
  239. data_metric_edit(receiver)
  240. res = success({}, "success")
  241. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  242. except Exception as e:
  243. res = failed({}, {"error": f"{e}"})
  244. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  245. @bp.route('/check', methods=['POST'])
  246. def data_metric_check():
  247. """
  248. 检查指标计算公式中的变量
  249. 请求参数:
  250. - formula: 指标计算公式文本,格式如:指标名称 = 变量1 + 变量2 * 数字
  251. 返回:
  252. - 变量检查结果列表,包含每个变量的匹配信息
  253. """
  254. try:
  255. # 获取请求参数
  256. receiver = request.get_json()
  257. formula_text = receiver.get('formula', '')
  258. if not formula_text:
  259. res = failed({}, {"error": "公式文本不能为空"})
  260. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  261. # 调用核心业务逻辑
  262. result = metric_check(formula_text)
  263. res = success(result, "success")
  264. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  265. except Exception as e:
  266. res = failed({}, {"error": f"{e}"})
  267. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  268. @bp.route('/delete', methods=['POST'])
  269. def data_metric_delete():
  270. """
  271. 删除数据指标
  272. 请求参数:
  273. - id: 指标节点ID
  274. 返回:
  275. - 删除结果状态信息
  276. """
  277. try:
  278. # 获取请求参数
  279. receiver = request.get_json()
  280. metric_id = receiver.get('id')
  281. # 验证参数
  282. if not metric_id:
  283. res = failed({}, {"error": "指标ID不能为空"})
  284. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  285. # 转换为整数
  286. try:
  287. metric_id = int(metric_id)
  288. except (ValueError, TypeError):
  289. res = failed({}, {"error": "指标ID必须为整数"})
  290. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  291. # 调用核心业务逻辑执行删除
  292. delete_result = metric_delete(metric_id)
  293. # 根据删除结果返回响应
  294. if delete_result["success"]:
  295. res = success({
  296. "id": metric_id,
  297. "message": delete_result["message"]
  298. }, "删除成功")
  299. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  300. else:
  301. res = failed({
  302. "id": metric_id,
  303. "message": delete_result["message"]
  304. }, delete_result["message"])
  305. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  306. except Exception as e:
  307. res = failed({}, {"error": f"删除失败: {str(e)}"})
  308. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)