# pvC UA EnumValueType 메타데이터 저장 최적화 제안서

> 작성일: 2026-05-07
> 상태: 분석 중
> 목적: xv, p-xxxxx 등 디지털 장비 태그의 pv 값 (i=7594) 저장 방식 최적화

---

## 1. 현재 pv 값 저장 방식 분석

### 1.1 pvC UA EnumValueType (i=7594) 데이터 흐름

디지털 장비(xv 밸브, p-xxxxx 펌프 등)의 pv(Output) 값은 pvC UA 표준 데이터 타입인 `EnumValueType (i=7594)`를 사용한다. 이 값은 pvC UA 서버에서 읽을 때 `{정수 | DisplayName | }` 형식의 문자열로 반환된다.

### 1.2 실제 DB 저장 예시

`realtime_table`에 pv 값이 저장되는 실제 데이터:

| tagname | livevalue | node_id |
|---------|-----------|---------|
| xv-3202.pv | `{0 \| L-MID \| }` | ns=1;s=sinamserver:xv-3202.pv |
| xv-3202_hs.pv | `{0 \| CLOSE \| }` | ns=1;s=sinamserver:xv-3202_hs.pv |
| xv-360.pv | `{0 \| MID \| }` | ns=1;s=sinamserver:xv-360.pv |
| p-10602a.pv | `{0 \| L-STpv \| }` | ns=1;s=sinamserver:p-10602a.pv |
| p-2816a.pv | `{0 \| L-STpv \| }` | ns=1;s=sinamserver:p-2816a.pv |
| p-203_hs.pv | `{0 \| STpv \| }` | ns=1;s=sinamserver:p-203_hs.pv |

### 1.3 pv 값의 구조

pv 값 `{0 \| L-STpv \| }`는 다음 3가지 정보를 포함:
- **정수 코드 (0)**: 실제 상태 코드 (0~7 범위)
- **DisplayName (L-STpv)**: 화면에 표시될 상태 이름
- **빈 설명 영역**: 현재 사용되지 않음

### 1.4 각 태그당 존재하는 속성 수

`node_map_master`에서 한 태그(예: p-10214)당 **426개 속성**이 존재한다. 주요 속성:
- 핵심 데이터: pv, pv, sp, md
- 상태 플래그: instate0~instate7
- 상태 설명: state0descriptor~state7descriptor
- 메타데이터: desc, area
- 품질/시간: pvquality, pvlastscannedtime 등
- 설정값: state0ondelay, alarmpriority 등
- 기타: equipprpverties, templateid, guid 등

---

## 2. tag_metadata 테이블 분석

### 2.1 현재 tag_metadata 테이블 스키마

```sql
CREATE TABLE tag_metadata (
    id          SERIAL PRIMARY KEY,
    base_tag    TEXT        NOT NULL,
    attribute   TEXT        NOT NULL,
    value       TEXT,
    node_id     TEXT,
    loaded_at   TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(base_tag, attribute)
)
```

### 2.2 MetadataLoaderService가 로드하는 속성

| 속성 | 데이터 타입 | 설명 |
|------|------------|------|
| desc | String | 태그 설명 |
| area | i=7594 | 소속 플랜트/Asset (EnumValueType) |
| state0descriptor | String | 상태 0 설명 |
| state1descriptor | String | 상태 1 설명 |
| state2descriptor | String | 상태 2 설명 |
| state3descriptor | String | 상태 3 설명 |
| state4descriptor | String | 상태 4 설명 |
| state5descriptor | String | 상태 5 설명 |
| state6descriptor | String | 상태 6 설명 |
| state7descriptor | String | 상태 7 설명 |

### 2.3 현재 tag_metadata 테이블 상태

**테이블이 비어 있음** — 메타데이터 로드가 아직 실행되지 않았거나 실패한 상태.

### 2.4 문제점: pv 값과 state descriptor의 중복

pv 값 `{0 | L-STpv | }` 자체가 **상태 코드 + DisplayName**을 포함하고 있다.
즉, `state0descriptor` 등의 값을 별도로 저장할 필요 없이 pv 값에서 파싱하면 된다.

| 데이터 소스 | 포함 정보 |
|------------|----------|
| `realtime_table`의 pv livevalue | `{0 \| L-STpv \| }` → 코드=0, 이름=L-STpv |
| `tag_metadata`의 state0descriptor | "L-STpv" (pv 값과 동일한 정보) |

**결론: pv 값에 이미 state descriptor 정보가 포함되어 있으므로 중복 저장.**

---

## 3. 메타데이터 컬럼 유지 여부 제안

### 3.1 컬럼 분류

tag_metadata 테이블에서 각 속성을 3가지 카테고리로 분류:

| 카테고리 | 속성 | 필요성 | 이유 |
|----------|------|--------|------|
| **필수 유지** | desc | ⭐ 필수 | 태그 설명은 pv 값에 포함되지 않음 |
| **필수 유지** | area | ⭐ 필수 | 소속 플랜트 정보는 pv 값에 포함되지 않음 |
| **불필요** | state0~7descriptor | ❌ 중복 | pv 값 `{코드 \| 이름 \| }`에 이미 포함 |

