# P&ID 추출 데이터 이용방안 플랜

작성일: 2026-05-14
배경: `pid_equipment` 테이블에 No-10_Plant_PID.dxf로부터 460건의 구조화 데이터(태그·라인번호·fluid·종류·area) 적재 완료. Experion realtime_table과의 자동 매핑(exact + prefix only, false positive 제거)도 동작 중. 다음 단계로 이 데이터를 무엇에 쓸지 결정.

## 현재 보유 데이터

| 컬럼 | 의미 | 예시 |
|------|------|------|
| `tag_no` | 추출된 원본 태그/라인번호 | `P-10138-600A-F2A-H100`, `PSV-10217`, `FCV-6113` |
| `instrument_type` | prefix 분류 | `FCV`, `PSV`, `P`, `T`, `BT`, `LIA` |
| `line_number` | LineNo 식별번호 (pipe만) | `10138`, `6203` |
| `confidence` | 추출 신뢰도 | 0.95~0.99 |
| `experion_tag_id` | realtime_table FK (매핑 시) | NULL or int |
| `is_active` | 활성 여부 | true/false |

서버 내부 사전(코드)으로 `_PID_FLUID_DICT`(P=PROCESS, CWS=COOLING WATER SUPPLY 등 19종), `_PID_EQUIPMENT_PREFIX`(P=Pump, T=Tank, F=Filter 등 14종), `_PID_INSTRUMENT_FIRST`/`_PID_INSTRUMENT_MODIFIER`(ISA letter 코드)도 함께 보유.

---

## 갈래 1 — 채팅 컨텍스트 보강 (최우선 추천)

**가치**: 운전원이 즉시 체감. 작업량 최소.
**작업량**: 0.5~1일.

### 구현
MCP 도구 2개 추가 (mcp-server/server.py):

```python
@mcp.tool()
async def find_pid_equipment(query: str, kind: str | None = None,
                              area: str | None = None, limit: int = 20) -> str:
    """P&ID 장비/계기 검색. tag_no ILIKE 매칭 + kind/area 필터.

    kind: 'pipe'|'equipment'|'instrument' (없으면 전체)
    area: 6-1 → tag_no가 P-61XX/T-61XX 등 6+1로 시작
    """

@mcp.tool()
async def get_pid_equipment_info(tag_no: str) -> str:
    """단일 태그 상세. 매핑된 Experion 태그도 함께 반환.

    응답: {tag_no, type(fluid 또는 장비종류), instrument_type, line_number,
            experion_tag, latest_value (있으면)}
    """
```

`ToolGuideKo`(OllamaController.cs)에 새 도구 설명 추가 → LLM이 자동 활용.

### 효과 예시
- "PSV-10217이 뭐야?" → "Pressure Safety Valve (mechanical, OPC 신호 없음). 10차 area"
- "10차에 펌프 몇 대?" → `find_pid_equipment(kind='equipment', area='10')` → 카운트 + 리스트
- "P-10138 어떤 fluid?" → "PROCESS FLUID, 600A, F2A spec, H100 단열"
- "FCV-6113 지금 값은?" → 매핑된 experion 태그로 자동 join → 실시간값

---

## 갈래 2 — 알람·이벤트 컨텍스트 자동 주입

**가치**: 알람 보고서가 풍부해짐. 운전원이 태그 코드만 보고 의미 파악 가능.
**작업량**: 0.5일. 기존 `summarize_events`/`generate_status_report`에 join 추가.

### 구현
- `query_events` 결과를 `pid_equipment`와 LEFT JOIN해서 각 이벤트에 PID 컨텍스트 첨부
- 예: ALARM event `tagname=fcv-6113.pv` → 컨텍스트 "FCV-6113, Flow Control Valve, 6-1 area, P-10138 라인의 PROCESS 흐름 제어"
- `generate_status_report` 마크다운 출력에 "## 영향 장비" 섹션 추가 (해당 area의 PID 인벤토리 요약)

### 효과
- 알람 요약 LLM이 "단순 태그 나열" → "공정 맥락 포함된 운전 보고서"로 격상
- KB 문서에서 같은 area의 절차서 자동 인용 (search_kb area 필터 + PID 데이터 연동)

---

## 갈래 3 — P&ID 다이어그램 시각화 (UI)

**가치**: 시각적 자산 파악. 도면 없이도 area별 장비 구조 한눈에 보기.
**작업량**: UI 작업 중심. 그래프 보기까지 가려면 from-to 추출 추가 필요.

### Phase 3a (즉시 가능)
현재 데이터만으로 — area별·종류별 트리뷰/리스트뷰:
```
└─ 10차 area
   ├─ 펌프 (15)         P-10101, P-10116, …
   ├─ 탱크 (19)         T-10101, T-10102, …
   ├─ 필터 (4)          F-10101A, F-10101B, …
   ├─ 안전밸브 (18)     PSV-10101, PSV-10103, …
   └─ 라인 (172)        P-10138, CWS-10612, …
```
14번 P&ID 탭 또는 신규 탭에서 구현.

### Phase 3b (확장, 작업 큼)
DXF에서 라인 끝점 좌표 + 장비 좌표 추출 후 KD-tree로 from-to 추정 → cytoscape/d3로 노드 그래프. 사용자 이미 결정한 "위치좌표 계산 없이" 방향과 상충 — **추후 필요해질 때만 착수**.

---

## 갈래 4 — 자산 마스터 + KB 통합 (장기 그림)

**가치**: 단순 검색을 넘어 정비·운영 데이터 통합 플랫폼.
**작업량**: 큼. 다른 데이터 소스 통합 필요.

### 구성요소
1. **equipment_registry.yaml** (이전 논의) — Experion htm에서 추출한 좌표·area + PID 추출 데이터 머지
2. **정비/점검 일정 테이블** — 종류별 주기 정의 (PSV 1년, FCV 6개월, …) → 다음 점검일 자동 계산
3. **누락 검출 리포트** — `pid_equipment`(P&ID 기준) vs `realtime_table`(Experion 기준) 양방향 차집합
   - P&ID에만 있는 것 → 측정 누락 (or mechanical/지표 없는 장비)
   - Experion에만 있는 것 → 도면 누락 (or 시스템 신호)
4. **KB 문서 자동 인용** — `search_kb`에 PID 태그 정보 자동 추가해서 검색 품질 향상

### 의존성
- equipment_registry yaml 구축이 선결 (이전 결정: htm 파서 추가)
- 정비 일정은 별도 사용자 입력 또는 기존 정비 시스템 연동 필요

---

## 갈래 5 — 기존 `infer_field_instruments`와의 통합 (보완 관계, 죽이지 않음)

**배경**: RAG 관리 → Field Instrument 탭의 기존 기능(`infer_field_instruments` → Excel 초안 다운로드)이 `parse_pid_dxf` 등장 후 의미를 잃었는지 의문 제기됨. **결론: 의미 없어지지 않음, 오히려 통합 가치가 생김**.

### 두 기능의 본질적 차이

| 구분 | `infer_field_instruments` (기존) | `parse_pid_dxf` (신규) |
|------|--------------------------------|-----------------------|
| **입력 소스** | Experion DB의 OPC 태그 (`v_tag_summary`) | DXF 도면 파일 |
| **추론 방향** | "실제 측정점이 있다 → 현장에 계기 있을 것" | "도면에 그려져 있다 → 자산으로 존재" |
| **`ficq-6101` 처리** | base 보고 FT-6101 / FIC-6101 / FCV-6101 **3개로 분해** | 도면에 그려진 그대로 (보통 FCV-6101, FIC-6101 등) 인식 |
| **PSV-10217** (mechanical, 신호 없음) | ❌ 못 만듦 | ✅ 도면에 있으면 추출 |
| **추가된 임시 계기** (OPC 신호만 존재) | ✅ 추론 가능 | ❌ 도면에 없으면 못 잡음 |
| **출력** | Excel 초안 (운전원 수동 검수 후 KB 업로드) | `pid_equipment` 테이블 |
| **동력기기** | `power_equipment` role로 별도 분류 | Pump prefix(`P`) 등으로 분류 |

### 잡는 누락이 정반대 — 단독으로는 불완전

- **OPC 신호 있지만 도면 미반영**: `infer_field_instruments`만 잡음 (도면 업데이트 누락 케이스)
- **도면엔 있지만 OPC 신호 없음**: `parse_pid_dxf`만 잡음 (PSV/수동밸브/게이지/파이프 등 mechanical 자산)
- **둘 다 존재**: `match_pid_tags`가 자동 연결 (exact + prefix)

### 통합으로 새로 가능해지는 작업

1. **`infer_field_instruments` 출력 자동 검증**
   - infer가 추론한 FT-6101이 실제 도면(`pid_equipment`)에 있는지 즉시 대조
   - 추론 정확도 자동 측정 → 추론 룰 개선 피드백 루프 가능

2. **Excel 초안 자동 라벨링 보강**
   - 도면에는 PSV-10217이 있는데 infer는 못 만들었음 → "OPC 신호 없는 mechanical" 라벨로 Excel에 자동 행 추가
   - 도면 LineNo 정보(`P-10138-600A-F2A-H100`)로 fluid/size/material 컬럼 자동 채움
   - infer가 만든 row와 P&ID에서만 보이는 row를 한 Excel에 합쳐서 운전원이 한 번에 검수

3. **양방향 차집합 리포트** (갈래 4의 핵심 산출물)

   | 차집합 | 의미 | 운영 액션 |
   |--------|------|-----------|
   | `pid_equipment` − `realtime_table` (도면 ⊖ 실제) | 도면엔 있는데 측정 없는 자산 | (a) mechanical → 정상, "OPC 신호 없음" 라벨로 마스터에 등록<br>(b) 측정 누락 → 신호 추가 검토 |
   | `realtime_table` − `pid_equipment` (실제 ⊖ 도면) | 측정은 있는데 도면 없는 신호 | 도면 업데이트 누락 → P&ID 도면 보완 요청 |
   | 양쪽 모두 | 정상 매핑 | UI에서 ✅ 표시 (현재 상태) |

### 구현 (작업량 적음)

- `infer_field_instruments`의 dedup 단계 뒤에 `pid_equipment` LEFT JOIN 한 번
  - infer 결과 row마다 `in_pid_drawing: bool`, `pid_line_number`, `pid_drawing_no` 컬럼 추가
  - infer가 만들지 못한 `pid_equipment` row를 별도 시트(`Pid_Only_Mechanical`)로 Excel에 추가
- `parse_pid_dxf`는 손대지 않음. infer 쪽에만 join 추가
- 운전원이 다운받는 Excel이 그대로 더 풍부해짐 — 추가 UI 변경 불필요

### 의존성

- 갈래 1·2 없어도 가능. 단독 진행 가능
- `infer_field_instruments`의 출력 Excel 포맷 일부 변경 (시트 추가) — 기존 운영 흐름에 영향 적음

---

## 갈래 6 — PID 추출 → 옵시디언 노트 부트스트랩 → 하이브리드 RAG

**배경**: `plans/옵시디언-구조적용-플랜.md`는 마크다운 노트 + `[[wikilinks]]` + frontmatter 기반 그래프 RAG를 제안하지만 **아직 미구현** (`notes/` 디렉토리·DB 테이블 없음, 위키링크가 들어있는 .md는 계획 문서 자체뿐). 옵시디언 노트 시스템을 0에서 시작하면 "초기 비어있음" 문제가 큰데, PID 추출 결과를 활용하면 **첫날부터 460개 anchor 노트**로 부트스트랩 가능.

### 역할 분담

| 영역 | PID 추출 (`pid_equipment`) | 옵시디언 노트 (planned) |
|------|---------------------------|------------------------|
| 채우는 것 | **뼈대** — 모든 태그/라인/장비 자동 인벤토리 | **살** — 절차, 루프 의미, 트러블슈팅, 벤더 정보, 운전 노하우 |
| 입력 | 도면 1번 + 자동 | 운전원/엔지니어 수기 + 채팅 저장 |
| 갱신 | 도면 바뀌면 재추출 | 사람이 알게 될 때마다 |
| 핵심 식별자 | `tag_no` (FCV-6113, P-10101) | 노트 ID `tag/fcv-6113`, `loop/compression-a` |

**공통 anchor가 tag ID** — PID가 만든 모든 태그는 곧 노트 ID가 되고, 사람이 쓴 노트는 `[[tag/fcv-6113]]` 위키링크로 PID 인벤토리에 자동 연결.

### 구현 단계

#### Step 1 — PID stub 노트 자동 생성

`pid_equipment` 460건을 각각 frontmatter-only `.md` 파일로 발행:

```yaml
---
id: tag/fcv-6113
kind: tag
prefix: FCV
type: Flow Control Valve
area: 6-1
line_number: 6113
experion_tag: fcv-6113.pv
source_drawing: No-10_Plant_PID.dxf
source: pid_equipment.id=42
generated_at: 2026-05-14
---
<!-- 본문은 비어둠 — 운전원이 채움 -->
```

- 디렉토리: `notes/tag/`, `notes/area/`, `notes/drawing/` 등
- 한 파일 = 한 노트 = `pid_equipment` 1행
- 본문 비어있어도 anchor 역할 → 위키링크 타깃으로 즉시 사용 가능

#### Step 2 — 사람 노트는 위에 자라남

운전원/엔지니어가 만드는 손 노트는 별도 kind:
- `notes/loop/compression-a.md` — "이 루프는 [[tag/fcv-6113]] [[tag/pt-6111]] [[tag/ft-6113]]로 구성됨"
- `notes/procedure/start-up-a.md` — "기동 시 [[loop/compression-a]] 먼저 안정화"
- `notes/event-pattern/surge-recovery.md` — "[[tag/fcv-6113]] surge 발생 시 [[procedure/anti-surge]] 적용"

**원칙**: 사람은 신규 마크다운만 쓰고 PID stub은 안 건드림. 재추출 시 stub 안전하게 덮어쓰기 가능.

#### Step 3 — Stub 갱신 정책

PID 재추출 시:
- frontmatter는 항상 최신 `pid_equipment` 값으로 덮어씀
- 본문(`<!-- comment -->` 아래)에 운전원이 추가한 텍스트가 있으면 보존 (merge 로직)
- 새로 등장한 tag → 신규 stub 생성
- 사라진 tag → `archived: true` frontmatter 추가 (파일 삭제 안 함, 위키링크 깨짐 방지)

#### Step 4 — KB 인덱싱 + 그래프 엣지 추출

`notes/` 디렉토리를 KbIngestWorker가 별도 컬렉션(예: `kb_notes`)으로 처리:
- 노트 1개 = 1 청크 (헤딩으로 자르지 않음 — 작은 단위 유지)
- frontmatter → Qdrant payload (area, kind, prefix 필터링 가능)
- 본문에서 `[[note-id]]` 정규식 추출 → 신규 테이블 `kb_note_edges (source_id, target_id, link_type)`에 적재

#### Step 5 — 채팅 RAG에 그래프 1-hop 결합

`search_kb`가 top-k 청크 검색 → 각 청크가 노트면 `kb_note_edges`에서 1-hop 위키링크 타깃 조회 → 그 타깃 노트들의 frontmatter+첫 200자를 컨텍스트에 함께 주입.

```python
# 의사 코드
chunks = search_kb(query, top_k=5)
extra_context = []
for chunk in chunks:
    if chunk.note_id:
        targets = db.fetch_edges(chunk.note_id, depth=1)
        extra_context.extend(targets)  # 5개 청크 + N개 1-hop 노트
return chunks + extra_context  # LLM에 함께 전달
```

LLM은 청크 5개만이 아니라 "이 청크와 명시적으로 연결된 절차/루프/이벤트 패턴"까지 함께 보고 답변.

### 트레이드오프

- (+) 옵시디언 시스템의 "초기 비어있음" 문제 해결 — 첫날부터 460개 anchor 보유
- (+) 운전원이 채팅에서 받은 답변을 `loop/surge-recovery.md` 한 줄로 영구화 가능 → 지식 자산이 자라남
- (+) 마크다운이라 외부 옵시디언 앱으로 열어 편집해도 호환 (부수효과)
- (−) stub 갱신 시 본문 보존 머지 로직 필요 (text diff 충돌 검출)
- (−) 운전원이 위키링크 작성 습관 안 들이면 anchor만 잔뜩에 살이 없는 상태 → **저진입 UX 필수**:
  - 채팅에서 "이 답변을 [[loop/compression-a]]에 저장" 같은 한 클릭 액션
  - 노트 작성 시 `[[` 입력하면 자동완성 드롭다운으로 PID stub 추천

### 의존성

- 갈래 1 권장 선결 — PID 데이터 조회 인프라가 있어야 stub 생성 스크립트 작성이 쉬움
- `옵시디언-구조적용-플랜.md`의 데이터 모델 결정사항 따름 (note ID 규칙, kind 9종, frontmatter 스키마)
- KbIngestWorker에 notes 디렉토리 전용 처리 분기 추가

### 작업 분할

| 단계 | 산출물 | 작업량 |
|------|--------|--------|
| Step 1 | `mcp-server/notes_sync.py` — pid_equipment → notes/tag/*.md stub 생성기 | 0.5일 |
| Step 3 | 머지 보존 로직 + archived 처리 | 0.5일 |
| Step 4 | KbIngestWorker notes 분기 + `kb_note_edges` 테이블 + 위키링크 파서 | 1일 |
| Step 5 | `search_kb` 그래프 1-hop 보강 + 채팅 통합 | 0.5일 |
| 사람 노트 저진입 UX | "답변 → 노트 저장" 버튼, `[[` 자동완성 | 1일 (UI) |
| **합계** | | **3.5일** |

---

## 갈래 7 — Excel 라운드트립으로 service/role 메타데이터 보강 (마지막 연결고리)

**배경**: DXF 파서는 tag·LineNo·fluid(파이프 한정)·prefix 분류는 자동 추출하지만, **장비의 실질 정보(service, role, contents, from-to)는 도면에 텍스트로 없음**. 예: T-201이 원료탱크인지 폐액탱크인지, 무슨 fluid를 담는지, 어디로 보내는지 — LLM이 답할 컨텍스트가 없음. 운전원이 가장 익숙한 도구(Excel)로 한 번에 채우는 round-trip이 가장 실용적인 해결책.

**갈래 5와의 차이**: 갈래 5는 `infer_field_instruments` Excel(현장 계기 초안, **다운로드 전용**)에 PID 정보를 합치는 것. **갈래 7은 PID 메타데이터를 사람이 채워 다시 시스템에 반영하는 round-trip — 편집 가능한 마스터 시트**.

### 시트 구성 (장비 종류별 분리)

| 시트 | 자동 채워진 컬럼 | 사용자가 채울 컬럼 |
|------|----------------|------------------|
| `Tanks` (T-XXX) | tag_no, area, source_drawing | service, role(feed/buffer/product/waste), contents(PGMEA/HBM/...), capacity, notes |
| `Drums` (D-XXX) | 위와 동일 | service, role, contents |
| `Filters` (F-XXX) | tag_no, area | service, medium, from_tag, to_tag |
| `Heat Exchangers` (E-XXX) | tag_no, area | service, hot_side, cold_side, duty |
| `Columns` (C-XXX) | tag_no, area | service, product, overhead, bottom |
| `Pumps` (P-XXX) | tag_no, area, mapped_experion_tag | service, driver(motor/turbine), from_tag, to_tag, power_kw |
| `Compressors / Fans` | tag_no, area | service, driver, power_kw |
| `Cooling Towers` | tag_no, area | service, capacity |
| `Manual Valves` (BV/GV/XV) | tag_no, area | service, normal_position(open/closed), purpose |
| `Pipes` (LineNo) | service, fluid, size, spec, insul (이미 파싱됨) | from_tag, to_tag (오직 이거만) |
| `Legend` | dropdown 후보 (role 종류, fluid 코드, position 등) | — |
| `Index` | 시트별 완성도 (채워진 비율) | — |

### Round-trip 흐름

```
[다운로드]
  pid_equipment → 장비 종류별 그룹핑 → 시트별 분리 → Excel 생성
  (자동 채운 컬럼 + 빈 컬럼 + dropdown legend)
                ↓
[운전원이 채움 (며칠~몇 주, 영역별 분할 가능)]
                ↓
[업로드]
  Excel 파싱 → 검증 (dropdown 값, 필수 필드, 참조 tag_no 존재 여부)
  → pid_equipment에 신규 컬럼들(service, role, contents, from_tag, to_tag, …) UPDATE
  → 변경 diff 리포트 (몇 건 채워졌는지, 무효 값 몇 건)
  → (갈래 6 도입 후) tag stub 노트 frontmatter 자동 갱신
```

### 데이터 모델 확장

`pid_equipment` 테이블에 컬럼 추가:
```sql
ALTER TABLE pid_equipment
  ADD COLUMN service        TEXT,     -- 운전원 입력 fluid/용도 (P=PROCESS와 별개로 자유 텍스트)
  ADD COLUMN role           TEXT,     -- feed_tank, buffer_tank, waste_tank, transfer_pump, ...
  ADD COLUMN contents       TEXT,     -- PGMEA, HBM, CITY_WATER, ...
  ADD COLUMN from_tag       TEXT,     -- 흐름 상류 tag (수동 입력)
  ADD COLUMN to_tag         TEXT,     -- 흐름 하류 tag
  ADD COLUMN driver         TEXT,     -- motor / turbine / electric / steam
  ADD COLUMN capacity       TEXT,     -- 자유 텍스트 (단위 포함)
  ADD COLUMN power_kw       NUMERIC,
  ADD COLUMN user_notes     TEXT,     -- 운전원 자유 메모
  ADD COLUMN metadata_filled_at TIMESTAMPTZ;  -- 마지막 사용자 갱신 시각
```

### 갈래 5/6과의 관계 — 같은 그림의 다른 단계

| 단계 | 역할 | 갈래 |
|------|------|------|
| 자동 추출 | 뼈대 (모든 태그 자동 인벤토리) | PID 파서 (완료) |
| 자동 검증 Excel | mechanical/누락 자산 검출 (read-only) | 갈래 5 |
| **수기 보강 round-trip** | **구조화 슬롯 일괄 채우기 (service/role/from-to)** | **갈래 7 (이 항목)** |
| 노트 자동 생성 | tag stub frontmatter에 갈래 7 데이터 자동 반영 | 갈래 6 Step 1 |
| 채팅 RAG 강화 | 위 모두 통합 검색 | 갈래 1+2+6 |

→ **갈래 7이 진짜 마지막 연결고리**. 이게 없으면 갈래 6 stub의 frontmatter가 빈약하고, 채팅도 "T-201이 뭐 담는지" 답을 못 함.

### 트레이드오프

- (+) Excel은 운전원이 가장 자연스럽게 다루는 도구 — 마크다운 학습 비용 0
- (+) 한 번 다운로드받아 오프라인에서도 채울 수 있음
- (+) 시트별 분리로 "오늘은 펌프만, 내일은 탱크만" 같은 분할 작업 가능
- (+) dropdown legend로 데이터 일관성 자동 확보
- (−) Excel은 discrete batch — 동시에 여러 사람이 채우면 머지 충돌 위험. 한 번에 한 사람 권장
- (−) 다운로드 받은 파일이 오래되면 사이에 PID 재추출로 신규 tag 생긴 경우 머지 필요 — 업로드 시 "신규 tag는 이번 Excel에 없음" 경고
- (−) 한 번에 460건 다 채우라 하면 부담 → 영역별/종류별 분할 가이드 필요 ("이번 주는 6-1 area Pump만")

### 구현 (작업량)

| 단계 | 산출물 | 작업량 |
|------|--------|--------|
| DB 마이그레이션 | `pid_equipment` 신규 컬럼 ALTER + InitializeAsync DDL 갱신 | 0.3일 |
| 다운로드 엔드포인트 | `GET /api/pid/metadata/excel` — 시트별 분리 Excel 생성 (openpyxl) | 0.5일 |
| 업로드 엔드포인트 | `POST /api/pid/metadata/excel` — 파싱+검증+UPDATE+diff 리포트 | 1일 |
| UI (RAG 관리 또는 P&ID 탭) | 다운로드/업로드 버튼 + 진행률 + diff 표시 | 0.5일 |
| Legend dropdown 사전 | role/contents/driver 등 유효값 정의 + 운영 후 확장 가능하게 | 0.2일 |
| **합계** | | **2.5일** |

### 의존성

- 갈래 1·5와 독립적으로 진행 가능
- 갈래 6보다 먼저 끝나야 stub frontmatter가 풍부해짐 → **Phase B 또는 Phase C 초반에 진행 권장**

---

## 갈래 8 — LineNo 파생 정보 컬럼 추가 (작지만 모든 갈래에 가치 전파)

**배경**: 현업 통찰 — 운영 부서는 LineNo(`P-10138`, `16456` 등 시공·단관 제작용 번호)로 파이프를 부르지 않음. 가치 있는 건 거기서 파생되는 **{service, fluid, size, material, flange_rating, insulation}**. LineNo 자체는 시공·정비 참조용으로 보존하되, 파생 정보를 별도 컬럼으로 저장해야 운영 검색·통계·일관성 검증이 가능해짐.

**현재 상태**: `_parse_pid_lineno`가 MCP 응답에 7개 필드를 다 담고 있지만, C# 측이 `line_number` 하나만 DB에 저장하고 나머지는 버리고 있음. 추출은 되는데 활용이 안 됨.

### 구현

```sql
-- pid_equipment 컬럼 추가 (line_number는 그대로 유지)
ALTER TABLE pid_equipment
  ADD COLUMN service_code     TEXT,     -- 'P', 'CWS', 'ST', 'VG', ...
  ADD COLUMN fluid_name       TEXT,     -- 'PROCESS FLUID', 'STEAM' (legend 변환)
  ADD COLUMN pipe_size        TEXT,     -- '25A', '600A'
  ADD COLUMN material_spec    TEXT,     -- 'F', 'S'
  ADD COLUMN flange_rating    INT,      -- 1=150#, 2=300#
  ADD COLUMN insul_code       TEXT,     -- 'A', 'B'
  ADD COLUMN insul_thickness  TEXT;     -- 'H50', 'H100', 'E50', 'n'
```

장비/계기 행은 이 컬럼들이 NULL — 정상. 한 테이블 유지, 정규화 분리 안 함.

### 변경 파일

| 파일 | 변경 |
|------|------|
| `ExperionDbContext.InitializeAsync` | ALTER TABLE 7개 컬럼 추가 (또는 새 설치용 CREATE TABLE 갱신) |
| `PidEquipment` 엔티티 | 7개 속성 + `[Column("snake_case")]` 매핑 |
| `ExtractedItem` DTO | 7개 필드 추가 (PipeService/FluidName/PipeSize/...) |
| `PidExtractorService.ParseJson` | MCP 응답의 pipe 필드를 ExtractedItem에 매핑 |
| `PidExtractorService.ExtractFromStreamAsync` | save 시 7개 컬럼 채움 (pipe 행만, instrument/equipment는 null) |
| MCP `extract_pid_tags` / `parse_pid_dxf` | 이미 7개 필드 응답 중 — 변경 불필요 |

### 활용은 자동으로 따라옴

컬럼만 채워지면 기존 갈래들이 즉시 더 풍부해짐:

- **갈래 1 채팅 검색**: "CWS 25A 이상 라인", "스팀 H100 단열 라인" 같은 속성 조합 검색 즉시 가능
- **갈래 6 stub frontmatter**: pipe 노트에 `service: P`, `fluid: PROCESS FLUID`, `pipe_size: 25A`, `insul_thickness: H50` 자동 반영 → 노트가 빈약하지 않음
- **갈래 7 Pipes 시트**: 자동 채워진 컬럼이 7개로 늘어남, 사용자는 from_tag/to_tag/서비스별칭만 채우면 됨
- **(부수효과)** 사양 분포 리포트, 일관성 검증 같은 분석이 가능해짐 — 필요할 때 별도 도구로 노출하면 됨 (지금은 컬럼만 추가)

### 작업량

| 단계 | 산출물 | 작업량 |
|------|--------|--------|
| DB 컬럼 추가 | ALTER TABLE + InitializeAsync DDL 갱신 | 0.2일 |
| `PidEquipment` 엔티티 + `[Column]` 매핑 | 7개 속성 추가 | 0.1일 |
| `ExtractedItem` 7개 필드 + ParseJson 매핑 | DTO 확장 | 0.2일 |
| `PidExtractorService` save 경로 | 7개 컬럼 INSERT | 0.2일 |
| 기존 460건 재추출 (또는 in-place UPDATE 스크립트) | truncate 후 재추출이 간단 | 0.1일 |
| **합계** | | **0.8일** |

### 의존성

- 없음. 단독 진행 가능
- **갈래 1·6·7의 가치를 증폭시키므로 가장 먼저 배치 권장**

---

## 갈래 9 — 추출 결과 저장·폐기 선택 UX (실수 방지)

**배경**: 현재 P&ID 추출 UI는 "추출 시작" 클릭 시 MCP 추출 → `pid_equipment` 즉시 INSERT가 자동으로 이뤄짐. 운전원이 실수로 다른 파일(다른 플랜트, 구버전 도면 등)을 추출해도 되돌릴 방법이 없음. TRUNCATE는 정상 데이터까지 날아가서 사용 불가. 추출 전/후에 사용자가 검토·취사선택할 수 있어야 함.

**결정 보류**: 아래 3개 옵션 중 어느 방향으로 갈지 사용자 결정 필요.

### 옵션 비교

#### 옵션 A — Dry-run + 저장 버튼 (사전 검토)

```
[추출 시작] → MCP 추출만 (DB 저장 X) → "미리보기 460건" 표시
              ├── [✅ DB에 저장] → INSERT 수행
              └── [🗑️ 폐기] → 결과 버림
```

| 항목 | 내용 |
|------|------|
| 장점 | 가장 안전 — DB 들어가기 전 검토 가능. 운전원 의도 명확 ("이걸 저장할까?") |
| 단점 | UI 흐름 2단계로 늘어남. 결과를 서버 임시 캐시 또는 클라이언트 메모리에 보관 필요. 사용자가 검토 중 페이지 떠나면 데이터 잃음 |
| 작업량 | 약 1일 |
| 구현 | `PidExtractorService.ExtractWithoutSaveAsync` 추가, `IMemoryCache` 결과 보관, UI에 미리보기 + 저장/폐기 버튼 |

#### 옵션 B — 자동 저장 + 즉시 취소 버튼 (사후 롤백)

```
[추출 시작] → MCP 추출 → DB 저장 (extraction_batch_id 부여)
                       → "✅ 추출 완료: 460건" + [🗑️ 이번 추출 취소] 버튼
                                                        ↓
                                          batch_id WHERE 절로 일괄 삭제
```

| 항목 | 내용 |
|------|------|
| 장점 | 기존 UX 거의 그대로. 추출 이력 자체가 자동으로 남아 추후 분석 가능 |
| 단점 | 저장 후 후속 작업(confidence 수정 등)이 일어났으면 일괄 삭제가 그 작업까지 날림 → "최근 N분만 취소 가능" 같은 가드 필요. 다른 사용자가 잠깐 잘못된 데이터 볼 수 있음 |
| 작업량 | 약 0.5일 |
| 구현 | `pid_equipment.extraction_batch_id` (UUID) 컬럼 추가, 추출 시 부여, UI에 "이번 추출 취소" 버튼 + 시간 가드 |

#### 옵션 C — A + 영구 batch_id 보존 (절충)

```
[추출 시작] → MCP 추출 (DB 저장 X, staging 보관)
              ├── [저장] → batch_id와 함께 INSERT
              │           → 이후에도 "추출 이력" 페이지에서 batch_id로 일괄 삭제 가능
              └── [폐기] → staging만 버림
```

| 항목 | 내용 |
|------|------|
| 장점 | A의 안전성 + B의 사후 롤백 모두 확보. "추출 이력" 페이지에서 과거 batch 단위 관리 가능 |
| 단점 | 작업량 가장 큼. UI도 가장 복잡 (미리보기 + 저장 + 이력 관리) |
| 작업량 | 약 1.5~2일 |
| 구현 | 옵션 A + 옵션 B 합친 형태. staging 캐시 + batch_id 컬럼 + 이력 페이지 |

### 추천 (결정 보류 상태)

| 우선 고려 | 추천 옵션 |
|----------|----------|
| 안전성 우선 | **옵션 A** |
| 작업량 최소 | 옵션 B |
| 장기 운영 (감사·이력) | 옵션 C |

운전원이 자주 추출하는 운영 환경이면 옵션 A의 사전 검토가 정신적 부담 적고 안전 — **기본 추천은 옵션 A**. 단, "추출 이력 관리" 같은 요구가 미래에 나올 가능성이 크면 옵션 C가 미리 대응됨.

### 의존성

- 옵션 A: `IMemoryCache` 또는 클라이언트 세션 — 표준 ASP.NET Core 기능
- 옵션 B/C: `pid_equipment.extraction_batch_id` 컬럼 (UUID) — 갈래 8 컬럼 추가와 함께 ALTER 묶어 처리하면 효율적
- 어느 옵션이든 갈래 8과 같은 시점에 진행하면 마이그레이션 1번으로 끝낼 수 있음

### 결정 시점

- 갈래 1~5 진행 중 사용 패턴 보면서 결정 가능 (당장 결정 안 해도 됨)
- 단, 옵션 B/C를 선택한다면 갈래 8 마이그레이션 시 `extraction_batch_id` 컬럼을 함께 추가하는 게 효율적 → **갈래 8 착수 전에 한 번 가볍게 결정 권장**

---

## 추천 진행 순서

| 우선순위 | 항목 | 작업량 | 가치 | 의존성 |
|----------|------|--------|------|--------|
| **0** | **갈래 8 (LineNo 파생 정보 컬럼 추가)** | 0.8일 | **모든 갈래에 가치 전파** | 없음 |
| **0+** | **갈래 9 (추출 결과 저장/폐기 UX)** | 0.5~2일 (옵션별) | 높음 (실수 방지) | 갈래 8과 함께 마이그레이션 묶기 권장 |
| **1** | 갈래 1 (MCP 도구 2개) | 0.5~1일 | 높음 | 갈래 8 권장 |
| **2** | 갈래 5 (infer ↔ pid_equipment 통합 Excel) | 0.5일 | 높음 | 없음 |
| **3** | 갈래 2 (이벤트 컨텍스트) | 0.5일 | 높음 | 갈래 1 |
| **4** | 갈래 7 (Excel round-trip으로 service/role 보강) | 2.5일 | 매우 높음 | 갈래 8 권장 |
| **5** | 갈래 6 (옵시디언 노트 부트스트랩 + 하이브리드 RAG) | 3.5일 | 매우 높음 (장기) | 갈래 1·7·8 권장 |
| 6 | 갈래 3a (트리뷰 UI) | 1~2일 | 중 | 없음 |
| 7 | 갈래 4 일부 (누락 검출 리포트 단독) | 0.5일 | 중 | 갈래 5와 중복 — 갈래 5에 통합 권장 |
| 보류 | 갈래 3b (그래프 시각화) | 큼 | 사용 패턴 보고 결정 | from-to 추출 |
| 보류 | 갈래 4 전체 (정비 일정·KB 통합) | 큼 | 큼 | equipment_registry, 정비 시스템 |

### 추천 흐름

**Phase 0 (반나절, 0.8일)** — 모든 갈래의 가치 증폭
0. **갈래 8**: `pid_equipment`에 service/fluid/size/material/flange/insul 7개 컬럼 추가 + 460건 재추출
   - 이후 모든 갈래가 더 풍부한 데이터로 동작

**Phase A (이번 주, 1~2일)** — 즉시 효과
1. **갈래 1**: `find_pid_equipment` / `get_pid_equipment_info` 추가 → 갈래 8로 채워진 7개 필드까지 검색·표시
2. **갈래 5** 병행: `infer_field_instruments`에 `pid_equipment` join 추가 → Excel 초안에 mechanical/누락 자산 자동 포함

**Phase B (Phase A 사용 패턴 본 뒤, 0.5일)** — 정착 단계
3. **갈래 2**: 알람·이벤트 컨텍스트에 PID 정보 자동 첨부 → 보고서 품질 향상

**Phase C (2.5일 + 운전원 채움 기간)** — 마지막 연결고리
4. **갈래 7**: Excel round-trip 인프라 구축 → 운전원이 service/role/contents/from-to 채움
   - 자동 컬럼이 갈래 8 덕분에 7개 더 풍부 → 사용자 부담 줄어듦
   - 구축 자체는 2.5일이지만 **운전원이 채우는 데 며칠~몇 주** 걸림 (영역별 분할)
   - 일부라도 채워지면 즉시 갈래 1·2의 답변 품질이 올라감 (T-201 → "PGMEA 원료 탱크" 답변 가능)

**Phase D (Phase C 진행 중 병행, 3.5일)** — 지식 자산화로 진화
5. **갈래 6**: PID stub 자동 생성 → 옵시디언 노트 시스템 부트스트랩 → 하이브리드 RAG
   - 갈래 8·7의 결과가 stub frontmatter로 자동 반영되어 빈약하지 않음
   - 운전원이 채팅에서 노트 작성 습관 들이면 시간이 갈수록 그래프가 자라남
   - **장기적으로는 이 갈래가 시스템의 핵심 자산** — PID/Experion은 자동 입력 소스, 노트는 사람의 운영 지식 저장소

**Phase E (필요해질 때)** — 확장
6. **갈래 3a**: 시각적 인벤토리 필요해지면 트리뷰 UI 작업
7. **갈래 4 전체** 또는 **갈래 3b**: 정비 시스템 연동/그래프 시각화 등은 실제 운영 요구가 명확해진 시점에 착수

### 우선순위 배치 근거

- **갈래 8 최우선**: 0.8일 작업으로 갈래 1·6·7 모두의 데이터 가치를 증폭. ROI 극단적으로 높음. 다른 모든 작업 전에 끝내는 게 합리적
- **갈래 5 > 갈래 4**: 갈래 4의 핵심 산출물(양방향 차집합 리포트)이 갈래 5의 Excel 시트로 자연스럽게 포함됨. 운전원이 이미 익숙한 UX 위에 얹는 거라 학습 비용 0
- **갈래 7 > 갈래 6**: 갈래 6 stub의 frontmatter는 갈래 7의 결과(service/role/contents)로 채워질 때 비로소 가치가 있음. 갈래 7 없이 갈래 6만 가면 빈 anchor만 잔뜩
- **갈래 7 > 갈래 3a**: service/role이 채워지지 않은 상태에서 시각화는 "이름만 있는 그림" — 우선 의미를 채워야 함
- **갈래 6은 갈래 1·2·7·8 이후가 적기**: 채팅에서 PID 데이터 활용 패턴이 나오고 메타데이터가 채워진 뒤라야 노트 시스템이 의미 있음

---

## 결정 필요 항목

- [ ] **갈래 8** 즉시 착수 여부 — `pid_equipment` 7개 컬럼 추가 + 460건 재추출 (0.8일, ROI 최고)
- [ ] **갈래 9** 옵션 선택 — A(dry-run, 1일) / B(자동저장+취소, 0.5일) / C(절충, 1.5~2일) 중 하나 선택. **갈래 8 착수 전 결정 권장** (B/C면 `extraction_batch_id` 컬럼을 갈래 8 마이그레이션에 함께 묶음)
- [ ] **갈래 1** 즉시 착수 여부 — `find_pid_equipment` / `get_pid_equipment_info` MCP 도구 추가
- [ ] **갈래 5** 즉시 착수 여부 — `infer_field_instruments`에 `pid_equipment` LEFT JOIN + 별도 시트 추가
- [ ] 갈래 2 진행 여부 — Phase A 정착 후
- [ ] **갈래 7** 데이터 모델 확정 — `pid_equipment` 컬럼 추가 항목(service/role/contents/from_tag/to_tag/driver/capacity/power_kw/user_notes) 확인
- [ ] **갈래 7** legend dropdown 사전 시드값 — role/contents/driver 유효값 누가 정의
- [ ] **갈래 7** 운전원 채우기 가이드 — 영역별 분할 방식(어떤 area부터?)
- [ ] **갈래 6 Step 1·2** 일정 — PID stub 생성기 + 머지 보존 로직 (갈래 8·7 컬럼이 stub frontmatter로 흐르도록 같이 설계)
- [ ] 갈래 6 Step 5 (그래프 1-hop RAG) — `search_kb` 수정 시점 확정 필요
- [ ] 갈래 6 저진입 UX (채팅 답변 → 노트 저장 버튼) — UI 작업 일정
- [ ] 갈래 3a 트리뷰 UI 작업 일정 — 갈래 7 보강 데이터가 채워진 뒤가 효과적
- [ ] 갈래 4 단독 누락 검출 리포트 — 갈래 5에 흡수할지, 별도 페이지로 만들지

## 잔여 데이터 정합성 이슈

- **중복 추출**: 현재 460건 중 동일 tag_no가 2번씩 들어간 행 다수 발견 (예: PSV-10101이 2회). DXF에서 같은 텍스트가 여러 위치에 그려져서 발생. unique index 또는 (tag_no, pid_drawing_no) 복합키로 해결 필요.
- **pid_drawing_no NULL**: 현재 모든 행 NULL. 도면 파일명을 자동 채우거나 사용자 입력 받도록 보강 필요 — 갈래 4 누락 검출에 도면 단위 추적 필요할 때.
- **prefix 오분류**: PFD, SP, SC, TR 등 도면 라벨/약어가 instrument로 잘못 분류되는 케이스 — `_PID_TAG_RE` 또는 `_classify_pid_tag`에 예외 사전 추가 필요.
