diff --git a/plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md b/plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md new file mode 100644 index 0000000..b59f73a --- /dev/null +++ b/plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md @@ -0,0 +1,392 @@ +# P&ID 추출 PREFIX 분류 — `tag_dcs` 컬럼 도입 플랜 + +> **작성일**: 2026-05-27 +> **작성자**: BigPickle +> **목적**: `pid_prefix_rules`와 `pid_equipment` 두 테이블에 `tag_dcs BOOLEAN` 컬럼을 추가해, +> P&ID 추출 **시작 시점**부터 현장 계기(field instrument)와 DCS 태그(DCS function block)를 구별한다. + +--- + +## 0. 배경 및 문제 + +### 현재 구조의 문제 + +현재 `pid_prefix_rules.category = 'instrument'` 아래에 두 종류가 혼재: + +| 종류 | 예시 prefix | 실제 의미 | +|------|------------|---------| +| **현장 계기** (field) | FT, PT, LT, TT, FCV, PCV, PSV, XV, FG, PG | 물리적 기기, 현장 설치 | +| **DCS 함수블록** (system) | FIC, TIC, PIC, LIC, FY, TY, PY, LY | DCS/SCADA 내부 연산 블록, 물리 기기 없음 | + +기존 `tag_class = 'field'/'system'` 컬럼이 이를 구별하려 했으나: +- **추출 후 후처리**에서 판정 (ISA 후속문자 분석 + Experion 연결 여부) +- **PREFIX 정의 UI**에서는 전혀 보이지 않아 운전원이 구별 불가 +- LLM이 pid_equipment 조회 시 instrument를 한꺼번에 가져와 혼동 + +### 목표 + +`pid_prefix_rules` 테이블에 `tag_dcs BOOLEAN` 추가 → PREFIX 분류 정의 시점부터 DCS 여부 명시. +`pid_equipment` 테이블에도 동일 컬럼 전파 → 추출 결과 전체에 flag 유지. + +--- + +## 1. DCS vs Field 분류 기준 + +### DCS 태그 (`tag_dcs = TRUE`) — DCS/Experion DB 포인트, 물리 기기 없음 + +| Prefix | 설명 | 비고 | +|--------|------|------| +| FIC | Flow Indicator Controller | 제어루프 함수블록 | +| TIC | Temperature Indicator Controller | | +| PIC | Pressure Indicator Controller | | +| LIC | Level Indicator Controller | | +| FY | Flow Relay/Converter/Computing | DCS 연산요소 | +| TY | Temperature Relay/Converter | | +| PY | Pressure Relay/Converter | | +| LY | Level Relay/Converter | | +| FV | Flow Valve (function block) | DCS 출력 함수블록 (주의: 물리 FCV와 구별) | +| TV | Temperature Valve (function block) | | +| PV | Pressure Valve (function block) | | +| LV | Level Valve (function block) | | + +> **주의**: FCV/PCV/LCV/TCV는 물리적 제어밸브 → `tag_dcs = FALSE` (field 유지) + +### 현장 계기 (`tag_dcs = FALSE`) — 물리 기기 + +| Prefix | 설명 | +|--------|------| +| FT, TT, PT, LT | 1차 측정 전송기 (Transmitter) | +| FG, TG, PG, LG | 게이지류 (Gauge) | +| FCV, TCV, PCV, LCV | 제어밸브 (물리 기기) | +| PSV | 안전밸브 | +| XV | 차단밸브 | +| VIP, VIT | 진동 프로브/전송기 | +| DP | 차압계 | +| BV | 볼/버터플라이 밸브 | + +--- + +## 2. 영향 범위 전체 목록 + +### 2.1 데이터베이스 (4곳) + +| 대상 | 변경 내용 | +|------|---------| +| `pid_prefix_rules` 테이블 | `tag_dcs BOOLEAN NOT NULL DEFAULT FALSE` 컬럼 추가 | +| `pid_prefix_rules` 시드 | DCS prefix에 `tag_dcs = TRUE` UPDATE | +| `pid_equipment` 테이블 | `tag_dcs BOOLEAN NOT NULL DEFAULT FALSE` 컬럼 추가 | +| `pid_equipment` 기존 행 | prefix rule로 backfill | + +**마이그레이션 SQL**: +```sql +-- pid_prefix_rules 컬럼 추가 +ALTER TABLE pid_prefix_rules ADD COLUMN IF NOT EXISTS tag_dcs BOOLEAN NOT NULL DEFAULT FALSE; + +-- DCS prefix 마킹 +UPDATE pid_prefix_rules +SET tag_dcs = TRUE +WHERE prefix IN ('FIC','TIC','PIC','LIC','FY','TY','PY','LY','FV','TV','PV','LV'); + +-- pid_equipment 컬럼 추가 +ALTER TABLE pid_equipment ADD COLUMN IF NOT EXISTS tag_dcs BOOLEAN NOT NULL DEFAULT FALSE; + +-- 기존 행 backfill (prefix rule 기반) +UPDATE pid_equipment pe +SET tag_dcs = pr.tag_dcs +FROM pid_prefix_rules pr +WHERE pr.prefix = pe.instrument_type + AND pr.tag_dcs = TRUE; +``` + +--- + +### 2.2 C# 도메인 엔티티 (2파일) + +#### `src/Core/Domain/Entities/PidPrefixRule.cs` +```csharp +// 추가 +[Column("tag_dcs")] +public bool TagDcs { get; set; } = false; +``` + +#### `src/Core/Domain/Entities/PidEquipment.cs` +```csharp +// 추가 (tag_class 아래) +[Column("tag_dcs")] +public bool TagDcs { get; set; } = false; +``` + +--- + +### 2.3 DTOs (1파일, 3개 record) + +#### `src/Core/Application/DTOs/PidPrefixRuleDto.cs` + +```csharp +// 기존 +public record PidPrefixRuleDto(int Id, string Prefix, string Category, string? Description, int SortOrder, DateTime CreatedAt); +public record CreatePidPrefixRuleRequest(string Prefix, string Category, string? Description, int SortOrder = 0); +public record UpdatePidPrefixRuleRequest(string Prefix, string Category, string? Description, int SortOrder = 0); + +// 수정 후 (TagDcs 추가) +public record PidPrefixRuleDto(int Id, string Prefix, string Category, bool TagDcs, string? Description, int SortOrder, DateTime CreatedAt); +public record CreatePidPrefixRuleRequest(string Prefix, string Category, bool TagDcs = false, string? Description = null, int SortOrder = 0); +public record UpdatePidPrefixRuleRequest(string Prefix, string Category, bool TagDcs = false, string? Description = null, int SortOrder = 0); +``` + +--- + +### 2.4 Application Services (1파일) + +#### `src/Core/Application/Services/PidExtractorService.cs` + +**변경 1**: `MatchCategoryAsync()` → prefix rule에서 `tag_dcs`도 반환 +현재는 `string? category`만 반환. `(string? category, bool tagDcs)` 튜플로 변경하거나, +별도 `GetPrefixRuleByTagAsync(tagNo)` 호출로 tag_dcs 획득. + +**변경 2**: `ClassifyTagClass()` 단순화 +기존 로직: `hasExperionLink → system`, ISA 후속문자 분석 → `system/field` +수정 후: `tag_dcs = TRUE` → `TagClass = "system"` (prefix rule이 ground truth) +Experion 연결 여부는 여전히 보완 신호로 유지 가능. + +**변경 3**: 추출 저장 시 `TagDcs` 채우기 +```csharp +// 기존 +item.Category = category; +item.TagClass = tagClass; + +// 수정 +item.Category = category; +item.TagDcs = tagDcs; // prefix rule에서 가져온 값 +item.TagClass = tagDcs ? PidEquipment.TagClassSystem : tagClass; // 파생 또는 별도 로직 +``` + +**변경 4**: CSV/Excel export에 `TagDcs` 열 추가 +- CSV 헤더: `TagNo,...,TagClass,TagDcs` +- Excel 열 추가 (17번 열): "DCS태그" 불리언 → "DCS"/"현장" 표시 + +**변경 5**: Excel import에서 `tag_dcs` 처리 +- Excel "DCS태그" 열 → `"DCS" → true, "현장" → false` + +**변경 6**: `BackfillTagClassAsync()` → `BackfillTagDcsAsync()` 추가 +기존 backfill 로직에서 `tag_dcs` 미지정 행도 함께 backfill. + +**변경 7**: `CreatePrefixRuleAsync` / `UpdatePrefixRuleAsync` +`request.TagDcs` → `rule.TagDcs` 저장. + +--- + +### 2.5 인터페이스 (1파일) + +#### `src/Core/Application/Interfaces/IExperionServices.cs` + +`IPidExtractorService` 인터페이스 시그니처 수정: +- `CreatePrefixRuleAsync(CreatePidPrefixRuleRequest)` — DTO 변경으로 자동 반영 +- `UpdatePrefixRuleAsync(int, UpdatePidPrefixRuleRequest)` — 동일 + +--- + +### 2.6 EF Core DbContext (1파일) + +#### `src/Infrastructure/Database/ExperionDbContext.cs` + +**변경 1**: Boot DDL에 `ALTER TABLE` 추가 +```csharp +await _ctx.Database.ExecuteSqlRawAsync( + "ALTER TABLE pid_prefix_rules ADD COLUMN IF NOT EXISTS tag_dcs BOOLEAN NOT NULL DEFAULT FALSE"); +await _ctx.Database.ExecuteSqlRawAsync( + "ALTER TABLE pid_equipment ADD COLUMN IF NOT EXISTS tag_dcs BOOLEAN NOT NULL DEFAULT FALSE"); +``` + +**변경 2**: 시드 INSERT 수정 +기존 INSERT는 `ON CONFLICT DO NOTHING` → 기존 행에 반영 안 됨. +마이그레이션 UPDATE 별도 실행 필요 (§2.1 마이그레이션 SQL). + +**변경 3**: EF 모델 바인딩 (필요시) +`modelBuilder.Entity()` 블록에 `tag_dcs` 명시 없어도 Column attribute로 자동 매핑. + +--- + +### 2.7 Web Controllers (1파일) + +#### `src/Web/Controllers/PidController.cs` + +**변경 1**: `GetPrefixRules` 응답 +현재 `PidPrefixRule` 엔티티를 직접 직렬화 → `TagDcs` 필드 자동 포함 (Column attribute 추가로 충분). + +**변경 2**: `CreatePrefixRule` / `UpdatePrefixRule` +`request.TagDcs` 가 DTO에 추가되므로 컨트롤러 수정 불필요 (서비스에서 처리). + +**변경 3**: `[JsonPropertyName("tagDcs")]` 확인 +익명객체 대신 DTO 반환 시 camelCase 보장 필요. (기존 패턴 확인 후 적용) + +--- + +### 2.8 Web UI (2파일) + +#### `src/Web/wwwroot/js/pid.js` + +**변경 1**: `CATEGORY_LABELS` / `CATEGORY_ORDER` +```javascript +// 기존 +instrument: { label: 'Instrument', badge: 'ok' }, + +// 수정 — DCS는 별도 배지 +// (카테고리가 'instrument'로 유지되고 tag_dcs로 구별하는 방식) +``` + +**변경 2**: PREFIX 그룹 렌더링 (`pidRenderPrefixGroups`) +각 prefix rule 행에 `tag_dcs` 체크박스/배지 추가: +```javascript +// 테이블 열 추가 +${r.tagDcs ? 'DCS' : '현장'} +// 편집 행에도 tag_dcs 토글 추가 + +``` + +**변경 3**: `pidAddPrefixRule(category)` 요청 body에 `tagDcs` 추가 +**변경 4**: `pidUpdatePrefixRule(id)` 요청 body에 `tagDcs` 추가 +**변경 5**: 장비 목록 테이블에 `tag_dcs` 배지 추가 (선택사항) + +#### `src/Web/wwwroot/panes/pid.html` + +- PREFIX 분류 정의 패널에 열 헤더 "DCS태그" 추가 +- 도움말 텍스트 갱신 + +--- + +### 2.9 MCP Server Python (2파일) + +#### `mcp-server/server.py` + +**변경 1**: `_classify_pid_tag()` 반환에 `tag_dcs` 필드 추가 +```python +# 기존 +return {"kind": "instrument", "prefix": prefix, "type": type_name} + +# 수정 — DCS prefix 목록 상수 추가 +_DCS_PREFIXES = {"FIC","TIC","PIC","LIC","FY","TY","PY","LY","FV","TV","PV","LV"} + +return { + "kind": "instrument", + "prefix": prefix, + "type": type_name, + "tag_dcs": prefix in _DCS_PREFIXES +} +``` + +**변경 2**: `_DB_SCHEMA` 상수에 `pid_equipment.tag_dcs` 컬럼 설명 추가 +```python +_DB_SCHEMA = """ +... +테이블: pid_equipment (P&ID 추출 태그/장비) + tag_no TEXT - 태그번호 + category TEXT - 'instrument' / 'power_equipment' / ... + tag_dcs BOOL - TRUE=DCS 함수블록(FIC/TIC/PIC 등), FALSE=현장 물리 계기(FT/PT/FCV 등) + tag_class TEXT - 'field' / 'system' (tag_dcs 기반 + Experion 연결 보완) + instrument_type TEXT - prefix (FT/FIC/P 등) +... +""" +``` + +**변경 3**: `upsert_pid_connection` 함수 +현재 허용 컬럼 목록: `from_tag, to_tag, from_at, to_at, role, category, tag_class, connection_locked` +→ `tag_dcs` 추가 허용 여부 검토 (운전원이 수동 override 가능하도록) + +#### `mcp-server/worker/sql_prompt.py` + +`DB_SCHEMA` 상수 — 현재 `pid_equipment`가 직접 언급되지 않으나, +향후 NL2SQL에서 "DCS 태그인지" 질문 처리를 위해 다음 추가: +``` +테이블: pid_equipment(tag_no TEXT, category TEXT, tag_dcs BOOL, tag_class TEXT, instrument_type TEXT, from_tag TEXT, to_tag TEXT) +※ tag_dcs=TRUE: DCS 함수블록(FIC/TIC/PIC류), FALSE: 현장 물리 계기(FT/FCV류) +``` + +--- + +### 2.10 프롬프트 / 지식 파일 (1파일) + +#### `prompts/plant_context.md` + +현재 계기/태그 분류 설명에 다음 추가: +```markdown +## pid_equipment.tag_dcs +- tag_dcs = TRUE: DCS 내부 함수블록 (FIC, TIC, PIC, LIC, FY, TY, PY, LY 등) + - 물리 기기 없음, Experion 데이터베이스 포인트로만 존재 +- tag_dcs = FALSE: 현장 물리 계기 (FT, PT, LT, FCV, PSV, XV 등) + - P&ID 도면에 기기 심벌로 표시되는 실물 +- "DCS 태그 몇 개?" → pid_equipment WHERE tag_dcs=TRUE COUNT +- "현장 계기 목록" → pid_equipment WHERE tag_dcs=FALSE AND category='instrument' +``` + +--- + +### 2.11 instrument_inference (검토 필요, 1파일) + +#### `mcp-server/instrument_inference/infer.py` + +현재 `_dcs_internal_roles` 집합으로 내부적으로 DCS/field 구별 중. +`tag_dcs` 컬럼 도입 후 이 로직이 중복될 수 있으나, infer.py는 **추론 단계**이므로 +`pid_prefix_rules.tag_dcs`를 직접 참조할 수 없음 (독립 실행 모듈). +→ **변경 불필요** — `_dcs_internal_roles` 로직은 infer 내부 용도로 유지. + +--- + +## 3. 단계별 구현 순서 + +### Phase 1: DB 스키마 (선행 필수) +1. `ExperionDbContext.cs` Boot DDL에 `ALTER TABLE` 추가 +2. 마이그레이션 SQL 실행 (직접 또는 재기동 시 자동 적용) + +### Phase 2: 도메인/DTO/서비스 (C# 코어) +1. `PidPrefixRule.cs` — `TagDcs` 프로퍼티 추가 +2. `PidEquipment.cs` — `TagDcs` 프로퍼티 추가 +3. `PidPrefixRuleDto.cs` — 3개 record 수정 +4. `PidExtractorService.cs` — 추출/CRUD/export/import/backfill 수정 + +### Phase 3: Web Controller +1. `PidController.cs` — camelCase 직렬화 확인 (필요시 `[JsonPropertyName]`) + +### Phase 4: Web UI +1. `pid.js` — PREFIX 그룹 렌더링 + Add/Update 폼 +2. `panes/pid.html` — 열 헤더 + +### Phase 5: MCP / LLM 경로 +1. `server.py` — `_classify_pid_tag` + `_DB_SCHEMA` + `upsert_pid_connection` +2. `worker/sql_prompt.py` — DB_SCHEMA pid_equipment 항목 +3. `prompts/plant_context.md` — tag_dcs 설명 + +### Phase 6: 검증 +1. `dotnet build` — 경고 0/에러 0 +2. `python3 -m py_compile mcp-server/server.py` — OK +3. 웹 UI: PREFIX 분류 탭에서 DCS/현장 배지 확인 +4. pid_equipment 추출 후 `SELECT tag_dcs, COUNT(*) FROM pid_equipment GROUP BY tag_dcs` 확인 +5. LLM 채팅: "FIC-6113이 DCS 태그야?" 질문 → 정상 답변 확인 + +--- + +## 4. 설계 결정 + +| 항목 | 결정 | 이유 | +|------|------|------| +| 컬럼 타입 | `tag_dcs BOOLEAN` (별도 카테고리 X) | 카테고리 변경 시 하위 의존(뷰·필터) 전파 범위 과도. Boolean이 최소 침습적 | +| `tag_class` 유지 | 유지 (deprecated 아님) | Experion 연결 ground truth 포함, 더 정밀. `tag_dcs`는 prefix 기반 빠른 flag | +| `tag_class` 파생 | `tag_dcs=TRUE → TagClass='system'` | 기존 ISA 분석 로직 보완이 아닌 override | +| FCV/PCV/LCV/TCV | `tag_dcs = FALSE` (현장 유지) | 물리 제어밸브. DCS가 제어하지만 기기 자체는 현장 | +| FV/TV/PV/LV | `tag_dcs = TRUE` | ISA 표준상 "Valve(function block output)" — 물리 기기 아닌 DCS 출력 | +| UI 표시 | category 컬럼 유지, tag_dcs 배지 추가 | 카테고리 탭 구조(instrument/power_equipment…) 그대로 유지 | +| seed UPDATE 시점 | Boot DDL 이후 별도 UPDATE | INSERT ON CONFLICT DO NOTHING은 기존 행 미반영 | +| backfill | 재기동 시 자동 실행 (Boot DDL에 포함) | 수동 실행 의존성 제거 | + +--- + +## 5. 잔여/고려사항 + +- **FICQ, TICQ 등 "Q" suffix prefix**: 현재 시드에 없음. 추출 시 FIC의 변형으로 처리되므로 + `instrument_type = 'FICQ'`로 저장되면 prefix rule 미매칭 → `tag_dcs = FALSE`(default) 오류 가능. + → 시드에 `('FICQ','instrument','Flow IC Totalizer',10, TRUE)` 등 추가 필요 여부 검토. + +- **누락 prefix 처리**: `pid_equipment.instrument_type`이 prefix rule에 없으면 backfill 불가. + → `tag_class = 'system'`인 행으로 보조 매핑 가능. + +- **P&ID 도면 재추출 여부**: 기존 추출 결과는 backfill SQL로 충분. 재추출 불필요.