Files
ExperionCrawler/plans/enum-metadata-optimize-coding-plan.md
2026-05-09 04:28:10 +09:00

26 KiB

Enum Metadata 최적화 - 코딩 계획

작성일: 2026-05-08 상태: 진행 중 기반 문서: plans/enum-metadata-optimization.md 목적: state0~7descriptor 제거, desc/area만 유지, pv 값 파싱 로직 추가


작업 Todo 리스트

각 단계는 독립적으로 완료 여부를 추적할 수 있다. 체크박스를 사용하여 진행 상황을 기록한다.

  • STEP 1 — 백업: 수정 대상 파일들을 .rooBackup/에 백업
  • STEP 2 — MetadataLoaderService.cs: MetaAttributes 배열에서 state0~7descriptor 제거
  • STEP 2.5 — MetadataLoaderService.cs: 클래스 주석 업데이트
  • STEP 3 — MetadataLoaderService.cs + ExperionDbContext.cs: 빌드 검증
  • STEP 4 — ExperionDbContext.cs: v_tag_summary 뷰에서 state descriptor JOIN 제거
  • STEP 4.5 — tag_metadata 고아 데이터 삭제 (선택적)
  • STEP 5 — ExperionDbContext.cs 변경 후 빌드 검증
  • STEP 6 — app.js: pv 값 파싱 헬퍼 함수 parseEnumPv() 추가
  • STEP 6.5 — NL2SQL DB_SCHEMA 동기화 (server.py + nl2sql_worker.py)
  • STEP 7 — app.js: pv 값 표시 관련 코드 모두 parseEnumPv() 적용
  • STEP 8 — End-to-End 검증: 전체 빌드 + UI 테스트
  • STEP 9 — git 커밋 및 정리

변경 대상 파일 목록

# 파일 변경 내용 영향 범위
1 src/Infrastructure/OpcUa/MetadataLoaderService.cs MetaAttributes 배열 수정 + 주석 업데이트 메타데이터 로딩
2 src/Infrastructure/Database/ExperionDbContext.cs v_tag_summary 뷰 단순화 + 고아 데이터 삭제 DB 뷰
3 src/Web/wwwroot/js/app.js pv 파싱 헬퍼 + 표시 로직 변경 프론트엔드 UI
4 mcp-server/server.py DB_SCHEMA에서 state0~2_descriptor 제거 NL2SQL
5 mcp-server/worker/nl2sql_worker.py DB_SCHEMA에서 state0~2_descriptor 제거 NL2SQL

각 단계 상세 계획


STEP 1 — 백업: 수정 대상 파일들을 .rooBackup/에 백업

목적: 수정 전 원본 보존. 실패 시 복원 가능.

실행 명령:

TIMESTAMP=$(date +%Y%m%d%H%M)
mkdir -p .rooBackup/enum-opt-$TIMESTAMP/src/Infrastructure/OpcUa
mkdir -p .rooBackup/enum-opt-$TIMESTAMP/src/Infrastructure/Database
mkdir -p .rooBackup/enum-opt-$TIMESTAMP/src/Web/wwwroot/js
mkdir -p .rooBackup/enum-opt-$TIMESTAMP/mcp-server/worker

cp src/Infrastructure/OpcUa/MetadataLoaderService.cs .rooBackup/enum-opt-$TIMESTAMP/src/Infrastructure/OpcUa/
cp src/Infrastructure/Database/ExperionDbContext.cs .rooBackup/enum-opt-$TIMESTAMP/src/Infrastructure/Database/
cp src/Web/wwwroot/js/app.js .rooBackup/enum-opt-$TIMESTAMP/src/Web/wwwroot/js/
cp mcp-server/server.py .rooBackup/enum-opt-$TIMESTAMP/mcp-server/
cp mcp-server/worker/nl2sql_worker.py .rooBackup/enum-opt-$TIMESTAMP/mcp-server/worker/

검증 기준:

  • .rooBackup/enum-opt-YYYYMMDDHHMM/ 폴더가 생성되고 5개 파일이 복사됨
  • 원본 파일과 백업 파일의 체크섬이 일치함 (md5sum 비교)

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 3.3: 수정이 필요한 코드 — MetadataLoaderService.cs, ExperionDbContext.cs, app.js

STEP 2 — MetadataLoaderService.cs: MetaAttributes 배열에서 state0~7descriptor 제거

