Files
ExperionCrawler/futurePlan/End-to-End P&ID Graph Pipeline/test_pipeline_phase3.py
windpacer f71ec310e4 feat: P&ID 그래프 파이프라인 및 MCP 서버 개선
- 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
2026-05-03 03:50:20 +09:00

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())