Files
ExperionCrawler/Qwen-crawler-analysis.md

362 lines
12 KiB
Markdown

# ExperionCrawler 프로젝트 분석 보고서 (Qwen 기반)
**작성일**: 2026-04-28
**분석 도구**: Qwen3-Coder-Next
**프로젝트 경로**: `/home/windpacer/projects/ExperionCrawler`
---
## 1. 개요
### 1.1 프로젝트 목표
Honeywell Experion OPC UA 서버를 위한 **웹 기반 데이터 수집 및 시계열 분석 도구**입니다.
OPC UA 프로토콜을 통해 실시간 데이터 수집, CSV 저장, TimescaleDB 이력 관리, 자연어 질의 처리까지 통합적으로 제공합니다.
### 1.2 기술 스택
| 계층 | 기술 | 용도 |
|------|------|------|
| **Backend** | .NET 8 (C#) | 웹 API, 백그라운드 서비스 |
| **Frontend** | Vanilla JS + Bootstrap | UI 구현 |
| **Database** | PostgreSQL + TimescaleDB | 시계열 데이터 저장 |
| **OPC UA** | opcua-sharp (Opc.Ua) | 실시간/히스토리 데이터 수집 |
| **MCP** | Python + Qwen3-Coder-Next | 자연어 → SQL 변환 (LLM 기반) |
---
## 2. 아키텍처
### 2.1 소스 구조 (Clean Architecture)
```
ExperionCrawler/
├── src/
│ ├── Core/ # 핵심 비즈니스 로직 (Domain, Application)
│ │ ├── Domain/
│ │ │ └── Entities/ # 엔티티 정의 (ExperionTag, ExperionRecord, RealtimePoint...)
│ │ └── Application/
│ │ ├── DTOs/ # 데이터 전송 객체
│ │ ├── Interfaces/ # 서비스 인터페이스 (DI 대상)
│ │ └── Services/ # 구현체 (TextToSqlService, KoreanTimeRangeExtractor...)
│ │
│ ├── Infrastructure/ # 기술적 구현 (OPC UA, DB, Mcp)
│ │ ├── Certificates/ # X.509 인증서 관리 (pki/ 디렉토리)
│ │ ├── Database/ # EF Core + TimescaleDB
│ │ ├── Csv/ # CSV 읽기/쓰기 (CsvHelper)
│ │ ├── OpcUa/ # OPC UA 클라이언트/서버 구현
│ │ └── Mcp/ # MCP 클라이언트 (Python 통신)
│ │
│ └── Web/ # ASP.NET Core 웹 프로젝트
│ ├── Controllers/ # API 컨트롤러
│ ├── Program.cs # DI, 미들웨어 구성
│ └── wwwroot/ # 정적 파일 (index.html, js/app.js, css/)
```
### 2.2 DI 컨테이너 등록 (`Program.cs`)
| Service | Lifetime | 구현체 |
|---------|----------|--------|
| `IExperionCertificateService` | Singleton | `ExperionCertificateService` |
| `IExperionStatusCodeService` | Singleton | `ExperionStatusCodeService` |
| `IOpcUaConfigProvider` | Singleton | `OpcUaConfigProvider` |
| `IExperionOpcClient` | Scoped | `ExperionOpcClient` |
| `IExperionCsvService` | Scoped | `ExperionCsvService` |
| `IExperionDbService` | Scoped | `ExperionDbService` |
| `ITextToSqlService` | Scoped | `TextToSqlService` |
| `IMcpService` | Singleton | `McpService` |
| `ExperionRealtimeService` | Singleton | 실시간 구독 (BackgroundService) |
| `ExperionHistoryService` | Singleton | 히스토리 구독 (BackgroundService) |
| `ExperionOpcServerService` | Singleton | OPC UA 서버 (BackgroundService) |
---
## 3. 핵심 기능
### 3.1 인증서 관리
| 기능 | 설명 |
|------|------|
| **생성** | `POST /api/certificate/create` → X.509 클라이언트 인증서 생성 |
| **상태 확인** | `GET /api/certificate/status?clientHostName=dbsvr` |
| **PKI 구조** | `pki/{own,trusted,issuers,rejected}/certs/` |
### 3.2 OPC UA 클라이언트
| 기능 | 설명 |
|------|------|
| **서버 접속** | `POST /api/connection/test` → 단일 태그 읽기, 노드 탐색 |
| **실시간 구독** | `ExperionRealtimeService` → Subscription 기반 콜백 |
| **히스토리 수집** | `ExperionHistoryService` → 주기적 Snapshot |
| **CSV 저장** | `ExperionCsvService``data/csv/` 디렉토리 |
| **DB 임포트** | `ExperionDbService``history_table` / `realtime_table` |
### 3.3 Text-to-SQL (자연어 → SQL)
| 기능 | 설명 |
|------|------|
| **자연어 파싱** | `POST /api/text-to-sql/parse``TextToSqlService.ParseNaturalLanguageAsync()` |
| **MCP 통합** | `POST /api/text-to-sql/query-nl` → LLM → SQL → 실행 |
| **도구 목록** | `GET /api/text-to-sql/tools` |
| **시계열 분석** | `POST /api/text-to-sql/analyze` → avg/max/min/추세 계산 |
| **간격 쿼리** | `POST /api/text-to-sql/query-history-interval` |
**시간 키워드 예시**:
- `"최근 1시간"`, `"최근 24시간"`, `"최근 7일"`, `"최근 1개월"`
- `"오늘"`, `"어제"`, `"오늘부터 ~ 까지"`, `"어제부터 ~ 까지"`
- `"오전 9시부터 오후 5시까지"`
### 3.4 MCP (Model Context Protocol)
| 기능 | 설명 |
|------|------|
| **Ping** | `GET /api/mcp/ping` → Python 서버 연결 확인 |
| **SQL 실행** | `POST /api/mcp/run-sql` → TimescaleDB 쿼리 |
| **PV 히스토리** | `POST /api/mcp/query-pv-history` → 태그명 + 시간 범위 |
| **태그 메타데이터** | `POST /api/mcp/get-tag-metadata` |
| **도면 목록** | `GET /api/mcp/list-drawings?unitNo=...` |
| **자연어 질의** | `POST /api/mcp/query-with-nl` |
### 3.5 OPC UA 서버
| 기능 | 설명 |
|------|------|
| **시작** | `POST /api/opcserver/start` → 자동 시작 플래그 저장 |
| **중지** | `POST /api/opcserver/stop` → 플래그 삭제 |
| **NodeManager** | `ExperionOpcServerNodeManager` → 커스텀 노드 매니저 |
---
## 4. 데이터베이스 스키마
### 4.1 테이블 정의
| 테이블명 | 용도 | 주요 컬럼 |
|----------|------|-----------|
| `raw_node_map` | 노드맵 원시 데이터 | `id, level, class, name, node_id, data_type` |
| `node_map_master` | 마스터 노드맵 | `id, level, class, name, node_id, data_type` |
| `realtime_table` | 실시간 포인트 | `id, tagname, node_id, livevalue, timestamp` |
| `history_table` | 시계열 이력 | `id, tagname, node_id, value, recorded_at` |
** TimescaleDB 확장 활성화: `CREATE EXTENSION IF NOT EXISTS timescaledb` **
---
## 5. API 엔드포인트
### 5.1 인증서
| 메서드 | 엔드포인트 | 기능 |
|--------|-----------|------|
| GET | `/api/certificate/status?clientHostName={name}` | 인증서 존재 여부 확인 |
| POST | `/api/certificate/create` | X.509 클라이언트 인증서 생성 |
### 5.2 연결
| 메서드 | 엔드포인트 | 기능 |
|--------|-----------|------|
| POST | `/api/connection/test` | 서버 접속 테스트, 단일 태그 읽기 |
| POST | `/api/connection/read` | nodeId 기반 읽기 |
| POST | `/api/connection/browse` | 노드 탐색 |
### 5.3 크롤링
| 메서드 | 엔드포인트 | 기능 |
|--------|-----------|------|
| POST | `/api/crawl/start` | 복수 노드 주기 수집 시작 |
| POST | `/api/crawl/stop` | 수집 중지 |
| POST | `/api/crawl/export` | CSV 다운로드 |
### 5.4 DB
| 메서드 | 엔드포인트 | 기능 |
|--------|-----------|------|
| GET | `/api/db/records?limit={n}&offset={m}` | 레코드 조회 |
| POST | `/api/db/import` | CSV 임포트 |
| POST | `/api/db/export` | CSV 다운로드 |
### 5.5 Text-to-SQL
| 메서드 | 엔드포인트 | 기능 |
|--------|-----------|------|
| POST | `/api/text-to-sql/parse` | 자연어 → SQL 변환 |
| POST | `/api/text-to-sql/execute` | SQL 실행 |
| POST | `/api/text-to-sql/suggest` | 쿼리 제안 |
| POST | `/api/text-to-sql/analyze` | 시계열 분석 |
| POST | `/api/text-to-sql/query-history-interval` | 사용자 지정 간격 조회 |
| POST | `/api/text-to-sql/query-nl` | MCP 통합 자연어 질의 |
| GET | `/api/text-to-sql/tools` | MCP 도구 목록 |
### 5.6 OPC UA 서버
| 메서드 | 엔드포인트 | 기능 |
|--------|-----------|------|
| POST | `/api/opcserver/start` | 서버 시작 |
| POST | `/api/opcserver/stop` | 서버 중지 |
| GET | `/api/opcserver/status` | 서버 상태 |
---
## 6. 주요 서비스 클래스
### 6.1 TextToSqlService
| 기능 | 메서드 |
|------|--------|
| 자연어 파싱 | `ParseNaturalLanguageAsync(string input)` |
| SQL 생성 | `BuildSqlFromNaturalLanguage(string input, out List<string> tagNames)` |
| 태그 매핑 | `GetMappingNodesAsync(List<string> tagNames)` |
| 시계열 분석 | `AnalyzeAsync(string sql)` |
| 시간 범위 추출 | `KoreanTimeRangeExtractor` 협업 |
### 6.2 ExperionOpcClient
| 기능 | 메서드 |
|------|--------|
| 단일 읽기 | `ReadAsync(string nodeId)` |
| 복수 읽기 | `ReadAsync(List<string> nodeIds)` |
| 노드 탐색 | `BrowseAsync(string nodeId)` |
| 연결 테스트 | `TestConnectionAsync(ExperionServerConfig cfg)` |
### 6.3 ExperionRealtimeService
| 기능 | 메서드 |
|------|--------|
| 시작 | `StartAsync(ExperionServerConfig cfg)` |
| 중지 | `StopAsync()` |
| 등록 | `SubscribeAsync(List<string> nodeIds)` |
| 해제 | `UnsubscribeAsync(List<string> nodeIds)` |
---
## 7. 설정 파일
### 7.1 appsettings.json
```json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=iiot_platform;Username=postgres;Password=postgres",
"ExperionDbConnection": "Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=postgres;Trust Server Certificate=true"
},
"OpcUaServer": {
"Port": 4841,
"EnableSecurity": false,
"AllowAnonymous": true,
"AllowedUsernames": ["mngr"],
"AllowedPasswords": ["mngr"]
}
}
```
### 7.2 자동 시작 플래그
| 파일 | 용도 |
|------|------|
| `realtime_autostart.json` | 실시간 구독 자동 시작 |
| `opcserver_autostart.json` | OPC UA 서버 자동 시작 |
---
## 8. 개발/배포
### 8.1 로컬 실행
```bash
cd src/Web
dotnet run
# → http://localhost:5000
```
### 8.2 Ubuntu 배포
```bash
git clone <repo> ExperionCrawler
cd ExperionCrawler
sudo bash deploy.sh
```
### 8.3 systemctl 관리
```bash
sudo systemctl status experioncrawler
sudo systemctl restart experioncrawler
sudo systemctl stop experioncrawler
sudo journalctl -u experioncrawler -f # 실시간 로그
```
---
## 9. 테스팅
### 9.1 단위 테스트 프로젝트
| 테스트 클래스 | 주요 테스트 항목 |
|---------------|----------------|
| `TextToSqlServiceTests.cs` | SQL 생성, 태그 매핑, 시간 범위 추출 |
| `SqlValidatorTests.cs` | SQL 인젝션 방지, 테이블 제한 |
| `KoreanTimeRangeExtractorTests.cs` | 한국어 시간 표현 파싱 |
### 9.2 테스트 명령어
```bash
dotnet test ExperionCrawler.Tests/
```
---
## 10. 주의사항
### 10.1 JSON 직렬화 정책
`Program.cs`에서 **PascalCase 유지** 설정:
```csharp
opt.JsonSerializerOptions.PropertyNamingPolicy = null; // camelCase로 변환하지 않음
```
**프론트엔드 대응**: app.js의 모든 API 응답은 소문자 키로 접근 (`res.id`, `res.tagName`).
### 10.2 인증서 경로
```bash
pki/own/certs/{clientHostName}.pfx # 클라이언트 인증서
pki/trusted/certs/ # 신뢰 피어
pki/issuers/certs/ # 신뢰 발급자 (필수)
pki/rejected/certs/ # 거부 인증서
```
### 10.3 TimescaleDB
- `history_table`은 TimescaleDB의 **Hypercube**로 자동 관리됨
- `recorded_at` 컬럼은 `TIMESTAMPTZ` 타입
---
## 11. 다음 개선 방향
| 항목 | 설명 |
|------|------|
| **realtime_table indexing** | `node_id` 유니크 인덱스만 있고 `tagname` 인덱스 추가 필요 |
| **CSV import 성능** | AssetLoader의 binary COPY 대신 EF Core Bulk Insert 고려 |
| **MCP error handling** | Python 서버 장애 시 fallback 처리 강화 |
| **UI/UX** | Bootstrap 5 업그레이드, 모바일 반응형 개선 |
| **OPC UA Security** | 현재 `AutoAcceptUntrustedCertificates=true` → 프로덕션 시 변경 필요 |
---
## 12. 관련 문서
| 문서 | 경로 | 설명 |
|------|------|------|
| CLAUDE.md | `CLAUDE.md` | Claude 작업 규칙 |
| .roo.md | `.roo.md` | Roo 작업 규칙 |
| task_state.md | `task_state.md` | Text-to-SQL 개발 로그 |
| issues.md | `issues.md` | 이슈 추적 |
| REVIEW_REQUEST.md | `REVIEW_REQUEST.md` | 코드 리뷰 요청 |
---
**분석 완료일**: 2026-04-28 23:11 (KST)
**분석 도구**: Qwen3-Coder-Next
**프로젝트 상태**: ✅ 활발한 개발 중 (Text-to-SQL + MCP 통합 완료)