파일: MetadataLoaderService.cs 변경 위치: 20~26줄 (MetaAttributes 배열 정의)

변경 전 코드:

// 로드할 메타데이터 속성 목록
private static readonly string[] MetaAttributes =
{
    "desc", "area",
    "state0descriptor", "state1descriptor", "state2descriptor",
    "state3descriptor", "state4descriptor", "state5descriptor",
    "state6descriptor", "state7descriptor"
};

변경 후 코드:

// 로드할 메타데이터 속성 목록 (state0~7descriptor 제거 — pv 값에서 파싱)
private static readonly string[] MetaAttributes =
{
    "desc", "area"
};

diff:

-// 로드할 메타데이터 속성 목록
+// 로드할 메타데이터 속성 목록 (state0~7descriptor 제거 — pv 값에서 파싱)
 private static readonly string[] MetaAttributes =
 {
-    "desc", "area",
-    "state0descriptor", "state1descriptor", "state2descriptor",
-    "state3descriptor", "state4descriptor", "state5descriptor",
-    "state6descriptor", "state7descriptor"
+    "desc", "area"
 };

변경 이유:

  • pv 값 {코드 | DisplayName | }에 이미 state descriptor 정보가 포함되어 있으므로 중복 저장 제거
  • OPC UA 읽기 요청 80% 감소 (10개 속성 → 2개 속성)
  • tag_metadata 행 80% 감소 (태그당 10행 → 2행)

영향 분석:

  • LoadMetadataAsync() 메서드: MetaAttributes.Contains(n.Name)으로 node_map_master에서 필터링하므로 state descriptor 노드는 더 이상 조회되지 않음
  • ReadTagsAsync(): 8개 menos의 nodeId로 배치 읽기 → 네트워크 트래픽 감소
  • UPSERT 쿼리: 8개 menos의 행 삽입 → DB 부하 감소
  • 클래스 주석 (11줄): state0~7descriptor 언급도 함께 제거 필요

검증 기준:

  • MetaAttributes 배열이 ["desc", "area"] 두 개만 포함
  • 컴파일 오류 없음
  • LoadMetadataAsync() 메서드의 로직 변경 없이 배열 변경만으로 동작

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 3.2: desc, area만 저장
  • 섹션 3.3: MetadataLoaderService.cs 수정 — MetaAttributes 배열에서 state0~7descriptor 제거
  • 섹션 4.2: tag_metadata 저장 속성 — desc, area 만

STEP 2.5 — MetadataLoaderService.cs: 클래스 주석 업데이트

파일: MetadataLoaderService.cs 변경 위치: 11줄 (클래스 XML 주석)

변경 전 코드 (11줄):

/// 메타데이터(desc, area, state0~7descriptor)를 OPC UA에서 읽어서 tag_metadata 테이블에 저장/갱신

변경 후 코드:

/// 메타데이터(desc, area)를 OPC UA에서 읽어서 tag_metadata 테이블에 저장/갱신

diff:

-/// 메타데이터(desc, area, state0~7descriptor)를 OPC UA에서 읽어서 tag_metadata 테이블에 저장/갱신
+/// 메타데이터(desc, area)를 OPC UA에서 읽어서 tag_metadata 테이블에 저장/갱신

변경 이유:

  • STEP 2에서 state0~7descriptor를 제거했는데 주석에는 여전히 남아 있음
  • 주석과 코드 불일치로 인한 혼란 방지

검증 기준:

  • 주석이 desc, area만 언급함
  • 컴파일 오류 없음 (주석 변경이므로 영향 없음)

STEP 3 — MetadataLoaderService.cs 변경 후 빌드 검증

목적: STEP 2 변경이 컴파일 오류 없이 통과하는지 확인

실행 명령:

dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q

검증 기준:

  • 빌드 성공 (exit code 0)
  • 경고 없음 (또는 기존 경고만)
  • MetaAttributes 배열 참조하는 다른 코드가 없는지 확인

실패 시 대응:

  • 컴파일 오류가 발생하면 오류 메시지를 읽고 원인 분석
  • MetaAttributes 길이에 의존하는 코드가 있으면 해당 코드도 수정

STEP 4 — ExperionDbContext.cs: v_tag_summary 뷰에서 state descriptor JOIN 제거

파일: ExperionDbContext.cs 변경 위치: 302~330줄 (v_tag_summary 뷰 생성 SQL)

