# 작업지시서 — 태그명 대소문자 통일 & DB 정합성 (전 탭)

> 대상: 후속 작업 LLM. 이 문서만으로 독립 실행 가능하도록 배경·파일·라인·검증을 모두 포함함.
> 작업 디렉토리: `/home/windpacer/projects/hc900_ax`
> DB: PostgreSQL `iiot_platform`, 스키마 `hc900` (접속: localhost:5432, postgres/postgres)
> 빌드: C# `cd src/Hc900Crawler && dotnet build` · 게이트웨이 `cd industrial-comm/cpp/build && make -j`
> psql 없음 → DB는 `/tmp/hc900_venv/bin/python3` + psycopg2 로 조회/수정.

## 0. 핵심 원칙 (반드시 준수)

**태그명은 단 하나의 표기 = Experion ItemName 원형 대소문자, 예 `FICQ-6101.PV`.**
게이트웨이 register-map `tag`, `hc900_map_master.tagname` = `hc900_tag`, `realtime_table.tagname`,
`history_table.tagname`, FF 설정, 모든 곳에서 **동일 표기. 절대 `ToLower()`/`LOWER()` 변환 금지.**

- 컨트롤러는 **컨트롤러당 게이트웨이 프로세스 1개**, 각자 own gRPC 포트. 태그는 컨트롤러 내에서만 유일.
- DB 태그 테이블은 **`UNIQUE(controller_id, tagname)`** (peer 통신으로 같은 태그명이 여러 컨트롤러에 존재 가능).
- 이미 완료된 것(참고): `Hc900RealtimeService.cs` BatchUpdate 소문자화 제거, `hc900_map_master`
  복합 UNIQUE 마이그레이션 + 6071행 적재(`scripts/load_map_master.py`), `register-map-c{1..4}.json`,
  `config/gateway-config.json`.

## 1. 현재 DB 상태 (조사 결과)

| 테이블 | 행수 | 비고 |
|---|---|---|
| hc900_map_master | 6071 | ✅ 적재됨 (proper-case) |
| realtime_table | 가변 | 구 크롤러가 **소문자**로 재기록 중 → 재빌드+재시작 필요 |
| history_table | 55272 | 구 소문자 데이터 혼재 |
| event_history_table | 0 | 크롤러 가동 중 누적됨 |
| **tag_metadata** | **0** | ⚠ 상태레이블/설명 없음 → TASK 3 |
| pid_equipment | 0 | P&ID 추출 시 채워짐(별도) |
| ff_configs / ff_columns | **없음** | ⚠ 테이블 미생성 → TASK 5 |
| node_map_master | 530080 | 구 ExperionCrawler용(Text-to-SQL). 손대지 말 것 |

## 2. 작업 목록

### TASK 1 — FF 서브시스템 소문자화 전면 제거 (최우선)
**문제**: FF가 태그를 `ToLowerInvariant()`로 저장/매칭. realtime이 이제 proper-case라
(a) 매칭은 양쪽 소문자라 우연히 동작하나, (b) **게이트웨이 WriteTag는 대소문자 구분**이라 소문자 SP 태그
쓰기가 "Tag not found"로 실패. FF 설정도 소문자로 저장됨.
**수정 대상** (모든 `.ToLowerInvariant()` / `.ToLower()` 를 제거하고 원형 유지):
- `src/Infrastructure/Control/FeedforwardSupervisor.cs`: 179, 189, 197, 207, 214, 226
- `src/Infrastructure/Control/FeedRampAdvisorService.cs`: 28, 37, 41 (특히 `.EndsWith(".pv")` → `.PV` 대응 주의)
- `src/Infrastructure/Control/SimOverrideStore.cs`: 20, 30
- `src/Infrastructure/Control/FeedforwardConfigStore.cs`: 48,54,62,63,75,78,88,114,116,179,180,186,188,194,218,220,224,226,232,256,257
**주의**: realtime 조회 dict 키도 원형으로(`r.TagName` 그대로). 비교 필요 시 양쪽 원형 동일 비교.
**기존 데이터 마이그레이션**: ff_* 테이블에 이미 저장된 소문자 태그를 proper-case로 갱신하거나 설정 재저장.
proper-case 정답은 `hc900_map_master.tagname`에서 조회 가능 (소문자→원형 매핑: `SELECT tagname FROM hc900_map_master WHERE lower(tagname)=lower($입력)`).
**검증**: FF 탭 advisory에 PV/SP 값 표시됨; SP 쓰기가 게이트웨이에서 성공(success=true).

### TASK 2 — 태그 쓰기 UI: MODE 접미사 오류 + MD 읽기전용
**문제**: `src/Hc900Crawler/wwwroot/js/write.js:47` 이 `tagName + '.MODE'` 로 전송하나,
register-map의 모드 태그는 **`.MD`(읽기전용)**. 모드 변경은 별도 R/W 레지스터로 해야 함:
- Auto/Manual → `<loop>.AutoManState` (0=Manual,1=Auto)
- LSP/RSP(=CASC) → `<loop>.RemLocSPState` (0=LSP,1=RSP)
- SP1/SP2 → `<loop>.SP_SelectState`, Tune → `<loop>.TuneSetState`
(읽기 상태는 `<loop>.MD` = LoopStatus, 0=RSP AUTO,1=RSP MAN,4=LSP AUTO,5=LSP MAN)
**수정**: write.js 모드 UI를 위 R/W 레지스터로 쓰도록 변경(`.MODE` 제거). 드롭다운을
Auto/Manual·LSP/RSP 등 개별 제어로 재구성 권장.
**검증**: C3(192.168.0.240, 데모, 쓰기 허용)에서 `FICQ-6101.RemLocSPState=1` 쓰면 Experion이 CASC 표시,
`=0`이면 LSP 복귀. (Modbus 직접 검증: `/tmp/hc900_venv/bin/python3`+pymodbus, 0x0AFC 레지스터.)

### TASK 3 — tag_metadata 적재 (상태레이블·설명) + controller_id
**문제**: `tag_metadata` 0행 → 디지털 태그가 상태레이블({1|RUN|}) 없이 raw 숫자로 표시.
`Hc900RealtimeService.cs:164`가 `WHERE attribute LIKE 'state%' AND controller_id=$1`로 조회하나
`scripts/load_state_labels.py:39`는 `(base_tag, attribute, value)`만 INSERT(controller_id 없음).
**수정**:
1. `scripts/load_state_labels.py`에 **controller_id 컬럼 추가** INSERT, `--controller` 인자화,
   Sinam에서 컨트롤러별 StatusPoint만 필터.
2. `base_tag`는 realtime tagname과 매칭되는 **원형 Experion ItemName** 사용 (소문자 금지).
   (state0~7 + description/area/sub_area 도 동일 규칙)
3. 4개 컨트롤러 적재 실행.
**검증**: 디지털 태그 livevalue가 `{N|레이블|}` 형식; 태그관리/트렌드에 설명 표시.

### TASK 4 — v_tag_summary 뷰 대소문자 ✅ 완료
**진단 결과**: `Hc900DbContext.cs:519`에서 이미 `split_part(tagname, '.', 1)` 사용, `LOWER()` 없음.
base_tag가 원형으로 유지됨. **작업 불필요**.

### TASK 5 — ff_* 테이블 생성 보장 ✅ 완료
**진단 결과**: `Hc900DbContext.cs:1124-1182`에 `ff_column_config`, `ff_stream_config` 테이블이
이미 `CREATE TABLE IF NOT EXISTS` + `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`로 생성됨.
작업지시서가 언급한 `ff_configs`/`ff_columns`는 실제 테이블명이 아님. **테이블은 이미 존재**.
**검증**: FF 탭 로드 시 500 없음; 설정 저장/조회 동작.

