routes.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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. driver = None
  88. try:
  89. driver = connect_graph()
  90. with driver.session() as session:
  91. query_result = session.run(cql, Id_list=id_list)
  92. id_relation = query_result.single()[0]
  93. except (ConnectionError, ValueError) as e:
  94. return json.dumps(failed({}, f"无法连接到数据库: {str(e)}"), ensure_ascii=False, cls=MyEncoder)
  95. finally:
  96. if driver:
  97. driver.close()
  98. result = code_generate_metric(content, id_relation)
  99. res = success(result, "success")
  100. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  101. except Exception as e:
  102. res = failed({}, {"error": f"{e}"})
  103. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  104. @bp.route('/detail', methods=['POST'])
  105. def data_metric_detail():
  106. """
  107. 获取数据指标详情
  108. 请求参数:
  109. - id: 指标ID
  110. 返回:
  111. - 指标详情数据
  112. """
  113. try:
  114. # 传入请求参数
  115. receiver = request.get_json()
  116. id = int(receiver.get('id'))
  117. response_data = handle_id_metric(id)
  118. res = success(response_data, "success")
  119. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  120. except Exception as e:
  121. res = failed({}, {"error": f"{e}"})
  122. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  123. @bp.route('/list', methods=['POST'])
  124. def data_metric_list():
  125. """
  126. 获取数据指标列表
  127. 请求参数:
  128. - current: 当前页码,默认1
  129. - size: 每页大小,默认10
  130. - name_en: 英文名称过滤
  131. - name_zh: 名称过滤
  132. - category: 类别过滤
  133. - time: 时间过滤
  134. - tag: 标签过滤
  135. 返回:
  136. - 指标列表数据和分页信息
  137. """
  138. try:
  139. # 传入请求参数
  140. receiver = request.get_json()
  141. page = int(receiver.get('current', 1))
  142. page_size = int(receiver.get('size', 10))
  143. name_en_filter = receiver.get('name_en', None)
  144. name_zh_filter = receiver.get('name_zh', None)
  145. category = receiver.get('category', None)
  146. create_time = receiver.get('create_time', None)
  147. tag = receiver.get('tag', None)
  148. # 计算跳过的记录的数量
  149. skip_count = (page - 1) * page_size
  150. data, total = metric_list(skip_count, page_size, name_en_filter,
  151. name_zh_filter, category, create_time, tag)
  152. response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
  153. res = success(response_data, "success")
  154. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  155. except Exception as e:
  156. res = failed({}, {"error": f"{e}"})
  157. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  158. @bp.route('/graph/all', methods=['POST'])
  159. def data_metric_graph_all():
  160. """
  161. 获取数据指标图谱
  162. 请求参数:
  163. - id: 指标ID
  164. - type: 图谱类型,可选kinship/impact/all
  165. - meta: 是否返回元数据,true/false
  166. 返回:
  167. - 图谱数据
  168. """
  169. try:
  170. # 传入请求参数
  171. receiver = request.get_json()
  172. nodeid = receiver['id']
  173. type = receiver['type'] # kinship/impact/all
  174. meta = receiver['meta'] # true/false 是否返回元数据
  175. if type == 'kinship':
  176. result = metric_kinship_graph(nodeid, meta)
  177. elif type == 'impact':
  178. result = metric_impact_graph(nodeid, meta)
  179. else:
  180. result = metric_all_graph(nodeid, meta)
  181. return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
  182. except Exception as e:
  183. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  184. @bp.route('/list/graph', methods=['POST'])
  185. def data_metric_list_graph():
  186. """
  187. 获取数据指标列表图谱
  188. 请求参数:
  189. - tag: 标签ID
  190. 返回:
  191. - 图谱数据,包含节点和连线
  192. """
  193. try:
  194. # 传入请求参数
  195. receiver = request.get_json()
  196. if not receiver or 'tag' not in receiver:
  197. raise ValueError("Missing 'tag' parameter in request body")
  198. nodeid = receiver['tag']
  199. # 构建查询条件
  200. params = {}
  201. where_clause = ""
  202. if nodeid is not None:
  203. where_clause = "MATCH (n)-[:LABEL]->(la) WHERE id(la) = $nodeId"
  204. params['nodeId'] = nodeid
  205. query = f"""
  206. MATCH (n:DataMetric)
  207. OPTIONAL MATCH (n)-[:child]->(child)
  208. {where_clause}
  209. WITH
  210. collect(DISTINCT {{id: toString(id(n)), text: n.name_zh, type: split(labels(n)[0], '_')[1]}}) AS nodes,
  211. collect(DISTINCT {{id: toString(id(child)), text: child.name_zh, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
  212. collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
  213. RETURN nodes + nodes2 AS nodes, lines AS lines
  214. """
  215. driver = None
  216. try:
  217. driver = connect_graph()
  218. with driver.session() as session:
  219. result = session.run(query, **params)
  220. res = {}
  221. for item in result:
  222. res = {
  223. "nodes": [record for record in item['nodes'] if record['id']],
  224. "lines": [record for record in item['lines'] if record['from'] and record['to']],
  225. }
  226. except (ConnectionError, ValueError) as e:
  227. return json.dumps(failed({}, f"无法连接到数据库: {str(e)}"), ensure_ascii=False, cls=MyEncoder)
  228. finally:
  229. if driver:
  230. driver.close()
  231. return json.dumps(success(res, "success"), ensure_ascii=False, cls=MyEncoder)
  232. except Exception as e:
  233. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  234. @bp.route('/update', methods=['POST'])
  235. def data_metric_update():
  236. """
  237. 更新数据指标
  238. 请求参数:
  239. - id: 指标ID
  240. - 其他需要更新的属性
  241. 返回:
  242. - 处理结果,成功或失败
  243. """
  244. try:
  245. # 传入请求参数
  246. receiver = request.get_json()
  247. data_metric_edit(receiver)
  248. res = success({}, "success")
  249. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  250. except Exception as e:
  251. res = failed({}, {"error": f"{e}"})
  252. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  253. @bp.route('/check', methods=['POST'])
  254. def data_metric_check():
  255. """
  256. 检查指标计算公式中的变量
  257. 请求参数:
  258. - formula: 指标计算公式文本,格式如:指标名称 = 变量1 + 变量2 * 数字
  259. 返回:
  260. - 变量检查结果列表,包含每个变量的匹配信息
  261. """
  262. try:
  263. # 获取请求参数
  264. receiver = request.get_json()
  265. formula_text = receiver.get('formula', '')
  266. if not formula_text:
  267. res = failed({}, {"error": "公式文本不能为空"})
  268. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  269. # 调用核心业务逻辑
  270. result = metric_check(formula_text)
  271. res = success(result, "success")
  272. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  273. except Exception as e:
  274. res = failed({}, {"error": f"{e}"})
  275. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  276. @bp.route('/delete', methods=['POST'])
  277. def data_metric_delete():
  278. """
  279. 删除数据指标
  280. 请求参数:
  281. - id: 指标节点ID
  282. 返回:
  283. - 删除结果状态信息
  284. """
  285. try:
  286. # 获取请求参数
  287. receiver = request.get_json()
  288. metric_id = receiver.get('id')
  289. # 验证参数
  290. if not metric_id:
  291. res = failed({}, {"error": "指标ID不能为空"})
  292. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  293. # 转换为整数
  294. try:
  295. metric_id = int(metric_id)
  296. except (ValueError, TypeError):
  297. res = failed({}, {"error": "指标ID必须为整数"})
  298. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  299. # 调用核心业务逻辑执行删除
  300. delete_result = metric_delete(metric_id)
  301. # 根据删除结果返回响应
  302. if delete_result["success"]:
  303. res = success({
  304. "id": metric_id,
  305. "message": delete_result["message"]
  306. }, "删除成功")
  307. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  308. else:
  309. res = failed({
  310. "id": metric_id,
  311. "message": delete_result["message"]
  312. }, delete_result["message"])
  313. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  314. except Exception as e:
  315. res = failed({}, {"error": f"删除失败: {str(e)}"})
  316. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)