Files
ExperionCrawler/CLAUDE.md
windpacer 2e844abf11 feat: 운전판정 고도화 — realtime stall 수정 + 교차검증 + 단위/레인지
- ExperionRealtimeService를 단일 SuperviseAsync supervisor로 재설계:
  비블로킹 부팅, PublishingStopped/KeepAliveStopped 워치독으로 silent
  stall 감지, 30초 주기 무한 재연결, flush 루프 단일화
- RealtimeServiceStatus에 LastDataAgeSeconds/Stalled 추가, History는
  Stalled 시 스냅샷 skip
- v_plant_running_state에 진공펌프(vp-) 포함 + 교차검증 4객체
  (pump_corroboration_manual, v_pump_signal_map,
  v_plant_running_state_corroborated, v_plant_running_state_agg)
  + v_instrument_range 뷰 (boot DDL)
- MetadataLoaderService에 euhi/eulo/units 메타속성 추가
- generate_status_report에 agg 조회 연동 + sample/focus 버그 수정
- plant_context.md에 펌프 prefix(p-/vp-) + 교차검증 뷰 사용법

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 16:47:20 +09:00

14 KiB

ExperionCrawler — 작업 이력

작업 규칙

  • 복잡한 작업은 항상 todo 목록 먼저 생성
  • 각 단계 시작 전 todo 목록 확인
  • 단계 완료 후 즉시 completed 표시

완료된 작업

운전판정 고도화 — realtime writer stall 수정 + 교차검증(corroboration) + 단위/레인지 (2026-05-24)

배경

"6-1차 펌프 4대 vs 5대" 질문에서 출발 → ① v_plant_running_state가 p-%만 집계해 진공펌프(vp-) 누락, ② 펌프 enum(RUN)만으론 허위 운전(deadhead·센서이상·frozen) 미검출, ③ 진단 중 realtime_table이 09:58 KST에 silent stall(수집 멈춤)된 운영 장애 발견.

구현 내역

# 항목 핵심
1 진공펌프 포함 v_plant_running_state 필터 p-%(p-% OR vp-%). VP-6117 등 진공펌프도 운전 집계
2 realtime writer stall 수정 ExperionRealtimeService를 단일 SuperviseAsync supervisor로 통일: 부팅 비블로킹(StartAsync(ct) 즉시 반환), GetLinkFault()Subscription.PublishingStopped/Session.KeepAliveStopped/Connected로 silent stall 감지 → 30초 주기 무한 재연결(3회-후-포기 제거), flush 루프 1회만 기동. RealtimeServiceStatusLastDataAgeSeconds/Stalled 추가, History는 Stalled 시 스냅샷 skip
3 교차검증 뷰 pump_corroboration_manual(수동 매핑) + v_pump_signal_map(토폴로지 FT.from_tag=펌프→FICQ 1:N + 수동) + v_plant_running_state_corroborated(신선도 게이트 120s + STALE + 유량·진공 임계) + v_plant_running_state_agg(CONFIRMED 기준 RUNNING, suspicious/stale 부가 카운트)
4 단위/레인지 메타데이터 별도 테이블 없이 tag_metadata 재사용 — MetadataLoaderService.MetaAttributeseuhi/eulo/units 추가(메타갱신 트리거 자동 편승). 타입 접근 v_instrument_range
5 유량 임계 보정 FS 5%가 과대사이징 계기(FS 2000, 운전 1157)엔 부적합(정상→SUSPICIOUS 오판) 발견 → GREATEST(1.0, LEAST(eu_hi*0.05, 5.0))로 [1~5 kg/hr] deadhead 밴드 캡
6 MCP 연동 generate_status_reportv_plant_running_state_agg 조회 추가(응답 pump_corroboration + LLM 프롬프트). 기존 sample/focus NameError 버그도 수정

수정 파일

파일 변경
src/Infrastructure/OpcUa/ExperionRealtimeService.cs supervisor 재설계(비블로킹·워치독·무한재시도·flush 단일화)
src/Infrastructure/OpcUa/ExperionHistoryService.cs Stalled 시 스냅샷 skip
src/Infrastructure/OpcUa/MetadataLoaderService.cs MetaAttributes에 euhi/eulo/units
src/Core/Application/Interfaces/IExperionServices.cs RealtimeServiceStatus에 LastDataAgeSeconds·Stalled
src/Infrastructure/Database/ExperionDbContext.cs v_plant_running_state(vp- 포함) + 교차검증 4객체 + v_instrument_range (boot DDL)
mcp-server/server.py generate_status_report agg 연동 + 버그수정
prompts/plant_context.md 펌프 prefix(p-/vp-) + 교차검증 뷰 사용법
plans/운전판정-고도화-플랜.md §0 감리 진단 결과(초안 정정·구현현황)