변경 전 코드 (315~329줄):

                  desc_md.value AS description,
                  area_md.value AS area,
                  s0d_md.value AS state0_descriptor,
                  s1d_md.value AS state1_descriptor,
                  s2d_md.value AS state2_descriptor
                FROM (SELECT DISTINCT split_part(tagname, '.', 1) AS base_tag FROM realtime_table) rt_base
                LEFT JOIN realtime_table pv_rt ON pv_rt.tagname = rt_base.base_tag || '.pv'
                LEFT JOIN realtime_table sp_rt ON sp_rt.tagname = rt_base.base_tag || '.sp'
                LEFT JOIN realtime_table op_rt ON op_rt.tagname = rt_base.base_tag || '.op'
                LEFT JOIN realtime_table instate0_rt ON instate0_rt.tagname = rt_base.base_tag || '.instate0'
                LEFT JOIN realtime_table instate1_rt ON instate1_rt.tagname = rt_base.base_tag || '.instate1'
                LEFT JOIN realtime_table instate2_rt ON instate2_rt.tagname = rt_base.base_tag || '.instate2'
                LEFT JOIN tag_metadata desc_md ON desc_md.base_tag = rt_base.base_tag AND desc_md.attribute = 'desc'
                LEFT JOIN tag_metadata area_md ON area_md.base_tag = rt_base.base_tag AND area_md.attribute = 'area'
                LEFT JOIN tag_metadata s0d_md ON s0d_md.base_tag = rt_base.base_tag AND s0d_md.attribute = 'state0descriptor'
                LEFT JOIN tag_metadata s1d_md ON s1d_md.base_tag = rt_base.base_tag AND s1d_md.attribute = 'state1descriptor'
                LEFT JOIN tag_metadata s2d_md ON s2d_md.base_tag = rt_base.base_tag AND s2d_md.attribute = 'state2descriptor'

변경 후 코드:

                  desc_md.value AS description,
                  area_md.value AS area
                FROM (SELECT DISTINCT split_part(tagname, '.', 1) AS base_tag FROM realtime_table) rt_base
                LEFT JOIN realtime_table pv_rt ON pv_rt.tagname = rt_base.base_tag || '.pv'
                LEFT JOIN realtime_table sp_rt ON sp_rt.tagname = rt_base.base_tag || '.sp'
                LEFT JOIN realtime_table op_rt ON op_rt.tagname = rt_base.base_tag || '.op'
                LEFT JOIN realtime_table instate0_rt ON instate0_rt.tagname = rt_base.base_tag || '.instate0'
                LEFT JOIN realtime_table instate1_rt ON instate1_rt.tagname = rt_base.base_tag || '.instate1'
                LEFT JOIN realtime_table instate2_rt ON instate2_rt.tagname = rt_base.base_tag || '.instate2'
                LEFT JOIN tag_metadata desc_md ON desc_md.base_tag = rt_base.base_tag AND desc_md.attribute = 'desc'
                LEFT JOIN tag_metadata area_md ON area_md.base_tag = rt_base.base_tag AND area_md.attribute = 'area'

diff:

                   desc_md.value AS description,
                   area_md.value AS area,
-                  s0d_md.value AS state0_descriptor,
-                  s1d_md.value AS state1_descriptor,
-                  s2d_md.value AS state2_descriptor
+                  area_md.value AS area
                 FROM (SELECT DISTINCT split_part(tagname, '.', 1) AS base_tag FROM realtime_table) rt_base
                 LEFT JOIN realtime_table pv_rt ON pv_rt.tagname = rt_base.base_tag || '.pv'
                 LEFT JOIN realtime_table sp_rt ON sp_rt.tagname = rt_base.base_tag || '.sp'
                 LEFT JOIN realtime_table op_rt ON op_rt.tagname = rt_base.base_tag || '.op'
                 LEFT JOIN realtime_table instate0_rt ON instate0_rt.tagname = rt_base.base_tag || '.instate0'
                 LEFT JOIN realtime_table instate1_rt ON instate1_rt.tagname = rt_base.base_tag || '.instate1'
                 LEFT JOIN realtime_table instate2_rt ON instate2_rt.tagname = rt_base.base_tag || '.instate2'
                 LEFT JOIN tag_metadata desc_md ON desc_md.base_tag = rt_base.base_tag AND desc_md.attribute = 'desc'
                 LEFT JOIN tag_metadata area_md ON area_md.base_tag = rt_base.base_tag AND area_md.attribute = 'area'
