Sfoglia il codice sorgente

修改数据模型列表接口,新增数据模型查找元数据
修改数据来源、数据模型的检索条件

maxiaolong 2 giorni fa
parent
commit
187ab11f56

+ 75 - 11
app/api/data_model/routes.py

@@ -4,6 +4,10 @@ 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'])
@@ -217,22 +221,29 @@ def data_model_list():
         return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
 
 
-# 数据模型的图谱(血缘关系Kinship+影响关系Impact+所有关系all)
+# 数据模型的图谱(血缘关系Kinship+影响关系Impact+所有关系all+社区关系community
 @bp.route('/data/model/graph/all', methods=['POST'])
 def data_model_graph_all():
     try:
         # 传入请求参数
         receiver = request.get_json()
-        nodeid = receiver['id']
-        type = receiver['type'] # kinship/impact/all
-        meta = receiver['meta'] # true/false 是否返回元数据
-
-        if type == 'kinship':
-            result = model_functions.model_kinship_graph(nodeid, meta)
-        elif type == 'impact':
-            result = model_functions.model_impact_graph(nodeid, meta)
+        type = receiver['type'] # kinship/impact/all/community
+
+        if type == 'community':
+            # 社区图谱查询,提取tag参数
+            tag = receiver.get('tag', None)
+            result = model_functions.model_community(tag)
         else:
-            result = model_functions.model_all_graph(nodeid, meta)
+            # 非社区查询时才提取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)), ensure_ascii=False, cls=MyEncoder)
@@ -307,4 +318,57 @@ def data_model_update():
         
         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) 
+        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据模型关联元数据搜索
+@bp.route('/search', methods=['POST'])
+def data_model_metadata_search():
+    """数据模型关联元数据搜索"""
+    try:
+        # 获取分页和筛选参数
+        page = int(request.json.get('current', 1))
+        page_size = int(request.json.get('size', 10))
+        model_id = request.json.get('id')
+        
+        en_name_filter = request.json.get('en_name')
+        name_filter = request.json.get('name')
+        category_filter = request.json.get('category')
+        tag_filter = request.json.get('tag')
+        
+        if model_id is None:
+            return json.dumps(failed({}, "模型ID不能为空"), ensure_ascii=False, cls=MyEncoder)
+            
+        # 确保传入的ID为整数
+        try:
+            model_id = int(model_id)
+        except (ValueError, TypeError):
+            return json.dumps(failed({}, f"模型ID必须为整数, 收到的是: {model_id}"), 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, 
+            en_name_filter, 
+            name_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))
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder) 

+ 275 - 45
app/core/data_model/model.py

@@ -557,72 +557,126 @@ def model_list(skip_count, page_size, en_name_filter=None, name_filter=None,
         tuple: (数据模型列表, 总数量)
     """
     try:
-        # 构建where子句
-        where_clause = []
+        # 构建where子句 - 只针对DataModel节点的过滤条件
+        datamodel_where_clause = []
         params = {}
 
         if name_filter is not None:
-            where_clause.append("n.name =~ $name")
+            datamodel_where_clause.append("n.name =~ $name")
             params['name'] = f".*{name_filter}.*"
         
         if en_name_filter is not None:
-            where_clause.append("n.en_name =~ $en_name")
+            datamodel_where_clause.append("n.en_name =~ $en_name")
             params['en_name'] = f".*{en_name_filter}.*"
 
         if category is not None:
-            where_clause.append("n.category = $category")
+            datamodel_where_clause.append("n.category = $category")
             params['category'] = category
     
         if level is not None:
-            where_clause.append("n.level = $level")
+            datamodel_where_clause.append("n.level = $level")
             params['level'] = level
-    
+
+        # 处理标签查询
         if tag is not None:
-            where_clause.append("id(t) = $tag")
-            params['tag'] = tag
+            # 确保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)"
 
-        # At the end of where_clause construction
-        where_str = " AND ".join(where_clause)
-        if where_str:
-            where_str = f"WHERE {where_str}"
+        # 构建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:
             # 计算总数量
-            count_query = f"""
-            MATCH (n:DataModel)
-            OPTIONAL MATCH (n)-[:label]->(t)
-            {where_str}
-            RETURN COUNT(DISTINCT n) AS count
-            """
+            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
 
-            # 查询数据
-            query = f"""
-            MATCH (n:DataModel)
-            OPTIONAL MATCH (n)-[:label]->(t)
-            {where_str}
-            RETURN DISTINCT 
-            id(n) as id, 
-            n.name as name, 
-            n.en_name as en_name, 
-            n.time as 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 as tag_name
-            ORDER BY n.time DESC
-            SKIP $skip
-            LIMIT $limit
-            """
+            # 查询数据 - 修复OPTIONAL MATCH的笛卡尔积问题
+            if tag is not None:
+                # 有标签查询时,直接使用标签连接
+                query = f"""
+                {match_clause}
+                {datamodel_where_str}
+                RETURN DISTINCT 
+                id(n) as id, 
+                n.name as name, 
+                n.en_name as en_name, 
+                n.time as 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 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 as name, 
+                n.en_name as en_name, 
+                n.time as 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 as tag_name
+                ORDER BY n.time DESC
+                SKIP $skip
+                LIMIT $limit
+                """
+            
+            logger.debug(f"Main query: {query}")
         
             result = session.run(query, skip=skip_count, limit=page_size, **params)
             
@@ -646,9 +700,11 @@ def model_list(skip_count, page_size, en_name_filter=None, name_filter=None,
                 }
                 data.append(item)
             
+            logger.info(f"Query returned {len(data)} items out of {total} total")
             return data, total
+            
     except Exception as e:
-        print(f"Error in model_list: {str(e)}")
+        logger.error(f"Error in model_list: {str(e)}")
         import traceback
         traceback.print_exc()
         return [], 0
@@ -1180,4 +1236,178 @@ def data_model_edit(receiver):
         else:
             logger.info(f"meta_data为空,不需要重新创建INCLUDES关系,DataModel({id})将不关联任何DataMeta节点")
     
-    return {"message": "数据模型更新成功"} 
+    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, en_name_filter=None,
+                     name_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 en_name_filter:
+                where_conditions.append(f"m.en_name CONTAINS '{en_name_filter}'")
+                
+            if name_filter:
+                where_conditions.append(f"m.name CONTAINS '{name_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 = $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)
+            total_count = count_result.single()["count"]
+            
+            # 分页查询
+            skip = (page - 1) * page_size
+            cypher = f"""
+            {match_clause}{where_clause}
+            {tag_match}
+            RETURN m
+            ORDER BY m.name
+            SKIP {skip} LIMIT {page_size}
+            """
+            
+            result = session.run(cypher, **count_params)
+            
+            # 格式化结果
+            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 

+ 85 - 23
app/core/data_resource/resource.py

@@ -488,42 +488,104 @@ def resource_list(page, page_size, en_name_filter=None, name_filter=None,
     """获取数据资源列表"""
     try:
         with neo4j_driver.get_session() as session:
-            # 构建查询条件
-            match_clause = "MATCH (n:DataResource)"
-            where_conditions = []
+            # 构建基础过滤条件(针对DataResource节点)
+            resource_conditions = []
             
             if en_name_filter:
-                where_conditions.append(f"n.en_name CONTAINS '{en_name_filter}'")
+                resource_conditions.append(f"n.en_name CONTAINS '{en_name_filter}'")
                 
             if name_filter:
-                where_conditions.append(f"n.name CONTAINS '{name_filter}'")
+                resource_conditions.append(f"n.name CONTAINS '{name_filter}'")
                 
             if type_filter and type_filter != 'all':
-                where_conditions.append(f"n.type = '{type_filter}'")
+                resource_conditions.append(f"n.type = '{type_filter}'")
                 
             if category_filter:
-                where_conditions.append(f"n.category = '{category_filter}'")
-                
-            # 标签过滤需要额外的匹配
-            if tag_filter:
-                match_clause += "-[:label]->(t:DataLabel)"
-                where_conditions.append(f"t.name = '{tag_filter}'")
+                resource_conditions.append(f"n.category = '{category_filter}'")
             
-            where_clause = " WHERE " + " AND ".join(where_conditions) if where_conditions else ""
+            # 构建基础WHERE子句
+            resource_where = " AND ".join(resource_conditions) if resource_conditions else ""
             
-            # 计算总数
-            count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
+            # 根据是否有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 = '{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 = '{tag_filter}'
+                    RETURN DISTINCT n
+                    ORDER BY n.time DESC
+                    SKIP {skip} LIMIT {page_size}
+                    """
+                else:
+                    # 只有标签过滤条件
+                    count_cypher = f"""
+                    MATCH (n:DataResource)-[:label]->(t:DataLabel)
+                    WHERE t.name = '{tag_filter}'
+                    RETURN count(DISTINCT n) as count
+                    """
+                    
+                    # 分页查询
+                    skip = (page - 1) * page_size
+                    cypher = f"""
+                    MATCH (n:DataResource)-[:label]->(t:DataLabel)
+                    WHERE t.name = '{tag_filter}'
+                    RETURN DISTINCT n
+                    ORDER BY n.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.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.time DESC
+                    SKIP {skip} LIMIT {page_size}
+                    """
+            
+            # 执行计数查询
             count_result = session.run(count_cypher)
             total_count = count_result.single()["count"]
             
-            # 分页查询
-            skip = (page - 1) * page_size
-            cypher = f"""
-            {match_clause}{where_clause}
-            RETURN n
-            ORDER BY n.time DESC
-            SKIP {skip} LIMIT {page_size}
-            """
+            # 执行分页查询
             result = session.run(cypher)
             
             # 格式化结果

+ 12 - 0
客户经理绩效计算.txt

@@ -0,0 +1,12 @@
+客户经理绩效工资=(存款类收益工资+贷款类收益工资+中收产品价值贡献收益工资+拓户类产品计价工资+风控类考核收益工资)*部门考核调节系数,部门调节系数范围[0.8,1.2]
+存款类收益工资=管户客户存款存量收益工资+管户客户存款增量收益工资
+(注意:当前对公客户经理存款考核权重:存量100%,增量400%;个贷客户经理:以支行储蓄存款日均余额增量完成率来考核;理财经理:只考核储蓄存款日均余额增量)
+管户客户存款存量收益工资=管户日均存款存量当月净利息收入*存量存款积分系数。存量部分封顶为3000分。
+管户客户存款增量收益工资=管户日均存款较上季度末增量当月净利息收入*增量存款积分系数。
+贷款类收益工资=管户客户存量贷款当月收益工资+新发放贷款收益工资
+管户客户存量贷款收益工资=管户客户存量贷款当月净利息收入-风险成本(当期拨备金额)-资金成本
+新发放贷款收益工资=新发放产品贷款金额*新发放贷款产品积分系数
+中收产品价值贡献收益工资=当月管户客户中间业务收入*中收类产品积分系数
+拓户类产品计价工资=拓户类计价产品折算营收值*拓户类产品积分系数
+风控类考核工资=不良贷款清收额折算营收值*风控类产品积分系数
+