설계 결정

항목 결정
stall 진단 신호 realtime.timestamp frozen + history(realtime 복사본)는 신선·값 frozen → 수집기 정지. UTC/KST 무관
생존 판정 _session.Connected 단독 → SDK PublishingStopped/KeepAliveStopped 추가(silent stall 감지)
단위/레인지 저장 별도 테이블 X — euhi/eulo/units가 OPC 자식노드라 tag_metadata EAV에 적합. 트리거 배선 0
진공 의미 pica-6111 = mmHg(≈torr), 0~760(760=대기압). 깊은진공=저압 → < 300 CONFIRMED
유량 임계 %FS 부적합(계기 과대사이징) → deadhead 절대 밴드 캡
MCP 재적용 MCP 서버 재시작 필요(미반영)

검증 (라이브 :5000, Web 재기동 2회)

  • dotnet build 경고 0/에러 0, py_compile OK
  • 재기동 후 supervisor 자동재개(929 포인트), realtime stall 복구(lag 1:13→<2s, fresh 0→929)
  • 메타갱신 2946건 적재(euhi/eulo/units), v_instrument_range 채워짐(ficq-6113 02000 kg/hr, pica-6111 0760 mmHg)
  • P6 운전 5대(진공 vp-6117 포함) 전부 CONFIRMED_RUNNING(유량 43 kg/hr, 진공 43 mmHg), agg confirmed=5/suspicious=0

잔여

  • MCP 서버 재시작해야 generate_status_report 변경 반영
  • 신버전 Web 앱은 백그라운드 dotnet run으로 기동 중 — 영속화는 운전원 터미널/deploy.sh(systemd) 권장
  • 진공 임계(300)·유량 deadhead 밴드는 운전 데이터로 추가 튜닝 여지
  • active_alarms에 SUSPICIOUS 주입은 운전원 검증 후(보류)

문서 탐색기 (Tab 16) 구현 (2026-05-24)

배경

프로젝트 폴더의 문서를 Web UI에서 직접 보고 관리. 트리는 프로젝트 폴더 전체를 탐색, 뷰어는 txt · md · pdf 3종(그 외 다운로드), 보기 + 관리 + 인라인 편집 범위. 사용자 결정: PDF=원본 그대로(iframe), Excel=제외, md 렌더=marked+코드강조+KaTeX+mermaid, 뷰어 바탕=흰색.

구현 내역

# 항목 핵심
1 안전 파일트리 루트 자동탐색(.git/.sln) → 상대경로만 취급, Path.GetFullPath 정규화 후 루트이탈·심볼릭이탈 차단. 제외 디렉토리(.git/bin/obj/node_modules/storage…)·민감파일(.pfx/.env/appsettings*.json…) 숨김+차단
2 뷰어 디스패치 md=marked→DOMPurify→hljs→KaTeX→mermaid / pdf=원본 iframe(Content-Type: application/pdf) / txt=pre / 그 외=다운로드. md 라이브러리는 첫 진입 시 지연로딩
3 인라인 편집 admin이면 ✎ 편집 → textarea(md는 실시간 분할 미리보기) → PUT /api/docs/text 저장
4 관리 새 폴더·업로드·이름변경·재귀삭제 — KB admin 토큰(X-Kb-Token) 재사용
5 UI 다크 트리(지연확장·필터·노드별 hover 액션) + 흰색 종이 뷰어, 상대링크 클릭 시 탐색기 내 이동, 토스트

수정 파일

