kcg-ai-monitoring/docs/architecture.md
htlee 04d4b12c50 docs: 2026-04-20 릴리즈 후속 정적 문서 최신화
Phase 0-1/0-2/0-3 + Phase 1-1 V034 반영 + Phase 1-2 재개 포인트 명시.

### 변경
- architecture.md — 27→29 보호 경로, /illegal-fishing + /transshipment 라우트 추가
- sfr-traceability.md
  - Flyway V001~V030 → V001~V034 (34 마이그레이션)
  - Database 51 → 56 테이블 (V034 detection_model_* 5종 + 뷰 1, 반영 대기)
  - Prediction 섹션에 stage_runner 사이클 에러 경계 (Phase 0-1) 추가
  - SFR-09: TransshipmentDetection(V033/PR #86) 페이지 추가
  - SFR-10: IllegalFishingPattern(V032/PR #85) 페이지 추가
  - 부록 A: 신규 화면 파일 경로 반영
- sfr-user-guide.md — SFR-10 하위에 "불법 조업 이벤트" + "환적 의심 탐지" 2개 신규 페이지 사용자 가이드 섹션 신설
- system-flow-guide.md — V030 미반영 경고를 V030~V034 노드 일괄 미반영으로 확장 (detection_models / detection_model_versions / detection_model_run_outputs / detection_model_metrics / dag_executor / shadow_runner / api.detection_models_* / ui.illegal_fishing / ui.transshipment_detection / ui.detection_model_management)
- prediction-analysis.md — P1 권고 4건 중 3건 완료 표시
  -  사이클 스테이지 에러 경계 (PR #83)
  - 🟡 임계값 외부화 (V034 스키마 머지, Phase 1-2 대기)
  -  ILLEGAL_FISHING_PATTERN 전용 페이지 (PR #85)
  -  환적 전용 페이지 (PR #86)
  - 변경 이력에 2026-04-20 행 추가

### 범위 밖
- memory/* 는 세션 로컬 경로, 저장소 외
- prediction/backend 코드 변경 없음 — 문서만
2026-04-20 07:10:48 +09:00

383 lines
19 KiB
Markdown

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# KCG AI Monitoring - 아키텍처 문서
> AI 기반 불법조업 탐지 차단 플랫폼 프론트엔드
---
## 기술스택
| 분류 | 라이브러리 | 버전 | 역할 |
|------|-----------|------|------|
| UI 프레임워크 | React | 19.2.4 | 함수형 컴포넌트 + Hooks 기반 SPA |
| 언어 | TypeScript | 5.9 | 정적 타입, strict 모드 |
| 빌드 도구 | Vite | 8.0.3 | Rolldown 기반 ESM 번들링 (~480ms build), HMR |
| CSS 프레임워크 | Tailwind CSS | 4.2.2 | `@tailwindcss/vite` 플러그인 통합 |
| 지도 (베이스맵) | MapLibre GL | 5.22 | CartoDB 래스터 타일 (Dark/Light 자동 전환) |
| 지도 (벡터) | deck.gl | 9.2 | MapboxOverlay 기반 GPU 벡터 렌더링 (40만척+) |
| 차트 | ECharts | 6.0 (core) | `lib/charts` 래퍼를 통한 프리셋 차트 |
| 상태관리 | Zustand | 5.0 | 8개 독립 스토어 (vessel, patrol, event, kpi 등) |
| 라우팅 | react-router-dom | 7.12.0 | BrowserRouter, 중첩 Route |
| 다국어 | react-i18next + i18next | 17.0 + 26.0 | 10 NS, ko/en 네임스페이스 기반 |
| 스타일 변형 | class-variance-authority (CVA) | 0.7 | card/badge/statusDot variants |
| 아이콘 | lucide-react | 0.487.0 | SVG 아이콘 컴포넌트 |
| 린트 | ESLint | 10.2 | Flat Config, typescript-eslint + react-hooks + react-refresh |
---
## 디렉토리 구조
```
src/
├── lib/
│ ├── charts/ # BaseChart + 4 프리셋 (Area, Bar, Pie, Line)
│ │ ├── BaseChart.tsx # 코어 (init, resize, dispose 자동 관리, 'kcg-dark' 테마)
│ │ ├── theme.ts # ECharts 테마 등록
│ │ ├── tokens.ts # 차트 색상 토큰
│ │ └── presets/ # AreaChart, BarChart, PieChart, LineChart
│ ├── map/
│ │ ├── BaseMap.tsx # MapLibre + deck.gl (forwardRef, memo, overlay 노출)
│ │ ├── hooks/ # useMapLayers, useStoreLayerSync (RAF 기반)
│ │ ├── layers/ # markers, polyline, heatmap, zones, static (STATIC_LAYERS)
│ │ │ ├── markers.ts # createMarkerLayer (transitions + DataFilterExtension)
│ │ │ ├── static.ts # EEZ + NLL 싱글턴 (GPU 1회 업로드)
│ │ │ ├── polyline.ts # 경로/트랙 라인
│ │ │ ├── heatmap.ts # 히트맵 (위험도 시각화)
│ │ │ ├── zones.ts # 구역 원
│ │ │ └── boundaries.ts # 레거시 GeoJSON (하위 호환)
│ │ ├── controls/ # (예약) 지도 컨트롤 확장
│ │ ├── constants.ts # EEZ, NLL, 타일 URL, 기본값
│ │ └── types.ts # MapVessel, MapLayerConfig, HeatPoint
│ ├── i18n/ # 10 NS (common, dashboard, detection, patrol, enforcement, statistics, ai, fieldOps, admin, auth)
│ │ ├── config.ts # i18next 초기화 (ko 기본, en 폴백)
│ │ └── locales/ # ko/*.json, en/*.json (10파일 x 2언어)
│ │ # 2026-04-17: common.json 에 aria(36)/error(7)/dialog(4)/success(2)/message(5) 네임스페이스 추가
│ └── theme/ # tokens, colors, variants (CVA)
│ ├── tokens.ts # CSS 변수 매핑 + resolved 색상값
│ ├── colors.ts # 시맨틱 팔레트 (risk, alert, vessel, status, chartSeries)
│ └── variants.ts # cardVariants, badgeVariants, statusDotVariants (CVA)
├── data/
│ ├── mock/ # 7 공유 mock 모듈
│ │ ├── vessels.ts # 선박 목록 (한국, 중국, 경비함)
│ │ ├── events.ts # 탐지/단속 이벤트
│ │ ├── transfers.ts # 전재(환적) 데이터
│ │ ├── patrols.ts # 순찰 경로/일정
│ │ ├── gear.ts # 어구 탐지 데이터
│ │ ├── kpi.ts # KPI/통계 데이터
│ │ └── enforcement.ts # 단속 이력 데이터
│ ├── areasCodes.json # 해역 코드 (52건)
│ ├── speciesCodes.json # 어종 코드 (578건)
│ ├── fisheryCodes.json # 어업유형 코드 (59건)
│ ├── vesselTypeCodes.json # 선박유형 코드 (186건)
│ └── commonCodes.ts # 코드 유틸리티
├── stores/ # 8 Zustand 스토어
│ ├── vesselStore.ts # 선박 목록, 선택, 필터
│ ├── patrolStore.ts # 순찰 경로/함정
│ ├── eventStore.ts # 탐지/경보 이벤트
│ ├── kpiStore.ts # KPI 메트릭, 추세
│ ├── transferStore.ts # 전재(환적) 데이터
│ ├── gearStore.ts # 어구 탐지
│ ├── enforcementStore.ts # 단속 이력
│ └── settingsStore.ts # theme/language + localStorage 동기화
├── services/ # 7 API 서비스 (현재 mock 반환)
│ ├── api.ts # fetch 래퍼 (향후 Axios 교체 예정)
│ ├── vessel.ts # getVessels, getSuspects, getVesselDetail
│ ├── event.ts # getEvents, getAlerts
│ ├── patrol.ts # getPatrolShips
│ ├── kpi.ts # getKpiMetrics, getMonthlyTrends, getViolationTypes
│ ├── ws.ts # connectWs (STOMP 스텁, 미구현)
│ └── index.ts # 배럴 export
├── shared/components/ # 공유 UI 컴포넌트 (design-system.html SSOT)
│ ├── ui/ # 9개 공통 컴포넌트 (2026-04-17 모든 화면 SSOT 준수 완료)
│ │ ├── card.tsx # Card(CVA variant), CardHeader, CardTitle, CardContent (4 variant)
│ │ ├── badge.tsx # Badge(CVA intent 8종 × size 4단계, LEGACY_MAP 변형 호환)
│ │ ├── button.tsx # Button (variant 5종 × size 3단계, icon/trailingIcon prop)
│ │ ├── input.tsx # Input (size/state, forwardRef)
│ │ ├── select.tsx # Select (aria-label|aria-labelledby|title TS union 강제)
│ │ ├── textarea.tsx # Textarea
│ │ ├── checkbox.tsx # Checkbox (native input 래퍼)
│ │ ├── radio.tsx # Radio
│ │ └── tabs.tsx # TabBar + TabButton (underline/pill/segmented 3 variant)
│ ├── layout/ # PageContainer / PageHeader / Section (표준 페이지 루트)
│ └── common/
│ ├── DataTable.tsx # 범용 테이블 (가변너비, 검색, 정렬, 페이징, 엑셀, 출력)
│ ├── Pagination.tsx # 페이지네이션
│ ├── SearchInput.tsx # 검색 입력 (i18n 통합)
│ ├── ExcelExport.tsx # 엑셀 다운로드
│ ├── FileUpload.tsx # 파일 업로드
│ ├── PageToolbar.tsx # 페이지 상단 툴바
│ ├── PrintButton.tsx # 인쇄 버튼
│ ├── SaveButton.tsx # 저장 버튼
│ └── NotificationBanner.tsx # 알림 배너 (common.aria.closeNotification)
├── features/ # 13 도메인 그룹 (31 페이지)
│ ├── dashboard/ # 종합 대시보드 (Dashboard)
│ ├── monitoring/ # 실시간 모니터링 (MonitoringDashboard)
│ ├── surveillance/ # 감시 (LiveMapView, MapControl)
│ ├── detection/ # 탐지 (DarkVessel, Gear, ChinaFishing)
│ ├── risk-assessment/ # 위험도 평가 (RiskMap, EnforcementPlan)
│ ├── patrol/ # 순찰 (PatrolRoute, FleetOptimization)
│ ├── enforcement/ # 단속 (EnforcementHistory, EventList)
│ ├── statistics/ # 통계 (Statistics, ExternalService, ReportManagement)
│ ├── ai-operations/ # AI 운영 (AIModelManagement, MLOps, AIAssistant)
│ ├── field-ops/ # 현장 대응 (MobileService, ShipAgent, AIAlert)
│ ├── admin/ # 관리 (AccessControl, SystemConfig, Notice, DataHub, AdminPanel)
│ ├── vessel/ # 선박 (VesselDetail, TransferDetection)
│ └── auth/ # 인증 (LoginPage)
├── app/ # 애플리케이션 셸
│ ├── App.tsx # BrowserRouter + Routes (26 보호 경로 + login)
│ ├── auth/AuthContext.tsx # 인증 컨텍스트 (ProtectedRoute)
│ └── layout/MainLayout.tsx # 사이드바 + 콘텐츠 + i18n 메뉴
└── styles/ # 전역 스타일
├── theme.css # CSS 커스텀 속성 (dark 기본 + .light 오버라이드)
├── tailwind.css # Tailwind 기본
└── fonts.css # 웹폰트
```
---
## Path Alias
| Alias | 경로 | 용도 |
|-------|------|------|
| `@/*` | `src/*` | 프로젝트 전체 절대 임포트 |
| `@lib/*` | `src/lib/*` | 공통 라이브러리 (charts, map, i18n, theme) |
| `@shared/*` | `src/shared/*` | 공유 UI 컴포넌트 |
| `@features/*` | `src/features/*` | 도메인 feature 모듈 |
| `@data/*` | `src/data/*` | 기준정보 + mock 데이터 |
| `@stores/*` | `src/stores/*` | Zustand 스토어 |
Vite `resolve.alias`와 TypeScript `compilerOptions.paths`에 동일하게 설정되어 있다.
---
## 의존성
### 프로덕션 (11개)
| 패키지 | 버전 | 용도 |
|--------|------|------|
| react | ^19.2.4 | UI 렌더링 |
| react-dom | ^19.2.4 | DOM 렌더링 |
| react-router-dom | ^7.12.0 | SPA 라우팅 |
| maplibre-gl | ^5.22.0 | 래스터 베이스맵 엔진 |
| deck.gl | ^9.2.11 | GPU 벡터 렌더링 (ScatterplotLayer, PathLayer 등) |
| @deck.gl/mapbox | ^9.2.11 | MapboxOverlay (MapLibre 인터리브) |
| echarts | ^6.0.0 | 차트 라이브러리 |
| zustand | ^5.0.12 | 경량 상태관리 (8개 스토어) |
| class-variance-authority | ^0.7.1 | Tailwind 변형 관리 (CVA) |
| react-i18next | ^17.0.2 | React 다국어 바인딩 |
| i18next | ^26.0.3 | 다국어 코어 |
| lucide-react | 0.487.0 | SVG 아이콘 |
### 개발 (13개)
| 패키지 | 버전 | 용도 |
|--------|------|------|
| vite | ^8.0.3 | 빌드/개발 서버 (Rolldown) |
| @vitejs/plugin-react | ^6.0.1 | React Fast Refresh |
| tailwindcss | ^4.2.2 | 유틸리티 CSS |
| @tailwindcss/vite | ^4.2.2 | Tailwind Vite 플러그인 |
| typescript | 5.9 | 타입 체크 |
| eslint | ^10.2.0 | 린트 (Flat Config) |
| @eslint/js | ^10.0.1 | ESLint JS 설정 |
| typescript-eslint | ^8.58.0 | TS ESLint 파서/규칙 |
| eslint-plugin-react-hooks | ^7.0.1 | Hooks 규칙 |
| eslint-plugin-react-refresh | ^0.5.2 | Fast Refresh 규칙 |
| globals | ^17.4.0 | 전역 변수 정의 |
| @types/react | ^19.2.14 | React 타입 |
| @types/react-dom | ^19.2.3 | ReactDOM 타입 |
---
## 공통 모듈 API 요약
### lib/map — BaseMap + deck.gl 최적화
**BaseMap** — MapLibre + deck.gl 통합 컴포넌트 (`forwardRef`, `memo`)
```typescript
interface BaseMapProps {
center?: [number, number]; // [lat, lng] 기본값 [35.5, 127.0]
zoom?: number; // 기본값 7
className?: string;
style?: React.CSSProperties;
height?: number | string; // 기본값 '100%'
layers?: Layer[]; // @deprecated — useMapLayers hook 사용 권장
onMapReady?: (map: Map) => void; // 지도 로드 완료 콜백
onClick?: (info: unknown) => void;
interactive?: boolean; // 기본 true
}
// ref를 통해 MapHandle 노출
interface MapHandle {
overlay: MapboxOverlay | null; // deck.gl overlay 직접 접근
}
```
- `useImperativeHandle`로 overlay 외부 노출 → hook에서 직접 `setProps()` 호출
- CartoDB Dark/Light 래스터 타일 자동 전환 (`settingsStore.theme` 구독)
- `interactive=false`로 정적 지도 프리뷰 생성 가능
**useMapLayers** — RAF 배치 레이어 업데이트 (React 리렌더 0회)
```typescript
useMapLayers(
handleRef: RefObject<MapHandle>, // BaseMap ref
buildLayers: () => Layer[], // 레이어 빌드 함수
deps: unknown[], // 변경 감지 (shallow 비교)
)
```
**useStoreLayerSync** — Zustand store.subscribe + RAF 기반 레이어 동기화
```typescript
useStoreLayerSync<T>(
handleRef: RefObject<MapHandle>,
subscribe: (cb: (state: T) => void) => () => void,
buildLayers: (state: T) => Layer[],
)
```
**STATIC_LAYERS** — EEZ + NLL 싱글턴 (모듈 로드 시 1회 생성, GPU 재전송 없음)
**createMarkerLayer** — ScatterplotLayer 생성 (transitions 보간 + DataFilterExtension)
### lib/charts — ECharts 래퍼
**BaseChart** — ECharts 코어 래퍼 컴포넌트
```typescript
interface BaseChartProps {
option: EChartsOption;
className?: string;
style?: React.CSSProperties;
height?: number; // 기본값 200
notMerge?: boolean;
onEvents?: Record<string, (params: unknown) => void>;
}
```
- `echarts.init(container, 'kcg-dark')` 프로젝트 다크 테마 자동 적용
- `ResizeObserver` 자동 리사이즈, unmount 시 `dispose` 자동 정리
**프리셋 차트**
| 컴포넌트 | 주요 Props | 설명 |
|----------|-----------|------|
| `AreaChart` | `data`, `xKey`, `series`, `yAxisDomain?` | smooth line + 반투명 영역 |
| `BarChart` | `data`, `xKey`, `series`, `horizontal?`, `itemColors?` | 수직/수평 막대, 항목별 색상 |
| `PieChart` | `data: {name, value, color?}[]`, `innerRadius?`, `outerRadius?` | 파이/도넛 (기본 도넛) |
| `LineChart` | `data`, `xKey`, `series` | smooth 라인, 원형 심볼 |
### lib/theme — CVA 변형 + 디자인 토큰
| 모듈 | 내용 |
|------|------|
| `variants.ts` | `cardVariants` (default/elevated/inner/transparent), `badgeVariants` (8 intent x 4 size), `statusDotVariants` (4 status x 3 size) |
| `tokens.ts` | `cssVars` (CSS 변수 참조), `resolvedColors` (ECharts/MapLibre용 하드코딩 값) |
| `colors.ts` | `riskColors` (5단계), `alertStyles`, `vesselColors`, `statusColors`, `chartSeriesColors` (8색 팔레트) |
### lib/i18n — 10 네임스페이스 다국어
- `i18next` + `react-i18next` 기반
- 기본 언어: `ko`, 폴백: `ko`, 지원: `ko` / `en`
- 10 네임스페이스: `common`, `dashboard`, `detection`, `patrol`, `enforcement`, `statistics`, `ai`, `fieldOps`, `admin`, `auth`
- 사용: `useTranslation('namespace')``t('key')`
- 언어 전환: `settingsStore.toggleLanguage()` + `localStorage` 동기화
---
## 렌더링 최적화 아키텍처
```
store 변경 → useStoreLayerSync → RAF → overlay.setProps() (React 리렌더 0회)
deps 변경 → useMapLayers → RAF → overlay.setProps() (React 리렌더 0회)
정적 레이어 → STATIC_LAYERS 싱글턴 (GPU 1회 업로드, 모든 페이지 공유)
동적 레이어 → transitions 보간 + DataFilterExtension (GPU 필터링)
시계/타이머 → useRef + DOM textContent 직접 조작 (setState 0회)
```
핵심 원칙: **React render cycle 완전 우회**. deck.gl overlay에 직접 `setProps()`를 호출하여 40만척+ 실시간 렌더링 시에도 React 리렌더가 발생하지 않는다.
---
## 테마 시스템
- **기본값**: `:root` = dark 테마, `.light` 클래스 오버라이드
- **시맨틱 CSS 변수**: `surface-raised`, `surface-overlay`, `text-heading`, `text-label`, `text-hint`, `border`
- **Tailwind 통합**: `@theme inline`으로 CSS 변수를 Tailwind 유틸리티에 매핑
- **settingsStore**: `theme` / `language` + `localStorage` 자동 동기화
- **지도 타일**: CartoDB Dark Matter ↔ CartoDB Positron 자동 전환 (`settingsStore.theme` 구독)
- **CVA 변형**: `cardVariants`, `badgeVariants`, `statusDotVariants`가 CSS 변수 참조 → 테마 자동 반응
---
## 라우팅 구조 (29 보호 경로 + login)
`App.tsx`에서 `BrowserRouter` > `AuthProvider` > `Routes`로 구성된다.
- `/login` — 비보호 라우트 (LoginPage)
- `/``ProtectedRoute` > `MainLayout` (사이드바 + Outlet)
- `/``/dashboard` 리다이렉트
- `/dashboard` — 종합 대시보드 (SFR-12)
- `/monitoring` — 실시간 모니터링
- `/risk-map` — 위험도 평가 (SFR-05)
- `/enforcement-plan` — 단속계획 (SFR-06)
- `/dark-vessel` — 무등화 선박 탐지 (SFR-09)
- `/gear-detection` — 어구 탐지 (SFR-10)
- `/gear-collision` — 어구 정체성 충돌 (SFR-10, V030 — 동일 어구 이름 × 복수 MMSI 공존 감지)
- `/illegal-fishing` — 불법 조업 이벤트 통합 대시보드 (SFR-09/10/11, V032 — GEAR_ILLEGAL+EEZ_INTRUSION+ZONE_DEPARTURE 3 카테고리)
- `/transshipment` — 환적 의심 전용 탐지 대시보드 (SFR-09, V033 — prediction 5단계 필터 결과)
- `/china-fishing` — 중국어선 탐지
- `/patrol-route` — 순찰경로 (SFR-07)
- `/fleet-optimization` — 함대 최적화 (SFR-08)
- `/enforcement-history` — 단속 이력 (SFR-11)
- `/event-list` — 이벤트 목록
- `/mobile-service` — 현장 모바일 (SFR-15)
- `/ship-agent` — 함정 에이전트 (SFR-16)
- `/ai-alert` — AI 경보 (SFR-17)
- `/statistics` — 통계 (SFR-13)
- `/external-service` — 외부연계 (SFR-14)
- `/reports` — 보고서 관리
- `/ai-model` — AI 모델 관리 (SFR-04)
- `/mlops` — MLOps (SFR-18~19)
- `/ai-assistant` — AI 어시스턴트 (SFR-20)
- `/data-hub` — 데이터허브 (SFR-03)
- `/system-config` — 환경설정 (SFR-02)
- `/notices` — 공지사항
- `/access-control` — 접근 권한 (SFR-01)
- `/admin` — 시스템 관리
- `/events` — 감시 (LiveMapView)
- `/map-control` — 지도 컨트롤
- `/vessel/:id` — 선박 상세
인증은 `AuthContext``useAuth().user` 존재 여부로 판단하며, 미인증 시 `/login`으로 리다이렉트한다.
---
## 빌드 설정
- **TypeScript**: `target: ES2020`, `module: ESNext`, `moduleResolution: bundler`, `strict: true`
- **Vite**: `react()` + `tailwindcss()` 플러그인, 6개 path alias (`@`, `@lib`, `@shared`, `@features`, `@data`, `@stores`)
- **ESLint 10 Flat Config**: `typescript-eslint` + `react-hooks` + `react-refresh` 규칙
- **빌드 속도**: Rolldown 기반 ~480ms
---
## 현재 아키텍처 특성
1. **Zustand 8개 스토어**: vessel, patrol, event, kpi, transfer, gear, enforcement, settings. `settingsStore`는 theme/language + localStorage 동기화 담당.
2. **deck.gl GPU 렌더링**: MapLibre(래스터 베이스맵) + deck.gl(벡터). React 리렌더 완전 분리, RAF 기반 `overlay.setProps()` 직접 호출.
3. **CVA 스타일 시스템**: `cardVariants`, `badgeVariants`, `statusDotVariants`로 Tailwind 패턴 통합. CSS 변수 기반 테마 반응.
4. **mock 기반 서비스 계층**: 7개 API 서비스가 `data/mock/` 모듈에서 데이터 반환. 향후 Axios + 실제 API로 교체 예정.
5. **i18n 10 NS 구조 완성**: 리소스 파일 완비, MainLayout 메뉴 + 페이지 제목 + LoginPage 적용 완료. 페이지 내부 텍스트는 대부분 한국어 하드코딩 잔존.
6. **Dark/Light 테마**: CSS 변수 기반 양방향 테마. 지도 타일 자동 전환. 일부 alert 색상(`red-500/20` 등) 하드코딩 잔존.
7. **단일 번들**: 코드 스플리팅 미적용 (~3.2MB), React.lazy 미사용. 모든 feature가 단일 번들로 빌드.
8. **WebSocket 미구현**: `connectWs` 스텁만 존재, STOMP.js + SockJS 미설치.