KCG AI 기반 불법조업 탐지·차단 플랫폼 프론트엔드. React 19 + TypeScript 5.9 + Vite 8 + MapLibre + deck.gl + Zustand + Tailwind CSS. SFR 20개 전체 UI 구현 완료, 백엔드 연동 대기. - npm + Nexus 프록시 레지스트리 설정 - 팀 워크플로우 v1.6.1 부트스트랩 파일 배치 - .githooks (commit-msg, post-checkout) - package.json name: kcg-ai-monitoring v0.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
253 lines
14 KiB
Markdown
253 lines
14 KiB
Markdown
# Mock 데이터 공유 현황 분석 및 통합 결과
|
|
|
|
> 최초 작성일: 2026-04-06
|
|
> 마지막 업데이트: 2026-04-06
|
|
> 대상: `kcg-ai-monitoring` 프론트엔드 코드베이스 전체 (31개 페이지)
|
|
> 상태: **통합 완료**
|
|
|
|
---
|
|
|
|
## 1. 선박 데이터 교차참조
|
|
|
|
현재 동일한 선박 데이터가 여러 컴포넌트에 독립적으로 하드코딩되어 있다. 각 파일마다 동일 선박의 속성(위험도, 위치, 상태 등)이 서로 다른 형식과 값으로 중복 정의되어 있어 데이터 일관성 문제가 발생한다.
|
|
|
|
| 선박명 | 등장 파일 수 | 파일 목록 |
|
|
|---|---|---|
|
|
| 鲁荣渔56555 | 7+ | Dashboard, MobileService, LiveMapView, MonitoringDashboard, EventList, EnforcementHistory, ChinaFishing |
|
|
| 浙甬渔60651 | 4 | Dashboard, LiveMapView, EventList, DarkVesselDetection |
|
|
| 冀黄港渔05001 | 6 | MobileService, LiveMapView, Dashboard, TransferDetection, EventList, GearDetection |
|
|
| 3001함 | 6+ | ShipAgent, MobileService, LiveMapView, Dashboard, PatrolRoute, FleetOptimization |
|
|
| 3009함 | 6+ | ShipAgent, MobileService, Dashboard, PatrolRoute, FleetOptimization, AIAlert |
|
|
| 미상선박-A | 5 | MobileService, Dashboard, LiveMapView, MonitoringDashboard, EventList |
|
|
|
|
### 문제점
|
|
- 하나의 선박이 평균 5~7개 파일에 중복 정의됨
|
|
- 선박 속성(이름, MMSI, 위치, 위험도, 상태)이 파일마다 미세하게 다를 수 있음
|
|
- 새 선박 추가/수정 시 모든 관련 파일을 일일이 찾아 수정해야 함
|
|
|
|
---
|
|
|
|
## 2. 위험도 스케일 불일치
|
|
|
|
동일한 선박의 위험도가 페이지마다 서로 다른 스케일로 표현되고 있다.
|
|
|
|
| 선박명 | Dashboard (risk) | DarkVesselDetection (risk) | MonitoringDashboard |
|
|
|---|---|---|---|
|
|
| 鲁荣渔56555 | **0.96** (0~1 스케일) | - | **CRITICAL** (레벨 문자열) |
|
|
| 浙甬渔60651 | **0.85** (0~1 스케일) | **94** (0~100 정수) | - |
|
|
| 미상선박-A | **0.94** (0~1 스케일) | **96** (0~100 정수) | - |
|
|
|
|
### 원인 분석
|
|
- Dashboard는 `risk: 0.96` 형식 (0~1 소수)
|
|
- DarkVesselDetection은 `risk: 96` 형식 (0~100 정수)
|
|
- MonitoringDashboard는 `'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'` 레벨 문자열
|
|
- LiveMapView는 `risk: 0.94` 형식 (0~1 소수)
|
|
- EventList는 레벨 문자열 (`AlertLevel`)
|
|
|
|
### 통합 방안
|
|
위험도를 **0~100 정수** 스케일로 통일하되, 레벨 문자열은 구간별 자동 매핑 유틸로 변환한다.
|
|
|
|
```
|
|
0~30: LOW | 31~60: MEDIUM | 61~85: HIGH | 86~100: CRITICAL
|
|
```
|
|
|
|
---
|
|
|
|
## 3. KPI 수치 중복
|
|
|
|
Dashboard와 MonitoringDashboard가 **완전히 동일한 KPI 수치**를 독립적으로 정의하고 있다.
|
|
|
|
| 지표 | Dashboard `KPI_DATA` | MonitoringDashboard `KPI` |
|
|
|---|---|---|
|
|
| 실시간 탐지 | 47 | 47 |
|
|
| EEZ 침범 | 18 | 18 |
|
|
| 다크베셀 | 12 | 12 |
|
|
| 불법환적 의심 | 8 | 8 |
|
|
| 추적 중 | 15 | 15 |
|
|
| 나포/검문(금일 단속) | 3 | 3 |
|
|
|
|
### 문제점
|
|
- 6개 KPI 수치가 두 파일에 100% 동일하게 하드코딩
|
|
- 수치 변경 시 양쪽 모두 수정해야 함
|
|
- Dashboard에는 `prev` 필드(전일 비교)가 추가로 있으나, Monitoring에는 없음
|
|
|
|
---
|
|
|
|
## 4. 이벤트 타임라인 중복
|
|
|
|
08:47~06:12 시계열 이벤트가 최소 4개 파일에 각각 정의되어 있다.
|
|
|
|
| 시각 | Dashboard | Monitoring | MobileService | EventList |
|
|
|---|---|---|---|---|
|
|
| 08:47 | EEZ 침범 (鲁荣渔56555) | EEZ 침범 (鲁荣渔56555 외 2척) | [긴급] EEZ 침범 탐지 | EVT-0001 EEZ 침범 |
|
|
| 08:32 | 다크베셀 출현 | 다크베셀 출현 | 다크베셀 출현 | EVT-0002 다크베셀 |
|
|
| 08:15 | 선단 밀집 경보 | 선단 밀집 경보 | - | EVT-0003 선단밀집 |
|
|
| 07:58 | 불법환적 의심 | 불법환적 의심 | 환적 의심 | EVT-0004 불법환적 |
|
|
| 07:41 | MMSI 변조 탐지 | MMSI 변조 탐지 | - | EVT-0005 MMSI 변조 |
|
|
| 07:23 | 함정 검문 완료 | 함정 검문 완료 | - | EVT-0006 검문 완료 |
|
|
| 06:12 | 속력 이상 탐지 | - | - | EVT-0010 속력 이상 |
|
|
|
|
### 문제점
|
|
- 동일 이벤트의 description이 파일마다 미세하게 다름 (예: "鲁荣渔56555" vs "鲁荣渔56555 외 2척")
|
|
- EventList에는 ID가 있으나(EVT-xxxx), 다른 파일에는 없음
|
|
- Dashboard에는 10개, Monitoring에는 6개, EventList에는 15개로 **건수도 불일치**
|
|
|
|
---
|
|
|
|
## 5. 환적 데이터 100% 중복
|
|
|
|
`TransferDetection.tsx`와 `ChinaFishing.tsx`에 **TR-001~TR-003 환적 데이터가 완전히 동일**하게 정의되어 있다.
|
|
|
|
```
|
|
TransferDetection.tsx:
|
|
const transferData = [
|
|
{ id: 'TR-001', time: '2026-01-20 13:42:11', a: {name:'장저우8호'}, b: {name:'黑江9호'}, ... },
|
|
{ id: 'TR-002', time: '2026-01-20 11:15:33', ... },
|
|
{ id: 'TR-003', time: '2026-01-20 09:23:45', ... },
|
|
];
|
|
|
|
ChinaFishing.tsx:
|
|
const TRANSFER_DATA = [
|
|
{ id: 'TR-001', time: '2026-01-20 13:42:11', a: {name:'장저우8호'}, b: {name:'黑江9호'}, ... },
|
|
{ id: 'TR-002', time: '2026-01-20 11:15:33', ... },
|
|
{ id: 'TR-003', time: '2026-01-20 09:23:45', ... },
|
|
];
|
|
```
|
|
|
|
### 문제점
|
|
- 변수명만 다르고 (`transferData` vs `TRANSFER_DATA`) 데이터 구조와 값이 100% 동일
|
|
- 한쪽만 수정하면 다른 쪽과 불일치 발생
|
|
|
|
---
|
|
|
|
## 6. 함정 상태 불일치
|
|
|
|
동일 함정의 상태가 페이지마다 모순되는 경우가 확인되었다.
|
|
|
|
| 함정 | ShipAgent | Dashboard | PatrolRoute | FleetOptimization |
|
|
|---|---|---|---|---|
|
|
| 5001함 | **오프라인** (`status: '오프라인'`) | **가용** (PATROL_SHIPS에 대기로 표시) | **가용** (`status: '가용'`) | **가용** (`status: '가용'`) |
|
|
| 3009함 | **온라인** (동기화 중) | **검문 중** | **출동중** | **출동중** |
|
|
| 1503함 | **미배포** | - | - | **정비중** |
|
|
|
|
### 문제점
|
|
- 5001함이 ShipAgent에서는 오프라인이지만, Dashboard/PatrolRoute/FleetOptimization에서는 가용으로 표시됨 -- **직접적 모순**
|
|
- 3009함의 상태가 "온라인", "검문 중", "출동중"으로 파일마다 다름
|
|
- 실제 운영 시 혼란을 초래할 수 있는 시나리오 불일치
|
|
|
|
---
|
|
|
|
## 7. 현재 상태: 통합 완료
|
|
|
|
아래 분석에서 식별한 모든 중복/불일치 문제를 해소하기 위해, 7개 공유 Mock 모듈 + 7개 Zustand 스토어 체계로 통합이 **완료**되었다.
|
|
|
|
### 7.1 완료된 아키텍처: mock -> store -> page
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ src/data/mock/ (7개 공유 모듈) │
|
|
├───────────┬──────────┬──────────┬────────┬───────────┬────────┬────────┤
|
|
│ vessels │ patrols │ events │ kpi │ transfers │ gear │enforce-│
|
|
│ .ts │ .ts │ .ts │ .ts │ .ts │ .ts │ment.ts │
|
|
└─────┬─────┴─────┬────┴─────┬────┴───┬────┴─────┬────┴───┬────┴───┬────┘
|
|
│ │ │ │ │ │ │
|
|
▼ ▼ ▼ ▼ ▼ ▼ ▼
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ src/stores/ (7개 Zustand 스토어 + settingsStore) │
|
|
├───────────┬──────────┬──────────┬────────┬───────────┬────────┬────────┤
|
|
│ vessel │ patrol │ event │ kpi │ transfer │ gear │enforce-│
|
|
│ Store │ Store │ Store │ Store │ Store │ Store │mentStr │
|
|
└─────┬─────┴─────┬────┴─────┬────┴───┬────┴─────┬────┴───┬────┴───┬────┘
|
|
│ │ │ │ │ │ │
|
|
▼ ▼ ▼ ▼ ▼ ▼ ▼
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ src/features/*/ (페이지 컴포넌트) │
|
|
│ store.load() 호출 -> store에서 데이터 구독 -> 뷰 변환은 페이지 책임 │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 7.2 스토어별 소비 현황 (16개 페이지가 스토어 사용)
|
|
|
|
| 스토어 | 소비 페이지 |
|
|
|---|---|
|
|
| `useVesselStore` | Dashboard, LiveMapView, DarkVesselDetection, VesselDetail |
|
|
| `usePatrolStore` | Dashboard, PatrolRoute, FleetOptimization |
|
|
| `useEventStore` | Dashboard, MonitoringDashboard, LiveMapView, EventList, MobileService, AIAlert |
|
|
| `useKpiStore` | Dashboard, MonitoringDashboard, Statistics |
|
|
| `useTransferStore` | TransferDetection, ChinaFishing |
|
|
| `useGearStore` | GearDetection |
|
|
| `useEnforcementStore` | EnforcementPlan, EnforcementHistory |
|
|
|
|
### 7.3 페이지 전용 인라인 데이터 (미통합)
|
|
|
|
아래 페이지들은 도메인 특성상 공유 mock에 포함하지 않고 페이지 전용 인라인 데이터를 유지한다.
|
|
|
|
| 페이지 | 인라인 데이터 | 사유 |
|
|
|---|---|---|
|
|
| ChinaFishing | `COUNTERS_ROW1/2`, `VESSEL_LIST`, `MONTHLY_DATA`, `VTS_ITEMS` | 중국어선 전용 센서 카운터/통계 (다른 페이지에서 미사용) |
|
|
| VesselDetail | `VESSELS: VesselTrack[]` | 항적 데이터 구조가 `VesselData`와 다름 (주석으로 명시) |
|
|
| MLOpsPage | 실험/배포 데이터 | MLOps 전용 도메인 데이터 |
|
|
| MapControl | 훈련구역 데이터 | 해상사격 훈련구역 전용 |
|
|
| DataHub | 수신현황 데이터 | 데이터 허브 전용 모니터링 |
|
|
| AIModelManagement | 모델/규칙 데이터 | AI 모델 관리 전용 |
|
|
| AIAssistant | `SAMPLE_CONVERSATIONS` | 챗봇 샘플 대화 |
|
|
| LoginPage | `DEMO_ACCOUNTS` | 데모 인증 정보 |
|
|
| 기타 (AdminPanel, SystemConfig 등) | 각 페이지 전용 설정/관리 데이터 | 관리 도메인 특화 |
|
|
|
|
### 7.4 설계 원칙 (구현 완료)
|
|
|
|
1. **위험도 0~100 통일**: 모든 선박의 위험도를 0~100 정수로 통일. 레벨 문자열은 유틸 함수로 변환.
|
|
2. **단일 원천(Single Source of Truth)**: 각 데이터는 하나의 mock 모듈에서만 정의하고, 스토어를 통해 접근.
|
|
3. **Lazy Loading**: 스토어의 `load()` 메서드가 최초 호출 시 `import()`로 mock 데이터를 동적 로딩 (loaded 플래그로 중복 방지).
|
|
4. **뷰 변환은 페이지 책임**: mock 모듈/스토어는 원본 데이터만 제공하고, 화면별 가공(필터, 정렬, 포맷)은 각 페이지에서 수행.
|
|
|
|
### 7.5 Mock 모듈 상세 (참고용)
|
|
|
|
참고: 초기 분석에서 계획했던 `areas.ts`는 최종 구현 시 `enforcement.ts`(단속 이력 데이터)로 대체되었다.
|
|
해역/구역 데이터는 RiskMap, MapControl 등 각 페이지에서 전용 데이터로 관리한다.
|
|
|
|
| # | 모듈 파일 | 스토어 | 내용 |
|
|
|---|---|---|---|
|
|
| 1 | `data/mock/vessels.ts` | `vesselStore` | 중국어선 + 한국어선 + 미상선박 마스터 (`MOCK_VESSELS`, `MOCK_SUSPECTS`) |
|
|
| 2 | `data/mock/patrols.ts` | `patrolStore` | 경비함정 마스터 + 경로/시나리오/커버리지 |
|
|
| 3 | `data/mock/events.ts` | `eventStore` | 이벤트 타임라인 + 알림 데이터 |
|
|
| 4 | `data/mock/kpi.ts` | `kpiStore` | KPI 수치 + 월별 추이 |
|
|
| 5 | `data/mock/transfers.ts` | `transferStore` | 환적 데이터 (TR-001~003) |
|
|
| 6 | `data/mock/gear.ts` | `gearStore` | 어구 데이터 (불법어구 목록) |
|
|
| 7 | `data/mock/enforcement.ts` | `enforcementStore` | 단속 이력 + 단속 계획 데이터 |
|
|
|
|
---
|
|
|
|
## 8. 작업 완료 요약
|
|
|
|
| 모듈 | 상태 | 스토어 소비 페이지 수 |
|
|
|---|---|---|
|
|
| `vessels.ts` | **완료** | 4개 (useVesselStore) |
|
|
| `events.ts` | **완료** | 6개 (useEventStore) |
|
|
| `patrols.ts` | **완료** | 3개 (usePatrolStore) |
|
|
| `kpi.ts` | **완료** | 3개 (useKpiStore) |
|
|
| `transfers.ts` | **완료** | 2개 (useTransferStore) |
|
|
| `gear.ts` | **완료** | 1개 (useGearStore) |
|
|
| `enforcement.ts` | **완료** | 2개 (useEnforcementStore) |
|
|
|
|
### 실제 작업 결과
|
|
- Mock 모듈 생성: 7개 파일 (`src/data/mock/`)
|
|
- Zustand 스토어 생성: 7개 + 1개 설정용 (`src/stores/`)
|
|
- 기존 페이지 리팩토링: 16개 페이지에서 스토어 소비로 전환
|
|
- 나머지 15개 페이지: 도메인 특화 인라인 데이터 유지 (공유 필요성 없음)
|
|
|
|
---
|
|
|
|
## 9. 결론
|
|
|
|
위 1~6절에서 분석한 6개의 심각한 중복/불일치 문제(위험도 스케일, 함정 상태 모순, KPI 중복, 이벤트 불일치, 환적 100% 중복, 선박 교차참조)는 **7개 공유 mock 모듈 + 7개 Zustand 스토어** 도입으로 모두 해소되었다.
|
|
|
|
달성한 효과:
|
|
- **데이터 일관성**: Single Source of Truth로 불일치 원천 차단
|
|
- **유지보수성**: 데이터 변경 시 mock 모듈 1곳만 수정
|
|
- **확장성**: 신규 페이지 추가 시 기존 store import로 즉시 사용
|
|
- **코드 품질**: 중복 인라인 데이터 제거, 16개 페이지가 스토어 기반으로 전환
|
|
- **성능**: Zustand lazy loading으로 최초 접근 시에만 mock 데이터 로딩
|
|
|
|
1~6절의 분석 내용은 통합 전 문제 식별 기록으로 보존한다.
|