### TASK 6 — PidExtractorService 태그 매칭
**문제**: `src/Core/Application/Services/PidExtractorService.cs:381-384`가
`tag_no.Split('.')[0].ToLowerInvariant()` vs `TagName.ToLower()` 비교.
**수정**: 양쪽 원형 비교 또는 명시적 대소문자 무시 비교로 일관화(소문자 변환 데이터 저장은 금지).
**검증**: P&ID 추출 후 장비-태그 링크 정상.

### TASK 7 — 게이트웨이 대소문자 정책 (방어적, 선택)
게이트웨이 `WriteTag`/`ReadTags`는 `tag_index_` exact-match로 **대소문자 구분**.
TASK 1·2로 모든 호출자가 원형을 보내면 불필요하나, 견고성 위해
`industrial-comm/cpp/src/gateway.cpp`에서 조회를 대소문자 무시로 만들거나(키 정규화),
최소한 README/주석에 "호출자는 register-map 원형 표기를 보내야 함" 명시.

## 3. 탭별 영향 요약 (확인용)

| 탭(pane/js) | 백킹 | 상태 |
|---|---|---|
| Setup (panes/setup.html, js/setup.js) | gateway-config.json, HealthCheck | ✅ 수정완료(연결판정·placeholder) |
| 태그 관리 (pb) | hc900_map_master | ✅ 수정완료(적재+복합UNIQUE) |
| 이력 조회 (hist) | history_table | ⚠ 구 소문자 이력 혼재 — 재시작 후 신규는 원형. 과거 데이터 마이그레이션 여부 결정 |
| 이벤트 (evt) | event_history_table | 크롤러 누적. 이벤트 감지 서비스가 tagname 비교 시 대소문자 점검 |
| 태그 쓰기 (write) | gateway/write | ⚠ TASK 2 |
| 트렌드 (trend) | history_table, v_tag_summary | ⚠ TASK 4 의존 |
| FF (ff) | ff_* , realtime, gateway write | ⚠ TASK 1,5 |
| P&ID (pid) | pid_equipment | ⚠ TASK 6 (링크) |
| Text-to-SQL (t2s) | node_map_master(구) | 별개 체계 — 손대지 말 것 |
| RAG (kbadmin), 문서(docs), LLM(llmchat) | kb_*, 파일시스템 | 태그 무관 — 영향 없음 |

## 4. 마무리 절차 (모든 TASK 후)
1. `cd src/Hc900Crawler && dotnet build` (0 errors 확인)
2. 게이트웨이 변경 시 `cd industrial-comm/cpp/build && make -j`
3. **크롤러 재시작** (구 프로세스가 소문자 재기록 중이므로 필수). 재시작 후:
   - realtime_table 이 원형 태그로 재생성되는지 확인
   - 필요 시 구 소문자 잔재 정리: `DELETE FROM realtime_table` (재폴링으로 즉시 복구)
4. 회귀 검증: 각 탭 로드 + Setup 연결상태(C3만 green) + 태그관리 livevalue 표시 + FF SP 쓰기.

## 5. 참고 사실 (맥락 없는 LLM용)
- 컨트롤러 IP: C1=192.168.0.250, C2=192.168.0.230, **C3=192.168.0.240(데모,쓰기OK)**, C4=192.168.0.220. modbus 502.
- gRPC 포트: C1 50051 … C4 50054 (`config/gateway-config.json`). 현재 C3만 물리 연결됨.
- 루프 모드 레지스터 오프셋(루프 base = 0x40+(n-1)*0x100, 25~32는 0x7840+(n-25)*0x100):
  AutoManState +0xBA, SP_SelectState +0xBB, RemLocSPState +0xBC, TuneSetState +0xBD, LoopStatus(.MD,R) +0xBE.
- 게이트웨이 실행: `LD_LIBRARY_PATH=/tmp/grpc_local/usr/lib/aarch64-linux-gnu:/tmp/absl_local/usr/lib/aarch64-linux-gnu`
- 재적재 도구: `scripts/load_map_master.py --from-config config/gateway-config.json`,
  `scripts/build_register_map_from_sinam.py --controller Cn --sinam docs/Sinam_Tag_all.xlsx -o docs/register-map-cn.json`
