# P&ID 재설계 - 코딩 계획 (독립 프로세스 병렬 아키텍처)

> 작성일: 2026-05-05
> 상태: 코드 구현 완료 (Phase 1~4 완료, Phase 5는 vLLM 서버 필요)
> 완료 시각: 2026-05-05T14:34 KST
> 목표: `No-10_Plant_PID.dxf`(28,819 엔티티) 처리 시 30분 타임아웃 해결
> 아키텍처: pid_worker.py(코디네이터) + 5개 독립 추출 프로세스

---

## DXF 구조 분석 결과 (2026-05-05 확인)

**No-10_Plant_PID.dxf 실제 구조:**
- 전체 X 범위: -176.5 ~ 5582.9 (너비 5,759)
- 전체 Y 범위: 4822.0 ~ 6756.3 (높이 1,934)
- TITLE 레이어: 긴 수직 LINE 없음 (도면 경계 LINE X)
- X 축 분포: 3800-3900 부근 sparse gap (자연적 분할 지점)
- Y 축 분포: 5100-5300에 밀집 (메인 도면 영역)

**분할 전략 변경:** TITLE 레이어 LINE 기반 → X/Y 축 엔티티 밀도 기반 sparse region 감지

---

## 제안 아키텍처

```
┌─────────────────────────────────────────────────────────────────┐
│                    pid_worker.py (코디네이터)                      │
│                                                                 │
│  Phase 1: 도면 분할 + 기하 추출 (순차)                            │
│  ├─ extractor.split_drawings() → 밀도 기반 도면 영역 분할         │
│  └─ extractor.extract_and_save() → geo.json                     │
│                                                                 │
│  Phase 2: 전체 텍스트 1회 추출                                    │
│  └─ DXF에서 TEXT/MTEXT → full_text.txt (1회만 읽기)               │
│                                                                 │
│  Phase 3: 5개 독립 프로세스 병렬 실행 ← 진짜 병렬                  │
│  ├─ subprocess: pid_extract_sensor.py  → results/sensor.json     │
│  ├─ subprocess: pid_extract_valve.py   → results/valve.json      │
│  ├─ subprocess: pid_extract_system.py  → results/system.json     │
│  ├─ subprocess: pid_extract_gauge.py   → results/gauge.json      │
│  └─ subprocess: pid_extract_pump.py    → results/pump.json       │
│                                                                 │
│  Phase 4: pid_worker가 결과 파일 읽어서 통합                      │
│  ├─ 5개 JSON 파일 로드                                           │
│  ├─ 중복 제거 (tagNo 기준)                                        │
│  └─ 위상 그래프 빌드 + 태그 매핑                                  │
│                                                                 │
│  Phase 5: 저장 + 응답                                            │
└─────────────────────────────────────────────────────────────────┘
```

---

## 변경 대상 파일

| 파일 | 변경 내용 |
|------|-----------|
| `mcp-server/pipeline/extractor.py` | 도면 분할 로직 추가 (밀도 기반) |
| `mcp-server/worker/pid_worker.py` | 코디네이터 로직: 프로세스 관리, 결과 통합 |
| `mcp-server/worker/pid_extract_sensor.py` | 신규: 센서 전용 추출기 (독립 프로세스) |
| `mcp-server/worker/pid_extract_valve.py` | 신규: 밸브 전용 추출기 (독립 프로세스) |
| `mcp-server/worker/pid_extract_system.py` | 신규: 시스템 전용 추출기 (독립 프로세스) |
| `mcp-server/worker/pid_extract_gauge.py` | 신규: 게이지 전용 추출기 (독립 프로세스) |
| `mcp-server/worker/pid_extract_pump.py` | 신규: 펌프 전용 추출기 (독립 프로세스) |
| `mcp-server/pipeline/mapper.py` | 기존 유지 (이미 배치 처리 구현됨) |
| `mcp-server/pipeline/topology.py` | 기존 유지 (이미 SpatialGrid 구현됨) |
| `mcp-server/pipeline/legend_parser.py` | 기존 유지 (이미 계측기 그룹 정의됨) |

---

## 체크리스트

### Phase 1: 도면 분할 (밀도 기반)
- [x] 1-1. DXF 구조 분석 스크립트 작성 (X/Y 밀도 히스토그램 출력)
- [x] 1-2. sparse region 감지 알고리즘 프로토타입
- [x] 1-3. DrawingRegion 데이터클래스 + split_drawings() 메서드 추가
- [x] 1-4. extract_region() 메서드 추가
- [x] 1-5. 도면 분할 통합 테스트 (2개 영역 감지, 2.6초 처리)

