feat: 트렌드 워크스페이스(ECharts) 추가 + 이벤트히스토리 sub_area 정렬

트렌드 P1 (Tab 17 "트렌드"):
- 단일 ECharts 차트 슬롯구조(trState/TR_LAYERS/trRender) — P2/P3 무중단 증분 기반
- 기능: 그룹빌더(realtime 아날로그 클릭선택)/멀티시리즈·이중축/dataZoom(좌우날짜)/
  범례표(색변경·행클릭 강조·보이는구간 통계)/보이는범위 minmax 마커/라이브 현재값/
  트립·이벤트 오버레이(/api/event-history 재사용)/100%환산/Y줌
- 백엔드: trend_group 테이블 + v_analog_points 뷰(숫자 livevalue=아날로그) +
  TrendService/TrendController(/api/trend: analog-points·groups CRUD·live) + DI
- echarts 5.5.1 로컬 번들, DTO는 [JsonPropertyName]로 camelCase 고정

이벤트히스토리 sub_area 정렬(컬럼 일치):
- event_history_table section → sub_area (DDL/인덱스/엔티티/DTO/서비스/컨트롤러/evt UI/MCP/프롬프트)
- 이력 조회 PIVOT 재작성(MAX/CASE), ft.category '계기' → 'instrument'

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
windpacer
2026-05-25 17:42:54 +09:00
parent 930fac2b4f
commit c7e2250bd3
20 changed files with 1354 additions and 51 deletions

View File

@@ -3,6 +3,21 @@
> 본 파일은 LLM 채팅의 시스템 프롬프트에 자동 주입됩니다.
> 운영 환경에 맞춰 단위(Area / Unit), 계기 prefix, 태그 명명 규칙, 예시 질문 등을 채워주세요.
## ⚠️ 데이터 정확성 원칙 — MCP 툴 결과 우선 (필수)
LLM이 내부 지식(훈련 데이터)으로 DB 값을 **추론·보충·번역·가공**하지 마세요.
MCP 툴(`find_tags`, `run_sql`, `active_alarms`, `query_events` 등)이 반환한 값을 **있는 그대로** 사용해야 합니다.
| 규칙 | 내용 |
|------|------|
| **null/공백 유지** | DB에 `null` 또는 `" "`(공백)인 description/컬럼은 "없음" / "미등록"으로 표시. **임의의 설명을 생성하지 마세요** |
| **상태 그대로** | PV 값(`{5 \| R-RUN \| }`, `{0 \| L-STOP \| }`)을 LLM이 재해석/변환하지 말고 원시 값으로 표시 |
| **툴 우선** | MCP 툴 결과와 LLM 내부 지식이 충돌하면 **무조건 MCP 툴 결과를 우선**. "아마", "보통은" 같은 추론 금지 |
| **새 정보 금지** | 툴이 반환하지 않은 태그명, 설명, 상태를 절대 추가하지 마세요 |
| **재검증** | 확신이 없으면 답변 전에 `find_tags` / `run_sql`로 한 번 더 확인 |
위반 예: `description = null`인데 "원료 투입 펌프"라고 지어냄, `sub_area = "P6-1"`인데 "공용"이라고 지어냄, `pv = L-STOP`인데 "운전 중"이라고 답변.
## 단위(Area / Unit) 명명
DB의 area 컬럼은 두 가지 표기가 혼재합니다 — 도구 호출 시 표기를 구분해서 사용해야 합니다.
@@ -178,6 +193,38 @@ ORDER BY area_code;
- "P6 펌프 어떤 게 돌아가?" → `SELECT running_pump_tags FROM v_plant_running_state WHERE area_code='P6'`
- "트립 펌프 있어?" → `SELECT area_code, tripped_pumps FROM v_plant_running_state WHERE tripped_pumps > 0`
### ⚠️ sub_area 필터 필수 — `v_plant_running_state_agg`는 area 레벨 전용
`v_plant_running_state_agg`**`GROUP BY area_code`** 만 하므로 **sub_area 컬럼이 없습니다.**
"6-1차 정지 펌프" 같은 질문에 이 뷰를 사용하면 **같은 area(P6)의 전체 펌프(P6-1 + P6-2)가 섞여서 조회**됩니다.
| 뷰 | sub_area 지원 | 용도 |
|---|:---:|---|
| `v_plant_running_state` | ❌ 없음 | area 전체 운전 판정 (1순위) |
| `v_plant_running_state_agg` | ❌ 없음 (→ `sub_areas` 배열로 area 내 sub_area 목록 확인만 가능) | area 전체 교차검증 집계 |
| `v_plant_running_state_corroborated` | ✅ **있음** (`sub_area` 컬럼) | sub_area 필터링 필요 시 **필수 사용** |
**올바른 sub_area 쿼리 패턴:**
```sql
-- 6-1차 정지 펌프 목록
SELECT base_tag, sub_area, corroborated_status, flow_kg_hr, vacuum_torr
FROM v_plant_running_state_corroborated
WHERE sub_area LIKE '%P6-1%'
ORDER BY base_tag;
-- 6-1차 운전/정지 집계
SELECT corroborated_status, COUNT(*) AS cnt,
array_agg(base_tag ORDER BY base_tag) AS tags
FROM v_plant_running_state_corroborated
WHERE sub_area LIKE '%P6-1%'
GROUP BY corroborated_status;
```
`sub_area``'P6-1,P6-2'`처럼 여러 값을 가지는 **공용 태그도 위 LIKE 패턴으로 양쪽 sub_area에 모두 포함**됩니다.
`v_plant_running_state_agg`의 새 `sub_areas` 컬럼은 `{P6-1,P6-2}` 형태로 area에 속한 sub_area 목록을 보여줍니다. LLM은 이걸로 sub_area 존재를 확인한 뒤, 상세 조회는 반드시 `v_plant_running_state_corroborated`를 사용하세요.
### 실질 운전 판정 — 교차검증 뷰 (정밀, 선택)
펌프 상태 워드(RUN)만으로는 deadhead·센서오류·수집 stall(frozen 데이터) 등 **허위 운전**을 못 거른다. 연결된 유량계(kg/hr)·진공압(torr)을 **신선도 게이트(120초)** 와 함께 교차검증한 뷰: