From d8095d0c8db2788fdb54fc4358c679da90840545 Mon Sep 17 00:00:00 2001 From: windpacer Date: Fri, 29 May 2026 09:49:36 +0900 Subject: [PATCH] =?UTF-8?q?P&ID:=20export=20GetEquipmentAsync=20null=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80=20+?= =?UTF-8?q?=20=EC=98=A4=EB=9E=98=EB=90=9C=20plan=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md | 392 ------------------ src/Web/Controllers/ExperionControllers.cs | 4 +- 2 files changed, 2 insertions(+), 394 deletions(-) delete mode 100644 plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md diff --git a/plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md b/plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md deleted file mode 100644 index b59f73a..0000000 --- a/plans/P&ID-추출-PREFIX-DB-수정플랜-byBigPickle.md +++ /dev/null @@ -1,392 +0,0 @@ -# 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로 충분. 재추출 불필요. diff --git a/src/Web/Controllers/ExperionControllers.cs b/src/Web/Controllers/ExperionControllers.cs index 5ea02a3..7a4b763 100644 --- a/src/Web/Controllers/ExperionControllers.cs +++ b/src/Web/Controllers/ExperionControllers.cs @@ -1128,7 +1128,7 @@ public class ExperionPidController : ControllerBase [HttpGet("export/csv")] public async Task ExportCsv([FromQuery] string? tagNo) { - var (_, items) = await _extractor.GetEquipmentAsync(tagNo, 1, int.MaxValue); + var (_, items) = await _extractor.GetEquipmentAsync(tagNo, null, 1, int.MaxValue); var csv = await _extractor.ExportToCsvAsync(items); var bytes = System.Text.Encoding.UTF8.GetBytes(csv); return File(bytes, "text/csv", $"pid-equipment-{DateTime.Now:yyyyMMdd}.csv"); @@ -1137,7 +1137,7 @@ public class ExperionPidController : ControllerBase [HttpGet("export/excel")] public async Task ExportExcel([FromQuery] string? tagNo) { - var (_, items) = await _extractor.GetEquipmentAsync(tagNo, 1, int.MaxValue); + var (_, items) = await _extractor.GetEquipmentAsync(tagNo, null, 1, int.MaxValue); var excelBytes = await _extractor.ExportToExcelAsync(items); return File(excelBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"pid-equipment-{DateTime.Now:yyyyMMdd}.xlsx"); }