### Phase 2: 독립 추출기 공통 템플릿
- [x] 2-1. 공통 추출기 템플릿 스크립트 작성 (CLI + vLLM 호출 + JSON 출력)
- [x] 2-2. 5개 계측기 유형별 프롬프트 상수 정의

### Phase 3: 5개 독립 추출기 스크립트
- [x] 3-1. pid_extract_sensor.py 생성
- [x] 3-2. pid_extract_valve.py 생성
- [x] 3-3. pid_extract_system.py 생성
- [x] 3-4. pid_extract_gauge.py 생성
- [x] 3-5. pid_extract_pump.py 생성
- [x] 3-6. 개별 추출기 단독 테스트

### Phase 4: pid_worker.py 코디네이터
- [x] 4-1. 전체 텍스트 1회 추출 로직 (full_text.txt 생성)
- [x] 4-2. 5개 프로세스 subprocess.Popen 병렬 실행
- [x] 4-3. 결과 JSON 통합 + tagNo 기준 중복 제거
- [x] 4-4. 기존 topology.py 호출 연동
- [x] 4-5. _build_pid_graph_parallel() 전체 교체

### Phase 5: 통합 테스트
- [ ] 5-1. 전체 파이프라인 통합 테스트 (No-10_Plant_PID.dxf)
- [ ] 5-2. 실패 격리 테스트 (1개 추출기 고의 실패)
- [ ] 5-3. 메모리 사용량 측정

---

## 실행 순서 및 의존성

```
Phase 1 (도면 분할)
  ├── 1-1 → 1-2 → 1-3 → 1-4 → 1-5  (순차)
  │
Phase 2 (공통 템플릿)
  ├── 2-1 → 2-2  (순차, Phase 1 완료 후)
  │
Phase 3 (5개 추출기 생성)
  ├── 3-1 ~ 3-5  (병렬 가능, Phase 2 완료 후)
  ├── 3-6        (순차, 3-1~3-5 완료 후)
  │
Phase 4 (pid_worker 코디네이터)
  ├── 4-1 → 4-2 → 4-3 → 4-4 → 4-5  (순차, Phase 3 완료 후)
  │
Phase 5 (통합 테스트)
  ├── 5-1 → 5-2 → 5-3  (순차, Phase 4 완료 후)
```

---

## 각 단계 상세 설명

### 1-1. DXF 구조 분석 스크립트 작성
- **파일**: `test_drawing_split.py` (신규)
- **목표**: DXF의 X/Y 축 엔티티 밀도 분포를 히스토그램으로 출력
- **작업**:
  1. ezdxf로 DXF 로드
  2. 각 엔티티의 중심 좌표 수집
  3. X/Y 축별 100 단위 버킷으로 밀도 계산
  4. sparse region (엔티티가 거의 없는 구간) 출력
- **완료 기준**: 콘솔에 X/Y 밀도 히스토그램 + sparse region 출력

### 1-2. sparse region 감지 알고리즘 프로토타입
- **파일**: `test_drawing_split.py` (확장)
- **목표**: 밀도 히스토그램에서 sparse region을 자동으로 감지
- **작업**:
  1. X/Y 축 밀도 배열에서 연속된 low-density 구간 감지
  2. 임계값: 버킷당 엔티티 수 < 전체 평균의 10%
  3. sparse region을 도면 경계로 사용
- **완료 기준**: No-10_Plant_PID.dxf에서 2-3개 도면 영역 감지

### 1-3. DrawingRegion + split_drawings() 메서드
- **파일**: `mcp-server/pipeline/extractor.py`
- **목표**: PidGeometricExtractor에 도면 분할 기능 추가
- **작업**:
  1. `DrawingRegion` 데이터클래스 정의 (drawing_no, x_min, x_max, y_min, y_max)
  2. `split_drawings() -> List[DrawingRegion]` 메서드 추가
  3. X/Y 밀도 기반 sparse region 감지 로직 구현
- **완료 기준**: `split_drawings()` 호출 시 도면 영역 목록 반환