파일 변경 요약
src/Infrastructure/Docs/DocBrowserService.cs (신규) 루트결정·SafeResolve(이탈/심볼릭/제외/민감 가드)·List/ReadText(바이너리·BOM·크기상한)/OpenRaw(MIME)/WriteText/Rename/Delete/MakeDir/SaveUploadAsync
src/Web/Controllers/DocsController.cs (신규) /api/docs — config·tree·text·raw(공개) / PUT text·rename·mkdir·upload·DELETE(admin, IKbAuthService.ValidateAsync)
src/Web/Program.cs DocBrowserService 싱글톤 등록
src/Web/appsettings.json DocBrowser 섹션(Root="", MaxTextBytes=2MB, MaxUploadBytes=50MB)
src/Web/wwwroot/index.html nav-item(16 문서 탐색기), #pane-docs(트리+뷰어), docs.css 링크, docs.js 스크립트
src/Web/wwwroot/js/app.js 탭 전환부 if (tab === 'docs') docsInit()
src/Web/wwwroot/js/docs.js (신규) 트리(지연확장·필터·관리액션 위임), 뷰어 디스패치, md 렌더 파이프라인, 지연 라이브러리 로더, 인라인 에디터, 관리(mkdir/upload/rename/delete), KB 토큰 재사용 잠금해제, 토스트
src/Web/wwwroot/css/docs.css (신규) 다크 트리 + 흰색 종이 뷰어, GitHub-라이트 마크다운 타이포, 편집 분할
src/Web/wwwroot/lib/ (신규) marked@12, dompurify@3.1.6, highlight.js@11.10(+github 테마), katex@0.16.11(js/css/auto-render + woff2 20종), mermaid@10.9.1 — 전부 로컬 번들

설계 결정

항목 결정
적용 환경 소스 트리가 있는 개발 환경 전용 (배포본 /opt/ExperionCrawler엔 소스 없음). 루트는 DocBrowser:Root로 재설정 가능
인증 조회=공개, 변경계열=KB admin 토큰 재사용(별도 비번 없음). 프론트는 sessionStorage.kbToken 공유
md 라이브러리 로딩 페이지 로드 시점이 아닌 md 첫 열람 시 지연로딩(mermaid 3.3MB 등 무게 회피)
XSS marked 출력은 DOMPurify 살균. pdf/원본은 iframe·MIME 한정
download 파라미터 ASP.NET bool 바인딩은 true/false만 허용 — 프론트 download=true 사용(초기 download=1은 400, 수정함)
뷰어 대상 우선 txt/md/pdf만. DOCS_TEXT_EXT/DOCS_MD_EXT 상수로 추후 확장 용이

빌드/검증 (라이브 :5000)

  • dotnet build 경고 0 / 에러 0, node -c docs.js/app.js OK, 정적자산 11종 200 서빙
  • 루트 자동탐색 → 프로젝트 루트, 트리 .git/bin/obj 제외, 경로이탈·민감파일(appsettings/.env)·.git 진입 차단
  • md/txt 읽기·하위트리·PDF inline(application/pdf)·다운로드 첨부 정상
  • 미인증 PUT/DELETE → 401, admin 로그인 후 mkdir/쓰기/읽기/이름변경/재귀삭제 정상, .env 쓰기 차단, 정리 확인

잔여

  • 브라우저 실물 렌더(트리 클릭·md/mermaid/KaTeX·편집 UI)는 미확인 — 사용자 Ctrl+F5 후 탭 진입으로 확인 필요
  • 검증 위해 기존 dotnet run(터미널)을 백그라운드 새 빌드로 교체 기동

Phase 7 + Phase 5 후순위 일괄 구현 (2026-05-14)

배경

plans/빅피클-잔여작업-코딩계획.md의 1~6번 항목을 일괄 구현. Phase 7 옵션 4종 + Phase 5 후순위 2종으로 채팅 UX·관리 편의·운영자 분석 능력을 강화.

구현 내역

# 항목 핵심
1 툴 카드 영구 보존 sess.messages[*].toolCalls[] 저장 → llmRenderMessages에서 재렌더링. F5 새로고침 후에도 툴 카드 유지
2 KB 청크 미리보기 UI Qdrant Scroll API → /api/kb/documents/{id}/chunks → 모달에 청크 카드 (접기/펼침)
3 시계열 미니 스파클라인 llmDetectTimeSeries (timestamp+value 키 자동 감지) → uPlot 90px 차트가 표 위에 자동 렌더링
4 NL2SQL 의도 라우터 _classify_intent 정규식 6규칙 → query_with_nl 진입 시 알람/요약/태그검색/이벤트로 위임. classify_intent MCP 도구로도 노출
5 대화 요약 sess.summary + summarizedUpTo 인덱스, LLM_MAX_HISTORY=20 초과 시 /api/ollama/summarize 호출 → systemPrompt에 누적 요약 prepend
6 에이전트 모드 #llm-agent-mode 토글 → AgentMode 요청 필드 → ComposeSystemPromptAgentModeGuideKo (ReAct 사이클) 주입

수정 파일

