feat: ExperionCrawler IIoT OPC UA Data Bridge Infrastructure
Major project initialization and feature implementation: **Core Features:** - OPC UA client for Honeywell Experion HS R530 integration - Real-time data streaming and history data retrieval - Text-to-SQL query engine with TimeScaleDB - JSON-based node configuration system - SQLite database with migration support **Architecture:** - Clean architecture with Domain, Application, Infrastructure layers - ASP.NET Core Web API frontend - Web UI with real-time visualization - PKI-based OPC UA authentication (TLS) **Infrastructure Components:** - ExperionOpcClient: OPC UA connection management - ExperionRealtimeService: Real-time data streaming - ExperionHistoryService: Historical data queries - TextToSqlService: Natural language to SQL queries - SqlValidator: SQL injection prevention **Database:** - TimescaleDB integration (recommended) or SQLite fallback - Entity Framework Core with Extenstion methods - OPCTag, KeyValue tables for data storage **Security:** - Certificate-based OPC UA endpoint security - SSL/TLS encryption for database connections - Output param binding injection prevention **Testing:** - Unit tests for TextToSqlService and SqlValidator - Integration tests for Korean time range extraction See REVIEW_REQUEST.md for detailed code review information.
This commit is contained in:
2851
plans/Text-to-SQL plan by claude.md
Normal file
2851
plans/Text-to-SQL plan by claude.md
Normal file
File diff suppressed because it is too large
Load Diff
53
plans/claude-review-guide.md
Normal file
53
plans/claude-review-guide.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 클로드 코드 검수 가이드
|
||||
> GLM-4.7-Flash 수정 완료 후 Claude Code에서 실행
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — 변경 범위 파악
|
||||
|
||||
```bash
|
||||
git log --oneline # GLM이 남긴 fix 커밋 목록 확인
|
||||
git diff main HEAD # 전체 변경사항 한눈에 보기
|
||||
git diff main HEAD --stat # 파일별 변경 라인 수
|
||||
```
|
||||
|
||||
## Step 2 — REVIEW_REQUEST.md 읽기
|
||||
|
||||
GLM이 작성한 `REVIEW_REQUEST.md`를 열어
|
||||
- 수정 완료 목록과 우려사항 확인
|
||||
- 수정 보류(needs-review) 항목 특별 주의
|
||||
|
||||
## Step 3 — 파일별 diff 검토
|
||||
|
||||
```bash
|
||||
git show HEAD~N:src/파일.cs # 수정 전 원본
|
||||
git diff HEAD~1 -- src/파일.cs # 해당 커밋 단일 변경
|
||||
```
|
||||
|
||||
Claude Code에게 물어볼 것:
|
||||
- "이 변경이 기존 동작을 바꾸는가?"
|
||||
- "edge case가 있는가?"
|
||||
- "OPC UA 프로토콜 관점에서 올바른가?"
|
||||
|
||||
## Step 4 — 빌드 및 동작 확인
|
||||
|
||||
```bash
|
||||
dotnet build src/Web/ExperionCrawler.csproj
|
||||
dotnet test ExperionCrawler.Tests/ --no-build 2>/dev/null || echo "테스트 없음"
|
||||
```
|
||||
|
||||
## Step 5 — 판정
|
||||
|
||||
| 결과 | 처리 |
|
||||
|------|------|
|
||||
| 승인 | `git checkout main && git merge --no-ff fix/glm-review` |
|
||||
| 부분 승인 | 문제 커밋만 `git revert`, 나머지 병합 |
|
||||
| 거부 | `git reset --hard HEAD~N` 후 Claude Code가 직접 재수정 |
|
||||
|
||||
## needs-review 항목 처리
|
||||
|
||||
GLM이 판단 보류한 항목은 Claude Code가 직접 검토 후:
|
||||
```
|
||||
rag_query("해당 문제 설명", search_code=True) # MCP로 관련 코드 컨텍스트 확인
|
||||
```
|
||||
판단 후 직접 수정하거나 issues.md에 wont-fix 표시
|
||||
169
plans/glm-code-review-task.md
Normal file
169
plans/glm-code-review-task.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# ExperionCrawler 코드 분석 및 수정 태스크
|
||||
> Roo Code(GLM-4.7-Flash 모드)에 이 파일 내용을 그대로 붙여넣어 실행
|
||||
|
||||
---
|
||||
|
||||
## 지시사항
|
||||
|
||||
당신은 ExperionCrawler (.NET 8 C#, PostgreSQL/TimescaleDB, OPC UA) 프로젝트의
|
||||
코드 품질 담당 엔지니어입니다.
|
||||
아래 Phase 순서대로 작업하고, 각 단계 완료 시 `task_state.md`에 기록하세요.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — 분석: issues.md 생성
|
||||
|
||||
### 1-1. 분석 대상 파일 (우선순위 순)
|
||||
|
||||
**[HIGH 우선순위]**
|
||||
- `src/Infrastructure/Database/ExperionDbContext.cs`
|
||||
- `src/Infrastructure/OpcUa/ExperionRealtimeService.cs`
|
||||
- `src/Core/Application/Services/TextToSqlService.cs`
|
||||
- `src/Infrastructure/OpcUa/ExperionOpcServerService.cs`
|
||||
- `src/Infrastructure/OpcUa/ExperionOpcServerNodeManager.cs`
|
||||
|
||||
**[MED 우선순위]**
|
||||
- `src/Web/Controllers/ExperionControllers.cs`
|
||||
- `src/Web/Controllers/TextToSqlController.cs`
|
||||
- `src/Core/Application/Services/SqlValidator.cs`
|
||||
- `src/Core/Application/Services/KoreanTimeRangeExtractor.cs`
|
||||
- `src/Infrastructure/OpcUa/ExperionOpcClient.cs`
|
||||
|
||||
**[LOW 우선순위]**
|
||||
- `src/Core/Application/Interfaces/IExperionServices.cs`
|
||||
- `src/Core/Application/DTOs/ExperionDtos.cs`
|
||||
- `src/Core/Application/DTOs/TextToSqlDtos.cs`
|
||||
- `src/Web/Program.cs`
|
||||
- `src/Infrastructure/OpcUa/ExperionHistoryService.cs`
|
||||
|
||||
### 1-2. 각 파일에서 확인할 항목
|
||||
|
||||
```
|
||||
□ null 참조 예외 가능성 (NullReferenceException)
|
||||
□ async/await 오용 (deadlock, fire-and-forget 미처리)
|
||||
□ IDisposable 미해제 (DbContext, HttpClient, Connection 등)
|
||||
□ 예외 삼킴 (catch(Exception){} 빈 블록)
|
||||
□ CancellationToken 미전파
|
||||
□ SQL Injection 가능성 (raw string interpolation)
|
||||
□ 경쟁 조건 (Race condition) — 특히 ConcurrentDictionary, lock 누락
|
||||
□ 불필요한 await (Task.Result, .Wait() 블로킹)
|
||||
□ 메모리 누수 (이벤트 핸들러 미구독 해제)
|
||||
□ 하드코딩된 값 (IP, 포트, 문자열 상수)
|
||||
□ 도메인 로직 오류 (KST/UTC 변환, OPC UA 상태 코드 처리)
|
||||
```
|
||||
|
||||
### 1-3. MCP 도구 활용
|
||||
|
||||
각 파일 분석 시 다음을 활용하세요:
|
||||
```
|
||||
search_codebase("파일명 또는 핵심 패턴") → 관련 구현 컨텍스트 확인
|
||||
ask_iiot_llm("OPC UA 관련 판단이 필요한 경우") → 도메인 전문 판단
|
||||
```
|
||||
|
||||
### 1-4. 결과물
|
||||
|
||||
`issues.md` 파일을 프로젝트 루트에 생성하세요:
|
||||
|
||||
```markdown
|
||||
# ExperionCrawler 코드 이슈 목록
|
||||
> 생성일: YYYY-MM-DD | 분석 모델: GLM-4.7-Flash
|
||||
|
||||
## 요약
|
||||
- HIGH: N건 / MED: N건 / LOW: N건
|
||||
|
||||
## 이슈 목록
|
||||
|
||||
| # | 파일 | 라인 | 심각도 | 분류 | 문제 설명 | 수정 방향 | 상태 |
|
||||
|---|------|------|--------|------|-----------|-----------|------|
|
||||
| 1 | src/.../파일.cs | 42 | HIGH | bug | 설명 | 수정 방향 | pending |
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — 수정: HIGH → MED → LOW 순서
|
||||
|
||||
### 2-1. 수정 규칙
|
||||
|
||||
1. **한 번에 이슈 1개씩** 수정
|
||||
2. 수정 전: `read_file`로 현재 내용 확인
|
||||
3. 수정 후: `dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q` 빌드 확인
|
||||
4. 빌드 성공 시: `issues.md`에서 해당 이슈 상태를 `fixed`로 변경
|
||||
5. 빌드 실패 시: 즉시 원인 분석 후 수정, 다음 이슈로 넘어가지 않음
|
||||
|
||||
### 2-2. 수정 불가 판단 기준
|
||||
|
||||
아래 경우 수정하지 말고 `issues.md`에 `needs-review`로 표시하세요:
|
||||
- 아키텍처 변경이 필요한 경우
|
||||
- 비즈니스 로직 판단이 불명확한 경우
|
||||
- 테스트 없이 검증 불가한 경우
|
||||
|
||||
### 2-3. 각 이슈 수정 후 커밋
|
||||
|
||||
```bash
|
||||
git add [수정된 파일]
|
||||
git commit -m "fix(#N): [이슈 요약]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — 검수 요청서 작성
|
||||
|
||||
모든 수정 완료 후 `REVIEW_REQUEST.md`를 생성하세요:
|
||||
|
||||
```markdown
|
||||
# 클로드 코드 검수 요청
|
||||
|
||||
## 작업 요약
|
||||
- 분석 파일: N개
|
||||
- 발견 이슈: HIGH N / MED N / LOW N
|
||||
- 수정 완료: N건
|
||||
- 검수 필요(needs-review): N건
|
||||
|
||||
## 검수 항목
|
||||
|
||||
### ✅ 수정 완료 (확인 요청)
|
||||
| # | 파일:라인 | 수정 내용 | 우려사항 |
|
||||
|---|-----------|-----------|---------|
|
||||
...
|
||||
|
||||
### ⚠️ 수정 보류 (판단 요청)
|
||||
| # | 파일:라인 | 문제 | 보류 이유 |
|
||||
|---|-----------|------|-----------|
|
||||
...
|
||||
|
||||
## 빌드 상태
|
||||
- 최종 빌드: ✅ 성공 / ❌ 실패
|
||||
- 경고: N건
|
||||
|
||||
## 검수 방법
|
||||
\`\`\`bash
|
||||
git log --oneline # 수정 커밋 목록
|
||||
git diff HEAD~N # 전체 변경사항
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — task_state.md 최신화
|
||||
|
||||
작업 중 및 완료 시 `task_state.md`를 아래 형식으로 유지하세요:
|
||||
|
||||
```markdown
|
||||
## 작업명: ExperionCrawler 코드 분석 및 수정
|
||||
## 시작시각: YYYY-MM-DD HH:MM
|
||||
## 진행 상태: Phase N / 4
|
||||
|
||||
### Phase 1 완료 파일
|
||||
- [x] ExperionDbContext.cs → 이슈 N건 발견
|
||||
- [x] ExperionRealtimeService.cs → 이슈 N건 발견
|
||||
- [ ] TextToSqlService.cs
|
||||
|
||||
### Phase 2 수정 현황
|
||||
- [x] #1 (HIGH) ExperionDbContext.cs:42 → fixed
|
||||
- [ ] #2 (HIGH) ExperionRealtimeService.cs:156 → in-progress
|
||||
|
||||
### 발견된 이슈 누적
|
||||
| # | 파일 | 심각도 | 내용 |
|
||||
|---|------|--------|------|
|
||||
```
|
||||
102
plans/local-llm-chat-webpage-plan.md
Normal file
102
plans/local-llm-chat-webpage-plan.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 폐쇄 네트워크 로컬 LLM 채팅 웹페이지 제작 계획
|
||||
|
||||
## 1. 분석 요약
|
||||
|
||||
### Ollama (로컬 LLM 런타임)
|
||||
- **기본 API 엔드포인트**: `http://localhost:11434/api`
|
||||
- **OpenAI 호환 API**: `http://localhost:11434/v1/`
|
||||
- **주요 API**:
|
||||
- `POST /api/chat` - 채팅 (메시지 배열 기반)
|
||||
- `POST /api/generate` - 텍스트 생성 (프롬프트 기반)
|
||||
- `GET /api/tags` - 로컬 모델 목록 조회
|
||||
- `POST /api/pull` - 모델 다운로드 (폐쇄 네트워크에서는 사전 다운로드 필요)
|
||||
- **스트리밍 응답**: NDJSON 형식 (`application/x-ndjson`), `"stream": false`로 비활성화 가능
|
||||
- **모델**: 폐쇄 네트워크에서는 `ollama pull`로 사전 다운로드 필요 (예: `llama3`, `gemma3`, `qwen3` 등)
|
||||
|
||||
### Open WebUI (참용 웹 UI)
|
||||
- **기술 스택**: Svelte (프론트엔드) + FastAPI (백엔드)
|
||||
- **설치**: Docker 또는 pip (`pip install open-webui`)
|
||||
- **Ollama 연결**: `OLLAMA_BASE_URL` 환경변수
|
||||
- **참고**: 폐쇄 네트워크에서 바로 사용 가능한 완성된 솔루션이지만, 커스텀 웹페이지 제작을 위한 참고용으로 활용
|
||||
|
||||
---
|
||||
|
||||
## 2. 필요한 정보 요약
|
||||
|
||||
### 필수 조건
|
||||
1. **Ollama 설치 및 실행** (폐쇄 네트워크에 사전 설치)
|
||||
2. **로컬 모델 다운로드** (사전 `ollama pull <model_name>` 실행)
|
||||
3. **웹 서버** (정적 파일 서빙 + API 프록시, 또는 프론트엔드에서 직접 Ollama API 호출)
|
||||
|
||||
### API 요청/응답 형식
|
||||
```json
|
||||
// 채팅 요청 (POST /api/chat)
|
||||
{
|
||||
"model": "llama3",
|
||||
"messages": [
|
||||
{"role": "user", "content": "안녕하세요"}
|
||||
],
|
||||
"stream": false
|
||||
}
|
||||
|
||||
// 채팅 응답
|
||||
{
|
||||
"model": "llama3",
|
||||
"message": {"role": "assistant", "content": "안녕하세요! 어떻게 도와드릴까요?"}
|
||||
}
|
||||
```
|
||||
|
||||
### 스트리밍 응답 처리 (선택사항)
|
||||
- NDJSON 형식으로 각 줄이独立的 JSON 객체
|
||||
- `done: true`로 응답 종료 신호
|
||||
- `ReadableStream` + `TextDecoder`로 처리 가능
|
||||
|
||||
---
|
||||
|
||||
## 3. Todo List
|
||||
|
||||
### Phase 1: 환경 준비
|
||||
- [ ] 1.1 폐쇄 네트워크 서버에 Ollama 설치
|
||||
- [ ] 1.2 필요한 LLM 모델 사전 다운로드 (`ollama pull`)
|
||||
- [ ] 1.3 Ollama 서비스 실행 및 `localhost:11434` 접근 확인
|
||||
|
||||
### Phase 2: 프론트엔드 기본 구조
|
||||
- [ ] 2.1 HTML/CSS/JavaScript 기반 채팅 UI 스키레션 작성
|
||||
- [ ] 2.2 채팅 메시지 표시 영역 (사용자/보조 구분)
|
||||
- [ ] 2.3 입력 필드 및 전송 버튼 구현
|
||||
- [ ] 2.4 반응형 디자인 (모바일/데스크톱)
|
||||
|
||||
### Phase 3: Ollama API 연동
|
||||
- [ ] 3.1 `fetch()`로 Ollama `/api/chat` 엔드포인트 호출 구현
|
||||
- [ ] 3.2 메시지 히스토리 관리 (배열 유지)
|
||||
- [ ] 3.3 모델 선택 기능 (`/api/tags`로 모델 목록 조회)
|
||||
- [ ] 3.4 로딩 상태 및 에러 처리
|
||||
|
||||
### Phase 4: 스트리밍 응답 (선택사항)
|
||||
- [ ] 4.1 NDJSON 스트리밍 파싱 구현
|
||||
- [ ] 4.2 실시간 텍스트 표시 (타이핑 효과)
|
||||
- [ ] 4.3 스트리밍 중단 기능
|
||||
|
||||
### Phase 5: 추가 기능
|
||||
- [ ] 5.1 채팅 기록 저장 (localStorage)
|
||||
- [ ] 5.2 새 채팅 시작 / 채팅 초기화
|
||||
- [ ] 5.3 Markdown 렌더링 (코드 블록, 수식 등)
|
||||
- [ ] 5.4 시스템 프롬프트 설정 기능
|
||||
|
||||
### Phase 6: 배포
|
||||
- [ ] 6.1 정적 파일 빌드
|
||||
- [ ] 6.2 폐쇄 네트워크 서버에 배포
|
||||
- [ ] 6.3 CORS 설정 (Ollama `OLLAMA_HOST` 환경변수)
|
||||
- [ ] 6.4 최종 테스트
|
||||
|
||||
---
|
||||
|
||||
## 4. 기술 선택 가이드
|
||||
|
||||
| 옵션 | 설명 | 추천도 |
|
||||
|------|------|--------|
|
||||
| 순수 HTML/JS | 의존성 없음, 폐쇄 네트워크에 적합 | ⭐⭐⭐ |
|
||||
| Vue/React SPA | 빌드 필요, 하지만 풍부한 생태계 | ⭐⭐ |
|
||||
| Open WebUI 그대로 사용 | 별도 개발 불필요, Docker로 배포 | ⭐⭐⭐ |
|
||||
|
||||
**폐쇄 네트워크 권장**: 순수 HTML/CSS/JavaScript 또는 Open WebUI Docker 배포
|
||||
110
plans/roo-nodemap-undefined-fix.md
Normal file
110
plans/roo-nodemap-undefined-fix.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Roo 작업 지시: 노드맵 대시보드 undefined 필드 수정
|
||||
|
||||
## 배경 및 원인
|
||||
|
||||
`src/Web/Program.cs` 에 다음 설정이 있음:
|
||||
```csharp
|
||||
opt.JsonSerializerOptions.PropertyNamingPolicy = null; // PascalCase 직렬화
|
||||
```
|
||||
|
||||
이로 인해 C# 익명 객체 shorthand `new { x.Id, x.NodeId }` 등은 PascalCase로 직렬화됨.
|
||||
프론트엔드(`app.js`)는 camelCase(`r.id`, `r.nodeId`)로 접근 → **모든 값이 `undefined`로 표시됨**.
|
||||
|
||||
같은 문제를 Browse 엔드포인트에서도 확인했으며, 명시적 camelCase 익명 객체로 수정 완료:
|
||||
```csharp
|
||||
// 수정 전
|
||||
return Ok(new { success = r.Success, nodes = r.Nodes, error = r.ErrorMessage });
|
||||
|
||||
// 수정 후
|
||||
return Ok(new {
|
||||
success = r.Success,
|
||||
error = r.ErrorMessage,
|
||||
nodes = r.Nodes.Select(n => new {
|
||||
nodeId = n.NodeId,
|
||||
displayName = n.DisplayName,
|
||||
nodeClass = n.NodeClass,
|
||||
hasChildren = n.HasChildren
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 수정 대상 파일
|
||||
|
||||
**`src/Web/Controllers/ExperionControllers.cs`**
|
||||
클래스: `ExperionNodeMapController`
|
||||
메서드: `Query()` (약 571번째 줄)
|
||||
|
||||
---
|
||||
|
||||
## 현재 코드 (문제)
|
||||
|
||||
```csharp
|
||||
return Ok(new
|
||||
{
|
||||
total = r.Total,
|
||||
items = r.Items.Select(x => new
|
||||
{
|
||||
x.Id, x.Level, x.Class, x.Name, x.NodeId, x.DataType
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
`x.Id` → JSON `"Id"` (PascalCase) → JS `r.id` = undefined
|
||||
|
||||
---
|
||||
|
||||
## 수정 후 코드 (목표)
|
||||
|
||||
```csharp
|
||||
return Ok(new
|
||||
{
|
||||
total = r.Total,
|
||||
items = r.Items.Select(x => new
|
||||
{
|
||||
id = x.Id,
|
||||
level = x.Level,
|
||||
@class = x.Class,
|
||||
name = x.Name,
|
||||
nodeId = x.NodeId,
|
||||
dataType = x.DataType
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
`@class` 는 C# 예약어 회피용이며, JSON 직렬화 시 `"class"` 로 정상 출력됨.
|
||||
|
||||
---
|
||||
|
||||
## 추가 확인 사항 (같은 패턴이 있는지 전수 검사)
|
||||
|
||||
`ExperionControllers.cs` 전체에서 `PropertyNamingPolicy = null` 환경에서 PascalCase로 직렬화될 수 있는 패턴을 모두 찾아 수정:
|
||||
|
||||
1. `new { x.PropertyName }` 형태의 shorthand 익명 객체
|
||||
2. 직접 typed record/class 인스턴스를 `Ok(...)` 에 넣는 경우
|
||||
|
||||
단, 다음은 이미 lowercase이므로 수정 불필요:
|
||||
- `new { success = ..., nodes = ... }` — 명시적 소문자 키
|
||||
- `new { total = ..., names = ... }` — 명시적 소문자 키
|
||||
|
||||
---
|
||||
|
||||
## 빌드 검증
|
||||
|
||||
수정 후 반드시:
|
||||
```bash
|
||||
dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q
|
||||
```
|
||||
- `Build succeeded` 확인
|
||||
- 에러 0건 확인
|
||||
|
||||
---
|
||||
|
||||
## 클로드 코드 검수 항목
|
||||
|
||||
수정 완료 후 아래 내용을 검수 요청:
|
||||
1. `ExperionNodeMapController.Query()` 응답 필드가 모두 camelCase인지
|
||||
2. 같은 패턴(`{ x.Prop }`)이 다른 컨트롤러에도 있는지 확인 여부
|
||||
3. 빌드 성공 여부
|
||||
4. `@class` → JSON `"class"` 직렬화 정상 작동 여부
|
||||
Reference in New Issue
Block a user