-                LEFT JOIN tag_metadata s0d_md ON s0d_md.base_tag = rt_base.base_tag AND s0d_md.attribute = 'state0descriptor'
-                LEFT JOIN tag_metadata s1d_md ON s1d_md.base_tag = rt_base.base_tag AND s1d_md.attribute = 'state1descriptor'
-                LEFT JOIN tag_metadata s2d_md ON s2d_md.base_tag = rt_base.base_tag AND s2d_md.attribute = 'state2descriptor'
                 """);

변경 이유:

  • state0~2descriptor가 더 이상 tag_metadata에 저장되지 않으므로 JOIN 제거
  • 뷰 조회 성능 향상 (3개 LEFT JOIN 제거)

영향 분석:

  • v_tag_summary 뷰를 조회하는 코드가 state0_descriptor, state1_descriptor, state2_descriptor 컬럼을 참조하면 NULL 반환
  • 이 뷰를 사용하는 곳이 있는지 검색 필요 (현재는 DB 초기화 시에만 사용)

검증 기준:

  • v_tag_summary 뷰 SQL에서 state descriptor 관련 JOIN 3개 제거됨
  • area_md.value AS area 뒤 쉼표 제거 (마지막 SELECT 컬럼)
  • SQL 문법 오류 없음

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 3.3: ExperionDbContext.cs 수정 — v_tag_summary 뷰에서 state descriptor JOIN 제거

STEP 4.5 — tag_metadata 고아 데이터 삭제 (선택적)

파일: ExperionDbContext.cs 변경 위치: InitializeAsync() 메서드 내 v_tag_summary 뷰 생성 직후 (330줄 이후)

추가할 코드:

// state descriptor 고아 데이터 정리 (state0~7descriptor는 더 이상 로딩하지 않음)
await _ctx.Database.ExecuteSqlRawAsync("""
    DELETE FROM tag_metadata WHERE attribute IN (
        'state0descriptor', 'state1descriptor', 'state2descriptor',
        'state3descriptor', 'state4descriptor', 'state5descriptor',
        'state6descriptor', 'state7descriptor'
    )
    """);

삽입 위치 상세:

  • 330줄 ("""); — v_tag_summary 뷰 생성 종료) 바로 다음에 삽입
  • CREATE EXTENSION IF NOT EXISTS timescaledb 이전

변경 이유:

  • MetaAttributes에서 state descriptor가 제거되면 더 이상 갱신되지 않으나, 기존 데이터는 영구히 남음
  • 테이블 크기와 불필요한 JOIN 결과 방지

검증 기준:

  • 실행 시 기존 state descriptor 행이 삭제됨
  • SELECT COUNT(*) FROM tag_metadata WHERE attribute LIKE 'state%descriptor' → 0 반환
  • desc/area 행은 영향 없음

참고: 기존 데이터를 보존해야 한다면 이 스텝을 스킵 가능. 하지만 v_tag_summary 뷰에서 해당 컬럼이 제거되었으므로 조회 자체가 불가능해짐.


STEP 5 — ExperionDbContext.cs 변경 후 빌드 검증

목적: STEP 4 변경이 컴파일 오류 없이 통과하는지 확인

실행 명령:

dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q

검증 기준:

  • 빌드 성공 (exit code 0)
  • SQL 문자열 문법 오류 없음 (raw string literal 안에서 SQL 구문 확인)
  • 쉼표 누락/과잉 없음

실패 시 대응:

  • SQL 구문 오류가 발생하면 SELECT 컬럼 목록의 쉼표 확인
  • area_md.value AS area가 마지막 컬럼이므로 쉼표 제거했는지 확인

STEP 6 — app.js: pv 값 파싱 헬퍼 함수 parseEnumPv() 추가

파일: app.js 삽입 위치: fmtVal() 함수 바로 아래 (2132줄 이후)

추가할 코드:

/**
 * OPC UA EnumValueType pv 값 파싱
 * "{0 | L-STpv | }" → "L-STpv"
 * "{0 | MID | }" → "MID"
 * 일반 값은 그대로 반환
 */
function parseEnumPv(v) {
  if (v == null) return v;
  const s = String(v);
  // "{코드 | DisplayName | }" 패턴 매칭
  const m = s.match(/^\{\s*\d+\s*\|\s*([^|]+?)\s*\|\s*\}$/);
  return m ? m[1].trim() : s;
}

