Explorar el Código

修复bug
清除冗余文件
清除冗余代码

maxiaolong hace 2 días
padre
commit
49421b587d
Se han modificado 100 ficheros con 1469 adiciones y 14464 borrados
  1. 0 12
      .cursor/pending_tasks.json
  2. 0 7
      .cursor/task_trigger.txt
  3. 0 338
      CYPHER_OPTIMIZATION_SUMMARY.md
  4. 0 325
      DATA_LABEL_DELETE_FEATURE.md
  5. 0 278
      DELETE_FEATURE_SUMMARY.md
  6. 0 46
      Deepseek-prompt.txt
  7. 0 336
      IMPLEMENTATION_CHECKLIST.md
  8. 0 366
      IMPLEMENTATION_SUMMARY.md
  9. 0 383
      METRIC_UPDATE_FIX.md
  10. 0 284
      README_METRIC_CHECK.md
  11. 0 284
      UPDATE_API_TEST_SUMMARY.md
  12. 0 33
      analyze_more.py
  13. 86 64
      app/__init__.py
  14. 0 111
      app/api/data_metric/README.md
  15. 0 5
      app/api/data_metric/__init__.py
  16. 0 381
      app/api/data_metric/routes.py
  17. 0 81
      app/api/data_model/README.md
  18. 0 6
      app/api/data_model/__init__.py
  19. 0 524
      app/api/data_model/routes.py
  20. 0 42
      app/api/data_resource/README.md
  21. 0 5
      app/api/data_resource/__init__.py
  22. 0 913
      app/api/data_resource/routes.py
  23. 85 47
      app/api/data_source/routes.py
  24. 0 96
      app/api/production_line/README.md
  25. 0 12
      app/api/production_line/__init__.py
  26. 0 269
      app/api/production_line/routes.py
  27. 43 26
      app/api/system/routes.py
  28. BIN
      app/app.rar
  29. 225 220
      app/core/business_domain/business_domain.py
  30. 0 37
      app/core/data_flow/测试.py
  31. 0 6
      app/core/data_interface/interface.py
  32. 0 116
      app/core/data_metric/README.md
  33. 0 2
      app/core/data_metric/__init__.py
  34. 0 1042
      app/core/data_metric/metric_interface.py
  35. 0 99
      app/core/data_model/README.md
  36. 0 2
      app/core/data_model/__init__.py
  37. 0 1887
      app/core/data_model/model.py
  38. 0 2
      app/core/data_resource/__init__.py
  39. 0 1825
      app/core/data_resource/resource.py
  40. 0 57
      app/core/production_line/README.md
  41. 0 10
      app/core/production_line/__init__.py
  42. 0 1238
      app/core/production_line/production_line.py
  43. 0 1
      app/test-jenkins
  44. 0 261
      check_project_status.py
  45. 0 0
      database/add_origin_source_field.sql
  46. 0 0
      database/alter_business_cards_simple.sql
  47. 0 0
      database/alter_business_cards_table.sql
  48. 0 0
      database/check_business_cards_table.sql
  49. 0 0
      database/rollback_business_cards_table.sql
  50. 0 0
      database/step_by_step_alter.sql
  51. 1030 0
      docs/CODE_DOCUMENTATION.md
  52. 0 215
      docs/api/metric-check-api.md
  53. 0 236
      docs/api_data_label_delete.md
  54. 0 264
      docs/api_data_metric_delete.md
  55. 0 0
      docs/archive/AUTO_TASK_EXECUTION_FIX.md
  56. 0 0
      docs/archive/CHECK_API_DIAGNOSTIC_REPORT.md
  57. 0 0
      docs/archive/CORS_FIX_README.md
  58. 0 0
      docs/archive/CURSOR_AUTO_EXECUTION_FIX.md
  59. 0 0
      docs/archive/CURSOR_AUTO_TASK_EXECUTION.md
  60. 0 0
      docs/archive/CURSOR_AUTO_TASK_TRIGGER.md
  61. 0 0
      docs/archive/CURSOR_FIX_README.md
  62. 0 0
      docs/archive/CURSOR_TASK_AUTOMATION_SUMMARY.md
  63. 0 0
      docs/archive/DDL_PARSER_TIMEOUT_FIX.md
  64. 0 0
      docs/archive/DDL_PARSE_FIX_SUMMARY.md
  65. 0 0
      docs/archive/DDL_Parse_API修复说明.md
  66. 0 0
      docs/archive/DDL_Parse_数组格式示例.json
  67. 0 0
      docs/archive/DDL_Parse_格式对比.json
  68. 0 0
      docs/archive/DDLparse格式.txt
  69. 0 0
      docs/archive/DataFlow_get_dataflow_by_id优化说明.md
  70. 0 0
      docs/archive/DataFlow_rule提取优化说明.md
  71. 0 0
      docs/archive/DataFlow_script_requirement优化说明.md
  72. 0 0
      docs/archive/DataFlow_task_list优化说明.md
  73. 0 0
      docs/archive/DataFlow_实施步骤优化说明.md
  74. 0 0
      docs/archive/N8N_WORKFLOW_SUMMARY.md
  75. 0 0
      docs/archive/REMOVAL_SUMMARY_CLEAN_LIST.md
  76. 0 0
      docs/archive/TEST_REPORT_218.md
  77. 0 0
      docs/archive/TRIGGER_OPTIMIZATION_SUMMARY.md
  78. 0 0
      docs/archive/WORKFLOW_UPDATE_SUMMARY.md
  79. 0 0
      docs/archive/add_webpage_talent_api_docs.md
  80. 0 0
      docs/archive/get-parsed-talents-api-documentation.md
  81. 0 0
      docs/archive/get_calendar_info.txt
  82. 0 0
      docs/archive/n8n_chat_trigger_error_diagnosis.md
  83. 0 0
      docs/archive/n8n_internal_error_fix.md
  84. 0 0
      docs/archive/n8n_tools_added_status.md
  85. 0 0
      docs/archive/n8n_workflow_enhancement_summary.md
  86. 0 0
      docs/archive/n8n_workflow_test_report.md
  87. 0 0
      docs/archive/n8n_workflow_test_success.md
  88. 0 0
      docs/archive/translate_api_documentation.md
  89. 0 0
      docs/archive/verify_check_api.md
  90. 0 0
      docs/archive/数据结构(2).txt
  91. 0 0
      docs/archive/每日签运需求.txt
  92. 0 0
      docs/archive/科室对照表_原始.sql
  93. 0 0
      docs/archive/返回格式.txt
  94. 0 371
      docs/diagrams/metric-check-flow.md
  95. 0 577
      docs/examples/metric-check-examples.md
  96. 0 255
      docs/features/metric-formula-check.md
  97. 0 232
      docs/路由简化报告_data_metric.md
  98. 0 80
      explore_api_218.py
  99. 0 67
      explore_api_endpoints.py
  100. 0 88
      migrate_meta_data_type.py

+ 0 - 12
.cursor/pending_tasks.json

@@ -1,12 +0,0 @@
-[
-  {
-    "task_id": 10,
-    "task_name": "测试",
-    "task_description": "# Task: 测试\n\n## Update Mode\n- **Mode**: Append (追加模式)\n- **Description**: 新数据将追加到目标表,不删除现有数据\n\n## Implementation Steps\n1. Extract data from source tables as specified in the DDL\n2. Apply transformation logic according to the rule:\n3. Generate Python program to implement the data transformation logic\n4. Write transformed data to target table using append mode\n5. Create an n8n workflow to schedule and execute the Python program",
-    "code_path": "app/core/data_flow",
-    "code_name": "测试",
-    "status": "processing",
-    "notified_at": "2025-12-04T20:25:27.822463",
-    "task_file": "G:\\code-lab\\DataOps-platform\\app\\core\\data_flow\\测试.py"
-  }
-]

+ 0 - 7
.cursor/task_trigger.txt

@@ -1,7 +0,0 @@
-CURSOR_AUTO_EXECUTE_TASK_TRIGGER
-生成时间: 2025-11-30 00:14:42
-状态: 所有任务已完成
-
-此文件用于触发Cursor自动执行任务。
-当此文件更新时间在5分钟内时,Cursor应该检查 .cursor/task_execute_instructions.md 并执行其中的任务。
-

+ 0 - 338
CYPHER_OPTIMIZATION_SUMMARY.md

@@ -1,338 +0,0 @@
-# Cypher 查询优化总结
-
-## 📋 优化概述
-
-优化了 `handle_id_metric` 函数中的 Cypher 查询脚本,简化查询逻辑,只查找第一层关系,提升查询性能。
-
-## 🔄 优化前后对比
-
-### 优化前的问题
-
-1. **复杂的嵌套查询**: 使用 UNWIND 展开 JSON 列表,然后多次 WITH 传递变量
-2. **重复的 OPTIONAL MATCH**: 对 DataModel 和 DataMetric 分别匹配
-3. **复杂的 CASE 逻辑**: 需要根据 type 判断使用哪个节点
-4. **难以维护**: 多层嵌套的 WITH 子句,逻辑不清晰
-
-### 优化后的改进
-
-✅ **直接查询第一层关系**: 使用简单的 OPTIONAL MATCH 查找所有第一层关系
-✅ **统一的节点匹配**: 使用 OR 条件一次性匹配 DataModel 和 DataMetric
-✅ **简化的聚合逻辑**: 使用列表推导式构建结果
-✅ **更好的可读性**: 清晰的注释和逻辑分层
-
-## 📊 优化详情
-
-### 查询结构
-
-#### 优化前(29行)
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-WITH apoc.convert.fromJsonList(n.id_list) AS info, n
-UNWIND info AS item
-WITH n, item.id AS model_or_metric_id, item.metaData AS meta_ids, item.type AS type
-
-// 数据模型或者数据指标
-OPTIONAL MATCH (n)-[:origin]->(m1:DataModel)
-WHERE type = 'model' AND id(m1) = model_or_metric_id
-WITH n, model_or_metric_id, meta_ids, type, m1
-OPTIONAL MATCH (n)-[:origin]->(m2:DataMetric)
-WHERE type = 'metric' AND id(m2) = model_or_metric_id
-WITH n, model_or_metric_id, meta_ids, type, m1, m2
-// 元数据
-OPTIONAL MATCH (n)-[:connection]-(meta:DataMeta)
-// 数据标签
-OPTIONAL MATCH (n)-[:LABEL]-(la:DataLabel)
-OPTIONAL MATCH (parent)-[:child]-(n)
-WITH properties(n) AS properties,collect(DISTINCT id(meta)) AS meta_list,parent,
-    {id: id(la), name_zh: la.name_zh} AS tag,
-    CASE 
-        WHEN type = 'model' THEN m1
-        WHEN type = 'metric' THEN m2
-        ELSE NULL
-    END AS m
-WITH {model_name: m.name_zh, model_id: id(m), meta: meta_list} AS result, properties,
-     tag,{id:id(parent),name_zh:parent.name_zh} as parentId
-RETURN collect(result) AS id_list, properties, tag,collect(parentId)as parentId
-```
-
-#### 优化后(46行,但逻辑更清晰)
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-
-// 查找第一层关系 - 来源关系(DataModel 和 DataMetric)
-OPTIONAL MATCH (n)-[:origin]->(origin)
-WHERE origin:DataModel OR origin:DataMetric
-
-// 查找第一层关系 - 元数据连接
-OPTIONAL MATCH (n)-[:connection]->(meta:DataMeta)
-
-// 查找第一层关系 - 数据标签
-OPTIONAL MATCH (n)-[:LABEL]->(label:DataLabel)
-
-// 查找第一层关系 - 父节点
-OPTIONAL MATCH (parent:DataMetric)-[:child]->(n)
-
-// 聚合数据
-WITH n, 
-     collect(DISTINCT label) AS labels,
-     collect(DISTINCT parent) AS parents,
-     collect(DISTINCT origin) AS origins,
-     collect(DISTINCT meta) AS metas
-
-// 构建 id_list(来源信息和元数据)
-WITH n, labels, parents,
-     [origin IN origins | {
-         model_name: origin.name_zh,
-         model_id: id(origin),
-         meta: [m IN metas | id(m)],
-         type: CASE 
-             WHEN 'DataModel' IN labels(origin) THEN 'model'
-             WHEN 'DataMetric' IN labels(origin) THEN 'metric'
-             ELSE null
-         END
-     }] AS id_list_data
-
-// 返回结果
-RETURN 
-    properties(n) AS properties,
-    id_list_data AS id_list,
-    CASE WHEN size(labels) > 0 
-         THEN {id: id(labels[0]), name_zh: labels[0].name_zh}
-         ELSE null
-    END AS tag,
-    [p IN parents | {id: id(p), name_zh: p.name_zh}] AS parentId
-```
-
-## 🎯 优化亮点
-
-### 1. 查询第一层关系
-
-**优化前**: 依赖节点属性中的 JSON 数据(`n.id_list`),需要先解析 JSON
-**优化后**: 直接通过图关系查询,更符合图数据库的特性
-
-### 2. 统一节点匹配
-
-**优化前**:
-```cypher
-OPTIONAL MATCH (n)-[:origin]->(m1:DataModel)
-WHERE type = 'model' AND id(m1) = model_or_metric_id
-...
-OPTIONAL MATCH (n)-[:origin]->(m2:DataMetric)
-WHERE type = 'metric' AND id(m2) = model_or_metric_id
-```
-
-**优化后**:
-```cypher
-OPTIONAL MATCH (n)-[:origin]->(origin)
-WHERE origin:DataModel OR origin:DataMetric
-```
-
-### 3. 简化数据聚合
-
-**优化前**: 使用多层 WITH 子句传递和组合数据
-**优化后**: 一次性聚合所有相关节点,然后使用列表推导式构建结果
-
-### 4. 清晰的关系查询
-
-每种关系类型都有明确的注释和独立的 OPTIONAL MATCH:
-- `origin` - 来源关系(DataModel/DataMetric)
-- `connection` - 元数据连接(DataMeta)
-- `LABEL` - 数据标签(DataLabel)
-- `child` - 父子关系(DataMetric)
-
-## 📈 性能改进
-
-### 查询效率
-
-| 指标 | 优化前 | 优化后 | 改进 |
-|------|--------|--------|------|
-| OPTIONAL MATCH 次数 | 5次 | 4次 | ↓ 20% |
-| WITH 子句层数 | 5层 | 2层 | ↓ 60% |
-| CASE 表达式 | 2个 | 2个 | = |
-| 依赖 APOC 插件 | 是 | 否 | ✅ |
-
-### 主要优势
-
-1. **减少依赖**: 不再依赖 `apoc.convert.fromJsonList`
-2. **简化逻辑**: 减少 WITH 子句嵌套层数
-3. **提升可读性**: 每个关系类型独立查询,逻辑清晰
-4. **更好的扩展性**: 添加新的关系类型更容易
-
-## 🔍 查询逻辑说明
-
-### 第一步:匹配目标节点
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-```
-根据节点ID匹配 DataMetric 节点。
-
-### 第二步:查找第一层关系
-```cypher
-OPTIONAL MATCH (n)-[:origin]->(origin)
-WHERE origin:DataModel OR origin:DataMetric
-
-OPTIONAL MATCH (n)-[:connection]->(meta:DataMeta)
-
-OPTIONAL MATCH (n)-[:LABEL]->(label:DataLabel)
-
-OPTIONAL MATCH (parent:DataMetric)-[:child]->(n)
-```
-使用 4 个 OPTIONAL MATCH 查询所有第一层关系。
-
-### 第三步:聚合节点数据
-```cypher
-WITH n, 
-     collect(DISTINCT label) AS labels,
-     collect(DISTINCT parent) AS parents,
-     collect(DISTINCT origin) AS origins,
-     collect(DISTINCT meta) AS metas
-```
-将每种类型的关联节点聚合成列表。
-
-### 第四步:构建返回数据
-```cypher
-WITH n, labels, parents,
-     [origin IN origins | {
-         model_name: origin.name_zh,
-         model_id: id(origin),
-         meta: [m IN metas | id(m)],
-         type: CASE 
-             WHEN 'DataModel' IN labels(origin) THEN 'model'
-             WHEN 'DataMetric' IN labels(origin) THEN 'metric'
-             ELSE null
-         END
-     }] AS id_list_data
-```
-使用列表推导式构建 `id_list` 数据结构。
-
-### 第五步:返回结果
-```cypher
-RETURN 
-    properties(n) AS properties,
-    id_list_data AS id_list,
-    CASE WHEN size(labels) > 0 
-         THEN {id: id(labels[0]), name_zh: labels[0].name_zh}
-         ELSE null
-    END AS tag,
-    [p IN parents | {id: id(p), name_zh: p.name_zh}] AS parentId
-```
-返回节点属性、关联数据列表、标签和父节点信息。
-
-## 📋 返回数据结构
-
-```json
-{
-  "properties": {
-    "name_zh": "指标名称",
-    "name_en": "metric_name",
-    "category": "应用类",
-    "create_time": "2025-11-03 11:31:40",
-    ...
-  },
-  "id_list": [
-    {
-      "model_name": "数据模型名称",
-      "model_id": 123,
-      "meta": [456, 789],
-      "type": "model"
-    }
-  ],
-  "tag": {
-    "id": 100,
-    "name_zh": "标签名称"
-  },
-  "parentId": [
-    {
-      "id": 200,
-      "name_zh": "父节点名称"
-    }
-  ]
-}
-```
-
-## ⚠️ 注意事项
-
-### 1. 数据完整性
-优化后的查询不再依赖节点的 `id_list` 属性(JSON 字段),而是直接查询图关系。确保图关系数据的完整性。
-
-### 2. 兼容性考虑
-如果旧数据中关系不完整,可能返回的 `id_list` 为空。建议:
-- 运行数据迁移脚本,从 JSON 字段重建关系
-- 或保留原查询作为备用方案
-
-### 3. 性能监控
-在生产环境中监控查询性能:
-- 响应时间
-- 数据库负载
-- 返回数据量
-
-## 🚀 后续优化建议
-
-### 1. 添加索引
-为提升查询性能,建议在以下字段上创建索引:
-```cypher
-CREATE INDEX ON :DataMetric(name_zh);
-CREATE INDEX ON :DataMetric(create_time);
-```
-
-### 2. 批量查询优化
-如果需要查询多个指标详情,可以修改为批量查询:
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) IN $nodeIds
-...
-```
-
-### 3. 分页支持
-对于关联节点较多的情况,考虑添加分页:
-```cypher
-OPTIONAL MATCH (n)-[:connection]->(meta:DataMeta)
-WITH n, collect(meta)[0..10] AS metas
-```
-
-## ✅ 测试验证
-
-### 测试场景
-
-1. ✅ 节点存在,有完整关系
-2. ✅ 节点存在,部分关系为空
-3. ✅ 节点存在,无任何关系
-4. ✅ 节点不存在
-5. ✅ 关联多个不同类型的节点
-
-### 测试方法
-
-```python
-from app.core.data_metric.metric_interface import handle_id_metric
-
-# 测试查询
-result = handle_id_metric(1378)
-print(result)
-```
-
-## 📝 总结
-
-### 主要改进
-
-✅ **简化查询逻辑** - 减少嵌套层数,提高可读性
-✅ **移除 APOC 依赖** - 不再依赖 `apoc.convert.fromJsonList`
-✅ **统一节点匹配** - 使用 OR 条件一次性匹配多种节点类型
-✅ **清晰的关系查询** - 每种关系独立查询,便于维护
-✅ **更好的性能** - 减少不必要的查询步骤
-
-### 实施状态
-
-- ✅ 代码已更新
-- ✅ Linter 检查通过
-- ✅ 保持向后兼容
-- ✅ 文档已更新
-
----
-
-**优化完成时间**: 2025-11-03
-**文件路径**: `app/core/data_metric/metric_interface.py`
-**函数**: `handle_id_metric` (行 335-404)
-

+ 0 - 325
DATA_LABEL_DELETE_FEATURE.md

@@ -1,325 +0,0 @@
-# 数据标签删除功能实现总结
-
-## 功能概述
-
-为 DataOps 平台新增了 DataLabel 节点删除功能,允许用户通过 API 接口删除指定的数据标签节点及其所有关联关系。
-
-## 实现内容
-
-### 1. 核心业务逻辑函数 (`app/core/data_interface/interface.py`)
-
-**新增函数**: `node_delete(node_id)`
-
-#### 函数功能
-- 删除指定 ID 的 DataLabel 节点
-- 自动清除与该节点相关的所有关系
-- 提供完整的错误处理和日志记录
-
-#### 实现细节
-
-```python
-def node_delete(node_id):
-    """
-    删除 DataLabel 节点及其所有关联关系
-    
-    Args:
-        node_id: 节点ID(整数)
-        
-    Returns:
-        dict: 删除结果,包含 success 状态和 message 信息
-    """
-```
-
-**执行流程**:
-
-1. **连接数据库**
-   ```python
-   driver = connect_graph()
-   if not driver:
-       return {"success": False, "message": "无法连接到数据库"}
-   ```
-
-2. **验证节点存在**
-   ```cypher
-   MATCH (n:DataLabel)
-   WHERE id(n) = $nodeId
-   RETURN n
-   ```
-
-3. **删除节点和关系**
-   ```cypher
-   MATCH (n:DataLabel)
-   WHERE id(n) = $nodeId
-   DETACH DELETE n
-   RETURN count(n) as deleted_count
-   ```
-
-4. **返回结果**
-   - 成功: `{"success": True, "message": "成功删除..."}`
-   - 失败: `{"success": False, "message": "错误信息..."}`
-
-#### 关键特性
-
-✅ **类型验证**: 只删除 DataLabel 类型的节点
-✅ **存在性检查**: 删除前验证节点是否存在
-✅ **关系清理**: 使用 `DETACH DELETE` 自动删除所有关联关系
-✅ **错误处理**: 完整的异常捕获和错误返回
-✅ **日志记录**: 详细记录删除操作和结果
-
-### 2. API 接口实现 (`app/api/data_interface/routes.py`)
-
-**新增接口**: `POST /api/data/label/delete`
-
-#### 接口功能
-- 接收前端删除请求
-- 验证请求参数
-- 调用核心业务逻辑
-- 返回标准化的响应
-
-#### 实现细节
-
-```python
-@bp.route('/data/label/delete', methods=['POST'])
-def data_label_delete():
-    """删除数据标签节点"""
-```
-
-**处理流程**:
-
-1. **获取参数**
-   ```python
-   node_id = receiver.get('id')
-   ```
-
-2. **参数验证**
-   ```python
-   if not node_id:
-       return failed({}, {"error": "节点ID不能为空"})
-   
-   node_id = int(node_id)  # 转换为整数
-   ```
-
-3. **执行删除**
-   ```python
-   delete_result = interface.node_delete(node_id)
-   ```
-
-4. **返回响应**
-   ```python
-   if delete_result["success"]:
-       return success({"id": node_id, "message": ...}, "删除成功")
-   else:
-       return failed({"id": node_id, "message": ...}, ...)
-   ```
-
-#### 响应格式
-
-**成功响应**:
-```json
-{
-  "code": 200,
-  "data": {
-    "id": 82,
-    "message": "成功删除 DataLabel 节点 (ID: 82)"
-  },
-  "msg": "删除成功"
-}
-```
-
-**失败响应**:
-```json
-{
-  "code": 500,
-  "data": {
-    "id": 82,
-    "message": "DataLabel 节点不存在 (ID: 82)"
-  },
-  "msg": "DataLabel 节点不存在 (ID: 82)"
-}
-```
-
-## 技术特点
-
-### 1. 安全性
-
-- ✅ **参数验证**: 严格的输入验证,防止无效请求
-- ✅ **类型检查**: 只删除 DataLabel 类型节点
-- ✅ **存在性验证**: 删除前检查节点是否存在
-- ✅ **异常处理**: 全面的错误捕获和处理
-
-### 2. 可靠性
-
-- ✅ **事务性**: 使用 Neo4j 的原子操作
-- ✅ **完整性**: `DETACH DELETE` 确保关系一并删除
-- ✅ **日志记录**: 详细的操作日志便于追踪
-- ✅ **错误反馈**: 清晰的错误信息
-
-### 3. 可维护性
-
-- ✅ **清晰的代码结构**: 分离业务逻辑和接口层
-- ✅ **完整的文档**: 函数和接口都有详细注释
-- ✅ **标准化响应**: 统一的 success/failed 响应格式
-- ✅ **日志支持**: 便于问题排查和审计
-
-## 使用示例
-
-### 请求示例
-
-```bash
-curl -X POST http://localhost:5000/api/data/label/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": 82}'
-```
-
-### Python 客户端示例
-
-```python
-import requests
-
-def delete_data_label(label_id):
-    url = "http://localhost:5000/api/data/label/delete"
-    response = requests.post(url, json={"id": label_id})
-    result = response.json()
-    
-    if result['code'] == 200:
-        print(f"✅ {result['data']['message']}")
-        return True
-    else:
-        print(f"❌ {result.get('msg', 'Unknown error')}")
-        return False
-
-# 使用示例
-delete_data_label(82)
-```
-
-### JavaScript 客户端示例
-
-```javascript
-async function deleteDataLabel(labelId) {
-  try {
-    const response = await fetch('/api/data/label/delete', {
-      method: 'POST',
-      headers: {'Content-Type': 'application/json'},
-      body: JSON.stringify({ id: labelId })
-    });
-    
-    const result = await response.json();
-    
-    if (result.code === 200) {
-      console.log(`✅ ${result.data.message}`);
-      return true;
-    } else {
-      console.error(`❌ ${result.msg}`);
-      return false;
-    }
-  } catch (error) {
-    console.error('请求失败:', error);
-    return false;
-  }
-}
-
-// 使用示例
-deleteDataLabel(82);
-```
-
-## 测试建议
-
-### 1. 功能测试
-
-- ✅ 删除存在的 DataLabel 节点
-- ✅ 尝试删除不存在的节点
-- ✅ 使用无效的节点ID(字符串、null等)
-- ✅ 验证关系是否被正确删除
-
-### 2. 边界测试
-
-- ✅ 删除有大量关系的节点
-- ✅ 删除没有关系的节点
-- ✅ 并发删除同一节点
-
-### 3. 错误测试
-
-- ✅ 数据库连接失败场景
-- ✅ 无效参数场景
-- ✅ 网络超时场景
-
-## 与其他功能的关系
-
-### 数据指标删除功能
-
-本功能的实现参考了 `metric_delete` 的设计模式:
-
-| 特性 | metric_delete | node_delete |
-|------|---------------|-------------|
-| 节点类型 | DataMetric | DataLabel |
-| 删除方式 | DETACH DELETE | DETACH DELETE |
-| 参数验证 | ✅ | ✅ |
-| 错误处理 | ✅ | ✅ |
-| 日志记录 | ✅ | ✅ |
-| 返回格式 | 统一 dict | 统一 dict |
-
-### 设计一致性
-
-两个删除功能保持了一致的设计:
-- 相同的参数验证逻辑
-- 相同的错误处理方式
-- 相同的返回格式
-- 相同的日志记录规范
-
-## 注意事项
-
-### ⚠️ 重要提醒
-
-1. **不可逆操作**: 删除操作是永久性的,无法撤销
-2. **级联影响**: 删除标签会影响引用该标签的其他节点
-3. **权限控制**: 建议在生产环境中添加权限验证
-4. **审计日志**: 所有删除操作都会记录在日志中
-
-### 💡 最佳实践
-
-1. **删除前确认**: 建议前端实现二次确认机制
-2. **依赖检查**: 删除前检查是否有其他节点引用该标签
-3. **批量删除**: 如需批量删除,建议逐个调用而非修改接口
-4. **错误处理**: 前端应妥善处理各种错误情况
-
-## 相关文件
-
-### 修改的文件
-
-- `app/core/data_interface/interface.py` - 新增 `node_delete` 函数
-- `app/api/data_interface/routes.py` - 新增 `/data/label/delete` 接口
-
-### 新增的文件
-
-- `docs/api_data_label_delete.md` - API 接口文档
-- `DATA_LABEL_DELETE_FEATURE.md` - 本功能总结文档
-
-## 部署说明
-
-### 代码部署
-
-1. 确保 Neo4j 连接配置正确
-2. 重启 Flask 应用加载新代码
-3. 验证接口是否可访问
-
-### 验证测试
-
-```bash
-# 测试接口是否可用
-curl -X POST http://your-server/api/data/label/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": 999}'
-
-# 预期返回节点不存在的错误
-```
-
-## 总结
-
-✅ **功能完整**: 实现了完整的删除功能
-✅ **设计规范**: 遵循项目的代码规范和设计模式
-✅ **文档齐全**: 提供了详细的 API 文档和功能说明
-✅ **测试友好**: 提供了多种测试示例
-✅ **生产就绪**: 具备完整的错误处理和日志记录
-
-该功能已经可以部署到生产环境使用!
-

+ 0 - 278
DELETE_FEATURE_SUMMARY.md

@@ -1,278 +0,0 @@
-# 数据指标删除功能实现总结
-
-## 📋 功能概述
-
-为 DataOps 平台的数据指标模块添加了完整的删除功能,包括核心业务逻辑和 RESTful API 接口。
-
-## ✅ 实现内容
-
-### 1. 核心业务函数
-
-**文件**: `app/core/data_metric/metric_interface.py`
-
-**新增函数**: `metric_delete(metric_node_id)`
-
-**功能特性**:
-- ✅ 连接 Neo4j 图数据库
-- ✅ 验证节点存在性
-- ✅ 使用 `DETACH DELETE` 自动删除节点及所有关联关系
-- ✅ 完善的错误处理和日志记录
-- ✅ 返回标准化的结果格式
-
-**代码位置**: 行 828-893
-
-### 2. API 接口
-
-**文件**: `app/api/data_metric/routes.py`
-
-**新增路由**: `POST /api/data_metric/delete`
-
-**接口特性**:
-- ✅ 参数验证(必填、类型检查)
-- ✅ 调用核心业务逻辑
-- ✅ 统一的响应格式
-- ✅ 完整的异常处理
-
-**代码位置**: 行 328-375
-
-### 3. 函数导入
-
-**更新文件**: `app/api/data_metric/routes.py`
-
-**更新内容**: 在导入列表中添加 `metric_delete` 函数
-
-**代码位置**: 行 15-20
-
-## 🔧 技术实现
-
-### Cypher 查询
-
-#### 检查节点存在
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-RETURN n
-```
-
-#### 删除节点和关系
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-DETACH DELETE n
-RETURN count(n) as deleted_count
-```
-
-### 删除机制
-
-使用 Neo4j 的 `DETACH DELETE` 语句,自动处理:
-- 所有传入关系(incoming relationships)
-- 所有传出关系(outgoing relationships)
-- 节点本身
-
-### 涉及的关系类型
-
-| 关系类型 | 方向 | 目标节点 | 说明 |
-|---------|------|---------|------|
-| origin | 传出 | DataModel/DataMetric | 指标来源 |
-| connection | 传出 | DataMeta | 元数据连接 |
-| LABEL | 传出 | DataLabel | 数据标签 |
-| child | 双向 | DataMetric | 父子关系 |
-
-## 📊 API 接口文档
-
-### 请求格式
-
-```http
-POST /api/data_metric/delete
-Content-Type: application/json
-
-{
-  "id": 1378
-}
-```
-
-### 成功响应
-
-```json
-{
-  "code": 200,
-  "msg": "删除成功",
-  "data": {
-    "id": 1378,
-    "message": "成功删除数据指标节点 (ID: 1378)"
-  }
-}
-```
-
-### 失败响应
-
-```json
-{
-  "code": 500,
-  "msg": "数据指标节点不存在 (ID: 1378)",
-  "data": {
-    "id": 1378,
-    "message": "数据指标节点不存在 (ID: 1378)"
-  }
-}
-```
-
-## 🛡️ 错误处理
-
-### 异常情况覆盖
-
-| 场景 | 处理方式 | 返回信息 |
-|------|---------|---------|
-| 数据库连接失败 | 记录错误日志 | "无法连接到数据库" |
-| 节点不存在 | 记录警告日志 | "数据指标节点不存在" |
-| 参数缺失 | 参数验证 | "指标ID不能为空" |
-| 参数类型错误 | 类型转换验证 | "指标ID必须为整数" |
-| 删除异常 | 捕获异常 | "删除失败: [详情]" |
-
-## 📝 日志记录
-
-### 日志级别和内容
-
-| 级别 | 场景 | 内容模板 |
-|-----|------|---------|
-| ERROR | 连接失败 | "无法连接到数据库" |
-| WARNING | 节点不存在 | "数据指标节点不存在: ID={id}" |
-| INFO | 删除成功 | "成功删除数据指标节点: ID={id}" |
-| WARNING | 删除失败 | "删除失败,节点可能已被删除: ID={id}" |
-| ERROR | 异常 | "删除数据指标节点失败: {error}" |
-
-## 🧪 测试验证
-
-### 建议测试场景
-
-#### 单元测试
-1. ✅ 删除存在的节点
-2. ✅ 删除不存在的节点
-3. ✅ 无效节点ID(非整数、null)
-4. ✅ 数据库连接失败
-5. ✅ 关系清理验证
-
-#### 集成测试
-1. ✅ 创建 -> 删除 -> 验证
-2. ✅ 带关系节点删除 -> 关系清理验证
-3. ✅ 相关节点状态验证
-
-#### API 测试
-```bash
-# 测试删除存在的节点
-curl -X POST http://localhost:5500/api/data_metric/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": 1378}'
-
-# 测试删除不存在的节点
-curl -X POST http://localhost:5500/api/data_metric/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": 99999}'
-
-# 测试参数缺失
-curl -X POST http://localhost:5500/api/data_metric/delete \
-  -H "Content-Type: application/json" \
-  -d '{}'
-
-# 测试无效参数
-curl -X POST http://localhost:5500/api/data_metric/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": "invalid"}'
-```
-
-## ⚠️ 注意事项
-
-### 重要提示
-
-1. **不可恢复**: 删除操作是永久性的,无法撤销
-2. **级联影响**: 删除会清除所有关联关系
-3. **权限控制**: 生产环境建议添加权限验证
-4. **审计日志**: 建议记录操作人和操作时间
-5. **业务验证**: 删除前检查业务依赖
-
-### 最佳实践
-
-1. **前端确认**: 添加二次确认对话框
-2. **软删除**: 重要数据考虑软删除(标记而不是物理删除)
-3. **批量操作**: 批量删除时逐个处理并记录结果
-4. **事务保证**: 当前实现确保原子性
-5. **用户反馈**: 清晰的成功/失败提示
-
-## 📁 文件变更清单
-
-| 文件 | 变更类型 | 说明 |
-|------|---------|------|
-| `app/core/data_metric/metric_interface.py` | 新增 | 添加 `metric_delete` 函数 |
-| `app/api/data_metric/routes.py` | 新增/修改 | 添加 `/delete` 路由,更新导入 |
-| `docs/api_data_metric_delete.md` | 新增 | API 接口完整文档 |
-| `DELETE_FEATURE_SUMMARY.md` | 新增 | 功能实现总结文档 |
-
-## 📊 代码统计
-
-- **新增函数**: 2个
-  - `metric_delete()` - 核心业务逻辑
-  - `data_metric_delete()` - API 路由处理
-
-- **新增代码行**: 约 130 行
-  - 核心函数: 65 行
-  - API 接口: 48 行
-  - 导入更新: 1 行
-  - 文档: 400+ 行
-
-## ✅ 代码质量
-
-- ✅ 所有 Linter 检查通过
-- ✅ 遵循项目编码规范
-- ✅ 完整的类型注释和文档字符串
-- ✅ 统一的错误处理模式
-- ✅ 标准化的日志记录
-- ✅ 符合 RESTful API 设计
-
-## 🚀 后续建议
-
-### 功能增强
-
-1. **批量删除**: 支持一次删除多个节点
-2. **软删除**: 添加逻辑删除标记,支持恢复
-3. **权限控制**: 集成用户权限验证
-4. **审计日志**: 记录操作人、IP、时间戳
-5. **级联删除选项**: 提供选项控制是否删除关联节点
-
-### 性能优化
-
-1. **批量操作**: 使用事务批量删除多个节点
-2. **异步处理**: 大量删除操作考虑异步队列
-3. **缓存清理**: 删除后清理相关缓存
-
-### 安全加固
-
-1. **权限验证**: 确保只有授权用户可以删除
-2. **操作限制**: 添加删除频率限制
-3. **数据备份**: 删除前自动备份重要数据
-
-## 📚 相关文档
-
-- [API 接口详细文档](docs/api_data_metric_delete.md)
-- [数据指标 API README](app/api/data_metric/README.md)
-- [Neo4j 图数据库操作](app/core/graph/graph_operations.py)
-
-## 👥 维护信息
-
-- **创建日期**: 2025-11-03
-- **版本**: v1.0
-- **状态**: ✅ 已完成,可以使用
-
----
-
-## 🎉 总结
-
-成功为 DataOps 平台的数据指标模块实现了完整的删除功能,包括:
-
-✅ **核心业务逻辑** - 健壮的删除函数,支持节点和关系的完整清理
-✅ **RESTful API** - 标准化的删除接口,完善的参数验证和错误处理
-✅ **错误处理** - 全面的异常捕获和友好的错误提示
-✅ **日志记录** - 完整的操作日志,便于问题排查
-✅ **文档完善** - 详细的 API 文档和使用说明
-
-该功能已经可以直接使用,满足生产环境要求!🎊
-

+ 0 - 46
Deepseek-prompt.txt

@@ -1,46 +0,0 @@
-你是一名数据库工程师,正在构建一个PostgreSQL数据中的汇总逻辑。请为以下需求生成一段标准的 PostgreSQL SQL 脚本:
-1.有一个源表: personal_loan_resource,它的定义语句如下:
-CREATE TABLE personal_loan_resource (
-    employee_profit_sharing_ratio numeric(18,6) COMMENT '员工分润比例',
-    customer_level_identifier smallint COMMENT '管户层级标识',
-    superior_organization_code varchar(15) COMMENT '上级机构编码',
-    amount_disbursed numeric(15,2) COMMENT '发放金额',
-    unified_authentication_code_2 varchar(255) COMMENT '统一认证码2',
-    customer_weight_coefficient numeric(18,6) COMMENT '客户权重系数',
-    monthly_interest_received_total numeric(15,2) COMMENT '月累计实收利息',
-    institution_code varchar(15) COMMENT '机构编码',
-    agreement_status_code varchar(10) COMMENT '协议状态代码',
-    secondary_subject_code varchar(50) COMMENT '二级科目编码',
-    unified_authentication_code_1 varchar(255) COMMENT '统一认证码1',
-    data_date date COMMENT '数据日期',
-    customer_category_identifier smallint COMMENT '客户类别标识',
-    customer_id varchar(50) COMMENT '客户编号',
-    disbursement_date date COMMENT '发放日期'
-);
-COMMENT ON TABLE personal_loan_resource IS '个人贷款_资源';
-
-2.有一个目标表:customer_service_ledger,它的定义语句如下:
-CREATE TABLE customer_service_ledger (
-    original_profit_sharing_record char COMMENT '原始分润记录',
-    unified_authentication_code_2 varchar(255) COMMENT '统一认证码2',
-    data_date date COMMENT '数据日期',
-    customer_category_identifier smallint COMMENT '客户类别标识',
-    secondary_subject varchar(50) COMMENT '二级科目',
-    primary_subject varchar(50) COMMENT '一级科目',
-    unified_authentication_code_1 varchar(255) COMMENT '统一认证码1',
-    customer_id varchar(50) COMMENT '客户编号',
-    customer_level_identifier smallint COMMENT '管户层级标识',
-    employee_profit_sharing_ratio numeric(18,6) COMMENT '员工分润比例',
-    customer_weight_coefficient numeric(18,6) COMMENT '客户权重系数',
-    amount numeric(15,2) COMMENT '金额',
-    superior_organization_code varchar(15) COMMENT '上级机构编码',
-    institution_code varchar(15) COMMENT '机构编码'
-);
-COMMENT ON TABLE customer_service_ledger IS '客户维业绩流水账';
-
-3.处理逻辑为:从源表里读取记录,根据贷款发放日期进行判断,如果贷款发放日期与数据日期为同一个月,则提取发放金额作为金额,在目标表里插入一条记录,“一级科目”字段设置内容为“当期个人贷款新发放金额”。;如果贷款发放日期与数据日期不是同一个月,则提取月累计实收利息作为金额,在目标表里插入一条记录,“一级科目”字段设置内容为“当期个人贷款月累计收息”。其他字段内容按照目标表的字段定义,从源表中读取对应字段进行填充。
-4.脚本应使用标准的 PostgreSQL 语法,适合在 Airflow、Python 脚本、或调度系统中调用;
-5.无需使用 UPSERT 或 ON CONFLICT
-6.请直接输出SQL,无需进行解释。
-7.请给这段sql起个英文名,不少于三个英文单词,使用"_"分隔,采用蛇形命名法。把sql的名字作为注释写在返回的sql中。
-8.生成的sql在向目标表插入数据的时候,向create_time字段写入当前日期时间now(),不用处理update_time字段

+ 0 - 336
IMPLEMENTATION_CHECKLIST.md

@@ -1,336 +0,0 @@
-# 指标公式检查功能 - 实现检查清单
-
-## ✅ 已完成项目
-
-### 1. 核心功能实现
-
-- [x] **metric_check 函数** (`app/core/data_metric/metric_interface.py`)
-  - [x] 公式解析逻辑
-  - [x] 运算符识别(+, -, *, /, (), (), [], {})
-  - [x] 中文变量提取
-  - [x] 数字过滤
-  - [x] 变量去重
-  - [x] Neo4j查询集成
-  - [x] 模糊匹配实现
-  - [x] 结果格式化
-  - [x] 错误处理
-  - [x] 日志记录
-
-### 2. API接口实现
-
-- [x] **data_metric_check 路由** (`app/api/data_metric/routes.py`)
-  - [x] POST方法支持
-  - [x] 请求参数验证
-  - [x] formula参数提取
-  - [x] 核心函数调用
-  - [x] JSON响应格式化
-  - [x] 错误处理
-  - [x] 导入语句更新
-
-### 3. 文档编写
-
-- [x] **API文档** (`docs/api/metric-check-api.md`)
-  - [x] 接口信息
-  - [x] 请求参数说明
-  - [x] 响应格式说明
-  - [x] 错误处理文档
-  - [x] 示例代码(Python, JavaScript, cURL)
-  - [x] 注意事项
-
-- [x] **功能文档** (`docs/features/metric-formula-check.md`)
-  - [x] 功能概述
-  - [x] 技术实现细节
-  - [x] 使用场景
-  - [x] 扩展建议
-  - [x] 依赖说明
-  - [x] 维护建议
-
-- [x] **流程图文档** (`docs/diagrams/metric-check-flow.md`)
-  - [x] 整体架构图
-  - [x] 详细流程图
-  - [x] 数据流图
-  - [x] 时序图
-  - [x] 状态转换图
-
-- [x] **使用示例** (`docs/examples/metric-check-examples.md`)
-  - [x] 基础示例(10+个)
-  - [x] 特殊情况处理
-  - [x] 错误处理示例
-  - [x] 实际应用场景
-  - [x] Python完整客户端示例
-  - [x] JavaScript集成示例
-
-- [x] **实现总结** (`IMPLEMENTATION_SUMMARY.md`)
-  - [x] 概述
-  - [x] 功能清单
-  - [x] 文件清单
-  - [x] 技术实现细节
-  - [x] 测试验证
-  - [x] 部署说明
-
-### 4. 测试代码
-
-- [x] **单元测试** (`tests/test_metric_check.py`)
-  - [x] 简单公式测试
-  - [x] 复杂公式测试
-  - [x] 无等号测试
-  - [x] 纯数字公式测试
-  - [x] 中文括号测试
-  - [x] 数据库连接失败测试
-  - [x] API成功调用测试
-  - [x] API参数验证测试
-  - [x] Mock对象使用
-  - [x] 测试用例文档注释
-
-### 5. 代码质量
-
-- [x] **代码规范**
-  - [x] PEP 8风格检查
-  - [x] 无linter错误
-  - [x] 类型注解(docstring)
-  - [x] 变量命名清晰
-  - [x] 函数职责单一
-  - [x] 注释完整
-
-- [x] **错误处理**
-  - [x] 参数验证
-  - [x] 异常捕获
-  - [x] 错误日志
-  - [x] 友好的错误消息
-
-- [x] **性能优化**
-  - [x] 变量去重
-  - [x] 查询限制(LIMIT 1)
-  - [x] Session连接管理
-
-### 6. 项目集成
-
-- [x] **文件结构**
-  - [x] 符合项目架构
-  - [x] 模块化设计
-  - [x] 清晰的分层
-
-- [x] **依赖管理**
-  - [x] 使用现有依赖
-  - [x] 无新增依赖
-  - [x] 兼容现有代码
-
-## 📋 功能清单
-
-### 支持的运算符
-- [x] 加法 (+)
-- [x] 减法 (-)
-- [x] 乘法 (*)
-- [x] 除法 (/)
-- [x] 圆括号 ()
-- [x] 中文圆括号 ()
-- [x] 方括号 []
-- [x] 花括号 {}
-
-### 支持的功能
-- [x] 公式解析
-- [x] 变量提取
-- [x] 中文识别
-- [x] 数字过滤
-- [x] 变量去重
-- [x] 模糊匹配
-- [x] 批量查询
-- [x] 结果格式化
-
-### 返回字段
-- [x] variable(变量名)
-- [x] name_zh(中文名称)
-- [x] name_en(英文名称)
-- [x] id(节点ID)
-- [x] create_time(创建时间)
-- [x] findit(是否找到:1/0)
-
-## 🧪 测试覆盖
-
-### 单元测试
-- [x] test_simple_formula - 简单公式
-- [x] test_complex_formula - 复杂公式
-- [x] test_formula_without_equals - 无等号
-- [x] test_formula_with_numbers_only - 纯数字
-- [x] test_formula_with_chinese_brackets - 中文括号
-- [x] test_database_connection_failure - 连接失败
-
-### API测试
-- [x] test_api_success - API成功
-- [x] test_api_empty_formula - 空公式
-- [x] test_api_missing_formula - 缺少参数
-
-### 测试场景
-- [x] 正常流程
-- [x] 边界情况
-- [x] 错误处理
-- [x] 异常情况
-
-## 📚 文档清单
-
-### API文档
-- [x] 接口信息
-- [x] 请求格式
-- [x] 响应格式
-- [x] 错误码说明
-- [x] 使用示例
-
-### 技术文档
-- [x] 架构设计
-- [x] 实现细节
-- [x] 数据流程
-- [x] 决策说明
-
-### 使用文档
-- [x] 快速开始
-- [x] 使用示例
-- [x] 最佳实践
-- [x] 常见问题
-
-### 流程图
-- [x] 整体架构
-- [x] 详细流程
-- [x] 时序图
-- [x] 状态图
-
-## 🔍 代码审查
-
-### 代码质量
-- [x] 无语法错误
-- [x] 无逻辑错误
-- [x] 无安全隐患
-- [x] 无性能问题
-
-### 代码风格
-- [x] 命名规范
-- [x] 注释完整
-- [x] 格式统一
-- [x] 结构清晰
-
-### 可维护性
-- [x] 模块化设计
-- [x] 低耦合度
-- [x] 高内聚性
-- [x] 易于扩展
-
-## 📊 功能验证
-
-### 基础功能
-- [x] 公式解析正确
-- [x] 变量提取准确
-- [x] 数据库查询成功
-- [x] 结果返回正确
-
-### 边界情况
-- [x] 空公式处理
-- [x] 无变量处理
-- [x] 无等号处理
-- [x] 连接失败处理
-
-### 性能验证
-- [x] 响应时间合理
-- [x] 资源占用正常
-- [x] 并发处理正常
-
-## 🚀 部署准备
-
-### 环境检查
-- [x] Python版本兼容
-- [x] 依赖包完整
-- [x] Neo4j连接配置
-- [x] 日志配置正确
-
-### 配置检查
-- [x] 无硬编码配置
-- [x] 使用环境变量
-- [x] 配置文档完整
-
-## 📝 文件清单
-
-### 修改的文件
-1. [x] `app/core/data_metric/metric_interface.py`
-2. [x] `app/api/data_metric/routes.py`
-
-### 新建的文件
-1. [x] `docs/api/metric-check-api.md`
-2. [x] `docs/features/metric-formula-check.md`
-3. [x] `docs/diagrams/metric-check-flow.md`
-4. [x] `docs/examples/metric-check-examples.md`
-5. [x] `tests/test_metric_check.py`
-6. [x] `IMPLEMENTATION_SUMMARY.md`
-7. [x] `IMPLEMENTATION_CHECKLIST.md`(本文件)
-
-## ✨ 额外完成
-
-- [x] 详细的流程图
-- [x] 丰富的使用示例
-- [x] Python客户端示例
-- [x] JavaScript集成示例
-- [x] 批量处理示例
-- [x] 实际场景集成示例
-
-## 🎯 质量指标
-
-- **代码覆盖率**: 目标 >80%(测试用例已编写)
-- **文档完整度**: 100%(所有必要文档已完成)
-- **Linter错误**: 0(已通过检查)
-- **已知Bug**: 0(未发现bug)
-
-## 🔄 后续建议
-
-### 短期(1-2周)
-- [ ] 根据实际使用情况调整匹配算法
-- [ ] 收集用户反馈
-- [ ] 监控API性能
-- [ ] 完善错误日志
-
-### 中期(1-3个月)
-- [ ] 实现更智能的匹配算法
-- [ ] 添加缓存机制
-- [ ] 优化查询性能
-- [ ] 增加更多测试用例
-
-### 长期(3-6个月)
-- [ ] 支持更复杂的公式
-- [ ] 实现公式语法验证
-- [ ] 添加可视化功能
-- [ ] 集成智能推荐
-
-## ✅ 最终确认
-
-- [x] 所有功能已实现
-- [x] 所有测试已通过
-- [x] 所有文档已完成
-- [x] 代码质量已检查
-- [x] 准备好部署
-
----
-
-## 签署确认
-
-**实现日期**: 2024-10-30
-**实现者**: Cursor AI Assistant
-**审核状态**: ✅ 通过
-**版本**: v1.0.0
-
-**备注**: 
-- 功能完整,测试充分
-- 文档详细,示例丰富
-- 代码质量良好,符合规范
-- 可以直接投入使用
-
----
-
-**下一步行动**:
-1. 部署到测试环境
-2. 进行集成测试
-3. 收集用户反馈
-4. 根据反馈优化功能
-
-
-
-
-
-
-
-

+ 0 - 366
IMPLEMENTATION_SUMMARY.md

@@ -1,366 +0,0 @@
-# 指标公式检查功能 - 实现总结
-
-## 概述
-
-本次实现了一个完整的指标公式检查功能,包括核心业务逻辑、API接口、测试用例和详细文档。
-
-## 实现的功能
-
-### 1. 核心函数:`metric_check`
-
-**位置**: `app/core/data_metric/metric_interface.py`
-
-**功能**:
-- 解析指标计算公式(格式:`指标名称 = 计算表达式`)
-- 提取公式中的中文变量
-- 在Neo4j数据库中查找匹配的元数据
-- 返回每个变量的匹配结果
-
-**主要特性**:
-- ✅ 支持多种运算符:`+`, `-`, `*`, `/`, `()`, `()`, `[]`, `{}`
-- ✅ 自动识别和提取中文变量
-- ✅ 自动过滤数字
-- ✅ 变量去重
-- ✅ 模糊匹配元数据
-- ✅ 完善的错误处理
-- ✅ 详细的日志记录
-
-**返回数据格式**:
-```json
-[
-  {
-    "variable": "变量名",
-    "name_zh": "中文名称",
-    "name_en": "英文名称",
-    "id": 节点ID,
-    "create_time": "创建时间",
-    "findit": 1或0
-  }
-]
-```
-
-### 2. API接口:`/api/data/metric/check`
-
-**位置**: `app/api/data_metric/routes.py`
-
-**方法**: `POST`
-
-**请求参数**:
-```json
-{
-  "formula": "销售额 = 单价 * 数量 + 运费"
-}
-```
-
-**响应格式**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "单价",
-      "name_zh": "单价",
-      "name_en": "unit_price",
-      "id": 12345,
-      "create_time": "2024-01-15 10:30:00",
-      "findit": 1
-    }
-  ]
-}
-```
-
-## 文件清单
-
-### 修改的文件
-
-1. **`app/core/data_metric/metric_interface.py`**
-   - 新增 `metric_check()` 函数(第643-748行)
-   - 功能:解析公式并匹配元数据
-
-2. **`app/api/data_metric/routes.py`**
-   - 更新导入语句(第15-19行)
-   - 新增 `data_metric_check()` API接口(第289-316行)
-   - 功能:处理HTTP请求并调用核心函数
-
-### 新建的文件
-
-3. **`docs/api/metric-check-api.md`**
-   - API接口详细文档
-   - 包含请求/响应格式、示例代码、错误处理等
-
-4. **`docs/features/metric-formula-check.md`**
-   - 功能设计文档
-   - 包含技术实现、使用场景、扩展建议等
-
-5. **`tests/test_metric_check.py`**
-   - 单元测试文件
-   - 包含7个核心功能测试用例
-   - 包含3个API接口测试用例
-
-6. **`IMPLEMENTATION_SUMMARY.md`**
-   - 本文件:实现总结
-
-## 技术实现细节
-
-### 公式解析流程
-
-```
-输入: "销售额 = 单价 * 数量 + 100"
-  ↓
-按等号分割 → ["销售额", "单价 * 数量 + 100"]
-  ↓
-提取右侧 → "单价 * 数量 + 100"
-  ↓
-按运算符分割 → ["单价", "数量", "100"]
-  ↓
-过滤中文变量 → ["单价", "数量"]
-  ↓
-去重 → ["单价", "数量"]
-  ↓
-Neo4j查询 → [
-  {variable: "单价", findit: 1, ...},
-  {variable: "数量", findit: 1, ...}
-]
-```
-
-### Neo4j查询
-
-```cypher
-MATCH (n:DataMeta)
-WHERE n.name CONTAINS $variable
-RETURN n, id(n) as node_id
-LIMIT 1
-```
-
-特点:
-- 使用 `CONTAINS` 实现模糊匹配
-- 限制返回1条记录提高性能
-- 返回节点完整信息和ID
-
-## 使用示例
-
-### Python示例
-
-```python
-import requests
-
-url = "http://localhost:5000/api/data/metric/check"
-data = {
-    "formula": "利润率 = (销售收入 - 成本) / 销售收入 * 100"
-}
-
-response = requests.post(url, json=data)
-result = response.json()
-
-for item in result['data']:
-    if item['findit'] == 1:
-        print(f"✓ {item['variable']}: 找到匹配 (ID: {item['id']})")
-    else:
-        print(f"✗ {item['variable']}: 未找到匹配")
-```
-
-### JavaScript示例
-
-```javascript
-fetch('http://localhost:5000/api/data/metric/check', {
-  method: 'POST',
-  headers: {'Content-Type': 'application/json'},
-  body: JSON.stringify({
-    formula: '销售额 = 单价 * 数量'
-  })
-})
-  .then(res => res.json())
-  .then(data => {
-    data.data.forEach(item => {
-      if (item.findit === 1) {
-        console.log(`✓ ${item.variable}: 找到`);
-      } else {
-        console.log(`✗ ${item.variable}: 未找到`);
-      }
-    });
-  });
-```
-
-## 测试验证
-
-### 运行单元测试
-
-```bash
-# 运行所有测试
-python -m pytest tests/test_metric_check.py -v
-
-# 运行特定测试
-python -m pytest tests/test_metric_check.py::TestMetricCheck::test_simple_formula -v
-
-# 查看测试覆盖率
-python -m pytest tests/test_metric_check.py --cov=app.core.data_metric --cov-report=html
-```
-
-### 测试场景覆盖
-
-✅ 简单公式(单个运算符)
-✅ 复杂公式(多个运算符和括号)
-✅ 中英文括号混用
-✅ 纯数字公式(无变量)
-✅ 缺少等号
-✅ 变量去重
-✅ 部分匹配/完全未匹配
-✅ 数据库连接失败
-✅ API请求参数验证
-✅ API错误处理
-
-## 代码质量
-
-### 符合项目规范
-
-- ✅ 遵循PEP 8代码风格
-- ✅ 使用类型注解(docstring)
-- ✅ 完善的错误处理
-- ✅ 详细的日志记录
-- ✅ 函数职责单一
-- ✅ 变量命名清晰
-- ✅ 注释完整
-
-### 无Linter错误
-
-```bash
-# 已通过linter检查
-✓ app/core/data_metric/metric_interface.py
-✓ app/api/data_metric/routes.py
-```
-
-## 应用场景
-
-### 1. 指标定义验证
-用户创建指标时,系统自动检查公式中的变量是否都已定义。
-
-### 2. 数据血缘追踪
-了解指标依赖哪些基础元数据,建立数据血缘关系。
-
-### 3. 数据质量检查
-提前发现未定义的变量,避免指标计算错误。
-
-### 4. 智能提示
-为用户提供变量的详细信息,辅助指标定义。
-
-## 未来扩展建议
-
-### 1. 增强匹配能力
-- 支持编辑距离算法
-- 支持拼音匹配
-- 返回多个候选结果
-- 支持同义词匹配
-
-### 2. 公式验证
-- 语法检查
-- 类型检查
-- 运算符合法性验证
-
-### 3. 智能推荐
-- 基于历史数据推荐变量
-- 提供常用公式模板
-- 变量自动补全
-
-### 4. 可视化
-- 依赖关系图
-- 交互式公式编辑器
-- 变量高亮显示
-
-## 性能考虑
-
-### 当前性能特点
-- 变量自动去重,避免重复查询
-- 使用 `LIMIT 1` 限制查询结果
-- 批量处理多个变量
-- 使用Neo4j session连接池
-
-### 优化建议
-- 添加缓存机制(Redis)
-- 为DataMeta.name字段创建索引
-- 考虑使用全文索引提高匹配速度
-- 实现异步查询(对于大量变量)
-
-## 部署注意事项
-
-### 环境要求
-- Python 3.8+
-- Flask 2.3.3+
-- Neo4j数据库
-- py2neo库
-
-### 配置检查
-1. 确保Neo4j数据库连接正常
-2. 确保DataMeta节点有name和en_name属性
-3. 检查日志配置是否正确
-
-### 监控建议
-- 监控API响应时间
-- 记录未匹配变量的统计
-- 监控数据库查询性能
-
-## 文档清单
-
-1. **API文档**: `docs/api/metric-check-api.md`
-   - 完整的API接口说明
-   - 请求/响应示例
-   - 错误处理说明
-
-2. **功能文档**: `docs/features/metric-formula-check.md`
-   - 功能设计说明
-   - 技术实现细节
-   - 使用场景和扩展建议
-
-3. **实现总结**: `IMPLEMENTATION_SUMMARY.md`(本文件)
-   - 实现概览
-   - 使用指南
-   - 测试说明
-
-## 总结
-
-本次实现完成了以下目标:
-
-✅ 创建了 `metric_check` 核心函数
-✅ 实现了 `/api/data/metric/check` API接口
-✅ 编写了完整的单元测试
-✅ 创建了详细的API和功能文档
-✅ 通过了代码质量检查
-✅ 符合项目架构和编码规范
-
-该功能可以立即投入使用,并为未来的功能扩展预留了接口。
-
-## 快速开始
-
-1. **确保依赖已安装**
-   ```bash
-   pip install flask py2neo
-   ```
-
-2. **启动应用**
-   ```bash
-   python run.py
-   ```
-
-3. **测试API**
-   ```bash
-   curl -X POST http://localhost:5000/api/data/metric/check \
-     -H "Content-Type: application/json" \
-     -d '{"formula": "销售额 = 单价 * 数量"}'
-   ```
-
-4. **查看结果**
-   检查返回的JSON数据,确认变量匹配状态。
-
----
-
-**实现日期**: 2024-10-30
-**实现者**: Cursor AI Assistant
-**版本**: 1.0.0
-
-
-
-
-
-
-
-

+ 0 - 383
METRIC_UPDATE_FIX.md

@@ -1,383 +0,0 @@
-# 数据指标更新功能修复说明
-
-## 问题描述
-
-调用 `/api/metric/update` 接口时遇到两个错误:
-
-### 错误 1:Node 对象赋值错误
-```json
-{
-  "code": {
-    "error": "'Node' object does not support item assignment"
-  }
-}
-```
-
-### 错误 2:Neo4j 属性类型错误
-```json
-{
-  "code": {
-    "error": "{code: Neo.ClientError.Statement.TypeError} {message: Property values can only be of primitive types or arrays thereof. Encountered: Map{metaData -> NO_VALUE, id -> Long(300), type -> String(\"metric\")}.}"
-  }
-}
-```
-
-## 问题原因
-
-在 `app/core/data_metric/metric_interface.py` 的 `data_metric_edit` 函数中存在多个问题:
-
-### 1. Node 对象赋值问题
-```python
-# 错误的代码(第678行)
-for key, value in data.items():
-    if value is not None and key != "model_selected":
-        node_a[key] = value  # ❌ Node对象不支持这种赋值方式
-```
-
-### 2. 复杂类型属性问题
-Neo4j 只支持以下属性类型:
-- 基本类型:`string`, `integer`, `float`, `boolean`
-- 基本类型的数组:`string[]`, `integer[]`, 等
-
-**不支持**:
-- 嵌套的 Map/字典对象
-- 包含复杂对象的列表
-- 自定义对象
-
-但代码没有过滤复杂类型,导致尝试存储 `{metaData: ..., id: 300, type: "metric"}` 这样的 Map 对象。
-
-### 3. 过时的 py2neo API
-- `session.push(node_a)` - 已废弃
-- `connect_graph.create(connection)` - 旧API调用方式
-- `connect_graph.merge(relationship_label)` - 旧API调用方式
-
-## 解决方案
-
-### 1. 添加属性类型验证和过滤
-
-实现 `is_valid_neo4j_property` 函数,只保留 Neo4j 支持的基本类型:
-
-```python
-def is_valid_neo4j_property(value):
-    """检查值是否为 Neo4j 支持的属性类型"""
-    if value is None:
-        return False
-    # 基本类型:str, int, float, bool
-    if isinstance(value, (str, int, float, bool)):
-        return True
-    # 列表类型:但列表中的元素必须是基本类型
-    if isinstance(value, list):
-        if not value:  # 空列表是允许的
-            return True
-        # 检查列表中所有元素是否为基本类型
-        return all(isinstance(item, (str, int, float, bool)) for item in value)
-    # 其他类型(dict, object等)不支持
-    return False
-```
-
-### 2. 复杂类型转换为 JSON 字符串
-
-对于复杂类型(如字典、包含对象的列表),转换为 JSON 字符串存储:
-
-```python
-# 准备更新属性,只保留有效类型
-update_props = {}
-for key, value in data.items():
-    if key in excluded_keys:
-        continue
-    if not is_valid_neo4j_property(value):
-        # 如果是复杂类型,尝试转换为 JSON 字符串
-        if isinstance(value, (dict, list)):
-            try:
-                import json
-                update_props[key] = json.dumps(value, ensure_ascii=False)
-                logger.info(f"属性 {key} 从复杂类型转换为JSON字符串")
-            except Exception as e:
-                logger.warning(f"跳过无法序列化的属性 {key}: {type(value)}")
-        else:
-            logger.warning(f"跳过不支持的属性类型 {key}: {type(value)}")
-    else:
-        update_props[key] = value
-```
-
-### 3. 使用 Cypher 查询更新节点属性
-
-```python
-# 使用 Cypher 更新节点属性
-if update_props:
-    set_clauses = []
-    for key in update_props.keys():
-        set_clauses.append(f"n.{key} = ${key}")
-    set_clause = ", ".join(set_clauses)
-    
-    update_query = f"""
-    MATCH (n:DataMetric)
-    WHERE id(n) = $metric_id
-    SET {set_clause}
-    RETURN n
-    """
-    session.run(update_query, metric_id=metric_id, **update_props)
-    logger.info(f"成功更新数据指标节点属性: ID={metric_id}, 更新字段: {list(update_props.keys())}")
-```
-
-### 4. 使用 Cypher MERGE 创建关系
-
-替换旧的 API 调用,使用 Cypher `MERGE` 子句:
-
-```python
-# 创建子节点关系
-child_query = """
-MATCH (parent:DataMetric), (child)
-WHERE id(parent) = $parent_id AND id(child) = $child_id
-MERGE (parent)-[:child]->(child)
-"""
-session.run(child_query, parent_id=metric_id, child_id=child_id_int)
-
-# 创建标签关系
-tag_query = """
-MATCH (metric:DataMetric), (tag:DataLabel)
-WHERE id(metric) = $metric_id AND id(tag) = $tag_id
-MERGE (metric)-[:LABEL]->(tag)
-"""
-session.run(tag_query, metric_id=metric_id, tag_id=tag_id_int)
-
-# 创建连接关系
-connection_query = """
-MATCH (metric:DataMetric), (meta)
-WHERE id(metric) = $metric_id AND id(meta) = $meta_id
-MERGE (metric)-[:connection]->(meta)
-"""
-session.run(connection_query, metric_id=metric_id, meta_id=meta_id_int)
-```
-
-### 5. 增强错误处理和验证
-
-```python
-# 验证必需参数
-metric_id = data.get("id")
-if not metric_id:
-    logger.error("数据指标ID不能为空")
-    raise ValueError("数据指标ID不能为空")
-
-# 验证节点存在性
-node_a = get_node_by_id('DataMetric', metric_id)
-if not node_a:
-    logger.error(f"数据指标节点不存在: ID={metric_id}")
-    raise ValueError(f"数据指标节点不存在: ID={metric_id}")
-
-# ID类型转换和验证
-try:
-    child_id_int = int(child_id)
-    # ... 使用 child_id_int
-except (ValueError, TypeError) as e:
-    logger.warning(f"无效的子节点ID: {child_id}, 错误: {str(e)}")
-    continue
-```
-
-### 6. 添加详细日志
-
-```python
-logger.info(f"属性 {key} 从复杂类型转换为JSON字符串")
-logger.info(f"成功更新数据指标节点属性: ID={metric_id}, 更新字段: {list(update_props.keys())}")
-logger.info(f"成功创建child关系: {metric_id} -> {child_id_int}")
-logger.info(f"成功创建LABEL关系: {metric_id} -> {tag_id_int}")
-logger.info(f"成功创建connection关系: {metric_id} -> {meta_id_int}")
-logger.info(f"数据指标编辑完成: ID={metric_id}")
-logger.warning(f"跳过无法序列化的属性 {key}: {type(value)}")
-logger.warning(f"跳过不支持的属性类型 {key}: {type(value)}")
-```
-
-## 修改内容对比
-
-### 修改前(问题代码)
-
-```python
-def data_metric_edit(data):
-    node_a = get_node_by_id('DataMetric', data["id"])
-    if node_a:
-        delete_relationships(data["id"])
-
-    # ❌ 错误1:直接给Node对象赋值
-    for key, value in data.items():
-        if value is not None and key != "model_selected":
-            node_a[key] = value  # Node对象不支持字典赋值
-    
-    # ❌ 错误2:没有过滤复杂类型
-    # 可能尝试存储 {id: 300, type: "metric"} 这样的Map对象
-    
-    # ❌ 错误3:使用过时的API
-    with connect_graph().session() as session:
-        session.push(node_a)  # 已废弃
-
-    # ❌ 错误4:使用过时的关系创建方式
-    connection = Relationship(node_a, 'child', child)
-    connect_graph.create(connection)  # 旧API
-```
-
-### 修改后(正确代码)
-
-```python
-def data_metric_edit(data):
-    metric_id = data.get("id")
-    if not metric_id:
-        raise ValueError("数据指标ID不能为空")
-    
-    node_a = get_node_by_id('DataMetric', metric_id)
-    if not node_a:
-        raise ValueError(f"数据指标节点不存在: ID={metric_id}")
-    
-    delete_relationships(metric_id)
-
-    # ✅ 正确:定义属性类型验证函数
-    def is_valid_neo4j_property(value):
-        if value is None:
-            return False
-        if isinstance(value, (str, int, float, bool)):
-            return True
-        if isinstance(value, list):
-            return all(isinstance(item, (str, int, float, bool)) for item in value)
-        return False
-
-    # ✅ 正确:过滤和转换属性
-    excluded_keys = {'id', 'model_selected', 'childrenId', 'tag'}
-    update_props = {}
-    for key, value in data.items():
-        if key in excluded_keys:
-            continue
-        if not is_valid_neo4j_property(value):
-            # 复杂类型转为JSON字符串
-            if isinstance(value, (dict, list)):
-                update_props[key] = json.dumps(value, ensure_ascii=False)
-        else:
-            update_props[key] = value
-    
-    driver = connect_graph()
-    with driver.session() as session:
-        # ✅ 正确:使用Cypher SET更新属性
-        if update_props:
-            set_clauses = [f"n.{key} = ${key}" for key in update_props.keys()]
-            set_clause = ", ".join(set_clauses)
-            update_query = f"""
-            MATCH (n:DataMetric)
-            WHERE id(n) = $metric_id
-            SET {set_clause}
-            RETURN n
-            """
-            session.run(update_query, metric_id=metric_id, **update_props)
-
-        # ✅ 正确:使用Cypher MERGE创建关系
-        child_query = """
-        MATCH (parent:DataMetric), (child)
-        WHERE id(parent) = $parent_id AND id(child) = $child_id
-        MERGE (parent)-[:child]->(child)
-        """
-        session.run(child_query, parent_id=metric_id, child_id=child_id_int)
-```
-
-## 改进效果
-
-| 方面 | 修复前 | 修复后 |
-|------|--------|--------|
-| API 兼容性 | ❌ 使用过时API | ✅ 使用标准Cypher |
-| Node 对象处理 | ❌ 字典式赋值(不支持) | ✅ Cypher SET 语句 |
-| 属性类型验证 | ❌ 无验证,直接存储 | ✅ 严格类型验证和过滤 |
-| 复杂类型处理 | ❌ 尝试直接存储Map对象 | ✅ 转换为JSON字符串 |
-| 错误处理 | ⚠️ 基础异常捕获 | ✅ 详细验证和日志 |
-| 参数验证 | ❌ 缺少验证 | ✅ 完整类型验证 |
-| 日志记录 | ⚠️ 基础日志 | ✅ 详细操作和警告日志 |
-| 代码可维护性 | ⚠️ 一般 | ✅ 良好 |
-
-## API 使用示例
-
-### 请求示例
-
-```bash
-POST /api/metric/update
-Content-Type: application/json
-
-{
-  "id": 12345,
-  "name_zh": "更新后的指标名称",
-  "name_en": "updated_metric_name",
-  "description": "这是更新后的描述",
-  "category": "财务指标",
-  "childrenId": [123, 456],
-  "tag": 789,
-  "model_selected": [
-    {
-      "meta": [
-        {"id": 111},
-        {"id": 222}
-      ]
-    }
-  ]
-}
-```
-
-### 成功响应
-
-```json
-{
-  "code": 200,
-  "data": {},
-  "msg": "success"
-}
-```
-
-### 失败响应
-
-```json
-{
-  "code": 500,
-  "data": {},
-  "msg": {
-    "error": "数据指标ID不能为空"
-  }
-}
-```
-
-## 测试验证
-
-运行测试脚本验证修复效果:
-
-```bash
-python test_metric_update.py
-```
-
-测试将会:
-1. 查找一个已存在的 DataMetric 节点
-2. 执行更新操作
-3. 验证更新结果
-4. 恢复原始数据
-
-## 相关文件
-
-### 修改的文件
-
-- `app/core/data_metric/metric_interface.py` - `data_metric_edit` 函数
-
-### 新增的文件
-
-- `test_metric_update.py` - 更新功能测试脚本
-- `METRIC_UPDATE_FIX.md` - 本文档
-
-## 注意事项
-
-1. **ID 类型**:所有节点 ID 必须是整数或可转换为整数的值
-2. **必需字段**:`id` 字段是必需的,不能为空
-3. **关系处理**:更新操作会先删除旧关系,再创建新关系
-4. **事务性**:所有操作在同一个数据库会话中执行,但不在显式事务中
-5. **日志监控**:建议监控日志以跟踪更新操作的执行情况
-
-## 未来优化建议
-
-1. **事务支持**:将多个操作包装在显式事务中,确保原子性
-2. **增量更新**:支持只更新指定的关系,而不是全部删除后重建
-3. **版本控制**:记录节点的修改历史
-4. **批量更新**:支持一次更新多个指标节点
-5. **异步处理**:对于复杂的更新操作,考虑使用异步任务
-
-## 总结
-
-通过将直接对 Node 对象的属性赋值改为使用 Cypher 查询更新,以及使用 Cypher MERGE 语句创建关系,成功修复了 `'Node' object does not support item assignment` 错误。新的实现使用标准的 Neo4j Python Driver API,具有更好的兼容性、可维护性和错误处理能力。
-

+ 0 - 284
README_METRIC_CHECK.md

@@ -1,284 +0,0 @@
-# 指标公式检查功能 - 快速指南
-
-## 🎯 功能简介
-
-指标公式检查功能用于解析和验证数据指标的计算公式,自动提取公式中的变量并在Neo4j数据库中查找匹配的元数据记录。
-
-**核心价值**:
-- ✅ 自动验证指标公式中的变量是否已定义
-- ✅ 提前发现数据质量问题
-- ✅ 建立数据血缘关系
-- ✅ 辅助用户正确定义指标
-
-## 🚀 快速开始
-
-### 1. API调用示例
-
-```bash
-curl -X POST http://localhost:5000/api/data/metric/check \
-  -H "Content-Type: application/json" \
-  -d '{"formula": "销售额 = 单价 * 数量"}'
-```
-
-### 2. 响应示例
-
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "单价",
-      "name_zh": "单价",
-      "name_en": "unit_price",
-      "id": 101,
-      "create_time": "2024-01-15 10:00:00",
-      "findit": 1
-    },
-    {
-      "variable": "数量",
-      "name_zh": "数量",
-      "name_en": "quantity",
-      "id": 102,
-      "create_time": "2024-01-15 10:01:00",
-      "findit": 1
-    }
-  ]
-}
-```
-
-## 📋 功能特性
-
-### 支持的运算符
-- ➕ 加法 `+`
-- ➖ 减法 `-`
-- ✖️ 乘法 `*`
-- ➗ 除法 `/`
-- 📐 括号 `()`, `()`, `[]`, `{}`
-
-### 智能识别
-- 🔤 自动识别中文变量
-- 🔢 自动过滤数字
-- 🔄 自动去除重复变量
-- 🎯 模糊匹配元数据
-
-## 📂 文件结构
-
-```
-DataOps-platform/
-├── app/
-│   ├── core/
-│   │   └── data_metric/
-│   │       └── metric_interface.py      # 核心函数 metric_check
-│   └── api/
-│       └── data_metric/
-│           └── routes.py                 # API接口 /data/metric/check
-├── tests/
-│   └── test_metric_check.py             # 单元测试
-├── docs/
-│   ├── api/
-│   │   └── metric-check-api.md          # API文档
-│   ├── features/
-│   │   └── metric-formula-check.md      # 功能文档
-│   ├── diagrams/
-│   │   └── metric-check-flow.md         # 流程图
-│   └── examples/
-│       └── metric-check-examples.md     # 使用示例
-├── IMPLEMENTATION_SUMMARY.md            # 实现总结
-├── IMPLEMENTATION_CHECKLIST.md          # 检查清单
-└── README_METRIC_CHECK.md              # 本文件
-```
-
-## 💻 使用示例
-
-### Python
-
-```python
-import requests
-
-url = "http://localhost:5000/api/data/metric/check"
-data = {"formula": "利润率 = (收入 - 成本) / 收入 * 100"}
-
-response = requests.post(url, json=data)
-result = response.json()
-
-for item in result['data']:
-    status = "✓" if item['findit'] == 1 else "✗"
-    print(f"{status} {item['variable']}")
-```
-
-### JavaScript
-
-```javascript
-fetch('http://localhost:5000/api/data/metric/check', {
-  method: 'POST',
-  headers: {'Content-Type': 'application/json'},
-  body: JSON.stringify({
-    formula: '销售额 = 单价 * 数量'
-  })
-})
-  .then(res => res.json())
-  .then(data => console.log(data));
-```
-
-## 📖 详细文档
-
-| 文档 | 内容 | 路径 |
-|------|------|------|
-| **API文档** | 接口说明、请求/响应格式、错误处理 | `docs/api/metric-check-api.md` |
-| **功能文档** | 技术实现、使用场景、扩展建议 | `docs/features/metric-formula-check.md` |
-| **流程图** | 架构图、流程图、时序图 | `docs/diagrams/metric-check-flow.md` |
-| **使用示例** | 10+个完整示例、集成代码 | `docs/examples/metric-check-examples.md` |
-| **实现总结** | 实现概览、技术细节、部署说明 | `IMPLEMENTATION_SUMMARY.md` |
-| **检查清单** | 完成项目、质量检查、后续建议 | `IMPLEMENTATION_CHECKLIST.md` |
-
-## 🧪 测试
-
-```bash
-# 运行所有测试
-python -m pytest tests/test_metric_check.py -v
-
-# 查看测试覆盖率
-python -m pytest tests/test_metric_check.py --cov=app.core.data_metric --cov-report=html
-```
-
-## 🔧 技术栈
-
-- **语言**: Python 3.8+
-- **框架**: Flask 2.3.3+
-- **数据库**: Neo4j
-- **驱动**: py2neo
-- **测试**: pytest, unittest
-
-## 📊 接口信息
-
-| 项目 | 内容 |
-|------|------|
-| **URL** | `/api/data/metric/check` |
-| **方法** | `POST` |
-| **Content-Type** | `application/json` |
-| **请求参数** | `{"formula": "指标名称 = 计算表达式"}` |
-| **响应格式** | JSON数组,包含变量匹配结果 |
-
-## 🎨 应用场景
-
-### 1. 指标创建验证
-在用户创建指标时,自动检查公式中的变量是否都已定义。
-
-### 2. 数据质量检查
-提前发现未定义的变量,避免指标计算错误。
-
-### 3. 数据血缘分析
-了解指标依赖哪些基础元数据,建立数据血缘关系。
-
-### 4. 智能提示
-为用户提供变量的详细信息,辅助指标定义。
-
-## 🔍 工作原理
-
-```
-输入公式: "销售额 = 单价 * 数量"
-    ↓
-解析公式: 提取右侧 "单价 * 数量"
-    ↓
-识别变量: ["单价", "数量"]
-    ↓
-查询数据库: 在Neo4j中查找DataMeta节点
-    ↓
-返回结果: [{variable:"单价", findit:1}, {variable:"数量", findit:1}]
-```
-
-## ⚡ 性能特性
-
-- ✅ 变量自动去重,避免重复查询
-- ✅ 使用 `LIMIT 1` 限制查询结果
-- ✅ 批量处理多个变量
-- ✅ 使用Neo4j session连接池
-
-## 🛠️ 配置要求
-
-### 环境依赖
-- Python 3.8+
-- Flask 2.3.3+
-- Neo4j数据库
-- py2neo库
-
-### 数据库要求
-- Neo4j中需要有DataMeta节点
-- DataMeta节点需要有以下属性:
-  - `name`: 中文名称
-  - `en_name`: 英文名称
-  - `createTime`: 创建时间
-
-## 📝 返回字段说明
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `variable` | string | 从公式中提取的变量名 |
-| `name_zh` | string | 匹配到的元数据中文名称 |
-| `name_en` | string | 匹配到的元数据英文名称 |
-| `id` | integer/null | 匹配到的元数据节点ID |
-| `create_time` | string | 匹配到的元数据创建时间 |
-| `findit` | integer | 是否找到匹配:1=找到,0=未找到 |
-
-## ❓ 常见问题
-
-### Q1: 公式必须包含等号吗?
-**A**: 是的,公式必须包含等号,格式为 `指标名称 = 计算表达式`。
-
-### Q2: 支持哪些运算符?
-**A**: 支持 `+`, `-`, `*`, `/`, `()`, `()`, `[]`, `{}` 等常见运算符。
-
-### Q3: 如何处理未找到的变量?
-**A**: 未找到的变量会返回 `findit: 0`,前端可以根据此标识提示用户。
-
-### Q4: 是否支持英文变量?
-**A**: 目前只识别包含中文字符的变量,纯英文和数字会被过滤。
-
-### Q5: 相同变量出现多次会查询多次吗?
-**A**: 不会,系统会自动去重,每个变量只查询一次。
-
-## 🚦 状态码
-
-| 状态码 | 说明 |
-|--------|------|
-| 200 | 成功 |
-| 400 | 参数错误 |
-| 500 | 服务器错误 |
-
-## 📞 获取帮助
-
-如有问题或建议,请:
-1. 查看详细文档:`docs/` 目录
-2. 查看使用示例:`docs/examples/metric-check-examples.md`
-3. 运行测试:`pytest tests/test_metric_check.py -v`
-
-## 🔄 版本历史
-
-### v1.0.0 (2024-10-30)
-- ✨ 初始版本发布
-- ✅ 实现核心功能
-- 📚 完成文档编写
-- 🧪 编写测试用例
-
-## 📄 许可证
-
-本项目为DataOps平台的一部分,遵循项目统一许可证。
-
----
-
-**快速导航**:
-- [API文档](docs/api/metric-check-api.md)
-- [功能文档](docs/features/metric-formula-check.md)
-- [使用示例](docs/examples/metric-check-examples.md)
-- [实现总结](IMPLEMENTATION_SUMMARY.md)
-
-**更新日期**: 2024-10-30
-
-
-
-
-
-
-
-

+ 0 - 284
UPDATE_API_TEST_SUMMARY.md

@@ -1,284 +0,0 @@
-# 数据指标更新接口测试总结
-
-## 测试请求
-
-### 接口
-`POST /api/metric/update`
-
-### 测试数据
-```json
-{
-  "name_zh": "测试指标_1762140669984",
-  "name_en": "metric_17621406",
-  "category": "应用类",
-  "organization": "citu",
-  "leader": "mxl",
-  "childrenId": [],
-  "frequency": "日",
-  "data_sensitivity": "低",
-  "tag": 82,
-  "describe": null,
-  "status": true,
-  "id_list": [
-    {
-      "id": 300,
-      "metaData": null,
-      "type": "metric"
-    },
-    {
-      "id": 2156,
-      "metaData": [2139, 2132, 2130],
-      "type": "model"
-    }
-  ],
-  "metric_rules": "<span class=\"contenteditable-span\" data-id=\"300\" contenteditable=\"false\">指标_1762140669984(指标)</span>...",
-  "code": "generate_python_function_for_metrics_mapping",
-  "id": 300
-}
-```
-
-## 数据类型分析
-
-### 基本类型属性(直接存储)✅
-| 属性名 | 类型 | 处理方式 |
-|--------|------|----------|
-| `name_zh` | string | 直接存储 |
-| `name_en` | string | 直接存储 |
-| `category` | string | 直接存储 |
-| `organization` | string | 直接存储 |
-| `leader` | string | 直接存储 |
-| `frequency` | string | 直接存储 |
-| `data_sensitivity` | string | 直接存储 |
-| `tag` | integer | 用于创建 LABEL 关系 |
-| `status` | boolean | 直接存储 |
-| `metric_rules` | string | 直接存储 |
-| `code` | string | 直接存储 |
-| `id` | integer | 节点标识,不作为属性存储 |
-
-### 特殊字段(关系处理)⚙️
-| 属性名 | 类型 | 处理方式 |
-|--------|------|----------|
-| `childrenId` | array | 用于创建 child 关系(本例为空数组) |
-| `tag` | integer | 用于创建 LABEL 关系(ID=82) |
-
-### 复杂类型属性(转换存储)🔄
-| 属性名 | 类型 | 处理方式 |
-|--------|------|----------|
-| `id_list` | array of objects | **转换为 JSON 字符串存储** |
-| `describe` | null | 过滤掉,不存储 |
-
-### `model_selected` 字段
-在测试数据中不存在,但如果存在会被排除,专门用于创建 connection 关系。
-
-## 属性处理流程
-
-### 1. `id_list` 处理(关键测试点)
-
-**原始值:**
-```javascript
-[
-  {
-    "id": 300,
-    "metaData": null,
-    "type": "metric"
-  },
-  {
-    "id": 2156,
-    "metaData": [2139, 2132, 2130],
-    "type": "model"
-  }
-]
-```
-
-**处理结果:**
-- ❌ **不是** Neo4j 支持的基本类型数组(包含对象)
-- ✅ 通过 `is_valid_neo4j_property()` 检测为复杂类型
-- ✅ 转换为 JSON 字符串存储
-- ✅ 存储值:`"[{\"id\":300,\"metaData\":null,\"type\":\"metric\"},{\"id\":2156,\"metaData\":[2139,2132,2130],\"type\":\"model\"}]"`
-
-### 2. 关系创建
-
-**LABEL 关系:**
-```cypher
-MATCH (metric:DataMetric), (tag:DataLabel)
-WHERE id(metric) = 300 AND id(tag) = 82
-MERGE (metric)-[:LABEL]->(tag)
-```
-
-**child 关系:**
-由于 `childrenId` 为空数组,不创建任何 child 关系。
-
-## 预期执行流程
-
-### 步骤 1:验证节点存在
-```cypher
-MATCH (n:DataMetric) WHERE id(n) = 300 RETURN n
-```
-- ✅ 节点存在,继续执行
-
-### 步骤 2:删除旧关系
-```cypher
-MATCH (n)-[r]-() WHERE id(n) = 300 DELETE r
-```
-- 删除节点 ID=300 的所有关系
-
-### 步骤 3:准备更新属性
-过滤和转换后的属性:
-```python
-{
-    "name_zh": "测试指标_1762140669984",
-    "name_en": "metric_17621406",
-    "category": "应用类",
-    "organization": "citu",
-    "leader": "mxl",
-    "frequency": "日",
-    "data_sensitivity": "低",
-    "status": True,
-    "id_list": "[{\"id\":300,...}]",  # JSON字符串
-    "metric_rules": "<span...",
-    "code": "generate_python_function_for_metrics_mapping"
-}
-```
-
-### 步骤 4:更新节点属性
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = 300
-SET n.name_zh = $name_zh,
-    n.name_en = $name_en,
-    n.category = $category,
-    n.organization = $organization,
-    n.leader = $leader,
-    n.frequency = $frequency,
-    n.data_sensitivity = $data_sensitivity,
-    n.status = $status,
-    n.id_list = $id_list,
-    n.metric_rules = $metric_rules,
-    n.code = $code
-RETURN n
-```
-
-### 步骤 5:创建 LABEL 关系
-```cypher
-MATCH (metric:DataMetric), (tag:DataLabel)
-WHERE id(metric) = 300 AND id(tag) = 82
-MERGE (metric)-[:LABEL]->(tag)
-```
-
-### 步骤 6:创建 child 关系
-由于 `childrenId` 为空,跳过此步骤。
-
-## 修复验证点
-
-### ✅ 修复点 1:Node 对象赋值问题
-**问题:** `node_a[key] = value` 不支持
-**修复:** 使用 Cypher `SET` 子句更新属性
-**验证:** 代码不再直接操作 Node 对象
-
-### ✅ 修复点 2:复杂类型属性问题
-**问题:** 尝试存储 Map 对象导致 `Neo.ClientError.Statement.TypeError`
-**修复:** 
-1. 实现 `is_valid_neo4j_property()` 验证函数
-2. 复杂类型自动转换为 JSON 字符串
-3. 不支持的类型跳过或记录警告
-
-**验证:** `id_list` 从对象数组转换为 JSON 字符串
-
-### ✅ 修复点 3:过时 API 使用
-**问题:** `session.push()`, `connect_graph.create()` 等过时 API
-**修复:** 全部替换为标准 Cypher 查询
-**验证:** 所有数据库操作使用 `driver.session().run()`
-
-## 测试环境要求
-
-### 必需条件
-1. ✅ Neo4j 数据库运行中(192.168.3.143:7687)
-2. ✅ 指标节点 ID=300 存在
-3. ✅ 数据标签节点 ID=82 存在
-4. ✅ Flask 应用配置正确
-
-### 测试方式
-
-#### 方式 1:通过 API 接口测试(推荐)
-```bash
-curl -X POST http://your-server/api/metric/update \
-  -H "Content-Type: application/json" \
-  -d '{"id": 300, "name_zh": "测试指标_1762140669984", ...}'
-```
-
-#### 方式 2:直接在生产环境测试
-1. 部署修复后的代码到生产环境
-2. 使用前端界面更新指标 ID=300
-3. 观察是否出现错误
-
-## 预期结果
-
-### 成功响应
-```json
-{
-  "code": 200,
-  "data": {},
-  "msg": "success"
-}
-```
-
-### 数据库状态
-- ✅ 节点属性已更新
-- ✅ `id_list` 存储为 JSON 字符串
-- ✅ LABEL 关系已创建:`(DataMetric:300)-[:LABEL]->(DataLabel:82)`
-- ✅ 旧关系已删除
-
-### 日志输出
-```
-INFO - 属性 id_list 从复杂类型转换为JSON字符串
-INFO - 成功更新数据指标节点属性: ID=300, 更新字段: ['name_zh', 'name_en', ...]
-INFO - 成功创建LABEL关系: 300 -> 82
-INFO - 数据指标编辑完成: ID=300
-```
-
-## 兼容性说明
-
-### 向后兼容
-- ✅ 基本类型属性保持不变
-- ✅ 关系创建逻辑保持不变
-- ⚠️  复杂类型属性存储格式改变(从直接存储改为JSON字符串)
-
-### 读取数据时注意
-如果前端或其他服务读取 `id_list` 属性,需要:
-```javascript
-// 之前:直接使用
-const idList = node.id_list;  // Array
-
-// 现在:需要解析
-const idList = JSON.parse(node.id_list);  // String -> Array
-```
-
-建议在数据访问层统一处理 JSON 字符串的解析。
-
-## 总结
-
-✅ **所有已知问题已修复:**
-1. Node 对象赋值错误 → 使用 Cypher SET
-2. Neo4j 属性类型错误 → 复杂类型转 JSON 字符串
-3. 过时 API 调用 → 使用标准 Cypher
-
-✅ **代码已优化:**
-1. 完整的类型验证机制
-2. 详细的日志记录
-3. 健壮的错误处理
-
-✅ **可以部署到生产环境进行实际测试**
-
-## 下一步
-
-1. 部署修复后的代码到生产环境
-2. 使用提供的测试数据调用 `/api/metric/update` 接口
-3. 验证响应成功且无错误
-4. 检查 Neo4j 数据库中节点属性是否正确更新
-5. 监控日志确认复杂类型转换正常工作
-
-如有问题,请查看日志中的详细信息,特别关注:
-- 属性类型转换警告
-- 关系创建日志
-- 任何异常堆栈跟踪
-

+ 0 - 33
analyze_more.py

@@ -1,33 +0,0 @@
-import pandas as pd
-
-# 读取Excel文件
-df = pd.read_excel('酒店职位名称20250519.xlsx', sheet_name='汇总版')
-
-print("=== 更详细的数据分析 ===")
-print(f"总共 {len(df)} 条记录")
-
-print("\n=== 前10行完整数据 ===")
-for i in range(min(10, len(df))):
-    row = df.iloc[i]
-    print(f"第{i+1}行:")
-    for col in df.columns:
-        print(f"  {col}: {row[col]}")
-    print()
-
-print("\n=== 各列唯一值数量 ===")
-for col in df.columns:
-    unique_count = df[col].nunique()
-    print(f"{col}: {unique_count} 个唯一值")
-
-print("\n=== 各列是否有空值 ===")
-for col in df.columns:
-    null_count = df[col].isnull().sum()
-    print(f"{col}: {null_count} 个空值")
-
-print("\n=== 部门统计 ===")
-dept_counts = df['部门(中文)'].value_counts()
-print(dept_counts)
-
-print("\n=== 职级统计 ===")
-level_counts = df['职级(中文)'].value_counts()
-print(level_counts) 

+ 86 - 64
app/__init__.py

@@ -4,30 +4,26 @@ from flask_cors import CORS
 import logging
 from app.config.config import config, current_env
 from app.config.cors import CORS_OPTIONS
-import os
 
 
 db = SQLAlchemy()
 
+
 def create_app():
     """Create and configure the Flask application"""
     app = Flask(__name__)
-    
+
     # 加载配置
     app.config.from_object(config[current_env])
-    
+
     # 初始化扩展
     # 配置CORS以解决跨域问题
     CORS(app, **CORS_OPTIONS)
     db.init_app(app)
-    
+
     # 注册蓝图
     from app.api.meta_data import bp as meta_bp
-    from app.api.data_resource import bp as resource_bp
-    from app.api.data_model import bp as data_model_bp
     from app.api.data_interface import bp as data_interface_bp
-    from app.api.data_metric import bp as data_metric_bp
-    from app.api.production_line import bp as production_line_bp
     from app.api.graph import bp as graph_bp
     from app.api.system import bp as system_bp
     from app.api.data_source import bp as data_source_bp
@@ -35,61 +31,63 @@ def create_app():
     from app.api.business_domain import bp as business_domain_bp
 
     app.register_blueprint(meta_bp, url_prefix='/api/meta')
-    app.register_blueprint(resource_bp, url_prefix='/api/resource')
-    app.register_blueprint(data_model_bp, url_prefix='/api/model')
     app.register_blueprint(data_interface_bp, url_prefix='/api/interface')
-    app.register_blueprint(data_metric_bp, url_prefix='/api/metric')
-    app.register_blueprint(production_line_bp, url_prefix='/api/pipeline')
     app.register_blueprint(graph_bp, url_prefix='/api/graph')
     app.register_blueprint(system_bp, url_prefix='/api/system')
     app.register_blueprint(data_source_bp, url_prefix='/api/datasource')
     app.register_blueprint(data_flow_bp, url_prefix='/api/dataflow')
     app.register_blueprint(business_domain_bp, url_prefix='/api/bd')
-    
+
     # Configure global response headers
     configure_response_headers(app)
-    
+
     # Configure logging
     configure_logging(app)
-    
+
     # 添加全局异常处理器
     configure_error_handlers(app)
-    
+
     # 输出启动信息
-    app.logger.info(f"Starting server in {current_env} mode on port {app.config['PORT']}")
-    
+    port = app.config['PORT']
+    app.logger.info(f"Starting server in {current_env} mode on port {port}")
+
     return app
 
+
 def configure_response_headers(app):
     """Configure global response headers for JSON content"""
-    
+
     @app.after_request
     def after_request(response):
         from flask import request
-        
+
         # 检查是否是API路径
         if request.path.startswith('/api/'):
             # 排除文件下载和特殊响应类型
-            if (response.content_type and 
-                any(ct in response.content_type for ct in [
-                    'application/octet-stream', 
-                    'application/pdf', 
-                    'image/', 
-                    'text/csv',
-                    'application/vnd.ms-excel',
-                    'application/vnd.openxmlformats-officedocument'
-                ])):
+            excluded_types = [
+                'application/octet-stream',
+                'application/pdf',
+                'image/',
+                'text/csv',
+                'application/vnd.ms-excel',
+                'application/vnd.openxmlformats-officedocument'
+            ]
+            if (response.content_type and
+                    any(ct in response.content_type for ct in excluded_types)):
                 # 保持原有的文件类型不变
                 pass
-            elif response.content_type and 'application/json' in response.content_type:
+            elif (response.content_type and
+                    'application/json' in response.content_type):
                 # 确保JSON响应设置正确的Content-Type和charset
-                response.headers['Content-Type'] = 'application/json; charset=utf-8'
-            elif (not response.content_type or 
-                  response.content_type == 'text/html; charset=utf-8' or
-                  response.content_type == 'text/plain'):
-                # 对于API路由,如果没有明确设置Content-Type或设置为HTML,默认设置为JSON
-                response.headers['Content-Type'] = 'application/json; charset=utf-8'
-            
+                ct = 'application/json; charset=utf-8'
+                response.headers['Content-Type'] = ct
+            elif (not response.content_type or
+                    response.content_type == 'text/html; charset=utf-8' or
+                    response.content_type == 'text/plain'):
+                # 对于API路由,默认设置为JSON
+                ct = 'application/json; charset=utf-8'
+                response.headers['Content-Type'] = ct
+
             # 确保CORS头部不被覆盖
             if 'Access-Control-Allow-Origin' not in response.headers:
                 # 动态设置Origin,支持任意前端地址
@@ -100,28 +98,44 @@ def configure_response_headers(app):
                 else:
                     # 如果没有Origin头部,设置为通配符
                     response.headers['Access-Control-Allow-Origin'] = '*'
-            
+
             # 专门处理预检请求(OPTIONS方法)
             if request.method == 'OPTIONS':
-                response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*')
-                response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
-                response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With, Accept, Origin, Cache-Control, X-File-Name'
+                origin = request.headers.get('Origin', '*')
+                response.headers['Access-Control-Allow-Origin'] = origin
+                methods = 'GET, POST, PUT, DELETE, OPTIONS'
+                response.headers['Access-Control-Allow-Methods'] = methods
+                headers = (
+                    'Content-Type, Authorization, X-Requested-With, '
+                    'Accept, Origin, Cache-Control, X-File-Name'
+                )
+                response.headers['Access-Control-Allow-Headers'] = headers
                 response.headers['Access-Control-Max-Age'] = '86400'
                 return response
-            
+
             # 根据配置设置凭据支持
             from app.config.cors import ALLOW_ALL_ORIGINS
             if 'Access-Control-Allow-Credentials' not in response.headers:
                 if ALLOW_ALL_ORIGINS:
-                    response.headers['Access-Control-Allow-Credentials'] = 'false'  # 通配符时不支持凭据
+                    # 通配符时不支持凭据
+                    response.headers['Access-Control-Allow-Credentials'] = (
+                        'false'
+                    )
                 else:
-                    response.headers['Access-Control-Allow-Credentials'] = 'true'
-            
+                    response.headers['Access-Control-Allow-Credentials'] = (
+                        'true'
+                    )
+
             if 'Access-Control-Allow-Methods' not in response.headers:
-                response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
+                methods = 'GET, POST, PUT, DELETE, OPTIONS'
+                response.headers['Access-Control-Allow-Methods'] = methods
             if 'Access-Control-Allow-Headers' not in response.headers:
-                response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With, Accept, Origin'
-            
+                headers = (
+                    'Content-Type, Authorization, X-Requested-With, '
+                    'Accept, Origin'
+                )
+                response.headers['Access-Control-Allow-Headers'] = headers
+
             # 添加安全头部
             if 'X-Content-Type-Options' not in response.headers:
                 response.headers['X-Content-Type-Options'] = 'nosniff'
@@ -129,72 +143,80 @@ def configure_response_headers(app):
                 response.headers['X-Frame-Options'] = 'DENY'
             if 'X-XSS-Protection' not in response.headers:
                 response.headers['X-XSS-Protection'] = '1; mode=block'
-        
+
         return response
 
+
 def configure_logging(app):
     """Configure logging for the application"""
     if not app.config.get('LOG_ENABLED', True):
         return None
-    
-    log_file = app.config.get('LOG_FILE', f'flask_{app.config["FLASK_ENV"]}.log')
+
+    log_file = app.config.get(
+        'LOG_FILE', f'flask_{app.config["FLASK_ENV"]}.log'
+    )
     log_level_name = app.config.get('LOG_LEVEL', 'INFO')
     log_level = getattr(logging, log_level_name)
-    log_format = app.config.get('LOG_FORMAT', '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s')
+    log_format = app.config.get(
+        'LOG_FORMAT',
+        '%(asctime)s - %(levelname)s - %(filename)s - '
+        '%(funcName)s - %(lineno)s - %(message)s'
+    )
     log_encoding = app.config.get('LOG_ENCODING', 'UTF-8')
     log_to_console = app.config.get('LOG_TO_CONSOLE', True)
-    
+
     # 配置根日志器
     root_logger = logging.getLogger()
     root_logger.setLevel(log_level)
-    
+
     # 清除所有现有处理器
     root_logger.handlers.clear()
-    
+
     # 文件处理器 - 只添加到根日志器
     file_handler = logging.FileHandler(log_file, encoding=log_encoding)
     file_handler.setLevel(log_level)
     logging_format = logging.Formatter(log_format)
     file_handler.setFormatter(logging_format)
     root_logger.addHandler(file_handler)
-    
+
     # 控制台处理器 - 只添加到根日志器
     if log_to_console:
         console = logging.StreamHandler()
         console.setLevel(log_level)
         console.setFormatter(logging_format)
         root_logger.addHandler(console)
-    
+
     # 确保Flask内部日志器使用我们的配置
     app.logger.handlers.clear()  # 移除Flask默认处理器
-    
+
     # 配置app日志器,但禁止传播到根日志器
     logger = logging.getLogger("app")
     logger.setLevel(log_level)
     logger.handlers.clear()  # 清除现有处理器
     logger.propagate = True  # 通过根日志器处理
-    
+
     app.logger.info(f"日志配置完成: 级别={log_level_name}, 文件={log_file}")
     return logger
 
+
 def configure_error_handlers(app):
     """Configure global error handlers for the application"""
-    
+
     @app.errorhandler(Exception)
     def handle_exception(e):
         """全局异常处理器,捕获所有未处理的异常"""
         # 记录详细的错误信息
         app.logger.error(f"未处理的异常: {str(e)}", exc_info=True)
-        
+
         # 返回标准化的错误响应
         error_response = {
             'success': False,
             'message': f'服务器内部错误: {str(e)}',
             'data': None
         }
-        
+
         return jsonify(error_response), 500
-    
+
     @app.errorhandler(404)
     def handle_not_found(e):
         """处理404错误"""
@@ -204,7 +226,7 @@ def configure_error_handlers(app):
             'message': '请求的资源不存在',
             'data': None
         }), 404
-    
+
     @app.errorhandler(500)
     def handle_internal_error(e):
         """处理500错误"""

+ 0 - 111
app/api/data_metric/README.md

@@ -1,111 +0,0 @@
-# API数据指标模块
-
-## 简介
-
-API数据指标模块(`app.api.data_metric`)提供了与数据指标相关的所有API接口,包括数据指标的新增、修改、查询、列表展示、图谱生成等功能。该模块作为前端与核心业务逻辑之间的桥梁,负责接收HTTP请求,调用相应的核心业务逻辑函数,并将结果返回给前端。
-
-## 主要功能
-
-1. **数据指标管理**:提供新增、更新、查询数据指标的接口
-2. **数据指标列表**:支持分页、筛选的数据指标列表展示
-3. **数据指标图谱**:生成不同类型的数据指标关系图谱,包括血缘关系、影响关系等
-4. **代码生成**:基于指标规则描述和映射关系生成指标计算代码
-5. **关系管理**:处理数据指标与其他实体(如模型、元数据、标签)之间的关系
-
-## API接口列表
-
-| 接口路径 | 方法 | 描述 |
-|---------|------|------|
-| `/data/metric/add` | POST | 新增数据指标 |
-| `/data/metric/update` | POST | 更新数据指标 |
-| `/data/metric/detail` | POST | 获取数据指标详情 |
-| `/data/metric/list` | POST | 获取数据指标列表 |
-| `/data/metric/code` | POST | 生成指标计算代码 |
-| `/data/metric/relation` | POST | 处理数据指标血缘关系 |
-| `/data/metric/graph/all` | POST | 获取数据指标图谱 |
-| `/data/metric/list/graph` | POST | 获取数据指标列表图谱 |
-
-## 使用示例
-
-### 1. 新增数据指标
-
-```json
-POST /data/metric/add
-Content-Type: application/json
-
-{
-  "name": "月活用户数",
-  "category": "用户指标",
-  "describe": "每月登录系统的独立用户数量",
-  "childrenId": [],
-  "tag": 123,
-  "id_list": [
-    {
-      "id": 456,
-      "type": "model",
-      "metaData": [789, 790]
-    }
-  ]
-}
-```
-
-### 2. 查询数据指标列表
-
-```json
-POST /data/metric/list
-Content-Type: application/json
-
-{
-  "current": 1,
-  "size": 10,
-  "name": "用户",
-  "category": "用户指标"
-}
-```
-
-### 3. 生成指标图谱
-
-```json
-POST /data/metric/graph/all
-Content-Type: application/json
-
-{
-  "id": 123,
-  "type": "all",
-  "meta": true
-}
-```
-
-## 依赖关系
-
-本模块主要依赖以下组件:
-
-1. **core.data_metric**:核心业务逻辑模块,包含数据指标相关的所有核心功能
-2. **routes.graph_routes**:图数据库连接和相关工具
-3. **DataOps_function.llm_solve**:代码生成相关功能
-4. **DataOps_function.meta_data_function**:元数据处理功能
-5. **model.result**:统一的响应结果模型
-
-## 错误处理
-
-所有API接口都包含统一的错误处理机制。当发生异常时,将返回包含错误信息的JSON响应:
-
-```json
-{
-  "code": -1,
-  "message": "操作失败",
-  "data": {},
-  "error": {
-    "error": "发生的具体错误信息"
-  }
-}
-```
-
-## 代码结构
-
-- **__init__.py**:定义Blueprint和路由导入
-- **routes.py**:包含所有API路由处理函数
-
-## 维护者
-
-数据平台开发团队 

+ 0 - 5
app/api/data_metric/__init__.py

@@ -1,5 +0,0 @@
-from flask import Blueprint
-
-bp = Blueprint('data_metric', __name__)
-
-from app.api.data_metric import routes 

+ 0 - 381
app/api/data_metric/routes.py

@@ -1,381 +0,0 @@
-"""
-数据指标API接口模块
-
-该模块提供了数据指标的各种API接口,包括:
-- 指标新增、更新和查询
-- 指标列表展示
-- 指标图谱生成
-- 指标代码生成
-- 指标关系管理
-"""
-
-from flask import request, jsonify
-import json
-from app.api.data_metric import bp
-from app.core.data_metric.metric_interface import (
-    metric_list, handle_metric_relation, handle_data_metric, 
-    handle_meta_data_metric, handle_id_metric, metric_kinship_graph, 
-    metric_impact_graph, metric_all_graph, data_metric_edit, metric_check,
-    metric_delete
-)
-from app.core.llm import code_generate_metric
-from app.core.meta_data import translate_and_parse
-from app.models.result import success, failed
-from app.core.graph.graph_operations import MyEncoder, connect_graph
-
-
-@bp.route('/relation', methods=['POST'])
-def data_metric_relation():
-    """
-    处理数据指标血缘关系
-    
-    请求参数:
-    - id: 数据模型ID
-    
-    返回:
-    - 处理结果,包含血缘关系数据
-    """
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        model_ids = receiver['id']  # 给定一个数据模型的id
-        response_data = handle_metric_relation(model_ids)
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/add', methods=['POST'])
-def data_metric_add():
-    """
-    新增数据指标
-    
-    请求参数:
-    - name: 指标名称
-    - 其他指标相关属性
-    
-    返回:
-    - 处理结果,成功或失败
-    """
-    # 传入请求参数
-    receiver = request.get_json()
-    metric_name_zh = receiver['name_zh']  # 数据指标的name
-    try:
-        result_list = translate_and_parse(metric_name_zh)
-        id, id_list = handle_data_metric(metric_name_zh, result_list, receiver)
-        handle_meta_data_metric(id, id_list)
-
-        res = success({}, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/code', methods=['POST'])
-def data_metric_code():
-    """
-    生成指标计算的代码
-    
-    请求参数:
-    - content: 指标规则描述
-    - relation: 映射关系
-    
-    返回:
-    - 生成的代码
-    """
-    # 传入请求参数
-    receiver = request.get_json()
-    content = receiver['content']  # 指标规则描述
-    relation = receiver['relation']  # 映射关系
-
-    try:
-        id_list = [item["id"] for item in relation]
-        cql = """
-        MATCH (n:DataMetric) WHERE id(n) IN $Id_list
-        WITH n.name_en AS name_en, n.name_zh AS name_zh
-        WITH collect({name_en: name_en, name_zh: name_zh}) AS res
-        WITH reduce(acc = {}, item IN res | apoc.map.setKey(acc, item.name_en, item.name_zh)) AS result
-        RETURN result
-        """
-        driver = None
-        try:
-            driver = connect_graph()
-            with driver.session() as session:
-                query_result = session.run(cql, Id_list=id_list)
-                id_relation = query_result.single()[0]
-        except (ConnectionError, ValueError) as e:
-            return json.dumps(failed({}, f"无法连接到数据库: {str(e)}"), ensure_ascii=False, cls=MyEncoder)
-        finally:
-            if driver:
-                driver.close()
-            
-        result = code_generate_metric(content, id_relation)
-        res = success(result, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/detail', methods=['POST'])
-def data_metric_detail():
-    """
-    获取数据指标详情
-    
-    请求参数:
-    - id: 指标ID
-    
-    返回:
-    - 指标详情数据
-    """
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        id = int(receiver.get('id'))
-        response_data = handle_id_metric(id)
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/list', methods=['POST'])
-def data_metric_list():
-    """
-    获取数据指标列表
-    
-    请求参数:
-    - current: 当前页码,默认1
-    - size: 每页大小,默认10
-    - name_en: 英文名称过滤
-    - name_zh: 名称过滤
-    - category: 类别过滤
-    - time: 时间过滤
-    - tag: 标签过滤
-    
-    返回:
-    - 指标列表数据和分页信息
-    """
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        page = int(receiver.get('current', 1))
-        page_size = int(receiver.get('size', 10))
-        name_en_filter = receiver.get('name_en', None)
-        name_zh_filter = receiver.get('name_zh', None)
-        category = receiver.get('category', None)
-        create_time = receiver.get('create_time', None)
-        tag = receiver.get('tag', None)
-
-        # 计算跳过的记录的数量
-        skip_count = (page - 1) * page_size
-
-        data, total = metric_list(skip_count, page_size, name_en_filter,
-                                  name_zh_filter, category, create_time, tag)
-
-        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/graph/all', methods=['POST'])
-def data_metric_graph_all():
-    """
-    获取数据指标图谱
-    
-    请求参数:
-    - id: 指标ID
-    - type: 图谱类型,可选kinship/impact/all
-    - meta: 是否返回元数据,true/false
-    
-    返回:
-    - 图谱数据
-    """
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        nodeid = receiver['id']
-        type = receiver['type']  # kinship/impact/all
-        meta = receiver['meta']  # true/false 是否返回元数据
-
-        if type == 'kinship':
-            result = metric_kinship_graph(nodeid, meta)
-        elif type == 'impact':
-            result = metric_impact_graph(nodeid, meta)
-        else:
-            result = metric_all_graph(nodeid, meta)
-        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/list/graph', methods=['POST'])
-def data_metric_list_graph():
-    """
-    获取数据指标列表图谱
-    
-    请求参数:
-    - tag: 标签ID
-    
-    返回:
-    - 图谱数据,包含节点和连线
-    """
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-
-        if not receiver or 'tag' not in receiver:
-            raise ValueError("Missing 'tag' parameter in request body")
-
-        nodeid = receiver['tag']
-
-        # 构建查询条件
-        params = {}
-        where_clause = ""
-
-        if nodeid is not None:
-            where_clause = "MATCH (n)-[:LABEL]->(la) WHERE id(la) = $nodeId"
-            params['nodeId'] = nodeid
-        query = f"""
-        MATCH (n:DataMetric)
-        OPTIONAL MATCH (n)-[:child]->(child)
-        {where_clause}
-        WITH 
-            collect(DISTINCT {{id: toString(id(n)), text: n.name_zh, type: split(labels(n)[0], '_')[1]}}) AS nodes,
-            collect(DISTINCT {{id: toString(id(child)), text: child.name_zh, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
-            collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
-        RETURN nodes  + nodes2 AS nodes, lines  AS lines
-        """
-        driver = None
-        try:
-            driver = connect_graph()
-            with driver.session() as session:
-                result = session.run(query, **params)
-                res = {}
-                for item in result:
-                    res = {
-                        "nodes": [record for record in item['nodes'] if record['id']],
-                        "lines": [record for record in item['lines'] if record['from'] and record['to']],
-                    }
-        except (ConnectionError, ValueError) as e:
-            return json.dumps(failed({}, f"无法连接到数据库: {str(e)}"), ensure_ascii=False, cls=MyEncoder)
-        finally:
-            if driver:
-                driver.close()
-        return json.dumps(success(res, "success"), ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/update', methods=['POST'])
-def data_metric_update():
-    """
-    更新数据指标
-    
-    请求参数:
-    - id: 指标ID
-    - 其他需要更新的属性
-    
-    返回:
-    - 处理结果,成功或失败
-    """
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        data_metric_edit(receiver)
-
-        res = success({}, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/check', methods=['POST'])
-def data_metric_check():
-    """
-    检查指标计算公式中的变量
-    
-    请求参数:
-    - formula: 指标计算公式文本,格式如:指标名称 = 变量1 + 变量2 * 数字
-    
-    返回:
-    - 变量检查结果列表,包含每个变量的匹配信息
-    """
-    try:
-        # 获取请求参数
-        receiver = request.get_json()
-        formula_text = receiver.get('formula', '')
-        
-        if not formula_text:
-            res = failed({}, {"error": "公式文本不能为空"})
-            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-        
-        # 调用核心业务逻辑
-        result = metric_check(formula_text)
-        
-        res = success(result, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-@bp.route('/delete', methods=['POST'])
-def data_metric_delete():
-    """
-    删除数据指标
-    
-    请求参数:
-    - id: 指标节点ID
-    
-    返回:
-    - 删除结果状态信息
-    """
-    try:
-        # 获取请求参数
-        receiver = request.get_json()
-        metric_id = receiver.get('id')
-        
-        # 验证参数
-        if not metric_id:
-            res = failed({}, {"error": "指标ID不能为空"})
-            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-        
-        # 转换为整数
-        try:
-            metric_id = int(metric_id)
-        except (ValueError, TypeError):
-            res = failed({}, {"error": "指标ID必须为整数"})
-            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-        
-        # 调用核心业务逻辑执行删除
-        delete_result = metric_delete(metric_id)
-        
-        # 根据删除结果返回响应
-        if delete_result["success"]:
-            res = success({
-                "id": metric_id,
-                "message": delete_result["message"]
-            }, "删除成功")
-            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-        else:
-            res = failed({
-                "id": metric_id,
-                "message": delete_result["message"]
-            }, delete_result["message"])
-            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-            
-    except Exception as e:
-        res = failed({}, {"error": f"删除失败: {str(e)}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder) 

+ 0 - 81
app/api/data_model/README.md

@@ -1,81 +0,0 @@
-# 数据模型 API 接口模块
-
-本模块提供了数据模型相关的所有API接口,包括数据模型的创建、查询、更新、删除以及各类数据模型图谱生成功能。
-
-## 主要功能
-
-1. **数据模型基础操作**
-   - 创建数据模型:支持从数据资源、已有数据模型或DDL中创建数据模型
-   - 查询数据模型:支持分页查询、多条件筛选
-   - 更新数据模型:修改数据模型的基本信息和标签关系
-   - 删除数据模型
-
-2. **数据模型血缘关系**
-   - 查询数据模型血缘关系
-   - 获取数据资源间的血缘关系
-   - 查询相关数据资源列表
-
-3. **数据模型图谱生成**
-   - 血缘关系图谱:展示数据模型与数据资源的血缘关系
-   - 影响关系图谱:展示数据模型之间的使用关系
-   - 全量关系图谱:展示数据模型的所有关系
-
-## API接口列表
-
-- `/model/data/relation`:数据模型血缘关系查询
-- `/model/relatives/relation`:查询与指定数据资源有血缘关系的资源列表
-- `/data/model/save`:保存从DDL中选取的数据模型
-- `/model/data/search`:从数据资源中创建数据模型
-- `/model/data/model/add`:从其他数据模型中创建数据模型
-- `/data/model/detail`:获取数据模型详情
-- `/data/model/delete`:删除数据模型
-- `/data/model/list`:查询数据模型列表
-- `/data/model/graph/all`:生成数据模型图谱
-- `/data/model/list/graph`:生成数据模型列表图谱
-- `/data/model/update`:更新数据模型信息
-
-## 使用示例
-
-### 创建数据模型
-```json
-POST /model/data/search
-{
-  "name": "客户信息模型",
-  "en_name": "customer_info_model",
-  "category": "业务模型",
-  "description": "包含客户基本信息的数据模型",
-  "tag": 123,
-  "childrenId": [],
-  "id_list": [
-    {
-      "resource_id": 456,
-      "metaData": [
-        {
-          "id": 789,
-          "data_standard": "GB/T 22240-2008",
-          "data_name": "客户ID"
-        }
-      ]
-    }
-  ]
-}
-```
-
-### 查询数据模型列表
-```json
-POST /data/model/list
-{
-  "current": 1,
-  "size": 10,
-  "name": "客户",
-  "category": "业务模型",
-  "tag": 123,
-  "level": 1
-}
-```
-
-## 依赖关系
-
-- 依赖核心业务逻辑模块 `app.core.data_model`,提供数据模型的业务处理功能
-- 依赖图数据库服务 `neo4j_driver` 进行数据存储和查询
-- 依赖元数据处理模块 `app.core.meta_data` 进行模型元数据解析 

+ 0 - 6
app/api/data_model/__init__.py

@@ -1,6 +0,0 @@
-from flask import Blueprint
-
-bp = Blueprint('data_model', __name__)
-
-from app.api.data_model import routes
-from app.api.data_model.routes import * 

+ 0 - 524
app/api/data_model/routes.py

@@ -1,524 +0,0 @@
-from flask import request, jsonify
-from app.api.data_model import bp
-from app.models.result import success, failed
-from app.api.graph.routes import MyEncoder
-from app.core.data_model import model as model_functions
-import json
-import logging
-
-# Configure logger
-logger = logging.getLogger(__name__)
-
-# 2024.09.11 数据模型血缘关系(传入数据资源id)
-@bp.route('/model/data/relation', methods=['POST'])
-def data_model_relation():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        resource_ids = receiver['id']  # 给定一个数据资源的id
-        response_data = model_functions.handle_model_relation(resource_ids)
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 传入一个数据资源的id,返回多个有血缘关系的数据资源
-@bp.route('/model/relatives/relation', methods=['POST'])
-def data_relatives_relation():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        page = int(receiver.get('current', 1))
-        page_size = int(receiver.get('size', 10))
-        id = receiver['id']
-
-        name_zh_filter = receiver.get('name_zh', None)
-        category = receiver.get('category', None)
-        time = receiver.get('time', None)
-
-        # 计算跳过的记录的数量
-        skip_count = (page - 1) * page_size
-
-        data, total = model_functions.model_resource_list(skip_count, page_size, name_zh_filter, id, category, time)
-
-        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# DDL数据模型保存
-@bp.route('/data/model/save', methods=['POST'])
-def data_model_save():
-    # 传入请求参数
-    receiver = request.get_json()
-    data_model = receiver['name_zh']
-    # DDL新增时,id_list(包含resource_id)不是必填项
-    id_list = receiver.get('id_list', [])
-    data_source = receiver.get('data_source')  # 获取data_source节点ID
-    
-    # resource_id和meta_id构成json格式
-    result = json.dumps(id_list, ensure_ascii=False)
-    try:
-        # 从DDL中选取保存数据模型(支持data_source参数)
-        result_list = [receiver['name_en']]
-        id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
-        
-        # 只有在id_list不为空时才处理资源关系
-        if id_list:
-            model_functions.handle_no_meta_data_model(id_list, receiver, data_model_node)
-        
-        model_functions.calculate_model_level(id)
-
-        # 创建BusinessDomain节点及其关联关系
-        try:
-            bd_id, bd_node = model_functions.handle_businessdomain_node(
-                data_model, result_list, result, receiver, id_list
-            )
-            logger.info(f"成功创建BusinessDomain节点,ID: {bd_id}")
-        except Exception as bd_error:
-            # BusinessDomain创建失败不应该影响主流程
-            logger.error(f"创建BusinessDomain节点失败(不中断主流程): {str(bd_error)}")
-
-        # 查询节点的实际属性(data_model_node 可能只是整数ID)
-        from app.services.neo4j_driver import neo4j_driver
-        with neo4j_driver.get_session() as session:
-            node_query = """
-            MATCH (n:DataModel) WHERE id(n) = $node_id
-            RETURN n.name_zh as name_zh, n.name_en as name_en, n.description as description,
-                   n.category as category, n.create_time as create_time, n.level as level,
-                   n.tag as tag, n.leader as leader, n.origin as origin, n.frequency as frequency,
-                   n.organization as organization, n.data_sensitivity as data_sensitivity, n.status as status
-            """
-            node_result = session.run(node_query, node_id=int(id))
-            node_record = node_result.single()
-        
-        # 构建响应数据 - data_model包装格式
-        response_data = {
-            "data_model": {
-                "id": id,
-                "name_zh": node_record['name_zh'] if node_record else None,
-                "name_en": node_record['name_en'] if node_record else None,
-                "description": node_record['description'] if node_record else None,
-                "category": node_record['category'] if node_record else None,
-                "create_time": node_record['create_time'] if node_record else None,
-                "level": node_record['level'] if node_record else None,
-                "tag": node_record['tag'] if node_record else None,
-                "leader": node_record['leader'] if node_record else None,
-                "origin": node_record['origin'] if node_record else None,
-                "frequency": node_record['frequency'] if node_record else None,
-                "organization": node_record['organization'] if node_record else None,
-                "data_sensitivity": node_record['data_sensitivity'] if node_record else None,
-                "status": node_record['status'] if node_record else None,
-                "data_source": data_source  # 数据源节点ID
-            }
-        }
-
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 新建数据模型请求接口(从数据资源中选取)
-# @bp.route('/model/data/search', methods=['POST'])
-@bp.route('/data/search', methods=['POST'])
-def data_model_search():
-    # 传入请求参数
-    receiver = request.get_json()
-    data_model = receiver['name_zh']
-    id_list = receiver['id_list']
-    data_source = receiver.get('data_source')  # 获取data_source节点ID
-    
-    # resource_id和meta_id构成json格式
-    result = json.dumps(id_list, ensure_ascii=False)
-    try:
-        # 从数据资源中选取保存数据模型(支持data_source参数)
-        from app.core.meta_data import translate_and_parse
-        result_list = translate_and_parse(data_model)
-        id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
-        if id_list:
-            model_functions.resource_handle_meta_data_model(id_list, id)
-        model_functions.calculate_model_level(id)
-
-        # 查询节点的实际属性(data_model_node 可能只是整数ID)
-        from app.services.neo4j_driver import neo4j_driver
-        with neo4j_driver.get_session() as session:
-            node_query = """
-            MATCH (n:DataModel) WHERE id(n) = $node_id
-            RETURN n.name_zh as name_zh, n.name_en as name_en, n.description as description,
-                   n.category as category, n.create_time as create_time, n.level as level,
-                   n.tag as tag, n.leader as leader, n.origin as origin, n.frequency as frequency,
-                   n.organization as organization, n.data_sensitivity as data_sensitivity, n.status as status
-            """
-            node_result = session.run(node_query, node_id=int(id))
-            node_record = node_result.single()
-        
-        # 构建响应数据 - data_model包装格式
-        response_data = {
-            "data_model": {
-                "id": id,
-                "name_zh": node_record['name_zh'] if node_record else None,
-                "name_en": node_record['name_en'] if node_record else None,
-                "description": node_record['description'] if node_record else None,
-                "category": node_record['category'] if node_record else None,
-                "create_time": node_record['create_time'] if node_record else None,
-                "level": node_record['level'] if node_record else None,
-                "tag": node_record['tag'] if node_record else None,
-                "leader": node_record['leader'] if node_record else None,
-                "origin": node_record['origin'] if node_record else None,
-                "frequency": node_record['frequency'] if node_record else None,
-                "organization": node_record['organization'] if node_record else None,
-                "data_sensitivity": node_record['data_sensitivity'] if node_record else None,
-                "status": node_record['status'] if node_record else None,
-                "data_source": data_source  # 数据源节点ID
-            }
-        }
-
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 新建数据模型请求接口(从数据模型中选取)
-@bp.route('/model/data/model/add', methods=['POST'])
-def data_model_model_add():
-    # 传入请求参数 
-    receiver = request.get_json()
-    data_model = receiver['name_zh']
-    id_list = receiver['id_list']
-    data_source = receiver.get('data_source')  # 获取data_source节点ID
-    
-    # model_id和meta_id构成json格式
-    result = json.dumps(id_list, ensure_ascii=False)
-    try:
-        # 从数据模型中选取保存数据模型
-        # handle_data_model 已经处理了 data_source 关系创建(支持 int/dict/string 格式)
-        from app.core.meta_data import translate_and_parse
-        result_list = translate_and_parse(data_model)
-        node_id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
-        model_functions.model_handle_meta_data_model(id_list, node_id)
-        model_functions.calculate_model_level(node_id)
-
-        # 查询节点的实际属性(data_model_node 可能只是整数ID)
-        from app.services.neo4j_driver import neo4j_driver
-        with neo4j_driver.get_session() as session:
-            node_query = """
-            MATCH (n:DataModel) WHERE id(n) = $node_id
-            RETURN n.name_zh as name_zh, n.name_en as name_en, n.description as description,
-                   n.category as category, n.create_time as create_time, n.level as level,
-                   n.tag as tag, n.leader as leader, n.origin as origin, n.frequency as frequency,
-                   n.organization as organization, n.data_sensitivity as data_sensitivity, n.status as status
-            """
-            node_result = session.run(node_query, node_id=int(node_id))
-            node_record = node_result.single()
-        
-        # 构建响应数据 - data_model包装格式
-        response_data = {
-            "data_model": {
-                "id": node_id,
-                "name_zh": node_record['name_zh'] if node_record else None,
-                "name_en": node_record['name_en'] if node_record else None,
-                "description": node_record['description'] if node_record else None,
-                "category": node_record['category'] if node_record else None,
-                "create_time": node_record['create_time'] if node_record else None,
-                "level": node_record['level'] if node_record else None,
-                "tag": node_record['tag'] if node_record else None,
-                "leader": node_record['leader'] if node_record else None,
-                "origin": node_record['origin'] if node_record else None,
-                "frequency": node_record['frequency'] if node_record else None,
-                "organization": node_record['organization'] if node_record else None,
-                "data_sensitivity": node_record['data_sensitivity'] if node_record else None,
-                "status": node_record['status'] if node_record else None,
-                "data_source": data_source  # 数据源节点ID,与name_zh在同一级别
-            }
-        }
-
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 数据模型-详情接口
-@bp.route('/data/model/detail', methods=['POST'])
-def data_model_detail():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        # 直接使用字符串ID,不做类型转换
-        id = receiver.get('id')
-        print(f"Received id from frontend: {id}")
-
-        result_data = model_functions.handle_id_model(id)
-        
-        # handle_id_model 返回的数据格式是 {"data_model": {...}}
-        # 提取内部的 data_model 数据
-        model_data = result_data.get("data_model", {})
-        
-        # 查询关联的数据源信息
-        from app.services.neo4j_driver import neo4j_driver
-        data_source_id = None
-        try:
-            model_id = int(id)
-            with neo4j_driver.get_session() as session:
-                # 查询数据模型关联的数据源节点
-                ds_cypher = """
-                MATCH (m:DataModel)-[:COME_FROM]->(ds:DataSource)
-                WHERE id(m) = $model_id
-                RETURN id(ds) as ds_id
-                """
-                ds_result = session.run(ds_cypher, model_id=model_id)
-                ds_record = ds_result.single()
-                
-                if ds_record:
-                    # 如果存在数据源关联,只返回ID
-                    data_source_id = ds_record['ds_id']
-                    logger.info(f"找到数据模型关联的数据源: model_id={model_id}, data_source_id={data_source_id}")
-                else:
-                    logger.info(f"数据模型未关联数据源: model_id={model_id}")
-        except Exception as e:
-            # 数据源查询失败不应该影响主流程
-            logger.error(f"查询数据源关联失败(不中断主流程): {str(e)}")
-        
-        # 删除 childrenId 字段(如果存在)
-        if 'childrenId' in model_data:
-            del model_data['childrenId']
-        
-        # 添加 data_source 字段
-        model_data['data_source'] = data_source_id
-        
-        # 构建响应数据 - data_model包装格式
-        response_data = {
-            "data_model": model_data
-        }
-        
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        import traceback
-        traceback.print_exc()
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 删除数据模型
-@bp.route('/data/model/delete', methods=['POST'])
-def data_model_delete():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        id = receiver.get('id')
-        print(f"Deleting data model with id: {id}")
-
-        # 首先删除数据模型节点
-        from app.services.neo4j_driver import neo4j_driver
-        query = """
-        MATCH (n:DataModel) where id(n) = $nodeId
-        detach delete n
-        """
-        with neo4j_driver.get_session() as session:
-            session.run(query, nodeId=id)
-
-        res = success({}, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        import traceback
-        traceback.print_exc()
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 列表 数据模型查询
-@bp.route('/data/model/list', methods=['POST'])
-def data_model_list():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        page = int(receiver.get('current', 1))
-        page_size = int(receiver.get('size', 10))
-        name_zh_filter = receiver.get('name_zh', None)
-        name_en_filter = receiver.get('name_en', None)
-        category = receiver.get('category', None)
-        tag = receiver.get('tag', None)
-        level = receiver.get('level', None)
-
-        # 计算跳过的记录的数量
-        skip_count = (page - 1) * page_size
-
-        data, total = model_functions.model_list(skip_count, page_size, name_en_filter,
-                                 name_zh_filter, category, tag, level)
-
-        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 数据模型的图谱(血缘关系Kinship+影响关系Impact+所有关系all+社区关系community)
-@bp.route('/data/model/graph/all', methods=['POST'])
-def data_model_graph_all():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        type = receiver['type'] # kinship/impact/all/community
-
-        if type == 'community':
-            # 社区图谱查询,提取tag参数
-            tag = receiver.get('tag', None)
-            result = model_functions.model_community(tag)
-        else:
-            # 非社区查询时才提取nodeid和meta参数
-            nodeid = receiver.get('id')
-            meta = receiver.get('meta', False) # true/false 是否返回元数据
-            
-            if type == 'kinship':
-                result = model_functions.model_kinship_graph(nodeid, meta)
-            elif type == 'impact':
-                result = model_functions.model_impact_graph(nodeid, meta)
-            else:
-                result = model_functions.model_all_graph(nodeid, meta)
-        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        return json.dumps(failed(str(e), 500, {}), ensure_ascii=False, cls=MyEncoder)
-
-
-# 数据模型的列表图谱
-@bp.route('/data/model/list/graph', methods=['POST'])
-def data_model_list_graph():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-
-        if not receiver or 'tag' not in receiver:
-            raise ValueError("Missing 'tag' parameter in request body")
-
-        nodeid = receiver['tag']
-
-        # 构建查询条件
-        params = {}
-        where_clause = ""
-
-        if nodeid is not None:
-            where_clause = "MATCH (n)-[:LABEL]->(la) WHERE id(la) = $nodeId"
-            params['nodeId'] = nodeid
-
-        from app.services.neo4j_driver import neo4j_driver
-        query = f"""
-        MATCH (n:DataModel)
-        OPTIONAL MATCH (n)-[:child]->(child)
-        {where_clause}
-        WITH 
-            collect(DISTINCT {{id: toString(id(n)), text: n.name_zh, type: split(labels(n)[0], '_')[1]}}) AS nodes,
-            collect(DISTINCT {{id: toString(id(child)), text: child.name_zh, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
-            collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
-        RETURN nodes + nodes2 AS nodes, lines AS lines
-        """
-
-        with neo4j_driver.get_session() as session:
-            result = session.run(query, **params)
-            data = result.data()
-            
-            if len(data) > 0:
-                # 过滤掉空节点(即 id 为 null 的节点)
-                nodes = []
-                for node in data[0]['nodes']:
-                    if node['id'] != 'null':
-                        nodes.append(node)
-                
-                lines = data[0]['lines']
-                
-                response_data = {
-                    'nodes': nodes,
-                    'edges': lines
-                }
-                
-                return json.dumps(success(response_data, "success"), ensure_ascii=False, cls=MyEncoder)
-            else:
-                return json.dumps(success({'nodes': [], 'edges': []}, "No data found"), ensure_ascii=False, cls=MyEncoder)
-    
-    except Exception as e:
-        return json.dumps(failed(str(e), 500, {}), ensure_ascii=False, cls=MyEncoder)
-
-
-# 更新数据模型
-@bp.route('/data/model/update', methods=['POST'])
-def data_model_update():
-    try:
-        # 传入请求参数
-        receiver = request.get_json()
-        
-        result = model_functions.data_model_edit(receiver)
-        
-        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        return json.dumps(failed(str(e), 500, {}), ensure_ascii=False, cls=MyEncoder)
-
-
-# 数据模型关联元数据搜索
-@bp.route('/search', methods=['POST'])
-def data_model_metadata_search():
-    """数据模型关联元数据搜索"""
-    try:
-        # 获取分页和筛选参数
-        receiver = request.get_json()
-        page = int(receiver.get('current', 1))
-        page_size = int(receiver.get('size', 10))
-        model_id = receiver.get('id')
-        
-        name_en_filter = receiver.get('name_en')
-        name_zh_filter = receiver.get('name_zh')
-        category_filter = receiver.get('category')
-        tag_filter = receiver.get('tag')
-        
-        if model_id is None:
-            return json.dumps(failed("模型ID不能为空", 400, {}), ensure_ascii=False, cls=MyEncoder)
-            
-        # 确保传入的ID为整数
-        try:
-            model_id = int(model_id)
-        except (ValueError, TypeError):
-            return json.dumps(failed(f"模型ID必须为整数, 收到的是: {model_id}", 400, {}), ensure_ascii=False, cls=MyEncoder)
-            
-        # 记录请求信息
-        logger.info(f"获取数据模型关联元数据请求,ID: {model_id}")
-            
-        # 调用业务逻辑查询关联元数据
-        metadata_list, total_count = model_functions.model_search_list(
-            model_id, 
-            page, 
-            page_size, 
-            name_en_filter, 
-            name_zh_filter, 
-            category_filter, 
-            tag_filter
-        )
-        
-        # 返回结果
-        response_data = {
-            "records": metadata_list,
-            "total": total_count,
-            "size": page_size,
-            "current": page
-        }
-        res = success(response_data, "success")
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-    except Exception as e:
-        logger.error(f"数据模型关联元数据搜索失败: {str(e)}")
-        res = failed(str(e), 500, {})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder) 

+ 0 - 42
app/api/data_resource/README.md

@@ -1,42 +0,0 @@
- # 数据资源API模块
-
-本模块是数据资源管理的API接口,提供了一系列与数据资源相关的操作,包括资源列表查询、详情查询、添加、更新、删除等功能。
-
-## 主要功能
-
-1. **基础操作**
-   - 获取数据资源列表 (`/list`)
-   - 获取数据资源详情 (`/detail`)
-   - 添加数据资源 (`/save`)
-   - 更新数据资源 (`/update`)
-   - 删除数据资源 (`/delete`)
-
-2. **数据查询与解析**
-   - DDL语句解析 (`/ddl`)
-   - SQL查询测试 (`/sql/test`)
-   - DDL语句识别 (`/ddl/identify`)
-
-3. **元数据关联**
-   - 关联元数据查询 (`/search`)
-   - 保存关联元数据 (`/save/metadata`)
-
-4. **图谱操作**
-   - 获取数据资源亲缘关系图谱 (`/graph`)
-   - 获取数据资源完整图谱 (`/graph/all`)
-
-5. **模型资源**
-   - 获取模型资源列表 (`/model/list`)
-
-6. **多语言支持**
-   - 数据资源名称翻译 (`/translate`)
-
-## 错误处理
-
-所有API接口都实现了统一的错误处理机制,当操作失败时会返回错误信息和相应的HTTP状态码。
-
-## 依赖项
-
-- Flask:Web框架
-- Neo4j:图数据库
-- MinIO:对象存储
-- 业务逻辑层 (`app.core.data_resource`)

+ 0 - 5
app/api/data_resource/__init__.py

@@ -1,5 +0,0 @@
-from flask import Blueprint
-
-bp = Blueprint('data_resource', __name__)
-
-from app.api.data_resource import routes 

+ 0 - 913
app/api/data_resource/routes.py

@@ -1,913 +0,0 @@
-from io import BytesIO
-import pandas as pd
-from flask import request, jsonify, current_app
-from app.api.data_resource import bp
-from app.models.result import success, failed
-import logging
-import json
-import re
-from minio import Minio
-from app.services.neo4j_driver import neo4j_driver
-from app.core.data_resource.resource import (
-    resource_list,
-    handle_node,
-    resource_kinship_graph,
-    resource_impact_all_graph,
-    model_resource_list,
-    select_create_ddl,
-    data_resource_edit,
-    handle_id_resource,
-    id_data_search_list,
-    table_sql,
-    select_sql
-)
-from app.core.meta_data import (
-    translate_and_parse,
-    infer_column_type,
-    get_formatted_time
-)
-import traceback
-from app.core.system.auth import require_auth
-from app.core.llm.ddl_parser import DDLParser
-
-logger = logging.getLogger("app")
-
-
-def get_minio_client():
-    """获取 MinIO 客户端实例"""
-    return Minio(
-        current_app.config['MINIO_HOST'],
-        access_key=current_app.config['MINIO_USER'],
-        secret_key=current_app.config['MINIO_PASSWORD'],
-        secure=current_app.config['MINIO_SECURE']
-    )
-
-
-def get_minio_config():
-    """获取 MinIO 配置"""
-    return {
-        'bucket_name': current_app.config['BUCKET_NAME'],
-        'prefix': current_app.config['PREFIX'],
-        'allowed_extensions': current_app.config['ALLOWED_EXTENSIONS']
-    }
-
-
-def is_english(text):
-    """检查文本是否为英文"""
-    pattern = r'^[a-zA-Z0-9_\s.,;:!?()\'"-]+$'
-    return text.isascii() and bool(re.match(pattern, text))
-
-
-@bp.route('/translate', methods=['POST'])
-def data_resource_translate():
-    # 获取表单数据
-    data_resource = request.form.get('data_resource')
-    meta_data = request.form.get('meta_data')
-    file = request.files.get('file')
-
-    if not data_resource or not file:
-        return jsonify(failed("缺少必要参数:data_resource 或文件"))
-
-    # 处理meta_data可能为None的情况
-    if meta_data:
-        try:
-            # 修复JSON解析问题,处理可能包含特殊引号的情况
-            # 替换可能存在的特殊引号字符
-            meta_data = meta_data.replace('â', '"')
-            meta_data = meta_data.replace('"', '"').replace('"', '"')
-            meta_data_list = json.loads(meta_data)
-        except json.JSONDecodeError as e:
-            logger.error(
-                f"解析meta_data失败: {meta_data}, 错误: {str(e)}"
-            )
-            # 尝试进行基本的字符串解析,以处理简单的数组格式
-            if meta_data.startswith('[') and meta_data.endswith(']'):
-                try:
-                    # 使用ast.literal_eval作为备用解析方法
-                    import ast
-                    meta_data_list = ast.literal_eval(meta_data)
-                except Exception:
-                    # 如果仍然失败,使用简单的字符串分割
-                    meta_data = meta_data.strip('[]')
-                    meta_data_list = [
-                        item.strip('"\'') for item in meta_data.split(',')
-                    ]
-            else:
-                meta_data_list = []
-    else:
-        logger.warning("meta_data为空,将使用空列表")
-        meta_data_list = []
-
-    # 构建翻译后的内容组合
-    translated_meta_data_list = []
-    for meta_item in meta_data_list:
-        if is_english(meta_item):  # 检查是否为英文
-            translated_meta_data_list.append(meta_item)  # 如果是英文,则直接添加
-        else:
-            # 否则翻译后添加
-            translated_meta_data_list.append(translate_and_parse(meta_item)[0])
-
-    # 对 data_resource 进行翻译
-    translated_data_resource = translate_and_parse(data_resource)
-    if translated_data_resource and len(translated_data_resource) > 0:
-        translated_data_resource = translated_data_resource[0]
-    else:
-        translated_data_resource = data_resource  # 翻译失败时使用原值
-
-    try:
-        # 构建最终的翻译结果
-        resource = {
-            "name_zh": data_resource,
-            "name_en": translated_data_resource
-        }
-        parsed_data = []
-
-        # 读取文件内容
-        file_content = file.read()
-        # 重置文件指针
-        file.seek(0)
-
-        try:
-            df = pd.read_excel(BytesIO(file_content))
-        except Exception as e:
-            return jsonify(failed(f"文件格式错误: {str(e)}"))
-            
-        # 获取列名和对应的数据类型
-        # 如果meta_data为空,使用DataFrame的列名
-        if not meta_data_list and not df.empty:
-            meta_data_list = df.columns.tolist()
-            translated_meta_data_list = []
-            for col in meta_data_list:
-                if is_english(col):
-                    translated_meta_data_list.append(col)
-                else:
-                    translated = translate_and_parse(col)[0]
-                    translated_meta_data_list.append(translated)
-                    
-        columns_and_types = infer_column_type(df)
-        for i in range(len(meta_data_list)):
-            zh = meta_data_list[i]
-            en = translated_meta_data_list[i]
-            if i < len(columns_and_types):
-                data_type = columns_and_types[i]
-            else:
-                data_type = "varchar(255)"
-            parsed_item = {
-                "name_zh": zh, "name_en": en, "data_type": data_type
-            }
-            parsed_data.append(parsed_item)
-
-        response_data = {
-            "head_data": parsed_data,
-            "data_resource": resource
-        }
-        return jsonify(success(response_data, "success"))
-
-    except Exception as e:
-        logger.error(f"翻译处理失败: {str(e)}", exc_info=True)
-        return jsonify(failed(str(e)))
-
-  
-@bp.route('/save', methods=['POST'])
-def data_resource_save():
-    """保存数据资源"""   
-    try:
-        # 获取表单数据
-        receiver = request.get_json()
-        if not receiver:
-            return jsonify(failed("参数不完整:缺少receiver"))
-        
-        # 检查url(允许为空)
-        if 'url' not in receiver or not receiver['url']:
-            logger.debug("url 为空")
-
-        additional_info = receiver.get('additional_info')
-        if not additional_info:
-            return jsonify(failed("参数不完整: 缺少additional_info"))
-                      
-        head_data = additional_info.get('head_data')
-        
-        # 获取 storage_location 和 data_source
-        storage_location = receiver.get('storage_location', '').strip()
-        
-        # 向后兼容:data_source 可能在 receiver 顶层(新客户端)或 additional_info 内(旧客户端)
-        # 使用显式 None 检查以支持 0 作为有效的节点ID
-        data_source = receiver.get('data_source')
-        if data_source is None:
-            data_source = additional_info.get('data_source', '')
-        
-        # 验证:至少需要 storage_location 或 data_source 之一
-        # 使用显式检查以支持 data_source=0(有效的节点ID)
-        if not storage_location and data_source in (None, ''):
-            return jsonify(failed(
-                "参数不完整:至少需要提供 storage_location 或 data_source"
-            ))
-        
-        # 获取资源类型(直接从前端上传的type字段获取)
-        resource_type = receiver.get('type')
-        if not resource_type:
-            return jsonify(failed("参数不完整:缺少type字段"))
-        
-        # 调用业务逻辑创建数据资源
-        # 只在 data_source 为 None 或空字符串时传 None,保留 0 作为有效值
-        ds_value = data_source if data_source not in (None, '') else None
-        resource_id = handle_node(
-            receiver, head_data,
-            data_source=ds_value,
-            resource_type=resource_type
-        )
-    
-        return jsonify(success({"id": resource_id}))
-    except Exception as e:
-        logger.error(f"保存数据资源失败: {str(e)}")
-        error_traceback = traceback.format_exc()
-        logger.error(f"错误详情: {error_traceback}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/delete', methods=['POST'])
-def data_resource_delete():
-    """删除数据资源"""
-    try:
-        # 获取资源ID
-        if not request.json:
-            return jsonify(failed("请求数据不能为空"))
-        
-        resource_id = request.json.get('id')
-        if resource_id is None:
-            return jsonify(failed("资源ID不能为空"))
-        
-        with neo4j_driver.get_session() as session:
-            # 删除数据资源节点及其关系
-            cypher = """
-            MATCH (n:DataResource)
-            WHERE id(n) = $resource_id
-            DETACH DELETE n
-            """
-            
-            session.run(cypher, resource_id=int(resource_id))
-            
-            return jsonify(success({"message": "数据资源删除成功"}))
-    except Exception as e:
-        logger.error(f"删除数据资源失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/update', methods=['POST'])
-def data_resource_update():
-    """更新数据资源"""
-    try:
-        # 获取更新数据
-        data = request.json
-        
-        if not data or "id" not in data:
-            return jsonify(failed("参数不完整"))
-        
-        # 调用业务逻辑更新数据资源
-        updated_data = data_resource_edit(data)
-        
-        return jsonify(success(updated_data))
-    except Exception as e:
-        logger.error(f"更新数据资源失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-# 解析ddl,使用正则表达式匹配,但没有进行翻译,也没有对注释进行识别
-# 使用ddl创建数据资源时,调用该API
-@bp.route('/ddl', methods=['POST'])
-def id_data_ddl():
-    """解析数据资源的DDL"""
-    try:
-        # 获取SQL内容
-        if not request.json:
-            return jsonify(failed("请求数据不能为空"))
-        
-        sql_content = request.json.get('sql', '')
-        if not sql_content:
-            return jsonify(failed("SQL内容不能为空"))
-        
-        # 记录原始SQL用于调试
-        logger.debug(f"原始SQL: {sql_content}")
-        
-        # 提取创建表的DDL语句
-        create_ddl_list = select_create_ddl(sql_content)
-        
-        if not create_ddl_list:
-            return jsonify(failed("未找到有效的CREATE TABLE语句"))
-        
-        # 解析每个表定义
-        tables_dict = {}  # 最终返回的表字典
-        
-        for ddl in create_ddl_list:
-            table_info = table_sql(ddl)
-            if table_info:
-                # table_info格式:
-                # {"table_name": {"exist": bool, "meta": [...], ...}}
-                # 合并到结果字典中
-                tables_dict.update(table_info)
-        
-        if not tables_dict:
-            return jsonify(failed("解析表结构失败"))
-        
-        # 记录结果
-        logger.debug(f"解析结果: {json.dumps(tables_dict, ensure_ascii=False)}")
-        
-        # 直接返回解析结果
-        return jsonify(success(tables_dict))
-        
-    except Exception as e:
-        logger.error(f"解析DDL失败: {str(e)}")
-        logger.error(traceback.format_exc())  # 添加详细错误堆栈
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/list', methods=['POST'])
-def data_resource_list():
-    """获取数据资源列表"""
-    try:
-        # 获取分页和筛选参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        page = int(request.json.get('current', 1))
-        page_size = int(request.json.get('size', 10))
-        name_en_filter = request.json.get('name_en')
-        name_zh_filter = request.json.get('name_zh')
-        type_filter = request.json.get('type', 'all')
-        category_filter = request.json.get('category')
-        tag_filter = request.json.get('tag')
-        
-        # 调用业务逻辑查询数据资源列表
-        resources, total_count = resource_list(
-            page, 
-            page_size, 
-            name_en_filter, 
-            name_zh_filter, 
-            type_filter, 
-            category_filter, 
-            tag_filter
-        )
-        
-        # 返回结果
-        return jsonify(success({
-            "records": resources,
-            "total": total_count,
-            "size": page_size,
-            "current": page
-        }))
-    except Exception as e:
-        logger.error(f"获取数据资源列表失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/search', methods=['POST'])
-def id_data_search():
-    """数据资源关联元数据搜索"""
-    try:
-        # 获取分页和筛选参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        page = int(request.json.get('current', 1))
-        page_size = int(request.json.get('size', 10))
-        resource_id = request.json.get('id')
-        
-        name_en_filter = request.json.get('name_en')
-        name_zh_filter = request.json.get('name_zh')
-        category_filter = request.json.get('category')
-        tag_filter = request.json.get('tag')
-        
-        if resource_id is None:
-            return jsonify(failed("资源ID不能为空"))
-            
-        # 确保传入的ID为整数
-        try:
-            resource_id = int(resource_id)
-        except (ValueError, TypeError):
-            return jsonify(failed(f"资源ID必须为整数, 收到的是: {resource_id}"))
-            
-        # 记录请求信息
-        logger.info(f"获取资源关联元数据请求,ID: {resource_id}")
-            
-        # 调用业务逻辑查询关联元数据
-        metadata_list, total_count = id_data_search_list(
-            resource_id, 
-            page, 
-            page_size, 
-            name_en_filter, 
-            name_zh_filter, 
-            category_filter, 
-            tag_filter
-        )
-        
-        # 返回结果
-        return jsonify(success({
-            "records": metadata_list,
-            "total": total_count,
-            "size": page_size,
-            "current": page
-        }))
-    except Exception as e:
-        logger.error(f"数据资源关联元数据搜索失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-def dynamic_type_conversion(value, target_type):
-    """动态类型转换"""
-    if value is None:
-        return None
-        
-    if target_type in ("int", "INT"):
-        return int(value)
-    elif target_type in ("float", "FLOAT", "double", "DOUBLE"):
-        return float(value)
-    elif target_type in ("bool", "BOOL", "boolean", "BOOLEAN"):
-        if isinstance(value, str):
-            return value.lower() in ('true', 'yes', '1', 't', 'y')
-        return bool(value)
-    else:
-        return str(value)
-
-
-@bp.route('/graph/all', methods=['POST'])
-def data_resource_graph_all():
-    """获取数据资源完整图谱"""
-    try:
-        # 获取参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        resource_id = request.json.get('id')
-        meta = request.json.get('meta', True)
-        
-        if resource_id is None:
-            return jsonify(failed("资源ID不能为空"))
-            
-        # 确保传入的ID为整数
-        try:
-            resource_id = int(resource_id)
-        except (ValueError, TypeError):
-            return jsonify(failed(f"资源ID必须为整数, 收到的是: {resource_id}"))
-            
-        # 调用业务逻辑获取完整图谱
-        graph_data = resource_impact_all_graph(resource_id, meta)
-        
-        return jsonify(success(graph_data))
-    except Exception as e:
-        logger.error(f"获取数据资源完整图谱失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/graph', methods=['POST'])
-def data_resource_list_graph():
-    """获取数据资源亲缘关系图谱"""
-    try:
-        # 获取参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        resource_id = request.json.get('id')
-        meta = request.json.get('meta', True)
-        
-        if resource_id is None:
-            return jsonify(failed("资源ID不能为空"))
-            
-        # 确保传入的ID为整数
-        try:
-            resource_id = int(resource_id)
-        except (ValueError, TypeError):
-            return jsonify(failed(f"资源ID必须为整数, 收到的是: {resource_id}"))
-            
-        # 记录请求信息
-        logger.info(f"获取图谱请求,ID: {resource_id}")
-            
-        # 调用业务逻辑获取图谱
-        graph_data = resource_kinship_graph(resource_id, meta)
-        
-        return jsonify(success(graph_data))
-    except Exception as e:
-        logger.error(f"获取数据资源亲缘关系图谱失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/save/metadata', methods=['POST'])
-def id_data_save():
-    """保存数据资源关联的元数据"""
-    try:
-        # 获取参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        resource_id = request.json.get('id')
-        metadata_list = request.json.get('data', [])
-        
-        if resource_id is None:
-            return jsonify(failed("资源ID不能为空"))
-            
-        if not metadata_list:
-            return jsonify(failed("元数据列表不能为空"))
-            
-        # 处理元数据保存
-        with neo4j_driver.get_session() as session:
-            # 获取数据资源名称
-            resource_query = """
-            MATCH (n:DataResource) 
-            WHERE id(n) = $resource_id
-            RETURN n.name as resource_name
-            """
-            resource_result = session.run(
-                resource_query, resource_id=int(resource_id)
-            )
-            resource_record = resource_result.single()
-            
-            if not resource_record:
-                return jsonify(failed(f"未找到ID为{resource_id}的数据资源"))
-                
-            resource_name = resource_record["resource_name"]
-            
-            # 先删除现有关系
-            cypher_delete = """
-            MATCH (n:DataResource)-[r:INCLUDES]->()
-            WHERE id(n) = $resource_id
-            DELETE r
-            """
-            session.run(cypher_delete, resource_id=int(resource_id))
-            
-            # 添加新关系
-            for meta in metadata_list:
-                # 创建元数据节点
-                meta_cypher = """
-                MERGE (m:DataMeta {name_zh: $name_zh})
-                ON CREATE SET m.name_en = $name_en, 
-                            m.create_time = $create_time,
-                            m.data_type = $type
-                ON MATCH SET m.data_type = $type
-                RETURN m
-                """
-                
-                create_time = get_formatted_time()
-                meta_result = session.run(
-                    meta_cypher,
-                    name_zh=meta["name_zh"],
-                    name_en=meta["name_en"],
-                    create_time=create_time,
-                    type=meta["data_type"]
-                )
-                meta_record = meta_result.single()
-                if not meta_record:
-                    logger.error(f"创建元数据节点失败: {meta['name_zh']}")
-                    continue
-                meta_node = meta_record["m"]
-                meta_id = meta_node.id
-                
-                # 打印节点ID信息,便于调试
-                logger.info(f"元数据节点ID: {meta_id}, 类型: {type(meta_id)}")
-                logger.info(
-                    f"数据资源节点ID: {resource_id}, 类型: {type(resource_id)}"
-                )
-                
-                # 使用明确的属性名匹配而不是ID
-                rel_cypher = """
-                MATCH (a:DataResource {name: $r_name}),
-                      (m:DataMeta {name: $m_name})
-                MERGE (a)-[r:INCLUDES]->(m)
-                RETURN r
-                """
-                
-                rel_result = session.run(
-                    rel_cypher,
-                    r_name=resource_name,
-                    m_name=meta["name"]
-                )
-                
-                # 检查关系是否创建成功
-                if rel_result.single():
-                    logger.info(f"成功创建关系: {resource_name} -> {meta['name']}")
-                else:
-                    logger.warning("关系创建结果为空")
-
-                # 额外验证关系是否创建
-                verify_cypher = """
-                MATCH (a:DataResource {name: $r_name})
-                      -[r:INCLUDES]->(m:DataMeta {name: $m_name})
-                RETURN count(r) as rel_count
-                """
-                
-                verify_result = session.run(
-                    verify_cypher,
-                    r_name=resource_name,
-                    m_name=meta["name"]
-                )
-                
-                verify_record = verify_result.single()
-                count = verify_record["rel_count"] if verify_record else 0
-                logger.info(f"验证关系数量: {count}")
-                
-        return jsonify(success({"message": "元数据保存成功"}))
-    except Exception as e:
-        logger.error(f"保存数据资源关联的元数据失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/sql/test', methods=['POST'])
-def sql_test():
-    """测试SQL查询"""
-    try:
-        # 获取参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        sql_query = request.json.get('sql', '')
-        
-        if not sql_query:
-            return jsonify(failed("SQL查询不能为空"))
-            
-        # 解析SQL
-        parsed_sql = select_sql(sql_query)
-        
-        if not parsed_sql:
-            return jsonify(failed("解析SQL失败"))
-            
-        # 返回解析结果
-        return jsonify(success(parsed_sql))
-    except Exception as e:
-        logger.error(f"测试SQL查询失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-# 使用LLM识别DDL语句,用来代替原来的正则的方式
-# 用于在数据资源创建时,识别DDL语句 /api/resource/ddl/parse
-@bp.route('/ddl/parse', methods=['POST'])
-def ddl_identify():
-    """识别DDL语句"""
-    try:
-        # 获取参数 - 支持两种方式:上传文件或JSON
-        sql_content = ''
-        
-        # 检查是否有文件上传
-        if 'file' in request.files:
-            file = request.files['file']
-            # 检查文件是否存在且文件名不为空
-            if file and file.filename:
-                # 检查是否是SQL文件
-                if not file.filename.lower().endswith('.sql'):
-                    return jsonify(failed("只接受SQL文件"))
-                
-                # 读取文件内容
-                sql_content = file.read().decode('utf-8')
-                logger.info(f"从上传的文件中读取SQL内容,文件名: {file.filename}")
-        # 如果没有文件上传,检查是否有JSON输入
-        elif request.is_json and request.json:
-            sql_content = request.json.get('sql', '')
-            
-        # 如果两种方式都没有提供SQL内容,则返回错误
-        if not sql_content:
-            return jsonify(failed("SQL内容不能为空,请上传SQL文件或提供SQL内容"))
-        
-        parser = DDLParser()        
-        # 提取创建表的DDL语句
-        ddl_list = parser.parse_ddl(sql_content)
-            
-        if not ddl_list:
-            return jsonify(failed("未找到有效的CREATE TABLE语句"))
-        
-        # 处理表的存在状态
-        if isinstance(ddl_list, list):
-            # 新格式:数组格式
-            # 获取所有表名
-            table_names = []
-            for table_item in ddl_list:
-                if isinstance(table_item, dict) and 'table_info' in table_item:
-                    table_name = table_item['table_info'].get('name_en')
-                    if table_name:
-                        table_names.append(table_name)
-            
-            # 首先为所有表设置默认的exist状态
-            for table_item in ddl_list:
-                if isinstance(table_item, dict):
-                    table_item["exist"] = False
-            
-            if table_names:
-                try:
-                    # 查询表是否存在
-                    with neo4j_driver.get_session() as session:
-                        table_query = """
-                        UNWIND $names AS name
-                        OPTIONAL MATCH (n:DataResource {name_en: name})
-                        RETURN name, n IS NOT NULL AS ex
-                        """
-                        table_results = session.run(
-                            table_query, names=table_names
-                        )
-
-                        # 创建存在状态映射
-                        exist_map = {}
-                        for record in table_results:
-                            table_name = record["name"]
-                            exists = record["ex"]
-                            exist_map[table_name] = exists
-                        
-                        # 更新存在的表的状态
-                        for table_item in ddl_list:
-                            is_valid = (
-                                isinstance(table_item, dict)
-                                and 'table_info' in table_item
-                            )
-                            if is_valid:
-                                tbl_info = table_item['table_info']
-                                t_name = tbl_info.get('name_en')
-                                if t_name and t_name in exist_map:
-                                    table_item["exist"] = exist_map[t_name]
-                except Exception as e:
-                    logger.error(f"检查表存在状态失败: {str(e)}")
-                    # 如果查询失败,所有表保持默认的False状态
-        elif isinstance(ddl_list, dict):
-            # 兼容旧格式:字典格式(以表名为key)
-            # 获取所有表名
-            table_names = list(ddl_list.keys())
-            
-            # 首先为所有表设置默认的exist状态
-            for table_name in table_names:
-                # 确保 ddl_list[table_name] 是字典类型
-                if isinstance(ddl_list[table_name], dict):
-                    ddl_list[table_name]["exist"] = False
-                else:
-                    logger.warning(
-                        f"表 {table_name} 的值不是字典类型: "
-                        f"{type(ddl_list[table_name])}"
-                    )
-            
-            if table_names:
-                try:
-                    # 查询表是否存在
-                    with neo4j_driver.get_session() as session:
-                        table_query = """
-                        UNWIND $names AS name
-                        OPTIONAL MATCH (n:DataResource {name_en: name})
-                        RETURN name, n IS NOT NULL AS ex
-                        """
-                        table_results = session.run(
-                            table_query, names=table_names
-                        )
-
-                        # 更新存在的表的状态
-                        for record in table_results:
-                            table_name = record["name"]
-                            exists = record["ex"]
-                            # 确保表名存在且对应的值是字典类型
-                            is_valid = (
-                                table_name in ddl_list
-                                and isinstance(ddl_list[table_name], dict)
-                            )
-                            if is_valid:
-                                ddl_list[table_name]["exist"] = exists
-                except Exception as e:
-                    logger.error(f"检查表存在状态失败: {str(e)}")
-                    # 如果查询失败,所有表保持默认的False状态
-        
-        logger.debug(f"识别到的DDL语句: {json.dumps(ddl_list, ensure_ascii=False)}")
-            
-        return jsonify(success(ddl_list))
-    except Exception as e:
-        logger.error(f"识别DDL语句失败: {str(e)}")
-        logger.error(traceback.format_exc())  # 添加详细错误堆栈
-        return jsonify(failed(str(e)))
-        
-
-# 废弃的识别DDL语句方法,该API 与 ddl API 功能类似,但功能简化了
-@bp.route('/ddl/identify', methods=['POST'])
-def sql_ddl_identify():
-    """识别DDL语句"""
-    try:
-        # 获取参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        sql_content = request.json.get('sql', '')
-        
-        if not sql_content:
-            return jsonify(failed("SQL内容不能为空"))
-            
-        # 提取创建表的DDL语句
-        create_ddl_list = select_create_ddl(sql_content)
-        
-        if not create_ddl_list:
-            return jsonify(failed("未找到有效的CREATE TABLE语句"))
-            
-        return jsonify(success({"count": len(create_ddl_list)}))
-    except Exception as e:
-        logger.error(f"识别DDL语句失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/model/list', methods=['POST'])
-def resource_model_list():
-    """获取模型资源列表"""
-    try:
-        # 获取分页和筛选参数
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        page = int(request.json.get('current', 1))
-        page_size = int(request.json.get('size', 10))
-        name_filter = request.json.get('name')
-        
-        # 调用业务逻辑查询模型资源列表
-        resources, total_count = model_resource_list(
-            page, page_size, name_filter
-        )
-        
-        # 返回结果
-        return jsonify(success({
-            "records": resources,
-            "total": total_count,
-            "size": page_size,
-            "current": page
-        }))
-    except Exception as e:
-        logger.error(f"获取模型资源列表失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/detail', methods=['POST'])
-def data_resource_detail():
-    """获取数据资源详情"""
-    try:
-        # 获取资源ID
-        if not request.json:
-            return jsonify(failed('请求数据不能为空'))
-        
-        resource_id = request.json.get('id')
-        
-        if resource_id is None:
-            return jsonify(failed("资源ID不能为空"))
-        
-        # 确保传入的ID为整数
-        try:
-            resource_id = int(resource_id)
-        except (ValueError, TypeError):
-            return jsonify(failed(f"资源ID必须为整数, 收到的是: {resource_id}"))
-            
-        # 记录请求信息
-        logger.info(f"获取资源详情请求,ID: {resource_id}")
-        
-        # 调用业务逻辑查询数据资源详情
-        resource_data = handle_id_resource(resource_id)
-        
-        if not resource_data:
-            logger.error(f"资源不存在,ID: {resource_id}")
-            return jsonify(failed("资源不存在"))
-        
-        # 记录从handle_id_resource返回的数据
-        logger.info(
-            f"handle_id_resource返回数据,describe字段: "
-            f"{resource_data.get('describe')}"
-        )
-            
-        # 确保返回的数据格式符合要求
-        response_data = {
-            "parsed_data": resource_data.get("parsed_data", []),
-            "tag": resource_data.get(
-                "tag", {"name_zh": None, "name_en": None, "id": None}
-            ),
-            "leader": resource_data.get("leader", ""),
-            "organization": resource_data.get("organization", ""),
-            "name_zh": resource_data.get("name_zh", ""),
-            "name_en": resource_data.get("name_en", ""),
-            "data_sensitivity": resource_data.get("data_sensitivity", ""),
-            "storage_location": resource_data.get("storage_location", "/"),
-            "create_time": resource_data.get("create_time", ""),
-            "update_time": resource_data.get("update_time", ""),
-            "type": resource_data.get("type", ""),
-            "category": resource_data.get("category", ""),
-            "url": resource_data.get("url", ""),
-            "frequency": resource_data.get("frequency", ""),
-            "status": resource_data.get("status", True),
-            "id": resource_data.get("id"),
-            "keywords": resource_data.get("keywords", []),
-            "describe": resource_data.get("describe", ""),
-            "data_source": resource_data.get("data_source")  # 新增:数据源节点ID
-        }
-        
-        # 记录最终返回的数据
-        logger.info(
-            f"最终返回的response_data,describe字段: "
-            f"{response_data.get('describe')}"
-        )
-            
-        return jsonify(success(response_data))
-    except Exception as e:
-        logger.error(f"获取数据资源详情失败: {str(e)}")
-        return jsonify(failed(str(e)))
-
-
-@bp.route('/config', methods=['GET'])
-@require_auth
-def get_resource_config():
-    """获取数据资源配置信息"""
-    config = get_minio_config()
-    return jsonify({
-        'allowed_extensions': list(config['allowed_extensions']),
-        'bucket_name': config['bucket_name'],
-        'prefix': config['prefix']
-    })

+ 85 - 47
app/api/data_source/routes.py

@@ -4,11 +4,14 @@ import json
 from datetime import datetime
 from app.models.result import success, failed
 from app.api.data_source import bp
-from app.core.graph.graph_operations import create_or_get_node, execute_cypher_query
-from flask_sqlalchemy import SQLAlchemy
+from app.core.graph.graph_operations import (
+    create_or_get_node, execute_cypher_query
+)
 from sqlalchemy import create_engine, text, URL
+
 logger = logging.getLogger(__name__)
 
+
 # 创建数据源时使用此api
 @bp.route('/save', methods=['POST'])
 def data_source_save():
@@ -16,28 +19,42 @@ def data_source_save():
     try:
         # 获取表单数据
         data = request.json
-        logger.debug(f"保存数据源请求数据: {json.dumps(data, ensure_ascii=False) if data else 'None'}")
+        log_data = json.dumps(data, ensure_ascii=False) if data else 'None'
+        logger.debug(f"保存数据源请求数据: {log_data}")
 
         # 检查必填参数
-        required_fields = ['database', 'host', 'port', 'username', 'password', 'name_en', 'type']
-        missing_fields = [field for field in required_fields if not data.get(field)]
-        
+        required_fields = [
+            'database', 'host', 'port', 'username',
+            'password', 'name_en', 'type'
+        ]
+        if not data:
+            missing_fields = required_fields
+        else:
+            missing_fields = [
+                field for field in required_fields if not data.get(field)
+            ]
+
         if missing_fields:
             error_msg = f"缺少必填参数: {', '.join(missing_fields)}"
             logger.error(error_msg)
             return jsonify(failed(error_msg))
 
+        # 此时 data 一定不为 None
+        assert data is not None
+
         # 检查name_en是否已存在
         check_query = """
         MATCH (n:DataSource)
         WHERE n.name_en = $name_en
         RETURN n
         """
-        result = execute_cypher_query(check_query, {'name_en': data['name_en']})
-        
+        result = execute_cypher_query(
+            check_query, {'name_en': data['name_en']}
+        )
+
         # 添加创建时间
         data['create_dt'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-        
+
         if result:
             # 如果存在,更新节点
             node = result[0]['n']
@@ -58,7 +75,7 @@ def data_source_save():
             # 如果不存在,创建新节点
             node_id = create_or_get_node('DataSource', **data)
             message = "数据源创建成功"
-        
+
         # 返回成功结果
         return jsonify(success({
             "id": node_id,
@@ -67,8 +84,8 @@ def data_source_save():
     except Exception as e:
         logger.error(f"保存数据源失败: {str(e)}")
         return jsonify(failed(str(e)))
-    
-    
+
+
 # 获取数据源列表 或根据id获取数据源信息
 @bp.route('/list', methods=['POST'])
 def data_source_list():
@@ -76,11 +93,11 @@ def data_source_list():
     try:
         # 获取请求参数
         data = request.json
-        
+
         # 构建查询条件
         where_conditions = []
         params = {}
-        
+
         # 如果指定了id
         if data and 'id' in data:
             where_conditions.append("id(n) = $id")
@@ -91,37 +108,40 @@ def data_source_list():
                 if value:  # 只处理非空值
                     where_conditions.append(f"n.{key} = ${key}")
                     params[key] = value
-        
+
         # 构建WHERE子句
-        where_clause = " WHERE " + " AND ".join(where_conditions) if where_conditions else ""
-        
+        if where_conditions:
+            where_clause = " WHERE " + " AND ".join(where_conditions)
+        else:
+            where_clause = ""
+
         # 构建查询语句
         cypher = f"""
         MATCH (n:DataSource)
         {where_clause}
         RETURN n
         """
-        
+
         # 执行查询
         result = execute_cypher_query(cypher, params)
-        
+
         # 格式化结果
         data_sources = []
         for record in result:
             node = record['n']
             node['id'] = node['_id']
             data_sources.append(node)
-        
+
         # 返回结果
         return jsonify(success({
             "data_source": data_sources,
             "total": len(data_sources)
         }))
-            
+
     except Exception as e:
         logger.error(f"获取数据源列表失败: {str(e)}")
         return jsonify(failed(str(e)))
-    
+
 
 @bp.route('/delete', methods=['POST'])
 def data_source_delete():
@@ -129,14 +149,15 @@ def data_source_delete():
     try:
         # 获取请求参数
         data = request.json
-        logger.debug(f"删除数据源请求数据: {json.dumps(data, ensure_ascii=False) if data else 'None'}")
+        log_data = json.dumps(data, ensure_ascii=False) if data else 'None'
+        logger.debug(f"删除数据源请求数据: {log_data}")
 
         # 检查参数
         if not data or ('id' not in data and 'name_en' not in data):
             error_msg = "必须提供id或name_en参数"
             logger.error(error_msg)
             return jsonify(failed(error_msg))
-        
+
         # 构建删除条件
         if 'id' in data:
             where_clause = "id(n) = $id"
@@ -154,10 +175,10 @@ def data_source_delete():
         DELETE r, n
         RETURN count(n) as deleted_count
         """
-        
+
         # 执行删除
         result = execute_cypher_query(delete_query, params)
-        
+
         if result and result[0]['deleted_count'] > 0:
             return jsonify(success({
                 "message": "数据源删除成功",
@@ -167,11 +188,10 @@ def data_source_delete():
             error_msg = "未找到指定的数据源"
             logger.error(error_msg)
             return jsonify(failed(error_msg))
-            
+
     except Exception as e:
         logger.error(f"删除数据源失败: {str(e)}")
         return jsonify(failed(str(e)))
-    
 
 
 @bp.route('/parse', methods=['POST'])
@@ -180,7 +200,8 @@ def data_source_connstr_parse():
     try:
         # 获取请求参数
         data = request.json
-        logger.debug(f"解析连接字符串请求数据: {json.dumps(data, ensure_ascii=False) if data else 'None'}")
+        log_data = json.dumps(data, ensure_ascii=False) if data else 'None'
+        logger.debug(f"解析连接字符串请求数据: {log_data}")
 
         # 检查参数
         if not data or 'conn_str' not in data:
@@ -194,8 +215,11 @@ def data_source_connstr_parse():
         result = parser.parse_db_conn_str(data['conn_str'])
 
         # 检查解析结果
-        if isinstance(result, dict) and 'code' in result and result['code'] == 500:
-            error_msg = f"解析连接字符串失败: {result.get('message', '未知错误')}"
+        is_error = (isinstance(result, dict) and
+                    'code' in result and result['code'] == 500)
+        if is_error:
+            msg = result.get('message', '未知错误')
+            error_msg = f"解析连接字符串失败: {msg}"
             logger.error(error_msg)
             return jsonify(failed(error_msg))
 
@@ -205,7 +229,7 @@ def data_source_connstr_parse():
     except Exception as e:
         logger.error(f"解析连接字符串失败: {str(e)}")
         return jsonify(failed(str(e)))
-    
+
 
 @bp.route('/valid', methods=['POST'])
 def data_source_connstr_valid():
@@ -213,7 +237,8 @@ def data_source_connstr_valid():
     try:
         # 获取请求参数
         data = request.json
-        logger.debug(f"验证连接信息请求数据: {json.dumps(data, ensure_ascii=False) if data else 'None'}")
+        log_data = json.dumps(data, ensure_ascii=False) if data else 'None'
+        logger.debug(f"验证连接信息请求数据: {log_data}")
 
         # 检查参数
         if not data:
@@ -240,10 +265,13 @@ def data_source_connstr_valid():
             WHERE n.name_en = $name_en
             RETURN n
             """
-            existing_source = execute_cypher_query(check_query, {'name_en': data['name_en']})
-            
+            existing_source = execute_cypher_query(
+                check_query, {'name_en': data['name_en']}
+            )
+
             if existing_source:
-                return jsonify(success("连接信息验证通过,但该数据源的定义已经存在,如果保存则会更新该数据源"))
+                msg = "连接信息验证通过,但该数据源的定义已经存在,如果保存则会更新该数据源"
+                return jsonify(success(msg))
             else:
                 return jsonify(success("连接信息验证通过"))
         else:
@@ -252,7 +280,7 @@ def data_source_connstr_valid():
     except Exception as e:
         logger.error(f"验证连接信息失败: {str(e)}")
         return jsonify(failed(str(e)))
-    
+
 
 @bp.route('/conntest', methods=['POST'])
 def data_source_conn_test():
@@ -260,17 +288,28 @@ def data_source_conn_test():
     try:
         # 获取请求参数
         data = request.json
-        logger.debug(f"测试连接请求数据: {json.dumps(data, ensure_ascii=False) if data else 'None'}")
+        log_data = json.dumps(data, ensure_ascii=False) if data else 'None'
+        logger.debug(f"测试连接请求数据: {log_data}")
 
         # 检查必需参数
-        required_fields = ['type', 'username', 'host', 'port', 'database', 'password']
-        missing_fields = [field for field in required_fields if not data.get(field)]
-        
+        required_fields = [
+            'type', 'username', 'host', 'port', 'database', 'password'
+        ]
+        if not data:
+            missing_fields = required_fields
+        else:
+            missing_fields = [
+                field for field in required_fields if not data.get(field)
+            ]
+
         if missing_fields:
             error_msg = f"缺少必需参数: {', '.join(missing_fields)}"
             logger.error(error_msg)
             return jsonify(failed(error_msg))
 
+        # 此时 data 一定不为 None
+        assert data is not None
+
         # 构建数据库URL
         db_url = URL.create(
             drivername=data['type'],
@@ -283,7 +322,7 @@ def data_source_conn_test():
 
         # 创建数据库引擎
         engine = create_engine(db_url, connect_args={'connect_timeout': 5})
-        
+
         # 测试连接
         try:
             with engine.connect() as conn:
@@ -294,10 +333,7 @@ def data_source_conn_test():
                     "connected": True
                 }))
         except Exception as e:
-            return jsonify(failed({
-                "message": f"连接测试失败: {str(e)}",
-                "connected": False
-            }))
+            return jsonify(failed(f"连接测试失败: {str(e)}"))
 
     except Exception as e:
         logger.error(f"测试连接失败: {str(e)}")
@@ -306,4 +342,6 @@ def data_source_conn_test():
 
 @bp.route('/graph', methods=['POST'])
 def data_source_graph_relationship():
-    pass
+    """获取数据源关系图"""
+    # TODO: 待实现
+    return jsonify(failed("该功能尚未实现"))

+ 0 - 96
app/api/production_line/README.md

@@ -1,96 +0,0 @@
-# 生产线API接口模块
-
-本模块提供生产线相关的API接口,用于前端交互与数据展示。
-
-## 功能概述
-
-生产线API模块提供了生产线列表查询和图谱绘制的接口,支持数据模型、数据资源和数据指标的展示和关系查询。
-
-## API接口
-
-### 1. 生产线列表查询 (/production/line/list)
-
-- **URL**: `/production/line/list`
-- **方法**: POST
-- **描述**: 获取生产线列表,支持分页和名称过滤
-- **请求参数**:
-  ```json
-  {
-    "current": 1,     // 当前页码,默认为1
-    "size": 10,       // 每页大小,默认为10
-    "name": "关键词"  // 可选,按名称过滤
-  }
-  ```
-- **返回数据**:
-  ```json
-  {
-    "code": 200,
-    "success": true,
-    "message": "success",
-    "data": {
-      "records": [
-        {
-          "id": 123,
-          "name": "样例生产线",
-          "type": "DataModel"
-        }
-        // 更多记录...
-      ],
-      "total": 100,
-      "size": 10,
-      "current": 1
-    }
-  }
-  ```
-
-### 2. 生产线图谱绘制 (/production/line/graph)
-
-- **URL**: `/production/line/graph`
-- **方法**: POST
-- **描述**: 根据生产线ID绘制关系图谱
-- **请求参数**:
-  ```json
-  {
-    "id": 123  // 节点ID
-  }
-  ```
-- **返回数据**:
-  ```json
-  {
-    "code": 200,
-    "success": true,
-    "message": "success",
-    "data": {
-      "nodes": [
-        {"id": "节点ID", "text": "节点名称", "type": "节点类型"},
-        // 更多节点...
-      ],
-      "lines": [
-        {"from": "起始节点ID", "to": "目标节点ID", "text": "关系描述"},
-        // 更多连线...
-      ],
-      "rootId": "根节点ID"
-    }
-  }
-  ```
-
-## 技术实现
-
-本模块通过Flask框架实现API接口,使用Neo4j图数据库进行数据查询,主要涉及以下技术点:
-
-- Flask路由定义与请求处理
-- Neo4j图数据库Cypher查询
-- 数据分页与过滤处理
-- JSON数据序列化与返回
-
-## 依赖关系
-
-本模块依赖于core/production_line模块中的核心功能实现:
-
-```python
-from app.core.production_line import production_draw_graph
-```
-
-- 数据资源(DataResource):处理数据资源与元数据节点及数据标准之间的关系
-
-graph_data = production_draw_graph(resource_id, "DataResource") 

+ 0 - 12
app/api/production_line/__init__.py

@@ -1,12 +0,0 @@
-"""
-Production Line API module
-包含生产线相关的API接口
-"""
-
-from flask import Blueprint
-
-bp = Blueprint('production_line', __name__)
-
-from app.api.production_line import routes
-
-__all__ = ['bp'] 

+ 0 - 269
app/api/production_line/routes.py

@@ -1,269 +0,0 @@
-import json
-import logging
-import requests
-from flask import request, jsonify, current_app
-from app.api.production_line import bp
-from app.models.result import success, failed
-from app.api.graph.routes import MyEncoder, connect_graph
-from app.core.production_line import production_draw_graph
-# from app.core.production_line.production_line import execute_production_line  # 注释掉原来的导入
-
-
-logger = logging.getLogger(__name__)
-
-# 生产线列表
-@bp.route('/production/line/list', methods=['POST'])
-def production_line_list():
-    """
-    获取生产线列表,支持分页和名称过滤
-    
-    Args (通过JSON请求体):
-        current (int): 当前页码,默认为1
-        size (int): 每页大小,默认为10
-        name_zh (str, optional): 名称过滤条件
-        
-    Returns:
-        JSON: 包含生产线列表和分页信息的响应
-    """
-    try:
-        receiver = request.get_json()
-        page = int(receiver.get('current', 1))
-        page_size = int(receiver.get('size', 10))
-        name_zh_filter = receiver.get('name_zh', None)
-
-        # 计算跳过的记录的数量
-        skip_count = (page - 1) * page_size
-
-        if name_zh_filter:
-            where_clause = f"n.name_zh CONTAINS'{name_zh_filter}'"
-        else:
-            where_clause = "TRUE"
-        cql = f"""
-        MATCH (n)
-                 WHERE (n:DataModel OR n:DataResource OR n:DataMetric) AND {where_clause}
-        WITH id(n) AS id, n.name_zh AS name_zh, labels(n)[0] AS type,n
-        RETURN  {{
-            id: id,
-            name_zh: name_zh,
-            name_en: n.name_en,
-            type: type
-        }} AS result,n.create_time as create_time
-        ORDER BY create_time desc
-        SKIP {skip_count}
-        LIMIT {page_size}
-        """
-        
-        driver = None
-        try:
-            driver = connect_graph()
-            with driver.session() as session:
-                result = session.run(cql)
-                data = result.data()
-                records = []
-                for item in data:
-                    records.append(item['result'])
-
-                              # 获取总量
-                total_query = f"MATCH (n) WHERE (n:DataModel OR n:DataResource OR n:DataMetric) AND {where_clause}" \
-                              f" RETURN COUNT(n) AS total"
-                total_result = session.run(total_query).single()["total"]
-            
-            response_data = {'records': records, 'total': total_result, 'size': page_size, 'current': page}
-            res = success(response_data, "success")
-            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-        except (ConnectionError, ValueError) as e:
-            return json.dumps(failed(f"无法连接到数据库: {str(e)}"), ensure_ascii=False, cls=MyEncoder)
-        finally:
-            if driver:
-                driver.close()
-
-    except Exception as e:
-        res = failed({}, {"error": f"{e}"})
-        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
-
-
-# 根据生产线列表,传入id,绘制图谱
-@bp.route('/production/line/graph', methods=['POST'])
-def production_line_graph():
-    """
-    根据生产线ID绘制关系图谱
-    
-    Args (通过JSON请求体):
-        id (int): 节点ID
-        
-    Returns:
-        JSON: 包含图谱数据的响应
-    """
-    try:
-        # 获取请求参数
-        receiver = request.get_json()
-        if not receiver or 'id' not in receiver:
-            return json.dumps(failed("缺少必要参数: id"), ensure_ascii=False, cls=MyEncoder)
-            
-        id = receiver['id']
-        # 修改: 专门处理ID为0的情况,将ID类型视为整数
-        if id is None:
-            return json.dumps(failed("节点ID不能为空"), ensure_ascii=False, cls=MyEncoder)
-            
-        # 确保ID是整数类型
-        try:
-            id = int(id)
-        except (ValueError, TypeError):
-            return json.dumps(failed("节点ID必须是整数"), ensure_ascii=False, cls=MyEncoder)
-            
-        driver = None
-        try:
-            driver = connect_graph()
-            with driver.session() as session:
-                # 检查节点是否存在
-                check_query = """
-                MATCH (n) WHERE id(n) = $nodeId 
-                RETURN labels(n)[0] as type, n.name_zh as name_zh
-                """
-                result = session.run(check_query, nodeId=id)
-                record = result.single()
-                
-                if not record:
-                    return json.dumps(failed(f"节点不存在: ID={id}"), ensure_ascii=False, cls=MyEncoder)
-                    
-                type = record["type"]
-        except (ConnectionError, ValueError) as e:
-            return json.dumps(failed(f"无法连接到数据库: {str(e)}"), ensure_ascii=False, cls=MyEncoder)
-        finally:
-            if driver:
-                driver.close()
-            
-        # 生成图谱
-        data = production_draw_graph(id, type)
-        
-        # 检查返回结果是否包含错误
-        if "error" in data:
-            error_msg = data.pop("error")
-            return json.dumps(failed(error_msg, data), ensure_ascii=False, cls=MyEncoder)
-        
-        return json.dumps(success(data, "success"), ensure_ascii=False, cls=MyEncoder)
-
-    except Exception as e:
-        logger.error(f"生成图谱失败: {str(e)}")
-        return json.dumps(failed(str(e)), ensure_ascii=False, cls=MyEncoder)
-
-
-"""
-Manual execution API endpoint
-Author: paul
-Date: 2024-03-20
-"""
-
-@bp.route('/production/line/execute', methods=['POST'])
-def production_line_execute():
-    """
-    手动执行数据资源加载 - 通过调用 Airflow REST API
-    
-    Args (通过JSON请求体):
-        id (int): 数据资源ID
-        target_table (str): 目标表名
-        dependency_level (str): 依赖级别,默认为 "resource"
-        
-    Returns:
-        JSON: 执行结果
-    """
-    try:
-        # 获取请求参数
-        receiver = request.get_json()
-        if not receiver:
-            return jsonify(failed("请求体不能为空"))
-            
-        #resource_id = receiver.get('id')
-        target_table = receiver.get('target_table')
-        dependency_level = receiver.get('dependency_level', 'resource')
-            
-        if not target_table:
-            return jsonify(failed("目标表名不能为空"))
-        
-        # Airflow API 配置 - 从配置文件获取
-        AIRFLOW_API_ENDPOINT = '/api/v1/dags/dataops_productline_manual_trigger_dag/dagRuns'
-        airflow_base_url = current_app.config['AIRFLOW_BASE_URL']
-        airflow_url = f"{airflow_base_url}{AIRFLOW_API_ENDPOINT}"
-        
-        # 构建请求数据
-        payload = {
-            "conf": {
-                "target_table": target_table,
-                "dependency_level": dependency_level
-            }
-        }
-        
-        # 设置认证信息(Basic Auth) - 从配置文件获取
-        auth = (current_app.config['AIRFLOW_AUTH_USER'], current_app.config['AIRFLOW_AUTH_PASSWORD'])
-        
-        # 设置请求头
-        headers = {
-            'Content-Type': 'application/json'
-        }
-        
-        # 记录请求信息到日志
-        logger.info(f"准备调用 Airflow API: {airflow_url}")
-        logger.info(f"请求参数: {json.dumps(payload, ensure_ascii=False, indent=2)}")
-        
-        # 调用 Airflow API
-        response = requests.post(
-            airflow_url,
-            json=payload,
-            auth=auth,
-            headers=headers,
-            timeout=30
-        )
-        
-        # 检查响应状态
-        if response.status_code == 200:
-            airflow_result = response.json()
-            
-            # 打印Airflow API返回的结果到日志
-            logger.info(f"Airflow API 返回结果: {json.dumps(airflow_result, ensure_ascii=False, indent=2)}")
-            
-            # 获取状态
-            state = airflow_result.get("state")
-            
-            # 根据状态设置消息
-            if state == "queued":
-                message = "触发成功,正在执行"
-                logger.info(f"DAG触发成功,状态: {state}, 运行ID: {airflow_result.get('dag_run_id')}")
-                # 返回成功响应,包含完整的 Airflow 返回结果
-                return jsonify(success(airflow_result, message))
-            else:
-                message = "无法触发执行,请联系管理员"
-                logger.warning(f"DAG触发状态异常,状态: {state}, 运行ID: {airflow_result.get('dag_run_id')}")
-                # 即使状态不是 queued,也返回完整的 Airflow 结果,但使用 failed 状态
-                return jsonify(failed(message, airflow_result))
-            
-        else:
-            # 处理错误响应
-            error_msg = f"Airflow API 调用失败,状态码: {response.status_code}"
-            try:
-                error_detail = response.json()
-                error_msg += f",错误详情: {error_detail}"
-            except:
-                error_msg += f",响应内容: {response.text}"
-                
-            logger.error(error_msg)
-            return jsonify(failed(error_msg))
-            
-    except requests.exceptions.Timeout:
-        error_msg = "调用 Airflow API 超时"
-        logger.error(error_msg)
-        return jsonify(failed(error_msg))
-        
-    except requests.exceptions.ConnectionError:
-        error_msg = "无法连接到 Airflow 服务器"
-        logger.error(error_msg)
-        return jsonify(failed(error_msg))
-        
-    except requests.exceptions.RequestException as e:
-        error_msg = f"请求 Airflow API 时发生错误: {str(e)}"
-        logger.error(error_msg)
-        return jsonify(failed(error_msg))
-        
-    except Exception as e:
-        error_msg = f"执行数据资源加载失败: {str(e)}"
-        logger.error(error_msg)
-        return jsonify(failed(error_msg)) 

+ 43 - 26
app/api/system/routes.py

@@ -8,7 +8,6 @@ from app.api.system import bp
 from app.models.result import success, failed
 import logging
 from app.core.system import (
-    check_neo4j_connection,
     check_system_health,
     get_system_info,
     get_system_config,
@@ -21,13 +20,14 @@ from app.core.common.functions import translate_and_parse
 
 logger = logging.getLogger("app")
 
+
 # 健康检查接口
 @bp.route('/health', methods=['GET'])
 def health_check():
     """
     系统健康状态检查
     检查关键依赖组件的连接状态
-    
+
     Returns:
         JSON: 系统健康状态信息
     """
@@ -39,13 +39,14 @@ def health_check():
         logger.error(f"健康检查失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 系统配置信息
 @bp.route('/config', methods=['GET'])
 def system_config():
     """
     获取系统配置信息
     返回非敏感的系统配置项
-    
+
     Returns:
         JSON: 系统配置信息
     """
@@ -57,13 +58,14 @@ def system_config():
         logger.error(f"获取系统配置失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 系统信息接口
 @bp.route('/info', methods=['GET'])
 def system_info():
     """
     获取系统运行环境信息
     包括操作系统、Python版本、资源使用情况等
-    
+
     Returns:
         JSON: 系统运行环境详细信息
     """
@@ -75,13 +77,14 @@ def system_info():
         logger.error(f"获取系统信息失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 配置验证接口
 @bp.route('/config/validate', methods=['GET'])
 def config_validate():
     """
     验证系统配置的有效性
     检查必要的配置项是否存在且有效
-    
+
     Returns:
         JSON: 配置验证结果
     """
@@ -96,32 +99,36 @@ def config_validate():
         logger.error(f"配置验证失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 用户注册接口
 @bp.route('/auth/register', methods=['POST'])
 def user_register():
     """
     用户注册
-    
+
     请求参数:
         username: 用户名
         password: 密码
-    
+
     Returns:
         JSON: 注册结果
     """
     try:
         # 获取请求参数
         data = request.json
+        if not data:
+            return jsonify(failed("请求数据不能为空", code=400))
+
         username = data.get('username')
         password = data.get('password')
-        
+
         # 参数验证
         if not username or not password:
             return jsonify(failed("用户名和密码不能为空", code=400))
-        
+
         # 注册用户
         success_flag, message = register_user(username, password)
-        
+
         if success_flag:
             return jsonify(success(message="注册成功"))
         else:
@@ -130,49 +137,54 @@ def user_register():
         logger.error(f"用户注册失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 用户登录接口
 @bp.route('/auth/login', methods=['POST'])
 def user_login():
     """
     用户登录
-    
+
     请求参数:
         username: 用户名
         password: 密码
-    
+
     Returns:
         JSON: 登录结果,包含用户信息
     """
     try:
         # 获取请求参数
         data = request.json
+        if not data:
+            return jsonify(failed("请求数据不能为空", code=400))
+
         username = data.get('username')
         password = data.get('password')
-        
+
         # 参数验证
         if not username or not password:
             return jsonify(failed("用户名和密码不能为空", code=400))
-        
+
         # 登录验证
         success_flag, result = login_user(username, password)
-        
+
         if success_flag:
             return jsonify(success(result, "登录成功"))
         else:
-            return jsonify(failed(result, code=401))
+            return jsonify(failed(str(result), code=401))
     except Exception as e:
         logger.error(f"用户登录失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 获取用户信息接口
 @bp.route('/auth/user/<username>', methods=['GET'])
 def get_user(username):
     """
     获取用户信息
-    
+
     Args:
         username: 用户名
-    
+
     Returns:
         JSON: 用户信息
     """
@@ -186,38 +198,43 @@ def get_user(username):
         logger.error(f"获取用户信息失败: {str(e)}")
         return jsonify(failed(str(e)))
 
+
 # 翻译接口
 @bp.route('/translate', methods=['POST'])
 def translate():
     """
     翻译节点名称
-    
+
     请求参数:
         node_name: 需要翻译的节点名称
-    
+
     Returns:
         JSON: 翻译结果
     """
     try:
         # 获取请求参数
         data = request.json
+        if not data:
+            return jsonify(failed("请求数据不能为空", code=400))
+
         node_name = data.get('node_name')
-        
+
         # 参数验证
         if not node_name:
             return jsonify(failed("node_name参数不能为空", code=400))
-        
+
         # 调用翻译函数
         translated_result = translate_and_parse(node_name)
-        
+
         # 返回翻译结果
+        translated = translated_result if translated_result else node_name
         result = {
             "original": node_name,
-            "translated": translated_result if translated_result else node_name,
+            "translated": translated,
             "translated_list": translated_result
         }
-        
+
         return jsonify(success(result, "翻译成功"))
     except Exception as e:
         logger.error(f"翻译失败: {str(e)}")
-        return jsonify(failed(f"翻译失败: {str(e)}")) 
+        return jsonify(failed(f"翻译失败: {str(e)}"))

BIN
app/app.rar


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 225 - 220
app/core/business_domain/business_domain.py


+ 0 - 37
app/core/data_flow/测试.py

@@ -1,37 +0,0 @@
-#!/usr/bin/env python3
-"""
-测试
-
-任务ID: 10
-创建时间: 2025-12-01 09:43:07.396030
-创建者: cursor
-
-任务描述:
-# Task: 测试
-
-## Update Mode
-- **Mode**: Append (追加模式)
-- **Description**: 新数据将追加到目标表,不删除现有数据
-
-## Implementation Steps
-1. Extract data from source tables as specified in the DDL
-2. Apply transformation logic according to the rule:
-3. Generate Python program to implement the data transformation logic
-4. Write transformed data to target table using append mode
-5. Create an n8n workflow to schedule and execute the Python program
-
-注意:此文件为任务占位符,需要根据任务描述实现具体功能。
-"""
-
-# TODO: 根据任务描述实现功能
-# # Task: 测试
-
-## Update Mode
-- **Mode**: Append (追加模式)
-- **Description**: 新数据将追加到目标表,不删除现有数据
-
-## Imple...
-
-if __name__ == '__main__':
-    print("任务文件已创建,请根据任务描述实现具体功能")
-    pass

+ 0 - 6
app/core/data_interface/interface.py

@@ -582,7 +582,6 @@ def label_kinship_graph(nodeid):
     OPTIONAL MATCH(b:DataModel)-[:LABEL]-(la)
     OPTIONAL MATCH(meta:DataMeta)-[:LABEL]-(la)
     OPTIONAL MATCH(d:data_standard)-[:LABEL]-(la)
-    OPTIONAL MATCH(e:DataMetric)-[:LABEL]-(la)
     WITH
         collect({
             id:toString(id(a)),
@@ -597,11 +596,6 @@ def label_kinship_graph(nodeid):
         collect({
             id:toString(id(d)),
             text:d.name_zh,
-            type:split(labels(d)[0],'_')[1]
-        })+
-        collect({
-            id:toString(id(e)),
-            text:e.name_zh,
             type:split(labels(e)[0],'_')[1]
         })+
         collect({

+ 0 - 116
app/core/data_metric/README.md

@@ -1,116 +0,0 @@
-# 核心数据指标模块
-
-## 简介
-
-核心数据指标模块(`app.core.data_metric`)包含与数据指标相关的所有核心业务逻辑,负责实现数据指标的管理、查询、图谱生成等功能的核心算法和数据处理逻辑。该模块不直接处理HTTP请求,而是由API层调用,实现业务与接口的分离。
-
-## 主要功能
-
-1. **数据指标管理**:创建、更新、查询数据指标的核心逻辑
-2. **数据指标列表**:实现数据指标列表的查询和过滤功能
-3. **图谱生成**:计算和生成不同类型的数据指标关系图谱,包括:
-   - 血缘关系图谱(Kinship Graph)
-   - 影响关系图谱(Impact Graph)
-   - 完整关系图谱(All Relationships Graph)
-4. **关系处理**:管理数据指标与其他实体(数据模型、其他指标、元数据、标签)之间的关系
-
-## 核心函数列表
-
-| 函数名 | 描述 |
-|-------|------|
-| `metric_list()` | 获取数据指标列表,支持多种过滤条件 |
-| `handle_metric_relation()` | 处理数据指标血缘关系 |
-| `id_mertic_graph()` | 生成数据指标关系图谱 |
-| `handle_data_metric()` | 创建或更新数据指标节点 |
-| `handle_meta_data_metric()` | 处理数据指标与其他节点的关系 |
-| `handle_id_metric()` | 获取数据指标详情 |
-| `metric_kinship_graph()` | 生成数据指标血缘关系图谱 |
-| `metric_impact_graph()` | 生成数据指标影响关系图谱 |
-| `metric_all_graph()` | 生成数据指标所有关系图谱 |
-| `data_metric_edit()` | 编辑数据指标 |
-
-## 数据模型
-
-### 数据指标(data_metric)
-
-数据指标是对数据的测量和计算规则,主要属性包括:
-
-- **name**: 指标名称(中文)
-- **en_name**: 指标英文名称
-- **category**: 指标分类
-- **describe**: 指标描述
-- **time**: 创建/更新时间
-- **tag**: 关联的标签ID
-- **code**: 计算代码
-- **id_list**: 关联的数据模型或其他指标ID列表
-
-### 指标关系
-
-数据指标与其他实体之间的主要关系类型:
-
-- **origin**: 指标来源(指标→模型或指标→指标)
-- **connection**: 指标与元数据的连接
-- **label**: 指标与标签的连接
-- **child**: 指标的下级关系
-
-## 使用示例
-
-### 1. 获取数据指标列表
-
-```python
-from app.core.data_metric.metric_interface import metric_list
-
-# 获取第一页,每页10条数据
-skip_count = 0
-page_size = 10
-data, total = metric_list(skip_count, page_size, name_filter="用户")
-
-print(f"共找到 {total} 条记录")
-for item in data:
-    print(f"指标名称: {item['name']}, 分类: {item['category']}")
-```
-
-### 2. 生成指标关系图谱
-
-```python
-from app.core.data_metric.metric_interface import metric_all_graph
-
-# 生成某个指标的所有关系图谱,包含元数据
-graph_data = metric_all_graph(nodeid=123, meta=True)
-
-# 输出图谱数据
-print(f"节点数量: {len(graph_data['nodes'])}")
-print(f"连线数量: {len(graph_data['lines'])}")
-```
-
-## 依赖项
-
-本模块主要依赖以下组件:
-
-1. **py2neo**: 用于Neo4j图数据库操作
-2. **routes.graph_routes**: 提供图数据库连接
-3. **DataOps_function.common_functions**: 提供通用操作函数
-4. **DataOps_function.meta_data_function**: 提供元数据相关功能
-5. **core.graph.graph_operations**: 提供节点和关系操作功能
-
-## 错误处理
-
-本模块使用Python标准异常处理机制。大多数函数在遇到错误时会抛出相应的异常,由调用者(通常是API层)捕获和处理。模块还使用logging模块记录关键操作和错误,日志文件为`metric_interface.log`。
-
-## 性能考虑
-
-数据指标图谱查询可能涉及大量的节点和关系,查询性能可能受到影响。为优化性能,本模块:
-
-1. 使用MATCH查询时,尽量添加适当的WHERE条件减少结果集
-2. 使用SKIP和LIMIT进行分页查询
-3. 合理使用OPTIONAL MATCH减少查询失败
-4. 对图谱节点和关系进行过滤,只返回有效的数据
-
-## 代码结构
-
-- **__init__.py**: 导入模块接口
-- **metric_interface.py**: 包含所有核心功能实现
-
-## 维护者
-
-数据平台开发团队 

+ 0 - 2
app/core/data_metric/__init__.py

@@ -1,2 +0,0 @@
-# 数据指标业务逻辑模块
-from app.core.data_metric import metric_interface 

+ 0 - 1042
app/core/data_metric/metric_interface.py

@@ -1,1042 +0,0 @@
-"""
-数据指标核心业务逻辑模块
-
-该模块提供了数据指标的各种核心功能,包括:
-- 指标列表查询和过滤
-- 指标血缘关系处理
-- 指标图谱生成(血缘关系、影响关系、所有关系)
-- 指标数据的创建、更新和查询
-"""
-
-import json
-import logging
-from py2neo import Relationship
-from app.core.common import delete_relationships, update_or_create_node, get_node_by_id_no_label
-from app.core.meta_data import get_formatted_time
-from app.core.graph.graph_operations import connect_graph
-from app.core.data_resource.resource import get_node_by_id
-from app.core.graph.graph_operations import get_node, create_or_get_node, relationship_exists
-
-# 使用应用日志
-logger = logging.getLogger("app")
-
-def metric_list(skip_count, page_size, name_en_filter=None,
-                name_zh_filter=None, category_filter=None, create_time_filter=None, tag_filter=None):
-    """
-    获取数据指标列表
-    
-    Args:
-        skip_count: 跳过的记录数量
-        page_size: 每页记录数量
-        name_en_filter: 英文名称过滤条件
-        name_zh_filter: 中文名称过滤条件
-        category_filter: 分类过滤条件
-        create_time_filter: 创建时间过滤条件
-        tag_filter: 标签ID过滤条件
-        
-    Returns:
-        tuple: (数据列表, 总记录数)
-    """
-    data = []
-
-    # 构建查询条件
-    params = {}
-
-    # 节点本身的过滤条件(放在 MATCH 之后、OPTIONAL MATCH 之前)
-    node_conditions = []
-    if name_zh_filter:
-        node_conditions.append("n.name_zh CONTAINS $name_zh_filter")
-        params['name_zh_filter'] = name_zh_filter
-    if name_en_filter:
-        node_conditions.append("n.name_en CONTAINS $name_en_filter")
-        params['name_en_filter'] = name_en_filter
-    if category_filter:
-        node_conditions.append("n.category CONTAINS $category_filter")
-        params['category_filter'] = category_filter
-    if create_time_filter:
-        node_conditions.append("n.create_time CONTAINS $create_time_filter")
-        params['create_time_filter'] = create_time_filter
-
-    node_where = (
-        "WHERE " + " AND ".join(node_conditions)
-        if node_conditions else ""
-    )
-
-    # 标签过滤条件(放在 OPTIONAL MATCH 之后)
-    tag_conditions = []
-    if tag_filter:
-        tag_conditions.append("id(la) = $tag_filter")
-        params['tag_filter'] = tag_filter
-
-    tag_where = (
-        "WHERE " + " AND ".join(tag_conditions)
-        if tag_conditions else ""
-    )
-
-    # 构建查询语句
-    cql = f"""
-    MATCH (n:DataMetric)
-    {node_where}
-    OPTIONAL MATCH (n)-[:LABEL]->(la:DataLabel)
-    {tag_where}
-    WITH n, la,
-         properties(n) AS properties,
-         n.create_time AS create_time,
-         id(n) AS nodeid,
-         CASE WHEN la IS NOT NULL
-              THEN {{id: id(la), name_zh: la.name_zh}}
-              ELSE null
-         END AS tag
-    RETURN properties, create_time, nodeid, tag
-    ORDER BY create_time DESC
-    SKIP $skip_count
-    LIMIT $page_size
-    """
-    
-    params['skip_count'] = skip_count
-    params['page_size'] = page_size
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            # 执行主查询
-            result = session.run(cql, **params)
-            for record in result:
-                properties = record['properties']
-                properties['id'] = record['nodeid']
-                properties['tag'] = record['tag']
-                
-                # 解析JSON字段
-                if "id_list" in properties and properties['id_list']:
-                    try:
-                        properties['id_list'] = json.loads(properties['id_list'])
-                    except (json.JSONDecodeError, TypeError):
-                        properties['id_list'] = []
-                
-                # 设置默认值
-                if "describe" not in properties or properties["describe"] is None:
-                    properties["describe"] = None
-                
-                data.append(properties)
-
-            # 获取总数 - 使用相同的过滤条件
-            total_query = f"""
-            MATCH (n:DataMetric)
-            {node_where}
-            OPTIONAL MATCH (n)-[:LABEL]->(la:DataLabel)
-            {tag_where}
-            RETURN COUNT(DISTINCT n) AS total
-            """
-            total_result = session.run(total_query, **params).single()["total"]
-        
-            return data, total_result
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return [], 0
-    finally:
-        if driver:
-            driver.close()
-
-
-def handle_metric_relation(model_ids):
-    """
-    处理数据指标血缘关系
-    
-    Args:
-        model_ids: 数据模型ID
-        
-    Returns:
-        list: 血缘关系数据
-    """
-    query = """
-            MATCH (search:DataModel)-[:connection]->(common_node:meta_node)<-[:connection]-(connect:DataModel)
-            WHERE id(search) = $model_Ids
-            WITH search, connect, common_node
-            MATCH (search)-[:connection]->(search_node:meta_node)
-            WITH search, connect, common_node, collect(DISTINCT id(search_node)) AS search_nodes
-            MATCH (connect)-[:connection]->(connect_node:meta_node)
-            WITH search, connect, common_node, search_nodes, collect(DISTINCT id(connect_node)) AS connect_nodes
-            WITH search, connect, search_nodes, connect_nodes, collect(DISTINCT id(common_node)) AS common_nodes
-
-            // 剔除 search_nodes 和 connect_nodes 中包含在 common_nodes 中的内容
-            WITH search, connect, common_nodes,
-                 [node IN search_nodes WHERE NOT node IN common_nodes] AS filtered_search_nodes,
-                 [node IN connect_nodes WHERE NOT node IN common_nodes] AS filtered_connect_nodes
-
-            RETURN  id(connect) as blood_model, common_nodes, 
-            filtered_search_nodes as origin_nodes, filtered_connect_nodes as blood_nodes
-            """
-
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            result = session.run(query, model_Ids=model_ids)
-            return result.data()
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return []
-    finally:
-        if driver:
-            driver.close()
-
-
-def id_mertic_graph(id):
-    """
-    生成数据指标图谱
-    
-    Args:
-        id: 指标ID
-        
-    Returns:
-        dict: 图谱数据,包含节点、连线和根节点ID
-    """
-    query = """
-    MATCH (n:DataMetric)
-    WHERE id(n) = $nodeId
-    WITH n, apoc.convert.fromJsonList(n.model_id) AS id_lists
-    UNWIND id_lists AS id_list
-    WITH n, id_list.id AS model_id, id_list.metaData AS meta_ids
-    OPTIONAL MATCH (t:DataLabel) WHERE id(t) = n.tag
-    UNWIND meta_ids AS meta_id
-    MATCH (d:DataModel) WHERE id(d) = model_id
-    MATCH (a:DataMeta) WHERE id(a) = meta_id
-    with 
-       collect({from:toString(id(n)),to:toString(id(d)),text:"来源"})+
-       collect({from:toString(id(n)),to:toString(id(a)),text:"包含"})+
-       collect({from:toString(id(d)),to:toString(id(a)),text:"包含"})+
-       collect({from:toString(id(n)),to:toString(id(t)),text:"标签"})+
-       collect({from:toString(id(d)),to:toString(id(t)),text:"标签"})AS line,
-       collect({id: toString(id(n)), text: n.name_zh, type: "metric"}) +
-       collect({id: toString(id(d)), text: d.name_zh, type: "model"}) +
-       collect({id: toString(id(t)), text: t.name_zh, type: "label"}) +
-       collect({id: toString(id(a)), text: a.name_zh}) AS node,n
-    WITH apoc.coll.toSet(line) AS lines,
-                 apoc.coll.toSet(node) AS nodes,
-                 toString(id(n)) as res
-    RETURN lines,nodes,res
-    """
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            data = session.run(query, nodeId=id)
-            
-            res = {}
-            for item in data:
-                res = {
-                    "nodes": [record for record in item['nodes'] if record['id']],
-                    "lines": [record for record in item['lines'] if record['from'] and record['to']],
-                    "rootId": item['res'],
-                }
-
-        logger.info(res)  # 记录 'res' 变量
-        return res
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return {}
-    finally:
-        if driver:
-            driver.close()
-
-
-def handle_data_metric(metric_name, result_list, receiver):
-    """
-    创建一个数据指标节点
-    
-    Args:
-        metric_name: 指标名称
-        result_list: 结果列表
-        receiver: 请求参数
-        
-    Returns:
-        tuple: (指标节点ID, ID列表)
-    """
-    data_metric_en = result_list[0]
-    id_list = receiver['id_list']
-    receiver['id_list'] = json.dumps(receiver['id_list'], ensure_ascii=False)
-
-    receiver.update({
-        'create_time': get_formatted_time(),
-        'name_en': data_metric_en
-    })
-
-    # get_node 和 create_or_get_node 都返回节点ID(整数)
-    data_metric_node_id = get_node('DataMetric', name_zh=metric_name) or create_or_get_node('DataMetric', **receiver)
-
-    # 处理子节点关系
-    child_list = receiver['childrenId']
-    driver = connect_graph()
-    if driver:
-        with driver.session() as session:
-            for child_id in child_list:
-                # 检查关系是否已存在
-                if not relationship_exists(data_metric_node_id, 'child', child_id):
-                    # 创建关系
-                    create_rel_query = """
-                    MATCH (parent:DataMetric), (child)
-                    WHERE id(parent) = $parent_id AND id(child) = $child_id
-                    MERGE (parent)-[r:child]->(child)
-                    RETURN r
-                    """
-                    session.run(create_rel_query, parent_id=data_metric_node_id, child_id=child_id)
-
-            # 处理标签关系
-            if receiver.get('tag'):
-                tag_id = receiver['tag']
-                if not relationship_exists(data_metric_node_id, 'LABEL', tag_id):
-                    # 创建标签关系
-                    create_label_query = """
-                    MATCH (metric:DataMetric), (label:DataLabel)
-                    WHERE id(metric) = $metric_id AND id(label) = $label_id
-                    MERGE (metric)-[r:LABEL]->(label)
-                    RETURN r
-                    """
-                    session.run(create_label_query, metric_id=data_metric_node_id, label_id=tag_id)
-
-    return data_metric_node_id, id_list
-
-
-def handle_meta_data_metric(data_metric_node_id, id_list):
-    """
-    处理数据指标与其他节点之间的关系
-    
-    Args:
-        data_metric_node_id: 数据指标节点ID
-        id_list: ID列表
-    """
-    # 提取 model_id 和 metric_id
-    model_ids = [item['id'] for item in id_list if item['type'] == 'model']
-    metric_ids = [item['id'] for item in id_list if item['type'] == 'metric']
-    
-    with connect_graph().session() as session:
-        # 创建与DataModel的关系
-        if model_ids:
-            cql_resource = """
-                MATCH (n:DataMetric)
-                WHERE id(n) = $data_metric_node_id
-                UNWIND $model_ids AS model_id
-                MATCH (d:DataModel)
-                WHERE id(d) = model_id
-                MERGE (n)-[:origin]->(d)
-            """
-            session.run(cql_resource, data_metric_node_id=data_metric_node_id, model_ids=model_ids)
-            
-        # 创建与DataMetric的关系
-        if metric_ids:
-            cql_resource = """
-                MATCH (n:DataMetric)
-                WHERE id(n) = $data_metric_node_id
-                UNWIND $metric_ids AS metric_id
-                MATCH (d:DataMetric)
-                WHERE id(d) = metric_id
-                MERGE (n)-[:origin]->(d)
-            """
-            session.run(cql_resource, data_metric_node_id=data_metric_node_id, metric_ids=metric_ids)
-
-        # 创建与元数据的关系
-        for record in id_list:
-            if "metaData" in record and record['metaData'] != []:
-                cql_meta = """
-                            MATCH (n:DataMetric)
-                            WHERE id(n) = $data_metric_node_id
-                            UNWIND $meta_ids AS meta_id
-                            MATCH (d:DataMeta)
-                            WHERE id(d) = meta_id
-                            MERGE (n)-[:connection]->(d)
-                        """
-                session.run(cql_meta, data_metric_node_id=data_metric_node_id, meta_ids=record['metaData'])
-
-
-def handle_id_metric(id):
-    """
-    获取数据指标详情
-    
-    Args:
-        id: 指标ID
-        
-    Returns:
-        dict: 指标详情数据
-    """
-    query = """
-    MATCH (n:DataMetric)
-    WHERE id(n) = $nodeId
-    
-    // 查找第一层关系 - 来源关系(DataModel 和 DataMetric)
-    OPTIONAL MATCH (n)-[:origin]->(origin)
-    WHERE origin:DataModel OR origin:DataMetric
-    
-    // 查找第一层关系 - 元数据连接
-    OPTIONAL MATCH (n)-[:connection]->(meta:DataMeta)
-    
-    // 查找第一层关系 - 数据标签
-    OPTIONAL MATCH (n)-[:LABEL]->(label:DataLabel)
-    
-    // 查找第一层关系 - 父节点
-    OPTIONAL MATCH (parent:DataMetric)-[:child]->(n)
-    
-    // 聚合数据
-    WITH n, 
-         collect(DISTINCT label) AS labels,
-         collect(DISTINCT parent) AS parents,
-         collect(DISTINCT origin) AS origins,
-         collect(DISTINCT meta) AS metas
-    
-    // 构建 id_list(来源信息和元数据)
-    WITH n, labels, parents,
-         [origin IN origins | {
-             model_name: origin.name_zh,
-             model_id: id(origin),
-             meta: [m IN metas | id(m)],
-             type: CASE 
-                 WHEN 'DataModel' IN labels(origin) THEN 'model'
-                 WHEN 'DataMetric' IN labels(origin) THEN 'metric'
-                 ELSE null
-             END
-         }] AS id_list_data
-    
-    // 返回结果
-    RETURN 
-        properties(n) AS properties,
-        id_list_data AS id_list,
-        CASE WHEN size(labels) > 0 
-             THEN {id: id(labels[0]), name_zh: labels[0].name_zh}
-             ELSE null
-        END AS tag,
-        [p IN parents | {id: id(p), name_zh: p.name_zh}] AS parentId
-    """
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            result = session.run(query, nodeId=id)
-            data_ = result.data()
-
-        if not data_:
-            return {"data_metric": {}}
-
-        record = data_[0]
-        properties = record['properties']
-        properties['id_list'] = record['id_list']
-        properties['tag'] = record['tag']
-        properties['parentId'] = record['parentId']
-
-        # 移除不需要的属性
-        properties.pop('model_id', None)
-
-        # 添加缺失的属性
-        for key in ["describe", "tag", "code"]:
-            if key not in properties:
-                properties[key] = None
-
-        response_data = {"data_metric": properties}
-
-        return response_data
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return {"data_metric": {}}
-    finally:
-        if driver:
-            driver.close()
-
-
-def metric_kinship_graph(nodeid, meta):
-    """
-    生成数据指标血缘关系图谱
-    
-    Args:
-        nodeid: 节点ID
-        meta: 是否包含元数据
-        
-    Returns:
-        dict: 图谱数据
-    """
-    # 查询语句 - 使用简单的路径匹配实现逐层查找
-    cql = """
-    // 使用可变长度路径查找所有相关节点
-    MATCH path = (start)-[:origin*0..]->(target)
-    WHERE id(start) = $nodeId
-    
-    // 提取路径中的所有节点
-    WITH collect(DISTINCT path) as paths
-    UNWIND paths as p
-    UNWIND nodes(p) as n
-    
-    // 收集所有节点信息
-    WITH collect(DISTINCT {
-        id: toString(id(n)),
-        text: n.name_zh+'-测试',
-        type: CASE 
-            WHEN 'DataMetric' IN labels(n) THEN 'metric'
-            WHEN 'DataModel' IN labels(n) THEN 'model'
-            ELSE split(labels(n)[0],'_')[1]
-        END
-    }) as nodes, paths
-    
-    // 从路径中提取关系
-    UNWIND paths as p
-    UNWIND relationships(p) as r
-    WITH nodes, collect(DISTINCT {
-        from: toString(id(startNode(r))),
-        to: toString(id(endNode(r))),
-        text: '来源'
-    }) as relations
-    
-    // 返回结果
-    RETURN nodes,
-           [rel in relations WHERE rel.from IS NOT NULL AND rel.to IS NOT NULL] as lines,
-           toString($nodeId) as rootId
-    """
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            data = session.run(cql, nodeId=nodeid)
-            
-            res = {}
-            for item in data:
-                res = {
-                    "nodes": [record for record in item['nodes'] if record['id']],
-                    "lines": [record for record in item['lines'] if record['from'] and record['to']],
-                    "rootId": str(nodeid)
-                }
-        logger.info(res)  # 记录 'res' 变量
-        return res
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return {}
-    finally:
-        if driver:
-            driver.close()
-
-
-def metric_impact_graph(nodeid, meta):
-    """
-    生成数据指标影响关系图谱
-    
-    Args:
-        nodeid: 节点ID
-        meta: 是否包含元数据
-        
-    Returns:
-        dict: 图谱数据
-    """
-    if meta:
-        # 查询语句
-        cql = """
-        MATCH(mc2:DataMetric)
-        WHERE id(mc2)=$nodeId
-        OPTIONAL MATCH(mc4:DataMetric)-[:origin]-(mc2)
-        OPTIONAL MATCH(mc2)-[:connection]-(meta:DataMeta)
-        OPTIONAL MATCH(mc2)-[:LABEL]-(la:DataLabel)
-        OPTIONAL MATCH(mc2)-[:child]-(child)
-        WITH 
-            collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
-            collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
-            collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
-            collect({id:toString(id(meta)),text:meta.name})+
-            collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
-            collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
-            collect({from:toString(id(mc2)),to:toString(id(meta)),text:'包含'})+
-            collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
-            collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
-        WITH  
-           toString(id(mc2)) as rootId,
-           apoc.coll.toSet(lines) as lines,
-           apoc.coll.toSet(nodes) as nodes
-        RETURN nodes,lines,rootId
-        """
-    else:
-        # 查询语句
-        cql = """
-            MATCH(mc2:DataMetric)
-            WHERE id(mc2)=$nodeId
-            OPTIONAL MATCH(mc4:DataMetric)-[:origin]-(mc2)
-            OPTIONAL MATCH(mc2)-[:LABEL]-(la:DataLabel)
-            OPTIONAL MATCH(mc2)-[:child]-(child)
-            WITH 
-                collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
-                collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
-                collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
-                collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
-                collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
-                collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
-                collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
-            WITH  
-               toString(id(mc2)) as rootId,
-               apoc.coll.toSet(lines) as lines,
-               apoc.coll.toSet(nodes) as nodes
-            RETURN nodes,lines,rootId
-            """
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            data = session.run(cql, nodeId=nodeid)
-            
-            res = {}
-            for item in data:
-                res = {
-                    "nodes": [record for record in item['nodes'] if record['id']],
-                    "lines": [record for record in item['lines'] if record['from'] and record['to']],
-                    "rootId": item['rootId']
-                }
-    
-        logger.info(res)  # 记录 'res' 变量
-        return res
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return {}
-    finally:
-        if driver:
-            driver.close()
-
-
-def metric_all_graph(nodeid, meta):
-    """
-    生成数据指标所有关系图谱
-    
-    Args:
-        nodeid: 节点ID
-        meta: 是否包含元数据
-        
-    Returns:
-        dict: 图谱数据
-    """
-    if meta:
-        # 查询语句
-        cql = """
-        MATCH(mc2:DataMetric)
-        WHERE id(mc2)=$nodeId
-        OPTIONAL MATCH(mc2)-[:origin]-(mo:DataModel)
-        OPTIONAL MATCH(mc2)-[:origin]-(mc1:DataMetric)
-        OPTIONAL MATCH(mc4:DataMetric)-[:origin]-(mc2)
-        OPTIONAL MATCH(mc2)-[:connection]-(meta:DataMeta)
-        OPTIONAL MATCH(mc2)-[:LABEL]-(la:DataLabel)
-        OPTIONAL MATCH(mc2)-[:child]-(child)
-        WITH 
-            collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
-            collect({id:toString(id(mo)),text:mo.name,type:split(labels(mo)[0],'_')[1]})+
-            collect({id:toString(id(mc1)),text:mc1.name,type:split(labels(mc1)[0],'_')[1]})+
-            collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
-            collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
-            collect({id:toString(id(meta)),text:meta.name})+
-            collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
-            collect({from:toString(id(mc2)),to:toString(id(mo)),text:'来源'})+
-            collect({from:toString(id(mc2)),to:toString(id(mc1)),text:'来源'})+
-            collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
-            collect({from:toString(id(mc2)),to:toString(id(meta)),text:'包含'})+
-            collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
-            collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
-        WITH  
-            toString(id(mc2)) as rootId,
-            apoc.coll.toSet(lines) as lines,
-            apoc.coll.toSet(nodes) as nodes
-        RETURN nodes,lines,rootId
-        """
-    else:
-        # 查询语句
-        cql = """
-            MATCH(mc2:DataMetric)
-            WHERE id(mc2)=$nodeId
-            OPTIONAL MATCH(mc2)-[:origin]-(mo:DataModel)
-            OPTIONAL MATCH(mc2)-[:origin]-(mc1:DataMetric)
-            OPTIONAL MATCH(mc4:DataMetric)-[:origin]-(mc2)
-            OPTIONAL MATCH(mc2)-[:LABEL]-(la:DataLabel)
-            OPTIONAL MATCH(mc2)-[:child]-(child)
-            WITH 
-                collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
-                collect({id:toString(id(mo)),text:mo.name,type:split(labels(mo)[0],'_')[1]})+
-                collect({id:toString(id(mc1)),text:mc1.name,type:split(labels(mc1)[0],'_')[1]})+
-                collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
-                collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
-                collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
-                collect({from:toString(id(mc2)),to:toString(id(mo)),text:'来源'})+
-                collect({from:toString(id(mc2)),to:toString(id(mc1)),text:'来源'})+
-                collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
-                collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
-                collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
-            WITH  
-                toString(id(mc2)) as rootId,
-                apoc.coll.toSet(lines) as lines,
-                apoc.coll.toSet(nodes) as nodes
-            RETURN nodes,lines,rootId
-            """
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            data = session.run(cql, nodeId=nodeid)
-            
-            res = {}
-            for item in data:
-                res = {
-                    "nodes": [record for record in item['nodes'] if record['id']],
-                    "lines": [record for record in item['lines'] if record['from'] and record['to']],
-                    "rootId": item['rootId']
-                }
-    
-        logger.info(res)  # 记录 'res' 变量
-        return res
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        return {}
-    finally:
-        if driver:
-            driver.close()
-
-
-def data_metric_edit(data):
-    """
-    编辑数据指标
-    
-    Args:
-        data: 数据指标数据
-    """
-    metric_id = data.get("id")
-    if not metric_id:
-        logger.error("数据指标ID不能为空")
-        raise ValueError("数据指标ID不能为空")
-    
-    # 验证节点是否存在
-    node_a = get_node_by_id('DataMetric', metric_id)
-    if not node_a:
-        logger.error(f"数据指标节点不存在: ID={metric_id}")
-        raise ValueError(f"数据指标节点不存在: ID={metric_id}")
-    
-    # 删除旧关系
-    delete_relationships(metric_id)
-
-    # 准备需要更新的属性(排除特殊字段和复杂类型)
-    excluded_keys = {'id', 'model_selected', 'childrenId', 'tag'}
-    
-    # 过滤函数:只保留 Neo4j 支持的基本类型
-    def is_valid_neo4j_property(value):
-        """检查值是否为 Neo4j 支持的属性类型"""
-        if value is None:
-            return False
-        # 基本类型:str, int, float, bool
-        if isinstance(value, (str, int, float, bool)):
-            return True
-        # 列表类型:但列表中的元素必须是基本类型
-        if isinstance(value, list):
-            # 空列表是允许的
-            if not value:
-                return True
-            # 检查列表中所有元素是否为基本类型
-            return all(isinstance(item, (str, int, float, bool)) for item in value)
-        # 其他类型(dict, object等)不支持
-        return False
-    
-    # 准备更新属性,只保留有效类型
-    update_props = {}
-    for key, value in data.items():
-        if key in excluded_keys:
-            continue
-        if not is_valid_neo4j_property(value):
-            # 如果是复杂类型,尝试转换为 JSON 字符串
-            if isinstance(value, (dict, list)):
-                try:
-                    import json
-                    update_props[key] = json.dumps(value, ensure_ascii=False)
-                    logger.info(f"属性 {key} 从复杂类型转换为JSON字符串")
-                except Exception as e:
-                    logger.warning(f"跳过无法序列化的属性 {key}: {type(value)}, 错误: {str(e)}")
-            else:
-                logger.warning(f"跳过不支持的属性类型 {key}: {type(value)}")
-        else:
-            update_props[key] = value
-    
-    driver = None
-    try:
-        driver = connect_graph()
-        with driver.session() as session:
-            # 更新节点属性
-            if update_props:
-                # 构建SET子句
-                set_clauses = []
-                for key in update_props.keys():
-                    set_clauses.append(f"n.{key} = ${key}")
-                set_clause = ", ".join(set_clauses)
-                
-                update_query = f"""
-                MATCH (n:DataMetric)
-                WHERE id(n) = $metric_id
-                SET {set_clause}
-                RETURN n
-                """
-                session.run(update_query, metric_id=metric_id, **update_props)
-                logger.info(f"成功更新数据指标节点属性: ID={metric_id}, 更新字段: {list(update_props.keys())}")
-
-            # 处理子节点关系
-            child_list = data.get('childrenId', [])
-            for child_id in child_list:
-                try:
-                    child_id_int = int(child_id)
-                    # 创建child关系
-                    child_query = """
-                    MATCH (parent:DataMetric), (child)
-                    WHERE id(parent) = $parent_id AND id(child) = $child_id
-                    MERGE (parent)-[:child]->(child)
-                    """
-                    session.run(child_query, parent_id=metric_id, child_id=child_id_int)
-                    logger.info(f"成功创建child关系: {metric_id} -> {child_id_int}")
-                except (ValueError, TypeError) as e:
-                    logger.warning(f"无效的子节点ID: {child_id}, 错误: {str(e)}")
-                    continue
-
-            # 处理数据标签关系
-            tag_id = data.get("tag")
-            if tag_id:
-                try:
-                    tag_id_int = int(tag_id)
-                    tag_query = """
-                    MATCH (metric:DataMetric), (tag:DataLabel)
-                    WHERE id(metric) = $metric_id AND id(tag) = $tag_id
-                    MERGE (metric)-[:LABEL]->(tag)
-                    """
-                    session.run(tag_query, metric_id=metric_id, tag_id=tag_id_int)
-                    logger.info(f"成功创建LABEL关系: {metric_id} -> {tag_id_int}")
-                except (ValueError, TypeError) as e:
-                    logger.warning(f"无效的标签ID: {tag_id}, 错误: {str(e)}")
-
-            # 处理元数据节点关系
-            model_selected = data.get('model_selected', [])
-            for record in model_selected:
-                meta_list = record.get("meta", [])
-                for parsed_item in meta_list:
-                    meta_id = parsed_item.get("id")
-                    if meta_id:
-                        try:
-                            meta_id_int = int(meta_id)
-                            connection_query = """
-                            MATCH (metric:DataMetric), (meta)
-                            WHERE id(metric) = $metric_id AND id(meta) = $meta_id
-                            MERGE (metric)-[:connection]->(meta)
-                            """
-                            session.run(connection_query, metric_id=metric_id, meta_id=meta_id_int)
-                            logger.info(f"成功创建connection关系: {metric_id} -> {meta_id_int}")
-                        except (ValueError, TypeError) as e:
-                            logger.warning(f"无效的元数据ID: {meta_id}, 错误: {str(e)}")
-                            continue
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"Neo4j数据库连接失败: {str(e)}")
-        raise ConnectionError("无法连接到数据库") from e
-    finally:
-        if driver:
-            driver.close()
-    
-    logger.info(f"数据指标编辑完成: ID={metric_id}")
-
-
-def create_metric_node(name, description, category, id_list):
-    """
-    创建指标节点
-    
-    Args:
-        name: 指标名称
-        description: 指标描述
-        category: 指标分类
-        id_list: 相关ID列表
-        
-    Returns:
-        tuple: (节点ID, ID列表)
-    """
-    data_metric_node = create_or_get_node('DataMetric', 
-                                        name=name,
-                                        description=description,
-                                        category=category,
-                                        id_list=id_list)
-                                        
-    if not hasattr(data_metric_node, 'id'):
-        raise ValueError("Failed to create valid metric node")
-        
-    return data_metric_node.id, id_list
-
-
-def metric_check(formula_text):
-    """
-    解析指标计算公式,提取运算变量并在Neo4j中查找匹配的元数据
-    
-    Args:
-        formula_text: 计算公式文本,格式如:指标名称 = 变量1 + 变量2 * 数字
-        
-    Returns:
-        list: JSON数组,包含每个变量的匹配结果
-              [{
-                  "variable": "变量名",
-                  "name_zh": "中文名称",
-                  "name_en": "英文名称",
-                  "id": 节点ID,
-                  "create_time": "创建时间",
-                  "findit": 1或0
-              }]
-    """
-    import re
-    
-    try:
-        # 分割等号,取右侧的计算算式
-        if '=' not in formula_text:
-            logger.error("公式格式错误:缺少等号")
-            return []
-        
-        parts = formula_text.split('=', 1)
-        if len(parts) != 2:
-            logger.error("公式格式错误:等号使用不正确")
-            return []
-        
-        formula = parts[1].strip()
-        
-        # 定义运算符模式(支持常见的数学运算符)
-        operators_pattern = r'[+\-*/()()\[\]{}]'
-        
-        # 按运算符分割,提取所有token
-        tokens = re.split(operators_pattern, formula)
-        
-        # 清理空白并过滤空字符串
-        tokens = [token.strip() for token in tokens if token.strip()]
-        
-        # 分离中文变量和数字
-        variables = []
-        for token in tokens:
-            # 检查是否包含中文字符
-            if re.search(r'[\u4e00-\u9fff]', token):
-                variables.append(token)
-        
-        # 去重
-        variables = list(set(variables))
-        
-        if not variables:
-            logger.info("公式中未找到中文变量")
-            return []
-        
-        # 在Neo4j中查找匹配的元数据
-        result = []
-        driver = None
-        try:
-            driver = connect_graph()
-            with driver.session() as session:
-                for variable in variables:
-                    # 查询元数据节点,模糊匹配name_zh字段
-                    cql = """
-                    MATCH (n:DataMeta)
-                    WHERE n.name_zh CONTAINS $variable
-                    RETURN n, id(n) as node_id
-                    LIMIT 1
-                    """
-                    
-                    query_result = session.run(cql, variable=variable)
-                    record = query_result.single()
-                    
-                    if record and record['n']:
-                        # 找到匹配的元数据
-                        node = record['n']
-                        node_data = {
-                            "variable": variable,
-                            "name_zh": node.get('name_zh', ''),
-                            "name_en": node.get('name_en', ''),
-                            "id": record['node_id'],
-                            "create_time": node.get('create_time', ''),
-                            "findit": 1
-                        }
-                    else:
-                        # 未找到匹配的元数据
-                        node_data = {
-                            "variable": variable,
-                            "name_zh": "",
-                            "name_en": "",
-                            "id": None,
-                            "create_time": "",
-                            "findit": 0
-                        }
-                    
-                    result.append(node_data)
-        except (ConnectionError, ValueError) as e:
-            logger.error(f"Neo4j数据库连接失败: {str(e)}")
-            return []
-        finally:
-            if driver:
-                driver.close()
-        
-        logger.info(f"公式检查完成,共检查{len(variables)}个变量")
-        return result
-        
-    except Exception as e:
-        logger.error(f"公式解析失败: {str(e)}")
-        return []
-
-
-def metric_delete(metric_node_id):
-    """
-    删除数据指标节点及其所有关联关系
-    
-    Args:
-        metric_node_id: 指标节点ID
-        
-    Returns:
-        dict: 删除结果,包含 success 状态和 message 信息
-    """
-    driver = None
-    try:
-        driver = connect_graph()
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"无法连接到Neo4j数据库: {str(e)}")
-        return {
-            "success": False,
-            "message": "无法连接到数据库"
-        }
-    
-    try:
-        with driver.session() as session:
-            # 首先检查节点是否存在
-            check_query = """
-            MATCH (n:DataMetric)
-            WHERE id(n) = $nodeId
-            RETURN n
-            """
-            check_result = session.run(check_query, nodeId=metric_node_id).single()
-            
-            if not check_result:
-                logger.warning(f"数据指标节点不存在: ID={metric_node_id}")
-                return {
-                    "success": False,
-                    "message": f"数据指标节点不存在 (ID: {metric_node_id})"
-                }
-            
-            # 删除节点及其所有关联关系
-            delete_query = """
-            MATCH (n:DataMetric)
-            WHERE id(n) = $nodeId
-            DETACH DELETE n
-            RETURN count(n) as deleted_count
-            """
-            delete_result = session.run(delete_query, nodeId=metric_node_id).single()
-            deleted_count = delete_result["deleted_count"]
-            
-            if deleted_count > 0:
-                logger.info(f"成功删除数据指标节点: ID={metric_node_id}")
-                return {
-                    "success": True,
-                    "message": f"成功删除数据指标节点 (ID: {metric_node_id})"
-                }
-            else:
-                logger.warning(f"删除失败,节点可能已被删除: ID={metric_node_id}")
-                return {
-                    "success": False,
-                    "message": "删除失败,节点可能已被删除"
-                }
-    except Exception as e:
-        logger.error(f"删除数据指标节点失败: {str(e)}")
-        return {
-            "success": False,
-            "message": f"删除失败: {str(e)}"
-        }
-    finally:
-        if driver:
-            driver.close()

+ 0 - 99
app/core/data_model/README.md

@@ -1,99 +0,0 @@
-# 数据模型核心业务逻辑模块
-
-本模块包含了数据模型相关的所有核心业务逻辑函数,处理数据模型的创建、查询、更新、删除以及与其他数据对象的关系管理。
-
-## 主要功能
-
-1. **数据模型基础操作**
-   - 创建数据模型节点
-   - 更新数据模型属性
-   - 删除数据模型节点
-   - 查询数据模型详情
-
-2. **数据模型关系管理**
-   - 数据模型与元数据(meta_node)的关系处理
-   - 数据模型与数据资源(DataResource)的关系处理
-   - 数据模型与其他数据模型的关系处理
-   - 数据模型与标签(DataLabel)的关系处理
-
-3. **数据模型层级管理**
-   - 计算并设置数据模型层级(level)属性
-   - 处理父子关系(child)
-
-4. **数据模型血缘分析**
-   - 数据模型血缘关系分析
-   - 数据资源血缘关系分析
-   - 生成血缘图谱数据
-
-5. **图谱生成**
-   - 生成血缘关系图谱(Kinship)
-   - 生成影响关系图谱(Impact)
-   - 生成全量关系图谱(All)
-
-## 核心函数列表
-
-- `calculate_model_level`:计算数据模型层级
-- `handle_model_relation`:处理数据模型血缘关系
-- `handle_data_model`:创建数据模型节点
-- `resource_handle_meta_data_model`:处理数据模型与数据资源的关系
-- `model_handle_meta_data_model`:处理数据模型与其他数据模型的关系
-- `handle_no_meta_data_model`:处理从DDL中选取的没有元数据的数据模型
-- `handle_id_model`:获取数据模型详情
-- `model_list`:获取数据模型列表
-- `model_resource_list`:获取有血缘关系的数据资源列表
-- `model_kinship_graph`:生成数据模型血缘图谱
-- `model_impact_graph`:生成数据模型影响图谱
-- `model_all_graph`:生成数据模型全量图谱
-- `data_model_edit`:更新数据模型
-
-## 数据模型
-
-数据模型(DataModel)节点具有以下主要属性:
-- `name`:数据模型名称(中文)
-- `en_name`:数据模型英文名称
-- `category`:数据模型分类
-- `description`:数据模型描述
-- `time`:创建/更新时间
-- `level`:数据模型层级
-- `id_list`:相关ID列表(JSON序列化字符串)
-
-## 关系类型
-
-数据模型相关的主要关系类型:
-- `component`:数据模型与元数据的组成关系
-- `resource`:数据模型与数据资源的关联关系
-- `use`:数据模型之间的使用关系
-- `child`:数据模型的父子关系
-- `label`:数据模型与标签的分类关系
-
-## 依赖关系
-
-- 依赖 `app.core.graph.graph_operations` 提供图数据库节点操作功能
-- 依赖 `app.routes.graph_routes` 提供图数据库连接
-- 依赖 `app.services.neo4j_driver` 提供Neo4j会话管理
-- 依赖 `app.core.meta_data` 提供元数据处理功能
-- 依赖 `app.utils.common_functions` 提供通用功能函数
-- 依赖 `app.core.data_resource` 提供数据资源处理功能
-
-## 调用示例
-
-```python
-# 创建数据模型示例
-from app.core.data_model.model import handle_data_model, calculate_model_level
-
-# 创建数据模型
-model_name = "客户信息模型"
-result_list = ["customer_info_model"]
-result = json.dumps([{"resource_id": 123, "metaData": [{"id": 456}]}])
-receiver = {
-    "category": "业务模型",
-    "description": "包含客户基本信息的数据模型",
-    "tag": 789,
-    "childrenId": []
-}
-
-model_id, model_node = handle_data_model(model_name, result_list, result, receiver)
-
-# 计算模型层级
-calculate_model_level(model_id)
-``` 

+ 0 - 2
app/core/data_model/__init__.py

@@ -1,2 +0,0 @@
-# 数据模型业务逻辑模块 
-from app.core.data_model import model 

+ 0 - 1887
app/core/data_model/model.py

@@ -1,1887 +0,0 @@
-"""
-数据模型核心业务逻辑模块
-
-本模块包含了数据模型相关的所有核心业务逻辑函数,包括:
-- 数据模型的创建、更新、删除
-- 数据模型与数据资源、元数据之间的关系处理
-- 数据模型血缘关系管理
-- 数据模型图谱生成
-- 数据模型层级计算等功能
-"""
-
-import math
-import threading
-from concurrent.futures import ThreadPoolExecutor
-import pandas as pd
-from py2neo import Relationship
-import logging
-import json
-
-# Configure logger
-logger = logging.getLogger(__name__)
-
-from app.core.graph.graph_operations import relationship_exists
-from app.core.graph.graph_operations import connect_graph,create_or_get_node,get_node
-from app.services.neo4j_driver import neo4j_driver
-from app.core.meta_data import get_formatted_time, handle_id_unstructured
-from app.core.common import delete_relationships, update_or_create_node, get_node_by_id_no_label
-from app.core.data_resource.resource import get_node_by_id, serialize_node_properties
-
-
-# 根据child关系计算数据模型当前的level自动保存
-def calculate_model_level(id):
-    """
-    根据child关系计算数据模型当前的level并自动保存
-    
-    Args:
-        id: 数据模型的节点ID(整数)
-        
-    Returns:
-        None
-    """
-    # 确保id是整数类型
-    node_id = int(id) if id is not None else None
-    
-    cql = """
-    MATCH (start_node:DataModel)
-    WHERE id(start_node) = $nodeId
-    CALL {
-        WITH start_node
-        OPTIONAL MATCH path = (start_node)-[:child*]->(end_node)
-        RETURN length(path) AS level
-    }
-    WITH coalesce(max(level), 0) AS max_level
-    RETURN max_level
-    """
-    
-    with connect_graph().session() as session:
-        result = session.run(cql, nodeId=node_id)
-        record = result.single()
-        data = record["max_level"] if record and "max_level" in record else 0
-        
-    # 更新level属性
-    update_query = """
-    MATCH (n:DataModel)
-    WHERE id(n) = $nodeId
-    SET n.level = $level
-    RETURN n
-    """
-    
-    with connect_graph().session() as session:
-        session.run(update_query, nodeId=node_id, level=data)
-
-
-# 处理数据模型血缘关系
-def handle_model_relation(resource_ids):
-    """
-    处理数据模型血缘关系
-    
-    Args:
-        resource_ids: 数据资源ID
-        
-    Returns:
-        血缘关系数据
-    """
-    query = """
-            MATCH (search:DataResource)-[:connection]->(common_node:meta_node)<-[:connection]-(connect:DataResource)
-            WHERE id(search) = $resource_Ids
-            WITH search, connect, common_node
-            MATCH (search)-[:connection]->(search_node:meta_node)
-            WITH search, connect, common_node, collect(DISTINCT id(search_node)) AS search_nodes
-            MATCH (connect)-[:connection]->(connect_node:meta_node)
-            WITH search, connect, common_node, search_nodes, collect(DISTINCT id(connect_node)) AS connect_nodes
-            WITH search, connect, search_nodes, connect_nodes, collect(DISTINCT id(common_node)) AS common_nodes
-
-            // 剔除 search_nodes 和 connect_nodes 中包含在 common_nodes 中的内容
-            WITH search, connect, common_nodes,
-                 [node IN search_nodes WHERE NOT node IN common_nodes] AS filtered_search_nodes,
-                 [node IN connect_nodes WHERE NOT node IN common_nodes] AS filtered_connect_nodes
-
-            RETURN  id(connect) as blood_resources, common_nodes, 
-            filtered_search_nodes as origin_nodes, filtered_connect_nodes as blood_nodes
-            """
-
-    with connect_graph().session() as session:
-        result = session.run(query, resource_Ids=resource_ids)
-    return result.data()
-
-
-# 创建一个数据模型节点
-def handle_data_model(data_model, result_list, result, receiver):
-    """
-    创建一个数据模型节点
-    
-    Args:
-        data_model: 数据模型名称
-        result_list: 数据模型英文名列表
-        result: 序列化的ID列表
-        receiver: 接收到的请求参数
-        
-    Returns:
-        tuple: (id, data_model_node)
-    """
-    try:
-        # 添加数据资源 血缘关系的字段 blood_resource
-        data_model_en = result_list[0] if result_list and len(result_list) > 0 else ""
-        receiver['id_list'] = result
-        add_attribute = {
-            'create_time': get_formatted_time(),
-            'name_en': data_model_en
-        }
-        receiver.update(add_attribute)
-        data_model_node = get_node('DataModel', name_zh=data_model) or create_or_get_node('DataModel', **receiver)
-
-        logger.info(f"通过查询或创建节点获得节点ID111,data_model_node: {data_model_node}")
-        # 获取节点ID,确保我们能安全地访问节点ID
-        node_id = data_model_node
-        if hasattr(data_model_node, 'id'):
-            logger.info(f"通过节点ID获取节点ID222,data_model_node: {data_model_node}")
-            node_id = data_model_node.id
-        else:
-            logger.info(f"通过查询节点名称获取节点ID333,data_model_node: {data_model_node}")
-            # 如果节点没有id属性,尝试通过查询获取
-            query = """
-            MATCH (n:DataModel {name: $name})
-            RETURN id(n) as node_id
-            """
-            with connect_graph().session() as session:
-                result = session.run(query, name=data_model)
-                record = result.single()
-                logger.info(f"通过查询节点名称获取节点ID444,record: {record}")
-                if record and "node_id" in record:
-                    logger.info(f"通过查询节点名称获取节点ID555,record: {record}")
-                    node_id = record["node_id"]
-        
-        # 安全地处理子节点关系
-        child_list = receiver.get('childrenId', [])
-        for child_id in child_list:
-            child_node = get_node_by_id_no_label(child_id)
-            if child_node:
-                # 直接使用Cypher查询检查关系是否存在
-                with connect_graph().session() as session:
-                    rel_query = """
-                    MATCH (a)-[r:child]->(b)
-                    WHERE id(a) = $start_id AND id(b) = $end_id
-                    RETURN count(r) > 0 as exists
-                    """
-                    rel_result = session.run(rel_query, 
-                                            start_id=int(node_id), 
-                                            end_id=int(child_node.id)).single()
-                    
-                    # 如果关系不存在,则创建关系
-                    if not (rel_result and rel_result["exists"]):
-                        child_node_id = child_node.id if child_node else None
-                        if child_node_id is not None:
-                            # 将变量转换为确定的int类型以避免类型检查问题
-                            child_id_int = int(child_node_id)
-                            session.execute_write(
-                                lambda tx: tx.run(
-                                    "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:child]->(b)",
-                                    a_id=int(node_id), b_id=child_id_int
-                                )
-                            )
-
-        # 根据传入参数id,和数据标签建立关系
-        if receiver.get('tag'):
-            tag = get_node_by_id('DataLabel', receiver['tag'])
-            if tag:
-                # 直接使用Cypher查询检查关系是否存在
-                with connect_graph().session() as session:
-                    rel_query = """
-                    MATCH (a)-[r:LABEL]->(b)
-                    WHERE id(a) = $start_id AND id(b) = $end_id
-                    RETURN count(r) > 0 as exists
-                    """
-                    rel_result = session.run(rel_query, 
-                                            start_id=int(node_id), 
-                                            end_id=int(tag.id)).single()
-                    
-                    # 如果关系不存在,则创建关系
-                    if not (rel_result and rel_result["exists"]):
-                        session.execute_write(
-                            lambda tx: tx.run(
-                                "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:LABEL]->(b)",
-                                a_id=int(node_id), b_id=int(tag.id)
-                            )
-                        )
-
-        # 处理数据源关系 - 创建COME_FROM关系
-        data_source = receiver.get('data_source')
-        if data_source:
-            try:
-                # 获取数据源的标识(支持多种格式)
-                data_source_id = None
-                data_source_name_en = None
-                
-                # 1. 如果是数字(节点ID)
-                if isinstance(data_source, (int, float)) or (isinstance(data_source, str) and data_source.isdigit()):
-                    data_source_id = int(data_source)
-                    logger.info(f"data_source 为节点ID: {data_source_id}")
-                # 2. 如果是字典且包含name_en
-                elif isinstance(data_source, dict) and data_source.get('name_en'):
-                    data_source_name_en = data_source['name_en']
-                    logger.info(f"data_source 为字典,提取name_en: {data_source_name_en}")
-                # 3. 如果是字符串(name_en)
-                elif isinstance(data_source, str):
-                    data_source_name_en = data_source
-                    logger.info(f"data_source 为字符串name_en: {data_source_name_en}")
-                
-                # 创建数据模型与数据源的关系
-                with connect_graph().session() as session:
-                    if data_source_id is not None:
-                        # 使用节点ID创建关系
-                        # 首先检查数据源节点是否存在
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE id(b) = $ds_id RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, ds_id=data_source_id)
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: ID={data_source_id},跳过关系创建")
-                        else:
-                            # 检查关系是否已存在
-                            rel_check_query = """
-                            MATCH (a:DataModel)-[r:COME_FROM]->(b:DataSource)
-                            WHERE id(a) = $model_id AND id(b) = $ds_id
-                            RETURN count(r) > 0 as exists
-                            """
-                            rel_check_result = session.run(rel_check_query,
-                                                          model_id=int(node_id),
-                                                          ds_id=data_source_id).single()
-                            
-                            # 如果关系不存在,则创建COME_FROM关系
-                            if not (rel_check_result and rel_check_result["exists"]):
-                                create_rel_cypher = """
-                                MATCH (a:DataModel), (b:DataSource)
-                                WHERE id(a) = $model_id AND id(b) = $ds_id
-                                CREATE (a)-[r:COME_FROM]->(b)
-                                RETURN r
-                                """
-                                session.run(create_rel_cypher,
-                                          model_id=int(node_id),
-                                          ds_id=data_source_id)
-                                logger.info(f"已创建数据模型与数据源的COME_FROM关系: model_id={node_id} -> data_source_id={data_source_id}")
-                            else:
-                                logger.info(f"数据模型与数据源的COME_FROM关系已存在: model_id={node_id} -> data_source_id={data_source_id}")
-                                
-                    elif data_source_name_en:
-                        # 使用name_en创建关系(兼容旧方式)
-                        # 首先检查数据源节点是否存在
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE b.name_en = $ds_name_en RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, ds_name_en=data_source_name_en)
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: name_en={data_source_name_en},跳过关系创建")
-                        else:
-                            # 检查关系是否已存在
-                            rel_check_query = """
-                            MATCH (a:DataModel)-[r:COME_FROM]->(b:DataSource)
-                            WHERE id(a) = $model_id AND b.name_en = $ds_name_en
-                            RETURN count(r) > 0 as exists
-                            """
-                            rel_check_result = session.run(rel_check_query,
-                                                          model_id=int(node_id),
-                                                          ds_name_en=data_source_name_en).single()
-                            
-                            # 如果关系不存在,则创建COME_FROM关系
-                            if not (rel_check_result and rel_check_result["exists"]):
-                                create_rel_cypher = """
-                                MATCH (a:DataModel), (b:DataSource)
-                                WHERE id(a) = $model_id AND b.name_en = $ds_name_en
-                                CREATE (a)-[r:COME_FROM]->(b)
-                                RETURN r
-                                """
-                                session.run(create_rel_cypher,
-                                          model_id=int(node_id),
-                                          ds_name_en=data_source_name_en)
-                                logger.info(f"已创建数据模型与数据源的COME_FROM关系: model_id={node_id} -> name_en={data_source_name_en}")
-                            else:
-                                logger.info(f"数据模型与数据源的COME_FROM关系已存在: model_id={node_id} -> name_en={data_source_name_en}")
-                    else:
-                        logger.warning(f"data_source参数无效,无法识别格式: {data_source}")
-                    
-            except Exception as e:
-                # 数据源关系创建失败不应该中断主流程
-                logger.error(f"处理数据源关系失败(不中断主流程): {str(e)}")
-                # 不再抛出异常,允许主流程继续
-
-        return node_id, data_model_node
-    except Exception as e:
-        logging.error(f"Error in handle_data_model: {str(e)}")
-        raise
-
-
-# (从数据资源中选取)
-def resource_handle_meta_data_model(id_lists, data_model_node_id):
-    """
-    处理从数据资源中选取的数据模型与元数据的关系
-    
-    Args:
-        id_lists: ID列表
-        data_model_node_id: 数据模型节点ID
-        
-    Returns:
-        None
-    """
-    try:
-        logger.info(f"开始处理数据模型与元数据的关系,数据模型ID: {data_model_node_id}")
-        
-        # 构建meta_id和resouce_id的列表
-        resouce_ids = [record['resource_id'] for record in id_lists]
-        meta_ids = [record['id'] for id_list in id_lists for record in id_list['metaData']]
-        
-        logger.info(f"资源ID列表: {resouce_ids}")
-        logger.info(f"元数据ID列表: {meta_ids}")
-        
-        # 创建与meta_node的关系 组成关系
-        if meta_ids:
-            logger.info("开始创建数据模型与元数据的关系")
-            query = """
-            MATCH (source:DataModel), (target:DataMeta)
-            WHERE id(source)=$source_id AND id(target) IN $target_ids
-            MERGE (source)-[:INCLUDES]->(target)
-            RETURN count(*) as count
-            """
-            with connect_graph().session() as session:
-                result = session.run(query, source_id=data_model_node_id, target_ids=meta_ids)
-                result_record = result.single()
-                count = result_record["count"] if result_record else 0
-                logger.info(f"成功创建 {count} 个数据模型与元数据的关系")
-
-        # 创建与DataResource的关系 资源关系
-        # 不在创建Modle时创建资源关系,将资源关系创建放在数据流程创建时处理
-        # 关系名称为DERIVED_FROM
-        # commented by mxl 2025-06-27
-        # 
-        # if resouce_ids:
-        #     logger.info("开始创建数据模型与数据资源的关系")
-        #     query = """
-        #     MATCH (source:DataModel), (target:DataResource)
-        #     WHERE id(source)=$source_id AND id(target) IN $target_ids
-        #     MERGE (source)-[:DERIVED_FROM]->(target)
-        #     RETURN count(*) as count
-        #     """
-        #     with connect_graph().session() as session:
-        #         result = session.run(query, source_id=data_model_node_id, target_ids=resouce_ids)
-        #         count = result.single()["count"]
-        #         logger.info(f"成功创建 {count} 个数据模型与数据资源的关系")
-                
-    except Exception as e:
-        logger.error(f"处理数据模型与元数据的关系时发生错误: {str(e)}")
-        raise
-
-
-# (从数据模型中选取)
-def model_handle_meta_data_model(id_lists, data_model_node_id):
-    """
-    处理从数据模型中选取的数据模型与元数据的关系
-    
-    Args:
-        id_lists: ID列表
-        data_model_node_id: 数据模型节点ID
-        
-    Returns:
-        None
-    """
-    # 构建meta_id和model_id的列表
-    model_ids = [record['model_id'] for record in id_lists]
-    meta_ids = [record['id'] for id_list in id_lists for record in id_list['metaData']]
-    
-    # 创建与meta_node的关系 组成关系
-    if meta_ids:
-        query = """
-        MATCH (source:DataModel), (target:DataMeta)
-        WHERE id(source)=$source_id AND id(target) IN $target_ids
-        MERGE (source)-[:component]->(target)
-        """
-        with neo4j_driver.get_session() as session:
-            session.run(query, source_id=data_model_node_id, target_ids=meta_ids)
-
-    # 创建与data_model的关系 模型关系
-    if model_ids:
-        query = """
-        MATCH (source:DataModel), (target:DataModel)
-        WHERE id(source)=$source_id AND id(target) IN $target_ids
-        MERGE (source)-[:use]->(target)
-        """
-        with neo4j_driver.get_session() as session:
-            session.run(query, source_id=data_model_node_id, target_ids=model_ids)
-
-
-# (从DDL中选取)
-def handle_no_meta_data_model(id_lists, receiver, data_model_node):
-    """
-    处理从DDL中选取的没有元数据的数据模型
-    
-    Args:
-        id_lists: ID列表(可以为空)
-        receiver: 接收到的请求参数
-        data_model_node: 数据模型节点
-        
-    Returns:
-        None
-    """
-    # DDL新增时,id_lists可能为空,提前返回
-    if not id_lists:
-        logger.info("id_lists为空,跳过资源关系处理")
-        return
-    
-    # 构建meta_id和resouce_id的列表
-    resouce_ids = [record['resource_id'] for record in id_lists if 'resource_id' in record]
-    meta_ids = [record['id'] for id_list in id_lists for record in id_list.get('metaData', []) if 'id' in record]
-    
-    # 获取数据模型节点ID
-    data_model_node_id = None
-    if hasattr(data_model_node, 'id'):
-        # data_model_node 是节点对象
-        data_model_node_id = data_model_node.id
-    elif isinstance(data_model_node, int):
-        # data_model_node 直接就是整数ID
-        data_model_node_id = data_model_node
-    elif isinstance(data_model_node, dict):
-        # data_model_node 是字典,尝试通过name_zh查询
-        query = """
-        MATCH (n:DataModel {name_zh: $name_zh})
-        RETURN id(n) as node_id
-        """
-        with connect_graph().session() as session:
-            result = session.run(query, name_zh=data_model_node.get('name_zh'))
-            record = result.single()
-            if record:
-                data_model_node_id = record["node_id"]
-    else:
-        # 未知类型,记录警告
-        logger.warning(f"data_model_node类型未知: {type(data_model_node)}, 值: {data_model_node}")
-    
-    if not data_model_node_id:
-        return
-    
-    # 创建与DataResource的关系 资源关系
-    if resouce_ids:
-        query = """
-        MATCH (source:DataModel), (target:DataResource)
-        WHERE id(source)=$source_id AND id(target) IN $target_ids
-        MERGE (source)-[:resource]->(target)
-        """
-        with connect_graph().session() as session:
-            session.run(query, source_id=data_model_node_id, target_ids=resouce_ids)
-
-    if meta_ids:
-        meta_node_list = []
-        for id in meta_ids:
-            query = """
-            MATCH (n)
-            WHERE id(n) = $node_id
-            RETURN n
-            """
-            with connect_graph().session() as session:
-                result = session.run(query, node_id=id)
-                # 必须在 session 作用域内处理结果
-                if result:
-                    record = result.data()
-                    if record:
-                        meta_node_list.append(record[0]['n'])
-        
-        # 提取接收到的数据并创建meta_node节点
-        meta_node = None
-        resource_ids = []
-        
-        for item in id_lists:
-            resource_id = item.get('resource_id')
-            if resource_id:
-                resource_ids.append(resource_id)
-            
-            for meta_item in item.get('metaData', []):
-                meta_id = meta_item['id']
-                data_standard = meta_item.get('data_standard', '')
-                name_en = meta_item.get('name_en', '')
-                name_zh = meta_item.get('name_zh', '')
-                
-                # 使用传递的参数创建meta_node节点
-                meta_params = {
-                    'name_zh': name_zh,
-                    'name_en': name_en,
-                    'standard': data_standard,
-                    'create_time': get_formatted_time()
-                }
-                
-                # 创建meta_node节点
-                meta_node = create_or_get_node('DataMeta', **meta_params)
-                
-                # 获取数据模型节点ID
-                dm_id = data_model_node_id if data_model_node_id is not None else data_model_node
-                # 确保dm_id是整数类型
-                if isinstance(dm_id, int):
-                    dm_id_int = dm_id
-                elif isinstance(dm_id, dict):
-                    dict_dm_id = dm_id.get('id')
-                    dm_id_int = int(dict_dm_id) if dict_dm_id is not None else None
-                elif hasattr(dm_id, 'id'):
-                    dm_id_int = int(dm_id.id)
-                else:
-                    try:
-                        dm_id_int = int(dm_id)
-                    except (ValueError, TypeError):
-                        dm_id_int = None
-                
-                if meta_node and dm_id_int is not None:
-                    # 确保meta_node_id是整数类型
-                    if isinstance(meta_node, int):
-                        meta_node_id_int = meta_node
-                    elif isinstance(meta_node, dict):
-                        dict_id = meta_node.get('id')
-                        meta_node_id_int = int(dict_id) if dict_id is not None else None
-                    elif hasattr(meta_node, 'id'):
-                        meta_node_id_int = int(meta_node.id)
-                    else:
-                        try:
-                            meta_node_id_int = int(meta_node)
-                        except (ValueError, TypeError):
-                            meta_node_id_int = None
-                    
-                    if meta_node_id_int is not None:
-                        # 直接使用Cypher查询检查关系是否存在
-                        with connect_graph().session() as session:
-                            rel_query = """
-                            MATCH (a)-[r:INCLUDES]->(b)
-                            WHERE id(a) = $start_id AND id(b) = $end_id
-                            RETURN count(r) > 0 as exists
-                            """
-                            rel_result = session.run(rel_query, 
-                                                   start_id=dm_id_int, 
-                                                   end_id=meta_node_id_int).single()
-                            
-                            # 如果关系不存在,则创建INCLUDES关系
-                            if not (rel_result and rel_result["exists"]):
-                                session.execute_write(
-                                    lambda tx: tx.run(
-                                        "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:INCLUDES]->(b)",
-                                        a_id=dm_id_int, b_id=meta_node_id_int
-                                    )
-                                )
-
-
-# 数据模型-详情接口
-def handle_id_model(id):
-    """
-    获取数据模型详情
-    
-    Args:
-        id: 数据模型的节点ID
-        
-    Returns:
-        dict: 包含数据模型详情的字典,格式为:
-        {"data_model": {
-            "resource_selected": [...],
-            "leader": ...,
-            "origin": ...,
-            "frequency": ...,
-            "childrenId": [...],
-            "organization": ...,
-            "name_zh": ...,
-            "name_en": ...,
-            "data_sensitivity": ...,
-            "describe": ...,
-            "tag": ...,
-            "create_time": ...,
-            "category": ...,
-            "status": ...
-        }}
-    """
-    node_id = id
-    cql = """
-        MATCH (n:DataModel) WHERE id(n) = $nodeId
-        OPTIONAL MATCH (n)-[:INCLUDES]->(meta:DataMeta)  
-        OPTIONAL MATCH (n)-[:DERIVED_FROM]->(resource:DataResource)
-        OPTIONAL MATCH (n)-[:LABEL]->(tag:DataLabel)
-        OPTIONAL MATCH (uses:model_use)-[:use]->(n)
-        OPTIONAL MATCH (n)-[:has_component]->(component)
-        WITH n, 
-            collect(DISTINCT meta) as meta_nodes,
-            collect(DISTINCT resource) as resources,
-            collect(DISTINCT component) as components,
-            collect(DISTINCT uses) as uses,
-            collect(DISTINCT tag) as tags,
-            CASE WHEN n.childrenId IS NOT NULL THEN n.childrenId ELSE [] END as children
-        RETURN {
-            // 基本信息
-            id: id(n),
-            name_zh: n.name_zh,
-            name_en: n.name_en,
-            create_time: n.create_time,
-            describe: n.describe,  
-            category: n.category,
-            level: n.level,
-            tag: CASE WHEN size(tags) > 0 AND tags[0] IS NOT NULL THEN {id: id(tags[0]), name: tags[0].name} ELSE null END,
-            // 添加其他必需字段
-            leader: n.leader,
-            origin: n.origin,
-            blood_resource: n.blood_resource,
-            frequency: n.frequency,
-            organization: n.organization,
-            data_sensitivity: n.data_sensitivity,
-            status: n.status,
-            // 子节点列表
-            childrenId: children
-        } AS result,
-        // 资源列表
-        [{
-            data_resource: [resource IN resources WHERE resource IS NOT NULL | {
-                id: id(resource),
-                name_zh: resource.name_zh,
-                name_en: resource.name_en,
-                description: resource.description
-            }],
-            resource_id: [resource IN resources WHERE resource IS NOT NULL | id(resource)],
-            meta_ids: [meta IN meta_nodes WHERE meta IS NOT NULL | {
-                id: id(meta),
-                name_zh: meta.name_zh,
-                name_en: meta.name_en,
-                data_type: meta.data_type
-            }]
-        }] AS resource_selected
-        """
-        
-    with connect_graph().session() as session:
-        result = session.run(cql, nodeId=node_id)
-        
-        # 处理查询结果
-        record = result.single()
-        logging.info(f"获得查询结果---------->>>{record}")
-        
-        if record:
-            # 获取基本属性和资源选择列表
-            properties = record["result"]
-            resource_selected = record["resource_selected"]
-            
-            # 确保所有必需字段都有默认值,避免空值
-            required_fields = ['tag', 'leader', 'origin', 'blood_resource', 
-                              'frequency', 'describe', 'organization', 'name_zh', 'name_en', 
-                              'data_sensitivity', 'create_time', 'category', 'status', 'childrenId']
-            
-            for field in required_fields:
-                if field not in properties or properties[field] is None:
-                    if field == 'tag':
-                        properties[field] = {}
-                    elif field == 'childrenId':
-                        properties[field] = []
-                    else:
-                        properties[field] = ""
-            
-            # 构建最终返回格式
-            final_data = {
-                "resource_selected": resource_selected,
-                **properties
-            }
-            
-            return {"data_model": final_data}
-        else:
-            # 如果没有查询到结果,返回空的结构
-            return {"data_model": {
-                "resource_selected": [{"meta_ids": [], "data_resource": None, "resource_id": None}],
-                "leader": None, "origin": None, "frequency": None, "childrenId": [],
-                "organization": None, "name_zh": None, "name_en": None, "data_sensitivity": None,
-                "describe": None, "tag": {}, "create_time": None, "category": None, "status": None
-            }}
-
-
-# 数据模型列表
-def model_list(skip_count, page_size, name_en_filter=None, name_zh_filter=None, 
-               category=None, tag=None, level=None):
-    """
-    获取数据模型列表
-    
-    Args:
-        skip_count: 跳过的数量
-        page_size: 页面大小
-        name_en_filter: 英文名称过滤条件
-        name_zh_filter: 名称过滤条件
-        category: 类别过滤条件
-        tag: 标签过滤条件
-        level: 层级过滤条件
-        
-    Returns:
-        tuple: (数据模型列表, 总数量)
-    """
-    try:
-        # 构建where子句 - 只针对DataModel节点的过滤条件
-        datamodel_where_clause = []
-        params = {}
-
-        if name_zh_filter is not None:
-            datamodel_where_clause.append("n.name_zh =~ $name_zh")
-            params['name_zh'] = f".*{name_zh_filter}.*"
-        
-        if name_en_filter is not None:
-            datamodel_where_clause.append("n.name_en =~ $name_en")
-            params['name_en'] = f".*{name_en_filter}.*"
-
-        if category is not None:
-            datamodel_where_clause.append("n.category = $category")
-            params['category'] = category
-    
-        if level is not None:
-            datamodel_where_clause.append("n.level = $level")
-            params['level'] = level
-
-        # 处理标签查询
-        if tag is not None:
-            # 确保tag参数是整数类型
-            try:
-                tag_id = int(tag)
-                params['tag'] = tag_id
-            except (ValueError, TypeError):
-                logger.warning(f"Invalid tag parameter: {tag}, expected integer")
-                return [], 0
-                
-            # 有标签查询条件时,需要确保标签关系存在
-            match_clause = "MATCH (n:DataModel)-[:LABEL]->(t)"
-            datamodel_where_clause.append("id(t) = $tag")
-        else:
-            # 没有标签查询条件时,先匹配DataModel,然后可选连接标签
-            match_clause = "MATCH (n:DataModel)"
-
-        # 构建DataModel节点的WHERE子句
-        datamodel_where_str = " AND ".join(datamodel_where_clause)
-        if datamodel_where_str:
-            datamodel_where_str = f"WHERE {datamodel_where_str}"
-    
-        # 构建查询
-        with connect_graph().session() as session:
-            # 计算总数量
-            if tag is not None:
-                # 有标签查询时,直接使用标签连接
-                count_query = f"""
-                {match_clause}
-                {datamodel_where_str}
-                RETURN COUNT(DISTINCT n) AS count
-                """
-            else:
-                # 无标签查询时,只计算DataModel节点
-                count_query = f"""
-                MATCH (n:DataModel)
-                {datamodel_where_str}
-                RETURN COUNT(n) AS count
-                """
-            
-            logger.debug(f"Count query: {count_query}")
-            logger.debug(f"Query parameters: {params}")
-            
-            count_result = session.run(count_query, **params)
-            count_record = count_result.single()
-            total = count_record['count'] if count_record else 0
-
-            # 查询数据 - 修复OPTIONAL MATCH的笛卡尔积问题
-            if tag is not None:
-                # 有标签查询时,直接使用标签连接
-                query = f"""
-                {match_clause}
-                {datamodel_where_str}
-                RETURN DISTINCT 
-                id(n) as id, 
-                n.name_zh as name_zh, 
-                n.name_en as name_en, 
-                n.create_time as create_time,
-                n.describe as describe,
-                n.level as level,
-                n.category as category,
-                n.status as status,
-                n.leader as leader,
-                n.origin as origin,
-                n.blood_resource as blood_resource,
-                n.organization as organization,
-                id(t) as tag_id,
-                t.name_zh as tag_name
-                ORDER BY time DESC
-                SKIP $skip
-                LIMIT $limit
-                """
-            else:
-                # 无标签查询时,先过滤DataModel节点,然后可选连接标签
-                query = f"""
-                MATCH (n:DataModel)
-                {datamodel_where_str}
-                WITH n
-                OPTIONAL MATCH (n)-[:LABEL]->(t)
-                RETURN 
-                id(n) as id, 
-                n.name_zh as name_zh, 
-                n.name_en as name_en, 
-                n.create_time as create_time,
-                n.describe as describe,
-                n.level as level,
-                n.category as category,
-                n.status as status,
-                n.leader as leader,
-                n.origin as origin,
-                n.blood_resource as blood_resource,
-                n.organization as organization,
-                id(t) as tag_id,
-                t.name_zh as tag_name
-                ORDER BY n.create_time DESC
-                SKIP $skip
-                LIMIT $limit
-                """
-            
-            logger.debug(f"Main query: {query}")
-        
-            result = session.run(query, skip=skip_count, limit=page_size, **params)
-            
-            # 处理结果
-            data = []
-            for record in result:
-                item = {
-                    "id": record['id'],
-                    "name_zh": record['name_zh'],
-                    "name_en": record['name_en'],
-                    "create_time": record['create_time'],
-                    "describe": record['describe'],
-                    "category": record['category'],
-                    "status": record['status'],
-                    "leader": record['leader'],
-                    "origin": record['origin'],
-                    "blood_resource": record['blood_resource'],
-                    "organization": record['organization'],
-                    "level": record['level'],
-                    "tag": {"id": record['tag_id'], "name_zh": record['tag_name']} if record['tag_id'] is not None else None
-                }
-                data.append(item)
-            
-            logger.info(f"Query returned {len(data)} items out of {total} total")
-            return data, total
-            
-    except Exception as e:
-        logger.error(f"Error in model_list: {str(e)}")
-        import traceback
-        traceback.print_exc()
-        return [], 0
-
-
-# 有血缘关系的数据资源列表
-def model_resource_list(skip_count, page_size, name_zh_filter=None, id=None, 
-                        category=None, create_time=None):
-    """
-    获取数据模型相关的数据资源列表
-    
-    Args:
-        skip_count: 跳过的数量
-        page_size: 页面大小
-        name_zh_filter: 名称过滤条件
-        id: 数据模型ID
-        category: 类别过滤条件
-        create_time: 时间过滤条件
-        
-    Returns:
-        tuple: (数据资源列表, 总数量)
-    """
-    try:
-        # 构建基础查询
-        base_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
-        MATCH (n)-[:children]->(m:DataResource)
-        """
-        
-        # 计算总数量
-        count_query = base_query + """
-        RETURN COUNT(m) as count
-        """
-        
-        with connect_graph().session() as session:
-            # 执行计数查询
-            count_result = session.run(count_query, nodeId=id)
-            count_record = count_result.single()
-            total = count_record['count'] if count_record else 0
-            
-            # 使用分页和筛选条件构建主查询
-            main_query = base_query + """
-            MATCH (m)-[:LABEL]->(l)
-            WHERE id(n) = $nodeId and labels(m) <> ['DataMeta']
-            RETURN m.name_zh as name_zh, 
-                   m.name_en as name_en,
-                   id(m) as id,
-                   l.name_zh as label,
-                   m.create_time as create_time,
-                   m.description as description,
-                   m.category as category 
-            ORDER BY m.create_time DESC
-            SKIP $skip LIMIT $limit
-            """
-            
-            # 执行主查询
-            result = session.run(main_query, nodeId=id, skip=skip_count, limit=page_size)
-            
-            # 处理结果
-            data = []
-            for record in result:
-                item = {
-                    "name_zh": record['name_zh'],
-                    "name_en": record['name_en'],
-                    "id": record['id'],
-                    "label": record['label'],
-                    "create_time": record['create_time'],
-                    "description": record['description'],
-                    "category": record['category']
-                }
-                data.append(item)
-            
-            return data, total
-    except Exception as e:
-        print(f"Error in model_resource_list: {str(e)}")
-        import traceback
-        traceback.print_exc()
-        return [], 0
-
-
-# 数据模型血缘图谱
-def model_kinship_graph(nodeid, meta=False):
-    """
-    生成数据模型的血缘关系图谱
-    按照DERIVED_FROM关系进行递归查找,从当前节点作为起点查找所有DERIVED_FROM关系指向的节点
-    
-    Args:
-        nodeid: 节点ID
-        meta: 是否包含元数据
-        
-    Returns:
-        dict: 包含节点和连线信息的图谱数据
-    """
-    try:
-        with connect_graph().session() as session:
-            # 确保nodeid为整数
-            try:
-                nodeid_int = int(nodeid)
-            except (ValueError, TypeError):
-                logger.error(f"节点ID不是有效的整数: {nodeid}")
-                return {"nodes": [], "lines": []}
-            
-            # 查询起始模型节点是否存在
-            start_node_query = """
-            MATCH (n:DataModel)
-            WHERE id(n) = $nodeId
-            RETURN n
-            """
-            
-            start_result = session.run(start_node_query, nodeId=nodeid_int)
-            start_record = start_result.single()
-            
-            if not start_record:
-                logger.error(f"未找到ID为{nodeid_int}的DataModel节点")
-                return {"nodes": [], "lines": []}
-            
-            # 递归查找DERIVED_FROM关系
-            cypher = """
-            MATCH (start:DataModel)
-            WHERE id(start) = $nodeId
-            MATCH path = (start)-[:DERIVED_FROM*0..]->(target)
-            WHERE target:DataResource OR target:DataModel
-            RETURN path
-            """
-                
-            result = session.run(cypher, nodeId=nodeid_int)
-            
-            # 收集节点和关系
-            nodes = {}
-            lines = {}
-            
-            for record in result:
-                # 处理路径
-                path = record['path']
-                logger.debug(f"处理路径,长度: {len(path)}, 节点数: {len(path.nodes)}, 关系数: {len(path.relationships)}")
-                
-                # 处理路径中的所有节点
-                for node in path.nodes:
-                    node_id = int(node.id)  # 直接转换为整数
-                    if node_id not in nodes:
-                        node_dict = serialize_node_properties(node)
-                        node_dict["id"] = str(node_id)
-                        node_dict["node_type"] = list(node.labels)[0] if node.labels else ""
-                        nodes[node_id] = node_dict
-                        logger.debug(f"添加节点: ID={node_id}, 标签={list(node.labels)}")
-                
-                # 处理路径中的所有关系
-                for rel in path.relationships:
-                    rel_id = int(rel.id)  # 直接转换为整数
-                    if rel_id not in lines:
-                        rel_dict = {
-                            "id": str(rel_id),
-                            "from": str(int(rel.start_node.id)),
-                            "to": str(int(rel.end_node.id)),
-                            "text": rel.type
-                        }
-                        lines[rel_id] = rel_dict
-                        logger.debug(f"添加关系: ID={rel_id}, 类型={rel.type}, 从{int(rel.start_node.id)}到{int(rel.end_node.id)}")
-            
-            # 如果需要元数据,查询INCLUDES关系
-            if meta:
-                meta_cypher = """
-                MATCH (start:DataModel)-[r:INCLUDES]->(meta:DataMeta)
-                WHERE id(start) = $nodeId
-                RETURN start, r, meta
-                """
-                meta_result = session.run(meta_cypher, nodeId=nodeid_int)
-                
-                for meta_record in meta_result:
-                    start_node = meta_record['start']
-                    rel = meta_record['r']
-                    meta_node = meta_record['meta']
-                    
-                    # 添加元数据节点
-                    meta_node_id = int(meta_node.id)
-                    if meta_node_id not in nodes:
-                        node_dict = serialize_node_properties(meta_node)
-                        node_dict["id"] = str(meta_node_id)
-                        node_dict["node_type"] = list(meta_node.labels)[0] if meta_node.labels else ""
-                        nodes[meta_node_id] = node_dict
-                    
-                    # 添加INCLUDES关系
-                    rel_id = int(rel.id)
-                    if rel_id not in lines:
-                        rel_dict = {
-                            "id": str(rel_id),
-                            "from": str(nodeid_int),
-                            "to": str(meta_node_id),
-                            "text": rel.type
-                        }
-                        lines[rel_id] = rel_dict
-            
-            logger.info(f"成功获取血缘关系图谱,ID: {nodeid_int}, 节点数: {len(nodes)}, 关系数: {len(lines)}")
-            return {
-                "nodes": list(nodes.values()),
-                "lines": list(lines.values())
-            }
-            
-    except Exception as e:
-        logger.error(f"获取数据模型血缘关系图谱失败: {str(e)}")
-        import traceback
-        logger.error(f"错误详情: {traceback.format_exc()}")
-        return {"nodes": [], "lines": []}
-
-
-# 数据模型影响图谱
-def model_impact_graph(nodeid, meta=False):
-    """
-    生成数据模型的影响关系图谱
-    按照DERIVED_FROM关系进行递归查找,从当前节点作为终点查找所有指向这个终点的节点
-    
-    Args:
-        nodeid: 节点ID
-        meta: 是否包含元数据
-        
-    Returns:
-        dict: 包含节点和连线信息的图谱数据
-    """
-    try:
-        with connect_graph().session() as session:
-            # 确保nodeid为整数
-            try:
-                nodeid_int = int(nodeid)
-            except (ValueError, TypeError):
-                logger.error(f"节点ID不是有效的整数: {nodeid}")
-                return {"nodes": [], "lines": []}
-            
-            # 查询起始模型节点是否存在
-            start_node_query = """
-            MATCH (n:DataModel)
-            WHERE id(n) = $nodeId
-            RETURN n
-            """
-            
-            start_result = session.run(start_node_query, nodeId=nodeid_int)
-            start_record = start_result.single()
-            
-            if not start_record:
-                logger.error(f"未找到ID为{nodeid_int}的DataModel节点")
-                return {"nodes": [], "lines": []}
-            
-            # 递归查找指向当前节点的DERIVED_FROM关系
-            cypher = """
-            MATCH (target:DataModel)
-            WHERE id(target) = $nodeId
-            MATCH path = (source)-[:DERIVED_FROM*0..]->(target)
-            WHERE source:DataResource OR source:DataModel
-            RETURN path
-            """
-                
-            result = session.run(cypher, nodeId=nodeid_int)
-            
-            # 收集节点和关系
-            nodes = {}
-            lines = {}
-            
-            for record in result:
-                # 处理路径
-                path = record['path']
-                logger.debug(f"处理影响路径,长度: {len(path)}, 节点数: {len(path.nodes)}, 关系数: {len(path.relationships)}")
-                
-                # 处理路径中的所有节点
-                for node in path.nodes:
-                    node_id = int(node.id)  # 直接转换为整数
-                    if node_id not in nodes:
-                        node_dict = serialize_node_properties(node)
-                        node_dict["id"] = str(node_id)
-                        node_dict["node_type"] = list(node.labels)[0] if node.labels else ""
-                        nodes[node_id] = node_dict
-                        logger.debug(f"添加影响节点: ID={node_id}, 标签={list(node.labels)}")
-                
-                # 处理路径中的所有关系
-                for rel in path.relationships:
-                    rel_id = int(rel.id)  # 直接转换为整数
-                    if rel_id not in lines:
-                        rel_dict = {
-                            "id": str(rel_id),
-                            "from": str(int(rel.start_node.id)),
-                            "to": str(int(rel.end_node.id)),
-                            "text": rel.type
-                        }
-                        lines[rel_id] = rel_dict
-                        logger.debug(f"添加影响关系: ID={rel_id}, 类型={rel.type}, 从{int(rel.start_node.id)}到{int(rel.end_node.id)}")
-            
-            # 如果需要元数据,查询INCLUDES关系
-            if meta:
-                meta_cypher = """
-                MATCH (target:DataModel)-[r:INCLUDES]->(meta:DataMeta)
-                WHERE id(target) = $nodeId
-                RETURN target, r, meta
-                """
-                meta_result = session.run(meta_cypher, nodeId=nodeid_int)
-                
-                for meta_record in meta_result:
-                    target_node = meta_record['target']
-                    rel = meta_record['r']
-                    meta_node = meta_record['meta']
-                    
-                    # 添加元数据节点
-                    meta_node_id = int(meta_node.id)
-                    if meta_node_id not in nodes:
-                        node_dict = serialize_node_properties(meta_node)
-                        node_dict["id"] = str(meta_node_id)
-                        node_dict["node_type"] = list(meta_node.labels)[0] if meta_node.labels else ""
-                        nodes[meta_node_id] = node_dict
-                    
-                    # 添加INCLUDES关系
-                    rel_id = int(rel.id)
-                    if rel_id not in lines:
-                        rel_dict = {
-                            "id": str(rel_id),
-                            "from": str(nodeid_int),
-                            "to": str(meta_node_id),
-                            "text": rel.type
-                        }
-                        lines[rel_id] = rel_dict
-            
-            logger.info(f"成功获取影响关系图谱,ID: {nodeid_int}, 节点数: {len(nodes)}, 关系数: {len(lines)}")
-            return {
-                "nodes": list(nodes.values()),
-                "lines": list(lines.values())
-            }
-            
-    except Exception as e:
-        logger.error(f"获取数据模型影响关系图谱失败: {str(e)}")
-        import traceback
-        logger.error(f"错误详情: {traceback.format_exc()}")
-        return {"nodes": [], "lines": []}
-
-
-# 数据模型全部图谱
-def model_all_graph(nodeid, meta=False):
-    """
-    生成数据模型的所有关系图谱
-    分别调用model_impact_graph查找影响关系,调用model_kinship_graph查找血缘关系,
-    然后合并两部分数据返回
-    
-    Args:
-        nodeid: 节点ID
-        meta: 是否包含元数据
-        
-    Returns:
-        dict: 包含节点和连线信息的图谱数据
-    """
-    try:
-        # 获取血缘关系图谱
-        kinship_data = model_kinship_graph(nodeid, meta)
-        
-        # 获取影响关系图谱
-        impact_data = model_impact_graph(nodeid, meta)
-        
-        # 合并节点数据,使用字典去重
-        merged_nodes = {}
-        merged_lines = {}
-        
-        # 添加血缘关系的节点和连线
-        if kinship_data and 'nodes' in kinship_data:
-            for node in kinship_data['nodes']:
-                node_id = node.get('id')
-                if node_id:
-                    merged_nodes[node_id] = node
-        
-        if kinship_data and 'lines' in kinship_data:
-            for line in kinship_data['lines']:
-                line_id = line.get('id')
-                if line_id:
-                    merged_lines[line_id] = line
-        
-        # 添加影响关系的节点和连线
-        if impact_data and 'nodes' in impact_data:
-            for node in impact_data['nodes']:
-                node_id = node.get('id')
-                if node_id:
-                    merged_nodes[node_id] = node
-        
-        if impact_data and 'lines' in impact_data:
-            for line in impact_data['lines']:
-                line_id = line.get('id')
-                if line_id:
-                    merged_lines[line_id] = line
-        
-        # 构建最终结果
-        result = {
-            "nodes": list(merged_nodes.values()),
-            "lines": list(merged_lines.values())
-        }
-        
-        logger.info(f"成功获取完整关系图谱,ID: {nodeid}, 节点数: {len(merged_nodes)}, 关系数: {len(merged_lines)}")
-        return result
-        
-    except Exception as e:
-        logger.error(f"获取数据模型完整关系图谱失败: {str(e)}")
-        return {"nodes": [], "lines": []}
-
-
-# 更新数据模型
-def data_model_edit(receiver):
-    """
-    更新数据模型
-    
-    Args:
-        receiver: 接收到的请求参数
-        
-    Returns:
-        更新结果
-    """
-    id = receiver.get('id')
-    name = receiver.get('name_zh')
-    name_en = receiver.get('name_en')
-    category = receiver.get('category')
-    describe = receiver.get('describe')
-    tag = receiver.get('tag')
-    frequency = receiver.get('frequency')
-    leader = receiver.get('leader')
-    organization = receiver.get('organization')
-    status = bool(receiver.get('status')) if receiver.get('status') is not None else None
-    meta_data = receiver.get('metaData', [])
-    
-    # 更新数据模型节点 - 添加新的字段
-    query = """
-    MATCH (n:DataModel) WHERE id(n) = $id
-    SET n.name_zh = $name_zh, 
-        n.name_en = $name_en, 
-        n.category = $category, 
-        n.describe = $describe,
-        n.frequency = $frequency,
-        n.leader = $leader,
-        n.organization = $organization,
-        n.status = $status,
-        n.create_time = $create_time
-    RETURN n
-    """
-    
-    create_time = get_formatted_time()
-    with connect_graph().session() as session:
-        result = session.run(query, 
-                             id=id, 
-                             name_zh=name, 
-                             name_en=name_en, 
-                             category=category, 
-                             describe=describe,
-                             frequency=frequency,
-                             leader=leader,
-                             organization=organization,
-                             status=status,
-                             create_time=create_time).data()
-    
-    # 处理标签关系
-    if tag:
-        # 先删除所有标签关系
-        delete_query = """
-        MATCH (n:DataModel)-[r:LABEL]->() WHERE id(n) = $id
-        DELETE r
-        """
-        with connect_graph().session() as session:
-            session.run(delete_query, id=id)
-        
-        # 再创建新的标签关系
-        tag_node = get_node_by_id('DataLabel', tag)
-        if tag_node:
-            model_node = get_node_by_id_no_label(id)
-            if model_node:
-                # 获取节点ID
-                model_id = model_node.id if hasattr(model_node, 'id') else model_node
-                tag_id = tag_node.id if hasattr(tag_node, 'id') else tag_node
-                
-                # 直接使用Cypher查询检查关系是否存在
-                with connect_graph().session() as session:
-                    rel_query = """
-                    MATCH (a)-[r:LABEL]->(b)
-                    WHERE id(a) = $start_id AND id(b) = $end_id
-                    RETURN count(r) > 0 as exists
-                    """
-                    rel_result = session.run(rel_query, 
-                                          start_id=int(model_id), 
-                                          end_id=int(tag_id)).single()
-                    
-                    # 如果关系不存在,则创建关系
-                    if not (rel_result and rel_result["exists"]):
-                        session.execute_write(
-                            lambda tx: tx.run(
-                                "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:LABEL]->(b)",
-                                a_id=int(model_id), b_id=int(tag_id)
-                            )
-                        )
-    
-    # 处理DataMeta节点关系更新
-    with connect_graph().session() as session:
-        # 先删除DataModel关联的所有DataMeta关系
-        delete_meta_query = """
-        MATCH (n:DataModel)-[r:INCLUDES]->(m:DataMeta)
-        WHERE id(n) = $id
-        DELETE r
-        """
-        session.run(delete_meta_query, id=id)
-        logger.info(f"已删除DataModel({id})的所有DataMeta关系")
-        
-        # 根据上传的metaData数据是否有值来决定是否重新构建INCLUDES关系
-        if meta_data:
-            # 根据上传的metaData数据重新构建INCLUDES关系
-            for meta_item in meta_data:
-                meta_id = meta_item.get('id')
-                if meta_id:
-                    try:
-                        meta_id = int(meta_id)
-                        # 验证DataMeta节点是否存在
-                        check_meta_query = """
-                        MATCH (m:DataMeta)
-                        WHERE id(m) = $meta_id
-                        RETURN m
-                        """
-                        meta_result = session.run(check_meta_query, meta_id=meta_id)
-                        
-                        if meta_result.single():
-                            # 创建INCLUDES关系
-                            create_includes_query = """
-                            MATCH (n:DataModel), (m:DataMeta)
-                            WHERE id(n) = $model_id AND id(m) = $meta_id
-                            CREATE (n)-[:INCLUDES]->(m)
-                            RETURN n, m
-                            """
-                            session.run(create_includes_query, model_id=id, meta_id=meta_id)
-                            logger.info(f"成功创建INCLUDES关系: DataModel({id}) -> DataMeta({meta_id})")
-                        else:
-                            logger.warning(f"DataMeta节点不存在,ID: {meta_id}")
-                    except (ValueError, TypeError) as e:
-                        logger.error(f"无效的meta_id: {meta_id}, 错误: {str(e)}")
-        else:
-            logger.info(f"meta_data为空,不需要重新创建INCLUDES关系,DataModel({id})将不关联任何DataMeta节点")
-    
-    return {"message": "数据模型更新成功"}
-
-
-def model_community(tag=None):
-    """
-    查询DataModel的所有节点及DERIVED_FROM关系
-    
-    Args:
-        tag: 可选的标签ID,如果指定则只查找有该标签的DataModel节点
-        
-    Returns:
-        dict: 包含节点和连线信息的图谱数据,格式与model_kinship_graph相同
-    """
-    try:
-        with connect_graph().session() as session:
-            # 构建查询条件
-            if tag is not None:
-                # 确保tag参数是整数类型
-                try:
-                    tag_id = int(tag)
-                except (ValueError, TypeError):
-                    logger.warning(f"Invalid tag parameter: {tag}, expected integer")
-                    return {"nodes": [], "lines": []}
-                
-                # 有标签查询条件时,查询有指定标签的DataModel节点及其DERIVED_FROM关系
-                cypher = """
-                MATCH (dm:DataModel)-[:LABEL]->(t)
-                WHERE id(t) = $tag_id
-                WITH dm
-                MATCH path = (dm)-[:DERIVED_FROM*0..]->(target:DataModel)
-                RETURN path
-                UNION
-                MATCH (dm:DataModel)-[:LABEL]->(t)
-                WHERE id(t) = $tag_id
-                WITH dm
-                MATCH path = (source:DataModel)-[:DERIVED_FROM*0..]->(dm)
-                RETURN path
-                """
-                
-                result = session.run(cypher, tag_id=tag_id)
-            else:
-                # 没有标签查询条件时,查询所有DataModel节点及其DERIVED_FROM关系
-                cypher = """
-                MATCH (dm:DataModel)
-                WITH dm
-                MATCH path = (dm)-[:DERIVED_FROM*0..]->(target:DataModel)
-                RETURN path
-                UNION
-                MATCH (dm:DataModel)
-                WITH dm
-                MATCH path = (source:DataModel)-[:DERIVED_FROM*0..]->(dm)
-                RETURN path
-                """
-                
-                result = session.run(cypher)
-            
-            # 收集节点和关系
-            nodes = {}
-            lines = {}
-            
-            for record in result:
-                # 处理路径
-                path = record['path']
-                logger.debug(f"处理社区路径,长度: {len(path)}, 节点数: {len(path.nodes)}, 关系数: {len(path.relationships)}")
-                
-                # 处理路径中的所有节点
-                for node in path.nodes:
-                    node_id = int(node.id)  # 直接转换为整数
-                    if node_id not in nodes:
-                        node_dict = serialize_node_properties(node)
-                        node_dict["id"] = str(node_id)
-                        node_dict["node_type"] = list(node.labels)[0] if node.labels else ""
-                        nodes[node_id] = node_dict
-                        logger.debug(f"添加社区节点: ID={node_id}, 标签={list(node.labels)}")
-                
-                # 处理路径中的所有关系
-                for rel in path.relationships:
-                    rel_id = int(rel.id)  # 直接转换为整数
-                    if rel_id not in lines:
-                        rel_dict = {
-                            "id": str(rel_id),
-                            "from": str(int(rel.start_node.id)),
-                            "to": str(int(rel.end_node.id)),
-                            "text": rel.type
-                        }
-                        lines[rel_id] = rel_dict
-                        logger.debug(f"添加社区关系: ID={rel_id}, 类型={rel.type}, 从{int(rel.start_node.id)}到{int(rel.end_node.id)}")
-            
-            logger.info(f"成功获取数据模型社区图谱,标签ID: {tag}, 节点数: {len(nodes)}, 关系数: {len(lines)}")
-            return {
-                "nodes": list(nodes.values()),
-                "lines": list(lines.values())
-            }
-            
-    except Exception as e:
-        logger.error(f"获取数据模型社区图谱失败: {str(e)}")
-        import traceback
-        logger.error(f"错误详情: {traceback.format_exc()}")
-        return {"nodes": [], "lines": []}
-
-
-def model_search_list(model_id, page, page_size, name_en_filter=None,
-                     name_zh_filter=None, category_filter=None, tag_filter=None):
-    """获取特定数据模型关联的元数据列表"""
-    try:
-        with connect_graph().session() as session:
-            # 确保model_id为整数
-            try:
-                model_id_int = int(model_id)
-            except (ValueError, TypeError):
-                logger.error(f"模型ID不是有效的整数: {model_id}")
-                return [], 0
-            
-            # 基本匹配语句 - 支持DataMeta和Metadata标签
-            match_clause = """
-            MATCH (n:DataModel)-[:INCLUDES]->(m)
-            WHERE id(n) = $model_id
-            AND (m:DataMeta OR m:Metadata)
-            """
-            
-            where_conditions = []
-            
-            if name_en_filter:
-                where_conditions.append(f"m.name_en CONTAINS '{name_en_filter}'")
-                
-            if name_zh_filter:
-                where_conditions.append(f"m.name_zh CONTAINS '{name_zh_filter}'")
-                
-            if category_filter:
-                where_conditions.append(f"m.category = '{category_filter}'")
-                
-            # 标签过滤需要额外的匹配
-            tag_match = ""
-            if tag_filter:
-                tag_match = "MATCH (m)-[:HAS_TAG]->(t:Tag) WHERE t.name_zh = $tag_filter"
-            
-            where_clause = " AND " + " AND ".join(where_conditions) if where_conditions else ""
-            
-            # 计算总数
-            count_cypher = f"""
-            {match_clause}{where_clause}
-            {tag_match}
-            RETURN count(m) as count
-            """
-            count_params = {"model_id": model_id_int}
-            if tag_filter:
-                count_params["tag_filter"] = tag_filter
-                
-            count_result = session.run(count_cypher, count_params)
-            count_record = count_result.single()
-            total_count = count_record["count"] if count_record else 0
-            
-            # 分页查询
-            skip = (page - 1) * page_size
-            cypher = f"""
-            {match_clause}{where_clause}
-            {tag_match}
-            RETURN m
-            ORDER BY m.name_zh
-            SKIP {skip} LIMIT {page_size}
-            """
-            
-            result = session.run(cypher, count_params)  # type: ignore[arg-type]
-            
-            # 格式化结果
-            metadata_list = []
-            for record in result:
-                meta = serialize_node_properties(record["m"])
-                meta["id"] = record["m"].id
-                metadata_list.append(meta)
-            
-            logger.info(f"成功获取数据模型关联元数据,ID: {model_id_int}, 元数据数量: {total_count}")
-            return metadata_list, total_count
-    except Exception as e:
-        logger.error(f"获取数据模型关联的元数据列表失败: {str(e)}")
-        return [], 0
-
-
-def get_businessdomain_node(name_zh):
-    """
-    查找BusinessDomain节点,需要同时满足两个条件:
-    1. name_zh匹配
-    2. 存在与"数据模型"标签的BELONGS_TO关系
-    
-    Args:
-        name_zh: 业务域节点的中文名称
-        
-    Returns:
-        节点对象或None(如果不存在)
-    """
-    try:
-        with connect_graph().session() as session:
-            query = """
-            MATCH (bd:BusinessDomain)-[:BELONGS_TO]->(label:DataLabel)
-            WHERE bd.name_zh = $name_zh 
-            AND (label.name_zh = '数据模型' OR label.name_en = 'data_model')
-            RETURN bd
-            LIMIT 1
-            """
-            result = session.run(query, name_zh=name_zh)
-            record = result.single()
-            
-            if record and record.get('bd'):
-                logger.info(f"找到已存在的BusinessDomain节点: name_zh={name_zh}")
-                return record['bd']
-            else:
-                logger.info(f"未找到BusinessDomain节点: name_zh={name_zh}")
-                return None
-                
-    except Exception as e:
-        logger.error(f"查询BusinessDomain节点时发生错误: {str(e)}")
-        return None
-
-
-def handle_businessdomain_node(data_model, result_list, result, receiver, id_list):
-    """
-    创建一个BusinessDomain业务域节点,属性和关联关系与DataModel节点一致
-    额外创建与DataLabel中"数据模型"标签的BELONGS_TO关系
-    
-    Args:
-        data_model: 数据模型名称
-        result_list: 数据模型英文名列表
-        result: 序列化的ID列表
-        receiver: 接收到的请求参数
-        id_list: ID列表(用于处理资源关系)
-        
-    Returns:
-        tuple: (node_id, business_domain_node)
-    """
-    try:
-        logger.info(f"开始创建BusinessDomain节点,名称: {data_model}")
-        
-        # 添加数据资源 血缘关系的字段 blood_resource
-        data_model_en = result_list[0] if result_list and len(result_list) > 0 else ""
-        
-        # 准备BusinessDomain节点的属性(与DataModel相同)
-        bd_attributes = {
-            'name_zh': data_model,
-            'name_en': data_model_en,
-            'id_list': result,
-            'create_time': get_formatted_time(),
-            'description': receiver.get('description', ''),
-            'category': receiver.get('category', ''),
-            'leader': receiver.get('leader', ''),
-            'origin': receiver.get('origin', ''),
-            'frequency': receiver.get('frequency', ''),
-            'organization': receiver.get('organization', ''),
-            'data_sensitivity': receiver.get('data_sensitivity', ''),
-            'status': receiver.get('status', '')
-        }
-        
-        # 创建BusinessDomain节点
-        # 使用专用函数查找,需要同时满足name_zh和BELONGS_TO关系
-        business_domain_node = get_businessdomain_node(data_model) or create_or_get_node('BusinessDomain', **bd_attributes)
-        
-        logger.info(f"BusinessDomain节点创建成功,data: {business_domain_node}")
-        
-        # 获取节点ID
-        node_id = business_domain_node
-        if hasattr(business_domain_node, 'id'):
-            node_id = business_domain_node.id
-        else:
-            # 如果节点没有id属性,尝试通过查询获取
-            query = """
-            MATCH (n:BusinessDomain {name_zh: $name})
-            RETURN id(n) as node_id
-            """
-            with connect_graph().session() as session:
-                result_query = session.run(query, name=data_model)
-                record = result_query.single()
-                if record and "node_id" in record:
-                    node_id = record["node_id"]
-        
-        logger.info(f"BusinessDomain节点ID: {node_id}")
-        
-        # 1. 处理子节点关系(child关系)
-        child_list = receiver.get('childrenId', [])
-        if child_list:
-            logger.info(f"处理BusinessDomain的child关系,子节点数量: {len(child_list)}")
-            for child_id in child_list:
-                child_node = get_node_by_id_no_label(child_id)
-                if child_node:
-                    with connect_graph().session() as session:
-                        rel_query = """
-                        MATCH (a)-[r:child]->(b)
-                        WHERE id(a) = $start_id AND id(b) = $end_id
-                        RETURN count(r) > 0 as exists
-                        """
-                        child_node_id = child_node.id if hasattr(child_node, 'id') else int(child_node)
-                        rel_result = session.run(rel_query, 
-                                                start_id=int(node_id), 
-                                                end_id=int(child_node_id)).single()
-                        
-                        if not (rel_result and rel_result["exists"]):
-                            child_id_int = int(child_node_id)
-                            session.execute_write(
-                                lambda tx: tx.run(
-                                    "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:child]->(b)",
-                                    a_id=int(node_id), b_id=child_id_int
-                                )
-                            )
-                            logger.info(f"创建BusinessDomain child关系: {node_id} -> {child_node_id}")
-        
-        # 2. 处理标签关系(LABEL关系)
-        if receiver.get('tag'):
-            logger.info(f"处理BusinessDomain的LABEL关系,标签ID: {receiver['tag']}")
-            tag = get_node_by_id('DataLabel', receiver['tag'])
-            if tag:
-                with connect_graph().session() as session:
-                    rel_query = """
-                    MATCH (a)-[r:LABEL]->(b)
-                    WHERE id(a) = $start_id AND id(b) = $end_id
-                    RETURN count(r) > 0 as exists
-                    """
-                    rel_result = session.run(rel_query, 
-                                            start_id=int(node_id), 
-                                            end_id=int(tag.id)).single()
-                    
-                    if not (rel_result and rel_result["exists"]):
-                        session.execute_write(
-                            lambda tx: tx.run(
-                                "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:LABEL]->(b)",
-                                a_id=int(node_id), b_id=int(tag.id)
-                            )
-                        )
-                        logger.info(f"创建BusinessDomain LABEL关系: {node_id} -> {tag.id}")
-        
-        # 3. 处理数据源关系(COME_FROM关系)
-        data_source = receiver.get('data_source')
-        if data_source:
-            logger.info(f"处理BusinessDomain的COME_FROM关系,数据源: {data_source}")
-            try:
-                data_source_id = None
-                data_source_name_en = None
-                
-                # 获取数据源标识(支持多种格式)
-                if isinstance(data_source, (int, float)) or (isinstance(data_source, str) and data_source.isdigit()):
-                    data_source_id = int(data_source)
-                elif isinstance(data_source, dict) and data_source.get('name_en'):
-                    data_source_name_en = data_source['name_en']
-                elif isinstance(data_source, str):
-                    data_source_name_en = data_source
-                
-                # 创建BusinessDomain与数据源的关系
-                with connect_graph().session() as session:
-                    if data_source_id is not None:
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE id(b) = $ds_id RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, ds_id=data_source_id)
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: ID={data_source_id},跳过关系创建")
-                        else:
-                            rel_check_query = """
-                            MATCH (a:BusinessDomain)-[r:COME_FROM]->(b:DataSource)
-                            WHERE id(a) = $bd_id AND id(b) = $ds_id
-                            RETURN count(r) > 0 as exists
-                            """
-                            rel_check_result = session.run(rel_check_query,
-                                                          bd_id=int(node_id),
-                                                          ds_id=data_source_id).single()
-                            
-                            if not (rel_check_result and rel_check_result["exists"]):
-                                create_rel_cypher = """
-                                MATCH (a:BusinessDomain), (b:DataSource)
-                                WHERE id(a) = $bd_id AND id(b) = $ds_id
-                                CREATE (a)-[r:COME_FROM]->(b)
-                                RETURN r
-                                """
-                                session.run(create_rel_cypher,
-                                          bd_id=int(node_id),
-                                          ds_id=data_source_id)
-                                logger.info(f"创建BusinessDomain与数据源的COME_FROM关系: bd_id={node_id} -> data_source_id={data_source_id}")
-                                
-                    elif data_source_name_en:
-                        check_ds_cypher = "MATCH (b:DataSource {name_en: $name_en}) RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, name_en=data_source_name_en)
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: name_en={data_source_name_en},跳过关系创建")
-                        else:
-                            rel_check_query = """
-                            MATCH (a:BusinessDomain)-[r:COME_FROM]->(b:DataSource {name_en: $ds_name_en})
-                            WHERE id(a) = $bd_id
-                            RETURN count(r) > 0 as exists
-                            """
-                            rel_check_result = session.run(rel_check_query,
-                                                          bd_id=int(node_id),
-                                                          ds_name_en=data_source_name_en).single()
-                            
-                            if not (rel_check_result and rel_check_result["exists"]):
-                                create_rel_cypher = """
-                                MATCH (a:BusinessDomain), (b:DataSource {name_en: $ds_name_en})
-                                WHERE id(a) = $bd_id
-                                CREATE (a)-[r:COME_FROM]->(b)
-                                RETURN r
-                                """
-                                session.run(create_rel_cypher,
-                                          bd_id=int(node_id),
-                                          ds_name_en=data_source_name_en)
-                                logger.info(f"创建BusinessDomain与数据源的COME_FROM关系: bd_id={node_id} -> name_en={data_source_name_en}")
-                    else:
-                        logger.warning(f"data_source参数无效,无法识别格式: {data_source}")
-                        
-            except Exception as e:
-                logger.error(f"创建BusinessDomain与数据源关系时发生错误: {str(e)}")
-        
-        # 4. 处理与id_list中资源和元数据的关系(如果有)
-        if id_list:
-            logger.info(f"处理BusinessDomain与资源/元数据的关系,id_list数量: {len(id_list)}")
-            # 构建meta_id和resouce_id的列表
-            resouce_ids = [record['resource_id'] for record in id_list if 'resource_id' in record]
-            meta_ids = [record['id'] for id_list_item in id_list for record in id_list_item.get('metaData', []) if 'id' in record]
-            
-            # 创建与DataResource的关系
-            if resouce_ids:
-                query = """
-                MATCH (source:BusinessDomain), (target:DataResource)
-                WHERE id(source)=$source_id AND id(target) IN $target_ids
-                MERGE (source)-[:resource]->(target)
-                """
-                with connect_graph().session() as session:
-                    session.run(query, source_id=int(node_id), target_ids=resouce_ids)
-                    logger.info(f"创建BusinessDomain与DataResource的关系,资源数量: {len(resouce_ids)}")
-            
-            # 处理元数据关系
-            if meta_ids:
-                for item in id_list:
-                    for meta_item in item.get('metaData', []):
-                        meta_id = meta_item['id']
-                        data_standard = meta_item.get('data_standard', '')
-                        name_en = meta_item.get('name_en', '')
-                        name_zh = meta_item.get('name_zh', '')
-                        
-                        # 创建meta_node节点
-                        meta_params = {
-                            'name_zh': name_zh,
-                            'name_en': name_en,
-                            'standard': data_standard,
-                            'create_time': get_formatted_time()
-                        }
-                        meta_node = create_or_get_node('DataMeta', **meta_params)
-                        
-                        # 创建BusinessDomain与DataMeta的关系
-                        if meta_node:
-                            meta_node_id = meta_node.id if hasattr(meta_node, 'id') else meta_node
-                            query = """
-                            MATCH (source:BusinessDomain), (target:DataMeta)
-                            WHERE id(source) = $source_id AND id(target) = $target_id
-                            MERGE (source)-[:INCLUDES]->(target)
-                            """
-                            with connect_graph().session() as session:
-                                session.run(query, source_id=int(node_id), target_id=int(meta_node_id))
-                logger.info(f"创建BusinessDomain与DataMeta的关系,元数据数量: {len(meta_ids)}")
-        
-        # 5. 创建与DataLabel中"数据模型"标签的BELONGS_TO关系
-        logger.info("查找DataLabel中的'数据模型'标签")
-        with connect_graph().session() as session:
-            # 查找名称为"数据模型"的DataLabel节点
-            find_label_query = """
-            MATCH (label:DataLabel)
-            WHERE label.name_zh = '数据模型' OR label.name_en = 'data_model'
-            RETURN id(label) as label_id
-            LIMIT 1
-            """
-            label_result = session.run(find_label_query)
-            label_record = label_result.single()
-            
-            if label_record:
-                label_id = label_record['label_id']
-                logger.info(f"找到'数据模型'标签,ID: {label_id}")
-                
-                # 检查BELONGS_TO关系是否已存在
-                rel_check_query = """
-                MATCH (a:BusinessDomain)-[r:BELONGS_TO]->(b:DataLabel)
-                WHERE id(a) = $bd_id AND id(b) = $label_id
-                RETURN count(r) > 0 as exists
-                """
-                rel_check_result = session.run(rel_check_query,
-                                              bd_id=int(node_id),
-                                              label_id=label_id).single()
-                
-                if not (rel_check_result and rel_check_result["exists"]):
-                    # 创建BELONGS_TO关系
-                    create_rel_query = """
-                    MATCH (a:BusinessDomain), (b:DataLabel)
-                    WHERE id(a) = $bd_id AND id(b) = $label_id
-                    CREATE (a)-[r:BELONGS_TO]->(b)
-                    RETURN r
-                    """
-                    session.run(create_rel_query, bd_id=int(node_id), label_id=label_id)
-                    logger.info(f"成功创建BusinessDomain与'数据模型'标签的BELONGS_TO关系: bd_id={node_id} -> label_id={label_id}")
-                else:
-                    logger.info(f"BusinessDomain与'数据模型'标签的BELONGS_TO关系已存在")
-            else:
-                logger.warning("未找到名称为'数据模型'的DataLabel节点,跳过BELONGS_TO关系创建")
-        
-        logger.info(f"BusinessDomain节点创建完成,ID: {node_id}")
-        return node_id, business_domain_node
-        
-    except Exception as e:
-        logger.error(f"创建BusinessDomain节点时发生错误: {str(e)}")
-        import traceback
-        logger.error(f"错误详情: {traceback.format_exc()}")
-        raise 

+ 0 - 2
app/core/data_resource/__init__.py

@@ -1,2 +0,0 @@
-# app/core/data_resource/__init__.py
-# 数据资源业务逻辑模块 

+ 0 - 1825
app/core/data_resource/resource.py

@@ -1,1825 +0,0 @@
-import json
-import re
-import logging
-from py2neo import Relationship
-import pandas as pd
-from app.services.neo4j_driver import neo4j_driver
-from app.core.graph.graph_operations import create_or_get_node, relationship_exists, get_node, connect_graph
-import time
-from datetime import datetime
-from app.core.meta_data import get_formatted_time, translate_and_parse
-from app import db
-from sqlalchemy import text
-
-logger = logging.getLogger("app")
-
-def serialize_neo4j_object(obj):
-    """
-    将Neo4j对象转换为可JSON序列化的格式
-    
-    Args:
-        obj: Neo4j节点或属性值
-        
-    Returns:
-        序列化后的对象
-    """
-    if hasattr(obj, 'year'):  # DateTime对象
-        # 将Neo4j DateTime转换为字符串
-        return obj.strftime("%Y-%m-%d %H:%M:%S") if hasattr(obj, 'strftime') else str(obj)
-    elif hasattr(obj, '__dict__'):  # 复杂对象
-        return str(obj)
-    else:
-        return obj
-
-def serialize_node_properties(node):
-    """
-    将Neo4j节点属性序列化为可JSON化的字典
-    
-    Args:
-        node: Neo4j节点对象
-        
-    Returns:
-        dict: 序列化后的属性字典
-    """
-    properties = {}
-    for key, value in dict(node).items():
-        properties[key] = serialize_neo4j_object(value)
-    return properties
-
-def get_formatted_time():
-    """获取格式化的当前时间"""
-    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
-
-def get_node_by_id(label, id):
-    """根据ID获取指定标签的节点"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 确保id为整数
-            try:
-                id_int = int(id)
-            except (ValueError, TypeError):
-                logger.error(f"节点ID不是有效的整数: {id}")
-                return None
-                
-            cypher = f"MATCH (n:{label}) WHERE id(n) = $id RETURN n "
-            result = session.run(cypher, {'id': id_int})  # type: ignore[arg-type]
-            record = result.single()
-            return record["n"] if record else None
-    except Exception as e:
-        logger.error(f"根据ID获取节点失败: {str(e)}")
-        return None
-
-def get_node_by_id_no_label(id):
-    """根据ID获取节点,不限制标签"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 确保id为整数
-            try:
-                id_int = int(id)
-            except (ValueError, TypeError):
-                logger.error(f"节点ID不是有效的整数: {id}")
-                return None
-                
-            cypher = "MATCH (n) WHERE id(n) = $id RETURN n"
-            result = session.run(cypher, {'id': id_int})
-            record = result.single()
-            return record["n"] if record else None
-    except Exception as e:
-        logger.error(f"根据ID获取节点失败: {str(e)}")
-        return None
-
-def delete_relationships(start_node, rel_type=None, end_node=None):
-    """删除关系"""
-    try:
-        with neo4j_driver.get_session() as session:
-            if rel_type and end_node:
-                cypher = "MATCH (a)-[r:`{rel_type}`]->(b) WHERE id(a) = $start_id AND id(b) = $end_id DELETE r"
-                cypher = cypher.replace("{rel_type}", rel_type)
-                session.run(cypher, {'start_id': start_node.id, 'end_id': end_node.id})  # type: ignore[arg-type]
-            elif rel_type:
-                cypher = "MATCH (a)-[r:`{rel_type}`]->() WHERE id(a) = $start_id DELETE r"
-                cypher = cypher.replace("{rel_type}", rel_type)
-                session.run(cypher, {'start_id': start_node.id})  # type: ignore[arg-type]
-            else:
-                cypher = "MATCH (a)-[r]->() WHERE id(a) = $start_id DELETE r"
-                session.run(cypher, {'start_id': start_node.id})
-        return True
-    except Exception as e:
-        logger.error(f"删除关系失败: {str(e)}")
-        return False
-
-def update_or_create_node(label, **properties):
-    """更新或创建节点"""
-    try:
-        with neo4j_driver.get_session() as session:
-            node_id = properties.pop('id', None)
-            if node_id:
-                # 更新现有节点
-                set_clause = ", ".join([f"n.{k} = ${k}" for k in properties.keys()])
-                cypher = f"MATCH (n:{label}) WHERE id(n) = $id SET {set_clause} RETURN n"
-                params = {'id': int(node_id)}
-                params.update(properties)
-                result = session.run(cypher, params)  # type: ignore[arg-type]
-            else:
-                # 创建新节点
-                props_str = ", ".join([f"{k}: ${k}" for k in properties.keys()])
-                cypher = f"CREATE (n:{label} {{{props_str}}}) RETURN n"
-                result = session.run(cypher, properties)  # type: ignore[arg-type]
-            
-            record = result.single()
-            return record["n"] if record else None
-    except Exception as e:
-        logger.error(f"更新或创建节点失败: {str(e)}")
-        return None
-
-def handle_businessdomain_node(receiver, head_data, data_source=None, resource_type=None):
-    """处理业务领域节点创建和关系建立"""
-    try:
-        # 验证必要参数
-        if not resource_type:
-            raise ValueError("resource_type参数不能为空")
-            
-        # 更新属性
-        update_attributes = {
-            'name_en': receiver.get('name_en', receiver.get('name_zh', '')),
-            'create_time': get_formatted_time(),
-            'type': resource_type
-        }
-        
-        # 记录describe字段
-        if "describe" in receiver:
-            logger.info(f"创建业务领域,describe字段将被设置为: {receiver.get('describe')}")
-        else:
-            logger.info("创建业务领域,describe字段不在创建数据中")
-        
-        # 清理不需要的属性
-        receiver_copy = receiver.copy()
-        if 'additional_info' in receiver_copy:
-            del receiver_copy['additional_info']
-        if 'data_source' in receiver_copy:
-            del receiver_copy['data_source']
-            
-        tag_list = receiver_copy.get('tag')
-        receiver_copy.update(update_attributes)
-
-        # 创建或获取 BusinessDomain 节点
-        with neo4j_driver.get_session() as session:
-            props_str = ", ".join([f"{k}: ${k}" for k in receiver_copy.keys()])
-            cypher = f"""
-            MERGE (n:BusinessDomain {{name_zh: $name_zh}})
-            ON CREATE SET n = {{{props_str}}}
-            ON MATCH SET {", ".join([f"n.{k} = ${k}" for k in receiver_copy.keys()])}
-            RETURN n
-            """
-            result = session.run(cypher, receiver_copy)  # type: ignore[arg-type]
-            record = result.single()
-            if not record:
-                raise ValueError("Failed to create or get BusinessDomain node")
-            business_domain_node = record["n"]
-            domain_id = business_domain_node.id
-            
-            logger.info(f"创建BusinessDomain节点成功,ID={domain_id}, describe字段: {business_domain_node.get('describe')}")
-
-            # 处理标签关系
-            if tag_list:
-                tag_node = get_node_by_id('DataLabel', tag_list)
-                if tag_node:
-                    rel_check = """
-                    MATCH (a:BusinessDomain)-[r:LABEL]->(b:DataLabel) 
-                    WHERE id(a) = $domain_id AND id(b) = $tag_id
-                    RETURN r
-                    """
-                    rel_result = session.run(rel_check, {'domain_id': domain_id, 'tag_id': tag_node.id})
-                    
-                    if not rel_result.single():
-                        rel_create = """
-                        MATCH (a:BusinessDomain), (b:DataLabel)
-                        WHERE id(a) = $domain_id AND id(b) = $tag_id
-                        CREATE (a)-[r:LABEL]->(b)
-                        RETURN r
-                        """
-                        session.run(rel_create, {'domain_id': domain_id, 'tag_id': tag_node.id})
-                        logger.info(f"成功创建BusinessDomain与DataLabel的LABEL关系")
-            
-            # 处理头部数据(元数据,字段)
-            if head_data:
-                for item in head_data:
-                    meta_cypher = """
-                    MERGE (m:DataMeta {name_zh: $name_zh})
-                    ON CREATE SET m.name_en = $name_en, 
-                                m.create_time = $create_time,
-                                m.data_type = $data_type,
-                                m.status = true
-                    ON MATCH SET m.data_type = $data_type,
-                                m.status = true
-                    RETURN m
-                    """
-                    
-                    create_time = get_formatted_time()
-                    meta_result = session.run(meta_cypher, {
-                        'name_zh': item['name_zh'],
-                        'name_en': item['name_en'],
-                        'create_time': create_time,
-                        'data_type': item['data_type']
-                    })
-                    meta_record = meta_result.single()
-                    
-                    if meta_record and meta_record["m"]:
-                        meta_node = meta_record["m"]
-                        meta_id = meta_node.id
-                        
-                        logger.info(f"创建或获取到元数据节点: ID={meta_id}, name_zh={item['name_zh']}")
-                        
-                        # 创建BusinessDomain与DataMeta的关系
-                        rel_cypher = """
-                        MATCH (a:BusinessDomain), (m:DataMeta)
-                        WHERE id(a) = $domain_id AND id(m) = $meta_id
-                        MERGE (a)-[r:INCLUDES]->(m)
-                        RETURN r
-                        """
-                        
-                        rel_result = session.run(rel_cypher, {
-                            'domain_id': domain_id,
-                            'meta_id': meta_id
-                        })
-                        
-                        rel_record = rel_result.single()
-                        if rel_record:
-                            logger.info(f"成功创建BusinessDomain与元数据的关系: {domain_id} -> {meta_id}")
-                        else:
-                            logger.warning(f"创建BusinessDomain与元数据的关系失败: {domain_id} -> {meta_id}")
-                    else:
-                        logger.error(f"未能创建或获取元数据节点: {item['name_zh']}")
-            
-            # 处理数据源关系
-            if data_source:
-                try:
-                    data_source_id = None
-                    data_source_name_en = None
-                    
-                    if isinstance(data_source, (int, float)) or (isinstance(data_source, str) and data_source.isdigit()):
-                        data_source_id = int(data_source)
-                        logger.info(f"data_source 为节点ID: {data_source_id}")
-                    elif isinstance(data_source, dict) and data_source.get('name_en'):
-                        data_source_name_en = data_source['name_en']
-                        logger.info(f"data_source 为字典,提取name_en: {data_source_name_en}")
-                    elif isinstance(data_source, str):
-                        data_source_name_en = data_source
-                        logger.info(f"data_source 为字符串name_en: {data_source_name_en}")
-                    
-                    if data_source_id is not None:
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE id(b) = $ds_id RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, {'ds_id': data_source_id})
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: ID={data_source_id},跳过关系创建")
-                        else:
-                            rel_data_source_cypher = """
-                            MATCH (a:BusinessDomain), (b:DataSource)
-                            WHERE id(a) = $domain_id AND id(b) = $ds_id
-                            MERGE (a)-[r:COME_FROM]->(b)
-                            RETURN r
-                            """
-                            rel_result = session.run(rel_data_source_cypher, {
-                                'domain_id': domain_id,
-                                'ds_id': data_source_id
-                            })
-                            rel_record = rel_result.single()
-                            
-                            if rel_record:
-                                logger.info(f"已创建BusinessDomain与数据源的COME_FROM关系: domain_id={domain_id} -> data_source_id={data_source_id}")
-                            else:
-                                logger.warning(f"创建COME_FROM关系失败,但不中断主流程: {domain_id} -> {data_source_id}")
-                                
-                    elif data_source_name_en:
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE b.name_en = $ds_name_en RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, {'ds_name_en': data_source_name_en})
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: name_en={data_source_name_en},跳过关系创建")
-                        else:
-                            rel_data_source_cypher = """
-                            MATCH (a:BusinessDomain), (b:DataSource)
-                            WHERE id(a) = $domain_id AND b.name_en = $ds_name_en
-                            MERGE (a)-[r:COME_FROM]->(b)
-                            RETURN r
-                            """
-                            rel_result = session.run(rel_data_source_cypher, {
-                                'domain_id': domain_id,
-                                'ds_name_en': data_source_name_en
-                            })
-                            rel_record = rel_result.single()
-                            
-                            if rel_record:
-                                logger.info(f"已创建BusinessDomain与数据源的COME_FROM关系: domain_id={domain_id} -> name_en={data_source_name_en}")
-                            else:
-                                logger.warning(f"创建COME_FROM关系失败,但不中断主流程: {domain_id} -> {data_source_name_en}")
-                    else:
-                        logger.warning(f"data_source参数无效,无法识别格式: {data_source}")
-                        
-                except Exception as e:
-                    logger.error(f"处理数据源关系失败(不中断主流程): {str(e)}")
-            
-            # 创建与"数据资源"标签的BELONGS_TO关系
-            try:
-                data_model_label_cypher = """
-                MATCH (label:DataLabel {name_zh: '数据资源'})
-                RETURN label
-                """
-                label_result = session.run(data_model_label_cypher)
-                label_record = label_result.single()
-                
-                if label_record:
-                    data_model_label_id = label_record["label"].id
-                    
-                    # 创建BELONGS_TO关系
-                    belongs_to_cypher = """
-                    MATCH (domain:BusinessDomain), (label:DataLabel)
-                    WHERE id(domain) = $domain_id AND id(label) = $label_id
-                    MERGE (domain)-[r:BELONGS_TO]->(label)
-                    RETURN r
-                    """
-                    belongs_result = session.run(belongs_to_cypher, {
-                        'domain_id': domain_id,
-                        'label_id': data_model_label_id
-                    })
-                    
-                    if belongs_result.single():
-                        logger.info(f"成功创建BusinessDomain与'数据资源'标签的BELONGS_TO关系: {domain_id} -> {data_model_label_id}")
-                    else:
-                        logger.warning(f"创建BELONGS_TO关系失败: {domain_id} -> {data_model_label_id}")
-                else:
-                    logger.warning("未找到'数据资源'标签,跳过BELONGS_TO关系创建")
-            except Exception as e:
-                logger.error(f"创建与'数据资源'标签的关系失败: {str(e)}")
-            
-            return domain_id
-    except Exception as e:
-        logger.error(f"处理业务领域节点创建和关系建立失败: {str(e)}")
-        raise
-
-# 数据资源-元数据 关系节点创建、查询
-def handle_node(receiver, head_data, data_source=None, resource_type=None):
-    """处理数据资源节点创建和关系建立"""
-    try:
-        # 验证必要参数
-        if not resource_type:
-            raise ValueError("resource_type参数不能为空")
-            
-        # 更新属性
-        update_attributes = {
-            'name_en': receiver.get('name_en', receiver.get('name_zh', '')),
-            'create_time': get_formatted_time(),
-            'type': resource_type  # 直接使用传入的资源类型值
-        }
-        
-        # 记录describe字段是否存在于创建数据中
-        if "describe" in receiver:
-            logger.info(f"创建资源,describe字段将被设置为: {receiver.get('describe')}")
-        else:
-            logger.info("创建资源,describe字段不在创建数据中")
-        
-        if 'additional_info' in receiver:
-            del receiver['additional_info']
-        # 从receiver中移除data_source属性,避免将复杂对象作为节点属性
-        if 'data_source' in receiver:
-            del receiver['data_source']
-            
-        tag_list = receiver.get('tag')
-        receiver.update(update_attributes)
-
-        # 创建或获取 DataResource 节点
-        with neo4j_driver.get_session() as session:
-            props_str = ", ".join([f"{k}: ${k}" for k in receiver.keys()])
-            cypher = f"""
-            MERGE (n:DataResource {{name_zh: $name_zh}})
-            ON CREATE SET n = {{{props_str}}}
-            ON MATCH SET {", ".join([f"n.{k} = ${k}" for k in receiver.keys()])}
-            RETURN n
-            """
-            result = session.run(cypher, receiver)  # type: ignore[arg-type]
-            record = result.single()
-            if not record:
-                raise ValueError("Failed to create or get DataResource node")
-            data_resource_node = record["n"]
-            resource_id = data_resource_node.id  # 使用id属性获取数值ID
-            
-            # 记录创建后的节点数据
-            logger.info(f"创建后的节点数据,describe字段: {data_resource_node.get('describe')}")
-
-            # 处理标签关系
-            if tag_list:
-                tag_node = get_node_by_id('DataLabel', tag_list)
-                if tag_node:
-                    # 检查关系是否存在
-                    rel_check = """
-                    MATCH (a:DataResource)-[r:LABEL]->(b:DataLabel) 
-                    WHERE id(a) = $resource_id AND id(b) = $tag_id
-                    RETURN r
-                    """
-                    rel_result = session.run(rel_check, {'resource_id': resource_id, 'tag_id': tag_node.id})  # 使用数值id
-                    
-                    # 如果关系不存在则创建
-                    if not rel_result.single():
-                        rel_create = """
-                        MATCH (a:DataResource), (b:DataLabel)
-                        WHERE id(a) = $resource_id AND id(b) = $tag_id
-                        CREATE (a)-[r:LABEL]->(b)
-                        RETURN r
-                        """
-                        session.run(rel_create, {'resource_id': resource_id, 'tag_id': tag_node.id})
-            
-            
-            # 处理头部数据(元数据,字段)
-            if head_data:
-                for item in head_data:
-                    # 创建元数据节点
-                    meta_cypher = """
-                    MERGE (m:DataMeta {name_zh: $name_zh})
-                    ON CREATE SET m.name_en = $name_en, 
-                                m.create_time = $create_time,
-                                m.data_type = $data_type,
-                                m.status = true
-                    ON MATCH SET m.data_type = $data_type,
-                                m.status = true
-                    RETURN m
-                    """
-                    
-                    create_time = get_formatted_time()
-                    meta_result = session.run(meta_cypher, {
-                        'name_zh': item['name_zh'],
-                        'name_en': item['name_en'],
-                        'create_time': create_time,
-                        'data_type': item['data_type']  # 使用data_type作为data_type属性
-                    })
-                    meta_record = meta_result.single()
-                    
-                    if meta_record and meta_record["m"]:
-                        meta_node = meta_record["m"]
-                        meta_id = meta_node.id  # 使用数值ID
-                        
-                        # 打印日志确认节点创建成功和ID
-                        logger.info(f"创建或获取到元数据节点: ID={meta_id}, name_zh={item['name_zh']}")
-                        
-                        # 确认数据资源节点是否可以正确查询到
-                        check_resource_cypher = """
-                        MATCH (n:DataResource) 
-                        WHERE id(n) = $resource_id 
-                        RETURN n
-                        """
-                        check_resource = session.run(check_resource_cypher, {'resource_id': resource_id})
-                        if check_resource.single():
-                            logger.info(f"找到数据资源节点: ID={resource_id}")
-                        else:
-                            logger.error(f"无法找到数据资源节点: ID={resource_id}")
-                            continue
-                        
-                        # 创建关系
-                        rel_cypher = """
-                        MATCH (a:DataResource), (m:DataMeta)
-                        WHERE id(a) = $resource_id AND id(m) = $meta_id
-                        MERGE (a)-[r:INCLUDES]->(m)
-                        RETURN r
-                        """
-                        
-                        rel_result = session.run(rel_cypher, {
-                            'resource_id': resource_id,
-                            'meta_id': meta_id
-                        })
-                        
-                        rel_record = rel_result.single()
-                        if rel_record:
-                            logger.info(f"成功创建数据资源与元数据的关系: {resource_id} -> {meta_id}")
-                        else:
-                            logger.warning(f"创建数据资源与元数据的关系失败: {resource_id} -> {meta_id}")
-                    else:
-                        logger.error(f"未能创建或获取元数据节点: {item['name_zh']}")
-            
-            # 处理数据源关系 - 支持所有资源类型
-            if data_source:
-                try:
-                    # 获取数据源节点的标识(支持多种格式)
-                    data_source_id = None
-                    data_source_name_en = None
-                    
-                    # 1. 如果是数字(节点ID)
-                    if isinstance(data_source, (int, float)) or (isinstance(data_source, str) and data_source.isdigit()):
-                        data_source_id = int(data_source)
-                        logger.info(f"data_source 为节点ID: {data_source_id}")
-                    # 2. 如果是字典且包含name_en
-                    elif isinstance(data_source, dict) and data_source.get('name_en'):
-                        data_source_name_en = data_source['name_en']
-                        logger.info(f"data_source 为字典,提取name_en: {data_source_name_en}")
-                    # 3. 如果是字符串(name_en)
-                    elif isinstance(data_source, str):
-                        data_source_name_en = data_source
-                        logger.info(f"data_source 为字符串name_en: {data_source_name_en}")
-                    
-                    # 创建数据资源与数据源的关系
-                    if data_source_id is not None:
-                        # 使用节点ID创建关系
-                        # 首先检查数据源节点是否存在
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE id(b) = $ds_id RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, {'ds_id': data_source_id})
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: ID={data_source_id},跳过关系创建")
-                        else:
-                            # 创建 COME_FROM 关系
-                            rel_data_source_cypher = """
-                            MATCH (a:DataResource), (b:DataSource)
-                            WHERE id(a) = $resource_id AND id(b) = $ds_id
-                            MERGE (a)-[r:COME_FROM]->(b)
-                            RETURN r
-                            """
-                            rel_result = session.run(rel_data_source_cypher, {
-                                'resource_id': resource_id,
-                                'ds_id': data_source_id
-                            })
-                            rel_record = rel_result.single()
-                            
-                            if rel_record:
-                                logger.info(f"已创建数据资源与数据源的COME_FROM关系: resource_id={resource_id} -> data_source_id={data_source_id}")
-                            else:
-                                logger.warning(f"创建COME_FROM关系失败,但不中断主流程: {resource_id} -> {data_source_id}")
-                                
-                    elif data_source_name_en:
-                        # 使用name_en创建关系(兼容旧方式)
-                        # 首先检查数据源节点是否存在
-                        check_ds_cypher = "MATCH (b:DataSource) WHERE b.name_en = $ds_name_en RETURN b"
-                        check_ds_result = session.run(check_ds_cypher, {'ds_name_en': data_source_name_en})
-                        
-                        if not check_ds_result.single():
-                            logger.warning(f"数据源节点不存在: name_en={data_source_name_en},跳过关系创建")
-                        else:
-                            # 创建 COME_FROM 关系
-                            rel_data_source_cypher = """
-                            MATCH (a:DataResource), (b:DataSource)
-                            WHERE id(a) = $resource_id AND b.name_en = $ds_name_en
-                            MERGE (a)-[r:COME_FROM]->(b)
-                            RETURN r
-                            """
-                            rel_result = session.run(rel_data_source_cypher, {
-                                'resource_id': resource_id,
-                                'ds_name_en': data_source_name_en
-                            })
-                            rel_record = rel_result.single()
-                            
-                            if rel_record:
-                                logger.info(f"已创建数据资源与数据源的COME_FROM关系: resource_id={resource_id} -> name_en={data_source_name_en}")
-                            else:
-                                logger.warning(f"创建COME_FROM关系失败,但不中断主流程: {resource_id} -> {data_source_name_en}")
-                    else:
-                        logger.warning(f"data_source参数无效,无法识别格式: {data_source}")
-                        
-                except Exception as e:
-                    # 数据源关系创建失败不应该中断主流程
-                    logger.error(f"处理数据源关系失败(不中断主流程): {str(e)}")
-                    # 不再抛出异常,允许主流程继续
-            
-            # 创建BusinessDomain节点及其关联关系
-            try:
-                domain_id = handle_businessdomain_node(receiver, head_data, data_source, resource_type)
-                logger.info(f"成功创建BusinessDomain节点,ID={domain_id}")
-            except Exception as e:
-                logger.error(f"创建BusinessDomain节点失败(不中断主流程): {str(e)}")
-                # 不抛出异常,允许主流程继续
-            
-            return resource_id
-    except Exception as e:
-        logger.error(f"处理数据资源节点创建和关系建立失败: {str(e)}")
-        raise
-
-    
-
-def handle_id_resource(resource_id):
-    """处理单个数据资源查询"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 确保resource_id为整数
-            try:
-                resource_id_int = int(resource_id)
-            except (ValueError, TypeError):
-                logger.error(f"资源ID不是有效的整数: {resource_id}")
-                return None
-            
-            # 使用数值ID查询
-            cypher = """
-            MATCH (n:DataResource)
-            WHERE id(n) = $resource_id
-            RETURN n
-            """
-            result = session.run(cypher, {'resource_id': resource_id_int})
-            record = result.single()
-            
-            if not record:
-                logger.error(f"未找到资源,ID: {resource_id_int}")
-                return None
-                
-            # 构建返回数据
-            logger.info(f"record: {record}")
-
-            data_resource = serialize_node_properties(record["n"])
-            
-            logger.info(f"data_resource: {data_resource}")
-            logger.info(f"describe field in node: {record['n'].get('describe')}")
-            
-            # 确保describe字段存在,即使为null也记录下来
-            if 'describe' in record["n"]:
-                data_resource["describe"] = record["n"].get('describe')
-                logger.info(f"设置describe字段: {data_resource['describe']}")
-            
-            data_resource["id"] = record["n"].id
-            
-            # 查询关联的标签
-            tag_cypher = """
-            MATCH (n:DataResource)-[r:LABEL]->(t:DataLabel)
-            WHERE id(n) = $resource_id
-            RETURN t
-            """
-            tag_result = session.run(tag_cypher, {'resource_id': resource_id_int})
-            tag_record = tag_result.single()
-            
-            # 设置标签信息
-            if tag_record:
-                tag = {
-                    "name_zh": tag_record["t"].get("name_zh"),
-                    "id": tag_record["t"].id
-                }
-            else:
-                tag = {
-                    "name_zh": None,
-                    "id": None
-                }
-            data_resource["tag"] = tag
-            
-            # 查询关联的数据源(COME_FROM关系)
-            data_source_cypher = """
-            MATCH (n:DataResource)-[r:COME_FROM]->(ds:DataSource)
-            WHERE id(n) = $resource_id
-            RETURN ds
-            """
-            data_source_result = session.run(data_source_cypher, {'resource_id': resource_id_int})
-            data_source_record = data_source_result.single()
-            
-            # 设置数据源信息
-            if data_source_record:
-                data_resource["data_source"] = data_source_record["ds"].id
-                logger.info(f"找到关联的数据源,ID: {data_source_record['ds'].id}")
-            else:
-                data_resource["data_source"] = None
-                logger.info(f"未找到关联的数据源")
-            
-            # 查询关联的元数据 - 支持meta_data和Metadata两种标签
-            meta_cypher = """
-            MATCH (n:DataResource)-[:INCLUDES]->(m)
-            WHERE id(n) = $resource_id
-            AND (m:DataMeta OR m:Metadata)
-            RETURN m
-            """
-            meta_result = session.run(meta_cypher, {'resource_id': resource_id_int})
-            
-            parsed_data = []
-            for meta_record in meta_result:
-                meta = serialize_node_properties(meta_record["m"])
-                meta_data = {
-                    "id": meta_record["m"].id,
-                    "name_zh": meta.get("name_zh"),
-                    "name_en": meta.get("name_en"),
-                    "data_type": meta.get("data_type"),
-                    "data_standard": {
-                        "name_zh": None,
-                        "id": None
-                    }
-                }
-                parsed_data.append(meta_data)
-            
-            data_resource["parsed_data"] = parsed_data
-            
-            # 确保所有必需字段都有默认值
-            required_fields = {
-                "leader": "",
-                "organization": "",
-                "name_zh": "",
-                "name_en": "",
-                "data_sensitivity": "",
-                "storage_location": "/",
-                "create_time": "",
-                "type": "",
-                "category": "",
-                "url": "",
-                "frequency": "",
-                "status": True,
-                "keywords": [],
-                "describe": ""
-            }
-            
-            for field, default_value in required_fields.items():
-                if field not in data_resource or data_resource[field] is None:
-                    data_resource[field] = default_value
-            
-            logger.info(f"成功获取资源详情,ID: {resource_id_int}, describe: {data_resource.get('describe')}")
-            return data_resource
-    except Exception as e:
-        logger.error(f"处理单个数据资源查询失败: {str(e)}")
-        return None
-
-def id_resource_graph(resource_id):
-    """获取数据资源图谱"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 查询数据资源节点及其关系
-            cypher = """
-            MATCH (n:DataResource)-[r]-(m)
-            WHERE id(n) = $resource_id
-            RETURN n, r, m
-            """
-            result = session.run(cypher, {'resource_id': int(resource_id)})
-            
-            # 收集节点和关系
-            nodes = {}
-            relationships = []
-            
-            for record in result:
-                # 处理源节点
-                source_node = serialize_node_properties(record["n"])
-                source_node["id"] = record["n"].id
-                nodes[source_node["id"]] = source_node
-                
-                # 处理目标节点
-                target_node = serialize_node_properties(record["m"])
-                target_node["id"] = record["m"].id
-                nodes[target_node["id"]] = target_node
-                
-                # 处理关系
-                rel = record["r"]
-                relationship = {
-                    "id": rel.id,
-                    "source": record["n"].id,
-                    "target": record["m"].id,
-                    "type": rel.type
-                }
-                relationships.append(relationship)
-            
-            return {
-                "nodes": list(nodes.values()),
-                "relationships": relationships
-            }
-    except Exception as e:
-        logger.error(f"获取数据资源图谱失败: {str(e)}")
-        return {"nodes": [], "relationships": []}
-
-def resource_list(page, page_size, name_en_filter=None, name_zh_filter=None, 
-                 type_filter='all', category_filter=None, tag_filter=None):
-    """获取数据资源列表"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 构建基础过滤条件(针对DataResource节点)
-            resource_conditions = []
-            
-            if name_en_filter:
-                resource_conditions.append(f"n.name_en CONTAINS '{name_en_filter}'")
-                
-            if name_zh_filter:
-                resource_conditions.append(f"n.name_zh CONTAINS '{name_zh_filter}'")
-                
-            if type_filter and type_filter != 'all':
-                resource_conditions.append(f"n.type = '{type_filter}'")
-                
-            if category_filter:
-                resource_conditions.append(f"n.category = '{category_filter}'")
-            
-            # 构建基础WHERE子句
-            resource_where = " AND ".join(resource_conditions) if resource_conditions else ""
-            
-            # 根据是否有tag_filter选择不同的查询策略
-            if tag_filter:
-                # 有标签过滤:先过滤DataResource,再连接标签
-                if resource_where:
-                    # 计算总数
-                    count_cypher = f"""
-                    MATCH (n:DataResource)
-                    WHERE {resource_where}
-                    WITH n
-                    MATCH (n)-[:LABEL]->(t:DataLabel)
-                    WHERE t.name_zh = '{tag_filter}'
-                    RETURN count(DISTINCT n) as count
-                    """
-                    
-                    # 分页查询
-                    skip = (page - 1) * page_size
-                    cypher = f"""
-                    MATCH (n:DataResource)
-                    WHERE {resource_where}
-                    WITH n
-                    MATCH (n)-[:LABEL]->(t:DataLabel)
-                    WHERE t.name_zh = '{tag_filter}'
-                    RETURN DISTINCT n
-                    ORDER BY n.create_time DESC
-                    SKIP {skip} LIMIT {page_size}
-                    """
-                else:
-                    # 只有标签过滤条件
-                    count_cypher = f"""
-                    MATCH (n:DataResource)-[:LABEL]->(t:DataLabel)
-                    WHERE t.name_zh = '{tag_filter}'
-                    RETURN count(DISTINCT n) as count
-                    """
-                    
-                    # 分页查询
-                    skip = (page - 1) * page_size
-                    cypher = f"""
-                    MATCH (n:DataResource)-[:LABEL]->(t:DataLabel)
-                    WHERE t.name_zh = '{tag_filter}'
-                    RETURN DISTINCT n
-                    ORDER BY n.create_time DESC
-                    SKIP {skip} LIMIT {page_size}
-                    """
-            else:
-                # 无标签过滤:标准查询
-                if resource_where:
-                    # 计算总数
-                    count_cypher = f"""
-                    MATCH (n:DataResource)
-                    WHERE {resource_where}
-                    RETURN count(n) as count
-                    """
-                    
-                    # 分页查询
-                    skip = (page - 1) * page_size
-                    cypher = f"""
-                    MATCH (n:DataResource)
-                    WHERE {resource_where}
-                    RETURN n
-                    ORDER BY n.create_time DESC
-                    SKIP {skip} LIMIT {page_size}
-                    """
-                else:
-                    # 无任何过滤条件
-                    count_cypher = "MATCH (n:DataResource) RETURN count(n) as count"
-                    
-                    # 分页查询
-                    skip = (page - 1) * page_size
-                    cypher = f"""
-                    MATCH (n:DataResource)
-                    RETURN n
-                    ORDER BY n.create_time DESC
-                    SKIP {skip} LIMIT {page_size}
-                    """
-            
-            # 执行计数查询
-            count_result = session.run(count_cypher)  # type: ignore[arg-type]
-            count_record = count_result.single()
-            total_count = count_record["count"] if count_record else 0
-            
-            # 执行分页查询
-            result = session.run(cypher)  # type: ignore[arg-type]
-            
-            # 格式化结果
-            resources = []
-            for record in result:
-                node = serialize_node_properties(record["n"])
-                node["id"] = record["n"].id
-                
-                # 查询关联的标签
-                tag_cypher = """
-                MATCH (n:DataResource)-[r:LABEL]->(t:DataLabel)
-                WHERE id(n) = $resource_id
-                RETURN t
-                """
-                tag_result = session.run(tag_cypher, {'resource_id': node["id"]})
-                tag_record = tag_result.single()
-                
-                if tag_record:
-                    tag = serialize_node_properties(tag_record["t"])
-                    tag["id"] = tag_record["t"].id
-                    node["tag_info"] = tag
-                
-                resources.append(node)
-                
-            return resources, total_count
-    except Exception as e:
-        logger.error(f"获取数据资源列表失败: {str(e)}")
-        return [], 0
-
-def id_data_search_list(resource_id, page, page_size, name_en_filter=None,
-                       name_zh_filter=None, category_filter=None, tag_filter=None):
-    """获取特定数据资源关联的元数据列表"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 确保resource_id为整数
-            try:
-                resource_id_int = int(resource_id)
-            except (ValueError, TypeError):
-                logger.error(f"资源ID不是有效的整数: {resource_id}")
-                return [], 0
-            
-            # 基本匹配语句 - 支持meta_data和Metadata标签
-            match_clause = """
-            MATCH (n:DataResource)-[:INCLUDES]->(m)
-            WHERE id(n) = $resource_id
-            AND (m:DataMeta OR m:Metadata)
-            """
-            
-            where_conditions = []
-            
-            if name_en_filter:
-                where_conditions.append(f"m.name_en CONTAINS '{name_en_filter}'")
-                
-            if name_zh_filter:
-                where_conditions.append(f"m.name_zh CONTAINS '{name_zh_filter}'")
-                
-            if category_filter:
-                where_conditions.append(f"m.category = '{category_filter}'")
-                
-            # 标签过滤需要额外的匹配
-            tag_match = ""
-            if tag_filter:
-                tag_match = "MATCH (m)-[:HAS_TAG]->(t:Tag) WHERE t.name_zh = $tag_filter"
-            
-            where_clause = " AND " + " AND ".join(where_conditions) if where_conditions else ""
-            
-            # 计算总数
-            count_cypher = f"""
-            {match_clause}{where_clause}
-            {tag_match}
-            RETURN count(m) as count
-            """
-            count_params = {"resource_id": resource_id_int}
-            if tag_filter:
-                count_params["tag_filter"] = tag_filter
-                
-            count_result = session.run(count_cypher, count_params)
-            count_record = count_result.single()
-            total_count = count_record["count"] if count_record else 0
-            
-            # 分页查询
-            skip = (page - 1) * page_size
-            cypher = f"""
-            {match_clause}{where_clause}
-            {tag_match}
-            RETURN m
-            ORDER BY m.name_zh
-            SKIP {skip} LIMIT {page_size}
-            """
-            
-            result = session.run(cypher, count_params)  # type: ignore[arg-type]
-            
-            # 格式化结果
-            metadata_list = []
-            for record in result:
-                meta = serialize_node_properties(record["m"])
-                meta["id"] = record["m"].id
-                metadata_list.append(meta)
-            
-            logger.info(f"成功获取资源关联元数据,ID: {resource_id_int}, 元数据数量: {total_count}")
-            return metadata_list, total_count
-    except Exception as e:
-        logger.error(f"获取数据资源关联的元数据列表失败: {str(e)}")
-        return [], 0
-
-def resource_kinship_graph(resource_id, include_meta=True):
-    """获取数据资源亲缘关系图谱"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 确保resource_id为整数
-            try:
-                resource_id_int = int(resource_id)
-            except (ValueError, TypeError):
-                logger.error(f"资源ID不是有效的整数: {resource_id}")
-                return {"nodes": [], "relationships": []}
-            
-            # 基本查询
-            cypher_parts = [
-                f"MATCH (n:DataResource) WHERE id(n) = $resource_id",
-                "OPTIONAL MATCH (n)-[:LABEL]->(l:DataLabel)",
-            ]
-            
-            # 是否包含元数据 - 支持meta_data和Metadata两种标签
-            if include_meta:
-                cypher_parts.append("OPTIONAL MATCH (n)-[:INCLUDES]->(m) WHERE (m:DataMeta OR m:Metadata)")
-                
-            cypher_parts.append("RETURN n, l, collect(m) as metadata")
-            
-            cypher = "\n".join(cypher_parts)
-            
-            result = session.run(cypher, {'resource_id': resource_id_int})  # type: ignore[arg-type]
-            record = result.single()
-            
-            if not record:
-                logger.error(f"未找到资源图谱数据,ID: {resource_id_int}")
-                return {"nodes": [], "relationships": []}
-                
-            # 收集节点和关系
-            nodes = {}
-            relationships = []
-            
-            # 处理数据资源节点
-            resource_node = serialize_node_properties(record["n"])
-            resource_node["id"] = record["n"].id
-            resource_node["node_type"] = list(record["n"].labels)
-            nodes[resource_node["id"]] = resource_node
-            
-            # 处理标签节点
-            if record["l"]:
-                label_node = serialize_node_properties(record["l"])
-                label_node["id"] = record["l"].id
-                label_node["node_type"] = list(record["l"].labels)
-                nodes[label_node["id"]] = label_node
-                
-                # 添加资源-标签关系
-                relationships.append({
-                    "id": f"rel-{resource_node['id']}-label-{label_node['id']}",
-                    "from": resource_node["id"],
-                    "to": label_node["id"],
-                    "text": "label"
-                })
-            
-            # 处理元数据节点
-            if include_meta and record["metadata"]:
-                for meta in record["metadata"]:
-                    if meta:  # 检查元数据节点是否存在
-                        meta_node = serialize_node_properties(meta)
-                        meta_node["id"] = meta.id
-                        meta_node["node_type"] = list(meta.labels)
-                        nodes[meta_node["id"]] = meta_node
-                        
-                        # 添加资源-元数据关系
-                        relationships.append({
-                            "id": f"rel-{resource_node['id']}-INCLUDES-{meta_node['id']}",
-                            "from": resource_node["id"],
-                            "to": meta_node["id"],
-                            "text": "INCLUDES"
-                        })
-            
-            logger.info(f"成功获取资源图谱,ID: {resource_id_int}, 节点数: {len(nodes)}")
-            return {
-                "nodes": list(nodes.values()),
-                "relationships": relationships
-            }
-    except Exception as e:
-        logger.error(f"获取数据资源亲缘关系图谱失败: {str(e)}")
-        return {"nodes": [], "relationships": []}
-
-def resource_impact_all_graph(resource_id, include_meta=True):
-    """获取数据资源影响关系图谱"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 确保resource_id为整数
-            try:
-                resource_id_int = int(resource_id)
-            except (ValueError, TypeError):
-                logger.error(f"资源ID不是有效的整数: {resource_id}")
-                return {"nodes": [], "lines": []}
-            
-            # 根据meta参数决定查询深度,限制为一层
-            if include_meta:
-                cypher = """
-                MATCH path = (n:DataResource)-[*1..1]-(m)
-                WHERE id(n) = $resource_id
-                RETURN path
-                """
-            else:
-                cypher = """
-                MATCH path = (n:DataResource)-[*1..1]-(m)
-                WHERE id(n) = $resource_id
-                AND NOT (m:DataMeta) AND NOT (m:Metadata)
-                RETURN path
-                """
-                
-            result = session.run(cypher, {'resource_id': resource_id_int})
-            
-            # 收集节点和关系
-            nodes = {}
-            lines = {}
-            
-            for record in result:
-                path = record["path"]
-                
-                # 处理路径中的所有节点
-                for node in path.nodes:
-                    if node.id not in nodes:
-                        node_dict = serialize_node_properties(node)
-                        node_dict["id"] = str(node.id)
-                        node_dict["node_type"] = list(node.labels)[0] if node.labels else ""
-                        nodes[node.id] = node_dict
-                
-                # 处理路径中的所有关系。Neo4j的路径对象(path)中,关系集合的属性名是relationships
-                for rel in path.relationships:
-                    if rel.id not in lines:
-                        rel_dict = {
-                            "id": str(rel.id),
-                            "from": str(rel.start_node.id),
-                            "to": str(rel.end_node.id),
-                            "text": rel.type
-                        }
-                        lines[rel.id] = rel_dict
-            
-            logger.info(f"成功获取完整图谱,ID: {resource_id_int}, 节点数: {len(nodes)}")
-            return {
-                "nodes": list(nodes.values()),
-                "lines": list(lines.values())
-            }
-    except Exception as e:
-        logger.error(f"获取数据资源影响关系图谱失败: {str(e)}")
-        return {"nodes": [], "lines": []}
-
-def clean_type(type_str):
-    """清洗SQL类型字符串"""
-    # 提取基本类型,不包括长度或精度信息
-    basic_type = re.sub(r'\(.*?\)', '', type_str).strip().upper()
-    
-    # 移除 VARYING 这样的后缀
-    basic_type = re.sub(r'\s+VARYING$', '', basic_type)
-    
-    # 标准化常见类型
-    type_mapping = {
-        'INT': 'INTEGER',
-        'INT4': 'INTEGER',
-        'INT8': 'BIGINT',
-        'SMALLINT': 'SMALLINT',
-        'BIGINT': 'BIGINT',
-        'FLOAT4': 'FLOAT',
-        'FLOAT8': 'DOUBLE',
-        'REAL': 'FLOAT',
-        'DOUBLE PRECISION': 'DOUBLE',
-        'NUMERIC': 'DECIMAL',
-        'BOOL': 'BOOLEAN',
-        'CHARACTER': 'CHAR',
-        'CHAR VARYING': 'VARCHAR',
-        'CHARACTER VARYING': 'VARCHAR',
-        'TEXT': 'TEXT',
-        'DATE': 'DATE',
-        'TIME': 'TIME',
-        'TIMESTAMP': 'TIMESTAMP',
-        'TIMESTAMPTZ': 'TIMESTAMP WITH TIME ZONE',
-        'BYTEA': 'BINARY',
-        'JSON': 'JSON',
-        'JSONB': 'JSONB',
-        'UUID': 'UUID',
-        'SERIAL': 'SERIAL',
-        'SERIAL4': 'SERIAL',
-        'SERIAL8': 'BIGSERIAL',
-        'BIGSERIAL': 'BIGSERIAL'
-    }
-    
-    # 尝试从映射表中获取标准化的类型
-    return type_mapping.get(basic_type, basic_type)
-
-def clean_field_name(field_name):
-    """清洗字段名"""
-    return field_name.strip('`').strip('"').strip("'")
-
-def select_create_ddl(sql_content):
-    """从SQL内容中提取创建表的DDL语句"""
-    try:
-        # 解析复杂的SQL文件,识别所有的CREATE TABLE语句及其关联的注释
-        # 找到所有以CREATE TABLE开头的语句块,每个语句块包含主语句和相关的注释
-        
-        # 首先,分割 SQL 内容按分号
-        statements = []
-        current_statement = ""
-        in_string = False
-        string_quote = None
-        
-        for char in sql_content:
-            if char in ["'", '"']:
-                if not in_string:
-                    in_string = True
-                    string_quote = char
-                elif char == string_quote:
-                    in_string = False
-                    string_quote = None
-                current_statement += char
-            elif char == ';' and not in_string:
-                current_statement += char
-                if current_statement.strip():
-                    statements.append(current_statement.strip())
-                current_statement = ""
-            else:
-                current_statement += char
-        
-        if current_statement.strip():
-            statements.append(current_statement.strip())
-        
-        # 找出所有的CREATE TABLE语句和关联的注释
-        create_table_statements = []
-        create_index = -1
-        in_table_block = False
-        current_table = None
-        current_block = ""
-        
-        for i, stmt in enumerate(statements):
-            if re.search(r'^\s*CREATE\s+TABLE', stmt, re.IGNORECASE):
-                # 如果已经在处理表,先保存当前块
-                if in_table_block and current_block:
-                    create_table_statements.append(current_block)
-                    
-                # 开始新的表块
-                in_table_block = True
-                current_block = stmt
-                
-                # 提取表名
-                table_match = re.search(r'CREATE\s+TABLE\s+(?:(?:"[^"]+"|\'[^\']+\'|[^"\'\s\.]+)\.)?(?:"([^"]+)"|\'([^\']+)\'|([^"\'\s\(]+))', stmt, re.IGNORECASE)
-                if table_match:
-                    current_table = table_match.group(1) or table_match.group(2) or table_match.group(3)
-                    current_table = current_table.strip('"\'') if current_table else ""
-            
-            elif in_table_block and (re.search(r'COMMENT\s+ON\s+TABLE', stmt, re.IGNORECASE) or 
-                                   re.search(r'COMMENT\s+ON\s+COLUMN', stmt, re.IGNORECASE)):
-                # 检查注释是否属于当前表
-                if current_table:
-                    # 表注释处理
-                    if re.search(r'COMMENT\s+ON\s+TABLE', stmt, re.IGNORECASE):
-                        table_comment_match = re.search(r'COMMENT\s+ON\s+TABLE\s+[\'"]?(\w+)[\'"]?', stmt, re.IGNORECASE)
-                        if table_comment_match:
-                            comment_table = table_comment_match.group(1).strip('"\'')
-                            if comment_table == current_table:
-                                current_block += " " + stmt
-                            else:
-                                # 这是另一个表的注释,当前表的DDL到此结束
-                                create_table_statements.append(current_block)
-                                in_table_block = False
-                                current_block = ""
-                                current_table = None
-                    # 列注释处理
-                    elif re.search(r'COMMENT\s+ON\s+COLUMN', stmt, re.IGNORECASE):
-                        column_comment_match = re.search(
-                            r'COMMENT\s+ON\s+COLUMN\s+[\'"]?(\w+)[\'"]?\.[\'"]?(\w+)[\'"]?\s+IS\s+\'([^\']+)\'',
-                            stmt,
-                            re.IGNORECASE
-                        )
-                        if column_comment_match:
-                            comment_table = column_comment_match.group(1)
-                            if comment_table == current_table:
-                                current_block += " " + stmt
-                            else:
-                                # 这是另一个表的注释,当前表的DDL到此结束
-                                create_table_statements.append(current_block)
-                                in_table_block = False
-                                current_block = ""
-                                current_table = None
-            
-            elif in_table_block and re.search(r'^\s*CREATE\s+', stmt, re.IGNORECASE):
-                # 如果遇到新的CREATE语句(不是注释),保存当前块并结束
-                create_table_statements.append(current_block)
-                in_table_block = False
-                current_block = ""
-                current_table = None
-        
-        # 添加最后一个块
-        if in_table_block and current_block:
-            create_table_statements.append(current_block)
-        
-        # 日志记录
-        logger.debug(f"提取到 {len(create_table_statements)} 个DDL语句")
-        for i, stmt in enumerate(create_table_statements):
-            logger.debug(f"DDL语句 {i+1}: {stmt}")
-            
-        return create_table_statements
-        
-    except Exception as e:
-        logger.error(f"提取DDL语句失败: {str(e)}")
-       # logger.error(traceback.format_exc())
-        return []
-
-def table_sql(sql):
-    """解析表定义SQL,支持带schema和不带schema两种格式"""
-    try:
-        # 支持以下格式:
-        # 1. CREATE TABLE tablename
-        # 2. CREATE TABLE "tablename"
-        # 3. CREATE TABLE 'tablename'
-        # 4. CREATE TABLE schema.tablename
-        # 5. CREATE TABLE "schema"."tablename"
-        # 6. CREATE TABLE 'schema'.'tablename'
-        # 匹配表名,支持带引号和不带引号的情况
-        table_pattern = r'CREATE\s+TABLE\s+(?:(?:"([^"]+)"|\'([^\']+)\'|([^"\'\s\.]+))\.)?(?:"([^"]+)"|\'([^\']+)\'|([^"\'\s\(]+))'
-        table_match = re.search(table_pattern, sql, re.IGNORECASE)
-        
-        if not table_match:
-            logger.error(f"无法匹配CREATE TABLE语句: {sql[:100]}...")
-            return None
-            
-        # 获取表名
-        schema = table_match.group(1) or table_match.group(2) or table_match.group(3)
-        table_name = table_match.group(4) or table_match.group(5) or table_match.group(6)
-        if not table_name:
-            logger.error("无法解析表名")
-            return None
-            
-        logger.debug(f"解析到表名: {table_name}")
-        
-        # 提取CREATE TABLE语句的主体部分(括号内的内容)
-        body_pattern = r'CREATE\s+TABLE\s+[^(]*\((.*?)\)(?=\s*;|\s*$)'
-        body_match = re.search(body_pattern, sql, re.DOTALL | re.IGNORECASE)
-        
-        if not body_match:
-            logger.error("无法提取表主体内容")
-            return None
-            
-        body_text = body_match.group(1).strip()
-        logger.debug(f"表定义主体部分: {body_text}")
-        
-        # 解析字段定义
-        fields = []
-        # 分割字段定义,处理括号嵌套和引号
-        field_defs = []
-        pos = 0
-        in_parentheses = 0
-        in_quotes = False
-        quote_char = None
-        
-        for i, char in enumerate(body_text):
-            if char in ["'", '"', '`'] and (not in_quotes or char == quote_char):
-                in_quotes = not in_quotes
-                if in_quotes:
-                    quote_char = char
-                else:
-                    quote_char = None
-            elif char == '(' and not in_quotes:
-                in_parentheses += 1
-            elif char == ')' and not in_quotes:
-                in_parentheses -= 1
-            elif char == ',' and in_parentheses == 0 and not in_quotes:
-                field_defs.append(body_text[pos:i].strip())
-                pos = i + 1
-                
-        # 添加最后一个字段定义
-        if pos < len(body_text):
-            field_defs.append(body_text[pos:].strip())
-            
-        logger.debug(f"解析出 {len(field_defs)} 个字段定义")
-        
-        # 处理每个字段定义
-        for field_def in field_defs:
-            # 跳过约束定义
-            if re.match(r'^\s*(?:PRIMARY|UNIQUE|FOREIGN|CHECK|CONSTRAINT)\s+', field_def, re.IGNORECASE):
-                continue
-                
-            # 提取字段名和类型
-            field_pattern = r'^\s*(?:"([^"]+)"|\'([^\']+)\'|`([^`]+)`|([a-zA-Z0-9_]+))\s+(.+?)(?:\s+DEFAULT\s+|\s+NOT\s+NULL|\s+REFERENCES|\s*$)'
-            field_match = re.search(field_pattern, field_def, re.IGNORECASE)
-            
-            if field_match:
-                # 提取字段名
-                field_name = field_match.group(1) or field_match.group(2) or field_match.group(3) or field_match.group(4)
-                # 提取类型
-                field_type = field_match.group(5).strip()
-                
-                # 处理类型中可能的括号
-                type_base = re.split(r'\s+', field_type)[0]
-                clean_type_value = clean_type(type_base)
-                
-                fields.append((field_name, clean_type_value))
-                logger.debug(f"解析到字段: {field_name}, 类型: {clean_type_value}")
-            else:
-                logger.warning(f"无法解析字段定义: {field_def}")
-        
-        # 提取表注释
-        table_comment = ""
-        table_comment_pattern = r"COMMENT\s+ON\s+TABLE\s+(?:['\"]?(\w+)['\"]?)\s+IS\s+'([^']+)'"
-        table_comment_match = re.search(table_comment_pattern, sql, re.IGNORECASE)
-        
-        if table_comment_match:
-            comment_table = table_comment_match.group(1)
-            if comment_table.strip("'\"") == table_name.strip("'\""):
-                table_comment = table_comment_match.group(2)
-                logger.debug(f"找到表注释: {table_comment}")
-            
-        # 提取列注释
-        comments = {}
-        column_comment_pattern = r"COMMENT\s+ON\s+COLUMN\s+['\"]?(\w+)['\"]?\.['\"]?(\w+)['\"]?\s+IS\s+'([^']+)'"
-        
-        for match in re.finditer(column_comment_pattern, sql, re.IGNORECASE):
-            comment_table = match.group(1)
-            column_name = match.group(2)
-            comment = match.group(3)
-            
-            # 检查表名是否匹配
-            if comment_table.strip("'\"") == table_name.strip("'\""):
-                comments[column_name] = comment
-                logger.debug(f"找到列注释: {column_name} - {comment}")
-            else:
-                logger.debug(f"忽略列注释,表名不匹配: {comment_table} vs {table_name}")
-        
-        # 检查字段和注释匹配情况
-        logger.debug("========字段和注释匹配情况========")
-        field_names = [f[0] for f in fields]
-        logger.debug(f"字段列表 ({len(field_names)}): {field_names}")
-        logger.debug(f"注释字段 ({len(comments)}): {list(comments.keys())}")
-        
-        # 构建返回结果
-        meta_list = []
-        for field_name, field_type in fields:
-            chinese_name = comments.get(field_name, "")
-            meta_list.append({
-                "name_en": field_name,
-                "data_type": field_type,
-                "name_zh": chinese_name if chinese_name else field_name
-            })
-            
-        # 检查表是否存在
-        try:
-            status = status_query([table_name])
-        except Exception as e:
-            logger.error(f"检查表存在状态失败: {str(e)}")
-            status = [False]
-            
-        # 构建返回结果
-        result = {
-            table_name: {
-                "exist": status[0] if status else False,
-                "meta": meta_list
-            }
-        }
-        
-        logger.debug(f"解析结果: {json.dumps(result, ensure_ascii=False)}")
-        return result
-        
-    except Exception as e:
-        logger.error(f"解析表定义SQL失败: {str(e)}")
-        logger.error(f"异常详情: {e}")
-        import traceback
-        logger.error(traceback.format_exc())
-        return None
-
-# 判断英文表名是否在图谱中存在
-def status_query(key_list):
-    query = """
-    unwind $Key_list as name
-    OPTIONAL MATCH (n:DataModel {name_en: name})
-    OPTIONAL MATCH (n:DataResource {name_en: name})
-         OPTIONAL MATCH (n:DataMetric {name_en: name})
-    WITH name, CASE 
-        WHEN n IS NOT NULL THEN True
-        ELSE False
-    END AS exist
-    return collect(exist)AS exist
-    """
-    with neo4j_driver.get_session() as session:
-        result = session.run(query, {'Key_list': key_list})
-        data = result.value() # 获取单个值
-    return data
-
-
-def select_sql(sql_query):
-    """解析SELECT查询语句"""
-    try:
-        # 提取SELECT子句
-        select_pattern = r'SELECT\s+(.*?)\s+FROM'
-        select_match = re.search(select_pattern, sql_query, re.IGNORECASE | re.DOTALL)
-        
-        if not select_match:
-            return None
-            
-        select_clause = select_match.group(1)
-        
-        # 分割字段
-        fields = []
-        
-        # 处理字段列表,避免在函数调用中的逗号导致错误分割
-        in_parenthesis = 0
-        current_field = ""
-        
-        for char in select_clause:
-            if char == '(':
-                in_parenthesis += 1
-                current_field += char
-            elif char == ')':
-                in_parenthesis -= 1
-                current_field += char
-            elif char == ',' and in_parenthesis == 0:
-                fields.append(current_field.strip())
-                current_field = ""
-            else:
-                current_field += char
-                
-        if current_field.strip():
-            fields.append(current_field.strip())
-        
-        # 解析每个字段
-        parsed_fields = []
-        
-        for field in fields:
-            # 检查是否有字段别名
-            alias_pattern = r'(.*?)\s+[aA][sS]\s+(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))$'
-            alias_match = re.search(alias_pattern, field)
-            
-            if alias_match:
-                field_expr = alias_match.group(1).strip()
-                field_alias = next((g for g in alias_match.groups()[1:] if g is not None), "")
-                
-                parsed_fields.append({
-                    "expression": field_expr,
-                    "alias": field_alias
-                })
-            else:
-                # 没有别名的情况
-                parsed_fields.append({
-                    "expression": field.strip(),
-                    "alias": None
-                })
-        
-        # 提取FROM子句和表名
-        from_pattern = r'FROM\s+(.*?)(?:\s+WHERE|\s+GROUP|\s+HAVING|\s+ORDER|\s+LIMIT|$)'
-        from_match = re.search(from_pattern, sql_query, re.IGNORECASE | re.DOTALL)
-        
-        tables = []
-        if from_match:
-            from_clause = from_match.group(1).strip()
-            
-            # 分析FROM子句中的表
-            table_pattern = r'(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))(?:\s+(?:AS\s+)?(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))?'
-            for match in re.finditer(table_pattern, from_clause):
-                table_name = match.group(1) or match.group(2) or match.group(3) or match.group(4)
-                if table_name:
-                    tables.append(table_name)
-        
-        return tables
-        
-    except Exception as e:
-        logger.error(f"解析SELECT查询语句失败: {str(e)}")
-       # logger.error(traceback.format_exc())
-        return None
-
-def model_resource_list(page, page_size, name_filter=None):
-    """获取模型资源列表"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 构建查询条件
-            match_clause = "MATCH (n:model_resource)"
-            where_clause = ""
-            
-            if name_filter:
-                where_clause = f" WHERE n.name_zh CONTAINS '{name_filter}'"
-            
-            # 计算总数
-            count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
-            count_result = session.run(count_cypher)  # type: ignore[arg-type]
-            count_record = count_result.single()
-            total_count = count_record["count"] if count_record else 0
-            
-            # 分页查询
-            skip = (page - 1) * page_size
-            cypher = f"""
-            {match_clause}{where_clause}
-            RETURN n
-            ORDER BY n.create_time DESC
-            SKIP {skip} LIMIT {page_size}
-            """
-            result = session.run(cypher)  # type: ignore[arg-type]
-            
-            # 格式化结果
-            resources = []
-            for record in result:
-                node = serialize_node_properties(record["n"])
-                node["id"] = record["n"].id
-                resources.append(node)
-                
-            return resources, total_count
-    except Exception as e:
-        logger.error(f"获取模型资源列表失败: {str(e)}")
-        return [], 0
-
-def data_resource_edit(data):
-    """编辑数据资源"""
-    try:
-        resource_id = data.get("id")
-        if not resource_id:
-            raise ValueError("缺少资源ID")
-            
-        with neo4j_driver.get_session() as session:
-            # 更新节点属性
-            update_fields = {}
-            for key, value in data.items():
-                if key != "id" and key != "parsed_data" and value is not None:
-                    update_fields[key] = value
-            
-            # 记录describe字段是否存在于待更新数据中
-            if "describe" in data:
-                logger.info(f"编辑资源,describe字段将被更新为: {data.get('describe')}")
-            else:
-                logger.info("编辑资源,describe字段不在更新数据中")
-            
-            # 添加更新时间
-            update_fields["create_time"] = get_formatted_time()
-            
-            # 构建更新语句,确保至少有 updateTime 字段要更新
-            if update_fields:
-                set_clause = ", ".join([f"n.{k} = ${k}" for k in update_fields.keys()])
-                cypher = f"""
-                MATCH (n:DataResource)
-                WHERE id(n) = $resource_id
-                SET {set_clause}
-                RETURN n
-                """
-                params = {'resource_id': int(resource_id)}
-                params.update(update_fields)
-                result = session.run(cypher, params)  # type: ignore[arg-type]
-            else:
-                # 如果没有字段需要更新,只查询节点
-                cypher = """
-                MATCH (n:DataResource)
-                WHERE id(n) = $resource_id
-                RETURN n
-                """
-                result = session.run(cypher, {'resource_id': int(resource_id)})
-            
-            updated_node = result.single()
-            
-            if not updated_node:
-                raise ValueError("资源不存在")
-            
-            # 记录更新后的节点数据
-            logger.info(f"更新后的节点数据,describe字段: {updated_node['n'].get('describe')}")
-            
-            # 处理标签关系
-            tag_id = data.get("tag")
-            if tag_id:
-                # 删除旧的标签关系
-                delete_rel_cypher = """
-                MATCH (n:DataResource)-[r:LABEL]->()
-                WHERE id(n) = $resource_id
-                DELETE r
-                """
-                session.run(delete_rel_cypher, {'resource_id': int(resource_id)})
-                
-                # 创建新的标签关系
-                create_rel_cypher = """
-                MATCH (n:DataResource), (t:DataLabel)
-                WHERE id(n) = $resource_id AND id(t) = $tag_id
-                CREATE (n)-[r:LABEL]->(t)
-                RETURN r
-                """
-                session.run(create_rel_cypher, {'resource_id': int(resource_id), 'tag_id': int(tag_id)})
-            
-            # 处理元数据关系
-            parsed_data = data.get("parsed_data", [])
-            
-            # 首先删除旧的元数据关系和清洗资源关系(无论parsed_data是否为空都要执行)
-            delete_meta_cypher = """
-            MATCH (n:DataResource)-[r:INCLUDES]->()
-            WHERE id(n) = $resource_id
-            DELETE r
-            """
-            session.run(delete_meta_cypher, {'resource_id': int(resource_id)})
-            
-            delete_clean_cypher = """
-            MATCH (n:DataResource)-[r:clean_resource]->()
-            WHERE id(n) = $resource_id
-            DELETE r
-            """
-            session.run(delete_clean_cypher, {'resource_id': int(resource_id)})
-            
-            # 根据parsed_data是否为空来决定是否执行预处理和关系新建操作
-            if parsed_data:
-                # 预处理 parsed_data,确保每个 metadata 都有有效的 ID
-                for meta in parsed_data:
-                    meta_id = meta.get("id")
-                    meta_name = meta.get("name_zh")
-                    
-                    if not meta_id and meta_name:
-                        # 如果没有 ID 但有 name_zh,先根据 name_zh 查找是否存在对应的 DataMeta 节点
-                        find_meta_cypher = """
-                        MATCH (m:DataMeta {name_zh: $meta_name})
-                        RETURN m
-                        """
-                        find_result = session.run(find_meta_cypher, {'meta_name': meta_name})
-                        existing_meta = find_result.single()
-                        
-                        if existing_meta:
-                            # 如果找到了,使用现有的 ID
-                            meta_id = existing_meta["m"].id
-                            meta["id"] = meta_id
-                            logger.info(f"找到现有的DataMeta节点: {meta_name}, ID: {meta_id}")
-                        else:
-                            # 如果没有找到,创建新的 DataMeta 节点
-                            create_meta_cypher = """
-                            CREATE (m:DataMeta {
-                                name_zh: $name_zh,
-                                name_en: $name_en,
-                                data_type: $data_type,
-                                create_time: $create_time
-                            })
-                            RETURN m
-                            """
-                            create_time = get_formatted_time()
-                            new_meta_result = session.run(create_meta_cypher, {
-                                'name_zh': meta_name,
-                                'name_en': meta.get("name_en", meta_name),
-                                'data_type': meta.get("data_type", "varchar(255)"),
-                                'create_time': create_time
-                            })
-                            new_meta = new_meta_result.single()
-                            if new_meta:
-                                meta_id = new_meta["m"].id
-                                meta["id"] = meta_id
-                                logger.info(f"创建新的DataMeta节点: {meta_name}, ID: {meta_id}")
-                            else:
-                                logger.error(f"创建DataMeta节点失败: {meta_name}")
-                                continue
-                    elif not meta_id:
-                        logger.warning(f"跳过没有ID和name的metadata: {meta}")
-                        continue
-                
-                # 创建新的元数据关系和相关关系
-                for meta in parsed_data:
-                    meta_id = meta.get("id")
-                    if meta_id:
-                        # 创建元数据关系
-                        create_meta_cypher = """
-                        MATCH (n:DataResource), (m:DataMeta)
-                        WHERE id(n) = $resource_id AND id(m) = $meta_id
-                        CREATE (n)-[r:INCLUDES]->(m)
-                        RETURN r
-                        """
-                        session.run(create_meta_cypher, {'resource_id': int(resource_id), 'meta_id': int(meta_id)})
-                        
-                        # 处理主数据关系
-                        master_data = meta.get("master_data")
-                        if master_data:
-                            # 创建主数据关系
-                            create_master_cypher = """
-                            MATCH (master), (meta:DataMeta)
-                            WHERE id(master) = $master_id AND id(meta) = $meta_id
-                            MERGE (master)-[r:master]->(meta)
-                            RETURN r
-                            """
-                            session.run(create_master_cypher, {'master_id': int(master_data), 'meta_id': int(meta_id)})
-                        
-                        # 处理数据标准关系
-                        data_standard = meta.get("data_standard")
-                        if data_standard and isinstance(data_standard, dict):
-                            standard_id = data_standard.get("id")
-                            if standard_id:
-                                # 创建数据标准与元数据的关系
-                                create_standard_meta_cypher = """
-                                MATCH (standard), (meta:DataMeta)
-                                WHERE id(standard) = $standard_id AND id(meta) = $meta_id
-                                MERGE (standard)-[r:clean_resource]->(meta)
-                                RETURN r
-                                """
-                                session.run(create_standard_meta_cypher, {'standard_id': int(standard_id), 'meta_id': int(meta_id)})
-                                
-                                # 创建数据资源与数据标准的关系
-                                create_resource_standard_cypher = """
-                                MATCH (resource:DataResource), (standard)
-                                WHERE id(resource) = $resource_id AND id(standard) = $standard_id
-                                MERGE (resource)-[r:clean_resource]->(standard)
-                                RETURN r
-                                """
-                                session.run(create_resource_standard_cypher, {'resource_id': int(resource_id), 'standard_id': int(standard_id)})
-            else:
-                logger.info(f"parsed_data为空,只删除旧的元数据关系,不创建新的关系")
-            
-            # 返回更新后的节点
-            node_data = serialize_node_properties(updated_node["n"])
-            node_data["id"] = updated_node["n"].id
-            
-            # 记录最终返回的describe字段
-            logger.info(f"data_resource_edit返回数据,describe字段: {node_data.get('describe')}")
-            
-            return node_data
-    except Exception as e:
-        logger.error(f"编辑数据资源失败: {str(e)}")
-        raise
-
-def handle_data_source(data_source):
-    """处理数据源信息,创建或获取数据源节点"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 获取英文名称作为唯一标识
-            ds_name_en = data_source.get("name_en")
-            if not ds_name_en:
-                logger.error("数据源缺少必要的name_en属性")
-                return None
-                
-            # 如果没有设置name_zh,使用name_en作为name_zh
-            if "name_zh" not in data_source or not data_source["name_zh"]:
-                data_source["name_zh"] = ds_name_en
-            
-            # 检查必填字段
-            required_fields = ["type", "host", "port", "database", "username"]
-            has_required_fields = all(data_source.get(field) for field in required_fields)
-            
-            # 查询是否已存在相同name_en的数据源
-            existing_cypher = """
-            MATCH (ds:DataSource {name_en: $name_en})
-            RETURN ds
-            """
-            
-            existing_result = session.run(existing_cypher, {'name_en': ds_name_en})
-            existing_record = existing_result.single()
-            
-            if existing_record:
-                existing_data_source = serialize_node_properties(existing_record["ds"])
-                logger.info(f"根据名称找到现有数据源: {existing_data_source.get('name_en')}")
-                return existing_data_source.get("name_en")
-            else:
-                # 数据源不存在,抛出异常
-                raise ValueError(f"未找到名称为 {ds_name_en} 的数据源,请先创建该数据源或提供完整的数据源信息")
-            
-    except Exception as e:
-        logger.error(f"处理数据源失败: {str(e)}")
-        raise RuntimeError(f"处理数据源失败: {str(e)}")

+ 0 - 57
app/core/production_line/README.md

@@ -1,57 +0,0 @@
-# 生产线核心功能模块
-
-本模块包含生产线相关的核心业务逻辑功能实现。
-
-## 功能概述
-
-生产线核心模块主要负责数据模型、数据资源和数据指标之间的关系构建与可视化,提供图形化展示和处理功能。
-
-## 主要功能
-
-### 生产线图谱绘制 (production_draw_graph)
-
-根据节点ID和类型绘制生产线图谱,支持以下三种类型的节点:
-- 数据模型(DataModel):处理数据模型与元数据节点及数据标准之间的关系
-- 数据资源(DataResource):处理数据资源与元数据节点及数据标准之间的关系
-- 数据指标(DataMetric):处理数据指标与元数据节点之间的关系
-
-## 技术实现
-
-本模块主要使用Neo4j图数据库进行数据关系的查询与处理,通过Cypher查询语言构建复杂的图谱关系。主要关系类型包括:
-
-- connection:连接关系
-- clean_model:模型清洗关系
-- clean_resource:资源清洗关系
-
-## 使用方法
-
-```python
-from app.core.production_line import production_draw_graph
-
-# 获取数据模型的图谱
-graph_data = production_draw_graph(model_id, "DataModel")
-
-# 获取数据资源的图谱
-graph_data = production_draw_graph(resource_id, "DataResource")
-
-# 获取数据指标的图谱
-graph_data = production_draw_graph(metric_id, "DataMetric")
-```
-
-## 返回数据结构
-
-图谱数据的格式为:
-
-```python
-{
-    "nodes": [
-        {"id": "节点ID", "text": "节点名称", "type": "节点类型"},
-        # 更多节点...
-    ],
-    "lines": [
-        {"from": "起始节点ID", "to": "目标节点ID", "text": "关系描述"},
-        # 更多连线...
-    ],
-    "rootId": "根节点ID"
-}
-``` 

+ 0 - 10
app/core/production_line/__init__.py

@@ -1,10 +0,0 @@
-"""
-Production Line core functionality module
-包含生产线相关的核心业务逻辑
-"""
-
-from app.core.production_line.production_line import production_draw_graph
-
-__all__ = [
-    'production_draw_graph',
-] 

+ 0 - 1238
app/core/production_line/production_line.py

@@ -1,1238 +0,0 @@
-from app.core.graph.graph_operations import connect_graph
-from flask import current_app
-import os
-import pandas as pd
-from datetime import datetime
-import psycopg2
-from psycopg2 import sql
-import logging
-from app.services.neo4j_driver import neo4j_driver
-import shutil
-import re
-from psycopg2.extras import execute_values
-import time
-from urllib.parse import urlparse, unquote, quote
-
-def production_draw_graph(id, type):
-    """
-    根据节点ID和类型绘制生产线图谱
-    
-    Args:
-        id: 节点ID
-        type: 节点类型(DataModel, DataResource, DataMetric)
-        
-    Returns:
-        dict: 包含节点、连线和根节点ID的图谱数据
-    """
-    # 获取Neo4j连接(如果连接失败会抛出ConnectionError异常)
-    driver = None
-    try:
-        driver = connect_graph()
-    except (ConnectionError, ValueError) as e:
-        logger.error(f"无法连接到Neo4j数据库: {str(e)}")
-        return {"nodes": [], "lines": [], "rootId": "", "error": "无法连接到数据库"}
-    
-    try:
-        # 首先验证节点是否存在
-        with driver.session() as session:
-            check_node_query = """
-            MATCH (n) 
-            WHERE id(n) = $nodeId 
-            RETURN n, labels(n) as labels, n.name_zh as name_zh
-            """
-            check_result = session.run(check_node_query, nodeId=id).single()
-            
-            if not check_result:
-                logger.error(f"节点不存在: ID={id}")
-                return {"nodes": [], "lines": [], "rootId": "", "error": "节点不存在"}
-            
-            actual_type = check_result["labels"][0]  # 获取实际的节点类型
-            node_name = check_result["name_zh"]
-            
-            # 如果提供的类型与实际类型不匹配,使用实际类型
-            if type.lower() != actual_type.lower():
-                logger.warning(f"提供的类型({type})与实际类型({actual_type})不匹配,使用实际类型")
-                type = actual_type
-        
-        # 数据模型
-        if type.lower() == "DataModel":
-            cql = """
-            MATCH (n:DataModel)
-            WHERE id(n) = $nodeId
-            OPTIONAL MATCH (n)-[r:connection]-(m:meta_node)
-            OPTIONAL MATCH (n)-[r2:clean_model]-(d:data_standard)
-            OPTIONAL MATCH (d)-[r3:clean_model]-(m)
-            WITH 
-                 collect({from: toString(id(n)), to: toString(id(m)), text: "包含"}) AS line1,
-                 collect({from: toString(id(n)), to: toString(id(d)), text: "清洗"}) AS line2,
-                 collect({from: toString(id(d)), to: toString(id(m)), text: "清洗"}) AS line3,
-                 collect({id: toString(id(n)), text: n.name_zh, type: "model"}) AS node1,
-                 collect({id: toString(id(m)), text: m.name}) AS node2,
-                 collect({id: toString(id(d)), text: d.name, type: "standard"}) AS node3,n
-            WITH apoc.coll.toSet(line1 + line2 + line3) AS lines,
-                 apoc.coll.toSet(node1 + node2 + node3) AS nodes,
-                 toString(id(n)) as res
-            RETURN lines,nodes,res
-            """
-        # 数据资源
-        elif type.lower() == "DataResource":
-            cql = """
-            MATCH (n:DataResource)
-            WHERE id(n) = $nodeId
-            OPTIONAL MATCH (n)-[r:connection]-(m:meta_node)
-            OPTIONAL MATCH (n)-[r2:clean_resource]-(d:data_standard)
-            OPTIONAL MATCH (d)-[r3:clean_resource]-(m)
-            WITH 
-                collect({from: toString(id(n)), to: toString(id(m)), text: "包含"}) AS lines1,
-                collect({from: toString(id(n)), to: toString(id(d)), text: "清洗"}) AS lines2,
-                collect({from: toString(id(d)), to: toString(id(m)), text: "清洗"}) AS lines3,
-                collect({id: toString(id(n)), text: n.name_zh, type: "resource"}) AS nodes1,
-                collect({id: toString(id(m)), text: m.name}) AS nodes2,
-                collect({id: toString(id(d)), text: d.name, type: "standard"}) AS nodes3,n     
-            WITH 
-                apoc.coll.toSet(lines1 + lines2 + lines3) AS lines,
-                apoc.coll.toSet(nodes1 + nodes2 + nodes3) AS nodes,
-                toString(id(n)) AS res       
-            RETURN lines, nodes, res
-            """
-        # 数据指标
-        elif type.lower() == "DataMetric":
-            cql = """
-            MATCH (n:DataMetric)
-            WHERE id(n) = $nodeId
-            OPTIONAL MATCH (n)-[r:connection]-(m:meta_node)
-            WITH collect({from: toString(id(n)), to: toString(id(m)), text: "处理"}) AS line1,
-                collect({id: toString(id(n)), text: n.name_zh, type: "metric"}) AS node1,
-                collect({id: toString(id(m)), text: m.name}) AS node2,n
-            WITH apoc.coll.toSet(line1) AS lines,
-                apoc.coll.toSet(node1 + node2) AS nodes,
-                toString(id(n)) as res
-            RETURN lines,nodes,res
-            """
-        else:
-            # 处理未知节点类型
-            cql = """
-            MATCH (n)
-            WHERE id(n) = $nodeId
-            OPTIONAL MATCH (n)-[r]-(m)
-            WITH collect({from: toString(id(n)), to: toString(id(m)), text: type(r)}) AS lines,
-                 collect({id: toString(id(n)), text: n.name_zh, type: labels(n)[0]}) AS nodes1,
-                 collect({id: toString(id(m)), text: m.name, type: labels(m)[0]}) AS nodes2,
-                 toString(id(n)) as res
-            RETURN apoc.coll.toSet(lines) AS lines, 
-                   apoc.coll.toSet(nodes1 + nodes2) AS nodes, 
-                   res
-            """
-            
-        with driver.session() as session:
-            try:
-                result = session.run(cql, nodeId=id)
-                data = result.data()
-                
-                # 如果没有数据,返回节点自身
-                if not data:
-                    return {
-                        "nodes": [{"id": str(id), "text": node_name, "type": type}],
-                        "lines": [],
-                        "rootId": str(id)
-                    }
-                
-                res = {}
-                for item in data:
-                    res = {
-                        "nodes": [record for record in item['nodes'] if record.get('id')],
-                        "lines": [record for record in item['lines'] if record.get('from') and record.get('to')],
-                        "rootId": item['res'],
-                    }
-                
-                # 确保节点列表不为空
-                if not res.get("nodes"):
-                    res["nodes"] = [{"id": str(id), "text": node_name, "type": type}]
-                
-                return res
-            except Exception as e:
-                logger.error(f"执行图谱查询失败: {str(e)}")
-                return {
-                    "nodes": [{"id": str(id), "text": node_name, "type": type}],
-                    "lines": [],
-                    "rootId": str(id),
-                    "error": f"查询执行失败: {str(e)}"
-                }
-                
-    except Exception as e:
-        logger.error(f"生成图谱失败: {str(e)}")
-        return {"nodes": [], "lines": [], "rootId": "", "error": str(e)}
-    finally:
-        # 确保 driver 被正确关闭,避免资源泄漏
-        if driver:
-            driver.close()
-
-"""
-Manual execution functions for production line
-Author: paul
-Date: 2024-03-20
-"""
-
-# 配置日志
-logger = logging.getLogger(__name__)
-
-# PostgreSQL配置
-def get_pg_config():
-    """从配置文件获取PostgreSQL配置,支持包含特殊字符的密码"""
-    db_uri = current_app.config['SQLALCHEMY_DATABASE_URI']
-    
-    # 尝试使用urlparse解析
-    uri = urlparse(db_uri)
-    
-    # 如果解析失败(缺少用户名或主机名)或包含特殊字符导致解析错误,使用手动解析
-    if uri.username is None or uri.hostname is None:
-        # 手动解析URI: postgresql://username:password@host:port/database
-        scheme_end = db_uri.find('://')
-        if scheme_end == -1:
-            raise ValueError("Invalid database URI format")
-        
-        auth_and_host = db_uri[scheme_end + 3:]  # 跳过 '://'
-        at_pos = auth_and_host.rfind('@')  # 从右向左查找最后一个@
-        
-        if at_pos == -1:
-            raise ValueError("Invalid database URI: missing @ separator")
-        
-        auth_part = auth_and_host[:at_pos]
-        host_part = auth_and_host[at_pos + 1:]
-        
-        # 解析用户名和密码(可能包含特殊字符)
-        colon_pos = auth_part.find(':')
-        if colon_pos == -1:
-            username = unquote(auth_part)
-            password = None
-        else:
-            username = unquote(auth_part[:colon_pos])
-            password = unquote(auth_part[colon_pos + 1:])
-        
-        # 解析主机、端口和数据库
-        slash_pos = host_part.find('/')
-        if slash_pos == -1:
-            raise ValueError("Invalid database URI: missing database name")
-        
-        host_port = host_part[:slash_pos]
-        database = unquote(host_part[slash_pos + 1:])
-        
-        # 解析主机和端口
-        colon_pos = host_port.find(':')
-        if colon_pos == -1:
-            hostname = host_port
-            port = 5432
-        else:
-            hostname = host_port[:colon_pos]
-            port = int(host_port[colon_pos + 1:])
-    else:
-        # urlparse解析成功,解码可能被URL编码的字段
-        username = unquote(uri.username) if uri.username else None
-        password = unquote(uri.password) if uri.password else None
-        database = unquote(uri.path[1:]) if uri.path and len(uri.path) > 1 else None
-        hostname = uri.hostname
-        port = uri.port or 5432
-    
-    # 验证必需的字段(username, database, hostname 是必需的,password 是可选的)
-    if not all([username, database, hostname]):
-        raise ValueError("Missing required database connection parameters: username, database, and hostname are required")
-    
-    return {
-        'dbname': database,
-        'user': username,
-        'password': password,
-        'host': hostname,
-        'port': str(port)
-    }
-
-def get_resource_storage_info(resource_id):
-    """
-    获取数据资源的存储位置和元数据信息
-    
-    Returns:
-        tuple: (storage_location, name_zh, name_en, metadata_list)
-        - storage_location: 存储位置
-        - name_zh: 资源中文名(用于查找Excel文件)
-        - name_en: 资源英文名(用于数据库表名)
-        - metadata_list: 元数据列表
-    """
-    try:
-        with neo4j_driver.get_session() as session:
-            # 获取资源基本信息
-            resource_query = """
-            MATCH (n:DataResource)
-            WHERE id(n) = $resource_id
-            RETURN n.storage_location as storage_location, 
-                   n.name_zh as name_zh,
-                   n.name_en as name_en
-            """
-            result = session.run(resource_query, resource_id=int(resource_id))
-            resource_data = result.single()
-            
-            if not resource_data:
-                raise ValueError(f"找不到ID为{resource_id}的数据资源")
-                
-            if not resource_data['storage_location']:
-                raise ValueError("存储位置未配置")
-                
-            # 查询元数据节点
-            metadata_query = """
-            MATCH (n:DataResource)-[:INCLUDES]->(m:DataMeta)
-            WHERE id(n) = $resource_id
-            RETURN m.name_zh as name, m.name_en as name_en, m.data_type as data_type
-            """
-            result = session.run(metadata_query, resource_id=int(resource_id))
-            metadata_list = [dict(record) for record in result]
-            
-            # 检查元数据列表是否为空
-            if not metadata_list:
-                logger.warning(f"数据资源 {resource_id} 没有元数据节点,将尝试从Excel文件推断元数据")
-            
-            # 检查英文名是否存在
-            if not resource_data['name_en']:
-                raise ValueError("数据资源的英文名不能为空")
-            
-            return (
-                resource_data['storage_location'],
-                resource_data['name_zh'],
-                resource_data['name_en'],
-                metadata_list
-            )
-    except Exception as e:
-        logger.error(f"获取资源存储信息失败: {str(e)}")
-        raise
-
-def check_and_create_table(table_name, metadata_list):
-    """
-    检查并创建PostgreSQL表
-    
-    Args:
-        table_name: 表名
-        metadata_list: 元数据列表
-    """
-    try:
-        conn = psycopg2.connect(**get_pg_config())
-        cur = conn.cursor()
-        
-        # 检查schema是否存在
-        cur.execute("CREATE SCHEMA IF NOT EXISTS ods;")
-        
-        # 检查表是否存在
-        cur.execute("""
-            SELECT EXISTS (
-                SELECT FROM information_schema.tables 
-                WHERE table_schema = 'ods' 
-                AND table_name = %s
-            );
-        """, (table_name,))
-        
-        table_exists = cur.fetchone()[0]
-        
-        if not table_exists:
-            # 如果元数据列表为空,无法创建表
-            if not metadata_list:
-                logger.warning(f"元数据列表为空,无法创建表。将在加载数据时自动创建")
-                return
-                
-            # 打印元数据列表用于调试
-            logger.info(f"元数据列表: {metadata_list}")
-            
-            # 构建建表SQL
-            columns = [
-                f"{meta['name_en']} {meta['data_type']}"
-                for meta in metadata_list
-                if 'name_en' in meta and meta['name_en'] and 'data_type' in meta and meta['data_type']
-            ]
-            
-            if not columns:
-                logger.warning("没有有效的列定义,无法创建表")
-                return
-                
-            sql = f"""
-            CREATE TABLE ods.{table_name} (
-                id SERIAL PRIMARY KEY,
-                {", ".join(columns)},
-                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-            )
-            """
-            
-            logger.info(f"创建表SQL: {sql}")
-            cur.execute(sql)
-            conn.commit()
-            logger.info(f"表 ods.{table_name} 创建成功")
-        else:
-            logger.info(f"表 ods.{table_name} 已存在")
-            
-            # 检查是否存在insert_dt列,如果存在,移除它(因为我们只使用created_at)
-            cur.execute(f"""
-                SELECT EXISTS (
-                    SELECT FROM information_schema.columns 
-                    WHERE table_schema = 'ods' 
-                    AND table_name = '{table_name}'
-                    AND column_name = 'insert_dt'
-                );
-            """)
-            insert_dt_exists = cur.fetchone()[0]
-            
-            # 如果insert_dt列存在,记录警告但不进行删除(删除列可能导致数据丢失)
-            if insert_dt_exists:
-                logger.warning(f"表 ods.{table_name} 存在冗余的insert_dt列,请考虑后续手动删除")
-            
-            # 检查是否存在created_at列,如果不存在,添加它
-            cur.execute(f"""
-                SELECT EXISTS (
-                    SELECT FROM information_schema.columns 
-                    WHERE table_schema = 'ods' 
-                    AND table_name = '{table_name}'
-                    AND column_name = 'created_at'
-                );
-            """)
-            created_at_exists = cur.fetchone()[0]
-            
-            # 如果created_at列不存在,添加它
-            if not created_at_exists:
-                alter_sql = f"ALTER TABLE ods.{table_name} ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;"
-                logger.info(f"添加created_at列: {alter_sql}")
-                cur.execute(alter_sql)
-                conn.commit()
-            
-            # 检查是否需要添加新列
-            if metadata_list:
-                # 获取现有列
-                cur.execute(f"""
-                    SELECT column_name 
-                    FROM information_schema.columns 
-                    WHERE table_schema = 'ods' 
-                    AND table_name = '{table_name}'
-                """)
-                existing_columns = [row[0] for row in cur.fetchall()]
-                
-                # 检查每个元数据是否需要作为新列添加
-                for meta in metadata_list:
-                    if 'name_en' in meta and meta['name_en'] and meta['name_en'].lower() not in (col.lower() for col in existing_columns):
-                        column_type = meta.get('data_type', 'VARCHAR(255)')
-                        alter_sql = f"ALTER TABLE ods.{table_name} ADD COLUMN {meta['name_en']} {column_type};"
-                        logger.info(f"添加新列: {alter_sql}")
-                        try:
-                            cur.execute(alter_sql)
-                            conn.commit()
-                        except Exception as e:
-                            logger.error(f"添加列失败: {str(e)}")
-    except Exception as e:
-        logger.error(f"创建表失败: {str(e)}")
-        conn.rollback()
-        raise
-    finally:
-        if cur:
-            cur.close()
-        if conn:
-            conn.close()
-
-def load_excel_to_postgresql(file_path, table_name, metadata_list):
-    """
-    加载Excel数据到PostgreSQL表
-    
-    Args:
-        file_path: Excel文件路径
-        table_name: 表名
-        metadata_list: 元数据列表
-        
-    Returns:
-        int: 加载的记录数
-    """
-    conn = None
-    cur = None
-    try:
-        # 读取Excel数据
-        df = pd.read_excel(file_path)
-        
-        # 如果Excel文件为空,返回0
-        if df.empty:
-            logger.warning(f"Excel文件 {file_path} 为空")
-            return 0
-            
-        # 如果元数据列表为空,尝试自动创建表
-        if not metadata_list:
-            logger.warning("元数据列表为空,尝试根据Excel文件自动创建表")
-            
-            # 创建数据库连接
-            conn = psycopg2.connect(**get_pg_config())
-            cur = conn.cursor()
-            
-            # 检查schema是否存在
-            cur.execute("CREATE SCHEMA IF NOT EXISTS ods;")
-            
-            # 检查表是否存在
-            cur.execute(f"""
-                SELECT EXISTS (
-                    SELECT FROM information_schema.tables 
-                    WHERE table_schema = 'ods' 
-                    AND table_name = '{table_name}'
-                );
-            """)
-            table_exists = cur.fetchone()[0]
-            
-            # 如果表不存在,根据DataFrame自动创建
-            if not table_exists:
-                # 生成列定义
-                columns = []
-                for col_name in df.columns:
-                    # 生成有效的SQL列名
-                    sql_col_name = re.sub(r'\W+', '_', col_name).lower()
-                    
-                    # 根据数据类型推断SQL类型
-                    dtype = df[col_name].dtype
-                    if pd.api.types.is_integer_dtype(dtype):
-                        sql_type = 'INTEGER'
-                    elif pd.api.types.is_float_dtype(dtype):
-                        sql_type = 'NUMERIC(15,2)'
-                    elif pd.api.types.is_datetime64_dtype(dtype):
-                        sql_type = 'TIMESTAMP'
-                    elif pd.api.types.is_bool_dtype(dtype):
-                        sql_type = 'BOOLEAN'
-                    else:
-                        sql_type = 'VARCHAR(255)'
-                        
-                    columns.append(f"{sql_col_name} {sql_type}")
-                
-                # 创建表,只包含created_at时间戳字段
-                create_sql = f"""
-                CREATE TABLE ods.{table_name} (
-                    id SERIAL PRIMARY KEY,
-                    {', '.join(columns)},
-                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-                );
-                """
-                logger.info(f"自动生成的建表SQL: {create_sql}")
-                cur.execute(create_sql)
-                conn.commit()
-                logger.info(f"表 ods.{table_name} 自动创建成功")
-            else:
-                # 检查是否存在created_at列
-                cur.execute(f"""
-                    SELECT EXISTS (
-                        SELECT FROM information_schema.columns 
-                        WHERE table_schema = 'ods' 
-                        AND table_name = '{table_name}'
-                        AND column_name = 'created_at'
-                    );
-                """)
-                created_at_exists = cur.fetchone()[0]
-                
-                # 如果created_at列不存在,添加它
-                if not created_at_exists:
-                    alter_sql = f"ALTER TABLE ods.{table_name} ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;"
-                    logger.info(f"添加created_at列: {alter_sql}")
-                    cur.execute(alter_sql)
-                    conn.commit()
-            
-            cur.close()
-            conn.close()
-            cur = None
-            conn = None
-            
-            # 创建临时元数据列表用于插入数据
-            metadata_list = []
-            for col_name in df.columns:
-                sql_col_name = re.sub(r'\W+', '_', col_name).lower()
-                metadata_list.append({
-                    'name_zh': col_name,
-                    'name_en': sql_col_name
-                })
-        
-        # 创建数据库连接
-        conn = psycopg2.connect(**get_pg_config())
-        cur = conn.cursor()
-        
-        # 准备插入数据
-        records = []
-        for _, row in df.iterrows():
-            record = {}
-            for meta in metadata_list:
-                if 'name_zh' in meta and meta['name_zh'] in df.columns and 'name_en' in meta:
-                    # 获取Excel中的值
-                    value = row[meta['name_zh']]
-                    # 处理NaN和None值
-                    if pd.isna(value):
-                        value = None
-                    record[meta['name_en']] = value
-            records.append(record)
-        
-        # 如果没有有效记录,返回0
-        if not records:
-            logger.warning("没有有效记录可插入")
-            return 0
-        
-        # 获取列名列表,只包括元数据列(不再包括insert_dt)
-        columns = [meta['name_en'] for meta in metadata_list if 'name_en' in meta]
-        if not columns:
-            logger.warning("没有有效列名")
-            return 0
-            
-        # 正确使用execute_values的方式
-        insert_sql = f"""
-        INSERT INTO ods.{table_name} ({", ".join(columns)})
-        VALUES %s
-        """
-        
-        # 准备要插入的数据元组
-        values = []
-        for record in records:
-            # 只包含数据列的值,不再需要添加时间戳
-            row_values = tuple(record.get(col, None) for col in columns)
-            values.append(row_values)
-        
-        # 执行批量插入
-        execute_values(cur, insert_sql, values)
-        conn.commit()
-        
-        # 返回插入的记录数
-        return len(values)
-    except Exception as e:
-        logger.error(f"加载Excel数据到PostgreSQL失败: {str(e)}", exc_info=True)
-        if conn:
-            conn.rollback()
-        raise
-    finally:
-        if cur:
-            cur.close()
-        if conn:
-            conn.close()
-
-def get_full_storage_path(relative_path):
-    """
-    根据相对路径获取完整的存储路径
-    
-    Args:
-        relative_path: Neo4j中存储的相对路径
-        
-    Returns:
-        str: 完整的存储路径
-    """
-    base_path = current_app.config['UPLOAD_BASE_PATH']
-    # 移除路径开头的斜杠(如果有)
-    relative_path = relative_path.lstrip('\\/')
-    # 根据操作系统使用正确的路径分隔符
-    if os.name == 'nt':  # Windows
-        full_path = os.path.join(base_path, relative_path.replace('/', '\\'))
-    else:  # Linux/Unix
-        full_path = os.path.join(base_path, relative_path.replace('\\', '/'))
-    return os.path.normpath(full_path)
-
-def get_archive_path():
-    """
-    获取当前日期的归档路径
-    
-    Returns:
-        str: 归档路径
-    """
-    base_path = current_app.config['ARCHIVE_BASE_PATH']
-    date_folder = datetime.now().strftime('%Y-%m-%d')
-    archive_path = os.path.join(base_path, date_folder)
-    
-    # 确保归档目录存在
-    os.makedirs(archive_path, exist_ok=True)
-    return archive_path
-
-def archive_excel_file(file_path):
-    """
-    将Excel文件复制到归档目录,保持原始文件名
-    
-    Args:
-        file_path: Excel文件的完整路径
-        
-    Returns:
-        str: 归档后的文件路径
-    """
-    archive_path = get_archive_path()
-    file_name = os.path.basename(file_path)
-    archive_file_path = os.path.join(archive_path, file_name)
-    
-    # 如果文件已经存在于归档目录,替换它
-    if os.path.exists(archive_file_path):
-        os.remove(archive_file_path)
-        logger.info(f"覆盖已存在的归档文件: {archive_file_path}")
-    
-    # 复制文件到归档目录
-    shutil.copy2(file_path, archive_file_path)
-    logger.info(f"文件已归档: {archive_file_path}")
-    
-    # 删除原始文件
-    os.remove(file_path)
-    logger.info(f"删除原始文件: {file_path}")
-    
-    return archive_file_path
-
-def execute_production_line(resource_id):
-    """
-    执行生产线数据加载
-    
-    Args:
-        resource_id: 数据资源ID
-        
-    Returns:
-        dict: 执行结果
-    """
-    try:
-        # 首先获取资源信息,判断类型
-        resource_type = get_resource_type(resource_id)
-        
-        # 根据资源类型执行不同的加载逻辑
-        if resource_type == 'ddl':
-            # DDL类型资源,执行数据库抽取
-            return execute_ddl_extraction(resource_id)
-        else:
-            # 其他类型(structure等),执行Excel数据加载
-            return execute_excel_loading(resource_id)
-    except Exception as e:
-        logger.error(f"执行生产线失败: {str(e)}", exc_info=True)
-        return {
-            "status": "error",
-            "message": str(e)
-        }
-
-def get_resource_type(resource_id):
-    """
-    获取资源类型
-    
-    Args:
-        resource_id: 数据资源ID
-        
-    Returns:
-        str: 资源类型,如'ddl'或'structure'
-    """
-    try:
-        with neo4j_driver.get_session() as session:
-            # 查询资源类型
-            cypher = """
-            MATCH (n:DataResource)
-            WHERE id(n) = $resource_id
-            RETURN n.type as type
-            """
-            result = session.run(cypher, resource_id=int(resource_id))
-            record = result.single()
-            
-            if not record:
-                raise ValueError(f"找不到ID为{resource_id}的数据资源")
-            
-            return record["type"] or 'structure'  # 默认为structure类型
-    except Exception as e:
-        logger.error(f"获取资源类型失败: {str(e)}")
-        raise
-
-def execute_excel_loading(resource_id):
-    """
-    执行Excel文件数据加载(原有的加载逻辑)
-    
-    Args:
-        resource_id: 数据资源ID
-        
-    Returns:
-        dict: 执行结果
-    """
-    try:
-        # 获取PostgreSQL配置
-        pg_config = get_pg_config()
-        
-        # 1. 获取存储信息
-        storage_location, name_zh, name_en, metadata_list = get_resource_storage_info(resource_id)
-        
-        # 2. 检查并创建表
-        check_and_create_table(name_en, metadata_list)
-        
-        # 3. 获取完整的存储路径并扫描Excel文件
-        full_storage_path = get_full_storage_path(storage_location)
-        
-        if not os.path.exists(full_storage_path):
-            # 如果目录不存在,创建它
-            try:
-                os.makedirs(full_storage_path, exist_ok=True)
-                logger.info(f"创建目录: {full_storage_path}")
-            except Exception as e:
-                raise ValueError(f"无法创建存储路径: {full_storage_path}, 错误: {str(e)}")
-        
-        # 首先使用中文名查找文件
-        excel_files = []
-        if name_zh:
-            excel_files = [
-                f for f in os.listdir(full_storage_path)
-                if f.startswith(name_zh) and f.endswith(('.xlsx', '.xls'))
-            ]
-            if excel_files:
-                logger.info(f"使用中文名'{name_zh}'找到Excel文件: {excel_files}")
-        
-        # 如果使用中文名没找到文件,尝试使用英文名
-        if not excel_files and name_en:
-            excel_files = [
-                f for f in os.listdir(full_storage_path)
-                if f.startswith(name_en) and f.endswith(('.xlsx', '.xls'))
-            ]
-            if excel_files:
-                logger.info(f"使用英文名'{name_en}'找到Excel文件: {excel_files}")
-        
-        # 如果两种方式都没找到文件,报错
-        if not excel_files:
-            error_msg = (
-                f"未找到匹配的Excel文件\n"
-                f"搜索路径: {full_storage_path}\n"
-                f"尝试查找的文件名模式:\n"
-                f"1. {name_zh}*.xlsx/xls (中文名)\n"
-                f"2. {name_en}*.xlsx/xls (英文名)\n"
-                f"请确认文件已上传到正确位置,且文件名以资源的中文名或英文名开头"
-            )
-            logger.error(error_msg)
-            raise ValueError(error_msg)
-        
-        # 4. 加载数据并归档文件
-        total_records = 0
-        processed_files = []
-        archived_files = []
-        
-        for excel_file in excel_files:
-            file_path = os.path.join(full_storage_path, excel_file)
-            try:
-                # 如果元数据为空,尝试从Excel文件中推断
-                if not metadata_list:
-                    logger.info(f"尝试从Excel文件 {excel_file} 推断元数据")
-                    metadata_list = extract_metadata_from_excel(file_path, name_en)
-                    if metadata_list:
-                        # 重新尝试创建表
-                        check_and_create_table(name_en, metadata_list)
-                    else:
-                        logger.warning("无法从Excel文件推断元数据,将尝试直接加载数据")
-                
-                # 加载数据到PostgreSQL
-                records = load_excel_to_postgresql(file_path, name_en, metadata_list)
-                total_records += records
-                processed_files.append(excel_file)
-                
-                # 归档成功处理的文件
-                archived_path = archive_excel_file(file_path)
-                archived_files.append(archived_path)
-                
-                logger.info(f"已处理并归档文件 {excel_file}, 加载 {records} 条记录")
-            except Exception as e:
-                logger.error(f"处理文件 {excel_file} 失败: {str(e)}", exc_info=True)
-                raise
-            
-        return {
-            "status": "success",
-            "message": f"数据加载成功,共处理 {len(processed_files)} 个文件,加载 {total_records} 条记录",
-            "total_records": total_records,
-            "files_processed": processed_files,
-            "files_archived": archived_files
-        }
-        
-    except Exception as e:
-        logger.error(f"执行Excel加载失败: {str(e)}", exc_info=True)
-        return {
-            "status": "error",
-            "message": str(e)
-        }
-
-def extract_metadata_from_excel(file_path, table_name):
-    """
-    从Excel文件中提取元数据
-    
-    Args:
-        file_path: Excel文件路径
-        table_name: 表名(用于翻译列名)
-    
-    Returns:
-        list: 元数据列表
-    """
-    try:
-        # 读取Excel文件的第一行作为列名
-        df = pd.read_excel(file_path, nrows=0)
-        
-        if df.empty:
-            logger.warning(f"Excel文件 {file_path} 为空")
-            return []
-            
-        # 获取列名
-        column_names = df.columns.tolist()
-        
-        # 翻译列名
-        metadata_list = []
-        for name in column_names:
-            # 使用已有的翻译功能
-            try:
-                from app.core.meta_data import translate_and_parse
-                from app.core.meta_data import infer_column_type
-                
-                # 翻译列名
-                name_en = translate_and_parse(name)[0] if name else f"column_{len(metadata_list)}"
-                
-                # 确保列名是合法的SQL标识符
-                name_en = re.sub(r'\W+', '_', name_en).lower()
-                
-                # 推断数据类型
-                df_sample = pd.read_excel(file_path, nrows=10)
-                col_index = column_names.index(name)
-                col_types = infer_column_type(df_sample)
-                data_type = col_types[col_index] if col_index < len(col_types) else 'VARCHAR(255)'
-                
-                metadata_list.append({
-                    'name_zh': name,
-                    'name_en': name_en,
-                    'data_type': data_type
-                })
-            except Exception as e:
-                logger.error(f"处理列 {name} 时出错: {str(e)}")
-                # 使用默认值
-                name_en = f"column_{len(metadata_list)}"
-                metadata_list.append({
-                    'name_zh': name,
-                    'name_en': name_en,
-                    'data_type': 'VARCHAR(255)'
-                })
-        
-        logger.info(f"从Excel推断出的元数据: {metadata_list}")
-        return metadata_list
-    except Exception as e:
-        logger.error(f"从Excel文件提取元数据失败: {str(e)}")
-        return []
-
-def execute_ddl_extraction(resource_id):
-    """
-    执行DDL资源数据抽取
-    
-    Args:
-        resource_id: 数据资源ID
-        
-    Returns:
-        dict: 执行结果
-    """
-    try:
-        from sqlalchemy import create_engine, text
-        import pandas as pd
-        
-        logger.info(f"开始执行DDL资源数据抽取,ID: {resource_id}")
-        
-        # 1. 获取资源详情
-        resource_data = get_resource_details(resource_id)
-        if not resource_data:
-            return {"status": "error", "message": f"资源不存在,ID: {resource_id}"}
-            
-        # 2. 获取资源元数据
-        metadata_list = resource_data.get('meta_list', [])
-        if not metadata_list:
-            return {"status": "error", "message": "资源没有元数据信息,无法创建表"}
-            
-        # 3. 获取资源表名
-        target_table_name = resource_data.get('name_en')
-        if not target_table_name:
-            return {"status": "error", "message": "资源没有英文名称,无法确定目标表名"}
-            
-        # 4. 获取关联的数据源信息
-        data_source_info = get_resource_data_source(resource_id)
-        if not data_source_info:
-            return {"status": "error", "message": "无法获取关联的数据源信息"}
-            
-        # 5. 在PostgreSQL中创建目标表
-        create_result = create_target_table(target_table_name, metadata_list)
-        if not create_result["success"]:
-            return {"status": "error", "message": f"创建目标表失败: {create_result['message']}"}
-            
-        # 6. 执行数据抽取
-        extract_result = extract_data_to_postgres(data_source_info, target_table_name, metadata_list)
-        
-        return {
-            "status": "success",
-            "message": f"数据抽取成功,从{extract_result['source_table']}表抽取到{extract_result['target_table']}表,共处理 {extract_result['total_records']} 条记录,执行了 {extract_result['execution_time']:.2f} 秒",
-            "total_records": extract_result['total_records'],
-            "source_table": extract_result['source_table'],
-            "target_table": extract_result['target_table'],
-            "execution_time": extract_result['execution_time']
-        }
-        
-    except Exception as e:
-        logger.error(f"DDL数据抽取失败: {str(e)}", exc_info=True)
-        return {
-            "status": "error",
-            "message": str(e)
-        }
-
-def get_resource_details(resource_id):
-    """
-    获取资源详细信息
-    
-    Args:
-        resource_id: 数据资源ID
-        
-    Returns:
-        dict: 资源详情
-    """
-    from app.core.data_resource.resource import handle_id_resource
-    return handle_id_resource(resource_id)
-
-def get_resource_data_source(resource_id):
-    """获取数据资源关联的数据源信息"""
-    try:
-        with neo4j_driver.get_session() as session:
-            # 查询数据资源节点连接的数据源节点
-            cypher = """
-            MATCH (n:DataResource)-[:originates_from]->(ds:DataSource)
-            WHERE id(n) = $resource_id
-            RETURN ds
-            """
-            
-            result = session.run(cypher, resource_id=int(resource_id))
-            record = result.single()
-            
-            if not record:
-                logger.warning(f"资源ID {resource_id} 没有关联的数据源")
-                return None
-            
-            # 构建数据源连接信息
-            data_source = dict(record["ds"])
-            return {
-                "type": data_source.get("type", "").lower(),
-                "host": data_source.get("host"),
-                "port": data_source.get("port"),
-                "database": data_source.get("database"),
-                "username": data_source.get("username"),
-                "password": data_source.get("password")
-                # 如果需要其他参数可以添加
-                # "param": data_source.get("param")
-            }
-    except Exception as e:
-        logger.error(f"获取数据源信息失败: {str(e)}")
-        return None
-
-def create_target_table(table_name, metadata_list):
-    """
-    在PostgreSQL中创建目标表
-    
-    Args:
-        table_name: 表名
-        metadata_list: 元数据列表
-        
-    Returns:
-        dict: {"success": bool, "message": str}
-    """
-    try:
-        import psycopg2
-        from flask import current_app
-        
-        # 获取PostgreSQL配置
-        pg_config = get_pg_config()
-        
-        conn = psycopg2.connect(**pg_config)
-        cur = conn.cursor()
-        
-        # 检查schema是否存在
-        cur.execute("CREATE SCHEMA IF NOT EXISTS ods;")
-        
-        # 检查表是否存在
-        cur.execute("""
-            SELECT EXISTS (
-                SELECT FROM information_schema.tables 
-                WHERE table_schema = 'ods' 
-                AND table_name = %s
-            );
-        """, (table_name,))
-        
-        table_exists = cur.fetchone()[0]
-        
-        if table_exists:
-            logger.info(f"表 ods.{table_name} 已存在,将跳过创建")
-            return {"success": True, "message": f"表 ods.{table_name} 已存在"}
-            
-        # 构建列定义
-        columns = []
-        for meta in metadata_list:
-            column_name = meta.get('name_en')
-            data_type = meta.get('data_type')
-            
-            if column_name and data_type:
-                columns.append(f"{column_name} {data_type}")
-        
-        if not columns:
-            return {"success": False, "message": "没有有效的列定义"}
-            
-        # 构建建表SQL
-        sql = f"""
-        CREATE TABLE ods.{table_name} (
-            id SERIAL PRIMARY KEY,
-            {", ".join(columns)},
-            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-        )
-        """
-        
-        logger.info(f"创建表SQL: {sql}")
-        cur.execute(sql)
-        conn.commit()
-        logger.info(f"表 ods.{table_name} 创建成功")
-        
-        return {"success": True, "message": f"表 ods.{table_name} 创建成功"}
-        
-    except Exception as e:
-        logger.error(f"创建目标表失败: {str(e)}")
-        if 'conn' in locals() and conn:
-            conn.rollback()
-        return {"success": False, "message": str(e)}
-    finally:
-        if 'cur' in locals() and cur:
-            cur.close()
-        if 'conn' in locals() and conn:
-            conn.close()
-
-def extract_data_to_postgres(source_conn_info, target_table, metadata_list):
-    """
-    从源数据库抽取数据到PostgreSQL
-    
-    Args:
-        source_conn_info: 源数据库连接信息
-        target_table: 目标表名
-        metadata_list: 元数据列表
-        
-    Returns:
-        dict: 抽取结果
-    """
-    try:
-        from sqlalchemy import create_engine, text
-        import pandas as pd
-        from flask import current_app
-        
-        # 源表名称与目标表相同
-        source_table = target_table
-        
-        # 批处理大小
-        batch_size = current_app.config.get('DATA_EXTRACT_BATCH_SIZE', 1000)
-        
-        # 源数据库连接字符串构建
-        db_type = source_conn_info["type"]
-        if db_type == "mysql":
-            # 对用户名、密码和数据库名进行URL编码,处理特殊字符
-            # 检查密码是否存在且不为None
-            username = source_conn_info.get('username', '')
-            password = source_conn_info.get('password')  # 可能为None
-            database = source_conn_info.get('database', '')
-            
-            encoded_username = quote(username, safe='') if username else ''
-            encoded_password = quote(password, safe='') if password else ''
-            encoded_database = quote(database, safe='') if database else ''
-            
-            # 构建连接字符串,如果密码为None则使用无密码格式
-            if password:
-                connection_string = f"mysql+pymysql://{encoded_username}:{encoded_password}@{source_conn_info['host']}:{source_conn_info['port']}/{encoded_database}"
-            else:
-                connection_string = f"mysql+pymysql://{encoded_username}@{source_conn_info['host']}:{source_conn_info['port']}/{encoded_database}"
-            
-            # 检查是否存在param参数,如存在则添加到连接字符串中
-            if 'param' in source_conn_info and source_conn_info['param']:
-                # 确保param参数以&开头
-                param = source_conn_info['param']
-                if not param.startswith('&'):
-                    param = '&' + param
-                connection_string = f"{connection_string}?{param[1:]}"
-                logger.debug(f"添加了数据源的param参数: {param}")
-                
-        elif db_type == "postgresql":
-            # 对用户名、密码和数据库名进行URL编码,处理特殊字符
-            # 检查密码是否存在且不为None
-            username = source_conn_info.get('username', '')
-            password = source_conn_info.get('password')  # 可能为None
-            database = source_conn_info.get('database', '')
-            
-            encoded_username = quote(username, safe='') if username else ''
-            encoded_password = quote(password, safe='') if password else ''
-            encoded_database = quote(database, safe='') if database else ''
-            
-            # 构建连接字符串,如果密码为None则使用无密码格式
-            if password:
-                connection_string = f"postgresql://{encoded_username}:{encoded_password}@{source_conn_info['host']}:{source_conn_info['port']}/{encoded_database}"
-            else:
-                connection_string = f"postgresql://{encoded_username}@{source_conn_info['host']}:{source_conn_info['port']}/{encoded_database}"
-        else:
-            raise ValueError(f"不支持的数据库类型: {db_type}")
-            
-        # 目标数据库连接参数
-        pg_config = get_pg_config()
-        # 对用户名、密码和数据库名进行URL编码,处理特殊字符
-        # 检查密码是否存在且不为None
-        pg_user = pg_config.get('user', '')
-        pg_password = pg_config.get('password')  # 可能为None
-        pg_dbname = pg_config.get('dbname', '')
-        
-        encoded_user = quote(pg_user, safe='') if pg_user else ''
-        encoded_password = quote(pg_password, safe='') if pg_password else ''
-        encoded_dbname = quote(pg_dbname, safe='') if pg_dbname else ''
-        
-        # 构建目标连接字符串,如果密码为None则使用无密码格式
-        if pg_password:
-            target_connection_string = f"postgresql://{encoded_user}:{encoded_password}@{pg_config['host']}:{pg_config['port']}/{encoded_dbname}"
-        else:
-            target_connection_string = f"postgresql://{encoded_user}@{pg_config['host']}:{pg_config['port']}/{encoded_dbname}"
-        
-        # 记录最终连接字符串
-        logger.debug(f"python连接源表的最终连接字符串: {connection_string}")
-        
-        # 连接源数据库
-        source_engine = create_engine(connection_string)
-        
-        # 连接目标数据库
-        target_engine = create_engine(target_connection_string)
-        
-        # 获取元数据列名,构建查询字段列表
-        column_names = [meta.get('name_en') for meta in metadata_list if meta.get('name_en')]
-        if not column_names:
-            raise ValueError("没有有效的列名")
-            
-        # 构建查询语句
-        select_columns = ", ".join(column_names)
-        query = f"SELECT {select_columns} FROM {source_table}"
-        
-        # 获取记录总数
-        with source_engine.connect() as conn:
-            count_result = conn.execute(text(f"SELECT COUNT(*) FROM {source_table}"))
-            total_count = count_result.scalar()
-            
-        # 分批抽取数据
-        total_records = 0
-        offset = 0
-        
-        # 计算开始时间
-        start_time = time.time()
-        
-        while offset < total_count:
-            # 构建带有分页的查询
-            paginated_query = f"{query} LIMIT {batch_size} OFFSET {offset}"
-            
-            # 读取数据批次
-            df = pd.read_sql(paginated_query, source_engine)
-            batch_count = len(df)
-            
-            if batch_count == 0:
-                break
-                
-            # 写入目标数据库
-            df.to_sql(
-                target_table, 
-                target_engine, 
-                schema='ods', 
-                if_exists='append', 
-                index=False
-            )
-            
-            total_records += batch_count
-            offset += batch_size
-            
-            logger.info(f"已抽取 {total_records}/{total_count} 条记录")
-            
-        # 计算执行时间
-        end_time = time.time()
-        execution_time = end_time - start_time
-        logger.info(f"作业抽取了 {total_records} 条记录,执行了 {execution_time:.2f} 秒")
-            
-        return {
-            "total_records": total_records,
-            "source_table": source_table,
-            "target_table": f"ods.{target_table}",
-            "execution_time": execution_time
-        }
-        
-    except Exception as e:
-        logger.error(f"数据抽取失败: {str(e)}")
-        raise 

+ 0 - 1
app/test-jenkins

@@ -1 +0,0 @@
-检查jenkins是否正常更新代码

+ 0 - 261
check_project_status.py

@@ -1,261 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-DataOps Platform 项目状态检查脚本
-用于诊断Cursor项目识别问题
-"""
-
-import os
-import sys
-import json
-from pathlib import Path
-
-def check_python_environment():
-    """检查Python环境"""
-    print("=" * 60)
-    print("Python环境检查")
-    print("=" * 60)
-    
-    print(f"Python版本: {sys.version}")
-    print(f"Python路径: {sys.executable}")
-    print(f"Python可执行文件: {os.path.exists(sys.executable)}")
-    
-    # 检查虚拟环境
-    in_venv = hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
-    print(f"是否在虚拟环境中: {in_venv}")
-    
-    if in_venv:
-        print(f"虚拟环境路径: {sys.prefix}")
-    
-    print()
-
-def check_project_structure():
-    """检查项目结构"""
-    print("=" * 60)
-    print("项目结构检查")
-    print("=" * 60)
-    
-    project_root = Path(__file__).parent
-    print(f"项目根目录: {project_root}")
-    
-    # 关键文件检查
-    key_files = [
-        "pyproject.toml",
-        "setup.py", 
-        "requirements.txt",
-        "application.py",
-        ".cursorrules",
-        ".gitignore",
-        "README.md"
-    ]
-    
-    for file_name in key_files:
-        file_path = project_root / file_name
-        exists = file_path.exists()
-        print(f"{file_name}: {'✓' if exists else '✗'} ({'存在' if exists else '缺失'})")
-    
-    print()
-    
-    # 目录结构检查
-    key_dirs = [
-        "app",
-        "app/api",
-        "app/config", 
-        "app/models",
-        "app/services",
-        "database",
-        "docs",
-        "tests",
-        ".vscode"
-    ]
-    
-    for dir_name in key_dirs:
-        dir_path = project_root / dir_name
-        exists = dir_path.exists()
-        print(f"{dir_name}/: {'✓' if exists else '✗'} ({'存在' if exists else '缺失'})")
-    
-    print()
-
-def check_cursor_compatibility():
-    """检查Cursor兼容性"""
-    print("=" * 60)
-    print("Cursor兼容性检查")
-    print("=" * 60)
-    
-    project_root = Path(__file__).parent
-    
-    # 检查pyproject.toml
-    pyproject_path = project_root / "pyproject.toml"
-    if pyproject_path.exists():
-        print("✓ pyproject.toml 存在 - 现代Python项目标准")
-        try:
-            with open(pyproject_path, 'r', encoding='utf-8') as f:
-                content = f.read()
-                if "build-system" in content and "project" in content:
-                    print("  ✓ 文件格式正确")
-                else:
-                    print("  ✗ 文件格式可能有问题")
-        except Exception as e:
-            print(f"  ✗ 读取文件失败: {e}")
-    else:
-        print("✗ pyproject.toml 缺失 - 可能导致项目识别问题")
-    
-    # 检查setup.py
-    setup_path = project_root / "setup.py"
-    if setup_path.exists():
-        print("✓ setup.py 存在 - 提供向后兼容性")
-    else:
-        print("✗ setup.py 缺失 - 可能影响旧版本工具识别")
-    
-    # 检查.cursorrules
-    cursorrules_path = project_root / ".cursorrules"
-    if cursorrules_path.exists():
-        print("✓ .cursorrules 存在 - Cursor编辑器配置")
-    else:
-        print("✗ .cursorrules 缺失 - Cursor编辑器配置不完整")
-    
-    # 检查.vscode配置
-    vscode_dir = project_root / ".vscode"
-    if vscode_dir.exists():
-        print("✓ .vscode 目录存在 - 工作区配置")
-        settings_path = vscode_dir / "settings.json"
-        if settings_path.exists():
-            print("  ✓ settings.json 存在")
-        else:
-            print("  ✗ settings.json 缺失")
-        
-        launch_path = vscode_dir / "launch.json"
-        if launch_path.exists():
-            print("  ✓ launch.json 存在")
-        else:
-            print("  ✗ launch.json 缺失")
-    else:
-        print("✗ .vscode 目录缺失 - 工作区配置不完整")
-    
-    print()
-
-def check_dependencies():
-    """检查依赖配置"""
-    print("=" * 60)
-    print("依赖配置检查")
-    print("=" * 60)
-    
-    project_root = Path(__file__).parent
-    requirements_path = project_root / "requirements.txt"
-    
-    if requirements_path.exists():
-        print("✓ requirements.txt 存在")
-        try:
-            with open(requirements_path, 'r', encoding='utf-8') as f:
-                lines = f.readlines()
-                flask_deps = [line for line in lines if 'flask' in line.lower()]
-                if flask_deps:
-                    print(f"  ✓ 包含Flask依赖: {len(flask_deps)} 个")
-                else:
-                    print("  ✗ 缺少Flask依赖")
-        except Exception as e:
-            print(f"  ✗ 读取文件失败: {e}")
-    else:
-        print("✗ requirements.txt 缺失")
-    
-    # 检查虚拟环境
-    venv_path = project_root / "venv"
-    if venv_path.exists():
-        print("✓ 虚拟环境存在")
-        python_exe = venv_path / "Scripts" / "python.exe" if os.name == 'nt' else venv_path / "bin" / "python"
-        if python_exe.exists():
-            print("  ✓ 虚拟环境Python可执行文件存在")
-        else:
-            print("  ✗ 虚拟环境Python可执行文件缺失")
-    else:
-        print("✗ 虚拟环境不存在 - 需要创建")
-    
-    print()
-
-def generate_cursor_workspace():
-    """生成Cursor工作区文件"""
-    print("=" * 60)
-    print("生成Cursor工作区文件")
-    print("=" * 60)
-    
-    project_root = Path(__file__).parent
-    workspace_path = project_root / "DataOps-platform.code-workspace"
-    
-    workspace_config = {
-        "folders": [
-            {
-                "name": "DataOps Platform",
-                "path": "."
-            }
-        ],
-        "settings": {
-            "python.defaultInterpreterPath": "./venv/Scripts/python.exe",
-            "python.terminal.activateEnvironment": True,
-            "python.linting.enabled": True,
-            "python.linting.flake8Enabled": True,
-            "python.formatting.provider": "black",
-            "python.testing.pytestEnabled": True,
-            "python.testing.pytestArgs": ["tests"],
-            "files.exclude": {
-                "**/__pycache__": True,
-                "**/*.pyc": True,
-                "**/venv": True,
-                "**/.git": True
-            },
-            "search.exclude": {
-                "**/venv": True,
-                "**/__pycache__": True,
-                "**/*.pyc": True
-            }
-        },
-        "extensions": {
-            "recommendations": [
-                "ms-python.python",
-                "ms-python.flake8",
-                "ms-python.black-formatter",
-                "ms-python.mypy-type-checker",
-                "ms-python.pytest-adapter"
-            ]
-        }
-    }
-    
-    try:
-        with open(workspace_path, 'w', encoding='utf-8') as f:
-            json.dump(workspace_config, f, indent=2, ensure_ascii=False)
-        print(f"✓ 工作区文件已生成: {workspace_path}")
-        print("  现在可以使用 'File > Open Workspace from File...' 打开此文件")
-    except Exception as e:
-        print(f"✗ 生成工作区文件失败: {e}")
-    
-    print()
-
-def main():
-    """主函数"""
-    print("DataOps Platform 项目状态检查工具")
-    print("用于诊断Cursor项目识别问题")
-    print()
-    
-    check_python_environment()
-    check_project_structure()
-    check_cursor_compatibility()
-    check_dependencies()
-    generate_cursor_workspace()
-    
-    print("=" * 60)
-    print("检查完成!")
-    print("=" * 60)
-    print()
-    print("如果发现问题,请按照以下步骤操作:")
-    print("1. 运行 run_project.bat 创建虚拟环境并安装依赖")
-    print("2. 使用 'File > Open Workspace from File...' 打开 .code-workspace 文件")
-    print("3. 或者直接使用 'File > Open Folder...' 打开项目目录")
-    print("4. 确保Cursor使用正确的Python解释器(虚拟环境中的Python)")
-    print()
-    print("如果问题仍然存在,请检查:")
-    print("- Python版本是否为3.8+")
-    print("- 是否有足够的磁盘空间")
-    print("- 防火墙设置是否阻止了端口访问")
-    print("- 系统环境变量是否正确设置")
-
-if __name__ == "__main__":
-    main()

+ 0 - 0
add_origin_source_field.sql → database/add_origin_source_field.sql


+ 0 - 0
alter_business_cards_simple.sql → database/alter_business_cards_simple.sql


+ 0 - 0
alter_business_cards_table.sql → database/alter_business_cards_table.sql


+ 0 - 0
check_business_cards_table.sql → database/check_business_cards_table.sql


+ 0 - 0
rollback_business_cards_table.sql → database/rollback_business_cards_table.sql


+ 0 - 0
step_by_step_alter.sql → database/step_by_step_alter.sql


+ 1030 - 0
docs/CODE_DOCUMENTATION.md

@@ -0,0 +1,1030 @@
+# DataOps Platform 代码说明文档
+
+> 本文档为 DataOps Platform 的技术说明文档,涵盖系统架构、模块设计、API 接口及数据结构说明。
+
+---
+
+## 目录
+
+1. [项目概述](#1-项目概述)
+2. [系统架构图](#2-系统架构图)
+3. [技术栈](#3-技术栈)
+4. [目录结构](#4-目录结构)
+5. [核心模块说明](#5-核心模块说明)
+6. [API 接口说明](#6-api-接口说明)
+7. [数据流程图](#7-数据流程图)
+8. [统一响应格式](#8-统一响应格式)
+9. [配置说明](#9-配置说明)
+
+---
+
+## 1. 项目概述
+
+DataOps Platform 是一个基于 Flask 的数据运营管理平台,用于数据管理、处理和分析。平台集成了图数据库 Neo4j 用于元数据管理、MinIO 用于文件存储、PostgreSQL 用于关系型数据存储,并集成 LLM(大语言模型)能力用于智能解析和代码生成。
+
+### 核心功能
+
+- **元数据管理**:管理和维护数据元信息
+- **业务领域管理**:定义和管理业务领域及其关联关系
+- **数据源管理**:数据源连接配置、验证和管理
+- **数据流管理**:数据流程的定义、执行和监控
+- **数据接口管理**:数据标准和标签的管理
+- **图谱操作**:Neo4j 图数据库操作
+- **系统管理**:用户认证、健康检查和配置管理
+
+---
+
+## 2. 系统架构图
+
+```
+┌─────────────────────────────────────────────────────────────────────────┐
+│                              前端应用层                                  │
+│                        (Vue.js / React 等)                               │
+└───────────────────────────────┬─────────────────────────────────────────┘
+                                │ HTTP/RESTful API
+                                ▼
+┌─────────────────────────────────────────────────────────────────────────┐
+│                            Flask 应用层                                  │
+│  ┌─────────────────────────────────────────────────────────────────┐   │
+│  │                        API 蓝图层 (Blueprints)                    │   │
+│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │   │
+│  │  │ /api/meta│ │ /api/bd  │ │/api/data │ │/api/data │ │/api/   │ │   │
+│  │  │          │ │          │ │ source   │ │  flow    │ │ graph  │ │   │
+│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘ │   │
+│  │  ┌──────────┐ ┌────────────────────────────────────────────────┐ │   │
+│  │  │/api/     │ │            /api/system                         │ │   │
+│  │  │interface │ │  (健康检查/配置/认证)                            │ │   │
+│  │  └──────────┘ └────────────────────────────────────────────────┘ │   │
+│  └─────────────────────────────────────────────────────────────────┘   │
+│                                   │                                     │
+│  ┌─────────────────────────────────────────────────────────────────┐   │
+│  │                        Core 核心业务层                            │   │
+│  │  ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ │   │
+│  │  │ meta_data │ │ business_ │ │ data_flow │ │ data_processing   │ │   │
+│  │  │           │ │ domain    │ │           │ │ (validator/cleaner)│ │   │
+│  │  └───────────┘ └───────────┘ └───────────┘ └───────────────────┘ │   │
+│  │  ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ │   │
+│  │  │   graph   │ │    llm    │ │ data_     │ │    common         │ │   │
+│  │  │ operations│ │ (解析/生成)│ │ interface │ │  (通用函数)        │ │   │
+│  │  └───────────┘ └───────────┘ └───────────┘ └───────────────────┘ │   │
+│  └─────────────────────────────────────────────────────────────────┘   │
+│                                   │                                     │
+│  ┌─────────────────────────────────────────────────────────────────┐   │
+│  │                        Services 服务层                            │   │
+│  │  ┌────────────────┐ ┌─────────────────┐ ┌─────────────────────┐  │   │
+│  │  │ neo4j_driver   │ │ db_healthcheck  │ │ package_function    │  │   │
+│  │  └────────────────┘ └─────────────────┘ └─────────────────────┘  │   │
+│  └─────────────────────────────────────────────────────────────────┘   │
+│                                   │                                     │
+│  ┌─────────────────────────────────────────────────────────────────┐   │
+│  │                        Models 模型层                              │   │
+│  │  ┌────────────────────────────────────────────────────────────┐  │   │
+│  │  │  result.py (统一响应格式: success/failed/json_response)     │  │   │
+│  │  └────────────────────────────────────────────────────────────┘  │   │
+│  └─────────────────────────────────────────────────────────────────┘   │
+└───────────────────────────────────┬─────────────────────────────────────┘
+                                    │
+                                    ▼
+┌─────────────────────────────────────────────────────────────────────────┐
+│                            数据存储层                                    │
+│  ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐    │
+│  │    Neo4j        │ │   PostgreSQL    │ │       MinIO             │    │
+│  │  (图数据库)     │ │  (关系型数据库)  │ │    (对象存储)           │    │
+│  │                 │ │                 │ │                         │    │
+│  │ • 元数据节点    │ │ • 用户数据      │ │ • SQL 文件              │    │
+│  │ • 业务领域      │ │ • 日历记录      │ │ • 文档文件              │    │
+│  │ • 数据源        │ │ • 数据流日志    │ │ • 上传资源              │    │
+│  │ • 标签关系      │ │                 │ │                         │    │
+│  └─────────────────┘ └─────────────────┘ └─────────────────────────┘    │
+└─────────────────────────────────────────────────────────────────────────┘
+                                    │
+                                    ▼
+┌─────────────────────────────────────────────────────────────────────────┐
+│                            外部服务层                                    │
+│  ┌─────────────────────┐ ┌─────────────────────────────────────────┐    │
+│  │    LLM API          │ │            Airflow                      │    │
+│  │  (通义千问/Qwen)    │ │       (工作流调度)                       │    │
+│  └─────────────────────┘ └─────────────────────────────────────────┘    │
+└─────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 3. 技术栈
+
+| 类别 | 技术 | 版本要求 |
+|------|------|---------|
+| 后端框架 | Flask | 2.3.3+ |
+| ORM | SQLAlchemy | 2.0+ |
+| 图数据库 | Neo4j | 4.0+ |
+| 关系数据库 | PostgreSQL | 13+ |
+| 对象存储 | MinIO | - |
+| LLM | 通义千问 (Qwen) | - |
+| 语言 | Python | 3.8+ |
+
+---
+
+## 4. 目录结构
+
+```
+DataOps-platform-new/
+├── app/                              # 主应用目录
+│   ├── __init__.py                   # Flask 应用工厂
+│   ├── api/                          # API 蓝图模块
+│   │   ├── business_domain/          # 业务领域 API
+│   │   │   ├── __init__.py
+│   │   │   └── routes.py             # 路由定义
+│   │   ├── data_flow/                # 数据流 API
+│   │   ├── data_interface/           # 数据接口 API
+│   │   ├── data_source/              # 数据源 API
+│   │   ├── graph/                    # 图操作 API
+│   │   ├── meta_data/                # 元数据 API
+│   │   └── system/                   # 系统 API
+│   ├── config/                       # 配置模块
+│   │   ├── config.py                 # 主配置文件
+│   │   └── cors.py                   # CORS 配置
+│   ├── core/                         # 核心业务逻辑
+│   │   ├── business_domain/          # 业务领域逻辑
+│   │   ├── common/                   # 通用函数
+│   │   ├── data_flow/                # 数据流逻辑
+│   │   ├── data_interface/           # 数据接口逻辑
+│   │   ├── data_processing/          # 数据处理 (验证/清洗)
+│   │   ├── graph/                    # 图操作逻辑
+│   │   ├── llm/                      # LLM 服务
+│   │   ├── meta_data/                # 元数据逻辑
+│   │   └── system/                   # 系统逻辑
+│   ├── models/                       # 数据模型
+│   │   └── result.py                 # 统一响应格式
+│   ├── scripts/                      # 数据库脚本
+│   └── services/                     # 服务层
+│       ├── neo4j_driver.py           # Neo4j 驱动
+│       └── db_healthcheck.py         # 数据库健康检查
+├── database/                         # 数据库 SQL 脚本
+├── docs/                             # 文档目录
+├── mcp-servers/                      # MCP 服务器
+├── tests/                            # 测试目录
+├── application.py                    # 应用入口
+├── requirements.txt                  # Python 依赖
+└── README.md                         # 项目说明
+```
+
+---
+
+## 5. 核心模块说明
+
+### 5.1 应用初始化模块 (`app/__init__.py`)
+
+Flask 应用工厂模式实现,主要功能:
+
+1. **创建应用实例**:加载配置并初始化 Flask 应用
+2. **注册蓝图**:注册所有 API 蓝图
+3. **配置 CORS**:跨域资源共享配置
+4. **配置日志**:应用日志记录
+5. **全局异常处理**:统一的错误响应处理
+
+```python
+# 蓝图注册路径
+/api/meta       -> 元数据接口
+/api/bd         -> 业务领域接口
+/api/datasource -> 数据源接口
+/api/dataflow   -> 数据流接口
+/api/interface  -> 数据接口
+/api/graph      -> 图操作接口
+/api/system     -> 系统接口
+```
+
+### 5.2 统一响应模块 (`app/models/result.py`)
+
+提供标准化的 API 响应格式:
+
+```python
+# 成功响应
+def success(data=None, message="操作成功", code=200):
+    return {"code": code, "message": message, "data": data}
+
+# 失败响应
+def failed(message="操作失败", code=500, data=None, error=None):
+    return {"code": code, "message": message, "data": data, "error": error}
+```
+
+### 5.3 Neo4j 驱动模块 (`app/services/neo4j_driver.py`)
+
+图数据库连接管理,采用单例模式:
+
+- **延迟初始化**:避免模块导入时的上下文问题
+- **连接验证**:提供连接可用性检查
+- **会话管理**:提供 Neo4j 会话获取接口
+
+### 5.4 图操作模块 (`app/core/graph/graph_operations.py`)
+
+核心图数据库操作:
+
+| 函数 | 功能 |
+|------|------|
+| `connect_graph()` | 连接 Neo4j 数据库 |
+| `create_or_get_node()` | 创建或更新节点 |
+| `create_relationship()` | 创建节点关系 |
+| `get_subgraph()` | 获取子图数据 |
+| `execute_cypher_query()` | 执行 Cypher 查询 |
+| `relationship_exists()` | 检查关系是否存在 |
+
+---
+
+## 6. API 接口说明
+
+### 6.1 系统接口 (`/api/system`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/health` | GET | 系统健康检查 |
+| `/config` | GET | 获取系统配置 |
+| `/info` | GET | 获取系统运行信息 |
+| `/config/validate` | GET | 验证配置有效性 |
+| `/auth/register` | POST | 用户注册 |
+| `/auth/login` | POST | 用户登录 |
+| `/auth/user/<username>` | GET | 获取用户信息 |
+| `/translate` | POST | 翻译节点名称 |
+
+#### 用户登录接口
+
+**请求参数:**
+```json
+{
+  "username": "string (必填)",
+  "password": "string (必填)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "登录成功",
+  "data": {
+    "user_id": "integer",
+    "username": "string",
+    "token": "string"
+  }
+}
+```
+
+---
+
+### 6.2 业务领域接口 (`/api/bd`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/list` | POST | 获取业务领域列表 |
+| `/detail` | POST | 获取业务领域详情 |
+| `/save` | POST | 保存业务领域 |
+| `/update` | POST | 更新业务领域 |
+| `/delete` | POST | 删除业务领域 |
+| `/upload` | POST | 上传文件 |
+| `/download` | GET | 下载文件 |
+| `/graphall` | POST | 获取完整关系图谱 |
+| `/ddlparse` | POST | 解析 DDL 语句 |
+| `/search` | POST | 搜索关联元数据 |
+| `/compose` | POST | 组合创建业务领域 |
+| `/labellist` | POST | 获取数据标签列表 |
+
+#### 获取业务领域列表
+
+**请求参数:**
+```json
+{
+  "current": "integer (页码,默认 1)",
+  "size": "integer (每页数量,默认 10)",
+  "name_en": "string (英文名过滤,可选)",
+  "name_zh": "string (中文名过滤,可选)",
+  "type": "string (类型过滤:all/table/view 等,默认 all)",
+  "category": "string (分类过滤,可选)",
+  "tag": "string (标签过滤,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [
+      {
+        "id": "integer",
+        "name_en": "string",
+        "name_zh": "string",
+        "type": "string",
+        "category": "string",
+        "create_time": "string",
+        "update_time": "string"
+      }
+    ],
+    "total": "integer",
+    "size": "integer",
+    "current": "integer"
+  }
+}
+```
+
+#### 文件上传接口
+
+**请求参数:**
+- Content-Type: `multipart/form-data`
+- file: 上传的文件(支持格式:txt, pdf, png, jpg, xlsx, xls, csv, sql, dll)
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "filename": "string (原始文件名)",
+    "size": "integer (文件大小,字节)",
+    "type": "string (文件类型)",
+    "url": "string (MinIO 存储路径)"
+  }
+}
+```
+
+#### DDL 解析接口
+
+**请求参数(方式一 - JSON):**
+```json
+{
+  "sql": "string (SQL DDL 内容)"
+}
+```
+
+**请求参数(方式二 - 文件上传):**
+- Content-Type: `multipart/form-data`
+- file: SQL 文件
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": [
+    {
+      "table_info": {
+        "name_en": "string (表名)",
+        "name_zh": "string (中文名)",
+        "comment": "string (表注释)"
+      },
+      "columns": [
+        {
+          "name": "string",
+          "type": "string",
+          "comment": "string",
+          "nullable": "boolean"
+        }
+      ],
+      "exist": "boolean (是否已存在)"
+    }
+  ]
+}
+```
+
+---
+
+### 6.3 元数据接口 (`/api/meta`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/node/list` | POST | 获取元数据列表 |
+| `/node/graph` | POST | 获取元数据图谱 |
+| `/node/add` | POST | 添加元数据节点 |
+| `/node/edit` | POST | 编辑元数据节点 |
+| `/node/update` | POST | 更新元数据节点 |
+| `/node/delete` | POST | 删除元数据节点 |
+| `/check` | GET | 检查元数据是否存在 |
+| `/search` | GET | 搜索元数据 |
+| `/full/text/query` | POST | 全文检索查询 |
+| `/unstructure/text/query` | POST | 非结构化文本查询 |
+| `/resource/upload` | POST | 资源文件上传 |
+| `/resource/display` | POST | 获取资源文件信息 |
+| `/resource/download` | GET | 下载资源文件 |
+| `/resource/translate` | POST | 文本资源翻译 |
+| `/resource/node` | POST | 创建文本资源节点 |
+| `/unstructured/process` | POST | 处理非结构化数据 |
+| `/text/graph` | POST | 创建文本图谱 |
+| `/config` | GET | 获取元数据配置 |
+
+#### 获取元数据列表
+
+**请求参数:**
+```json
+{
+  "current": "integer (页码,默认 1)",
+  "size": "integer (每页数量,默认 10)",
+  "name_en": "string (英文名过滤,可选)",
+  "name_zh": "string (中文名过滤,可选)",
+  "category": "string (分类过滤,可选)",
+  "time": "string (时间过滤,可选)",
+  "tag": "array (标签过滤,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [
+      {
+        "id": "integer",
+        "name_en": "string",
+        "name_zh": "string",
+        "data_type": "string",
+        "category": "string",
+        "status": "boolean",
+        "create_time": "string",
+        "updateTime": "string"
+      }
+    ],
+    "total": "integer",
+    "size": "integer",
+    "current": "integer"
+  }
+}
+```
+
+#### 获取元数据图谱
+
+**请求参数:**
+```json
+{
+  "nodeId": "integer (必填,节点 ID)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "node": {
+      "id": "integer",
+      "name_zh": "string",
+      "name_en": "string",
+      "properties": {}
+    },
+    "related_nodes": [
+      {
+        "id": "integer",
+        "name_zh": "string",
+        "labels": ["string"]
+      }
+    ],
+    "relationships": [
+      {
+        "id": "integer",
+        "type": "string",
+        "source": "integer",
+        "target": "integer"
+      }
+    ]
+  }
+}
+```
+
+#### 添加元数据节点
+
+**请求参数:**
+```json
+{
+  "name_zh": "string (必填,中文名称)",
+  "name_en": "string (英文名称,可选)",
+  "data_type": "string (必填,数据类型)",
+  "category": "string (分类,可选)",
+  "alias": "string (别名,可选)",
+  "affiliation": "string (归属,可选)",
+  "describe": "string (描述,可选)",
+  "status": "boolean (状态,默认 true)",
+  "tag": "array (标签列表,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "id": "integer",
+    "name_zh": "string",
+    "name_en": "string",
+    "data_type": "string",
+    "create_time": "string",
+    "tag": [
+      {
+        "id": "integer",
+        "name_zh": "string",
+        "name_en": "string"
+      }
+    ]
+  }
+}
+```
+
+---
+
+### 6.4 数据源接口 (`/api/datasource`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/save` | POST | 保存数据源 |
+| `/list` | POST | 获取数据源列表 |
+| `/delete` | POST | 删除数据源 |
+| `/parse` | POST | 解析连接字符串 |
+| `/valid` | POST | 验证连接信息 |
+| `/conntest` | POST | 测试数据源连接 |
+| `/graph` | POST | 获取数据源关系图 |
+
+#### 保存数据源
+
+**请求参数:**
+```json
+{
+  "database": "string (必填,数据库名)",
+  "host": "string (必填,主机地址)",
+  "port": "integer (必填,端口号)",
+  "username": "string (必填,用户名)",
+  "password": "string (必填,密码)",
+  "name_en": "string (必填,数据源英文名)",
+  "type": "string (必填,数据库类型:postgresql/mysql 等)",
+  "name_zh": "string (中文名,可选)",
+  "describe": "string (描述,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "id": "integer",
+    "message": "string (数据源创建成功/数据源更新成功)"
+  }
+}
+```
+
+#### 测试数据源连接
+
+**请求参数:**
+```json
+{
+  "type": "string (必填,数据库类型)",
+  "username": "string (必填,用户名)",
+  "password": "string (必填,密码)",
+  "host": "string (必填,主机地址)",
+  "port": "integer (必填,端口号)",
+  "database": "string (必填,数据库名)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "message": "string (连接测试成功)",
+    "connected": true
+  }
+}
+```
+
+---
+
+### 6.5 数据流接口 (`/api/dataflow`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/get-dataflows-list` | GET | 获取数据流列表 |
+| `/get-dataflow/<id>` | GET | 获取数据流详情 |
+| `/add-dataflow` | POST | 创建数据流 |
+| `/update-dataflow/<id>` | PUT | 更新数据流 |
+| `/delete-dataflow/<id>` | DELETE | 删除数据流 |
+| `/execute-dataflow/<id>` | POST | 执行数据流 |
+| `/get-dataflow-status/<id>` | GET | 获取执行状态 |
+| `/get-dataflow-logs/<id>` | GET | 获取执行日志 |
+| `/create-script` | POST | 生成数据处理脚本 |
+| `/get-BD-list` | GET | 获取业务领域列表 |
+
+#### 创建数据流
+
+**请求参数:**
+```json
+{
+  "name": "string (必填,数据流名称)",
+  "description": "string (描述,可选)",
+  "source_id": "integer (源数据配置,可选)",
+  "target_id": "integer (目标数据配置,可选)",
+  "config": "object (流程配置,可选)",
+  "schedule": "string (调度配置,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "数据流创建成功",
+  "data": {
+    "id": "integer",
+    "name": "string",
+    "description": "string",
+    "status": "string",
+    "created_at": "string"
+  }
+}
+```
+
+#### 生成脚本接口
+
+**请求参数:**
+```json
+{
+  "requirement": "string (需求描述)",
+  "source_config": "object (源配置)",
+  "target_config": "object (目标配置)",
+  "rules": "array (转换规则)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "脚本生成成功",
+  "data": {
+    "script_content": "string (生成的脚本内容)",
+    "format": "txt",
+    "generated_at": "string (生成时间)"
+  }
+}
+```
+
+---
+
+### 6.6 数据接口模块 (`/api/interface`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/data/standard/add` | POST | 添加数据标准 |
+| `/data/standard/detail` | POST | 获取数据标准详情 |
+| `/data/standard/code` | POST | 生成标准代码 |
+| `/data/standard/update` | POST | 更新数据标准 |
+| `/data/standard/list` | POST | 获取数据标准列表 |
+| `/data/standard/graph/all` | POST | 获取标准图谱 |
+| `/data/label/add` | POST | 添加数据标签 |
+| `/data/label/detail` | POST | 获取标签详情 |
+| `/data/label/list` | POST | 获取标签列表 |
+| `/data/label/delete` | POST | 删除标签 |
+| `/data/label/graph/all` | POST | 获取标签图谱 |
+| `/data/label/dynamic/identify` | POST | 动态识别标签 |
+| `/labellist` | POST | 获取标签列表(简化版) |
+| `/graphall` | POST | 获取完整图谱 |
+| `/metric/label/standard/delete` | POST | 删除指标标签关联 |
+
+#### 添加数据标签
+
+**请求参数:**
+```json
+{
+  "name_zh": "string (必填,中文名称)",
+  "category": "string (分类,可选)",
+  "group": "string (分组,可选)",
+  "describe": "string (描述,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": ""
+}
+```
+
+---
+
+### 6.7 图操作接口 (`/api/graph`)
+
+| 接口路径 | 方法 | 描述 |
+|----------|------|------|
+| `/query` | POST | 执行 Cypher 查询 |
+| `/node/create` | POST | 创建节点 |
+| `/relationship/create` | POST | 创建关系 |
+| `/subgraph` | POST | 获取子图数据 |
+
+#### 执行 Cypher 查询
+
+**请求参数:**
+```json
+{
+  "cypher": "string (必填,Cypher 查询语句)",
+  "params": "object (查询参数,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": [
+    {
+      "n": {
+        "_id": "integer",
+        "_labels": ["string"],
+        "property1": "value1"
+      }
+    }
+  ]
+}
+```
+
+#### 创建节点
+
+**请求参数:**
+```json
+{
+  "labels": "array (必填,节点标签列表)",
+  "properties": "object (节点属性)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "n": {
+      "_id": "integer",
+      "_labels": ["string"],
+      "properties": {}
+    }
+  }
+}
+```
+
+#### 创建关系
+
+**请求参数:**
+```json
+{
+  "startNodeId": "integer (必填,起始节点 ID)",
+  "endNodeId": "integer (必填,结束节点 ID)",
+  "type": "string (必填,关系类型)",
+  "properties": "object (关系属性,可选)"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "r": {
+      "_id": "integer",
+      "_type": "string",
+      "_start_node_id": "integer",
+      "_end_node_id": "integer"
+    }
+  }
+}
+```
+
+---
+
+## 7. 数据流程图
+
+### 7.1 用户认证流程
+
+```
+┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
+│   客户端    │     │  Flask API  │     │  Core 层    │     │ PostgreSQL  │
+└──────┬──────┘     └──────┬──────┘     └──────┬──────┘     └──────┬──────┘
+       │                   │                   │                   │
+       │  POST /auth/login │                   │                   │
+       │──────────────────>│                   │                   │
+       │                   │  login_user()     │                   │
+       │                   │──────────────────>│                   │
+       │                   │                   │  查询用户          │
+       │                   │                   │──────────────────>│
+       │                   │                   │<──────────────────│
+       │                   │                   │  验证密码          │
+       │                   │<──────────────────│                   │
+       │   返回登录结果     │                   │                   │
+       │<──────────────────│                   │                   │
+       │                   │                   │                   │
+```
+
+### 7.2 元数据创建流程
+
+```
+┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
+│   客户端    │     │  Flask API  │     │  Core 层    │     │   Neo4j     │
+└──────┬──────┘     └──────┬──────┘     └──────┬──────┘     └──────┬──────┘
+       │                   │                   │                   │
+       │ POST /node/add    │                   │                   │
+       │──────────────────>│                   │                   │
+       │                   │  验证参数          │                   │
+       │                   │──────────────────>│                   │
+       │                   │                   │  MERGE 节点       │
+       │                   │                   │──────────────────>│
+       │                   │                   │<──────────────────│
+       │                   │                   │  创建标签关系      │
+       │                   │                   │──────────────────>│
+       │                   │                   │<──────────────────│
+       │                   │<──────────────────│                   │
+       │   返回节点信息     │                   │                   │
+       │<──────────────────│                   │                   │
+       │                   │                   │                   │
+```
+
+### 7.3 文件上传流程
+
+```
+┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
+│   客户端    │     │  Flask API  │     │  MinIO 客户端│     │   MinIO     │
+└──────┬──────┘     └──────┬──────┘     └──────┬──────┘     └──────┬──────┘
+       │                   │                   │                   │
+       │ POST /upload      │                   │                   │
+       │ (multipart/form)  │                   │                   │
+       │──────────────────>│                   │                   │
+       │                   │  验证文件类型      │                   │
+       │                   │──────────────────>│                   │
+       │                   │                   │  put_object()    │
+       │                   │                   │──────────────────>│
+       │                   │                   │<──────────────────│
+       │                   │<──────────────────│                   │
+       │   返回文件信息     │                   │                   │
+       │<──────────────────│                   │                   │
+       │                   │                   │                   │
+```
+
+### 7.4 DDL 解析流程
+
+```
+┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
+│   客户端    │     │  Flask API  │     │  DDLParser  │     │  LLM API    │
+└──────┬──────┘     └──────┬──────┘     └──────┬──────┘     └──────┬──────┘
+       │                   │                   │                   │
+       │ POST /ddlparse    │                   │                   │
+       │──────────────────>│                   │                   │
+       │                   │  parse_ddl()      │                   │
+       │                   │──────────────────>│                   │
+       │                   │                   │  调用 LLM 解析    │
+       │                   │                   │──────────────────>│
+       │                   │                   │<──────────────────│
+       │                   │                   │  格式化结果       │
+       │                   │<──────────────────│                   │
+       │                   │  检查节点存在性    │                   │
+       │                   │──────────────────>│ (Neo4j 查询)      │
+       │                   │<──────────────────│                   │
+       │   返回解析结果     │                   │                   │
+       │<──────────────────│                   │                   │
+       │                   │                   │                   │
+```
+
+---
+
+## 8. 统一响应格式
+
+### 8.1 成功响应
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    // 具体业务数据
+  }
+}
+```
+
+### 8.2 失败响应
+
+```json
+{
+  "code": 500,
+  "message": "操作失败描述",
+  "data": null,
+  "error": "详细错误信息(可选)"
+}
+```
+
+### 8.3 分页响应
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [],
+    "total": 100,
+    "size": 10,
+    "current": 1
+  }
+}
+```
+
+### 8.4 HTTP 状态码
+
+| 状态码 | 含义 |
+|--------|------|
+| 200 | 成功 |
+| 400 | 请求参数错误 |
+| 401 | 未授权/认证失败 |
+| 404 | 资源不存在 |
+| 500 | 服务器内部错误 |
+
+---
+
+## 9. 配置说明
+
+### 9.1 环境配置
+
+系统支持两种环境配置:
+
+| 环境 | 说明 | 触发条件 |
+|------|------|---------|
+| development | 开发环境 | Windows 系统或 FLASK_ENV=development |
+| production | 生产环境 | Linux 系统或 FLASK_ENV=production |
+
+### 9.2 主要配置项
+
+#### 数据库配置
+
+| 配置项 | 开发环境 | 生产环境 |
+|--------|---------|---------|
+| NEO4J_URI | bolt://localhost:7687 | bolt://192.168.3.143:7687 |
+| SQLALCHEMY_DATABASE_URI | postgresql://postgres:postgres@localhost:5432/dataops | postgresql://postgres:dataOps@192.168.3.143:5432/dataops |
+
+#### MinIO 配置
+
+| 配置项 | 开发环境 | 生产环境 |
+|--------|---------|---------|
+| MINIO_HOST | localhost:9000 | 192.168.3.143:9000 |
+| MINIO_BUCKET | dataops-bucket | dataops-bucket |
+
+#### LLM 配置
+
+| 配置项 | 说明 |
+|--------|------|
+| LLM_BASE_URL | 通义千问 API 地址 |
+| LLM_MODEL_NAME | 模型名称 (qwen-turbo) |
+| LLM_API_KEY | API 密钥 |
+
+### 9.3 文件上传配置
+
+支持的文件类型:`txt, pdf, png, jpg, jpeg, gif, xlsx, xls, csv, sql, dll`
+
+---
+
+## 附录
+
+### A. Neo4j 节点标签说明
+
+| 标签 | 说明 |
+|------|------|
+| DataMeta | 元数据节点 |
+| BusinessDomain | 业务领域节点 |
+| DataSource | 数据源节点 |
+| DataLabel | 数据标签节点 |
+| data_standard | 数据标准节点 |
+| Tag | 标签节点 |
+
+### B. 常用关系类型
+
+| 关系类型 | 说明 |
+|----------|------|
+| LABEL | 元数据与标签的关联 |
+| HAS_TAG | 资源与标签的关联 |
+| master_data | 主数据关联 |
+| BELONGS_TO | 归属关系 |
+
+### C. 错误码参考
+
+| 错误码 | 含义 |
+|--------|------|
+| 200 | 操作成功 |
+| 400 | 参数错误 |
+| 401 | 认证失败 |
+| 404 | 资源不存在 |
+| 500 | 服务器错误 |
+
+---
+
+*文档生成时间:2025年12月*
+*版本:1.0*
+

+ 0 - 215
docs/api/metric-check-api.md

@@ -1,215 +0,0 @@
-# 指标公式检查API文档
-
-## API概述
-
-该API用于检查指标计算公式中的变量,并在Neo4j数据库中查找匹配的元数据。
-
-## 接口信息
-
-- **URL**: `/api/data/metric/check`
-- **方法**: `POST`
-- **Content-Type**: `application/json`
-
-## 请求参数
-
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| formula | string | 是 | 指标计算公式文本 |
-
-### 公式格式说明
-
-公式格式为:`指标名称 = 计算表达式`
-
-- 等号左侧:指标名称
-- 等号右侧:包含中文变量和数学运算符的计算表达式
-- 支持的运算符:`+`、`-`、`*`、`/`、`()`、`()`、`[]`、`{}`
-- 变量:中文字符串或数字
-- 只有中文字符串会被识别为需要查找的变量
-
-### 请求示例
-
-```json
-{
-  "formula": "销售额 = 单价 * 数量 + 运费"
-}
-```
-
-更复杂的示例:
-
-```json
-{
-  "formula": "利润率 = (销售收入 - 成本) / 销售收入 * 100"
-}
-```
-
-## 响应格式
-
-### 成功响应
-
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "单价",
-      "name_zh": "单价",
-      "name_en": "unit_price",
-      "id": 12345,
-      "create_time": "2024-01-15 10:30:00",
-      "findit": 1
-    },
-    {
-      "variable": "数量",
-      "name_zh": "数量",
-      "name_en": "quantity",
-      "id": 12346,
-      "create_time": "2024-01-15 10:31:00",
-      "findit": 1
-    },
-    {
-      "variable": "运费",
-      "name_zh": "",
-      "name_en": "",
-      "id": null,
-      "create_time": "",
-      "findit": 0
-    }
-  ]
-}
-```
-
-### 响应字段说明
-
-| 字段名 | 类型 | 说明 |
-|--------|------|------|
-| variable | string | 从公式中提取的变量名 |
-| name_zh | string | 匹配到的元数据中文名称(未找到时为空字符串) |
-| name_en | string | 匹配到的元数据英文名称(未找到时为空字符串) |
-| id | integer/null | 匹配到的元数据节点ID(未找到时为null) |
-| create_time | string | 匹配到的元数据创建时间(未找到时为空字符串) |
-| findit | integer | 是否找到匹配:1表示找到,0表示未找到 |
-
-### 错误响应
-
-#### 参数错误
-
-```json
-{
-  "code": 400,
-  "message": "failed",
-  "data": {},
-  "error": {
-    "error": "公式文本不能为空"
-  }
-}
-```
-
-#### 公式格式错误
-
-```json
-{
-  "code": 400,
-  "message": "failed",
-  "data": {},
-  "error": {
-    "error": "公式格式错误:缺少等号"
-  }
-}
-```
-
-#### 服务器错误
-
-```json
-{
-  "code": 500,
-  "message": "failed",
-  "data": {},
-  "error": {
-    "error": "具体错误信息"
-  }
-}
-```
-
-## 功能说明
-
-### 变量提取规则
-
-1. 公式以等号(`=`)分割,只处理等号右侧的计算表达式
-2. 按照数学运算符分割表达式,提取所有token
-3. 只保留包含中文字符的token作为变量
-4. 数字会被自动过滤掉
-5. 相同的变量会去重,每个变量只查询一次
-
-### 元数据匹配规则
-
-1. 使用模糊匹配(CONTAINS)在Neo4j的DataMeta节点中查找
-2. 匹配字段为节点的`name`属性
-3. 每个变量只返回第一个匹配的结果
-4. 如果没有找到匹配,`findit`字段设置为0
-
-## 使用场景
-
-1. **指标定义验证**:在创建或编辑指标时,验证公式中的变量是否存在于元数据库中
-2. **数据血缘分析**:了解指标依赖哪些元数据
-3. **数据质量检查**:发现公式中引用但未在元数据库中定义的变量
-
-## 示例代码
-
-### Python (requests)
-
-```python
-import requests
-import json
-
-url = "http://your-server/api/data/metric/check"
-headers = {"Content-Type": "application/json"}
-data = {
-    "formula": "销售额 = 单价 * 数量 + 运费"
-}
-
-response = requests.post(url, headers=headers, json=data)
-result = response.json()
-
-print(json.dumps(result, indent=2, ensure_ascii=False))
-```
-
-### JavaScript (fetch)
-
-```javascript
-const url = 'http://your-server/api/data/metric/check';
-const data = {
-  formula: '销售额 = 单价 * 数量 + 运费'
-};
-
-fetch(url, {
-  method: 'POST',
-  headers: {
-    'Content-Type': 'application/json'
-  },
-  body: JSON.stringify(data)
-})
-  .then(response => response.json())
-  .then(result => console.log(result))
-  .catch(error => console.error('Error:', error));
-```
-
-### cURL
-
-```bash
-curl -X POST http://your-server/api/data/metric/check \
-  -H "Content-Type: application/json" \
-  -d '{"formula": "销售额 = 单价 * 数量 + 运费"}'
-```
-
-## 注意事项
-
-1. 公式必须包含等号(`=`),否则会返回错误
-2. 变量名必须包含中文字符才会被识别
-3. 元数据匹配使用模糊匹配,可能会匹配到相似的名称
-4. 数据库连接失败时会返回空数组
-5. 相同的变量名只会查询一次并返回一个结果
-
-## 更新历史
-
-- **2025-10-30**: 初始版本创建

+ 0 - 236
docs/api_data_label_delete.md

@@ -1,236 +0,0 @@
-# 数据标签删除接口文档
-
-## 接口信息
-
-- **接口路径**: `/api/data/label/delete`
-- **请求方法**: `POST`
-- **接口描述**: 删除指定的 DataLabel 节点及其所有关联关系
-
-## 请求参数
-
-### 请求体 (JSON)
-
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| id | integer | 是 | DataLabel 节点的ID |
-
-### 请求示例
-
-```json
-{
-  "id": 82
-}
-```
-
-## 响应格式
-
-### 成功响应
-
-**状态码**: 200
-
-**响应体**:
-```json
-{
-  "code": 200,
-  "data": {
-    "id": 82,
-    "message": "成功删除 DataLabel 节点 (ID: 82)"
-  },
-  "msg": "删除成功"
-}
-```
-
-### 失败响应
-
-#### 1. 节点ID为空
-
-**状态码**: 200
-
-**响应体**:
-```json
-{
-  "code": 500,
-  "data": {},
-  "msg": {
-    "error": "节点ID不能为空"
-  }
-}
-```
-
-#### 2. 节点ID类型错误
-
-**状态码**: 200
-
-**响应体**:
-```json
-{
-  "code": 500,
-  "data": {},
-  "msg": {
-    "error": "节点ID必须为整数"
-  }
-}
-```
-
-#### 3. 节点不存在
-
-**状态码**: 200
-
-**响应体**:
-```json
-{
-  "code": 500,
-  "data": {
-    "id": 999,
-    "message": "DataLabel 节点不存在 (ID: 999)"
-  },
-  "msg": "DataLabel 节点不存在 (ID: 999)"
-}
-```
-
-#### 4. 数据库连接失败
-
-**状态码**: 200
-
-**响应体**:
-```json
-{
-  "code": 500,
-  "data": {
-    "id": 82,
-    "message": "无法连接到数据库"
-  },
-  "msg": "无法连接到数据库"
-}
-```
-
-#### 5. 其他错误
-
-**状态码**: 200
-
-**响应体**:
-```json
-{
-  "code": 500,
-  "data": {},
-  "msg": {
-    "error": "删除失败: [具体错误信息]"
-  }
-}
-```
-
-## 功能说明
-
-### 删除操作
-
-1. **参数验证**:
-   - 验证节点ID是否存在
-   - 验证节点ID是否为有效整数
-
-2. **节点检查**:
-   - 检查节点是否存在于数据库中
-   - 验证节点是否为 DataLabel 类型
-
-3. **删除执行**:
-   - 使用 `DETACH DELETE` 删除节点
-   - 同时删除与该节点关联的所有关系
-
-4. **结果返回**:
-   - 返回删除成功或失败的状态信息
-
-### 删除的关系类型
-
-该接口会删除 DataLabel 节点的所有关系,包括但不限于:
-- `[:LABEL]` - 与 DataMetric 的标签关系
-- `[:TAG]` - 与 data_standard 的标签关系
-- 其他任何与该节点相关的关系
-
-## 使用示例
-
-### cURL 示例
-
-```bash
-curl -X POST http://your-server/api/data/label/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": 82}'
-```
-
-### Python 示例
-
-```python
-import requests
-import json
-
-url = "http://your-server/api/data/label/delete"
-data = {"id": 82}
-
-response = requests.post(url, json=data)
-result = response.json()
-
-if result['code'] == 200 and 'success' in result.get('msg', ''):
-    print(f"删除成功: {result['data']['message']}")
-else:
-    print(f"删除失败: {result.get('msg', 'Unknown error')}")
-```
-
-### JavaScript 示例
-
-```javascript
-fetch('http://your-server/api/data/label/delete', {
-  method: 'POST',
-  headers: {
-    'Content-Type': 'application/json'
-  },
-  body: JSON.stringify({ id: 82 })
-})
-  .then(response => response.json())
-  .then(data => {
-    if (data.code === 200 && data.msg === '删除成功') {
-      console.log('删除成功:', data.data.message);
-    } else {
-      console.error('删除失败:', data.msg);
-    }
-  })
-  .catch(error => {
-    console.error('请求失败:', error);
-  });
-```
-
-## 注意事项
-
-1. **不可逆操作**:删除操作是永久性的,无法撤销,请谨慎使用
-
-2. **关系清理**:删除节点时会自动清除所有关联关系,无需手动删除关系
-
-3. **节点类型限制**:该接口只能删除 DataLabel 类型的节点,其他类型的节点不会被删除
-
-4. **ID 格式**:节点ID必须是整数类型,字符串或其他类型会被拒绝
-
-5. **级联影响**:
-   - 删除 DataLabel 节点后,引用该标签的 DataMetric 节点将失去标签关联
-   - 建议在删除前检查是否有其他节点引用该标签
-
-6. **日志记录**:所有删除操作都会记录在应用日志中,便于审计和追踪
-
-## 错误处理
-
-接口内部实现了完整的错误处理机制:
-
-- 参数验证失败:返回明确的错误提示
-- 节点不存在:返回节点不存在的提示
-- 数据库连接失败:返回连接失败的提示
-- 其他异常:捕获并返回详细的错误信息
-
-所有错误都会记录在日志中,便于问题排查。
-
-## 相关接口
-
-- `POST /api/data/label/add` - 添加数据标签
-- `POST /api/data/label/list` - 获取数据标签列表
-- `POST /api/data/label/detail` - 获取数据标签详情
-- `POST /api/data/label/update` - 更新数据标签
-
-## 更新历史
-
-- 2025-01-XX: 初始版本创建
-

+ 0 - 264
docs/api_data_metric_delete.md

@@ -1,264 +0,0 @@
-# 数据指标删除接口文档
-
-## API 接口
-
-### 删除数据指标
-
-**接口路径**: `/api/data_metric/delete`
-
-**请求方法**: `POST`
-
-**请求参数**:
-
-| 参数名 | 类型 | 必填 | 说明 |
-|--------|------|------|------|
-| id | Integer | 是 | 指标节点ID |
-
-**请求示例**:
-
-```json
-{
-  "id": 1378
-}
-```
-
-**响应格式**:
-
-成功响应:
-```json
-{
-  "code": 200,
-  "msg": "删除成功",
-  "data": {
-    "id": 1378,
-    "message": "成功删除数据指标节点 (ID: 1378)"
-  }
-}
-```
-
-失败响应(节点不存在):
-```json
-{
-  "code": 500,
-  "msg": "数据指标节点不存在 (ID: 1378)",
-  "data": {
-    "id": 1378,
-    "message": "数据指标节点不存在 (ID: 1378)"
-  }
-}
-```
-
-失败响应(参数错误):
-```json
-{
-  "code": 500,
-  "msg": {
-    "error": "指标ID不能为空"
-  },
-  "data": {}
-}
-```
-
-## 核心功能
-
-### metric_delete 函数
-
-**位置**: `app/core/data_metric/metric_interface.py`
-
-**函数签名**:
-```python
-def metric_delete(metric_node_id: int) -> dict:
-    """
-    删除数据指标节点及其所有关联关系
-    
-    Args:
-        metric_node_id: 指标节点ID
-        
-    Returns:
-        dict: 删除结果,包含 success 状态和 message 信息
-    """
-```
-
-**功能说明**:
-1. 连接 Neo4j 图数据库
-2. 检查指定ID的 DataMetric 节点是否存在
-3. 使用 `DETACH DELETE` 删除节点及其所有关联关系
-4. 返回删除结果状态
-
-**返回值结构**:
-```python
-{
-    "success": True/False,  # 删除是否成功
-    "message": "状态信息"    # 详细的状态描述
-}
-```
-
-## 删除机制
-
-### Cypher 查询
-
-**检查节点存在**:
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-RETURN n
-```
-
-**删除节点和关系**:
-```cypher
-MATCH (n:DataMetric)
-WHERE id(n) = $nodeId
-DETACH DELETE n
-RETURN count(n) as deleted_count
-```
-
-### DETACH DELETE 说明
-
-`DETACH DELETE` 是 Neo4j 的关键字,功能包括:
-1. 自动删除节点的所有传入关系(incoming relationships)
-2. 自动删除节点的所有传出关系(outgoing relationships)
-3. 删除节点本身
-
-这意味着不需要手动遍历和删除关系,一条语句即可完成。
-
-## 删除的关系类型
-
-根据 DataMetric 节点的关系模型,删除时会自动清理以下关系:
-
-| 关系类型 | 方向 | 目标节点 | 说明 |
-|---------|------|---------|------|
-| origin | 传出 | DataModel / DataMetric | 指标来源 |
-| connection | 传出 | DataMeta | 元数据连接 |
-| LABEL | 传出 | DataLabel | 数据标签 |
-| child | 传出/传入 | DataMetric | 父子关系 |
-
-## 错误处理
-
-### 异常情况
-
-1. **数据库连接失败**
-   - 返回: `{"success": False, "message": "无法连接到数据库"}`
-   - HTTP 状态: 500
-
-2. **节点不存在**
-   - 返回: `{"success": False, "message": "数据指标节点不存在 (ID: xxx)"}`
-   - HTTP 状态: 500
-
-3. **参数错误**
-   - 缺少 ID: `{"error": "指标ID不能为空"}`
-   - 无效 ID: `{"error": "指标ID必须为整数"}`
-   - HTTP 状态: 500
-
-4. **删除异常**
-   - 返回: `{"success": False, "message": "删除失败: [错误详情]"}`
-   - HTTP 状态: 500
-
-## 使用示例
-
-### Python 请求示例
-
-```python
-import requests
-import json
-
-url = "http://localhost:5500/api/data_metric/delete"
-headers = {"Content-Type": "application/json"}
-data = {"id": 1378}
-
-response = requests.post(url, headers=headers, json=data)
-result = response.json()
-
-if result["code"] == 200:
-    print(f"删除成功: {result['data']['message']}")
-else:
-    print(f"删除失败: {result['msg']}")
-```
-
-### cURL 请求示例
-
-```bash
-curl -X POST http://localhost:5500/api/data_metric/delete \
-  -H "Content-Type: application/json" \
-  -d '{"id": 1378}'
-```
-
-### JavaScript/Fetch 示例
-
-```javascript
-fetch('http://localhost:5500/api/data_metric/delete', {
-  method: 'POST',
-  headers: {
-    'Content-Type': 'application/json'
-  },
-  body: JSON.stringify({ id: 1378 })
-})
-.then(response => response.json())
-.then(data => {
-  if (data.code === 200) {
-    console.log('删除成功:', data.data.message);
-  } else {
-    console.log('删除失败:', data.msg);
-  }
-})
-.catch(error => console.error('请求错误:', error));
-```
-
-## 日志记录
-
-删除操作会在以下情况记录日志:
-
-| 日志级别 | 场景 | 日志内容 |
-|---------|------|---------|
-| ERROR | 数据库连接失败 | "无法连接到数据库" |
-| WARNING | 节点不存在 | "数据指标节点不存在: ID={id}" |
-| INFO | 删除成功 | "成功删除数据指标节点: ID={id}" |
-| WARNING | 删除失败 | "删除失败,节点可能已被删除: ID={id}" |
-| ERROR | 异常错误 | "删除数据指标节点失败: {错误详情}" |
-
-## 注意事项
-
-⚠️ **重要提示**:
-
-1. **不可恢复**: 删除操作是永久性的,无法撤销
-2. **级联影响**: 删除会清除所有关联关系,可能影响其他节点的关系结构
-3. **权限控制**: 建议在生产环境中添加权限验证
-4. **审计日志**: 建议记录删除操作的审计信息(操作人、时间等)
-5. **业务验证**: 删除前应检查是否有其他业务逻辑依赖此节点
-
-## 最佳实践
-
-1. **删除前确认**: 在前端添加二次确认对话框
-2. **批量删除**: 如需批量删除,建议在循环中调用,并记录每次结果
-3. **事务管理**: 当前实现使用单次会话,确保原子性
-4. **错误通知**: 删除失败时应通知用户具体原因
-5. **软删除**: 对于重要数据,考虑实现软删除(标记而不是物理删除)
-
-## 测试建议
-
-### 单元测试场景
-
-1. 测试删除存在的节点
-2. 测试删除不存在的节点
-3. 测试无效的节点ID(非整数、null等)
-4. 测试数据库连接失败的情况
-5. 测试删除后关系是否被清理
-
-### 集成测试场景
-
-1. 创建节点 -> 删除节点 -> 验证删除
-2. 创建带关系的节点 -> 删除节点 -> 验证关系被清理
-3. 验证删除后其他相关节点的状态
-
-## 版本历史
-
-| 版本 | 日期 | 变更说明 |
-|------|------|---------|
-| 1.0 | 2025-11-03 | 初始版本,实现基本删除功能 |
-
-## 相关接口
-
-- `POST /api/data_metric/add` - 新增数据指标
-- `POST /api/data_metric/update` - 更新数据指标
-- `POST /api/data_metric/detail` - 查询指标详情
-- `POST /api/data_metric/list` - 指标列表查询
-

+ 0 - 0
docs/AUTO_TASK_EXECUTION_FIX.md → docs/archive/AUTO_TASK_EXECUTION_FIX.md


+ 0 - 0
CHECK_API_DIAGNOSTIC_REPORT.md → docs/archive/CHECK_API_DIAGNOSTIC_REPORT.md


+ 0 - 0
CORS_FIX_README.md → docs/archive/CORS_FIX_README.md


+ 0 - 0
docs/CURSOR_AUTO_EXECUTION_FIX.md → docs/archive/CURSOR_AUTO_EXECUTION_FIX.md


+ 0 - 0
docs/CURSOR_AUTO_TASK_EXECUTION.md → docs/archive/CURSOR_AUTO_TASK_EXECUTION.md


+ 0 - 0
docs/CURSOR_AUTO_TASK_TRIGGER.md → docs/archive/CURSOR_AUTO_TASK_TRIGGER.md


+ 0 - 0
CURSOR_FIX_README.md → docs/archive/CURSOR_FIX_README.md


+ 0 - 0
CURSOR_TASK_AUTOMATION_SUMMARY.md → docs/archive/CURSOR_TASK_AUTOMATION_SUMMARY.md


+ 0 - 0
DDL_PARSER_TIMEOUT_FIX.md → docs/archive/DDL_PARSER_TIMEOUT_FIX.md


+ 0 - 0
DDL_PARSE_FIX_SUMMARY.md → docs/archive/DDL_PARSE_FIX_SUMMARY.md


+ 0 - 0
docs/DDL_Parse_API修复说明.md → docs/archive/DDL_Parse_API修复说明.md


+ 0 - 0
docs/DDL_Parse_数组格式示例.json → docs/archive/DDL_Parse_数组格式示例.json


+ 0 - 0
docs/DDL_Parse_格式对比.json → docs/archive/DDL_Parse_格式对比.json


+ 0 - 0
docs/DDLparse格式.txt → docs/archive/DDLparse格式.txt


+ 0 - 0
docs/DataFlow_get_dataflow_by_id优化说明.md → docs/archive/DataFlow_get_dataflow_by_id优化说明.md


+ 0 - 0
docs/DataFlow_rule提取优化说明.md → docs/archive/DataFlow_rule提取优化说明.md


+ 0 - 0
docs/DataFlow_script_requirement优化说明.md → docs/archive/DataFlow_script_requirement优化说明.md


+ 0 - 0
docs/DataFlow_task_list优化说明.md → docs/archive/DataFlow_task_list优化说明.md


+ 0 - 0
docs/DataFlow_实施步骤优化说明.md → docs/archive/DataFlow_实施步骤优化说明.md


+ 0 - 0
N8N_WORKFLOW_SUMMARY.md → docs/archive/N8N_WORKFLOW_SUMMARY.md


+ 0 - 0
REMOVAL_SUMMARY_CLEAN_LIST.md → docs/archive/REMOVAL_SUMMARY_CLEAN_LIST.md


+ 0 - 0
TEST_REPORT_218.md → docs/archive/TEST_REPORT_218.md


+ 0 - 0
TRIGGER_OPTIMIZATION_SUMMARY.md → docs/archive/TRIGGER_OPTIMIZATION_SUMMARY.md


+ 0 - 0
WORKFLOW_UPDATE_SUMMARY.md → docs/archive/WORKFLOW_UPDATE_SUMMARY.md


+ 0 - 0
add_webpage_talent_api_docs.md → docs/archive/add_webpage_talent_api_docs.md


+ 0 - 0
get-parsed-talents-api-documentation.md → docs/archive/get-parsed-talents-api-documentation.md


+ 0 - 0
docs/get_calendar_info.txt → docs/archive/get_calendar_info.txt


+ 0 - 0
docs/n8n_chat_trigger_error_diagnosis.md → docs/archive/n8n_chat_trigger_error_diagnosis.md


+ 0 - 0
docs/n8n_internal_error_fix.md → docs/archive/n8n_internal_error_fix.md


+ 0 - 0
docs/n8n_tools_added_status.md → docs/archive/n8n_tools_added_status.md


+ 0 - 0
docs/n8n_workflow_enhancement_summary.md → docs/archive/n8n_workflow_enhancement_summary.md


+ 0 - 0
docs/n8n_workflow_test_report.md → docs/archive/n8n_workflow_test_report.md


+ 0 - 0
docs/n8n_workflow_test_success.md → docs/archive/n8n_workflow_test_success.md


+ 0 - 0
translate_api_documentation.md → docs/archive/translate_api_documentation.md


+ 0 - 0
verify_check_api.md → docs/archive/verify_check_api.md


+ 0 - 0
docs/数据结构(2).txt → docs/archive/数据结构(2).txt


+ 0 - 0
docs/每日签运需求.txt → docs/archive/每日签运需求.txt


+ 0 - 0
docs/科室对照表_原始.sql → docs/archive/科室对照表_原始.sql


+ 0 - 0
docs/返回格式.txt → docs/archive/返回格式.txt


+ 0 - 371
docs/diagrams/metric-check-flow.md

@@ -1,371 +0,0 @@
-# 指标公式检查流程图
-
-## 整体架构
-
-```
-┌─────────────┐
-│   前端界面   │
-│  (Browser)  │
-└──────┬──────┘
-       │ POST /api/data/metric/check
-       │ {"formula": "销售额 = 单价 * 数量"}
-       ↓
-┌──────────────────────────────────────┐
-│     API Layer (Flask Routes)          │
-│  app/api/data_metric/routes.py       │
-│                                       │
-│  @bp.route('/data/metric/check')     │
-│  def data_metric_check():            │
-│    - 接收请求参数                     │
-│    - 参数验证                         │
-│    - 调用核心函数                     │
-│    - 返回JSON响应                     │
-└──────────────┬───────────────────────┘
-               │
-               │ metric_check(formula_text)
-               ↓
-┌──────────────────────────────────────┐
-│   Business Logic Layer (Core)        │
-│  app/core/data_metric/               │
-│  metric_interface.py                 │
-│                                       │
-│  def metric_check(formula_text):     │
-│    1. 解析公式                        │
-│    2. 提取变量                        │
-│    3. 查询数据库                      │
-│    4. 返回结果                        │
-└──────────────┬───────────────────────┘
-               │
-               │ Cypher Query
-               ↓
-┌──────────────────────────────────────┐
-│     Data Layer (Neo4j)                │
-│                                       │
-│  MATCH (n:DataMeta)                  │
-│  WHERE n.name CONTAINS $variable     │
-│  RETURN n, id(n) as node_id          │
-│  LIMIT 1                             │
-└──────────────────────────────────────┘
-```
-
-## 详细流程
-
-### 1. 公式解析流程
-
-```
-输入公式: "销售额 = (单价 * 数量) + 运费 - 10"
-          │
-          ↓
-    按等号分割
-          │
-          ↓
-┌─────────────────────────┐
-│ 左侧: "销售额"           │ → 指标名称(暂不使用)
-│ 右侧: "(单价*数量)+运费-10"│ → 计算表达式
-└─────────┬───────────────┘
-          │
-          ↓
-    正则分割运算符
-    [+\-*/()()\[\]{}]
-          │
-          ↓
-┌─────────────────────────┐
-│ Tokens:                 │
-│ ["单价", "数量",         │
-│  "运费", "10"]          │
-└─────────┬───────────────┘
-          │
-          ↓
-    过滤中文变量
-          │
-          ↓
-┌─────────────────────────┐
-│ Variables:              │
-│ ["单价", "数量", "运费"]│
-└─────────┬───────────────┘
-          │
-          ↓
-      变量去重
-          │
-          ↓
-┌─────────────────────────┐
-│ Unique Variables:       │
-│ ["单价", "数量", "运费"]│
-└─────────────────────────┘
-```
-
-### 2. 数据库查询流程
-
-```
-变量列表: ["单价", "数量", "运费"]
-     │
-     ↓
-┌────────────────────────┐
-│  for variable in list  │
-└────┬───────────────────┘
-     │
-     ↓ 查询Neo4j
-┌──────────────────────────────┐
-│ MATCH (n:DataMeta)           │
-│ WHERE n.name CONTAINS "单价" │
-│ RETURN n, id(n)              │
-└────┬─────────────────────────┘
-     │
-     ↓ 是否找到?
-     │
-     ├─ 是 ─→ ┌────────────────────┐
-     │        │ {                  │
-     │        │   variable: "单价" │
-     │        │   name_zh: "单价"  │
-     │        │   name_en: "price" │
-     │        │   id: 101          │
-     │        │   findit: 1        │
-     │        │ }                  │
-     │        └────────────────────┘
-     │
-     └─ 否 ─→ ┌────────────────────┐
-              │ {                  │
-              │   variable: "运费" │
-              │   name_zh: ""      │
-              │   name_en: ""      │
-              │   id: null         │
-              │   findit: 0        │
-              │ }                  │
-              └────────────────────┘
-```
-
-### 3. 响应构建流程
-
-```
-查询结果列表
-     │
-     ↓
-┌──────────────────────────┐
-│ [                        │
-│   {variable:"单价",       │
-│    findit:1, id:101},    │
-│   {variable:"数量",       │
-│    findit:1, id:102},    │
-│   {variable:"运费",       │
-│    findit:0, id:null}    │
-│ ]                        │
-└────┬─────────────────────┘
-     │
-     ↓ 包装成API响应
-┌──────────────────────────┐
-│ {                        │
-│   code: 200,             │
-│   message: "success",    │
-│   data: [...]            │
-│ }                        │
-└────┬─────────────────────┘
-     │
-     ↓ JSON序列化
-     │
-     ↓ 返回给前端
-┌──────────────────────────┐
-│   前端显示结果            │
-│   ✓ 单价: 已找到          │
-│   ✓ 数量: 已找到          │
-│   ✗ 运费: 未找到          │
-└──────────────────────────┘
-```
-
-## 错误处理流程
-
-```
-请求到达
-    │
-    ↓
-┌───────────────┐
-│ 参数验证       │ ─┬─→ formula为空 ──→ 返回错误
-└───────┬───────┘  │
-        │          └─→ 无formula字段 ─→ 返回错误
-        ↓
-┌───────────────┐
-│ 公式验证       │ ──→ 无等号 ──→ 返回空数组
-└───────┬───────┘
-        │
-        ↓
-┌───────────────┐
-│ 变量提取       │ ──→ 无中文变量 ──→ 返回空数组
-└───────┬───────┘
-        │
-        ↓
-┌───────────────┐
-│ 数据库查询     │ ─┬─→ 连接失败 ──→ 返回空数组
-└───────┬───────┘  │
-        │          └─→ 查询异常 ──→ 记录日志,返回错误
-        │
-        ↓
-    正常返回
-```
-
-## 数据流图
-
-```
-┌─────────┐
-│  前端   │
-└────┬────┘
-     │
-     │ 1. 用户输入公式
-     │    "销售额 = 单价 * 数量"
-     │
-     ↓
-┌─────────────┐
-│  API接口    │
-└──────┬──────┘
-       │
-       │ 2. 解析并验证
-       │    formula_text = "销售额 = 单价 * 数量"
-       │
-       ↓
-┌──────────────────┐
-│  核心业务逻辑     │
-└───────┬──────────┘
-        │
-        │ 3. 提取变量
-        │    variables = ["单价", "数量"]
-        │
-        ↓
-┌──────────────┐
-│   Neo4j     │ ←─ 4. 查询元数据
-└──────┬───────┘    "MATCH (n:DataMeta) WHERE..."
-       │
-       │ 5. 返回节点数据
-       │    [{name:"单价", en_name:"price", id:101}, ...]
-       │
-       ↓
-┌──────────────────┐
-│  核心业务逻辑     │
-└───────┬──────────┘
-        │
-        │ 6. 格式化结果
-        │    [{variable:"单价", findit:1, id:101}, ...]
-        │
-        ↓
-┌─────────────┐
-│  API接口    │
-└──────┬──────┘
-       │
-       │ 7. JSON响应
-       │    {code:200, message:"success", data:[...]}
-       │
-       ↓
-┌─────────┐
-│  前端   │
-└─────────┘
-    显示结果
-```
-
-## 时序图
-
-```
-前端          API接口         核心函数         Neo4j
- │              │               │              │
- │─POST────────→│               │              │
- │ formula      │               │              │
- │              │               │              │
- │              │─调用─────────→│              │
- │              │ metric_check  │              │
- │              │               │              │
- │              │               │─解析公式────→│
- │              │               │              │
- │              │               │←提取变量─────│
- │              │               │ ["单价","数量"]│
- │              │               │              │
- │              │               │─查询: 单价──→│
- │              │               │              │
- │              │               │←返回节点────│
- │              │               │ {id:101,...} │
- │              │               │              │
- │              │               │─查询: 数量──→│
- │              │               │              │
- │              │               │←返回节点────│
- │              │               │ {id:102,...} │
- │              │               │              │
- │              │←返回结果─────│              │
- │              │ [findit:1,...]│              │
- │              │               │              │
- │←JSON响应────│               │              │
- │ {code:200}   │               │              │
- │              │               │              │
-```
-
-## 状态转换图
-
-```
-[开始]
-   │
-   ↓
-[接收请求] ─错误─→ [返回错误响应]
-   │
-   │ 成功
-   ↓
-[解析公式] ─无等号─→ [返回空数组]
-   │
-   │ 有等号
-   ↓
-[提取变量] ─无变量─→ [返回空数组]
-   │
-   │ 有变量
-   ↓
-[查询数据库] ─连接失败─→ [返回空数组]
-   │
-   │ 连接成功
-   ↓
-[处理每个变量]
-   │
-   ├─找到匹配─→ [findit=1]
-   │              │
-   └─未找到───→ [findit=0]
-                  │
-                  ↓
-             [收集结果]
-                  │
-                  ↓
-             [返回成功响应]
-                  │
-                  ↓
-                [结束]
-```
-
-## 关键决策点
-
-```
-1. 公式是否有效?
-   ├─ 是: 继续
-   └─ 否: 返回错误
-
-2. 是否提取到变量?
-   ├─ 是: 查询数据库
-   └─ 否: 返回空数组
-
-3. 数据库连接是否正常?
-   ├─ 是: 执行查询
-   └─ 否: 返回空数组
-
-4. 是否找到匹配?
-   ├─ 是: findit=1
-   └─ 否: findit=0
-
-5. 是否还有更多变量?
-   ├─ 是: 继续查询
-   └─ 否: 返回结果
-```
-
----
-
-**说明**:
-- 实线箭头 (→): 正常流程
-- 虚线箭头 (⇢): 数据流
-- 分支 (├─): 决策点
-- 单向箭头 (↓): 顺序执行
-
-
-
-
-
-
-
-

+ 0 - 577
docs/examples/metric-check-examples.md

@@ -1,577 +0,0 @@
-# 指标公式检查 - 使用示例
-
-## 基础示例
-
-### 示例1: 简单的加法公式
-
-**输入**:
-```json
-{
-  "formula": "销售总额 = 单价 + 数量"
-}
-```
-
-**输出**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "单价",
-      "name_zh": "单价",
-      "name_en": "unit_price",
-      "id": 101,
-      "create_time": "2024-01-15 10:00:00",
-      "findit": 1
-    },
-    {
-      "variable": "数量",
-      "name_zh": "数量",
-      "name_en": "quantity",
-      "id": 102,
-      "create_time": "2024-01-15 10:01:00",
-      "findit": 1
-    }
-  ]
-}
-```
-
-### 示例2: 包含乘法的公式
-
-**输入**:
-```json
-{
-  "formula": "销售额 = 单价 * 数量"
-}
-```
-
-**说明**: 系统会提取 "单价" 和 "数量" 两个变量并在数据库中查找。
-
-### 示例3: 复杂的计算公式
-
-**输入**:
-```json
-{
-  "formula": "利润率 = (销售收入 - 成本) / 销售收入 * 100"
-}
-```
-
-**输出**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "销售收入",
-      "name_zh": "销售收入",
-      "name_en": "sales_revenue",
-      "id": 201,
-      "create_time": "2024-02-01 14:30:00",
-      "findit": 1
-    },
-    {
-      "variable": "成本",
-      "name_zh": "成本",
-      "name_en": "cost",
-      "id": 202,
-      "create_time": "2024-02-01 14:31:00",
-      "findit": 1
-    }
-  ]
-}
-```
-
-**说明**: 数字 "100" 会被自动过滤,不会出现在结果中。
-
-## 特殊情况示例
-
-### 示例4: 变量未找到
-
-**输入**:
-```json
-{
-  "formula": "总成本 = 原材料成本 + 人工成本 + 其他费用"
-}
-```
-
-**输出**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "原材料成本",
-      "name_zh": "原材料成本",
-      "name_en": "material_cost",
-      "id": 301,
-      "create_time": "2024-03-01 09:00:00",
-      "findit": 1
-    },
-    {
-      "variable": "人工成本",
-      "name_zh": "人工成本",
-      "name_en": "labor_cost",
-      "id": 302,
-      "create_time": "2024-03-01 09:01:00",
-      "findit": 1
-    },
-    {
-      "variable": "其他费用",
-      "name_zh": "",
-      "name_en": "",
-      "id": null,
-      "create_time": "",
-      "findit": 0
-    }
-  ]
-}
-```
-
-**前端处理建议**:
-```javascript
-data.data.forEach(item => {
-  if (item.findit === 0) {
-    // 提示用户该变量未找到
-    alert(`变量 "${item.variable}" 未在元数据库中找到,是否需要创建?`);
-  }
-});
-```
-
-### 示例5: 中文括号
-
-**输入**:
-```json
-{
-  "formula": "总额 = (收入 + 支出)* 汇率"
-}
-```
-
-**说明**: 支持中文括号(),会正确提取 "收入"、"支出" 和 "汇率" 三个变量。
-
-### 示例6: 混合括号
-
-**输入**:
-```json
-{
-  "formula": "结果 = [收入 + (支出 * 税率)] / 总数"
-}
-```
-
-**说明**: 支持方括号 [] 和圆括号 () 混用。
-
-### 示例7: 纯数字公式
-
-**输入**:
-```json
-{
-  "formula": "常量 = 100 + 200 * 3"
-}
-```
-
-**输出**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": []
-}
-```
-
-**说明**: 没有中文变量,返回空数组。
-
-### 示例8: 重复变量
-
-**输入**:
-```json
-{
-  "formula": "总计 = 单价 + 单价 * 0.1"
-}
-```
-
-**输出**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "单价",
-      "name_zh": "单价",
-      "name_en": "unit_price",
-      "id": 101,
-      "create_time": "2024-01-15 10:00:00",
-      "findit": 1
-    }
-  ]
-}
-```
-
-**说明**: 重复的变量会自动去重,只返回一次。
-
-## 错误处理示例
-
-### 示例9: 缺少等号
-
-**输入**:
-```json
-{
-  "formula": "销售额"
-}
-```
-
-**输出**:
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": []
-}
-```
-
-**说明**: 缺少等号会返回空数组,不会报错。
-
-### 示例10: 空公式
-
-**输入**:
-```json
-{
-  "formula": ""
-}
-```
-
-**输出**:
-```json
-{
-  "code": 400,
-  "message": "failed",
-  "data": {},
-  "error": {
-    "error": "公式文本不能为空"
-  }
-}
-```
-
-### 示例11: 缺少参数
-
-**输入**:
-```json
-{}
-```
-
-**输出**:
-```json
-{
-  "code": 400,
-  "message": "failed",
-  "data": {},
-  "error": {
-    "error": "公式文本不能为空"
-  }
-}
-```
-
-## 实际应用场景
-
-### 场景1: 指标创建时的验证
-
-```javascript
-// 前端代码示例
-function validateMetricFormula(formula) {
-  fetch('/api/data/metric/check', {
-    method: 'POST',
-    headers: {'Content-Type': 'application/json'},
-    body: JSON.stringify({formula})
-  })
-  .then(res => res.json())
-  .then(result => {
-    if (result.code === 200) {
-      const notFound = result.data.filter(item => item.findit === 0);
-      
-      if (notFound.length > 0) {
-        // 显示警告
-        const variables = notFound.map(item => item.variable).join(', ');
-        showWarning(`以下变量未找到:${variables}`);
-        
-        // 询问用户是否继续
-        if (confirm('是否继续创建指标?')) {
-          createMetric(formula);
-        }
-      } else {
-        // 所有变量都找到,直接创建
-        createMetric(formula);
-      }
-    }
-  });
-}
-```
-
-### 场景2: 实时检查和提示
-
-```javascript
-// 前端代码示例 - 输入时实时检查
-let checkTimeout;
-document.getElementById('formulaInput').addEventListener('input', (e) => {
-  clearTimeout(checkTimeout);
-  
-  checkTimeout = setTimeout(() => {
-    const formula = e.target.value;
-    
-    if (formula.includes('=')) {
-      fetch('/api/data/metric/check', {
-        method: 'POST',
-        headers: {'Content-Type': 'application/json'},
-        body: JSON.stringify({formula})
-      })
-      .then(res => res.json())
-      .then(result => {
-        if (result.code === 200) {
-          displayCheckResults(result.data);
-        }
-      });
-    }
-  }, 500); // 500ms防抖
-});
-
-function displayCheckResults(data) {
-  const container = document.getElementById('checkResults');
-  container.innerHTML = '';
-  
-  data.forEach(item => {
-    const div = document.createElement('div');
-    div.className = item.findit ? 'found' : 'not-found';
-    div.innerHTML = `
-      <span>${item.variable}</span>
-      ${item.findit ? 
-        `<span class="success">✓ ${item.name_zh} (${item.name_en})</span>` :
-        `<span class="error">✗ 未找到</span>`
-      }
-    `;
-    container.appendChild(div);
-  });
-}
-```
-
-### 场景3: 批量检查多个指标
-
-```python
-# Python批量检查示例
-import requests
-
-formulas = [
-    "销售额 = 单价 * 数量",
-    "利润 = 收入 - 成本",
-    "利润率 = 利润 / 收入 * 100"
-]
-
-results = []
-for formula in formulas:
-    response = requests.post(
-        'http://localhost:5000/api/data/metric/check',
-        json={'formula': formula}
-    )
-    result = response.json()
-    
-    if result['code'] == 200:
-        not_found = [item for item in result['data'] if item['findit'] == 0]
-        results.append({
-            'formula': formula,
-            'status': 'ok' if len(not_found) == 0 else 'warning',
-            'missing_variables': [item['variable'] for item in not_found]
-        })
-
-# 生成报告
-for item in results:
-    print(f"公式: {item['formula']}")
-    print(f"状态: {item['status']}")
-    if item['missing_variables']:
-        print(f"缺失变量: {', '.join(item['missing_variables'])}")
-    print('---')
-```
-
-### 场景4: 与其他功能集成
-
-```javascript
-// 指标编辑器示例
-class MetricEditor {
-  constructor() {
-    this.formula = '';
-    this.checkResults = [];
-  }
-  
-  // 设置公式
-  setFormula(formula) {
-    this.formula = formula;
-    this.checkFormula();
-  }
-  
-  // 检查公式
-  async checkFormula() {
-    if (!this.formula.includes('=')) return;
-    
-    const response = await fetch('/api/data/metric/check', {
-      method: 'POST',
-      headers: {'Content-Type': 'application/json'},
-      body: JSON.stringify({formula: this.formula})
-    });
-    
-    const result = await response.json();
-    this.checkResults = result.data || [];
-    this.updateUI();
-  }
-  
-  // 更新界面
-  updateUI() {
-    // 高亮显示未找到的变量
-    const notFound = this.checkResults.filter(item => item.findit === 0);
-    notFound.forEach(item => {
-      this.highlightVariable(item.variable, 'error');
-    });
-    
-    // 显示统计
-    const total = this.checkResults.length;
-    const found = this.checkResults.filter(item => item.findit === 1).length;
-    this.showStats(`${found}/${total} 个变量已找到`);
-  }
-  
-  // 是否可以保存
-  canSave() {
-    const notFound = this.checkResults.filter(item => item.findit === 0);
-    return notFound.length === 0;
-  }
-  
-  // 获取缺失的变量
-  getMissingVariables() {
-    return this.checkResults
-      .filter(item => item.findit === 0)
-      .map(item => item.variable);
-  }
-}
-```
-
-## Python客户端完整示例
-
-```python
-import requests
-import json
-
-class MetricChecker:
-    def __init__(self, base_url):
-        self.base_url = base_url
-        self.api_url = f"{base_url}/api/data/metric/check"
-    
-    def check(self, formula):
-        """检查单个公式"""
-        try:
-            response = requests.post(
-                self.api_url,
-                json={'formula': formula},
-                headers={'Content-Type': 'application/json'}
-            )
-            return response.json()
-        except Exception as e:
-            return {'error': str(e)}
-    
-    def check_batch(self, formulas):
-        """批量检查多个公式"""
-        results = []
-        for formula in formulas:
-            result = self.check(formula)
-            results.append({
-                'formula': formula,
-                'result': result
-            })
-        return results
-    
-    def get_missing_variables(self, formula):
-        """获取缺失的变量列表"""
-        result = self.check(formula)
-        if result.get('code') == 200:
-            return [
-                item['variable'] 
-                for item in result.get('data', []) 
-                if item['findit'] == 0
-            ]
-        return []
-    
-    def validate(self, formula):
-        """验证公式是否完全可用"""
-        missing = self.get_missing_variables(formula)
-        return len(missing) == 0, missing
-    
-    def format_report(self, formula):
-        """生成格式化的报告"""
-        result = self.check(formula)
-        
-        if result.get('code') != 200:
-            return f"错误: {result.get('error', '未知错误')}"
-        
-        report = [f"公式: {formula}", "=" * 50]
-        
-        data = result.get('data', [])
-        if not data:
-            report.append("未找到任何变量")
-        else:
-            for item in data:
-                status = "✓" if item['findit'] == 1 else "✗"
-                name = item['name_zh'] if item['findit'] == 1 else "未找到"
-                report.append(f"{status} {item['variable']}: {name}")
-        
-        return "\n".join(report)
-
-# 使用示例
-if __name__ == '__main__':
-    checker = MetricChecker('http://localhost:5000')
-    
-    # 示例1: 检查单个公式
-    print("示例1: 检查单个公式")
-    print(checker.format_report("销售额 = 单价 * 数量"))
-    print()
-    
-    # 示例2: 验证公式
-    print("示例2: 验证公式")
-    formula = "利润 = 收入 - 成本"
-    is_valid, missing = checker.validate(formula)
-    if is_valid:
-        print(f"✓ 公式有效: {formula}")
-    else:
-        print(f"✗ 公式无效,缺少变量: {', '.join(missing)}")
-    print()
-    
-    # 示例3: 批量检查
-    print("示例3: 批量检查")
-    formulas = [
-        "销售额 = 单价 * 数量",
-        "成本 = 原材料 + 人工",
-        "利润率 = 利润 / 销售额"
-    ]
-    results = checker.check_batch(formulas)
-    for item in results:
-        print(f"公式: {item['formula']}")
-        data = item['result'].get('data', [])
-        found = sum(1 for x in data if x['findit'] == 1)
-        total = len(data)
-        print(f"  变量匹配: {found}/{total}")
-        print()
-```
-
-## 总结
-
-这些示例展示了:
-1. 基础用法和各种公式格式
-2. 特殊情况和边界条件处理
-3. 错误处理机制
-4. 实际应用场景的集成方式
-5. 前后端完整的使用示例
-
-根据实际需求,可以灵活调整和扩展这些示例代码。
-
-
-
-
-
-
-
-

+ 0 - 255
docs/features/metric-formula-check.md

@@ -1,255 +0,0 @@
-# 指标公式检查功能
-
-## 功能概述
-
-该功能用于解析和验证指标计算公式,自动提取公式中的变量并在Neo4j数据库中查找匹配的元数据记录。
-
-## 实现文件
-
-### 1. 核心业务逻辑
-
-**文件**: `app/core/data_metric/metric_interface.py`
-
-**函数**: `metric_check(formula_text)`
-
-#### 功能描述
-- 解析输入的计算公式文本
-- 按运算符拆解公式,提取中文变量
-- 在Neo4j数据库中查找匹配的元数据
-- 返回变量及其匹配结果的JSON数组
-
-#### 输入参数
-- `formula_text` (str): 计算公式文本,格式为 "指标名称 = 计算表达式"
-
-#### 返回值
-```python
-[
-    {
-        "variable": "变量名",
-        "name_zh": "中文名称",
-        "name_en": "英文名称",
-        "id": 节点ID,
-        "create_time": "创建时间",
-        "findit": 1或0
-    }
-]
-```
-
-#### 核心实现逻辑
-
-1. **公式解析**
-   - 按等号分割,提取等号右侧的计算表达式
-   - 使用正则表达式识别运算符:`+`, `-`, `*`, `/`, `()`, `()`, `[]`, `{}`
-   - 按运算符分割表达式,提取所有token
-
-2. **变量识别**
-   - 过滤包含中文字符的token作为变量
-   - 自动过滤纯数字
-   - 对相同变量去重
-
-3. **元数据匹配**
-   - 使用Neo4j的CONTAINS进行模糊匹配
-   - 匹配DataMeta节点的name属性
-   - 每个变量返回第一个匹配结果
-   - 未找到时返回findit=0
-
-### 2. API接口
-
-**文件**: `app/api/data_metric/routes.py`
-
-**路由**: `/api/data/metric/check`
-
-**方法**: `POST`
-
-#### 请求示例
-```json
-{
-  "formula": "销售额 = 单价 * 数量 + 运费"
-}
-```
-
-#### 响应示例
-```json
-{
-  "code": 200,
-  "message": "success",
-  "data": [
-    {
-      "variable": "单价",
-      "name_zh": "单价",
-      "name_en": "unit_price",
-      "id": 12345,
-      "create_time": "2024-01-15 10:30:00",
-      "findit": 1
-    },
-    {
-      "variable": "数量",
-      "name_zh": "数量",
-      "name_en": "quantity",
-      "id": 12346,
-      "create_time": "2024-01-15 10:31:00",
-      "findit": 1
-    },
-    {
-      "variable": "运费",
-      "name_zh": "",
-      "name_en": "",
-      "id": null,
-      "create_time": "",
-      "findit": 0
-    }
-  ]
-}
-```
-
-## 技术特点
-
-### 1. 灵活的公式解析
-- 支持多种运算符:加、减、乘、除、括号
-- 支持中英文括号
-- 自动过滤数字
-- 智能提取中文变量
-
-### 2. 智能匹配
-- 使用Neo4j的CONTAINS实现模糊匹配
-- 每个变量只查询一次,提高效率
-- 明确标识是否找到匹配(findit字段)
-
-### 3. 错误处理
-- 完善的异常处理机制
-- 详细的日志记录
-- 友好的错误提示
-
-### 4. 性能优化
-- 变量自动去重
-- 使用Neo4j session批量查询
-- 限制每个变量只返回一个结果(LIMIT 1)
-
-## 使用场景
-
-### 1. 指标定义验证
-在创建或编辑指标时,验证公式中的变量是否都存在于元数据库中。
-
-```python
-# 前端发送公式
-formula = "利润率 = (销售收入 - 成本) / 销售收入 * 100"
-
-# 后端返回检查结果
-# findit=1: 变量存在
-# findit=0: 变量不存在,需要用户确认或创建
-```
-
-### 2. 数据血缘分析
-了解指标依赖哪些基础元数据,便于进行数据血缘追踪。
-
-### 3. 数据质量检查
-发现公式中引用但未在元数据库中定义的变量,提前预防数据质量问题。
-
-### 4. 智能提示
-为用户提供变量的详细信息(中文名、英文名、创建时间等),辅助指标定义。
-
-## 示例场景
-
-### 场景1: 所有变量都存在
-```
-输入: "销售额 = 单价 * 数量"
-结果: 单价和数量都在元数据库中找到(findit=1)
-操作: 可以直接保存指标
-```
-
-### 场景2: 部分变量不存在
-```
-输入: "总成本 = 原材料成本 + 人工成本 + 其他费用"
-结果: 原材料成本和人工成本找到,其他费用未找到(findit=0)
-操作: 提示用户"其他费用"未定义,询问是否创建新元数据
-```
-
-### 场景3: 复杂计算公式
-```
-输入: "ROI = (投资收益 - 投资成本) / 投资成本 * 100"
-结果: 返回投资收益和投资成本的匹配状态
-操作: 根据匹配结果决定是否可以保存
-```
-
-## 扩展建议
-
-### 1. 增强匹配算法
-- 实现更智能的模糊匹配(编辑距离、拼音匹配)
-- 支持同义词匹配
-- 返回多个可能的匹配结果供用户选择
-
-### 2. 公式验证
-- 验证公式的语法正确性
-- 检查运算符使用是否合理
-- 验证变量类型是否匹配运算要求
-
-### 3. 自动建议
-- 基于历史数据推荐相似的变量
-- 提供常用公式模板
-- 智能补全变量名
-
-### 4. 可视化
-- 显示变量之间的依赖关系图
-- 标注未找到的变量
-- 提供交互式的公式编辑器
-
-## 测试
-
-测试文件位于: `tests/test_metric_check.py`
-
-包含以下测试用例:
-1. 简单公式测试
-2. 复杂公式测试(多种运算符)
-3. 无等号公式测试
-4. 纯数字公式测试
-5. 中文括号测试
-6. 数据库连接失败测试
-7. API接口测试
-
-运行测试:
-```bash
-python -m pytest tests/test_metric_check.py -v
-```
-
-## 依赖
-
-- Flask: Web框架
-- Neo4j: 图数据库
-- py2neo: Neo4j Python驱动
-- re: 正则表达式库(Python标准库)
-
-## 配置
-
-无需额外配置,使用现有的Neo4j数据库连接。
-
-## 日志
-
-使用应用级别的日志记录器,关键操作都有日志记录:
-- 公式解析开始
-- 变量提取结果
-- 数据库查询
-- 匹配结果
-- 错误信息
-
-## 维护建议
-
-1. **定期检查日志**:了解用户常用的公式模式和未匹配的变量
-2. **优化匹配规则**:根据实际使用情况调整匹配算法
-3. **更新文档**:保持API文档与实现同步
-4. **性能监控**:监控查询性能,必要时添加索引
-
-## 更新历史
-
-- **2024-10-30**: 初始版本
-  - 实现基础公式解析功能
-  - 实现Neo4j元数据匹配
-  - 创建API接口
-  - 编写测试用例和文档
-
-
-
-
-
-
-
-

+ 0 - 232
docs/路由简化报告_data_metric.md

@@ -1,232 +0,0 @@
-# 路由简化完成报告 - data_metric
-
-## 📋 修改内容
-
-已成功将 `app/api/data_metric/routes.py` 中的所有路由简化,去掉了冗余的 `/data/metric` 前缀。
-
----
-
-## 🔄 修改对比
-
-| 原路由 | 新路由 | 完整访问路径 |
-|--------|--------|------------|
-| `/data/metric/relation` | `/relation` | `/api/metric/relation` |
-| `/data/metric/add` | `/add` | `/api/metric/add` |
-| `/data/metric/code` | `/code` | `/api/metric/code` |
-| `/data/metric/detail` | `/detail` | `/api/metric/detail` |
-| `/data/metric/list` | `/list` | `/api/metric/list` |
-| `/data/metric/graph/all` | `/graph/all` | `/api/metric/graph/all` |
-| `/data/metric/list/graph` | `/list/graph` | `/api/metric/list/graph` |
-| `/data/metric/update` | `/update` | `/api/metric/update` |
-| `/data/metric/check` | `/check` | `/api/metric/check` |
-
----
-
-## 📊 修改统计
-
-- **修改文件数**: 1
-- **修改路由数**: 9
-- **路由简化**: 从 `/data/metric/<endpoint>` → `/<endpoint>`
-- **Blueprint前缀**: `/api/metric` (保持不变)
-- **最终路径格式**: `/api/metric/<endpoint>` ✅
-
----
-
-## 🎯 优化效果
-
-### 修改前示例
-```
-Blueprint: /api/metric
-路由: /data/metric/check
-完整路径: /api/metric/data/metric/check  ❌ 路径重复
-```
-
-### 修改后示例
-```
-Blueprint: /api/metric
-路由: /check
-完整路径: /api/metric/check  ✅ 简洁明了
-```
-
----
-
-## 📝 所有接口路径(修改后)
-
-### 1. 指标关系管理
-**POST** `/api/metric/relation`
-- **功能**: 处理数据指标血缘关系
-- **请求参数**: 
-  - `id`: 数据模型ID
-- **返回**: 血缘关系数据
-
-### 2. 指标新增
-**POST** `/api/metric/add`
-- **功能**: 新增数据指标
-- **请求参数**: 
-  - `name`: 指标名称
-  - 其他指标相关属性
-- **返回**: 处理结果
-
-### 3. 代码生成
-**POST** `/api/metric/code`
-- **功能**: 生成指标计算代码
-- **请求参数**: 
-  - `content`: 指标规则描述
-  - `relation`: 映射关系
-- **返回**: 生成的代码
-
-### 4. 指标详情
-**POST** `/api/metric/detail`
-- **功能**: 获取数据指标详情
-- **请求参数**: 
-  - `id`: 指标ID
-- **返回**: 指标详情数据
-
-### 5. 指标列表
-**POST** `/api/metric/list`
-- **功能**: 获取数据指标列表
-- **请求参数**: 
-  - `current`: 当前页码(默认1)
-  - `size`: 每页大小(默认10)
-  - `name_en`: 英文名称过滤
-  - `name_zh`: 名称过滤
-  - `category`: 类别过滤
-  - `time`: 时间过滤
-  - `tag`: 标签过滤
-- **返回**: 指标列表数据和分页信息
-
-### 6. 指标图谱(全量)
-**POST** `/api/metric/graph/all`
-- **功能**: 获取数据指标图谱
-- **请求参数**: 
-  - `id`: 指标ID
-  - `type`: 图谱类型(kinship/impact/all)
-  - `meta`: 是否返回元数据(true/false)
-- **返回**: 图谱数据
-
-### 7. 指标列表图谱
-**POST** `/api/metric/list/graph`
-- **功能**: 获取数据指标列表图谱
-- **请求参数**: 
-  - `tag`: 标签ID
-- **返回**: 图谱数据(包含节点和连线)
-
-### 8. 指标更新
-**POST** `/api/metric/update`
-- **功能**: 更新数据指标
-- **请求参数**: 
-  - `id`: 指标ID
-  - 其他需要更新的属性
-- **返回**: 处理结果
-
-### 9. 公式检查
-**POST** `/api/metric/check`
-- **功能**: 检查指标计算公式中的变量
-- **请求参数**: 
-  - `formula`: 指标计算公式文本,格式如:`指标名称 = 变量1 + 变量2 * 数字`
-- **返回**: 变量检查结果列表
-- **响应示例**:
-  ```json
-  {
-    "code": 200,
-    "message": "success",
-    "data": [
-      {
-        "variable": "变量名",
-        "name_zh": "中文名称",
-        "name_en": "英文名称",
-        "id": "变量ID",
-        "create_time": "创建时间",
-        "findit": 0
-      }
-    ]
-  }
-  ```
-
----
-
-## ⚠️ 重要提示
-
-### 需要重启Flask应用
-
-修改后的路由需要重启Flask应用才能生效!
-
-**操作步骤**:
-1. 重启Flask应用服务
-2. 清除可能的缓存
-3. 验证新路径是否可访问
-
----
-
-## ✅ 验证测试
-
-### 测试脚本
-
-重启服务后,可以使用以下Python脚本测试新路径:
-
-```python
-import requests
-import urllib3
-
-# 禁用SSL警告
-urllib3.disable_warnings()
-
-# 测试接口
-url = "https://company.citupro.com:18183/api/metric/check"
-data = {"formula": "测试 = 变量1 + 变量2"}
-
-response = requests.post(url, json=data, verify=False)
-print(f"Status: {response.status_code}")
-print(response.json())
-```
-
-### 预期结果
-
-- **状态码**: `200` ✅
-- **响应**: JSON格式的变量检查结果
-
-### 使用curl测试
-
-```bash
-curl -k -X POST https://company.citupro.com:18183/api/metric/check \
-  -H "Content-Type: application/json" \
-  -d '{"formula": "测试 = 变量1 + 变量2"}'
-```
-
----
-
-## 📈 收益与改进
-
-### 优化收益
-
-1. **路径更简洁**: 去除了冗余的 `/data/metric` 前缀
-2. **RESTful规范**: 符合标准的RESTful API设计
-3. **维护性提升**: 路径结构更清晰,易于理解和维护
-4. **可读性增强**: 完整路径从 `/api/metric/data/metric/xxx` 简化为 `/api/metric/xxx`
-
-### 后续建议
-
-考虑对其他API模块进行类似的路由简化:
-- `app/api/data_model/routes.py`
-- `app/api/data_resource/routes.py`
-- `app/api/data_interface/routes.py`
-- 等其他模块
-
----
-
-## 📅 修改记录
-
-- **修改时间**: 2025年10月31日
-- **修改人**: AI Assistant
-- **修改类型**: 路由简化优化
-- **影响范围**: `app/api/data_metric/routes.py` 所有9个路由
-- **向后兼容**: ⚠️ 不兼容旧路径,需要更新前端调用
-
----
-
-## ✅ 完成状态
-
-**路由简化工作已完成!** 所有路径现在更加简洁、符合RESTful规范。🎉
-
-**下一步**: 重启Flask应用并验证所有接口正常工作。
-

+ 0 - 80
explore_api_218.py

@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-探索 192.168.3.218:18183 服务器的 API 结构
-"""
-
-import requests
-import json
-
-BASE_URL = "http://192.168.3.218:18183"
-
-def test_endpoint(path, method="GET", params=None):
-    """测试一个端点"""
-    url = f"{BASE_URL}{path}"
-    print(f"\n{'='*60}")
-    print(f"测试: {method} {url}")
-    if params:
-        print(f"参数: {params}")
-    print('='*60)
-    
-    try:
-        if method == "GET":
-            response = requests.get(url, params=params, timeout=5)
-        elif method == "POST":
-            response = requests.post(url, json=params, timeout=5)
-        
-        print(f"状态码: {response.status_code}")
-        print(f"响应时间: {response.elapsed.total_seconds():.3f}秒")
-        
-        try:
-            data = response.json()
-            print("响应内容:")
-            print(json.dumps(data, indent=2, ensure_ascii=False))
-        except:
-            print("响应内容:")
-            print(response.text[:500])
-            
-    except Exception as e:
-        print(f"错误: {str(e)}")
-
-print("\n" + "🔍 探索 API 结构 ".center(70, "="))
-print(f"服务器: {BASE_URL}")
-
-# 测试常见的根路径
-print("\n\n📡 测试根路径...")
-test_endpoint("/")
-
-# 测试 API 路径
-print("\n\n📡 测试可能的 API 路径...")
-test_endpoint("/api")
-
-# 测试元数据相关路径
-print("\n\n📡 测试元数据相关路径...")
-possible_paths = [
-    "/api/meta",
-    "/api/metadata",
-    "/meta",
-    "/metadata",
-    "/api/meta/list",
-    "/api/meta/node/list",
-]
-
-for path in possible_paths:
-    test_endpoint(path)
-
-# 测试 health check
-print("\n\n📡 测试健康检查...")
-test_endpoint("/health")
-test_endpoint("/api/health")
-
-print("\n\n" + "="*70)
-print("探索完成!")
-print("="*70)
-
-
-
-
-
-
-

+ 0 - 67
explore_api_endpoints.py

@@ -1,67 +0,0 @@
-import requests
-import json
-
-def explore_api_endpoints():
-    """探索服务器上可用的API端点"""
-    
-    base_url = "http://192.168.3.143:5500"
-    
-    # 常见的API路径模式
-    possible_paths = [
-        "/",
-        "/api",
-        "/api/",
-        "/api/data-parse",
-        "/api/data-parse/",
-        "/health",
-        "/status",
-        "/docs",
-        "/swagger",
-        "/openapi",
-        "/process-urls",
-        "/api/process-urls",
-        "/data-parse/process-urls",
-        "/api/data-parse/process-urls"
-    ]
-    
-    print(f"正在探索服务器: {base_url}")
-    print("=" * 60)
-    
-    for path in possible_paths:
-        url = base_url + path
-        try:
-            print(f"\n🔍 测试路径: {path}")
-            
-            # 尝试GET请求
-            try:
-                response = requests.get(url, timeout=10)
-                print(f"  GET {path} -> 状态码: {response.status_code}")
-                if response.status_code == 200:
-                    print(f"  响应内容: {response.text[:200]}...")
-            except Exception as e:
-                print(f"  GET {path} -> 错误: {e}")
-            
-            # 尝试POST请求(对于process-urls接口)
-            if "process-urls" in path:
-                try:
-                    test_data = {"urlArr": ["https://example.com"]}
-                    response = requests.post(
-                        url, 
-                        json=test_data, 
-                        headers={"Content-Type": "application/json"},
-                        timeout=10
-                    )
-                    print(f"  POST {path} -> 状态码: {response.status_code}")
-                    if response.status_code != 404:
-                        print(f"  响应内容: {response.text[:200]}...")
-                except Exception as e:
-                    print(f"  POST {path} -> 错误: {e}")
-                    
-        except Exception as e:
-            print(f"  ❌ 测试失败: {e}")
-    
-    print("\n" + "=" * 60)
-    print("探索完成!")
-
-if __name__ == "__main__":
-    explore_api_endpoints() 

+ 0 - 88
migrate_meta_data_type.py

@@ -1,88 +0,0 @@
-"""
-元数据属性迁移脚本:将meta_data节点的type属性改为data_type属性
-运行方式:python migrate_meta_data_type.py
-"""
-
-import logging
-import sys
-import time
-from app.services.neo4j_driver import neo4j_driver
-
-# 配置日志
-logging.basicConfig(
-    level=logging.INFO,
-    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
-    handlers=[
-        logging.StreamHandler(sys.stdout)
-    ]
-)
-
-logger = logging.getLogger("meta_data_migration")
-
-def migrate_type_to_data_type():
-    """
-    将meta_data节点的type属性迁移到data_type属性
-    """
-    try:
-        with neo4j_driver.get_session() as session:
-            # 查询拥有type属性的meta_data节点数量
-            count_query = """
-            MATCH (n:meta_data)
-            WHERE EXISTS(n.type)
-            RETURN COUNT(n) as count
-            """
-            count_result = session.run(count_query)
-            node_count = count_result.single()["count"]
-            
-            if node_count == 0:
-                logger.info("没有找到带有type属性的meta_data节点,无需迁移")
-                return
-                
-            logger.info(f"找到 {node_count} 个需要迁移的meta_data节点")
-            
-            # 执行迁移:复制type属性到data_type,然后删除type属性
-            migrate_query = """
-            MATCH (n:meta_data)
-            WHERE EXISTS(n.type)
-            SET n.data_type = n.type
-            REMOVE n.type
-            RETURN COUNT(n) as migrated_count
-            """
-            
-            migrate_result = session.run(migrate_query)
-            migrated_count = migrate_result.single()["migrated_count"]
-            
-            logger.info(f"成功迁移 {migrated_count} 个meta_data节点的type属性到data_type属性")
-            
-            # 验证迁移结果
-            verify_query = """
-            MATCH (n:meta_data)
-            WHERE EXISTS(n.type)
-            RETURN COUNT(n) as remaining_count
-            """
-            verify_result = session.run(verify_query)
-            remaining_count = verify_result.single()["remaining_count"]
-            
-            if remaining_count > 0:
-                logger.warning(f"仍有 {remaining_count} 个meta_data节点保留了type属性")
-            else:
-                logger.info("所有meta_data节点的type属性已成功迁移到data_type属性")
-                
-    except Exception as e:
-        logger.error(f"迁移过程中发生错误: {str(e)}")
-        raise
-
-if __name__ == "__main__":
-    try:
-        logger.info("开始迁移meta_data节点的type属性到data_type属性...")
-        start_time = time.time()
-        
-        migrate_type_to_data_type()
-        
-        end_time = time.time()
-        execution_time = end_time - start_time
-        logger.info(f"迁移完成,耗时: {execution_time:.2f} 秒")
-        
-    except Exception as e:
-        logger.error(f"迁移失败: {str(e)}")
-        sys.exit(1) 

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio