- P&ID: 연결 분석 API, Prefix 규칙 관리, 카테고리 분류, DXF 그래프 빌드 - LLM: 대화 요약, tool card 영구 보존, 시계열 차트(uPlot), 에이전트 모드 - KB: 청크 미리보기, Field Instrument Inference, 인증/Qdrant 클라이언트 - MCP: 서버 기능 확장, 파이프라인 수정, timeout 개선 - Frontend: P&ID UI, LLM UI, KB UI, OPC UA Write 탭 추가 - 설정: AGENTS.md, plant_context, README, opencode.json 업데이트 - 정리: 진단 체크리스트 문서 삭제
9.3 KiB
DXF P&ID 추출 개선 2차 — 배관번호·펌프 태그 누락 버그 수정
작성일: 2026-05-17
작성: Claude Sonnet 4.6
문제 개요
P&ID DXF 파일에서 아래 태그들이 추출되지 않는 버그 보고:
P-10101(10차 펌프 장비 태그)P-10101-25A-F1A-n(10차 프로세스 배관번호)P-9101,P-9102(9차 펌프 장비 태그)P-9107-25A-F-n등 (9차 배관번호)
사용자가 "중복방지 로직에 차단된 것 아닌가" 질문함 → 실제로는 regex 불일치 와 레이어 처리 누락 이 원인.
실제 DXF 구조 조사 결과
파일별 배관번호 형식 차이
| DXF 파일 | 배관번호 레이어 | 형식 예시 | 필드 수 |
|---|---|---|---|
p9-p&id-20.03.19.dxf (9차) |
LINENO |
P-9107-25A-F-n |
5필드 |
p9-p&id-20.03.19.dxf (9차) |
14-D-PIPELINE-LINE |
CHR-9641-50A-F-C50 |
6필드 |
plant-10100-only.dxf (10차) |
LINENO |
P-10101-25A-F1A-n |
7필드 |
10차플랜트-P&ID.dxf |
LINENO |
P-10138-600A-F2A-H100 |
7필드 |
펌프 태그 레이어
| DXF 파일 | 레이어 | 예시 |
|---|---|---|
| 9차 플랜트 | 0 |
P-9101, P-9116, P-9201 |
| 10차 플랜트 | 0, 1 |
P-10101, VP-10117, DP-10101 |
펌프 태그는 모두 일반 TEXT 엔티티 → 추출 자체는 가능했으나 배관번호로 오인되는 문제 있었음.
버그 분석
Bug 1: _PID_LINENO_FULL_RE regex가 9차 배관번호 형식 불일치
파일: mcp-server/server.py
기존 regex (7필드 고정):
^([A-Z][A-Z0-9]{0,3})-(\d{3,6})-(\d{1,4}[A-Z]?)-([A-Z])(\d)([A-Z])-([A-Za-z0-9]+)$
그룹 구조: SERVICE - LINENUM - SIZE - MATERIAL - FLANGE_DIGIT - INSUL_CODE - INSUL_THICK
| 입력 | 결과 | 이유 |
|---|---|---|
P-10101-25A-F1A-n |
✓ MATCH | F→1→A→n 순서 맞음 |
P-9107-25A-F-n |
✗ FAIL | F 다음에 \d 기대하나 -n 등장 |
CHR-9641-50A-F-C50 |
✗ FAIL | 동일 이유 |
9차 플랜트는 플랜지등급 숫자와 단열코드가 분리되지 않고 F-n, F-H50 형식으로 통합되어 있어 7필드 regex에 걸리지 않음.
Bug 2: _extract_pid_dxf_fast가 레이어별로만 배관번호 판단
파일: mcp-server/server.py
기존 로직:
if layer == 'LINENO': # LINENO 레이어만 배관번호 처리
parsed = _parse_pid_lineno(txt)
...
continue
if _PID_TAG_RE.match(txt): # 그 외 레이어는 TAG_RE만 체크
...
결과:
14-D-PIPELINE-LINE레이어의CHR-9641-50A-F-C50→ TAG_RE 불일치 → 완전 누락- 다른 도면에서 배관번호 레이어 이름이 다르면 → 모두 누락
레이어 이름 하드코딩은 도면 간 이식성이 없음. 레이어 이름이 아닌 regex 패턴으로 판단해야 함.
Bug 3: build_pid_graph_parallel pump extractor가 배관번호를 펌프로 오인
파일: mcp-server/worker/pid_extract_prompts.py
LLM pump extractor 프롬프트에 5자리 예시만 있고 배관번호 제외 지시 없음:
Examples: P-10101, VP-10117, DP-10101, C-10201, CP-10301, BP-10401
DXF 전체 텍스트에 P-10101-25A-F1A-n이 포함되어 있을 때 LLM이 이를 보고 P-10101로 잘못 추출.
→ Phase 4 seen_tagnos 중복 제거에서 실제 펌프 P-10101과 충돌
→ 배관번호 P-10101-25A-F1A-n은 graph에서 완전 누락
(사용자가 의심한 "중복방지 로직 차단"은 이 케이스에 해당 — 다만 원인은 LLM의 잘못된 추출임)
수정 내용
Fix 1: _PID_LINENO_FULL_RE — 5~7필드 통합 regex
mcp-server/server.py
# 기존 (7필드 고정)
_PID_LINENO_FULL_RE = re.compile(
r'^([A-Z][A-Z0-9]{0,3})-(\d{3,6})-(\d{1,4}[A-Z]?)-([A-Z])(\d)([A-Z])-([A-Za-z0-9]+)$'
)
# 수정 (5~7필드 통합: pipe_spec이 F, F1A, F2A 등 가변)
_PID_LINENO_FULL_RE = re.compile(
r'^([A-Z][A-Z0-9]{0,3})-(\d{3,6})-(\d{1,4}[A-Z]?)-([A-Za-z][A-Za-z0-9]*)-([A-Za-z0-9]+)$'
)
새 그룹: (service, line_no, size, pipe_spec, insul)
| 입력 | 매칭 | pipe_spec | insul |
|---|---|---|---|
P-9107-25A-F-n |
✓ | F | n |
P-9113-20A-F-H50 |
✓ | F | H50 |
CHR-9641-50A-F-C50 |
✓ | F | C50 |
P-10101-25A-F1A-n |
✓ | F1A | n |
P-10138-600A-F2A-H100 |
✓ | F2A | H100 |
VG-6203-15A-F1A-n |
✓ | F1A | n |
_parse_pid_lineno 반환값도 그룹 수에 맞게 단순화:
# 기존: material_spec, flange_rating, insul_code, insul_thickness (4개 필드)
# 수정: pipe_spec, insul (2개 필드로 통합)
return {
"raw": token, "service": service, "fluid": ...,
"line_no": line_no, "size": size,
"pipe_spec": pipe_spec, # F, F1A, F2A 등
"insul": insul, # n, H50, H100, C50 등
}
Fix 2: _extract_pid_dxf_fast — regex 우선, 레이어는 보조 힌트로만
mcp-server/server.py
# 기존: 레이어 이름 == 'LINENO' 이면 배관번호
if layer == 'LINENO':
parsed = _parse_pid_lineno(txt)
...
continue
if _PID_TAG_RE.match(txt):
...
# 수정: FULL_RE 매칭 → 레이어 무관 배관번호, 짧은 형식만 레이어 힌트 사용
if _PID_LINENO_FULL_RE.match(txt): # 완전한 배관번호 → 레이어 무관
parsed = _parse_pid_lineno(txt)
if parsed is not None:
linenos.append(parsed)
continue
if 'LINENO' in layer.upper(): # 레이어 이름에 LINENO 포함 → 짧은 형식도 배관번호
parsed = _parse_pid_lineno(txt) # (P-10101 같은 단순형은 펌프와 구분 불가능,
if parsed is not None: # 레이어 힌트 불가피)
linenos.append(parsed)
continue
if _PID_TAG_RE.match(txt): # 일반 장비/계기 태그
...
핵심 원칙: 완전한 배관번호는 regex로 식별, 레이어 이름에 의존하지 않음
Fix 3: pump extractor 프롬프트 개선
mcp-server/worker/pid_extract_prompts.py
_PUMP_PROMPT = _PROMPT_HEADER + """
Extract ONLY pumps and compressors (simple equipment tags, NO pipe size suffix).
Target equipment types: P (pump), VP (vertical pump), DP (dual pump),
C (compressor), CP (centrifugal pump), BP (booster pump), SP (sump pump),
and their variants.
Examples (4~5 digit loop numbers): P-10101, VP-10117, DP-10101, C-10201, P-9101, P-9116, VP-9201
IMPORTANT: Do NOT extract pipeline/line numbers that have a pipe size suffix (e.g. 25A, 50A, 100A).
SKIP (pipeline, not a pump): P-10101-25A-F1A-n, P-9107-25A-F-n, CHR-9641-50A-F-C50
INCLUDE (pump tag): P-10101, VP-10117, P-9101
"""
변경점:
- 4자리 번호 예시 추가 (
P-9101,P-9116,VP-9201) - 배관번호 제외 지시 명시 (파이프 사이즈 suffix 있으면 제외)
- SKIP / INCLUDE 예시로 명확하게 구분
검증 결과
regex 단위 테스트 (14/14 통과)
✓ P-9107-25A-F-n → pipe (9차 5필드)
✓ P-9113-20A-F-H50 → pipe (9차 단열)
✓ P-9127-500A-F-H100 → pipe (9차 대구경)
✓ P-10101-25A-F1A-n → pipe (10차 7필드)
✓ P-10138-600A-F2A-H100→ pipe (10차 대구경)
✓ CHR-9641-50A-F-C50 → pipe (냉각수 6필드)
✓ VG-6203-15A-F1A-n → pipe (벤트가스)
✓ SW-10810-25A-F1A-E50 → pipe (소프트워터)
✓ P-10101 → tag (10차 펌프)
✓ P-9101 → tag (9차 펌프)
✓ VP-10117 → tag (진공펌프)
✓ FIT-10101 → tag (유량계)
✓ FCV-6113 → tag (유량제어밸브)
✓ PT-9101 → tag (압력계)
실제 DXF 엔드투엔드 검증
=== p9-p&id-20.03.19.dxf (9차) ===
배관번호 총 242개 (P-: 83개) ← 수정 전: 0개
P 배관번호 예시: P-9107-25A, P-9114-20A, P-9113-20A, ...
펌프 태그: P-9101, P-6101, P-201, P-9201, P-9116
=== plant-10100-only.dxf (10차) ===
배관번호 총 96개 (P-: 57개)
P 배관번호 예시: P-10138-600A, P-10143-32A, P-10127-65A, ...
펌프 태그: P-10101, P-10114, P-10116, P-10118
수정 파일 목록
| 파일 | 변경 라인 | 내용 |
|---|---|---|
mcp-server/server.py |
~221 | _PID_LINENO_FULL_RE regex 교체 |
mcp-server/server.py |
~244 | _parse_pid_lineno 반환값 pipe_spec/insul로 단순화 |
mcp-server/server.py |
~305 | _extract_pid_dxf_fast 레이어/배관번호 처리 로직 수정 |
mcp-server/server.py |
~359 | _extract_pid_tags_from_text step 1 출력에 pipeSpec/insul 추가 |
mcp-server/worker/pid_extract_prompts.py |
~61 | _PUMP_PROMPT 개선 |
설계 결정 사항
| 항목 | 결정 | 이유 |
|---|---|---|
| regex 필드 통합 방식 | 5필드 통합 (pipe_spec이 F, F1A, F2A 통합) |
플랜트마다 배관 사양 코드 체계가 달라 고정 필드 분해는 취약 |
| 레이어 이름 역할 | FULL_RE 불일치 시 보조 힌트로만 사용 | 레이어 이름은 회사·도면마다 다름. regex가 primary. |
짧은 배관번호(P-10101) 처리 |
LINENO 계열 레이어에서만 배관번호로 인식 | P-10101은 펌프 태그와 텍스트가 동일 → 레이어 힌트 불가피 |
| C# PidExtractorService | 미수정 | 펌프 태그(TEXT 엔티티)는 기존 코드에서 정상 추출됨. ATTRIB 읽기 추가는 별도 검토 필요 |