파일 변경 요약
src/Web/wwwroot/js/app.js llmRenderToolCardsHtml(영구 렌더), llmDetectTimeSeries+llmBuildSparklineHtml+llmMountSparkline(uPlot), kbShowChunks+kbRenderChunks(모달), LLM_MAX_HISTORY+llmEnsureSummary+sess.summary 표시, llmAgentMode+llmToggleAgentMode, 툴 카드 표시 + 요약 prepend 로직
src/Web/wwwroot/index.html #kb-chunk-modal 모달, #llm-agent-row/#llm-agent-mode 토글
src/Web/wwwroot/css/style.css .kb-chunk-*(청크 카드/뱃지/locator), .llm-sparkline-box/-chart, .llm-summary-card(접기/펼침)
src/Web/Controllers/OllamaController.cs AgentMode request 필드, ComposeSystemPrompt(agentMode), AgentModeGuideKo ReAct 가이드, POST /api/ollama/summarize 엔드포인트, OllamaSummarizeRequest DTO, ToolGuideKoclassify_intent 추가
src/Web/Controllers/KbController.cs GET /api/kb/documents/{id}/chunks (admin) — KbQdrantClient Scroll 호출
src/Infrastructure/Kb/KbQdrantClient.cs GetChunksByDocIdAsync(collection, docId, limit) — payload-only Scroll API
mcp-server/server.py _CLASSIFY_RULES+_classify_intent+@mcp.tool() classify_intent, query_with_nl 진입부에서 라우팅 후 5개 전용 도구로 위임 (실패 시 SQL fallback)

의도 라우터 규칙

정규식 라우팅 대상
활성.*알람|현재.*알람|지금.*알람|active.*alarm active_alarms
트립|trip active_alarms
상태\s*보고서|교대.*보고|status.*report|운전.*보고 generate_status_report
요약|보고서|리포트|summary|summarize|report summarize_events
태그.*찾|tag.*찾|찾아\s*줘|find.*tag|어떤.*태그 find_tags
이벤트.*조회|이벤트.*목록|event.*list|event.*query|로그.*조회 query_events
(그 외) query_with_nl (기본 SQL 경로)

스모크 테스트 10건 모두 통과 — "지금 알람 알려줘"→active_alarms / "FIC-6113.PV 값 보여줘"→query_with_nl / "안녕"→query_with_nl 등.

설계 결정

항목 결정
툴 카드 영구화 assistantMsg.toolCalls{id,name,args,ok,preview,length,payload} 누적 저장. 기존 세션에 toolCalls 없으면 렌더링 생략(역호환)
AbortError 시 content 또는 toolCalls가 비어있지 않으면 메시지 유지 (도구만 호출하고 중단된 경우도 보존)
시계열 감지 timestamp/recorded_at/ts/time/datetime + value/pv/val/fieldvalue/sp/op 페어. 3건 이상이어야 차트 생성. uPlot 미로딩 시 렌더 생략
스파클라인 부착 innerHTML로 컨테이너만 만든 후 requestAnimationFrame에서 uPlot 생성 (DOM 마운트 후 실행 보장)
요약 임계 LLM_MAX_HISTORY=20 초과 시 오래된 절반을 요약. 누적 요약은 이전 요약을 system 메시지로 함께 전송
요약 송신 sess.summary가 있으면 systemPrompt 맨 앞에 [이전 대화 요약] 블록 prepend. 사용자에게는 접이식 카드로 표시
의도 라우터 fallback 라우팅 시도 실패(예외) 시 조용히 SQL 경로로 fallback
에이전트 모드 조건부 표시 llmType==='vllm' && llmUseTools 일 때만 토글 노출. localStorage 영속
청크 미리보기 권한 admin 토큰 필요. payload만 조회(vector 제외), 최대 500개/문서

빌드/검증

  • dotnet build src/Web/ExperionCrawler.csproj — 경고 0건, 에러 0건
  • python3 -m py_compile mcp-server/server.py mcp-server/worker/nl2sql_worker.py — OK
  • python3 -c "import server" — 9개 도구 모두 attribute로 노출 확인
  • node -c src/Web/wwwroot/js/app.js — syntax OK
  • 의도 분류기 10/10 통과

런타임 셋업

  • mcp-server 재시작 — classify_intent 신규 도구 인식
  • 브라우저 캐시 무효화 (Ctrl+F5) — 신규 JS/CSS 적용
  • 사용자 첫 진입 시 에이전트 모드 토글은 OFF (옵트인)

잔여

  • 결정 보류: 현장 재고 데이터 출처 (KB 엑셀 업로드로 즉시 가능, 별도 개발 불필요), 임베딩 모델 BGE-M3 마이그레이션 (위험 대비 임계값 평가 필요)
  • 모두 코드 작업 아닌 분석/결정 항목