# ๐Ÿ•ธ๏ธ Graph Pipeline Phase 2: ์œ„์ƒ ๋ชจ๋ธ๋ง (Topology Modeling) ์ด ๋ฌธ์„œ๋Š” P&ID Graph Pipeline์˜ ๋‘ ๋ฒˆ์งธ ๋‹จ๊ณ„์ธ **์œ„์ƒ ๋ชจ๋ธ๋ง**์˜ ์ƒ์„ธ ๊ตฌํ˜„ ๊ณ„ํš์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. 1๋‹จ๊ณ„์—์„œ ์ถ”์ถœํ•œ ๊ธฐํ•˜ํ•™์  ๊ฐ์ฒด(์ขŒํ‘œ, BBox)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ, ์„ค๋น„ ๊ฐ„์˜ **์—ฐ๊ฒฐ์„ฑ(Connectivity)**๊ณผ **ํ๋ฆ„(Flow)**์„ ์ •์˜ํ•˜๋Š” ์ง€์‹ ๊ทธ๋ž˜ํ”„(Knowledge Graph)๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์ž…๋‹ˆ๋‹ค. --- ## ๐Ÿšฉ [Supervisor's Audit] ์ง„๋‹จ ๊ฒฐ๊ณผ ๋ฐ ๊ฐœ์„  ๊ถŒ๊ณ  **๊ฐ๋…์ž ์ง„๋‹จ ์ผ์ž:** 2026-05-02 **์ง„๋‹จ ๊ฒฐ๊ณผ:** โš ๏ธ **๋ถ€๋ถ„์  ๋ณด์™„ ํ•„์š” (Partial Improvement Required)** ### ๐Ÿ” ์ฃผ์š” ์ง„๋‹จ ๋‚ด์šฉ 1. **์—ฐ๊ฒฐ์„ฑ ์ถ”๋ก ์˜ ๋‹จ์ˆœ์„ฑ (Critical):** ํ˜„์žฌ `_find_connected_nodes`๊ฐ€ ๋‹จ์ˆœ BBox ๊ต์ฐจ(`intersects`)๋งŒ ํ™•์ธํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ P&ID์—์„œ ๋ฐฐ๊ด€(Line)์€ ์„ค๋น„์˜ ์™ธ๊ณฝ์„ ์— ๋‹ฟ๊ฑฐ๋‚˜ ๋งค์šฐ ๊ทผ์ ‘ํ•œ ํ˜•ํƒœ๋กœ ๋‚˜ํƒ€๋‚˜๋ฉฐ, ๋‹จ์ˆœ BBox ๊ต์ฐจ๋Š” ์˜คํƒ(False Positive) ํ™•๋ฅ ์ด ๋งค์šฐ ๋†’์Šต๋‹ˆ๋‹ค. 2. **๋ฐฉํ–ฅ์„ฑ ์ •์˜ ๋ถ€์žฌ (Medium):** `DiGraph`๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์‹ค์ œ ์—ฃ์ง€์— ๋ฐฉํ–ฅ์„ฑ์„ ๋ถ€์—ฌํ•˜๋Š” ๊ตฌ์ฒด์ ์ธ ๋กœ์ง(ํ™”์‚ดํ‘œ ์ธ์‹, ๊ณต์ • ํ๋ฆ„ ๊ทœ์น™)์ด ์˜ˆ์‹œ ์ฝ”๋“œ์— ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. 3. **์ž„๊ณ„๊ฐ’ ํ•˜๋“œ์ฝ”๋”ฉ (Low):** `min_dist < 50.0`๊ณผ ๊ฐ™์€ ์ž„๊ณ„๊ฐ’์ด ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ์–ด, ๋„๋ฉด ์Šค์ผ€์ผ(Scale)์ด ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ ๋Œ€์‘์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. 4. **๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ ๋ถ€์กฑ (Medium):** ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ํ›„ ๊ณ ๋ฆฝ๋œ ๋…ธ๋“œ(Isolated Nodes)๋‚˜ ๋น„์ •์ƒ์ ์ธ ๋ฃจํ”„์— ๋Œ€ํ•œ ๊ฒ€์ฆ ๋‹จ๊ณ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ### ๐Ÿ› ๏ธ ์ˆ˜์ • ๋ฐ ๋ฐ˜์˜ ์‚ฌํ•ญ - **์—ฐ๊ฒฐ์„ฑ ๋กœ์ง ๊ณ ๋„ํ™”:** BBox ๊ต์ฐจ ๋ฐฉ์‹์—์„œ $\rightarrow$ **Line End-point ๊ธฐ๋ฐ˜ ๊ทผ์ ‘ ๋ถ„์„** ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ. - **๋ฐฉํ–ฅ์„ฑ ์ถ”๋ก  ๋‹จ๊ณ„ ๋ช…์‹œ:** ํ™”์‚ดํ‘œ ์‹ฌ๋ณผ ๋ฐ ๊ณต์ • ํ๋ฆ„ ๊ธฐ๋ฐ˜์˜ `source` $\rightarrow$ `target` ๊ฒฐ์ • ๋กœ์ง ์ถ”๊ฐ€. - **์„ค์ •์˜ ์™ธ๋ถ€ํ™”:** ์ž„๊ณ„๊ฐ’($\epsilon$)์„ ์„ค์ • ํŒŒ์ผ์ด๋‚˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๊ด€๋ฆฌํ•˜๋„๋ก ๊ตฌ์กฐ ๋ณ€๊ฒฝ. - **๊ฒ€์ฆ ๋‹จ๊ณ„ ์ถ”๊ฐ€:** ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ• ํ›„ ์œ„์ƒ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์‚ฌ(Topology Validation) ๋‹จ๊ณ„ ๋„์ž…. --- ## ๐Ÿ“ฆ 1. ํ•„์ˆ˜ ํŒจํ‚ค์ง€ ๋ฐ ํ™˜๊ฒฝ ์„ค์ • ### 1.1 Python ํŒจํ‚ค์ง€ | ํŒจํ‚ค์ง€ | ์šฉ๋„ | ๋น„๊ณ  | |---|---|---| | `networkx` | ๊ทธ๋ž˜ํ”„ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ƒ์„ฑ ๋ฐ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ถ„์„ | ํ•ต์‹ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ | | `shapely` | ๊ฐ์ฒด ๊ฐ„ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ ๋ฐ ํฌํ•จ ๊ด€๊ณ„ ๋ถ„์„ | 1๋‹จ๊ณ„์™€ ์—ฐ๊ณ„ | | `scikit-learn` | (์„ ํƒ) KD-Tree๋ฅผ ์ด์šฉํ•œ ๊ณ ์† ๊ทผ์ ‘ ์ด์›ƒ ๊ฒ€์ƒ‰ | ๋Œ€๊ทœ๋ชจ ๋„๋ฉด ์ตœ์ ํ™” | | `matplotlib` | ์ƒ์„ฑ๋œ ๊ทธ๋ž˜ํ”„์˜ ์œ„์ƒ ๊ตฌ์กฐ ์‹œ๊ฐํ™” ๊ฒ€์ฆ | ๋””๋ฒ„๊น…์šฉ | ### 1.2 ์„ค์น˜ ๋ช…๋ น์–ด ```bash pip install networkx shapely scikit-learn matplotlib ``` --- ## ๐Ÿ“ 2. ์ƒ์„ธ ์„ค๊ณ„ ๊ตฌ์กฐ ### 2.1 ๊ทธ๋ž˜ํ”„ ์ •์˜ (Graph Definition) * **๋…ธ๋“œ (Nodes):** * `Equipment`: ํŽŒํ”„, ํƒฑํฌ, ์—ด๊ตํ™˜๊ธฐ ๋“ฑ (์†์„ฑ: ID, ํƒ€์ž…, BBox, CenterPoint) * `Instrument`: ์ „์†ก๊ธฐ, ๋ฐธ๋ธŒ, ๊ฒŒ์ด์ง€ ๋“ฑ (์†์„ฑ: ID, ํƒ€์ž…, BBox, CenterPoint) * `Tag`: ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ํƒœ๊ทธ (์†์„ฑ: TagName, Value, BBox) * **์—ฃ์ง€ (Edges):** * `Pipe`: ์„ค๋น„-์„ค๋น„, ์„ค๋น„-๊ณ„๊ธฐ ๊ฐ„์˜ ๋ฌผ๋ฆฌ์  ์—ฐ๊ฒฐ (์†์„ฑ: LineNumber, ๋ฐฉํ–ฅ์„ฑ, ์—ฐ๊ฒฐํƒ€์ž…) * `Association`: ํƒœ๊ทธ-์„ค๋น„ ๊ฐ„์˜ ๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ (์†์„ฑ: ๊ด€๊ณ„ ํƒ€์ž… - ์˜ˆ: 'belongs_to') ### 2.2 ์œ„์ƒ ์ถ”๋ก  ๋กœ์ง (Topology Inference) 1. **ํƒœ๊ทธ-์„ค๋น„ ๊ฒฐํ•ฉ (Tag-to-Entity Binding):** * ํƒœ๊ทธ ํ…์ŠคํŠธ์˜ BBox์™€ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์‹ฌ๋ณผ(Equipment/Instrument)์„ ์ฐพ์•„ `Association` ์—ฃ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 2. **๋ฐฐ๊ด€ ์—ฐ๊ฒฐ์„ฑ ๋ถ„์„ (Line Connectivity) [๊ฐœ์„ ]:** * `LINE` ๋˜๋Š” `POLYLINE`์˜ **์‹œ์ž‘์ ๊ณผ ๋์ (End-points)**์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. * ๊ฐ ๋์ ์ด ํŠน์ • ์„ค๋น„์˜ BBox ๋‚ด๋ถ€์— ์žˆ๊ฑฐ๋‚˜, ์„ค์ •๋œ ์ž„๊ณ„ ๊ฑฐ๋ฆฌ($\epsilon$) ์ด๋‚ด์— ์žˆ์„ ๋•Œ๋งŒ `Pipe` ์—ฃ์ง€๋กœ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. (๋‹จ์ˆœ BBox ๊ต์ฐจ ๋ฐฉ์‹ ์ง€์–‘) 3. **ํ๋ฆ„ ๋ฐฉํ–ฅ์„ฑ ๋ถ€์—ฌ (Flow Direction) [์ถ”๊ฐ€]:** * ๋ฐฐ๊ด€ ์ƒ์˜ ํ™”์‚ดํ‘œ ์‹ฌ๋ณผ ์œ„์น˜์™€ ๋ฐฉํ–ฅ์„ ๋ถ„์„ํ•˜์—ฌ `source` $\rightarrow$ `target`์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. * ํ™”์‚ดํ‘œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ, ๊ณต์ • ํ‘œ์ค€(์˜ˆ: ํƒฑํฌ $\rightarrow$ ํŽŒํ”„ $\rightarrow$ ๋ฐธ๋ธŒ)์— ๋”ฐ๋ฅธ ๊ธฐ๋ณธ ๋ฐฉํ–ฅ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค. 4. **์œ„์ƒ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ (Topology Validation) [์ถ”๊ฐ€]:** * ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ ๊ณ ๋ฆฝ ๋…ธ๋“œ ํƒ์ƒ‰ ๋ฐ ๋ฆฌํฌํŒ…. * ๋น„์ •์ƒ์ ์ธ ์‚ฌ์ดํด(Cycle) ๋˜๋Š” ๋‹จ์ ˆ ๊ตฌ๊ฐ„ ํ™•์ธ. --- ## ๐Ÿ’ป 3. ์‹ค์ œ ๊ตฌํ˜„ ์ฝ”๋”ฉ ๊ฐ€์ด๋“œ (Example) ### 3.1 ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ• ํ•ต์‹ฌ ์ฝ”๋“œ ```python import networkx as nx from shapely.geometry import box, Point, LineString class PidTopologyBuilder: def __init__(self, geometric_data, all_extracted_tags=None, config=None): """ - geometric_data: Phase 1์—์„œ ์ถ”์ถœ๋œ ๊ธฐํ•˜ํ•™์  ๋ฐ์ดํ„ฐ - all_extracted_tags: ํ†ตํ•ฉ๋œ ํƒœ๊ทธ ๋ฆฌ์ŠคํŠธ - config: {'dist_threshold': 50.0, 'tag_threshold': 100.0} ๋“ฑ ์„ค์ •๊ฐ’ """ self.data = geometric_data self.all_tags = all_extracted_tags if all_extracted_tags else [] self.config = config if config else {'dist_threshold': 50.0, 'tag_threshold': 100.0} self.G = nx.DiGraph() # ๋ฐฉํ–ฅ์„ฑ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ def build_graph(self): # 1. ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๋…ธ๋“œ๋กœ ์ถ”๊ฐ€ for item in self.data: self.G.add_node(item['id'], type=item['type'], bbox=box(*item['bbox'].values()), value=item.get('value')) # 2. ๋ถ„์‚ฐ ์ถ”์ถœ๋œ ํƒœ๊ทธ ํ†ตํ•ฉ ๋ฐ ๋…ธ๋“œ ์ถ”๊ฐ€ for tag in self.all_tags: self.G.add_node(tag['id'], type='TEXT', bbox=box(*tag['bbox'].values()), value=tag.get('tagName')) # 3. ํƒœ๊ทธ-์„ค๋น„ ๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ (Association) tags = [n for n, d in self.G.nodes(data=True) if d['type'] == 'TEXT'] equipments = [n for n, d in self.G.nodes(data=True) if d['type'] != 'TEXT'] for tag in tags: best_match = self._find_nearest_equipment(tag, equipments) if best_match: self.G.add_edge(tag, best_match, relation='associated_with') # 4. ๋ฐฐ๊ด€ ๊ธฐ๋ฐ˜ ๋ฌผ๋ฆฌ์  ์—ฐ๊ฒฐ (Pipe) [๊ฐœ์„ ๋จ] lines = [n for n, d in self.G.nodes(data=True) if d['type'] in ['LINE', 'POLYLINE']] for line_id in lines: line_geom = self.G.nodes[line_id]['bbox'] # ์‹ค์ œ๋กœ๋Š” LineString ๊ฐ์ฒด์—ฌ์•ผ ํ•จ # ๋ผ์ธ์˜ ๋์  ์ถ”์ถœ (๊ฐ€์ •: line_geom์ด LineString์ธ ๊ฒฝ์šฐ) endpoints = [line_geom.coords[0], line_geom.coords[-1]] if hasattr(line_geom, 'coords') else [] connected_nodes = [] for pt in endpoints: p = Point(pt) for eq_id in equipments: if self.G.nodes[eq_id]['bbox'].distance(p) < self.config['dist_threshold']: connected_nodes.append(eq_id) if len(connected_nodes) >= 2: # ๋ฐฉํ–ฅ์„ฑ ์ถ”๋ก  ๋กœ์ง (๋‹จ์ˆœํ™”: ์‹œ์ž‘์  -> ๋์ ) self.G.add_edge(connected_nodes[0], connected_nodes[1], relation='pipe') def _find_nearest_equipment(self, tag_id, equipment_ids): tag_bbox = self.G.nodes[tag_id]['bbox'] min_dist = float('inf') nearest = None for eq_id in equipment_ids: eq_bbox = self.G.nodes[eq_id]['bbox'] dist = tag_bbox.distance(eq_bbox) if dist < min_dist: min_dist = dist nearest = eq_id return nearest if min_dist < self.config['tag_threshold'] else None def validate_topology(self): """์œ„์ƒ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ""" isolated = list(nx.isolates(self.G)) return {"isolated_nodes": isolated, "node_count": self.G.number_of_nodes(), "edge_count": self.G.number_of_edges()} # ์‹คํ–‰ ์˜ˆ์‹œ all_tags = flatten_results([worker1_res, worker2_res]) config = {'dist_threshold': 30.0, 'tag_threshold': 80.0} builder = PidTopologyBuilder(geometric_data, all_extracted_tags=all_tags, config=config) builder.build_graph() validation_res = builder.validate_topology() print(f"Validation Result: {validation_res}") ``` ### 3.2 ์œ„์ƒ ๋ถ„์„ ์œ ํ‹ธ๋ฆฌํ‹ฐ: ์˜ํ–ฅ๋„ ๋ถ„์„ (Impact Analysis) ```python def analyze_impact(graph, start_node): """ํŠน์ • ์„ค๋น„ ์žฅ์•  ์‹œ ํ•˜๋ฅ˜(Downstream)์— ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๋ชจ๋“  ๋…ธ๋“œ ์ถ”์ถœ""" # BFS๋ฅผ ํ†ตํ•ด ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋…ธ๋“œ ํƒ์ƒ‰ impacted_nodes = nx.descendants(graph, start_node) return list(impacted_nodes) # ์˜ˆ: P-101 ํŽŒํ”„ ๊ณ ์žฅ ์‹œ ์˜ํ–ฅ ๋ถ„์„ affected = analyze_impact(graph, "node_P101") print(f"Impacted Equipment: {affected}") ``` --- ## ๐Ÿš€ 4. Phase 2 ์™„๋ฃŒ ๊ธฐ์ค€ (Definition of Done) - [ ] ๋ชจ๋“  ์„ค๋น„์™€ ๊ณ„๊ธฐ๊ฐ€ ๊ทธ๋ž˜ํ”„์˜ **๋…ธ๋“œ(Node)**๋กœ ๋ณ€ํ™˜๋˜์—ˆ๋Š”๊ฐ€? - [ ] ๋ถ„์‚ฐ ์ถ”์ถœ๋œ ํƒœ๊ทธ ๋ฆฌ์ŠคํŠธ๊ฐ€ `flatten_results`๋ฅผ ํ†ตํ•ด ํ†ตํ•ฉ๋˜์–ด ๊ทธ๋ž˜ํ”„์— ๋ฐ˜์˜๋˜์—ˆ๋Š”๊ฐ€? - [ ] ํƒœ๊ทธ์™€ ์„ค๋น„ ๊ฐ„์˜ **๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ(Association)**์ด ์ •ํ™•ํ•˜๊ฒŒ ๋งคํ•‘๋˜์—ˆ๋Š”๊ฐ€? - [ ] ๋ฐฐ๊ด€(Line)์˜ **๋์  ๋ถ„์„**์„ ํ†ตํ•ด ์„ค๋น„ ๊ฐ„์˜ **๋ฌผ๋ฆฌ์  ์—ฐ๊ฒฐ(Pipe Edge)**์ด ์ƒ์„ฑ๋˜์—ˆ๋Š”๊ฐ€? (BBox ๊ต์ฐจ ๋ฐฉ์‹ ๋ฐฐ์ œ) - [ ] ํ™”์‚ดํ‘œ ๋ฐ ๊ณต์ • ๊ทœ์น™์— ๊ธฐ๋ฐ˜ํ•œ **๋ฐฉํ–ฅ์„ฑ(Directionality)**์ด ์—ฃ์ง€์— ๋ถ€์—ฌ๋˜์—ˆ๋Š”๊ฐ€? - [ ] `validate_topology`๋ฅผ ํ†ตํ•ด ๊ณ ๋ฆฝ ๋…ธ๋“œ ๋ฐ ์œ„์ƒ ์˜ค๋ฅ˜๊ฐ€ ๊ฒ€ํ† ๋˜์—ˆ๋Š”๊ฐ€? - [ ] `nx.descendants` ๋“ฑ์„ ํ†ตํ•ด ํŠน์ • ๋…ธ๋“œ๋กœ๋ถ€ํ„ฐ์˜ **ํ๋ฆ„ ์ถ”์ (Flow Tracing)**์ด ๊ฐ€๋Šฅํ•œ๊ฐ€? - [ ] ์ƒ์„ฑ๋œ ๊ทธ๋ž˜ํ”„ ๊ตฌ์กฐ๊ฐ€ JSON(GraphML ๋“ฑ) ํ˜•ํƒœ๋กœ ์ €์žฅ๋˜์–ด Phase 3๋กœ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•œ๊ฐ€?