Files
HC900-Crawler/docs/작업지시서-API-필드-PascalCase-통일.md
windpacer aa2174118a docs: 작업지시서(PascalCase 통일)·AI운전원 제어 논의메모·HC900 IO 데이터시트 추가
- 작업지시서-API-필드-PascalCase-통일.md: PascalCase 통일 작업 지시서.
- 논의-AI운전원-제어-아이디어.md: AI 운전원 제어 아이디어 논의 메모.
- ControlEdge HC900 IO Modules Specifications.pdf: RTD/AI 모듈 스펙(레인지·정확도)
  데이터시트 — PT100 양자화 분석 근거 자료.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 12:33:32 +09:00

44 KiB
Raw Blame History

구현 검증 보고서 — 작업지시서-API-필드-PascalCase-통일

검증일: 2026-06-11
검증 대상: 작업지시서 §5 Phase 03 구현 결과
검증 방법: 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 0TextToSqlController.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

// 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.valuept.Value 미변경 라이브 틱 데이터가 차트에 반영 안 됨
L676 p.Descp.Description (DTO 속성명 불일치) 그룹 빌더 설명 필드 undefined
L751 g.membersg.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.successres.Success L135, 370, 434, 469
res.errorres.Error L143, 434, 470
res.messageres.Message L84, 100, 349, 362
res.countres.Count L84, 100, 111, 112, 238, 349, 362
res.itemsres.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

[JsonPropertyName("analogmon1")]   // ← 미사용 DTO. PointBuilderBuildDto 자체가 dead code
public PointBuilderGroupDto AnalogMonitor1 { get; set; } = new();

Hc900PointBuilderBuildDto(같은 파일 L106-113)가 실제 사용 중이므로, 위 클래스 전체 삭제 권장.


추가 발견: TrendDtos.csTrendMemberDto [JsonPropertyName] 3개 제거됨

// 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.membersg.Members로, m.tagm.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.membersg.Members L751 — TrendGroupDto.Members는 PascalClass, API는 "Members", JS는 g.membersundefined. 그룹 로딩 전면 실패
[x] trend.js m.tagm.Tag L752-758 — TrendMemberDto.Tag/Color/Axis가 PascalCase인데 JS가 소문자로 읽음
[x] trend.js p.Descp.Description L676 — AnalogPointDto.DescriptionDesc로 오기입
[x] trend.js pt.valuept.Value L659 — TrendLivePointDto.Value인데 소문자 pt.valueundefined
[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.membersg.Members, L752-758 m.tagm.Tag, m.colorm.Color, m.axism.Axis. L676 p.Descp.Description. L659 pt.valuept.Value. L755-758 .desc.Desc, .unit.Unit, .euLo.EuLo, .euHi.EuHi. L698-708 p.tagNamep.TagName, p.descriptionp.Description, p.areap.Area, p.valuep.Value, p.unitp.Unit, p.euLop.EuLo, p.euHip.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 자가 검증

  • 각 지적 사항을 "현재 파일 몇 번 줄"로 직접 가리킬 수 있는가? — trend.js L659·L676·L698-708·L751-758 등
  • HIGH 항목은 재현 가능한 시나리오를 한 문장으로 말할 수 있는가? — 트렌드 탭에서 그룹 선택 시 빈 차트, 그룹 빌더에서 모든 필드 undefined
  • 교차 검증 4개 질문을 모두 통과한 항목만 포함되었는가? — 통과
  • 보고서의 수정 예시가 현재 코드에 아직 적용되지 않은 내용인가? — 적용되지 않음 (현재 버그 상태임)
  • 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.csPropertyNamingPolicy = null 설정 아래에서 C# 코드의 속성명이 JSON으로 그대로 직렬화됨을 전제로, 15개 컨트롤러와 17개 JS 파일을 대상으로 카탈로그화·단계별 전환을 정의한다. 트리거는 커밋 dbad4a5TextToSqlController만 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)

// 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)

// 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.jsquery-history-interval 읽을 때 이미 d.Success ?? d.success 폴백이 적용돼 있어 현재 동작.

그러나 실제 코드는 폴백이 없었다 (본 진단 작성자가 수정하기 전):

// hist.js L122-127 (수정 전) — 폴백 없음
if (!d.success) { throw new Error(d.error || '조회 실패'); }
const rows  = d.rows || [];       // API는 { Rows, TagNames } (PascalCase) 반환
const tNames = d.tagNames || [];
// 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.Successundefined임 확인 가능 🔴 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:44return Ok(new { success = true, sql });t2s.js:118-120, 428-432, 592-598res.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 자가 검증

  • 각 지적 사항을 "현재 파일 몇 번 줄"로 직접 가리킬 수 있는가? — TextToSqlController.cs:44, t2s.js:118·432·592
  • HIGH 항목은 재현 가능한 시나리오를 한 문장으로 말할 수 있는가? — Text-to-SQL 탭에서 질문 입력 → "변환" 버튼 → "SQL 변환 실패" 메시지
  • 교차 검증 4개 질문을 모두 통과한 항목만 포함되었는가? — §3.3 항목은 Q1(이미 수정)으로 본 항목에서 제외, 기록용으로만 기재
  • 보고서의 수정 예시가 현재 코드에 아직 적용되지 않은 내용인가? — parse 수정은 Plan §5.0에 명시되어 있으나 아직 적용되지 않음
  • "더 좋은 방법 제안"과 "현재 코드가 틀렸다"를 혼동하지 않았는가? — 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 직렬화 설정:

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/소문자다.

커밋 dbad4a5TextToSqlController만 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.successd.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를 그대로 미러링. OllamaControllerllmchat.js 양쪽이 동일 규약. 이 shape는 LLM API 정렬을 위해 소문자 유지 (단 tool_start/tool_resultid/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(L3850)만 소문자 잔존 → 즉시 수정 대상
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.jsquery-history-interval 읽을 때 이미 d.Success ?? d.success 폴백이 적용돼 있어 현재 동작. 통일 작업 시 폴백 제거하고 PascalCase 단일화.

3.3 이미 적용된 수정 (작업 트리, 미커밋)

이번 디버깅 중 선행 적용된 변경 — 본 작업에 흡수/계승:

  • llmchat.js: /tools 응답 d.Success ?? d.success, d.Tools ?? d.tools 폴백 추가
  • IMcpService.cs / McpService.cs: McpToolDtoInputSchema 추가·전달
  • 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 — 즉시 핫픽스 (이미 진행 중, 우선 머지)

  • llmchat.js /tools 케이싱 폴백
  • McpToolDto.InputSchema 추가
  • TextToSqlController.parse(L3850) 응답을 PascalCase로 (success/sql/errorSuccess/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 소문자 응답 잔존 탐지 (백엔드)

# 익명객체에서 소문자로 시작하는 필드 = 변경 후보
grep -rnE "new \{[^}]*\b[a-z][a-zA-Z]* =" src/Hc900Crawler/Controllers/

# 단, 외부 payload 키(model, messages, stream, tools 등)는 제외 — 수동 판별

6.2 소문자 응답 읽기 탐지 (프론트)

# 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): 값 쓰기, 이벤트 목록

빌드/정적 점검

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/t2sRenderTabler[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 정리).