- P&ID 그래프 파이프라인 구현 (py) - pid_geometric_extractor.py: 기하학적 특징 추출 - pid_intelligent_mapper.py: 태그 매핑 - pid_topology_builder.py: 위상 구축 - test_pipeline_phase2.py, test_pipeline_phase3.py: 테스트 - MCP 서버 개선 - server.py: 멀티프로세싱 지원 - pipeline/: 분석, 추출, 매핑, 위상 모듈 추가 - C# P&ID 그래프 서비스 - PidGraphDtos.cs: DTO 정의 - PidGraphService.cs: 비즈니스 로직 - PidGraphController.cs: API 컨트롤러 - OPC UA 서비스 개선 - ExperionOpcServerService.cs - ExperionRealtimeService.cs - ExperionFastService.cs - MCP 클라이언트 및 호스팅 서비스 개선 - McpClient.cs - McpServerHostedService.cs - 웹 UI 개선 - pid_graph_view.html: P&ID 그래프 뷰어 - pid-viewer.js: 뷰어 로직 - app.js: 메인 앱 - pid_graph.css: 스타일 - 프로젝트 설정 업데이트 - ExperionCrawler.csproj - Program.cs
135 lines
5.1 KiB
Python
135 lines
5.1 KiB
Python
import json
|
|
import sys
|
|
import os
|
|
import asyncio
|
|
import networkx as nx
|
|
|
|
# 경로 설정을 위해 현재 파일의 디렉토리를 sys.path에 추가
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.append(current_dir)
|
|
|
|
from pid_geometric_extractor import PidGeometricExtractor
|
|
from pid_topology_builder import PidTopologyBuilder
|
|
from pid_intelligent_mapper import IntelligentMapper, validate_mapping
|
|
|
|
async def run_full_pipeline():
|
|
# 1. 경로 설정
|
|
input_dxf = os.path.join(current_dir, "No-10_Plant_PID.dxf")
|
|
geo_json_path = os.path.join(current_dir, "shared_geo_data.json")
|
|
graph_json_path = os.path.join(current_dir, "pid_graph_topology.json")
|
|
mapping_result_path = os.path.join(current_dir, "pid_final_mapping.json")
|
|
|
|
# --- Phase 1: Geometric Extraction ---
|
|
print("\n--- Phase 1: Geometric Extraction ---")
|
|
try:
|
|
extractor = PidGeometricExtractor(input_dxf)
|
|
extractor.extract_and_save(geo_json_path)
|
|
print(f"Geometric data saved to {geo_json_path}")
|
|
except Exception as e:
|
|
print(f"Phase 1 failed: {e}")
|
|
return
|
|
|
|
# --- Phase 2: Topology Modeling ---
|
|
print("\n--- Phase 2: Topology Modeling ---")
|
|
try:
|
|
with open(geo_json_path, 'r', encoding='utf-8') as f:
|
|
geometric_data = json.load(f)
|
|
|
|
builder = PidTopologyBuilder(
|
|
geometric_data=geometric_data,
|
|
all_extracted_tags=[],
|
|
config={'dist_threshold': 50.0, 'tag_threshold': 100.0}
|
|
)
|
|
builder.build_graph()
|
|
builder.save_graph(graph_json_path)
|
|
print(f"Graph topology saved to {graph_json_path}")
|
|
except Exception as e:
|
|
print(f"Phase 2 failed: {e}")
|
|
return
|
|
|
|
# --- Phase 3: Intelligent Mapping ---
|
|
print("\n--- Phase 3: Intelligent Mapping ---")
|
|
try:
|
|
# 1. 그래프 로드
|
|
with open(graph_json_path, 'r', encoding='utf-8') as f:
|
|
graph_data = json.load(f)
|
|
|
|
# NetworkX 그래프 복원 (node_link_data 형식 대응)
|
|
from networkx.readwrite import json_graph
|
|
G = json_graph.node_link_graph(graph_data)
|
|
|
|
# 2. 시스템 태그 리스트 (실제로는 API나 DB에서 가져와야 함)
|
|
# 테스트를 위한 가상 태그 리스트
|
|
system_tags = [
|
|
"PT-101.PV", "PT-102.PV", "FT-201.PV", "LT-301.PV",
|
|
"P-101.STATUS", "P-101.SPEED", "V-101.OPEN", "V-101.CLOSE",
|
|
"T-101.TEMP", "TK-101.LEVEL"
|
|
]
|
|
|
|
# 3. 매퍼 초기화 (API Key는 환경변수나 설정파일에서 가져오는 것을 권장)
|
|
api_key = os.getenv("OPENAI_API_KEY", "your-api-key-here")
|
|
mapper = IntelligentMapper(G, system_tags, api_key=api_key)
|
|
|
|
# 4. 노드 분류 및 매핑 실행
|
|
nodes = list(G.nodes())
|
|
transmitter_nodes = [n for n in nodes if "Transmitter" in G.nodes[n].get('type', '')]
|
|
valve_nodes = [n for n in nodes if "Valve" in G.nodes[n].get('type', '')]
|
|
equipment_nodes = [n for n in nodes if "Equipment" in G.nodes[n].get('type', '') or "Pump" in G.nodes[n].get('type', '')]
|
|
|
|
print(f"Mapping {len(transmitter_nodes)} transmitters, {len(valve_nodes)} valves, {len(equipment_nodes)} equipment...")
|
|
|
|
# 비동기 실행
|
|
results = await asyncio.gather(
|
|
mapper.extract_transmitters(transmitter_nodes),
|
|
mapper.extract_valves(valve_nodes),
|
|
mapper.extract_equipment(equipment_nodes)
|
|
)
|
|
|
|
# 결과 통합
|
|
final_mapping_raw = {}
|
|
for res in results:
|
|
final_mapping_raw.update(res)
|
|
|
|
# 5. 검증 및 최종 결과 정리
|
|
# 가상 메타데이터 (실제로는 시스템에서 조회)
|
|
mock_metadata = {
|
|
"PT-101.PV": {"unit": "bar", "description": "Pressure Transmitter 101"},
|
|
"P-101.STATUS": {"unit": "", "description": "Pump 101 Status"},
|
|
}
|
|
|
|
final_results = []
|
|
for node_id, mapping in final_mapping_raw.items():
|
|
symbol_type = G.nodes[node_id].get('type', 'Unknown')
|
|
tag = mapping.resolved_tag
|
|
meta = mock_metadata.get(tag, {"unit": "", "description": ""})
|
|
|
|
is_valid, val_msg = validate_mapping(tag, symbol_type, meta)
|
|
|
|
final_results.append({
|
|
"node_id": node_id,
|
|
"symbol_type": symbol_type,
|
|
"original_text": G.nodes[node_id].get('value', ''),
|
|
"resolved_tag": tag,
|
|
"confidence": mapping.confidence,
|
|
"reason": mapping.reason,
|
|
"validation": {
|
|
"is_valid": is_valid,
|
|
"message": val_msg
|
|
}
|
|
})
|
|
|
|
# 6. 결과 저장
|
|
with open(mapping_result_path, 'w', encoding='utf-8') as f:
|
|
json.dump(final_results, f, indent=4, ensure_ascii=False)
|
|
|
|
print(f"Final mapping results saved to {mapping_result_path}")
|
|
print(f"Successfully mapped {len(final_results)} nodes.")
|
|
|
|
except Exception as e:
|
|
print(f"Phase 3 failed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(run_full_pipeline())
|