P0 셀프서비스 결정론 리포트 — 적산·물질수지 폐합·cleaning 마스크 (+ P1 온라인 스펙) #1
BIN
docs/ControlEdge HC900 IO Modules Specifications.pdf
Normal file
BIN
docs/ControlEdge HC900 IO Modules Specifications.pdf
Normal file
Binary file not shown.
76
docs/논의-AI운전원-제어-아이디어.md
Normal file
76
docs/논의-AI운전원-제어-아이디어.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 논의 정리: AI 운전원 / 직접제어 아이디어
|
||||
|
||||
> 2026-06-11 대화 정리. 스마트폰에서 진행한 브레인스토밍을 다시 논의하기 위해 기록.
|
||||
|
||||
## 출발 질문
|
||||
- 히스토리컬 데이터 기준으로 **현재 제어출력(OP)에서 앞으로 5분간 유량 예측**이 가능한가?
|
||||
|
||||
### 결론
|
||||
가능. 단 "예측"을 둘로 나눠야 함:
|
||||
- **OP → 유량 정상상태**: 유량 루프는 응답 빠른 계통(시정수 수 초~수십 초). 5분(300초)은 과도응답이 아니라 거의 정상상태. → OP에 대한 정상상태 게인 맵으로 예측 쉬움. (이미 learned-control의 steady-state map과 동일한 물건)
|
||||
- **5분 동안의 변동 요인**: 예측 오차의 진짜 원인.
|
||||
- OP 자체가 5분 안에 바뀜 (자동=컨트롤러, 수동=운전원) → OP 미래궤적부터 예측해야 함
|
||||
- 미측정 외란 (헤더 압력, 상류 공급, 다른 루프 간섭)
|
||||
|
||||
### 데이터 제약
|
||||
| 소스 | 주기 | 5분=점개수 | 용도 |
|
||||
|---|---|---|---|
|
||||
| `history_table` | 60초 | ~5점 | 정상상태 예측엔 충분, 동특성 식별엔 거침 |
|
||||
| `realtime_table` | ~1초 | ~300점 | FOPDT 동특성 식별에 필요 |
|
||||
|
||||
---
|
||||
|
||||
## 우리 프로젝트의 진짜 자산 (moat)
|
||||
이 셋을 **한 시스템 안에** 다 가진 게 차별점 (대부분 시스템은 하나만 가짐):
|
||||
1. **컨트롤러 직접 쓰기 가능** — gateway WriteTag + mode write가 C3에서 실측 검증됨
|
||||
2. **운전원 행동 히스토리 + P&ID 그래프 + KB 문서** — trace_connections, build_pid_graph
|
||||
3. **LLM/RAG 추론 레이어**
|
||||
|
||||
## 후보 아이디어 (임팩트 순)
|
||||
1. 🔥 **"AI 운전원" — 자문에서 자율로**: 학습 모델이 선택된 MANUAL 루프에 직접 OP 쓰기. 쓰기 경로 이미 검증됨. 단계적(자문→1클릭 승인→화이트리스트 자율). 안전장치: 그래프/KB로 하류 영향 사전 체크.
|
||||
2. **그래프 기반 자동 근본원인 추적**: 값 비정상 시 P&ID 그래프 따라 상류 전파 추적 → 원인 노드 식별. 알람 홍수를 한 줄 인과로 압축.
|
||||
3. **외란 도착 예측 → 선제 피드포워드**: 그래프에 시간 입혀서 상류 외란 도착 시각 계산 → PV 움직이기 전에 OP 선제 조정. 5분 예측이 보고용이 아니라 선제 제어입력이 됨.
|
||||
4. **소프트 센서**: 필드계기는 고유 태그 없음. 측정 안 되는 양을 상관 태그+모델로 추정 → 비용 0으로 계측점 늘리기.
|
||||
|
||||
**추천 방향:** #1을 목표로, #2·#3을 안전·예측 엔진으로 깔기 = "설명 가능하고(그래프) 앞을 내다보는(예측) AI 운전원".
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 핵심 반론 (사용자 지적)
|
||||
지난 **4개월 운전원 수동 데이터** 온도 프로파일을 보면 **민감단 온도가 크게 안 흔들림 → 운전원이 이미 제어를 매우 잘함**.
|
||||
→ 우리가 직접 제어해서 "더 정밀하게" 이겨봐야 어필이 안 됨. 운전원의 정상상태 제어를 타이트하게 이기는 건 어렵고, 이겨도 감동이 없음.
|
||||
|
||||
### 대응 전략: 채점표를 바꿔라
|
||||
운전원이 이기는 축(정상상태 온도 타이트함)에서 싸우지 말고, 운전원이 **구조적으로 못 이기는** 세 축에서 싸운다.
|
||||
|
||||
**① 경제 마진 — 진짜 팔리는 숫자**
|
||||
- 민감단 온도가 안 흔들린다 = 운전원이 **보수적 마진**을 깔고 운전(환류/스팀 여유). 그 여유 = 매일 버려지는 에너지/처리량.
|
||||
- 어필: "온도 안정도는 운전원과 **동일**, 셋포인트를 최적점으로 당겨 스팀 X% 절감."
|
||||
- 제어를 이기는 게 아니라 **같은 안정도로 더 싸게**. APC가 30년간 돈 번 방식. 운전원은 다변수 동시 최적화 못 하니 구조적으로 못 따라옴.
|
||||
|
||||
**② 선제성(앞먹임) — 운전원은 PV 움직인 뒤에 반응**
|
||||
- 우리는 그래프로 상류 외란 도착 미리 계산 → 온도 움직이기 전 OP 조정.
|
||||
- 어필: "공급 변동 시 민감단 온도 최대 편차, 운전원 대비 절반." 운전원이 원리적으로 못 이기는 영역.
|
||||
|
||||
**③ 분산(variance) — 4개월 프로파일의 함정**
|
||||
- 깔끔한 프로파일은 잘하는 운전원의 좋은 날 평균. 교대조별·시간대별(새벽 3시)·신참으로 쪼개면 표준편차 다름.
|
||||
- 가치 = "항상 최고 운전원 수준, 24시간, 모든 교대조." 평균이 아니라 최악 시간대를 끌어올림.
|
||||
|
||||
---
|
||||
|
||||
## 다음 액션: 제어 건드리기 전 오프라인 검증
|
||||
4개월 히스토리로 **카운터팩추얼 분석** (컨트롤러 안 건드림, 데이터로만):
|
||||
1. 운전원이 깔고 있는 보수 마진 측정 (민감단 온도 vs 제약한계 거리, 환류/스팀 여유분)
|
||||
2. "온도 안정도 그대로 두고 마진만 당겼다면 절감액 = ?" 정량 추정
|
||||
3. 외란 구간 운전원 반응 지연 측정 → 앞먹임으로 줄일 수 있는 편차 정량화
|
||||
|
||||
→ 이 세 숫자가 나오면 제어 켜기 전에 어필 자료 완성. 셋 다 "운전원보다 정밀"이라는 어려운 약속이 아니라 "운전원이 못 하는 것"이라는 방어 가능한 약속.
|
||||
|
||||
### 첫 단추 (추천)
|
||||
**①(마진)부터.** 필요한 것: 민감단 온도 태그 + 환류/스팀 관련 태그. → 4개월치로 "버려지는 마진"이 실제 존재하는지 데이터로 확인.
|
||||
|
||||
## 다시 논의할 때 정할 것
|
||||
- [ ] 어느 축부터 갈지 (추천: ① 마진)
|
||||
- [ ] 대상 컬럼/루프 및 민감단 온도 태그명
|
||||
- [ ] 환류/스팀 등 마진 관련 태그명
|
||||
- [ ] realtime 1초급 데이터가 실제로 쌓이는지 확인 (동특성 식별용)
|
||||
796
docs/작업지시서-API-필드-PascalCase-통일.md
Normal file
796
docs/작업지시서-API-필드-PascalCase-통일.md
Normal file
@@ -0,0 +1,796 @@
|
||||
# 구현 검증 보고서 — 작업지시서-API-필드-PascalCase-통일
|
||||
|
||||
**검증일**: 2026-06-11
|
||||
**검증 대상**: 작업지시서 §5 Phase 0–3 구현 결과
|
||||
**검증 방법**: `diagnosis-checklist.md` 8단계 + 전수 코드 스캔 (28개 파일 변경, +631/-544줄)
|
||||
**빌드 상태**: ✅ `dotnet build` 0 Error, 0 Warning
|
||||
**전체 등급**: 🟠 MED — Phase 0(PascalCase parse) 완료, Phase 1·2 다수 누락, Phase 3 양호
|
||||
|
||||
---
|
||||
|
||||
## STEP 1 — 맥락 파악
|
||||
|
||||
**질문: 무엇이 구현되었고, 무엇을 검증하는가?**
|
||||
|
||||
작업지시서는 4개 Phase로 구성:
|
||||
- **Phase 0** — `TextToSqlController.parse` 핫픽스 (PascalCase 응답)
|
||||
- **Phase 1** — 15개 컨트롤러 익명객체 응답 PascalCase 통일
|
||||
- **Phase 2** — 17개 JS 파일 응답 읽기 PascalCase 통일
|
||||
- **Phase 3** — 내부용 `[JsonPropertyName]` 제거
|
||||
|
||||
구현 결과 28개 파일이 변경됨 — 컨트롤러 15개 전부, JS 10개, DTO 2개, 서비스 1개 수정. 본 검증은 각 Phase가 **완전히** 구현되었는지, **누락 및 회귀**가 없는지를 진단한다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 2 — 구조 탐색
|
||||
|
||||
변경된 파일 계층:
|
||||
|
||||
```
|
||||
Controllers/*.cs (15) ──응답 PascalCase──▶ wwwroot/js/*.js (10) ──소비
|
||||
│ │
|
||||
└── DTOs/*.cs (2) ──[JsonPropertyName] 제거──┘
|
||||
│
|
||||
└── Service/*.cs (1) ──InputSchema 추가 (관련 없음)
|
||||
```
|
||||
|
||||
**28개 변경 파일 중 `git diff --stat` 기준**:
|
||||
- 컨트롤러: 15개 (모든 컨트롤러)
|
||||
- JS: 10개 (core.js·app.js·evt.js 제외한 나머지)
|
||||
- DTO: `ExperionDtos.cs`, `TrendDtos.cs`
|
||||
- 서비스: `McpService.cs`, `IMcpService.cs`
|
||||
- 기타: `prompts/plant_context.md`
|
||||
|
||||
---
|
||||
|
||||
## STEP 3 — 코드 읽기
|
||||
|
||||
### Phase 0: TextToSqlController.parse — ✅ PASS
|
||||
|
||||
```csharp
|
||||
// TextToSqlController.cs L44 — 변경됨
|
||||
return Ok(new { Success = true, Sql = sql }); // ← PascalCase ✅
|
||||
return Ok(new { Success = false, Error = ex.Message });
|
||||
```
|
||||
|
||||
이전: `new { success = true, sql }` → 현재: `new { Success = true, Sql = sql }`.
|
||||
`t2s.js`가 이미 `parseRes.Sql`·`parseRes.Success`로 읽고 있으므로 즉시 복구됨.
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: 컨트롤러 통일 — ❌ 11/15 누락
|
||||
|
||||
| 컨트롤러 | 상태 | 잔여 소문자 |
|
||||
|----------|------|-----------|
|
||||
| `FastController.cs` | ✅ CLEAN | 0 |
|
||||
| `TrendController.cs` | ✅ CLEAN | 0 |
|
||||
| `DocsController.cs` | ✅ CLEAN | 0 |
|
||||
| `PidGraphController.cs` | ✅ CLEAN | 0 |
|
||||
| `TextToSqlController.cs` | ✅ CLEAN | 0 |
|
||||
| `HypertableController.cs` | ⚠️ 1건 | `statusMessage = info.StatusMessage` L22 |
|
||||
| `KbAuthController.cs` | ⚠️ 1건 | `new { valid }` L50 |
|
||||
| `Hc900Controllers.cs` | ⚠️ 5건 | Gateway health/status/tags L36-79, `{ total, tags }` L415 |
|
||||
| `SetupController.cs` | ⚠️ ~6건 | controllers status/config L40-80 (주석: "camelCase 보장") |
|
||||
| `OllamaController.cs` | ⚠️ ~8건 | `GetModels`/`GetVllmModels` 응답 L291-594 (`success`, `error`, `models`, `excluded`), SSE 툴 이벤트 페이로드 L165-194 |
|
||||
| `SteamAdvisorController.cs` | ⚠️ ~5건 | ComputeStages 내부 익명객체 L555-577 |
|
||||
| `PointBuilderController.cs` | ⚠️ ~8건 | Items 매핑 L426-444, Sinam 파일 목록 L207-214 |
|
||||
| `KbController.cs` | ⚠️ ~12건 | Document/Job Select L155-264, error 응답 |
|
||||
| `PidController.cs` | ⚠️ ~22건 | Equipment DTO 매핑 L115-141, prefix rule CRUD |
|
||||
| `FeedforwardController.cs` | ⚠️ **150+건** | `MapConfig`·`MapRampJob`·`MapRamp`·`MapColumn` 전부 미변경 L213-519 |
|
||||
|
||||
> **Phase 1 판정**: 4/15만 CLEAN, 나머지 11개에 소문자 잔존. 특히 `FeedforwardController`는 전체 응답 객체 트리가 100% 소문자여서 효과가 전혀 없음. `PidController`·`KbController`도 대량 미변경.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: JS 파일 통일 — ❌ 12/17 파일에 소문자 잔존
|
||||
|
||||
**🔴 HIGH — trend.js (4개 버그)**
|
||||
|
||||
| 위치 | 문제 | 영향 |
|
||||
|------|------|------|
|
||||
| L659 | `pt.value` → `pt.Value` 미변경 | 라이브 틱 데이터가 차트에 반영 안 됨 |
|
||||
| L676 | `p.Desc` → `p.Description` (DTO 속성명 불일치) | 그룹 빌더 설명 필드 `undefined` |
|
||||
| L751 | `g.members` → `g.Members` 미변경 | 그룹 멤버 로딩 전면 실패 |
|
||||
| L752-758 | `m.tag`·`m.color`·`m.axis`·`m.desc` 등 전부 소문자 | 멤버 속성 전부 `undefined` |
|
||||
| L755-758 | `trAnalogMap[m.tag]?.desc` — map 저장은 `Desc`, 조회는 `desc` | 키 불일치로 항상 `undefined` |
|
||||
| L698-708 | `p.tagName`·`p.description`·`p.area`·`p.value` 등 전부 소문자 | 그룹 빌더 UI 전면 미동작 |
|
||||
|
||||
**🟠 MED — pb.js (~19건)**
|
||||
|
||||
| 패턴 | 위치 |
|
||||
|------|------|
|
||||
| `res.success` → `res.Success` | L135, 370, 434, 469 |
|
||||
| `res.error` → `res.Error` | L143, 434, 470 |
|
||||
| `res.message` → `res.Message` | L84, 100, 349, 362 |
|
||||
| `res.count` → `res.Count` | L84, 100, 111, 112, 238, 349, 362 |
|
||||
| `res.items` → `res.Items` | L230, 240, 275, 283 |
|
||||
|
||||
**🟠 MED — kbadmin.js (~12건)**
|
||||
|
||||
`data.success`·`data.error`·`data.chunks`·`data.affected`·`data.total` 등 전부 소문자 (L202-341)
|
||||
|
||||
**🟡 LOW — 기타 미변경 파일**
|
||||
|
||||
| 파일 | 건수 | 예시 |
|
||||
|------|------|------|
|
||||
| `setup.js` | ~6건 | `r.success ?? r.ok`, `r.message` |
|
||||
| `write.js` | ~4건 | `d.success`, `d.error`, `d.controllers` |
|
||||
| `steam.js` | ~3건 | `d.columns`, `data.suggestions`, `d.message` |
|
||||
| `fast.js` | ~2건 | `data.items`, 요청바디 `{ name, samplingMs }` |
|
||||
| `ff.js` | ~2건 | `data.columns` |
|
||||
| `pid.js` | ~5건 | `data.items`, `err.error` |
|
||||
| `llmchat.js` | 0건 (기존 폴백 유지) | `d.Success ?? d.success` — 방어적, 통일 후 제거 대상 |
|
||||
|
||||
> **Phase 2 판정**: `hist.js`·`evt.js`·`docs.js`·`core.js`·`app.js`는 CLEAN. `t2s.js`는 PascalCase로 올바름. 나머지 12개 파일에 소문자 잔존. 특히 **trend.js는 4개 런타임 버그**(그룹 로딩·라이브 틱·그룹 빌더·아날로그 맵)가 공존.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: `[JsonPropertyName]` 정리 — ✅ 양호 (1건 REMOVE 권장)
|
||||
|
||||
25개 `[JsonPropertyName]` 중 24개가 외부 계약으로 KEEP 판정 정확함.
|
||||
|
||||
**REMOVE 권장 1건**: `ExperionDtos.cs:64`
|
||||
```csharp
|
||||
[JsonPropertyName("analogmon1")] // ← 미사용 DTO. PointBuilderBuildDto 자체가 dead code
|
||||
public PointBuilderGroupDto AnalogMonitor1 { get; set; } = new();
|
||||
```
|
||||
→ `Hc900PointBuilderBuildDto`(같은 파일 L106-113)가 실제 사용 중이므로, 위 클래스 전체 삭제 권장.
|
||||
|
||||
---
|
||||
|
||||
### 추가 발견: `TrendDtos.cs` — `TrendMemberDto` `[JsonPropertyName]` 3개 제거됨
|
||||
|
||||
```csharp
|
||||
// BEFORE
|
||||
[JsonPropertyName("tag")] public string Tag { get; set; } = "";
|
||||
[JsonPropertyName("color")] public string Color { get; set; } = "";
|
||||
[JsonPropertyName("axis")] public string Axis { get; set; } = "";
|
||||
// AFTER
|
||||
public string Tag { get; set; } = "";
|
||||
public string Color { get; set; } = "";
|
||||
public string Axis { get; set; } = "";
|
||||
```
|
||||
|
||||
**의도**: `PropertyNamingPolicy = null`에서 자연스럽게 PascalCase 직렬화.
|
||||
**영향**: `TrendGroupDto.Members`가 `[{"Tag":"FICQ-6101.PV","Color":"#e41a1c","Axis":"left"}, ...]`로 나감.
|
||||
**JS 영향**: `trend.js` L751-758이 `g.members`를 `g.Members`로, `m.tag`를 `m.Tag`로 바꾸지 않으면 그룹 기능 중단. 현재 둘 다 소문자여서 **그룹 기능 동작 안 함**.
|
||||
|
||||
---
|
||||
|
||||
## STEP 4 — 호출 계층 지도
|
||||
|
||||
Phase 0은 완료되었으나 Phase 1과 Phase 2의 **미완성으로 인한 의존 체인 단절**:
|
||||
|
||||
```
|
||||
Phase 1 미완료 (11/15 컨트롤러)
|
||||
↓
|
||||
Phase 2에서 수정한 JS가 PascalCase API를 호출
|
||||
↓
|
||||
미변경 컨트롤러가 camelCase → JS undefined
|
||||
↓
|
||||
Silent failure — 빈 테이블, 동작하지 않는 버튼
|
||||
```
|
||||
|
||||
반대 방향도 존재:
|
||||
|
||||
```
|
||||
Phase 1 완료 (4/15 컨트롤러 — Fast, Trend, Docs, PidGraph)
|
||||
↓
|
||||
Phase 2 미완료 (trend.js 등이 여전히 camelCase로 읽음)
|
||||
↓
|
||||
PascalCase API 응답 → JS undefined
|
||||
↓
|
||||
Silent failure
|
||||
```
|
||||
|
||||
**즉, 어떤 방향이든 현재 코드는 **컨트롤러 11개 + JS 12개에 걸쳐 미스매치 상태**다.**
|
||||
|
||||
---
|
||||
|
||||
## STEP 5 — 패턴 매칭 (체크리스트)
|
||||
|
||||
### 🔴 HIGH — 미변경 camelCase 변수 참조
|
||||
|
||||
| 체크 | 항목 | 판단 |
|
||||
|------|------|------|
|
||||
| [x] | **trend.js `g.members` → `g.Members`** | L751 — `TrendGroupDto.Members`는 PascalClass, API는 `"Members"`, JS는 `g.members` → `undefined`. 그룹 로딩 전면 실패 |
|
||||
| [x] | **trend.js `m.tag` → `m.Tag` 등** | L752-758 — `TrendMemberDto.Tag`/`Color`/`Axis`가 PascalCase인데 JS가 소문자로 읽음 |
|
||||
| [x] | **trend.js `p.Desc` → `p.Description`** | L676 — `AnalogPointDto.Description`을 `Desc`로 오기입 |
|
||||
| [x] | **trend.js `pt.value` → `pt.Value`** | L659 — `TrendLivePointDto.Value`인데 소문자 `pt.value`는 `undefined` |
|
||||
| [x] | **trend.js 아날로그 맵 키 불일치** | L676 `{ Desc, Unit, ... }`로 저장 → L755-758 `.desc`, `.unit`으로 조회 |
|
||||
| [x] | **trRenderAnalog 전면 미변경** | L698-708 — 8개 필드 전부 소문자 |
|
||||
|
||||
### 🟠 MED — 불완전한 변경
|
||||
|
||||
`pb.js`·`kbadmin.js`·`setup.js`·`write.js`·`steam.js`·`ff.js`·`fast.js`·`pid.js` — 변경 자체가 누락.
|
||||
|
||||
### 🟢 LOW — Phase 3 DTO 정리
|
||||
|
||||
`ExperionDtos.cs`의 dead code 제거는 미이행되었으나 실행에 영향 없음.
|
||||
|
||||
---
|
||||
|
||||
## STEP 6 — 교차 검증
|
||||
|
||||
| Q | 질문 | 결과 |
|
||||
|---|------|------|
|
||||
| Q1 | 이미 수정된 문제인가? | trend.js 4개 버그는 **아직 수정되지 않음** (현재 파일 상태). pb.js 등 8개 파일도 미수정. |
|
||||
| Q2 | 다른 레이어에서 처리? | trend.js 그룹 로딩(`g.members`) → 우회 불가. JS에서 유일한 소비 계층. |
|
||||
| Q3 | 의도적 설계? | trend.js L676 `p.Desc`는 오기입(오타). 나머지는 단순 누락. |
|
||||
| Q4 | 재현 시나리오? | trend.js: 트렌드 탭 → 그룹 선택 → 빈 화면(멤버 로딩 실패). 그룹 빌더 → 모든 태그 필드 `undefined`. |
|
||||
|
||||
---
|
||||
|
||||
## STEP 7 — 심각도 분류 및 STEP 8 — 보고서
|
||||
|
||||
### 🔴 HIGH 1. trend.js 그룹 기능 전면 불능
|
||||
|
||||
**문제**: `TrendGroupDto.Members`·`TrendMemberDto.Tag/Color/Axis`가 PascalCase로 직렬화되지만, `trApplyGroup`(L751)이 `g.members`·`m.tag` 등 소문자로 읽음. `trAnalogMap`도 저장 키(Desc)와 조회 키(desc)가 불일치.
|
||||
|
||||
**근거**: `TrendDtos.cs` L11-16: `[JsonPropertyName]` 3개 제거, 속성명 `Tag`·`Color`·`Axis`.
|
||||
`trend.js` L751-758: `g.members.map(m => ({ tag: m.tag, color: m.color, axis: m.axis, desc: trAnalogMap[m.tag]?.desc }))`.
|
||||
|
||||
**영향**: 트렌드 탭 → 그룹 선택 시 멤버 0명 로드 → 차트 빈 화면. 라이브 틱(`pt.value` L659)도 누락되어 데이터 갱신 안 됨.
|
||||
|
||||
**수정**: L751 `g.members` → `g.Members`, L752-758 `m.tag` → `m.Tag`, `m.color` → `m.Color`, `m.axis` → `m.Axis`. L676 `p.Desc` → `p.Description`. L659 `pt.value` → `pt.Value`. L755-758 `.desc` → `.Desc`, `.unit` → `.Unit`, `.euLo` → `.EuLo`, `.euHi` → `.EuHi`. L698-708 `p.tagName` → `p.TagName`, `p.description` → `p.Description`, `p.area` → `p.Area`, `p.value` → `p.Value`, `p.unit` → `p.Unit`, `p.euLo` → `p.EuLo`, `p.euHi` → `p.EuHi`.
|
||||
|
||||
---
|
||||
|
||||
### 🟠 MED 2. Phase 1 컨트롤러 11개 소문자 잔존
|
||||
|
||||
**문제**: 15개 컨트롤러 중 11개에 소문자 익명객체가 잔존. 특히 `FeedforwardController`(150+ 속성), `PidController`(22), `KbController`(12)가 대량.
|
||||
|
||||
**근거**: STEP 3 Phase 1 표 참조.
|
||||
|
||||
**영향**: 해당 컨트롤러의 JS(`ff.js`, `pid.js`, `kbadmin.js` 등)가 소문자를 읽는다면 현재는 우연히 맞지만, **Phase 2에서 JS를 PascalCase로 바꾸면 깨짐**. 또는 반대로 **JS가 PascalCase를 읽으면 현재부터 깨져 있음**.
|
||||
|
||||
**수정**: 각 컨트롤러의 모든 `return Ok(new { ... })`·`return BadRequest(new { ... })`에서 소문자 키를 PascalCase로 변경. 작업지시서 §6.1 grep 명령어로 탐색 후 일괄 수정.
|
||||
|
||||
---
|
||||
|
||||
### 🟠 MED 3. Phase 2 JS 12개 파일 소문자 잔존
|
||||
|
||||
**문제**: `pb.js`(~19건), `kbadmin.js`(~12건), `setup.js`·`write.js`·`steam.js`·`ff.js`·`fast.js`·`pid.js` 등 12개 파일이 PascalCase API 응답을 소문자로 읽는 코드 잔존.
|
||||
|
||||
**근거**: STEP 3 Phase 2 표 참조.
|
||||
|
||||
**영향**: 해당 탭의 기능이 현재 작동 중이라면, 그것은 해당 컨트롤러가 아직 PascalCase로 바뀌지 않았기 때문(Phase 1도 미완료). **Phase 1이 완료되는 순간 이 JS들은 깨진다.**
|
||||
|
||||
**수정**: 각 파일에서 `.success` → `.Success`, `.error` → `.Error`, `.items` → `.Items` 등 일괄 치환. 작업지시서 §6.2 grep 명령어로 탐색 후 수정.
|
||||
|
||||
---
|
||||
|
||||
### 🟡 LOW 4. `ExperionDtos.cs` dead code `[JsonPropertyName]` 잔존
|
||||
|
||||
**문제**: `[JsonPropertyName("analogmon1")]`이 미사용 DTO에 남아 있음.
|
||||
|
||||
**근거**: `ExperionDtos.cs:64`. `PointBuilderBuildDto` 클래스 전체가 `Hc900PointBuilderBuildDto`로 대체되어 미참조.
|
||||
|
||||
**영향**: 없음 (dead code).
|
||||
|
||||
**수정**: 해당 `[JsonPropertyName]` 라인 및 미사용 DTO 클래스 전체 삭제.
|
||||
|
||||
---
|
||||
|
||||
## STEP 8 자가 검증
|
||||
|
||||
- [x] 각 지적 사항을 "현재 파일 몇 번 줄"로 직접 가리킬 수 있는가? — trend.js L659·L676·L698-708·L751-758 등
|
||||
- [x] HIGH 항목은 재현 가능한 시나리오를 한 문장으로 말할 수 있는가? — 트렌드 탭에서 그룹 선택 시 빈 차트, 그룹 빌더에서 모든 필드 undefined
|
||||
- [x] 교차 검증 4개 질문을 모두 통과한 항목만 포함되었는가? — 통과
|
||||
- [x] 보고서의 수정 예시가 현재 코드에 아직 적용되지 않은 내용인가? — 적용되지 않음 (현재 버그 상태임)
|
||||
- [x] Phase 1·2의 미완료는 "구현 중단"이 아닌 "구현 누락"으로 판단 — trend.js의 오기입(p.Desc)은 오타, 나머지는 단순 미변경
|
||||
|
||||
---
|
||||
|
||||
# 작업지시서 — 내부 REST API 필드명 PascalCase 전면 통일
|
||||
|
||||
**진단일**: 2026-06-11
|
||||
**진단 방법**: `diagnosis-checklist.md` 8단계 순차 적용
|
||||
**등급**: 🟠 MED — Plan 작성자의 현황 파악에 오류 1건 확인
|
||||
|
||||
---
|
||||
|
||||
## STEP 1 — 맥락 파악
|
||||
|
||||
**질문: 이 파일은 무엇을 하는 파일인가?**
|
||||
|
||||
작업지시서(이하 "Plan") — 백엔드 내부 REST API 응답 JSON 필드명을 PascalCase로 전면 통일하기 위한 실행 계획. `Program.cs`의 `PropertyNamingPolicy = null` 설정 아래에서 C# 코드의 속성명이 JSON으로 그대로 직렬화됨을 전제로, 15개 컨트롤러와 17개 JS 파일을 대상으로 카탈로그화·단계별 전환을 정의한다. 트리거는 커밋 `dbad4a5`가 `TextToSqlController`만 PascalCase로 바꾸고 `llmchat.js` 등 소비자를 누락해 발생한 무음 버그(silent failure).
|
||||
|
||||
Plan은 아래와 같은 구조로 읽힌다:
|
||||
1. 배경(§1): 근본 메커니즘 설명 + "PascalCase 통일" 결정
|
||||
2. 범위(§2): 변경 대상 vs 절대 변경 금지 대상을 표로 정리
|
||||
3. 현황 인벤토리(§3): 컨트롤러별 익명객체 소문자 응답 라인 수 + JS 파일별 주 컨트롤러 매핑
|
||||
4. 표준 규약(§4) + 실행 계획(§5, 4Phase) + 작업자 가이드(§6) + 검증 체크리스트(§7) + 리스크(§8) + DoD(§9)
|
||||
|
||||
> **STEP 1 결론**: Plan의 목적과 구조는 명확하며, §2(변경 금지 외부 계약)와 §4(표준 규약)의 판단 기준이 구체적이어서 실행자의 혼란을 방지한다. 다만 §3 현황 인벤토리가 **"이미 수정된 사항"과 "아직 수정되지 않은 사항"을 구분하지 않고 혼합 기재**하여, 문서만 읽는 실행자가 실제 현재 상태를 오인할 위험이 있다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 2 — 구조 탐색
|
||||
|
||||
**도움이 될 관련 파일 목록 (Plan이 참조하는 실제 코드):**
|
||||
|
||||
| 파일 | Plan § | Plan에서 주장하는 역할 |
|
||||
|------|--------|----------------------|
|
||||
| `src/Hc900Crawler/Program.cs` | §1.1 | `PropertyNamingPolicy = null` (JSON 직렬화 원인) |
|
||||
| `src/Hc900Crawler/Controllers/TextToSqlController.cs` | §3.1, §5.0 | `parse`만 소문자 잔존 |
|
||||
| `src/Hc900Crawler/wwwroot/js/t2s.js` | §3.2, §5.0 | 이미 `parseRes.Sql` PascalCase 기대 |
|
||||
| `src/Hc900Crawler/wwwroot/js/hist.js` | §3.2, §3.3 | `d.Success ?? d.success` 폴백 있음 ← **미확인** |
|
||||
| `src/Hc900Crawler/wwwroot/js/trend.js` | §3.2, §3.3 | `d.Success ?? d.success` 폴백 있음 ← **미확인** |
|
||||
| `src/Hc900Crawler/Controllers/*.cs` (14개) | §3.1 현황 인벤토리 | 전수 조사 대상 |
|
||||
| `src/Hc900Crawler/wwwroot/js/*.js` (17개) | §3.2 JS 매핑 | 전수 조사 대상 |
|
||||
|
||||
> **STEP 2 결론**: 의존 파일 목록은 완전하다. 다만 hist.js·trend.js에 대한 §3.3의 주장(폴백 존재)은 **검증되지 않은 채 작성**되었다( 아래 STEP 3에서 확인).
|
||||
|
||||
---
|
||||
|
||||
## STEP 3 — 코드 읽기 ★ 주요 오류 발견
|
||||
|
||||
Plan의 핵심 주장 3가지를 실제 코드와 대조했다.
|
||||
|
||||
### 3.1 ✅ `TextToSqlController.parse` — 소문자 잔존 (Plan §3.1 L86)
|
||||
|
||||
```csharp
|
||||
// TextToSqlController.cs L44 (실제)
|
||||
return Ok(new { success = true, sql });
|
||||
return Ok(new { success = false, error = ex.Message });
|
||||
```
|
||||
|
||||
Plan의 주장과 **일치**. `parse` 엔드포인트만 `{ success, sql, error }` (camelCase)를 반환하고, 나머지(9개) 엔드포인트는 전부 `{ Success, Sql, Error }` (PascalCase). Plan은 이 차이를 정확히 지적했다.
|
||||
|
||||
### 3.2 ✅ `t2s.js` — 이미 PascalCase 기대 (Plan §5.0 L150)
|
||||
|
||||
```javascript
|
||||
// t2s.js L118-120 (실제 — parse 버튼 핸들러)
|
||||
const res = await api('POST', '/api/text-to-sql/parse', { query: input });
|
||||
if (res.Success) { // ← PascalCase를 읽지만
|
||||
sqlTextarea.value = res.Sql;
|
||||
} else {
|
||||
sqlTextarea.value = `오류: ${res.Error || '알 수 없는 오류'}`;
|
||||
}
|
||||
// t2s.js L428-432 (실제 — T2S 채팅 핸들러)
|
||||
const parseRes = await api('POST', '/api/text-to-sql/parse', { query: message });
|
||||
if (!parseRes.Success || !parseRes.Sql) { // ← PascalCase를 읽지만
|
||||
t2sAddChatMessage('system', `<span class="t2s-error">SQL 변환 실패: ...`);
|
||||
```
|
||||
|
||||
Plan의 주장과 **일치**. t2s.js는 3곳(버튼·T2S 채팅·API 채팅) 모두 `res.Success`, `res.Sql`, `res.Error`로 PascalCase를 읽고 있다. 그러나 실제 API는 `{ success, sql, error }`(camelCase)를 반환하므로, **지금 이 순간 `parse` 버튼과 T2S 채팅의 SQL 변환 기능은 조용히 깨져 있다**:
|
||||
- `res.Success`는 항상 `undefined` → if문이 항상 else로 빠짐
|
||||
- 화면에 "SQL 변환 실패: 알 수 없는 오류" 표시
|
||||
- 실제 SQL 생성은 정상이지만 **UI가 결과를 보여주지 못함**
|
||||
|
||||
### 3.3 ❌ `hist.js`·`trend.js` — 폴백 부재 (Plan §3.3 L119)
|
||||
|
||||
Plan §3.3(이미 적용된 수정)은 다음과 같이 기술한다:
|
||||
|
||||
> `hist.js`·`trend.js`는 `query-history-interval` 읽을 때 이미 `d.Success ?? d.success` 폴백이 적용돼 있어 현재 동작.
|
||||
|
||||
그러나 **실제 코드는 폴백이 없었다** (본 진단 작성자가 수정하기 전):
|
||||
|
||||
```javascript
|
||||
// hist.js L122-127 (수정 전) — 폴백 없음
|
||||
if (!d.success) { throw new Error(d.error || '조회 실패'); }
|
||||
const rows = d.rows || []; // API는 { Rows, TagNames } (PascalCase) 반환
|
||||
const tNames = d.tagNames || [];
|
||||
```
|
||||
|
||||
```javascript
|
||||
// trend.js L358 (수정 전) — 폴백 없음
|
||||
rows = (d && d.success !== false) ? (d.rows || []) : [];
|
||||
```
|
||||
|
||||
Plan 수립 당시 **이 코드는 동작하지 않았다**. `query-history-interval`은 `{ Success, Rows, TagNames }`(PascalCase)를 반환하지만 JS는 `d.success`, `d.rows`, `d.tagNames`(camelCase)로 읽어 모두 `undefined`였다. 즉:
|
||||
- hist.js의 **간격 조회 탭이 정상 동작하지 않았다** — 빈 테이블 렌더
|
||||
- trend.js의 **집계 경로(interval !== '1 minute')가 동작하지 않았다** — rows가 항상 빈 배열 → rawQuery로 폴백
|
||||
|
||||
> **STEP 3 결론**: Plan은 사실 확인을 충분히 하지 않은 주장(§3.3)을 포함하고 있다. 최소한 `grep "d\.success" hist.js trend.js` 한 줄이면 발견할 수 있었던 오류. §3.3은 "이미 적용된 수정"을 기술하는 섹션인데, 실제로는 적용되지 않은 상태였다. 여기에 기재된 3개 항목 중 **llmchat.js·McpToolDto·OllamaController 폴백만 적용**되어 있었고 hist.js·trend.js는 적용되지 않았다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 4 — 호출 계층 지도 작성
|
||||
|
||||
Plan이 제안한 5개 Phase의 의존 관계 분석:
|
||||
|
||||
```
|
||||
Phase 0 — 핫픽스 (parse 엔드포인트)
|
||||
↓ 성공해야
|
||||
Phase 1 — 컨트롤러 백엔드 통일 (15개, 단위별 작업)
|
||||
↓ 각 컨트롤러 수정 직후
|
||||
Phase 2 — 프론트엔드 통일 (대응 JS)
|
||||
↓ 반복
|
||||
Phase 1↔2 완료 후
|
||||
Phase 3 — DTO [JsonPropertyName] 정리
|
||||
↓
|
||||
Phase 4 — 전체 회귀 검증 (체크리스트 §7)
|
||||
```
|
||||
|
||||
> **STEP 4 결론**: Phase 순서와 의존 관계는 타당하다. 특히 "백엔드+프론트 짝 커밋" 원칙(§8.6)이 강조된 점은 과거 실패(dbadd4a5의 부분 머지)를 반영한 조치로 적절하다. 다만 Phase 1의 우선순위(라인 수 오름차순)는 위험도 기반이 아니라 **작업량 기반**이라서, **영향 범위가 큰 컨트롤러**(예: OllamaController는 혼재로 인해 리스크가 높음)가 후순위로 밀릴 수 있다. 위험도 기반으로 `FeedforwardController`(44 lines, 단순)는 먼저, `OllamaController`(22 lines, ⚠️혼재)는 중간에 넣는 등 보강이 권장된다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 5 — 패턴 매칭 (체크리스트 순회)
|
||||
|
||||
Plan을 8개 체크리스트 영역으로 평가. Plan은 **이미 존재하는 버그가 아니라 앞으로의 작업 계획**이므로, 아래는 Plan 자체의 완전성·정확성을 평가한다.
|
||||
|
||||
### 🟡 LOW — 미정의 변수·함수 없음 (양호)
|
||||
|
||||
Plan의 변수 참조(§6 grep 명령어, §7 체크리스트 항목)는 모두 정의되어 있다.
|
||||
|
||||
### 🟡 LOW — 하드코딩
|
||||
|
||||
Plan은 의도적으로 **하드코딩된 현황 인벤토리**(§3)를 포함한다. 이는 문서가 스냅샷 성격을 가지므로 적절하다. grep 명령어를 함께 제공(§6)해 독자가 최신 상태를 재생성할 수 있도록 한 점은 좋은 설계.
|
||||
|
||||
### 🟠 MED — 에러 응답 형식 불일치
|
||||
|
||||
Plan §4.2는 "공통 봉투 통일: 모든 응답은 최상위에 `Success`(bool)"라고 규정하지만, §3.1 현황 인벤토리 기준으로 **6개 컨트롤러**가 응답 형식이 파편화되어 있다:
|
||||
|
||||
| 컨트롤러 | 응답 형식 | 문제 |
|
||||
|----------|----------|------|
|
||||
| `HypertableController` | `{ info.IsHypertable, info.TableName }` | 최상위에 `Success` 없음, 엔티티 직접 반환 |
|
||||
| `FastController` | `{ items }` 또는 `{ session.Id, status }` | 혼재 — 일부만 `{ success = true }` |
|
||||
| `TrendController` | `{ items }` 또는 `{ error }` | 최상위 `Success` 없음 |
|
||||
| `OllamaController` | `{ success, models }` 또는 `{ reply }` | 내부 응답도 camelCase |
|
||||
| `PidGraphController` | `{ success, data, message }` | `data`가 내포 객체 |
|
||||
| `Hc900Controllers` | 엔티티 직접 반환(`Ok(points)` 또는 `Ok(result)`) | 래핑 없음, 형식 다양 |
|
||||
|
||||
Plan은 이 차이를 인지하고 §4.2에서 통일을 규정했으나, Phase 1의 작업 우선순위는 현황 복잡성을 반영하지 않았다. 응답 형식이 이미 `{ Success, Error }` 구조에 가까운 컨트롤러(HypertableController·FastController)는 변경량이 적고, 엔티티를 직접 반환하는 컨트롤러(Hc900Controllers)는 래핑이 추가로 필요하다.
|
||||
|
||||
### 🟡 LOW — 동시성 / async 관련
|
||||
|
||||
Plan의 Phase 1-2 작업은 순차적 엔드포인트 변경으로 동시성 이슈 없음.
|
||||
|
||||
### 🟡 LOW — 보안
|
||||
|
||||
SQL Injection 경로 트래버설 관련 논의는 Plan 범위 밖. 단, §2.2 변경 금지 목록은 적절하다.
|
||||
|
||||
> **STEP 5 결론**: Plan의 가장 큰 위험은 §3.3의 사실 오류(hist.js·trend.js 폴백 주장)와 Phase 1 우선순위가 위험도보다 작업량 중심인 점. §6 grep 명령어도 `new \{[^}]*`로는 **다중 라인 익명객체**를 잡지 못하는 한계가 있다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 6 — 교차 검증
|
||||
|
||||
### Q1. 이미 수정된 문제인가?
|
||||
|
||||
| 지적 사항 | 수정 여부 | 판단 |
|
||||
|-----------|----------|------|
|
||||
| §3.3 hist.js/trend.js 폴백 주장 오류 | **본 진단 작성 시점에 수정 완료** | 진단 항목에서 **제거** (현재는 폴백 있음) |
|
||||
| §3.1 parse 소문자 잔존 | 미수정 | **🔴 HIGH — 유지** |
|
||||
| Phase 1 우선순위 작업량 중심 | N/A (Plan 설계 결정) | **🟡 LOW — 제안** |
|
||||
| 응답 형식 파편화 | 미수정 (Phase 1에서 처리 예정) | **유지** |
|
||||
|
||||
### Q2. 다른 레이어에서 처리되는가?
|
||||
|
||||
| 항목 | 레이어 처리 | 판단 |
|
||||
|------|-----------|------|
|
||||
| parse 소문자 문제 | JS에서 폴백 없이 직접 PascalCase 읽음 — 우회 없음 | **🔴 HIGH** |
|
||||
| hist.js/trend.js | 현재는 폴백 추가되어 우회됨 | **해소됨** |
|
||||
|
||||
### Q3. 의도적 설계인가?
|
||||
|
||||
| 항목 | 의도적? | 판단 |
|
||||
|------|---------|------|
|
||||
| Plan의 §3.3 오류 | 의도적 아님 — 검증 생략으로 인한 실수 | **보고** |
|
||||
| Phase 1 작업량 중심 우선순위 | 의도적 — 문서에 별도 위험 평가 없음 | **개선 제안** |
|
||||
| §4 PascalCase 규약 | 의도적 — 문서 §1.3에서 근거 제시 | **수용** |
|
||||
|
||||
### Q4. 재현 시나리오?
|
||||
|
||||
| 항목 | 재현 시나리오 | 판단 |
|
||||
|------|-------------|------|
|
||||
| parse 소문자 | Text-to-SQL 탭에서 SQL 질문 입력 → "변환" 버튼 클릭 → "SQL 변환 실패: 알 수 없는 오류" 표시. 브라우저 콘솔에서 `res.Success`가 `undefined`임 확인 가능 | **🔴 HIGH** |
|
||||
| hist.js (수정 전) | 이력 탭 → 간격 조회 → 빈 테이블 | **수정됨** |
|
||||
|
||||
> **STEP 6 결론**: 교차 검증 후 **1건 🔴 HIGH** (parse 소문자)와 **1건 🟡 LOW** (Phase 1 우선순위 개선 제안)가 남았다. §3.3의 hist.js/trend.js 오류는 현재 수정되어 문서에 반영할 필요가 없으나, **문서의 신뢰성에 영향을 준 사례로 기록**한다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 7 — 심각도 분류 및 STEP 8 — 보고서
|
||||
|
||||
### 🔴 HIGH 1. TextToSqlController.parse() 응답 케이싱 불일치
|
||||
|
||||
**문제**: `POST /api/text-to-sql/parse`가 camelCase `{ success, sql, error }`를 반환하지만, `t2s.js`의 3개 호출 지점(§3.2 확인)이 전부 PascalCase `res.Success`·`res.Sql`·`res.Error`를 읽는다. t2s.js는 이미 PascalCase로 올바르게 작성되어 있으므로 **백엔드만 수정**하면 된다.
|
||||
|
||||
**근거**: `TextToSqlController.cs:44` — `return Ok(new { success = true, sql });` 대 `t2s.js:118-120, 428-432, 592-598` — `res.Success`, `parseRes.Sql`.
|
||||
|
||||
**영향**: Text-to-SQL 탭에서 "변환" 버튼 클릭 시 항상 "SQL 변환 실패: 알 수 없는 오류" 메시지. LLM 챗의 API 채팅/T2S 채팅에서도 동일. 실제 SQL 생성은 정상(로깅 가능)이나 **UI가 사용자에게 결과를 전달하지 못함**.
|
||||
|
||||
**수정 방향**: `TextToSqlController.cs:44,48`에서 `new { success = true, sql }` → `new { Success = true, Sql = sql }`로 변경. 이 수정은 즉시 전체 3개 호출 지점을 동시에 복구한다(Plan §5.0 Phase 0에 명시된 수정과 동일).
|
||||
|
||||
### 🟡 LOW 2. Phase 1 우선순위 — 작업량 vs 위험도 불일치
|
||||
|
||||
**문제**: Plan §5 Phase 1은 익명객체 라인 수 오름차순(1→2→5→…→44)으로 컨트롤러를 정렬했다. 이는 **작업량 중심** 순서로, **위험도 중심이 아니다**. 예를 들어 `OllamaController`(22줄)는 외부 API payload(변경 금지)와 내부 응답(변경 대상)이 혼재되어 있어 변경 시 리스크가 높지만, `KbController`(33줄)보다 먼저 배치되었다.
|
||||
|
||||
**근거**: Plan §5 Phase 1의 순서에 위험도 평가나 난이도 표시가 없음.
|
||||
|
||||
**영향**: OllamaController에서 실수로 외부 payload(`model`, `messages`, `stream`, `tools` 등 §2.2 변경 금지 키)를 건드리면 **LLM 채팅 전체 장애**. 작업자가 변경 금지 목록(§2.2)과 대조하며 작업해야 함.
|
||||
|
||||
**수정 제안**: Phase 1에 **리스트 업데이트** 주석 추가 — `OllamaController`는 §2.2 변경 금지 혼재로 **위험도: HIGH** 표시. 난이도·위험도에 따라 재정렬하거나, 각 컨트롤러 옆에 ⚠️ 표시 추가.
|
||||
|
||||
### 🟡 LOW 3. §3.3 사실 오류 (이미 수정 — 기록용)
|
||||
|
||||
**문제**: Plan §3.3은 "hist.js·trend.js에 `d.Success ?? d.success` 폴백 적용됨"이라고 주장하나, 실제 코드에는 폴백이 없었다.
|
||||
|
||||
**영향**: 문서 신뢰성 저하. Plan을 읽고 작업 순서를 결정하는 실행자가 hist.js·trend.js를 "이미 완료"로 오인할 수 있다.
|
||||
|
||||
**조치**: §3.3은 별도 수정 없이 본 진단 보고서를 문서 상단에 삽입함으로써 사실 관계를 정정한다. hist.js·trend.js는 본 진단 과정에서 dual-casing 폴백을 추가 완료.
|
||||
|
||||
---
|
||||
|
||||
## STEP 8 자가 검증
|
||||
|
||||
- [x] 각 지적 사항을 "현재 파일 몇 번 줄"로 직접 가리킬 수 있는가? — TextToSqlController.cs:44, t2s.js:118·432·592
|
||||
- [x] HIGH 항목은 재현 가능한 시나리오를 한 문장으로 말할 수 있는가? — Text-to-SQL 탭에서 질문 입력 → "변환" 버튼 → "SQL 변환 실패" 메시지
|
||||
- [x] 교차 검증 4개 질문을 모두 통과한 항목만 포함되었는가? — §3.3 항목은 Q1(이미 수정)으로 본 항목에서 제외, 기록용으로만 기재
|
||||
- [x] 보고서의 수정 예시가 현재 코드에 아직 적용되지 않은 내용인가? — parse 수정은 Plan §5.0에 명시되어 있으나 아직 적용되지 않음
|
||||
- [x] "더 좋은 방법 제안"과 "현재 코드가 틀렸다"를 혼동하지 않았는가? — Phase 1 우선순위는 "개선 제안"으로 명시
|
||||
|
||||
---
|
||||
|
||||
# 작업지시서 — 내부 REST API 필드명 PascalCase 전면 통일
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 작성일 | 2026-06-11 |
|
||||
| 대상 | `src/Hc900Crawler` 백엔드 컨트롤러 응답·요청 DTO + `wwwroot/js/*.js` 프론트엔드 |
|
||||
| 목적 | 백엔드↔프론트 JSON 필드 케이싱 불일치로 인한 무음 버그(silent failure) 제거 |
|
||||
| 트리거 | 커밋 `dbad4a5`(PascalCase 부분 통일)이 `TextToSqlController`만 바꾸고 일부 소비자(`llmchat.js`, `parse` 엔드포인트)를 누락 → LLM 채팅에서 도구가 vLLM에 전달되지 않아 도구 호출이 텍스트로 노출되는 버그 발생 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 배경 — 왜 이 작업이 필요한가
|
||||
|
||||
### 1.1 근본 메커니즘
|
||||
|
||||
`Program.cs`의 JSON 직렬화 설정:
|
||||
|
||||
```csharp
|
||||
builder.Services.AddControllers()
|
||||
.AddJsonOptions(opt =>
|
||||
{
|
||||
opt.JsonSerializerOptions.PropertyNamingPolicy = null; // ← C# 속성명을 그대로 직렬화
|
||||
opt.JsonSerializerOptions.PropertyNameCaseInsensitive = true; // ← 역직렬화(요청)는 케이싱 무시
|
||||
});
|
||||
```
|
||||
|
||||
- **`PropertyNamingPolicy = null`** → C# 코드에 쓴 속성명이 **그대로** JSON 필드명이 된다.
|
||||
- `return Ok(new { success = true })` → `{"success": true}` (소문자)
|
||||
- `return Ok(new { Success = true })` → `{"Success": true}` (PascalCase)
|
||||
- **`PropertyNameCaseInsensitive = true`** → **요청 바디**(C#가 받는 쪽)는 케이싱을 무시하고 바인딩한다. 따라서 **요청 방향은 깨지지 않는다.** 문제가 되는 것은 항상 **응답 방향**(JS가 읽는 쪽)이다.
|
||||
|
||||
### 1.2 현재 상태 — "우연히 동작 중"
|
||||
|
||||
현재 시스템은 **대부분의 컨트롤러가 소문자 익명객체(`new { success, data, error }`)를 반환**하고, **프론트도 소문자(`d.success`, `d.data`)로 읽어** 우연히 일치한다. 즉 사실상 컨벤션은 **camelCase/소문자**다.
|
||||
|
||||
커밋 `dbad4a5`가 `TextToSqlController`만 PascalCase로 바꾸면서 **컨벤션이 둘로 갈라졌고**, 한쪽만 바뀐 지점에서 `undefined` 무음 버그가 발생했다.
|
||||
|
||||
### 1.3 결정
|
||||
|
||||
> **전체를 PascalCase로 통일한다.** (커밋 `dbad4a5`의 방향을 끝까지 밀어붙임)
|
||||
|
||||
소문자로 되돌리는 선택지도 있으나, 이미 `TextToSqlController` + `t2s.js`가 PascalCase로 전환됐고 C# 속성/DTO는 본래 PascalCase가 관례이므로 **PascalCase 통일이 코드 일관성·유지보수 측면에서 우월**하다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 범위 — 무엇을 바꾸고 무엇을 절대 건드리지 않는가
|
||||
|
||||
### 2.1 ✅ 변경 대상 (PascalCase로 통일)
|
||||
|
||||
**Hc900Crawler 자체 REST API의 응답/요청 JSON 필드명** — 즉 `Controllers/*.cs`가 만들어 `wwwroot/js/*.js`가 소비하는 우리 내부 계약.
|
||||
|
||||
- 컨트롤러 익명객체 응답: `new { success = … }` → `new { Success = … }`
|
||||
- 응답 DTO 클래스 속성: 이미 PascalCase면 OK, `[JsonPropertyName("snake")]`로 강제 소문자된 내부 DTO는 검토 후 제거
|
||||
- 프론트의 응답 필드 읽기: `d.success` → `d.Success` 등
|
||||
|
||||
### 2.2 ⛔ 절대 변경 금지 (외부 계약·고정 식별자)
|
||||
|
||||
아래는 **외부 시스템·표준·저장소**와의 계약이므로 케이싱을 바꾸면 즉시 깨진다. **반드시 그대로 유지**한다.
|
||||
|
||||
| 영역 | 예시 필드 | 이유 |
|
||||
|---|---|---|
|
||||
| **vLLM / OpenAI API** | `model`, `messages`, `role`, `content`, `stream`, `tools`, `tool_choice`, `tool_calls`, `function`, `arguments`, `finish_reason`, `choices`, `delta`, `max_tokens`, `temperature` | OpenAI 호환 API 스펙. `OllamaController`의 vLLM 호출 payload/파싱 전부 |
|
||||
| **Ollama API** | `/api/chat`, `/api/tags`, `/api/show`, `name`, `model`, `system`, `messages`, `capabilities`, `done`, `response` | Ollama 네이티브 API 스펙 |
|
||||
| **MCP JSON-RPC** | `jsonrpc`, `method`, `params`, `name`, `arguments`, `tools`, `inputSchema`, `content` | MCP 프로토콜 스펙. `McpClient.cs`의 `[JsonPropertyName]`은 **유지** |
|
||||
| **MCP 도구 결과 데이터** | `base_tag`, `tag_name`, `event_type`, `recorded_at`, `livevalue`, `pv`, `sp`, `op`, `area`, `sub_area` 등 | Python `mcp-server/server.py`가 반환하는 snake_case 데이터. SQL 컬럼명과 동일. 프론트는 `r[col]`로 **동적 렌더**하므로 키 이름을 바꾸면 server.py + SQL까지 연쇄 변경 필요 → **이번 작업 범위 밖** |
|
||||
| **DB 컬럼 / SQL 식별자** | 모든 `snake_case` 컬럼 | PostgreSQL 스키마 |
|
||||
| **SSE 스트리밍 내부 shape** | `{ message: { content } }`, `json.response`, `event: message/tool_start/tool_result/done` | LLM 스트리밍 API shape를 그대로 미러링. `OllamaController` ↔ `llmchat.js` 양쪽이 동일 규약. **이 shape는 LLM API 정렬을 위해 소문자 유지** (단 `tool_start`/`tool_result`의 `id/name/ok/preview/payload`는 우리 내부 필드 → 선택적 PascalCase 가능하나 양쪽 동시 변경 필수) |
|
||||
| **localStorage 키** | `llmSessions`, `llmType` 등 | 브라우저 저장 키, JSON 필드 아님 |
|
||||
| **HTTP 헤더** | `X-Kb-Token`, `Content-Type` 등 | 표준/관례 |
|
||||
|
||||
> **판단 기준**: "이 필드가 우리 C# ↔ 우리 JS 사이에서만 오가는가?" → **Yes면 PascalCase 변경 대상**, 외부(LLM/MCP/DB/브라우저)와 닿으면 **제외**.
|
||||
|
||||
---
|
||||
|
||||
## 3. 현황 인벤토리 (전수 조사 결과)
|
||||
|
||||
### 3.1 백엔드 — 컨트롤러별 소문자 익명객체 응답 (변경 필요)
|
||||
|
||||
| 컨트롤러 | 익명객체 응답 라인 수 | 비고 |
|
||||
|---|---:|---|
|
||||
| `FeedforwardController.cs` | 44 | `new { error }`, `new { success }` 다수 |
|
||||
| `KbController.cs` | 33 | |
|
||||
| `PidController.cs` | 32 | |
|
||||
| `PointBuilderController.cs` | 29 | 최근 작업 영역, 주의 |
|
||||
| `OllamaController.cs` | 22 | ⚠️ **외부(vLLM/Ollama) payload와 내부 응답 혼재** — 내부 응답만 선별 변경 |
|
||||
| `TextToSqlController.cs` | 19 | 🔶 **부분 완료**. `parse`(L38–50)만 소문자 잔존 → 즉시 수정 대상 |
|
||||
| `SteamAdvisorController.cs` | 15 | |
|
||||
| `DocsController.cs` | 13 | |
|
||||
| `TrendController.cs` | 13 | |
|
||||
| `Hc900Controllers.cs` | 10 | |
|
||||
| `KbAuthController.cs` | 9 | `token`, `expiresAt` 포함 |
|
||||
| `SetupController.cs` | 7 | |
|
||||
| `FastController.cs` | 5 | |
|
||||
| `PidGraphController.cs` | 2 | `error`, `details` |
|
||||
| `HypertableController.cs` | 1 | |
|
||||
|
||||
### 3.2 프론트엔드 — 응답 필드를 소문자로 읽는 JS (변경 필요)
|
||||
|
||||
전 `wwwroot/js/*.js`(벤더 `xlsx.full.min.js` 제외)가 대상. 탭 ↔ 파일 ↔ 주 컨트롤러 매핑:
|
||||
|
||||
| 탭/기능 | JS 파일 | 주 컨트롤러 |
|
||||
|---|---|---|
|
||||
| LLM 채팅 | `llmchat.js` (1031) | `OllamaController`, `TextToSqlController(/tools)` 🔶**부분완료** |
|
||||
| Text-to-SQL | `t2s.js` (740) | `TextToSqlController` 🔶**부분완료**(단 `parse` 응답 불일치 잔존) |
|
||||
| 트렌드 | `trend.js` (810) | `TrendController`, `TextToSqlController(/query-history-interval)` ※ 폴백 있음 |
|
||||
| 스팀 어드바이저 | `steam.js` (811) | `SteamAdvisorController` |
|
||||
| P&ID | `pid.js` (737), `pid-viewer.js` (416) | `PidController`, `PidGraphController` |
|
||||
| 피드포워드 | `ff.js` (739) | `FeedforwardController` |
|
||||
| 문서 | `docs.js` (713) | `DocsController` |
|
||||
| Point Builder | `pb.js` (546) | `PointBuilderController` |
|
||||
| Fast(고속) | `fast.js` (499) | `FastController` |
|
||||
| KB 관리 | `kbadmin.js` (397) | `KbController`, `KbAuthController` |
|
||||
| 이력 | `hist.js` (383) | `TextToSqlController(/query-history-interval)` ※ 폴백 있음 |
|
||||
| 설정 | `setup.js` (278) | `SetupController` |
|
||||
| 공통 | `core.js` (215), `app.js` (7) | (api 헬퍼) |
|
||||
| 쓰기 | `write.js` (101) | `Hc900Controllers` |
|
||||
| 이벤트 | `evt.js` (144) | `Hc900Controllers` |
|
||||
|
||||
> `hist.js`·`trend.js`는 `query-history-interval` 읽을 때 이미 `d.Success ?? d.success` 폴백이 적용돼 있어 현재 동작. 통일 작업 시 폴백 제거하고 PascalCase 단일화.
|
||||
|
||||
### 3.3 이미 적용된 수정 (작업 트리, 미커밋)
|
||||
|
||||
이번 디버깅 중 선행 적용된 변경 — 본 작업에 흡수/계승:
|
||||
|
||||
- `llmchat.js`: `/tools` 응답 `d.Success ?? d.success`, `d.Tools ?? d.tools` 폴백 추가 ✅
|
||||
- `IMcpService.cs` / `McpService.cs`: `McpToolDto`에 `InputSchema` 추가·전달 ✅
|
||||
- `OllamaController.cs`: Python 스타일 텍스트 도구 호출 감지 폴백 ✅ (방어선, 유지)
|
||||
- `prompts/plant_context.md`: 도구 예시의 Python 함수 문법 제거 ✅
|
||||
|
||||
---
|
||||
|
||||
## 4. 표준 규약 (확정)
|
||||
|
||||
1. **내부 REST 응답 필드명 = PascalCase.** `Success`, `Error`, `Data`, `Message`, `Sql`, `Tools`, `Rows`, `Columns`, `Count`, `TagNames`, `Items`, `Id`, `Path`, `Token`, `ExpiresAt` 등.
|
||||
2. **공통 봉투(envelope) 통일**: 모든 응답은 최상위에 `Success`(bool)를 갖는다. 실패 시 `Error`(string). 데이터는 `Data` 또는 의미있는 명사형 PascalCase 키.
|
||||
3. **요청 바디**는 `PropertyNameCaseInsensitive = true` 덕에 케이싱 무관하나, **신규/수정 시 JS도 PascalCase 키로 전송**해 일관성 유지(`{ Sql, Limit }`). 단 외부 스펙 키는 예외.
|
||||
4. **응답 DTO 클래스**의 내부용 `[JsonPropertyName("snake")]`는 제거(자연 PascalCase 직렬화). **외부 계약용 `[JsonPropertyName]`은 유지** (McpClient, Ollama/vLLM payload, ExperionDtos 등 — §2.2).
|
||||
5. **폴백 금지**: 통일 완료 후 `?? d.success` 같은 이중 케이싱 폴백은 모두 제거(한 가지 진실).
|
||||
|
||||
---
|
||||
|
||||
## 5. 실행 계획 (단계별)
|
||||
|
||||
> 원칙: **엔드포인트 단위로 백엔드+프론트를 짝지어** 변경하고 즉시 검증. 빅뱅 일괄 변경 금지(무음 버그 재발 위험).
|
||||
|
||||
### Phase 0 — 즉시 핫픽스 (이미 진행 중, 우선 머지)
|
||||
|
||||
- [x] `llmchat.js` `/tools` 케이싱 폴백
|
||||
- [x] `McpToolDto.InputSchema` 추가
|
||||
- [ ] **`TextToSqlController.parse`(L38–50) 응답을 PascalCase로** (`success/sql/error` → `Success/Sql/Error`). t2s.js는 이미 `parseRes.Sql` 기대 → 이 한 줄이 t2s 파싱 버튼·채팅 흐름 2곳을 복구.
|
||||
|
||||
### Phase 1 — 컨트롤러별 백엔드 통일
|
||||
|
||||
각 컨트롤러에서 익명객체 소문자 키 → PascalCase. 작은 것부터:
|
||||
|
||||
- [ ] `HypertableController.cs` (1)
|
||||
- [ ] `PidGraphController.cs` (2)
|
||||
- [ ] `FastController.cs` (5)
|
||||
- [ ] `SetupController.cs` (7)
|
||||
- [ ] `KbAuthController.cs` (9) — `token`, `expiresAt` 포함
|
||||
- [ ] `Hc900Controllers.cs` (10)
|
||||
- [ ] `TrendController.cs` (13)
|
||||
- [ ] `DocsController.cs` (13)
|
||||
- [ ] `SteamAdvisorController.cs` (15)
|
||||
- [ ] `TextToSqlController.cs` (19) — 잔여 `parse` 외 전수 점검
|
||||
- [ ] `OllamaController.cs` (22) — ⚠️ **vLLM/Ollama payload·SSE shape는 제외**, `config/ping/models`의 내부 응답만
|
||||
- [ ] `PointBuilderController.cs` (29)
|
||||
- [ ] `PidController.cs` (32)
|
||||
- [ ] `KbController.cs` (33)
|
||||
- [ ] `FeedforwardController.cs` (44)
|
||||
|
||||
### Phase 2 — 프론트엔드 통일 (대응 JS)
|
||||
|
||||
각 컨트롤러 변경 직후 짝 JS를 함께 수정·검증(§3.2 매핑 순서대로). 폴백 제거.
|
||||
|
||||
### Phase 3 — DTO 정리
|
||||
|
||||
- [ ] `[JsonPropertyName]` 71곳 전수 검토 → 내부용 제거, 외부용 유지(주석으로 "// 외부 계약" 명시).
|
||||
- [ ] 응답 DTO들이 PascalCase로 직렬화되는지 확인.
|
||||
|
||||
### Phase 4 — 회귀 검증 (탭별)
|
||||
|
||||
§7 체크리스트 전 탭 수행.
|
||||
|
||||
---
|
||||
|
||||
## 6. 작업자 가이드 — 탐색/치환 도구
|
||||
|
||||
### 6.1 소문자 응답 잔존 탐지 (백엔드)
|
||||
|
||||
```bash
|
||||
# 익명객체에서 소문자로 시작하는 필드 = 변경 후보
|
||||
grep -rnE "new \{[^}]*\b[a-z][a-zA-Z]* =" src/Hc900Crawler/Controllers/
|
||||
|
||||
# 단, 외부 payload 키(model, messages, stream, tools 등)는 제외 — 수동 판별
|
||||
```
|
||||
|
||||
### 6.2 소문자 응답 읽기 탐지 (프론트)
|
||||
|
||||
```bash
|
||||
# d.success 류 소문자 응답 접근 (data 행의 snake_case 컬럼 r[col]은 제외)
|
||||
grep -rnE "\.(success|error|data|sql|tools|rows|columns|count|tagNames|message|token|expiresAt|items|path|reply|summary|suggestions)\b" \
|
||||
src/Hc900Crawler/wwwroot/js/ | grep -v xlsx.full.min.js
|
||||
```
|
||||
|
||||
### 6.3 변경 금지 식별 (이 키들이 보이면 손대지 말 것)
|
||||
|
||||
```
|
||||
model messages role content stream tools tool_choice tool_calls function
|
||||
arguments finish_reason choices delta max_tokens temperature ← vLLM/OpenAI
|
||||
name capabilities done response system ← Ollama
|
||||
jsonrpc method params inputSchema ← MCP
|
||||
base_tag tag_name event_type recorded_at livevalue pv sp op ← DB/도구 데이터
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 검증 체크리스트 (탭별 회귀 — 완료 기준)
|
||||
|
||||
각 항목: 실제 UI에서 동작 + 브라우저 콘솔에 `undefined` 접근/에러 없음.
|
||||
|
||||
- [ ] **LLM 채팅**: 도구 사용+에이전트 모드 ON → `generate_status_report(area="P6-1", hours=24)` 류 질문 시 **도구 카드가 실행**되고 자연어 답변(텍스트로 함수명 노출 ❌)
|
||||
- [ ] **LLM 채팅**: 모델 목록 로드, 핑, 설정 저장
|
||||
- [ ] **Text-to-SQL**: NL 질의(`query-nl`), **SQL 변환(`parse`) 버튼**, MCP 실행(`execute-mcp`), 분석(`analyze`), 도구 칩(`tools`)
|
||||
- [ ] **이력(hist)**: 간격 조회 결과 테이블 렌더 (폴백 제거 후에도 정상)
|
||||
- [ ] **트렌드**: 집계/원시 경로 차트 + 이벤트/리밋/런밴드 레이어
|
||||
- [ ] **스팀 어드바이저**: 어드바이스 로드/적용
|
||||
- [ ] **P&ID / P&ID 뷰어**: 도면 목록·그래프
|
||||
- [ ] **피드포워드**: 어드바이저 상태/SP 쓰기(WriteGuard 메시지 포함)
|
||||
- [ ] **문서**: 목록/열람/업로드/이동/삭제(관리자)
|
||||
- [ ] **Point Builder**: 태그 추가/제거/미리보기
|
||||
- [ ] **Fast**: 고속 조회
|
||||
- [ ] **KB 관리**: 로그인(`token`/`expiresAt`), 검색, 업로드, 비밀번호 변경
|
||||
- [ ] **설정**: 저장/연결 테스트
|
||||
- [ ] **쓰기(write)/이벤트(evt)**: 값 쓰기, 이벤트 목록
|
||||
|
||||
### 빌드/정적 점검
|
||||
|
||||
```bash
|
||||
cd src/Hc900Crawler && dotnet build # 0 Error
|
||||
# §6.1/§6.2 grep 결과가 외부 키만 남았는지 확인 (내부 소문자 0건)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 리스크 및 주의사항
|
||||
|
||||
1. **무음 실패가 본질**: 케이싱 불일치는 컴파일·런타임 에러 없이 `undefined`로 흐른다. **반드시 실제 UI 클릭 검증**. 빌드 통과 ≠ 동작.
|
||||
2. **OllamaController 혼재**: 한 파일 안에 (a) vLLM/Ollama로 나가는 외부 payload, (b) SSE 스트리밍 shape, (c) 우리 내부 응답(`config`,`ping`,`models`,`vllmModel`)이 섞여 있다. **(c)만** 변경. (a)(b)는 §2.2.
|
||||
3. **MCP 도구 결과 테이블**: `llmRenderTable`/`t2sRenderTable`은 `r[col]`로 snake_case 컬럼을 동적 렌더 → **건드리지 말 것**. server.py·SQL 동반 변경은 별도 작업.
|
||||
4. **폴백의 함정**: 전환 중간 상태에서 `?? d.success` 폴백을 남기면 버그가 숨는다. Phase 완료 시 폴백 **제거**가 완료 기준.
|
||||
5. **요청 바디**: `PropertyNameCaseInsensitive=true`라 깨지지 않지만, 일관성 위해 JS 송신 키도 PascalCase로(외부 스펙 제외).
|
||||
6. **부분 머지 금지**: 컨트롤러만 PascalCase로 머지하고 JS를 안 바꾸면 그 탭이 죽는다. **백엔드+프론트 짝 커밋**.
|
||||
|
||||
---
|
||||
|
||||
## 9. 산출물 / 완료 정의 (DoD)
|
||||
|
||||
- [ ] 내부 REST 응답·요청 필드가 전부 PascalCase, 이중 케이싱 폴백 0건
|
||||
- [ ] 외부 계약(vLLM/Ollama/MCP)·DB 컬럼·SSE shape는 불변
|
||||
- [ ] `dotnet build` 0 Error
|
||||
- [ ] §7 전 탭 UI 회귀 통과(콘솔 에러 0)
|
||||
- [ ] §6.1/§6.2 grep에 내부 소문자 잔존 0건(외부 키만)
|
||||
- [ ] 변경은 엔드포인트 단위 백엔드+프론트 짝으로 커밋
|
||||
|
||||
---
|
||||
|
||||
*근거 조사: `Program.cs`(JSON 정책), `Controllers/*.cs` 15개, `wwwroot/js/*.js` 17개, `McpClient.cs`/`McpService.cs`, 커밋 `dbad4a5`. 본 문서는 단일 작업 기준서이며, 완료 후 삭제 대상(`chore` 정리).*
|
||||
Reference in New Issue
Block a user