삽입 위치 상세:

  • 2132줄 (}fmtVal 함수 종료) 바로 다음에 삽입
  • 기존 fmtVal 함수는 변경하지 않음

기능 설명:

  • 입력: pv 값 문자열 (예: "{0 | L-STpv | }")
  • 출력: DisplayName 부분만 (예: "L-STpv")
  • EnumValueType 형식이 아닌 일반 값은 그대로 반환

정규식 설명:

  • ^\{{로 시작
  • \s*\d+\s* — 정수 코드 (공백 허용)
  • \|\s*| 구분자
  • ([^|]+?) — DisplayName (첫 번째 캡처 그룹)
  • \s*\|\s*\}$| }로 끝남

검증 기준:

  • parseEnumPv("{0 | L-STpv | }")"L-STpv" 반환
  • parseEnumPv("{0 | MID | }")"MID" 반환
  • parseEnumPv("123.45")"123.45" 반환 (변경 없음)
  • parseEnumPv(null)null 반환
  • 브라우저 콘솔에서 수동 테스트 가능

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 3.2: pv 값 파싱 방법 — {코드 | 이름 | }에서 DisplayName 추출
  • 섹션 3.3: app.js — pv 값 표시 시 파싱 로직 추가

STEP 6.5 — NL2SQL DB_SCHEMA 동기화 (server.py + nl2sql_worker.py)

파일: mcp-server/server.py / mcp-server/worker/nl2sql_worker.py 변경 위치: 두 파일의 DB_SCHEMA 문자열 내 tag_metadata / v_tag_summary 설명 부분

변경 전 코드 (server.py:447 + 462-464 + 470 + 474-475):

  attribute   TEXT         - 속성명 ('desc', 'area', 'state0descriptor', ...)
  ...
  state0_descriptor TEXT   - 비트 0 의미 (예: "Run/Stop")
  state1_descriptor TEXT   - 비트 1 의미 (예: "Remote/Local")
  state2_descriptor TEXT   - 비트 2 의미 (예: "Trip/Normal")
  ...
  - 메타데이터: desc (String), area (Enum), state0descriptor~7 (String)
  ...
  - state0descriptor~7은 해당 비트의 의미 설명
  - instate0=true이고 state0descriptor="Run/Stop"이면 → "Run" 상태

변경 후 코드:

  attribute   TEXT         - 속성명 ('desc', 'area')
  ...
  description       TEXT   - 장비 설명 (tag_metadata.desc)
  area              TEXT   - 소속 플랜트 (tag_metadata.area)
  ...
  - 메타데이터: desc (String), area (Enum)
  ...
  - pv 값이 EnumValueType 형식인 경우 `{코드 | DisplayName | }`에서 DisplayName으로 상태 확인 가능
  - v_tag_summary 뷰를 사용하면 실시간값+메타데이터 한 번에 조회 가능

diff (server.py 기준, nl2sql_worker.py는 동일):

-  attribute   TEXT         - 속성명 ('desc', 'area', 'state0descriptor', ...)
+  attribute   TEXT         - 속성명 ('desc', 'area')
   value       TEXT         - 메타데이터 값
   node_id     TEXT         - OPC UA 노드 ID
   loaded_at   TIMESTAMPTZ  - 마지막 로드 시각

   뷰: v_tag_summary  (실시간값 + 메타데이터 통합 뷰)
   ...
   description       TEXT   - 장비 설명 (tag_metadata.desc)
   area              TEXT   - 소속 플랜트 (tag_metadata.area)
-  state0_descriptor TEXT   - 비트 0 의미 (예: "Run/Stop")
-  state1_descriptor TEXT   - 비트 1 의미 (예: "Remote/Local")
-  state2_descriptor TEXT   - 비트 2 의미 (예: "Trip/Normal")

   새로운 태그 타입:
   - 아날로그: ficq-6101.pv/sp/op (Double)
   - 디지털 XV: xv-6124.pv/op (Int32), xv-6124.instate0~7 (Boolean)
   - Pump: p-6102.pv/op (Int32), p-6102.instate0~7 (Boolean)
-  - 메타데이터: desc (String), area (Enum), state0descriptor~7 (String)
+  - 메타데이터: desc (String), area (Enum)

   BCD 상태 조회 팁:
   - instate0~7은 Boolean (true/false)
