routes.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. import json
  2. from flask import Response, current_app, jsonify, request
  3. from app import db
  4. from app.api.data_interface import bp
  5. from app.core.data_interface import interface
  6. from app.core.data_rules.repository import DataRuleRepository
  7. from app.core.graph.graph_operations import (
  8. MyEncoder,
  9. connect_graph,
  10. create_or_get_node,
  11. )
  12. from app.core.meta_data import get_formatted_time, translate_and_parse
  13. from app.models.result import failed, success
  14. def _repository():
  15. configured = current_app.extensions.get("data_rule_repository")
  16. if configured is not None:
  17. return configured
  18. return DataRuleRepository(db.session)
  19. def _prepare_standard_write(value):
  20. if not isinstance(value, dict):
  21. raise ValueError("request body must be an object")
  22. receiver = dict(value)
  23. if "code" in receiver:
  24. raise ValueError(
  25. "data standard executable code is read-only migration data"
  26. )
  27. rule_version_id = receiver.get("rule_version_id")
  28. if rule_version_id is not None:
  29. _repository().require_published_rule_version(rule_version_id)
  30. receiver.pop("rule_version_status", None)
  31. return receiver
  32. @bp.route("/data/standard/add", methods=["POST"])
  33. def data_standard_add():
  34. try:
  35. receiver = _prepare_standard_write(request.get_json(silent=True))
  36. name_zh = receiver["name_zh"]
  37. name_en = translate_and_parse(name_zh)
  38. receiver["name_en"] = name_en[0]
  39. receiver["create_time"] = get_formatted_time()
  40. receiver["tag"] = json.dumps(receiver["tag"], ensure_ascii=False)
  41. create_or_get_node("data_standard", **receiver)
  42. res = success("", "success")
  43. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  44. except (KeyError, TypeError, ValueError) as e:
  45. res = failed(str(e), 400, {})
  46. return jsonify(res), 400
  47. except Exception as e:
  48. res = failed(str(e), 500, {})
  49. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  50. @bp.route("/data/standard/detail", methods=["POST"])
  51. def data_standard_detail():
  52. try:
  53. receiver = request.get_json()
  54. nodeid = receiver["id"] # id
  55. cql = """MATCH (n:data_standard) where id(n) = $nodeId
  56. RETURN properties(n) as property"""
  57. # Create a session from the driver returned by connect_graph
  58. with connect_graph().session() as session:
  59. result = session.run(cql, nodeId=nodeid).single()
  60. property = result["property"] if result else {}
  61. if "tag" not in property:
  62. property["tag"] = None
  63. else:
  64. property["tag"] = json.loads(property["tag"])
  65. if "describe" not in property:
  66. property["describe"] = None
  67. res = success(property, "success")
  68. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  69. except Exception as e:
  70. res = failed(str(e), 500, {})
  71. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  72. @bp.route("/data/standard/code", methods=["POST"])
  73. def data_standard_code():
  74. return (
  75. jsonify(
  76. failed(
  77. "旧数据标准代码生成入口已关闭,请发布 RuleVersion 后绑定",
  78. 410,
  79. {"semantics": "read_only_migration"},
  80. )
  81. ),
  82. 410,
  83. )
  84. @bp.route("/data/standard/update", methods=["POST"])
  85. def data_standard_update():
  86. try:
  87. receiver = _prepare_standard_write(request.get_json(silent=True))
  88. name_zh = receiver["name_zh"]
  89. name_en = translate_and_parse(name_zh)
  90. receiver["name_en"] = name_en[0]
  91. receiver["create_time"] = get_formatted_time()
  92. create_or_get_node("data_standard", **receiver)
  93. res = success("", "success")
  94. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  95. except (KeyError, TypeError, ValueError) as e:
  96. res = failed(str(e), 400, {})
  97. return jsonify(res), 400
  98. except Exception as e:
  99. res = failed(str(e), 500, {})
  100. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  101. @bp.route("/data/standard/list", methods=["POST"])
  102. def data_standard_list():
  103. try:
  104. receiver = request.get_json()
  105. page = int(receiver.get("current", 1))
  106. page_size = int(receiver.get("size", 10))
  107. name_en_filter = receiver.get("name_en", None)
  108. name_zh_filter = receiver.get("name_zh", None)
  109. category = receiver.get("category", None)
  110. time = receiver.get("time", None)
  111. skip_count = (page - 1) * page_size
  112. data, total = interface.standard_list(
  113. skip_count,
  114. page_size,
  115. name_en_filter,
  116. name_zh_filter,
  117. category,
  118. time,
  119. )
  120. response_data = {
  121. "records": data,
  122. "total": total,
  123. "size": page_size,
  124. "current": page,
  125. }
  126. res = success(response_data, "success")
  127. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  128. except Exception as e:
  129. res = failed(str(e), 500, {})
  130. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  131. @bp.route("/data/standard/graph/all", methods=["POST"])
  132. def data_standard_graph_all():
  133. try:
  134. receiver = request.get_json()
  135. nodeid = receiver["id"]
  136. type = receiver["type"] # kinship/impact/all
  137. if type == "kinship":
  138. result = interface.standard_kinship_graph(nodeid)
  139. elif type == "impact":
  140. result = interface.standard_impact_graph(nodeid)
  141. else:
  142. result = interface.standard_all_graph(nodeid)
  143. return json.dumps(
  144. success(result, "success"),
  145. ensure_ascii=False,
  146. cls=MyEncoder,
  147. )
  148. except Exception as e:
  149. res = failed(str(e), 500, {})
  150. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  151. @bp.route("/data/label/add", methods=["POST"])
  152. def data_label_add():
  153. try:
  154. receiver = request.get_json()
  155. name_zh = receiver["name_zh"]
  156. name_en = translate_and_parse(name_zh)
  157. receiver["name_en"] = name_en[0]
  158. receiver["create_time"] = get_formatted_time()
  159. create_or_get_node("DataLabel", **receiver)
  160. res = success("", "success")
  161. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  162. except Exception as e:
  163. res = failed(str(e), 500, {})
  164. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  165. @bp.route("/data/label/detail", methods=["POST"])
  166. def data_label_detail():
  167. try:
  168. receiver = request.get_json()
  169. nodeid = receiver["id"] # id
  170. cql = """MATCH (n:DataLabel) where id(n) = $nodeId
  171. RETURN properties(n) as property"""
  172. with connect_graph().session() as session:
  173. result = session.run(cql, nodeId=nodeid).single()
  174. property = result["property"] if result else {}
  175. if "describe" not in property:
  176. property["describe"] = None
  177. # 将传入的节点ID添加到返回信息中
  178. property["id"] = nodeid
  179. res = success(property, "success")
  180. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  181. except Exception as e:
  182. res = failed(str(e), 500, {})
  183. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  184. @bp.route("/data/label/list", methods=["POST"])
  185. def data_label_list():
  186. try:
  187. receiver = request.get_json()
  188. page = int(receiver.get("current", 1))
  189. page_size = int(receiver.get("size", 10))
  190. name_en_filter = receiver.get("name_en", None)
  191. name_zh_filter = receiver.get("name_zh", None)
  192. category = receiver.get("category", None)
  193. group = receiver.get("group", None)
  194. skip_count = (page - 1) * page_size
  195. data, total = interface.label_list(
  196. skip_count,
  197. page_size,
  198. name_en_filter,
  199. name_zh_filter,
  200. category,
  201. group,
  202. )
  203. response_data = {
  204. "records": data,
  205. "total": total,
  206. "size": page_size,
  207. "current": page,
  208. }
  209. res = success(response_data, "success")
  210. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  211. except Exception as e:
  212. res = failed(str(e), 500, {})
  213. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  214. @bp.route("/labellist", methods=["POST"])
  215. def interface_label_list():
  216. """获取 DataLabel 列表(支持多条件 category_filter 过滤)"""
  217. try:
  218. receiver = request.get_json() or {}
  219. page = int(receiver.get("current", 1))
  220. page_size = int(receiver.get("size", 10))
  221. name_en_filter = receiver.get("name_en")
  222. name_zh_filter = receiver.get("name_zh")
  223. category_filter = receiver.get("category_filter")
  224. group_filter = receiver.get("group")
  225. skip_count = (page - 1) * page_size
  226. data, total = interface.label_list(
  227. skip_count,
  228. page_size,
  229. name_en_filter,
  230. name_zh_filter,
  231. category_filter,
  232. group_filter,
  233. )
  234. # 只保留 id, name_zh, name_en 三个字段
  235. records = [
  236. {
  237. "id": item.get("id"),
  238. "name_zh": item.get("name_zh"),
  239. "name_en": item.get("name_en"),
  240. }
  241. for item in data
  242. ]
  243. response_data = {
  244. "records": records,
  245. "total": total,
  246. "size": page_size,
  247. "current": page,
  248. }
  249. res = success(response_data, "success")
  250. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  251. except Exception as e:
  252. res = failed(str(e), 500, {})
  253. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  254. @bp.route("/graphall", methods=["POST"])
  255. def interface_graph_all():
  256. """获取完整关系图谱"""
  257. try:
  258. receiver = request.get_json() or {}
  259. domain_id = receiver.get("id")
  260. include_meta = receiver.get("meta", True)
  261. if domain_id is None:
  262. res = failed("节点ID不能为空")
  263. return Response(
  264. json.dumps(res, ensure_ascii=False, cls=MyEncoder),
  265. mimetype="application/json",
  266. )
  267. try:
  268. domain_id = int(domain_id)
  269. except (ValueError, TypeError):
  270. res = failed(f"节点ID必须为整数, 收到的是: {domain_id}")
  271. return Response(
  272. json.dumps(res, ensure_ascii=False, cls=MyEncoder),
  273. mimetype="application/json",
  274. )
  275. graph_data = interface.graph_all(domain_id, include_meta)
  276. res = success(graph_data)
  277. return Response(
  278. json.dumps(res, ensure_ascii=False, cls=MyEncoder),
  279. mimetype="application/json",
  280. )
  281. except Exception as e:
  282. res = failed("获取图谱失败", error=str(e))
  283. return Response(
  284. json.dumps(res, ensure_ascii=False, cls=MyEncoder),
  285. mimetype="application/json",
  286. )
  287. @bp.route("/data/label/dynamic/identify", methods=["POST"])
  288. def data_label_dynamic_identify():
  289. try:
  290. receiver = request.get_json()
  291. name_filter = receiver.get("content", None)
  292. data = interface.dynamic_label_list(name_filter)
  293. res = success(data, "success")
  294. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  295. except Exception as e:
  296. res = failed(str(e), 500, {})
  297. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  298. @bp.route("/data/label/graph/all", methods=["POST"])
  299. def data_label_graph():
  300. try:
  301. receiver = request.get_json()
  302. nodeid = receiver["id"]
  303. type = receiver["type"] # kinship/impact/all
  304. if type == "kinship":
  305. result = interface.label_kinship_graph(nodeid)
  306. elif type == "impact":
  307. result = interface.label_impact_graph(nodeid)
  308. else:
  309. result = interface.label_kinship_graph(nodeid)
  310. return json.dumps(
  311. success(result, "success"),
  312. ensure_ascii=False,
  313. cls=MyEncoder,
  314. )
  315. except Exception as e:
  316. res = failed(str(e), 500, {})
  317. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  318. @bp.route("/metric/label/standard/delete", methods=["POST"])
  319. def metric_label_standard_delete():
  320. try:
  321. receiver = request.get_json()
  322. sourceid = receiver["sourceid"]
  323. targetid = receiver["targetid"]
  324. cql = """
  325. MATCH (source)-[r]-(target)
  326. WHERE id(source) = $sourceid AND id(target) = $targetid
  327. DELETE r
  328. """
  329. with connect_graph().session() as session:
  330. session.run(cql, sourceid=sourceid, targetid=targetid)
  331. res = success("", "success")
  332. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  333. except Exception as e:
  334. res = failed(str(e), 500, {})
  335. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  336. @bp.route("/data/label/delete", methods=["POST"])
  337. def data_label_delete():
  338. """Delete data label node"""
  339. try:
  340. receiver = request.get_json()
  341. node_id = receiver.get("id") if receiver else None
  342. if not node_id:
  343. return jsonify(failed("node id is required", 400, {}))
  344. try:
  345. node_id = int(node_id)
  346. except (ValueError, TypeError):
  347. return jsonify(failed("node id must be an integer", 400, {}))
  348. delete_result = interface.node_delete(node_id)
  349. message = delete_result.get("message") or ""
  350. if delete_result.get("success"):
  351. res = success(
  352. {
  353. "id": node_id,
  354. "message": message,
  355. },
  356. "delete success",
  357. )
  358. else:
  359. res = failed(
  360. message,
  361. 500,
  362. {
  363. "id": node_id,
  364. "message": message,
  365. },
  366. )
  367. return jsonify(res)
  368. except Exception as e:
  369. return jsonify(failed(f"delete failed: {str(e)}", 500, {}))