### 1-4. extract_region() 메서드
- **파일**: `mcp-server/pipeline/extractor.py`
- **목표**: 특정 도면 영역만 추출하는 메서드
- **작업**:
  1. `extract_region(region: DrawingRegion) -> List[GeometricEntity]` 추가
  2. bbox가 region 범위 내에 있는 엔티티만 필터링
- **완료 기준**: 각 도면별 엔티티 수 합계가 전체와 일치

### 1-5. 도면 분할 통합 테스트
- **파일**: `test_drawing_split.py`
- **목표**: DXF 로드 → 분할 → 영역별 추출 → 검증
- **완료 기준**: 모든 도면 영역 정상 추출, 총 처리 < 30초

### 2-1. 공통 추출기 템플릿
- **파일**: `mcp-server/worker/pid_extract_template.py` (신규)
- **목표**: 5개 추출기가 공유하는 공통 로직
- **작업**:
  1. CLI 인자 파싱: `--input`, `--output`, `--prompt-file`
  2. vLLM HTTP 클라이언트 연결 (VLLM_BASE_URL 환경변수)
  3. LLM 호출 → JSON 파싱 → 태그 목록 반환
  4. `max_tokens=65536`, finish_reason=length 복구
  5. 결과를 JSON 파일로 쓰기
- **완료 기준**: 템플릿 스크립트 단독 실행 가능

### 2-2. 계측기 유형별 프롬프트 정의
- **파일**: 각 추출기 스크립트 상단 상수
- **프롬프트 목록**:
  - `_SENSOR_PROMPT` — FT, FIT, LT, PT, TE, PG, LG, TG
  - `_VALVE_PROMPT` — FCV, TCV, LCV, PCV, XV, FV, LV, PV, TV
  - `_SYSTEM_PROMPT` — LI, PI, TI, FIQ, FICQ, TICA, PICA, LICA
  - `_GAUGE_PROMPT` — PG, TG, LG
  - `_PUMP_PROMPT` — P-10101, VP-10117, DP-10101 등

### 3-1 ~ 3-5. 5개 추출기 스크립트
- 각 템플릿 기반, 해당 프롬프트 적용
- 단독 실행 가능 (CLI 인자로 input/output 전달)

### 3-6. 개별 추출기 테스트
- **파일**: `test_individual_extractors.py` (신규)
- 5개 추출기 각각 단독 실행, JSON schema 검증

### 4-1. 전체 텍스트 1회 추출
- **파일**: `mcp-server/worker/pid_worker.py`
- DXF에서 TEXT/MTEXT 한 번만 읽어 full_text.txt 생성

### 4-2. 5개 프로세스 병렬 실행
- **파일**: `mcp-server/worker/pid_worker.py`
- `subprocess.Popen`으로 5개 추출기 동시 실행
- timeout=300초, 실패 시 로깅 + 계속

### 4-3. 결과 통합 + 중복 제거
- **파일**: `mcp-server/worker/pid_worker.py`
- 5개 JSON 로드 → tagNo 기준 병합 (첫 번째 우선)

### 4-4. 위상 그래프 빌드 + 태그 매핑
- **파일**: `mcp-server/worker/pid_worker.py`
- 기존 `IntelligentMapper`, `TopologyBuilder` 호출

### 4-5. _build_pid_graph_parallel() 전체 교체
- **파일**: `mcp-server/worker/pid_worker.py`
- 기존 asyncio.gather 기반 코드를 새 아키텍처로 교체

### 5-1 ~ 5-3. 통합 테스트
- 전체 파이프라인, 실패 격리, 메모리 측정

---

## 주의 사항

1. **백업 필수**: 각 파일 수정 전 `.rooBackup/`에 백업
2. **diff 제시**: 변경 내용 diff 형식으로 제시 후 확인
3. **작은 단계**: 각 단계를 독립적으로 완료하고 검증
4. **테스트 우선**: 테스트 스크립트 먼저 작성 후 구현
5. **기존 코드 유지**: topology.py, mapper.py는 기존 유지
6. **프로세스 간 통신**: 파일 기반 (JSON)으로만 통신
7. **임시 파일 정리**: 각 요청 완료 후 임시 디렉토리 삭제

---

## 다음 시작 시

1. 이 파일의 체크리스트에서 첫 번째 미완료 항목부터 시작
2. 각 단계 완료 시 체크리스트 업데이트 ([ ] → [x])
3. 문제가 발생하면 해당 단계에서 중단하고 원인 분석
4. 완료 기준을 충족해야 다음 단계로 진행