-  - state0descriptor~7은 해당 비트의 의미 설명
-  - instate0=true이고 state0descriptor="Run/Stop"이면 → "Run" 상태
+  - pv 값이 EnumValueType 형식인 경우 `{코드 | DisplayName | }`에서 DisplayName으로 상태 확인 가능
   - v_tag_summary 뷰를 사용하면 실시간값+메타데이터 한 번에 조회 가능

변경 이유:

  • v_tag_summary 뷰에서 state0~2_descriptor 컬럼이 제거되면 LLM이 해당 컬럼을 SELECT하는 SQL을 생성하면 실패함
  • DB_SCHEMA는 LLM의 시스템 프롬프트로 사용되므로 실제 DB 스키마와 반드시 일치해야 함

검증 기준:

  • server.py DB_SCHEMA에서 state0_descriptor / state1_descriptor / state2_descriptor 언급 없음
  • nl2sql_worker.py DB_SCHEMA에서 동일하게 제거됨
  • attribute 설명이 'desc', 'area'만 포함
  • MCP 서버 재시작 후 NL2SQL 쿼리가 정상 동작 (state descriptor 없이)

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 3.1: pv 값이 EnumValueType 형식인 경우 DisplayName 파싱으로 상태 확인

STEP 7 — app.js: pv 값 표시 관련 코드 모두 parseEnumPv() 적용

파일: app.js 변경 위치: pbRender() 함수 내 liveValue 표시 부분 (633줄)

변경 전 코드 (633줄):

<td class="val">${p?.liveValue != null ? esc(String(fmtVal(p.liveValue))) : '<span style="color:var(--t3)">—</span>'}</td>

변경 후 코드:

<td class="val">${p?.liveValue != null ? esc(String(fmtVal(parseEnumPv(p.liveValue)))) : '<span style="color:var(--t3)">—</span>'}</td>

diff:

-<td class="val">${p?.liveValue != null ? esc(String(fmtVal(p.liveValue))) : '<span style="color:var(--t3)">—</span>'}</td>
+<td class="val">${p?.liveValue != null ? esc(String(fmtVal(parseEnumPv(p.liveValue)))) : '<span style="color:var(--t3)">—</span>'}</td>

변경 이유:

  • liveValue가 EnumValueType 형식({0 | L-STpv | })이면 DisplayName만 표시
  • 일반 숫자 값은 parseEnumPv()가 그대로 반환하므로 영향 없음
  • fmtVal()은 숫자 포맷팅만 담당, parseEnumPv()는 EnumValueType 파싱만 담당 (단일 책임)

다른 적용 위치 확인:

  • fmtVal()을 사용하는 모든 곳을 검색하여 pv 값 표시에 사용되는 곳에 parseEnumPv() 적용
  • t2sRenderTable() (1606줄): fmtVal(val) — 시계열 데이터이므로 변경 불필요 (아날로그 값 중심)
  • histQuery() 결과 렌더링 (956줄): fmtVal(raw) — 이력 데이터이므로 변경 불필요

