# 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 tagNames)` | | 태그 매핑 | `GetMappingNodesAsync(List tagNames)` | | 시계열 분석 | `AnalyzeAsync(string sql)` | | 시간 범위 추출 | `KoreanTimeRangeExtractor` 협업 | ### 6.2 ExperionOpcClient | 기능 | 메서드 | |------|--------| | 단일 읽기 | `ReadAsync(string nodeId)` | | 복수 읽기 | `ReadAsync(List nodeIds)` | | 노드 탐색 | `BrowseAsync(string nodeId)` | | 연결 테스트 | `TestConnectionAsync(ExperionServerConfig cfg)` | ### 6.3 ExperionRealtimeService | 기능 | 메서드 | |------|--------| | 시작 | `StartAsync(ExperionServerConfig cfg)` | | 중지 | `StopAsync()` | | 등록 | `SubscribeAsync(List nodeIds)` | | 해제 | `UnsubscribeAsync(List 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 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 통합 완료)