Initial: integrated 2025 LawGraph (graphrag_pipeline) + 2026 kg_project
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
Neo4j数据导入脚本 - 从CSV导入法规知识图谱数据
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
from neo4j import GraphDatabase
|
||||
import json
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Neo4jImporter:
|
||||
"""Neo4j数据导入器"""
|
||||
|
||||
def __init__(self, uri: str, username: str, password: str, database: str = 'neo4j'):
|
||||
self.driver = GraphDatabase.driver(uri, auth=(username, password))
|
||||
self.database = database
|
||||
logger.info(f"连接Neo4j: {uri}")
|
||||
|
||||
def close(self):
|
||||
self.driver.close()
|
||||
|
||||
def _run_query(self, query, parameters=None):
|
||||
with self.driver.session(database=self.database) as session:
|
||||
result = session.run(query, parameters or {})
|
||||
return [record.data() for record in result]
|
||||
|
||||
def create_constraints(self):
|
||||
"""创建约束和索引"""
|
||||
schema_file = Path(__file__).parent / 'schema.cypher'
|
||||
if schema_file.exists():
|
||||
with open(schema_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
for line in content.split('\n'):
|
||||
line = line.strip()
|
||||
if line.startswith('CREATE ') and not line.startswith('//'):
|
||||
try:
|
||||
self._run_query(line)
|
||||
logger.info(f"执行: {line[:60]}...")
|
||||
except Exception as e:
|
||||
if 'already exists' not in str(e):
|
||||
logger.warning(f"约束跳过: {e}")
|
||||
|
||||
def import_nodes(self, nodes_csv: str):
|
||||
"""导入节点"""
|
||||
nodes_df = pd.read_csv(nodes_csv, encoding='utf-8-sig')
|
||||
logger.info(f"导入 {len(nodes_df)} 个节点")
|
||||
|
||||
# 按类型分批导入
|
||||
for node_type in nodes_df['type'].unique():
|
||||
type_df = nodes_df[nodes_df['type'] == node_type]
|
||||
label = node_type
|
||||
|
||||
for _, row in type_df.iterrows():
|
||||
props = {}
|
||||
try:
|
||||
props = json.loads(row.get('properties', '{}'))
|
||||
except:
|
||||
pass
|
||||
props['id'] = row['id']
|
||||
props['name'] = row['label']
|
||||
|
||||
query = f"MERGE (n:{label} {{id: $id}}) SET n += $props"
|
||||
self._run_query(query, {'id': row['id'], 'props': props})
|
||||
|
||||
logger.info(f" {label}: {len(type_df)} 个")
|
||||
|
||||
def import_relationships(self, rels_csv: str):
|
||||
"""导入关系"""
|
||||
rels_df = pd.read_csv(rels_csv, encoding='utf-8-sig')
|
||||
logger.info(f"导入 {len(rels_df)} 个关系")
|
||||
|
||||
imported = 0
|
||||
for _, row in rels_df.iterrows():
|
||||
rel_type = row['type']
|
||||
props = {}
|
||||
try:
|
||||
props = json.loads(row.get('properties', '{}'))
|
||||
except:
|
||||
pass
|
||||
|
||||
query = f"""
|
||||
MATCH (a {{id: $source}})
|
||||
MATCH (b {{id: $target}})
|
||||
MERGE (a)-[r:{rel_type}]->(b)
|
||||
SET r += $props
|
||||
"""
|
||||
try:
|
||||
self._run_query(query, {
|
||||
'source': row['source'],
|
||||
'target': row['target'],
|
||||
'props': props
|
||||
})
|
||||
imported += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"关系导入失败: {row['source']} -> {row['target']} ({rel_type}): {e}")
|
||||
|
||||
logger.info(f"成功导入 {imported}/{len(rels_df)} 个关系")
|
||||
|
||||
def import_all(self, nodes_csv: str, rels_csv: str):
|
||||
"""完整导入流程"""
|
||||
logger.info("开始导入...")
|
||||
self.create_constraints()
|
||||
self.import_nodes(nodes_csv)
|
||||
self.import_relationships(rels_csv)
|
||||
logger.info("导入完成!")
|
||||
|
||||
|
||||
def main():
|
||||
import yaml
|
||||
|
||||
config_file = Path(__file__).parent.parent / 'config' / 'legal_config.yaml'
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
neo4j_config = config['neo4j']
|
||||
importer = Neo4jImporter(
|
||||
neo4j_config['uri'],
|
||||
neo4j_config['username'],
|
||||
neo4j_config['password'],
|
||||
neo4j_config['database']
|
||||
)
|
||||
|
||||
output_dir = Path(__file__).parent.parent / 'output'
|
||||
nodes_csv = str(output_dir / 'nodes_merged.csv')
|
||||
rels_csv = str(output_dir / 'rels_merged.csv')
|
||||
|
||||
try:
|
||||
importer.import_all(nodes_csv, rels_csv)
|
||||
finally:
|
||||
importer.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
// Neo4j数据模型 - 城市规划法律法规知识图谱
|
||||
|
||||
// ============================================================
|
||||
// 约束和索引
|
||||
// ============================================================
|
||||
|
||||
CREATE CONSTRAINT law_id IF NOT EXISTS FOR (l:Law) REQUIRE l.law_id IS UNIQUE;
|
||||
CREATE CONSTRAINT reg_id IF NOT EXISTS FOR (r:AdministrativeRegulation) REQUIRE r.reg_id IS UNIQUE;
|
||||
CREATE CONSTRAINT rule_id IF NOT EXISTS FOR (r:DepartmentalRule) REQUIRE r.rule_id IS UNIQUE;
|
||||
CREATE CONSTRAINT doc_id IF NOT EXISTS FOR (d:PolicyDocument) REQUIRE d.doc_id IS UNIQUE;
|
||||
CREATE CONSTRAINT chapter_id IF NOT EXISTS FOR (c:Chapter) REQUIRE c.chapter_id IS UNIQUE;
|
||||
CREATE CONSTRAINT section_id IF NOT EXISTS FOR (s:Section) REQUIRE s.section_id IS UNIQUE;
|
||||
CREATE CONSTRAINT article_id IF NOT EXISTS FOR (a:Article) REQUIRE a.article_id IS UNIQUE;
|
||||
CREATE CONSTRAINT body_id IF NOT EXISTS FOR (g:GovernmentBody) REQUIRE g.body_id IS UNIQUE;
|
||||
CREATE CONSTRAINT subject_id IF NOT EXISTS FOR (s:LegalSubject) REQUIRE s.subject_id IS UNIQUE;
|
||||
CREATE CONSTRAINT concept_id IF NOT EXISTS FOR (s:SpatialConcept) REQUIRE s.concept_id IS UNIQUE;
|
||||
CREATE CONSTRAINT proc_id IF NOT EXISTS FOR (p:AdministrativeProcedure) REQUIRE p.proc_id IS UNIQUE;
|
||||
|
||||
// 索引
|
||||
CREATE INDEX law_name IF NOT EXISTS FOR (l:Law) ON (l.name);
|
||||
CREATE INDEX law_status IF NOT EXISTS FOR (l:Law) ON (l.status);
|
||||
CREATE INDEX article_type IF NOT EXISTS FOR (a:Article) ON (a.article_type);
|
||||
CREATE INDEX body_name IF NOT EXISTS FOR (g:GovernmentBody) ON (g.name);
|
||||
CREATE INDEX subject_category IF NOT EXISTS FOR (s:LegalSubject) ON (s.category);
|
||||
|
||||
// 全文搜索
|
||||
CREATE FULLTEXT INDEX law_search IF NOT EXISTS FOR (l:Law) ON EACH [l.name, l.full_title];
|
||||
CREATE FULLTEXT INDEX article_search IF NOT EXISTS FOR (a:Article) ON EACH [a.text];
|
||||
|
||||
// ============================================================
|
||||
// 导入命令示例
|
||||
// ============================================================
|
||||
|
||||
// LOAD CSV WITH HEADERS FROM 'file:///nodes.csv' AS row
|
||||
// WITH row WHERE row.type = 'Law'
|
||||
// CREATE (:Law {law_id: row.id, name: row.label, full_title: apoc.text.replace(row.label, '"', '')});
|
||||
//
|
||||
// LOAD CSV WITH HEADERS FROM 'file:///rels.csv' AS row
|
||||
// MATCH (a {id: row.source})
|
||||
// MATCH (b {id: row.target})
|
||||
// CALL apoc.create.relationship(a, row.type, {}, b) YIELD rel
|
||||
// RETURN rel;
|
||||
|
||||
// ============================================================
|
||||
// 常用查询
|
||||
// ============================================================
|
||||
|
||||
// 查询某法律的所有条文
|
||||
// MATCH (l:Law {name: '城乡规划法'})-[:HAS_CHAPTER]->(ch)-[:CONTAINS_ARTICLE]->(art:Article)
|
||||
// RETURN ch.title, art.number, art.text;
|
||||
|
||||
// 查询引用某法律的法规
|
||||
// MATCH (d)-[r:CITES]->(l:Law {name: '城乡规划法'})
|
||||
// RETURN d.name, type(r), r.context;
|
||||
|
||||
// 查询涉及某主题的所有条文
|
||||
// MATCH (art:Article)-[:REGULATES]->(s:LegalSubject {name: '建设用地'})
|
||||
// RETURN art.text;
|
||||
|
||||
// 查询某机关发布的所有文件
|
||||
// MATCH (d)-[:ISSUED_BY]->(g:GovernmentBody {name: '自然资源部'})
|
||||
// RETURN d.name, labels(d);
|
||||
Reference in New Issue
Block a user