Files
ExperionCrawler/dxf-graph/pid_analysis_engine.py
2026-05-08 17:22:10 +09:00

105 lines
3.5 KiB
Python

import networkx as nx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, List, Optional
import uvicorn
import json
import os
app = FastAPI(title="P&ID Analysis Engine")
# 전역 변수로 그래프 및 매핑 데이터 로드
TOPOLOGY_FILE = "futurePlan/End-to-End P&ID Graph Pipeline/pid_graph_topology.json"
MAPPING_FILE = "futurePlan/End-to-End P&ID Graph Pipeline/pid_final_mapping.json"
topology_graph = nx.DiGraph()
tag_mapping = {}
def load_data():
global topology_graph, tag_mapping
try:
if os.path.exists(TOPOLOGY_FILE):
with open(TOPOLOGY_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
# NetworkX 그래프 생성
for node in data.get('nodes', []):
topology_graph.add_node(node['id'], **node)
for edge in data.get('edges', []):
topology_graph.add_edge(edge['source'], edge['target'], **edge)
print(f"Successfully loaded topology from {TOPOLOGY_FILE}")
if os.path.exists(MAPPING_FILE):
with open(MAPPING_FILE, 'r', encoding='utf-8') as f:
tag_mapping = json.load(f)
print(f"Successfully loaded mapping from {MAPPING_FILE}")
except Exception as e:
print(f"Error loading data: {e}")
@app.on_event("startup")
async def startup_event():
load_data()
class ImpactRequest(BaseModel):
nodeId: str
class ImpactResult(BaseModel):
startNode: str
impactedNodes: Dict[str, int] # { nodeId: depth }
path: List[List[str]]
def get_propagation_path_with_flow(graph, start_node):
"""
엣지의 방향성(flow_direction)과 상태(valve_status)를 고려한 실제 영향 전파 경로 추출
"""
if start_node not in graph:
return {}
# 1. 유효한 엣지만 필터링 (방향이 forward이고 밸브가 open인 경로)
# 실제 데이터에 flow_direction이나 valve_status가 없을 경우를 대비해 기본값 설정
valid_edges = [
(u, v) for u, v, d in graph.edges(data=True)
if d.get('flow_direction', 'forward') == 'forward'
and d.get('valve_status', 'open') == 'open'
]
filtered_graph = nx.DiGraph()
filtered_graph.add_edges_from(valid_edges)
# 2. 전파 단계별 노드 추출 (BFS)
try:
propagation_levels = nx.single_source_shortest_path_length(filtered_graph, start_node)
return propagation_levels
except Exception:
return {}
@app.get("/impact/{nodeId}")
async def analyze_impact(nodeId: str):
if nodeId not in topology_graph:
raise HTTPException(status_code=404, detail=f"Node {nodeId} not found in topology")
impact_map = get_propagation_path_with_flow(topology_graph, nodeId)
# 경로 추출 (시각화를 위해 간단하게 모든 영향 노드로의 최단 경로 포함)
paths = []
for target in impact_map.keys():
if target != nodeId:
try:
path = nx.shortest_path(topology_graph, source=nodeId, target=target)
paths.append(path)
except nx.NetworkXNoPath:
continue
return {
"startNode": nodeId,
"impactedNodes": impact_map,
"paths": paths
}
@app.get("/health")
async def health_check():
return {"status": "healthy", "nodes": topology_graph.number_of_nodes(), "edges": topology_graph.number_of_edges()}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)