105 lines
3.5 KiB
Python
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)
|