### 3.2 제안: desc, area만 저장

**tag_metadata 테이블에는 `desc`와 `area`만 저장하고, `state0~7descriptor`는 저장하지 말 것.**

#### 근거

1. **정보 중복 제거**: pv 값 `{0 | L-STpv | }`에서 상태 이름 "L-STpv"을 파싱 가능
2. **저장소 절감**: 태그당 8개 state descriptor 행 제거 (10개 → 2개 속성)
3. **로드 시간 단축**: pvC UA에서 읽어야 할 속성 10개 → 2개로 감소
4. **데이터 일관성**: state descriptor는 pvC 서버 설정에 따라 변경될 수 있음. pv 값이 실시간 상태이므로 더 신뢰할 수 있음

#### pv 값 파싱 방법

```
pv 값: "{0 | L-STpv | }"
파싱: 코드=0, 상태이름="L-STpv"

pv 값: "{0 | MID | }"
파싱: 코드=0, 상태이름="MID"
```

프론트엔드에서 pv 값 표시 시 `{`와 `|`를 파싱하여 상태 이름만 표시하면 됨.

### 3.3 수정이 필요한 코드

| 파일 | 수정 내용 |
|------|----------|
| `MetadataLoaderService.cs` | `MetaAttributes` 배열에서 state0~7descriptor 제거 |
| `ExperionDbContext.cs` | `v_tag_summary` 뷰에서 state descriptor JOIN 제거 |
| `app.js` | pv 값 표시 시 파싱 로직 추가 |

### 3.4 tag_metadata 테이블의 node_id, loaded_at 컬럼 유지 여부

| 컬럼 | 유지 제안 | 이유 |
|------|----------|------|
| node_id | 유지 | pvC UA 노드 ID는 재로드 시 변경될 수 있으므로 추적 필요 |
| loaded_at | 유지 | 메타데이터 갱신 시점 확인에 유용 |

**결론: node_id와 loaded_at은 유지하는 것이 좋음.**

---

## 4. 요약 및 다음 단계

### 4.1 핵심 발견

1. **pv 값 저장 방식**: `realtime_table`의 `livevalue`에 `{코드 | DisplayName | }` 형식으로 저장됨
2. **정보 중복**: pv 값에 state descriptor 정보가 이미 포함되어 있음
3. **tag_metadata 비어있음**: 메타데이터 로드가 아직 실행되지 않은 상태
4. **속성 과다**: 각 태그당 426개 속성 중 실제로 필요한 것은 desc, area 2개뿐

### 4.2 최종 제안

| 항목 | 결정 |
|------|------|
| tag_metadata 저장 속성 | desc, area 만 |
| state0~7descriptor | 저장하지 않음 (pv 값에서 파싱) |
| node_id 컬럼 | 유지 (추적용) |
| loaded_at 컬럼 | 유지 (갱신 시점 확인) |

### 4.3 기대 효과

- **pvC UA 읽기 요청 80% 감소**: 10개 속성 → 2개 속성
- **tag_metadata 행 80% 감소**: 태그당 10행 → 2행
- **메타데이터 로드 시간 단축**: 네트워크 트래픽과 DB 삽입량 대폭 감소
- **데이터 일관성 향상**: pv 값이 실시간 상태이므로 더 신뢰할 수 있음

### 4.4 다음 단계 
1. `MetadataLoaderService.cs` 수정: state descriptor 제거
2. `ExperionDbContext.cs` 수정: v_tag_summary 뷰 단순화
3. 프론트엔드 pv 값 파싱 로직 추가
4. 메타데이터 로드 테스트 실행
5. P-XXXXX, XV-XXXXX.pv 등의 상태변화를 감지하여 EVENT TABLE에 기록하여 LLM의 플랜트 상태보고에 활용하게 하고, NL2SQL에서도 언제부터 언제까지의 이벤트 정보를 표시 할 수있게 하자(UI 'Event 조회' 별도 페이지 만들어서 )

EVENT TABLE은 컬럼 TimeStamp, Tagname, Value, StateText 를 p-xxxxx, xv-xxxxx등의 pv 값{ 값 | 상태 텍스트 | }에서 가져오고 값의 변경이 일어났을때만   ,  event history 테이블에 기록.

event 테이블을 아날로그값에도 적용할지는 나중에 생각하고 일단 Brain Storming 기록만
  - ficq-6113 : pid controller 류 들의  Manual : pv  기준, Auto : sp 기준으로 값이 변경되면 event 히스토리에 변경값 기록 이전값->변경값 두개다 기록
  - Auto <-> Manual 전환도 기록 : ficq-6113.md 기준 -> 실시간 테이블 스키마 변경 
  - user 전환 (근무교대 시프트 기록) ---> 이건 아직 파악 못했슴 (node_map_master 에 정보 없슴)