검증 기준:

  • 포인트빌더 테이블에서 xv-3202.pv 값이 "{0 | L-MID | }" 대신 "L-MID"로 표시됨
  • 일반 숫자 값(예: ficq-6113.pv = "123.45")은 여전히 "123.45"로 표시됨
  • 브라우저 콘솔에서 JS 오류 없음

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 3.2: 프론트엔드에서 pv 값 표시 시 {|를 파싱하여 상태 이름만 표시

STEP 8 — End-to-End 검증: 전체 빌드 + UI 테스트

목적: 모든 변경 사항이 함께 동작하는지 최종 확인

실행 명령:

# 1. 전체 솔루션 빌드
dotnet build ExperionCrawler.sln -v q

# 2. 애플리케이션 시작
dotnet run --project src/Web/ExperionCrawler.csproj

검증 체크리스트:

빌드 검증

  • dotnet build 성공 (exit code 0)
  • 컴파일 오류 0개
  • 경고 수 증가 없음

백엔드 검증

  • 애플리케이션 시작 시 DB 초기화 성공
  • v_tag_summary 뷰 생성 성공 (state descriptor JOIN 없음)
  • tag_metadata 고아 데이터 삭제 성공 (STEP 4.5)
  • 메타데이터 로드 시 desc, area만 조회됨 (로그 확인)
  • tag_metadata 테이블에 state0~7descriptor 행 없음

NL2SQL 검증

  • MCP 서버 재시작 성공
  • "xv-6124 상태 알려줘" 쿼리가 state descriptor 없이 정상 동작
  • 생성된 SQL에서 state0_descriptor 컬럼 없음

프론트엔드 검증

  • 브라우저 콘솔 JS 오류 없음
  • 포인트빌더 테이블에서 digital 태그 pv 값이 DisplayName만 표시됨
    • 예: xv-3202.pv"L-MID" (기존: "{0 | L-MID | }")
  • 아날로그 태그 pv 값은 정상 표시됨
    • 예: ficq-6113.pv"123.45"
  • 메타데이터 조회 시 desc, area만 반환됨

성능 검증

  • 메타데이터 로드 시간 감소 확인 (기존 대비)
  • tag_metadata 테이블 행 수 감소 확인 (기존 10행/태그 → 2행/태그)

enum-metadata-optimization.md 규칙 매핑:

  • 섹션 4.3: 기대 효과 — OPC UA 읽기 요청 80% 감소, tag_metadata 행 80% 감소
  • 섹션 4.4: 다음 단계 — 메타데이터 로드 테스트 실행

STEP 9 — git 커밋 및 정리

목적: 변경 사항을 커밋하고 작업 기록 남기기

커밋 전략: 각 파일 변경을 별도 커밋으로 관리

# 1. MetadataLoaderService.cs 커밋
git add src/Infrastructure/OpcUa/MetadataLoaderService.cs
git commit -m "feat: MetaAttributes에서 state0~7descriptor 제거, 주석 동시 업데이트"

# 2. ExperionDbContext.cs 커밋
git add src/Infrastructure/Database/ExperionDbContext.cs
git commit -m "feat: v_tag_summary 뷰에서 state descriptor JOIN 제거, 고아 데이터 DELETE 추가"

# 3. app.js 커밋
git add src/Web/wwwroot/js/app.js
git commit -m "feat: pv 값 파싱 헬퍼 parseEnumPv() 추가, 포인트빌더 테이블 적용"

# 4. NL2SQL DB_SCHEMA 커밋
git add mcp-server/server.py mcp-server/worker/nl2sql_worker.py
git commit -m "feat: NL2SQL DB_SCHEMA에서 state0~2_descriptor 제거 (v_tag_summary 변경 반영)"

# 5. 계획 문서 커밋
git add plans/enum-metadata-optimize-coding-plan.md
git commit -m "docs: enum metadata 최적화 코딩 계획 작성"

검증 기준:

  • 각 커밋 메시지가 변경 내용을 명확히 설명함
  • git log에서 커밋 순서가 논리적임
  • .rooBackup/ 폴더에 gitignore 적용되어 있음 (백업 파일 커밋 제외)

부록: rollback 절차

작업 중 문제가 발생하면 백업 파일로 복원:

TIMESTAMP=$(ls -d .rooBackup/enum-opt-* | tail -1 | xargs basename)
cp .rooBackup/$TIMESTAMP/src/Infrastructure/OpcUa/MetadataLoaderService.cs src/Infrastructure/OpcUa/
cp .rooBackup/$TIMESTAMP/src/Infrastructure/Database/ExperionDbContext.cs src/Infrastructure/Database/
cp .rooBackup/$TIMESTAMP/src/Web/wwwroot/js/app.js src/Web/wwwroot/js/
cp .rooBackup/$TIMESTAMP/mcp-server/server.py mcp-server/
cp .rooBackup/$TIMESTAMP/mcp-server/worker/nl2sql_worker.py mcp-server/worker/

검증 요약

각 STEP의 검증 기준을 한눈에 확인:

STEP 파일 핵심 검증 항목
1 백업 파일 5개 생성됨
2 MetadataLoaderService.cs MetaAttributes = ["desc", "area"]
2.5 MetadataLoaderService.cs 클래스 주석 업데이트
3 빌드 성공
4 ExperionDbContext.cs state descriptor JOIN 3개 제거됨
4.5 ExperionDbContext.cs 고아 데이터 DELETE 쿼리 추가
5 빌드 성공
6 app.js parseEnumPv() 함수 추가됨
6.5 server.py + nl2sql_worker.py DB_SCHEMA에서 state descriptor 제거
7 app.js pbRender()에서 parseEnumPv() 적용됨
8 전체 End-to-End + NL2SQL 테스트 통과
9 git 커밋 완료