From 831045ace9c088253fcf109e493768154864acd2 Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 17 Apr 2026 06:52:51 +0900 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=82=B0=EC=B6=9C=EB=AC=B8=EC=84=9C=202026-04-17?= =?UTF-8?q?=20=EA=B8=B0=EC=A4=80=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/architecture.md: shared/components/ui 9개·i18n 네임스페이스 갱신 - docs/sfr-traceability.md: v3.0 전면 재작성 (운영 상태 기반 531 라인) - docs/sfr-user-guide.md: 헤더 + SFR-01/02/09/10/11/12/13/17 구현 현황 갱신 - docs/data-sharing-analysis.md / next-refactoring.md / page-workflow.md: stale 3건 제거 --- docs/architecture.md | 21 +- docs/data-sharing-analysis.md | 252 -------- docs/next-refactoring.md | 194 ------ docs/page-workflow.md | 436 -------------- docs/sfr-traceability.md | 1040 +++++++++++---------------------- docs/sfr-user-guide.md | 224 +++---- 6 files changed, 466 insertions(+), 1701 deletions(-) delete mode 100644 docs/data-sharing-analysis.md delete mode 100644 docs/next-refactoring.md delete mode 100644 docs/page-workflow.md diff --git a/docs/architecture.md b/docs/architecture.md index 7d4200e..5226871 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -50,6 +50,7 @@ src/ │ ├── 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) @@ -89,20 +90,28 @@ src/ │ ├── ws.ts # connectWs (STOMP 스텁, 미구현) │ └── index.ts # 배럴 export │ -├── shared/components/ # 공유 UI 컴포넌트 -│ ├── ui/ -│ │ ├── card.tsx # Card(CVA variant), CardHeader, CardTitle, CardContent -│ │ └── badge.tsx # Badge(CVA intent/size) +├── 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 # 검색 입력 +│ ├── SearchInput.tsx # 검색 입력 (i18n 통합) │ ├── ExcelExport.tsx # 엑셀 다운로드 │ ├── FileUpload.tsx # 파일 업로드 │ ├── PageToolbar.tsx # 페이지 상단 툴바 │ ├── PrintButton.tsx # 인쇄 버튼 │ ├── SaveButton.tsx # 저장 버튼 -│ └── NotificationBanner.tsx # 알림 배너 +│ └── NotificationBanner.tsx # 알림 배너 (common.aria.closeNotification) │ ├── features/ # 13 도메인 그룹 (31 페이지) │ ├── dashboard/ # 종합 대시보드 (Dashboard) diff --git a/docs/data-sharing-analysis.md b/docs/data-sharing-analysis.md deleted file mode 100644 index a6179a3..0000000 --- a/docs/data-sharing-analysis.md +++ /dev/null @@ -1,252 +0,0 @@ -# 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절의 분석 내용은 통합 전 문제 식별 기록으로 보존한다. diff --git a/docs/next-refactoring.md b/docs/next-refactoring.md deleted file mode 100644 index f9804fa..0000000 --- a/docs/next-refactoring.md +++ /dev/null @@ -1,194 +0,0 @@ -# KCG AI Monitoring - 다음 단계 리팩토링 TODO - -> 프론트엔드 UI 스캐폴딩 + 기반 인프라(상태관리, 지도 GPU, mock 데이터, CVA) 완료 상태. 백엔드 연동 및 운영 품질 확보를 위해 남은 항목을 순차적으로 진행한다. - ---- - -## 1. ✅ 상태관리 도입 (Zustand 5.0) — COMPLETED - -`zustand` 5.0.12 설치, `src/stores/`에 8개 독립 스토어 구현 완료. - -- `vesselStore` — 선박 목록, 선택, 필터 -- `patrolStore` — 순찰 경로/함정 -- `eventStore` — 탐지/경보 이벤트 -- `kpiStore` — KPI 메트릭, 추세 -- `transferStore` — 전재(환적) -- `gearStore` — 어구 탐지 -- `enforcementStore` — 단속 이력 -- `settingsStore` — theme/language + localStorage 동기화, 지도 타일 자동 전환 - -> `AuthContext`는 유지 (인증은 Context API가 적합, 마이그레이션 불필요로 결정) - ---- - -## 2. API 서비스 계층 (Axios 1.14) — 구조 완성, 실제 연동 대기 - -### 현재 상태 -- `src/services/`에 7개 서비스 모듈 구현 (api, vessel, event, patrol, kpi, ws, index) -- `api.ts`: fetch 래퍼 (`apiGet`, `apiPost`) — 향후 Axios 교체 예정 -- 각 서비스가 `data/mock/` 모듈에서 mock 데이터 반환 (실제 HTTP 호출 0건) -- `ws.ts`: STOMP WebSocket 스텁 존재, 미구현 - -### 남은 작업 -- [ ] `axios` 1.14 설치 → `api.ts`의 fetch 래퍼를 Axios 인스턴스로 교체 -- [ ] Axios 인터셉터: - - Request: Authorization 헤더 자동 주입 - - Response: 401 → 로그인 리다이렉트, 500 → 에러 토스트 -- [ ] `@tanstack/react-query` 5.x 설치 → TanStack Query Provider 추가 -- [ ] 각 서비스의 mock 반환을 실제 API 호출로 교체 -- [ ] 로딩 스켈레톤, 에러 바운더리 공통 컴포넌트 - ---- - -## 3. 실시간 인프라 (STOMP.js + SockJS) — 스텁 구조만 존재 - -### 현재 상태 -- `services/ws.ts`에 `connectWs` 스텁 함수 존재 (인터페이스 정의 완료) -- STOMP.js, SockJS 미설치 — 실제 WebSocket 연결 없음 -- `useStoreLayerSync` hook으로 store→지도 실시간 파이프라인 준비 완료 - -### 남은 작업 -- [ ] `@stomp/stompjs` + `sockjs-client` 설치 -- [ ] `ws.ts` 스텁을 실제 STOMP 클라이언트로 구현 -- [ ] 구독 채널 설계: - - `/topic/ais-positions` — 실시간 AIS 위치 - - `/topic/alerts` — 경보/이벤트 - - `/topic/detections` — 탐지 결과 - - `/user/queue/notifications` — 개인 알림 -- [ ] 재연결 로직 (지수 백오프) -- [ ] store → `useStoreLayerSync` → 지도 마커 실시간 업데이트 연결 -- [ ] `eventStore`와 연동하여 알림 배너/뱃지 카운트 업데이트 - ---- - -## 4. ✅ 고급 지도 레이어 (deck.gl 9.2) — COMPLETED - -`deck.gl` 9.2.11 + `@deck.gl/mapbox` 설치, MapLibre + deck.gl 인터리브 아키텍처 구현 완료. - -- **BaseMap**: `forwardRef` + `memo`, `MapboxOverlay`를 `useImperativeHandle`로 외부 노출 -- **useMapLayers**: RAF 배치 레이어 업데이트, React 리렌더 0회 -- **useStoreLayerSync**: Zustand store.subscribe → RAF → overlay.setProps (React 우회) -- **STATIC_LAYERS**: EEZ + NLL PathLayer 싱글턴 (GPU 1회 업로드) -- **createMarkerLayer**: ScatterplotLayer + transitions 보간 + DataFilterExtension -- **createRadiusLayer**: 반경 원 표시용 ScatterplotLayer -- 레거시 GeoJSON 레이어(`boundaries.ts`)는 하위 호환으로 유지 - -> 성능 목표 40만척+ GPU 렌더링 달성. TripsLayer/HexagonLayer/IconLayer는 실데이터 확보 후 추가 예정. - ---- - -## 5. ✅ 더미 데이터 통합 — COMPLETED - -`src/data/mock/`에 7개 공유 mock 모듈 구현 완료. TypeScript 인터페이스 정의 포함. - -``` -data/mock/ -├── vessels.ts # VesselData — 선박 목록 (한국, 중국, 경비함) -├── events.ts # EventRecord, AlertRecord — 탐지/단속 이벤트 -├── transfers.ts # 전재(환적) 데이터 -├── patrols.ts # PatrolShip — 순찰 경로/함정 -├── gear.ts # 어구 탐지 데이터 -├── kpi.ts # KpiMetric, MonthlyTrend, ViolationType -└── enforcement.ts # 단속 이력 데이터 -``` - -- `services/` 계층이 mock 모듈을 import하여 반환 → 향후 API 교체 시 서비스만 수정 -- 인터페이스가 API 응답 타입 계약 역할 수행 - ---- - -## 6. i18n 실적용 — 구조 완성, 내부 텍스트 미적용 - -### 현재 상태 -- 10 네임스페이스 리소스 완비: common, dashboard, detection, patrol, enforcement, statistics, ai, fieldOps, admin, auth -- ko/en 각 10파일 (총 20 JSON) -- `settingsStore.toggleLanguage()` + `localStorage` 동기화 구현 완료 -- **적용 완료**: MainLayout 사이드바 메뉴명, 24개 페이지 제목, LoginPage -- **미적용**: 각 페이지 내부 텍스트 (카드 레이블, 테이블 헤더, 상태 텍스트 등) — 대부분 한국어 하드코딩 잔존 - -### 남은 작업 -- [ ] 각 feature 페이지 내부 텍스트를 `useTranslation('namespace')` + `t()` 로 교체 -- [ ] 날짜/숫자 포맷 로컬라이즈 (`Intl.DateTimeFormat`, `Intl.NumberFormat`) -- [ ] 누락 키 감지 자동화 (i18next missing key handler 또는 lint 규칙) - ---- - -## 7. ✅ Tailwind 공통 스타일 모듈화 (CVA) — COMPLETED - -`class-variance-authority` 0.7.1 설치, `src/lib/theme/variants.ts`에 3개 CVA 변형 구현 완료. - -- **cardVariants**: default / elevated / inner / transparent — CSS 변수 기반 테마 반응 -- **badgeVariants**: 8 intent (critical~cyan) x 4 size (xs~lg) — 150회+ 반복 패턴 통합 -- **statusDotVariants**: 4 status (online/warning/danger/offline) x 3 size (sm/md/lg) -- `shared/components/ui/card.tsx`, `badge.tsx`에 CVA 적용 완료 -- CSS 변수(`surface-raised`, `surface-overlay`, `border`) 참조로 Dark/Light 자동 반응 - ---- - -## 8. 코드 스플리팅 — 미착수 - -### 현재 상태 -- **단일 번들 ~3.2MB** (모든 feature + deck.gl + MapLibre + ECharts 포함) -- `React.lazy` 미적용, 모든 31개 페이지가 동기 import -- 초기 로딩 시 사용하지 않는 페이지 코드까지 전부 다운로드 - -### 필요한 이유 -- 초기 로딩 성능 개선 (FCP, LCP) -- 현장 모바일 환경 (LTE/3G)에서의 사용성 확보 -- 번들 캐싱 효율 향상 (변경된 chunk만 재다운로드) - -### 구현 계획 -- [ ] `React.lazy` + `Suspense`로 feature 단위 동적 임포트: - ```typescript - const Dashboard = lazy(() => import('@features/dashboard/Dashboard')); - const RiskMap = lazy(() => import('@features/risk-assessment/RiskMap')); - ``` -- [ ] `App.tsx` 라우트 전체를 lazy 컴포넌트로 교체 -- [ ] 로딩 폴백 컴포넌트 (스켈레톤 또는 스피너) 공통화 -- [ ] Vite `build.rollupOptions.output.manualChunks` 설정: - ```typescript - manualChunks: { - 'vendor-react': ['react', 'react-dom', 'react-router-dom'], - 'vendor-map': ['maplibre-gl', 'deck.gl', '@deck.gl/mapbox'], - 'vendor-chart': ['echarts'], - } - ``` -- [ ] 목표: 초기 번들 < 300KB (gzip), 각 feature chunk < 100KB -- [ ] `vite-plugin-compression`으로 gzip/brotli 사전 압축 검토 - ---- - -## 9. Light 테마 하드코딩 정리 - -### 현재 상태 -- Dark/Light 테마 전환 구조 완성 (CSS 변수 + `.light` 클래스 + settingsStore) -- 시맨틱 변수(`surface-raised`, `text-heading` 등) + CVA 변형은 정상 작동 -- **문제**: 일부 alert/status 색상이 Tailwind 하드코딩 (`bg-red-500/20`, `text-red-400`, `border-red-500/30` 등) - - Dark에서는 자연스러우나, Light 전환 시 대비/가독성 부족 - -### 구현 계획 -- [ ] 하드코딩 alert 색상을 CSS 변수 또는 CVA intent로 교체 -- [ ] `badgeVariants`의 intent 색상도 CSS 변수 기반으로 전환 검토 -- [ ] Light 모드 전용 대비 테스트 (WCAG AA 기준) - ---- - -## 우선순위 및 의존관계 - -``` -✅ 완료 ───────────────────────────────────── -[1. Zustand] [4. deck.gl] [5. mock 데이터] [7. CVA] - -진행 중 / 남은 작업 ────────────────────────── -[6. i18n 내부 텍스트] ──┐ - ├──▶ [2. API 실제 연동] ──▶ [3. 실시간 STOMP] -[9. Light 테마 정리] ───┘ - -[8. 코드 스플리팅] ← 독립 작업, 언제든 착수 가능 (~3.2MB → 목표 <300KB) -``` - -### 권장 진행 순서 - -1. **Phase A (품질)**: i18n 내부 텍스트 적용 (6) + Light 테마 하드코딩 정리 (9) + 코드 스플리팅 (8) -2. **Phase B (연동)**: Axios 설치 + API 실제 연동 (2) -3. **Phase C (실시간)**: STOMP.js + SockJS 실시간 인프라 (3) diff --git a/docs/page-workflow.md b/docs/page-workflow.md deleted file mode 100644 index 349e572..0000000 --- a/docs/page-workflow.md +++ /dev/null @@ -1,436 +0,0 @@ -# 페이지 역할표 및 업무 파이프라인 - -> 최초 작성일: 2026-04-06 -> 마지막 업데이트: 2026-04-06 -> 대상: `kcg-ai-monitoring` 프론트엔드 31개 페이지 - ---- - -## 0. 공통 아키텍처 - -### 디렉토리 구조 - -모든 페이지는 `src/features/` 아래 도메인별 디렉토리에 배치되어 있다. - -``` -src/features/ - admin/ AccessControl, AdminPanel, DataHub, NoticeManagement, SystemConfig - ai-operations/ AIAssistant, AIModelManagement, MLOpsPage - auth/ LoginPage - dashboard/ Dashboard - detection/ ChinaFishing, DarkVesselDetection, GearDetection, GearIdentification - enforcement/ EnforcementHistory, EventList - field-ops/ AIAlert, MobileService, ShipAgent - monitoring/ MonitoringDashboard - patrol/ FleetOptimization, PatrolRoute - risk-assessment/ EnforcementPlan, RiskMap - statistics/ ExternalService, ReportManagement, Statistics - surveillance/ LiveMapView, MapControl - vessel/ TransferDetection, VesselDetail -``` - -### 데이터 흐름 - -모든 공유 데이터는 **mock -> store -> page** 패턴으로 흐른다. - -``` -src/data/mock/*.ts --> src/stores/*Store.ts --> src/features/*/*.tsx - (7개 공유 모듈) (7개 Zustand 스토어) (16개 페이지가 스토어 소비) -``` - -- 스토어는 `load()` 호출 시 `import()`로 mock 데이터를 lazy loading -- 도메인 특화 데이터는 페이지 내 인라인으로 유지 (MLOps, MapControl, DataHub 등) -- 상세 매핑은 `docs/data-sharing-analysis.md` 참조 - -### 지도 렌더링 - -지도가 필요한 11개 페이지는 공통 `src/lib/map/` 인프라를 사용한다. - -- **deck.gl** 기반 렌더링 (`BaseMap.tsx`) -- **`useMapLayers`** 훅: 페이지별 동적 레이어 구성 -- **`STATIC_LAYERS`**: EEZ/KDLZ 등 정적 레이어를 상수로 분리하여 zero rerender 보장 -- 사용 페이지: Dashboard, LiveMapView, MapControl, EnforcementPlan, PatrolRoute, FleetOptimization, GearDetection, DarkVesselDetection, RiskMap, VesselDetail, MobileService - -### 다국어 (i18n) - -- `react-i18next` 기반, 24개 페이지 + MainLayout + LoginPage에 i18n 적용 -- 지원 언어: 한국어 (ko), 영어 (en) -- 페이지 타이틀, 주요 UI 라벨이 번역 키로 관리됨 - -### 테마 - -- `settingsStore`에서 dark/light 테마 전환 지원 -- 기본값: dark (해양 감시 시스템 특성상) -- `localStorage`에 선택 유지, CSS 클래스 토글 방식 - ---- - -## 1. 31개 페이지 역할표 - -### 1.1 인증/관리 (4개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-01 | LoginPage | `/login` | 전체 | SSO/GPKI/비밀번호 인증, 5회 실패 잠금 | ID/PW, 인증 방식 선택 | 세션 발급, 역할 부여 | - | 모든 페이지 (인증 게이트) | -| SFR-01 | AccessControl | `/access-control` | 관리자 | RBAC 권한 관리, 감사 로그 | 역할/사용자/권한 설정 | 권한 변경, 감사 기록 | LoginPage | 전체 시스템 접근 제어 | -| SFR-02 | SystemConfig | `/system-config` | 관리자 | 공통코드 기준정보 관리 (해역52/어종578/어업59/선박186) | 코드 검색/필터 | 코드 조회, 설정 변경 | AccessControl | 탐지/분석 엔진 기준데이터 | -| SFR-02 | NoticeManagement | `/notices` | 관리자 | 시스템 공지(배너/팝업/토스트), 역할별 대상 설정 | 공지 작성, 기간/대상 설정 | 배너/팝업 노출 | AccessControl | 모든 페이지 (NotificationBanner) | - -### 1.2 데이터 수집/연계 (1개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-03 | DataHub | `/data-hub` | 관리자 | 통합데이터 허브 — 선박신호 수신 현황 히트맵, 연계 채널 모니터링 | 수신 소스 선택 | 수신률 조회, 연계 상태 확인 | 외부 센서 (VTS, AIS, V-PASS 등) | 탐지 파이프라인 전체 | - -### 1.3 AI 모델/운영 (3개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-04 | AIModelManagement | `/ai-model` | 분석관 | 모델 레지스트리, 탐지 규칙, 피처 엔지니어링, 학습 파이프라인, 7대 탐지엔진 | 모델 버전/규칙/피처 설정 | 모델 배포, 성능 리포트 | DataHub (학습 데이터) | DarkVessel, GearDetection, TransferDetection 등 탐지 엔진 | -| SFR-18/19 | MLOpsPage | `/mlops` | 분석관/관리자 | MLOps/LLMOps 운영 대시보드 (실험, 배포, API Playground, LLM 테스트) | 실험 템플릿, HPS 설정 | 실험 결과, 모델 배포 | AIModelManagement | AIAssistant, 탐지 엔진 | -| SFR-20 | AIAssistant | `/ai-assistant` | 상황실/분석관 | 자연어 Q&A 의사결정 지원 (법령 조회, 대응 절차 안내) | 자연어 질의 | 답변 + 법령 참조 | MLOpsPage (LLM 모델) | 작전 의사결정 | - -### 1.4 탐지 (4개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-09 | DarkVesselDetection | `/dark-vessel` | 분석관 | AIS 조작/위장/Dark Vessel 패턴 탐지 (6가지 패턴), 지도+테이블 | AIS 데이터 스트림 | 의심 선박 목록, 위험도, 라벨 분류 | DataHub (AIS/레이더) | RiskMap, LiveMapView, EventList | -| SFR-10 | GearDetection | `/gear-detection` | 분석관 | 불법 어망/어구 탐지 및 관리, 허가 상태 판정 | 어구 센서/영상 | 어구 목록, 불법 판정 결과 | DataHub (센서) | RiskMap, EnforcementPlan | -| - | GearIdentification | `features/detection/` | 분석관 | 어구 국적 판별 (중국/한국/불확실), GB/T 5147 기준 | 어구 물리적 특성 입력 | 판별 결과 (국적, 신뢰도, 경보등급) | GearDetection | EnforcementHistory | -| - | ChinaFishing | `/china-fishing` | 분석관/상황실 | 중국어선 통합 감시 (센서 카운터, 특이운항, 월별 통계, 환적 탐지, VTS 연계) | 센서 데이터 융합 | 감시 현황, 환적 의심 목록 | DataHub, DarkVessel | RiskMap, EnforcementPlan | - -### 1.5 환적 탐지 (1개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| - | TransferDetection | `features/vessel/` | 분석관 | 선박 간 근접 접촉 및 환적 의심 행위 분석 (거리/시간/속도 기준) | AIS 궤적 분석 | 환적 이벤트 목록, 의심도 점수 | DataHub, DarkVessel | EventList, EnforcementPlan | - -### 1.6 위험도 평가/계획 (2개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-05 | RiskMap | `/risk-map` | 분석관/상황실 | 격자 기반 불법조업 위험도 지도 + MTIS 해양사고 통계 연계 | 탐지 결과, 사고 통계 | 히트맵, 해역별 위험도, 사고 통계 차트 | DarkVessel, GearDetection, ChinaFishing | EnforcementPlan, PatrolRoute | -| SFR-06 | EnforcementPlan | `/enforcement-plan` | 상황실 | 단속 계획 수립, 경보 연계, 우선지역 예보 | 위험도 데이터, 가용 함정 | 단속 계획 테이블, 지도 표시 | RiskMap | PatrolRoute, FleetOptimization | - -### 1.7 순찰/함대 (2개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-07 | PatrolRoute | `/patrol-route` | 상황실 | AI 단일 함정 순찰 경로 추천 (웨이포인트, 거리/시간/연료 산출) | 함정 선택, 구역 조건 | 추천 경로, 웨이포인트 목록 | EnforcementPlan, RiskMap | 함정 출동 (ShipAgent) | -| SFR-08 | FleetOptimization | `/fleet-optimization` | 상황실 | 다함정 협력형 경로 최적화 (커버리지 시뮬레이션, 승인 워크플로) | 함대 목록, 구역 조건 | 최적화 결과, 커버리지 비교 | EnforcementPlan, PatrolRoute | 함정 출동 (ShipAgent) | - -### 1.8 감시/지도 (2개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| - | LiveMapView | `/events` | 상황실 | 실시간 해역 감시 지도 (AIS 선박 + 이벤트 경보 + 아군 함정) | 실시간 AIS/이벤트 스트림 | 지도 마커, 이벤트 카드, 위험도 바 | 탐지 엔진 전체 | EventList, AIAlert | -| - | MapControl | `/map-control` | 상황실/관리자 | 해역 통제 관리 (해상사격 훈련구역도 No.462, 군/해경 구역) | 구역 데이터 | 훈련구역 지도, 상태 테이블 | 국립해양조사원 데이터 | LiveMapView (레이어) | - -### 1.9 대시보드/모니터링 (2개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| - | Dashboard | `/dashboard` | 전체 | 종합 상황판 (KPI, 타임라인, 위험선박 TOP8, 함정 현황, 해역 위험도, 시간대별 탐지 추이) | 전 시스템 데이터 집계 | 한눈에 보는 현황 | 탐지/순찰/이벤트 전체 | 각 상세 페이지로 드릴다운 | -| SFR-12 | MonitoringDashboard | `/monitoring` | 상황실 | 모니터링 및 경보 현황판 (KPI, 24시간 추이, 탐지 유형 분포, 실시간 이벤트) | 경보/탐지 데이터 | 경보 현황 대시보드 | 탐지 엔진, EventList | AIAlert, EnforcementPlan | - -### 1.10 이벤트/이력 (2개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| - | EventList | `/event-list` | 상황실/분석관 | 이벤트 전체 목록 (검색/정렬/페이징/엑셀/출력), 15건+ 이벤트 | 필터 조건 | 이벤트 테이블, 엑셀 내보내기 | 탐지 엔진, LiveMapView | EnforcementHistory, ReportManagement | -| SFR-11 | EnforcementHistory | `/enforcement-history` | 분석관 | 단속/탐지 이력 관리 (AI 매칭 검증 포함) | 검색 조건 | 이력 테이블, AI 일치 여부 | EventList, 현장 단속 | ReportManagement, Statistics | - -### 1.11 현장 대응 (3개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-15 | MobileService | `/mobile-service` | 현장 단속요원 | 모바일 앱 프리뷰 (위험도/의심선박/경로추천/경보, 푸시 설정) | 모바일 위치, 푸시 설정 | 경보 수신, 지도 조회 | AIAlert, LiveMapView | 현장 단속 수행 | -| SFR-16 | ShipAgent | `/ship-agent` | 현장 단속요원 | 함정용 Agent 관리 (배포/동기화 상태, 버전 관리) | 함정 Agent 설치 | Agent 상태 조회, 동기화 | PatrolRoute, FleetOptimization | 현장 단속 수행 | -| SFR-17 | AIAlert | `/ai-alert` | 상황실/현장 | AI 탐지 알림 자동 발송 (함정/관제요원 대상, 탐지시각/좌표/유형/신뢰도 포함) | 탐지 이벤트 트리거 | 알림 발송, 수신 확인 | MonitoringDashboard, EventList | MobileService, ShipAgent | - -### 1.12 통계/외부연계/보고 (3개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| SFR-13 | Statistics | `/statistics` | 상황실/분석관 | 통계/지표/성과 분석 (월별 추이, 위반유형, KPI 달성률) | 기간/유형 필터 | 차트, KPI 테이블, 보고서 | EnforcementHistory, EventList | 외부 보고, 전략 수립 | -| SFR-14 | ExternalService | `/external-service` | 관리자/외부 | 외부 서비스 제공 (해수부/수협/기상청 API/파일 연계, 비식별/익명화 정책) | 서비스 설정 | API 호출 수, 연계 상태 | Statistics, 탐지 결과 | 외부기관 | -| - | ReportManagement | `/reports` | 분석관/상황실 | 증거 관리 및 보고서 생성 (사건별 자동 패키징) | 사건 선택, 증거 파일 업로드 | 보고서 PDF, 증거 패키지 | EnforcementHistory, EventList | 검찰/외부기관 | - -### 1.13 선박 상세 (1개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| - | VesselDetail | `/vessel/:id` | 분석관/상황실 | 선박 상세 정보 (AIS 데이터, 항적, 입항 이력, 선원 정보, 비허가 선박 목록) | 선박 ID/MMSI | 상세 프로필, 지도 항적 | LiveMapView, DarkVessel, EventList | EnforcementPlan, ReportManagement | - -### 1.14 시스템 관리 (1개) - -| SFR | 화면명 | 경로 | 사용자 | 핵심 기능 | 입력 | 출력/액션 | 업스트림 | 다운스트림 | -|---|---|---|---|---|---|---|---|---| -| - | AdminPanel | `/admin` | 관리자 | 시스템 인프라 관리 (서버 상태, CPU/메모리/디스크 모니터링) | - | 서버 상태 대시보드 | - | 시스템 안정성 보장 | - ---- - -## 2. 업무 파이프라인 (4개) - -### 2.1 탐지 파이프라인 - -불법 조업을 탐지하고 실시간 감시하여 현장 작전까지 연결하는 핵심 파이프라인. - -``` -AIS/레이더/위성 신호 - │ - ▼ - ┌─────────┐ - │ DataHub │ ← 통합데이터 허브 (VTS, AIS, V-PASS, E-Nav 수집) - └────┬────┘ - │ - ▼ - ┌──────────────────────────────────────────────┐ - │ AI 탐지 엔진 (AIModelManagement 관리) │ - │ │ - │ DarkVesselDetection ─ AIS 조작/위장/소실 │ - │ GearDetection ─────── 불법 어구 탐지 │ - │ ChinaFishing ──────── 중국어선 통합 감시 │ - │ TransferDetection ─── 환적 행위 탐지 │ - │ GearIdentification ── 어구 국적 판별 │ - └──────────────┬───────────────────────────────┘ - │ - ▼ - ┌──────────┐ ┌───────────────────┐ - │ RiskMap │─────▶│ LiveMapView │ ← 실시간 지도 감시 - └────┬─────┘ │ MonitoringDashboard│ ← 경보 현황판 - │ └───────────────────┘ - ▼ - ┌──────────────────┐ - │ EnforcementPlan │ ← 단속 우선지역 예보 - └────────┬─────────┘ - │ - ▼ - ┌──────────────┐ ┌───────────────────┐ - │ PatrolRoute │─────▶│ FleetOptimization │ ← 다함정 최적화 - └──────┬───────┘ └─────────┬─────────┘ - │ │ - ▼ ▼ - ┌──────────┐ - │ AIAlert │ ← 함정/관제 자동 알림 발송 - └────┬─────┘ - │ - ▼ - 현장 작전 (MobileService, ShipAgent) -``` - -### 2.2 대응 파이프라인 - -AI 알림 수신 후 현장 단속, 이력 기록, 보고서 생성까지의 대응 프로세스. - -``` - ┌──────────┐ - │ AIAlert │ ← AI 탐지 알림 자동 발송 - └────┬─────┘ - │ - ▼ - ┌──────────────────────────────────┐ - │ 현장 대응 │ - │ │ - │ MobileService ── 모바일 경보 수신│ - │ ShipAgent ────── 함정 Agent 연동 │ - └──────────────┬───────────────────┘ - │ - ▼ - 현장 단속 수행 - (정선/검문/나포/퇴거) - │ - ▼ - ┌──────────────────────┐ - │ EnforcementHistory │ ← 단속 이력 등록, AI 매칭 검증 - └──────────┬───────────┘ - │ - ▼ - ┌──────────────────────┐ - │ ReportManagement │ ← 증거 패키징, 보고서 생성 - └──────────┬───────────┘ - │ - ▼ - 검찰/외부기관 (ExternalService 통해 연계) -``` - -### 2.3 분석 파이프라인 - -축적된 데이터를 분석하여 전략적 의사결정을 지원하는 파이프라인. - -``` - ┌─────────────┐ - │ Statistics │ ← 월별 추이, 위반유형, KPI 달성률 - └──────┬──────┘ - │ - ▼ - ┌──────────┐ - │ RiskMap │ ← 격자 위험도 + MTIS 해양사고 통계 - └────┬─────┘ - │ - ▼ - ┌──────────────┐ - │ VesselDetail │ ← 개별 선박 심층 분석 (항적, 이력) - └──────┬───────┘ - │ - ▼ - ┌──────────────┐ - │ AIAssistant │ ← 자연어 Q&A (법령 조회, 대응 절차) - └──────┬───────┘ - │ - ▼ - 전략 수립 (순찰 패턴, 탐지 규칙 조정) -``` - -### 2.4 관리 파이프라인 - -시스템 접근 제어, 환경 설정, 데이터 관리, 인프라 모니터링 파이프라인. - -``` - ┌────────────────┐ - │ AccessControl │ ← RBAC 역할/권한 설정 - └───────┬────────┘ - │ - ▼ - ┌────────────┐ - │ LoginPage │ ← SSO/GPKI/비밀번호 인증 - └──────┬─────┘ - │ - ▼ - ┌──────────────────────────────────────┐ - │ 시스템 설정/관리 │ - │ │ - │ SystemConfig ──── 공통코드/환경설정 │ - │ NoticeManagement ── 공지/배너/팝업 │ - │ DataHub ────────── 데이터 수집 관리 │ - │ AdminPanel ────── 서버/인프라 모니터 │ - └──────────────────────────────────────┘ -``` - ---- - -## 3. 사용자 역할별 페이지 접근 매트릭스 - -시스템에 정의된 5개 역할(LoginPage의 `DEMO_ACCOUNTS` 및 AccessControl의 `ROLES` 기반)에 대한 페이지 접근 권한. - -### 3.1 역할 정의 - -| 역할 | 코드 | 설명 | 인원(시뮬) | -|---|---|---|---| -| 시스템 관리자 | `ADMIN` | 전체 시스템 관리 권한 | 3명 | -| 상황실 운영자 | `OPERATOR` | 상황판, 통계, 경보 운영 | 12명 | -| 분석 담당자 | `ANALYST` | AI 모델, 통계, 항적 분석 | 8명 | -| 현장 단속요원 | `FIELD` | 함정 Agent, 모바일 대응 | 45명 | -| 유관기관 열람자 | `VIEWER` | 공유 대시보드 열람 | 6명 | - -### 3.2 접근 매트릭스 - -| 페이지 | ADMIN | OPERATOR | ANALYST | FIELD | VIEWER | -|---|---|---|---|---|---| -| **인증/관리** | | | | | | -| LoginPage | O | O | O | O | O | -| AccessControl | O | - | - | - | - | -| SystemConfig | O | - | - | - | - | -| NoticeManagement | O | - | - | - | - | -| AdminPanel | O | - | - | - | - | -| **데이터/AI** | | | | | | -| DataHub | O | - | - | - | - | -| AIModelManagement | O | - | O | - | - | -| MLOpsPage | O | - | O | - | - | -| AIAssistant | O | O | O | - | - | -| **탐지** | | | | | | -| DarkVesselDetection | O | - | O | - | - | -| GearDetection | O | - | O | - | - | -| ChinaFishing | O | O | O | - | - | -| TransferDetection | O | - | O | - | - | -| **위험도/계획** | | | | | | -| RiskMap | O | O | O | - | - | -| EnforcementPlan | O | O | - | - | - | -| **순찰** | | | | | | -| PatrolRoute | O | O | - | - | - | -| FleetOptimization | O | O | - | - | - | -| **감시/지도** | | | | | | -| LiveMapView | O | O | O | - | - | -| MapControl | O | O | - | - | - | -| **대시보드** | | | | | | -| Dashboard | O | O | O | O | O | -| MonitoringDashboard | O | O | - | - | - | -| **이벤트/이력** | | | | | | -| EventList | O | O | O | O | - | -| EnforcementHistory | O | - | O | - | - | -| **현장 대응** | | | | | | -| MobileService | O | - | - | O | - | -| ShipAgent | O | - | - | O | - | -| AIAlert | O | O | - | O | - | -| **통계/보고** | | | | | | -| Statistics | O | O | O | - | - | -| ExternalService | O | - | - | - | O | -| ReportManagement | O | O | O | - | - | -| **선박 상세** | | | | | | -| VesselDetail | O | O | O | - | - | - -### 3.3 역할별 요약 - -| 역할 | 접근 가능 페이지 | 페이지 수 | -|---|---|---| -| **시스템 관리자** (ADMIN) | 전체 페이지 | 31 | -| **상황실 운영자** (OPERATOR) | Dashboard, MonitoringDashboard, LiveMapView, MapControl, EventList, EnforcementPlan, PatrolRoute, FleetOptimization, ChinaFishing, RiskMap, Statistics, ReportManagement, AIAssistant, AIAlert, VesselDetail | 15 | -| **분석 담당자** (ANALYST) | Dashboard, DarkVesselDetection, GearDetection, ChinaFishing, TransferDetection, RiskMap, LiveMapView, EventList, EnforcementHistory, Statistics, ReportManagement, VesselDetail, AIAssistant, AIModelManagement, MLOpsPage | 15 | -| **현장 단속요원** (FIELD) | Dashboard, MobileService, ShipAgent, AIAlert, EventList | 5 | -| **유관기관 열람자** (VIEWER) | Dashboard, ExternalService | 2 | - ---- - -## 4. 페이지 간 데이터 흐름 요약 - -``` - ┌──────────────────┐ - │ LoginPage │ - │ (인증 게이트) │ - └────────┬─────────┘ - │ - ┌────────────────────┬┴──────────────────┐ - ▼ ▼ ▼ - ┌──────────────┐ ┌─────────────────┐ ┌─────────────┐ - │ 관리 파이프라인│ │ 탐지 파이프라인 │ │ 현장 대응 │ - │ │ │ │ │ │ - │ AccessControl│ │ DataHub │ │ MobileSvc │ - │ SystemConfig │ │ ↓ │ │ ShipAgent │ - │ NoticeManage │ │ AI탐지엔진 │ │ AIAlert │ - │ DataHub │ │ (DV/Gear/CN/TR)│ └──────┬──────┘ - │ AdminPanel │ │ ↓ │ │ - └──────────────┘ │ RiskMap │ │ - │ ↓ │ ▼ - │ EnforcementPlan │ ┌──────────────┐ - │ ↓ │ │ 대응 파이프라인│ - │ PatrolRoute │ │ │ - │ FleetOptim │ │ Enforcement │ - │ ↓ │ │ History │ - │ LiveMapView │ │ ReportManage │ - │ Monitoring │ │ ExternalSvc │ - └────────┬────────┘ └──────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 분석 파이프라인 │ - │ │ - │ Statistics │ - │ VesselDetail │ - │ AIAssistant │ - └─────────────────┘ -``` - ---- - -## 5. 미할당 SFR 참고 - -현재 라우트에서 확인되는 SFR 번호 기준, 아래 기능은 기존 페이지에 통합되어 있다: - -- **Dashboard**: SFR 번호 미부여, 종합 상황판 (기존 유지) -- **LiveMapView**: SFR 번호 미부여, 실시간 감시 지도 -- **EventList**: SFR-02 공통 컴포넌트 적용 대상으로 분류 -- **MapControl**: SFR 번호 미부여, 해역 통제 관리 -- **VesselDetail**: SFR 번호 미부여, 선박 상세 -- **ReportManagement**: SFR 번호 미부여, 증거/보고서 관리 -- **AdminPanel**: SFR 번호 미부여, 인프라 관리 -- **GearIdentification**: ChinaFishing 내 서브 컴포넌트 diff --git a/docs/sfr-traceability.md b/docs/sfr-traceability.md index 41336ff..e660c28 100644 --- a/docs/sfr-traceability.md +++ b/docs/sfr-traceability.md @@ -1,905 +1,531 @@ # SFR 요구사항 추적 매트릭스 (Requirements Traceability Matrix) -**프로젝트:** AI 기반 불법조업 감시 시스템 -**문서 버전:** 2.0 -**최종 업데이트:** 2026-04-06 +**프로젝트:** AI 기반 불법조업 감시 시스템 (KCG AI Monitoring) +**문서 버전:** 3.0 +**최종 업데이트:** 2026-04-17 **근거 문서:** 제안요청서 (RFP) 소프트웨어 기능 요구사항 (SFR) -### 기술 스택 및 아키텍처 현황 +--- -| 항목 | 내용 | -|------|------| -| 기술 스택 | React 19, Vite 8, deck.gl 9.2, Zustand 5.0, ECharts, MapLibre GL | -| 데이터 흐름 | `data/mock` → Zustand store → 페이지 렌더 | -| 렌더링 | deck.gl 제로 리렌더 아키텍처 (`useMapLayers` + RAF) | -| i18n | 10 네임스페이스, MainLayout + 24페이지 + LoginPage 적용 | -| 테마 | Dark/Light 전환 지원 (CSS 변수 기반) | +## 기술 스택 및 아키텍처 현황 (2026-04-17 기준) + +### 전체 스택 +| 레이어 | 기술 | 상태 | +|-------|------|------| +| Frontend | React 19 + TypeScript 5.9 + Vite 8 + Tailwind CSS 4 + Zustand 5 + MapLibre GL 5 + deck.gl 9 + ECharts 6 + react-i18next | 운영 배포 (rocky-211 nginx) | +| Backend | Spring Boot 3.5.7 + Java 21 + PostgreSQL 14.19 + Flyway V001~V029 + Spring Security + JWT + Caffeine + 트리 RBAC | 운영 배포 (rocky-211 :18080) | +| Prediction | Python 3.11+ + FastAPI + APScheduler, 14 알고리즘, 7단계 분류 파이프라인 | 운영 배포 (redis-211 :18092, 5분 주기) | +| Database | PostgreSQL `kcgaidb` / 48 테이블 / schema `kcg` + snpdb(AIS 원천) | 운영 | +| Design System | `/design-system.html` 쇼케이스 SSOT + `shared/constants/` 25개 카탈로그 + `shared/components/ui/` 9개 공통 컴포넌트 | SSOT 전영역 준수 (2026-04-17 PR #C 완료) | +| i18n | 10 네임스페이스 × ko/en, `common.json` 에 aria/error/dialog/message 54키 추가 | alert/confirm/aria-label 하드코딩 제거 완료 (2026-04-17 PR #B) | + +### 데이터 흐름 +``` +snpdb(AIS 원천) → prediction(14 algo, 5분 주기) → kcgaidb(직접 write) + ↓ +Frontend ← Backend /api/analysis/* + /api/events + /api/alerts + ... (65+ API) +``` + +- Backend ↔ Prediction: HTTP 호출 없이 **kcgaidb DB 공유**로만 연동 +- Frontend ↔ Backend: REST API + JWT 쿠키 인증 + +### 배포 토폴로지 +| 서비스 | 서버 | 포트 | 배포 | +|--------|------|------|------| +| Frontend dist | rocky-211 (nginx 443) | HTTPS | Gitea Actions 자동배포 (main push) | +| Backend JAR | rocky-211 | :18080 | systemd `kcg-ai-backend`, 수동 scp + restart | +| Prediction FastAPI | redis-211 | :18092 | systemd `kcg-ai-prediction`, 수동 rsync + restart | +| PostgreSQL | 211.208.115.83 | :5432 | `kcgaidb / schema kcg / user kcg-app` | +| Redis | redis-211 | :6379 | AI 채팅 컨텍스트 캐시 | --- ## 요약 -| 구분 | 건수 | -|------|------| -| 전체 SFR | 20건 | -| UI 완료 | 20건 (100%) | -| 기능 일부 구현 (프론트엔드 시뮬레이션) | 20건 | -| 백엔드 연동 완료 | 0건 (0%) | - -> 현재 전체 SFR에 대해 화면(UI) 프로토타입이 완성되었으며(31페이지), 시뮬레이션 데이터 기반으로 동작합니다. -> 데이터는 `data/mock` JSON에서 8개의 Zustand store(`kpiStore`, `vesselStore`, `eventStore`, `enforcementStore`, `patrolStore`, `gearStore`, `transferStore`, `settingsStore`)를 거쳐 페이지에 전달됩니다. -> 지도 페이지는 deck.gl 레이어 렌더링을 사용하며, 모든 페이지에 i18n 제목/설명 및 Dark/Light 테마가 적용되어 있습니다. -> 실제 백엔드 API, AI 모델, 외부 시스템 연동은 2차 개발 단계에서 수행됩니다. - -### 구현 진척 요약 +| 구분 | 2026-04-06 | 2026-04-17 | +|------|-----------|-----------| +| 전체 SFR | 20건 | 20건 | +| UI 완료 | 20건 (100%) | 20건 (100%) | +| **백엔드 연동 완료** | **0건 (0%)** | **15건 (75%)** | +| **prediction 연동 완료** | **0건 (0%)** | **10건 (50%)** | +| **운영 배포** | - | rocky-211 + redis-211 | +### 2026-04-06 이후 구현 진척 | 항목 | 상태 | |------|------| | UI 프로토타입 | ✅ 완료 (31페이지) | -| 기술스택 전환 | ✅ 완료 (React 19, Vite 8, deck.gl, ECharts) | -| 데이터 통합 | ✅ 완료 (7 mock → 8 store) | -| i18n 구조 | ✅ 완료 (제목/메뉴, 내부 텍스트 미완) | -| 테마 시스템 | ✅ 완료 (dark/light, 시맨틱 CSS 변수) | -| API 서비스 | 🔲 샘플 구조만 (mock 반환) | -| 실시간 인프라 | 🔲 스캐폴드만 (STOMP.js 미설치) | -| 코드 스플리팅 | 🔲 미적용 (3.2MB 단일 번들) | +| 기술 스택 | ✅ 모노레포 전환 (frontend + backend + prediction + database) | +| 백엔드 (Spring Boot) | ✅ JWT + 트리 RBAC + 감사 로그 + 데모 계정 5종 + 65+ API | +| prediction (Python) | ✅ 14 algo + 7단계 파이프라인 + DAR-03 G-01~G-06 + pair tier + NAME_FUZZY 매칭 | +| DB 마스터 | ✅ 48 테이블 (V001~V029), 한중어업협정 906척 레지스트리 | +| 실 API 연동 | ✅ Dashboard/Monitoring/Statistics/EventList/Enforcement/Detection 4종/ChinaFishing/TransferDetection/VesselDetail/LiveMapView/ParentReview/Exclusion/LabelSession/AIAlert/AccessControl/Permission/Audit 등 | +| i18n | ✅ alert/confirm/aria-label 하드코딩 제거 (2026-04-17 PR #B) | +| 디자인 시스템 SSOT | ✅ raw button/input/select/tab/checkbox → 공통 컴포넌트, 라이트/다크 쌍 (2026-04-17 PR #C) | +| 백엔드 계층 분리 | ✅ controller → service → repository 일관성, RestClientConfig @Bean (2026-04-17 PR #A) | +| iran 백엔드 프록시 | ✅ 완전 제거 (2026-04-17 PR #A) | + +### 여전히 미적용 (후속 과제) +- 코드 스플리팅 (React.lazy + manualChunks) +- JSX placeholder/텍스트 한글 35건 i18n 전환 +- spoofing_score 산출 재설계 (중국 MID 412 전원 0 수렴) +- external.iran_backend 매니페스트 노드 완전 삭제 (1~2 릴리즈 후) --- ## SFR 목록 -| SFR | 요구사항 명칭 | 구현 화면 | 구현 상태 | -|-----|-------------|----------|----------| -| SFR-01 | 시스템 로그인 및 권한 관리 | LoginPage, AccessControl | UI 완료 | -| SFR-02 | 시스템 기본 환경설정 및 공통 기능 | SystemConfig, NoticeManagement, 공통 컴포넌트 | UI 완료 | -| SFR-03 | 통합 데이터 허브 수집·연계 관리 | DataHub | UI 완료 | -| SFR-04 | AI 불법조업 예측모델 관리 | AIModelManagement | UI 완료 | -| SFR-05 | 격자 기반 불법조업 위험도 지도 생성·시각화 | RiskMap | UI 완료 | -| SFR-06 | 단속 계획·경보 연계 | EnforcementPlan | UI 완료 | -| SFR-07 | AI 경비함정 단일 함정 순찰·경로 추천 | PatrolRoute | UI 완료 | -| SFR-08 | AI 경비함정 다함정 협력형 경로 최적화 | FleetOptimization | UI 완료 | -| SFR-09 | 불법 어선 패턴 탐지 | DarkVesselDetection | UI 완료 | -| SFR-10 | 불법 어망·어구 탐지 및 관리 | GearDetection, GearIdentification | UI 완료 | -| SFR-11 | 단속·탐지 이력 관리 | EnforcementHistory, EventList | UI 완료 | -| SFR-12 | 모니터링 및 경보 현황판(대시보드) | Dashboard, MonitoringDashboard | UI 완료 | -| SFR-13 | 통계·지표·성과 분석 | Statistics | UI 완료 | -| SFR-14 | 외부 서비스 제공 결과 연계 | ExternalService | UI 완료 | -| SFR-15 | 단속요원 이용 모바일 대응 서비스 | MobileService | UI 완료 | -| SFR-16 | 함정용 단말 Agent 개발 | ShipAgent | UI 완료 | -| SFR-17 | 현장 함정 즉각 대응 AI 알림 메시지 발송 | AIAlert | UI 완료 | -| SFR-18 | 기계학습 운영 기능 | MLOpsPage | UI 완료 | -| SFR-19 | 대규모 언어모델(LLM) 운영 기능 | MLOpsPage (LLMOps 탭) | UI 완료 | -| SFR-20 | 자연어 처리 기반 AI 의사결정 지원(Q&A) 서비스 | AIAssistant | UI 완료 | +범례: ✅ 운영 / 🟡 구조 완성, 데이터 미연동 / 🔲 UI 프로토타입만 + +| SFR | 요구사항 명칭 | 구현 화면 | 백엔드 | Prediction | +|-----|-------------|----------|-------|-----------| +| SFR-01 | 시스템 로그인 및 권한 관리 | LoginPage, AccessControl, PermissionsPanel | ✅ JWT + 트리 RBAC | - | +| SFR-02 | 시스템 기본 환경설정 및 공통 기능 | SystemConfig, NoticeManagement, 공통 컴포넌트 | 🟡 MasterData API | - | +| SFR-03 | 통합 데이터 허브 수집·연계 관리 | DataHub | 🔲 Mock | ✅ snpdb + signal-batch | +| SFR-04 | AI 불법조업 예측모델 관리 | AIModelManagement | 🔲 Mock | - | +| SFR-05 | 격자 기반 불법조업 위험도 지도 | RiskMap | 🔲 Mock | 🟡 prediction_risk_grid | +| SFR-06 | 단속 계획·경보 연계 | EnforcementPlan | ✅ /api/enforcement/plans | - | +| SFR-07 | AI 경비함정 단일 함정 순찰·경로 | PatrolRoute | 🔲 Mock | - | +| SFR-08 | AI 경비함정 다함정 협력형 경로 | FleetOptimization | 🔲 Mock | - | +| SFR-09 | 불법 어선 패턴 탐지 (Dark Vessel) | DarkVesselDetection, TransferDetection | ✅ /api/analysis/* | ✅ Dark 11패턴 + Transship 5단계 | +| SFR-10 | 불법 어망·어구 탐지 및 관리 | GearDetection, GearIdentification | ✅ /api/vessel-analysis/groups + /api/analysis/gear-detections | ✅ DAR-03 G-01~G-06 + pair tier | +| SFR-11 | 단속·탐지 이력 관리 | EnforcementHistory, EventList | ✅ /api/events + /api/enforcement/records | ✅ prediction_events | +| SFR-12 | 모니터링 및 경보 현황판 | Dashboard, MonitoringDashboard, ChinaFishing | ✅ /api/stats + /api/alerts + /api/analysis/* | ✅ prediction_kpi_realtime + stats | +| SFR-13 | 통계·지표·성과 분석 | Statistics | ✅ /api/stats (daily/monthly/hourly) | ✅ prediction_stats_* | +| SFR-14 | 외부 서비스 제공 결과 연계 | ExternalService | 🔲 Mock | - | +| SFR-15 | 단속요원 이용 모바일 대응 서비스 | MobileService | 🟡 /api/alerts 연동 | - | +| SFR-16 | 함정용 단말 Agent 개발 | ShipAgent | 🔲 Mock | - | +| SFR-17 | 현장 함정 즉각 AI 알림 발송 | AIAlert | ✅ /api/alerts | - | +| SFR-18 | 기계학습 운영 기능 (MLOps) | MLOpsPage | 🔲 Mock | - | +| SFR-19 | LLM 운영 기능 | MLOpsPage (LLMOps 탭) | 🔲 Mock | - | +| SFR-20 | 자연어 처리 AI 의사결정 지원 Q&A | AIAssistant | 🟡 /api/prediction/chat stub | - | --- ## 상세 추적 내역 ---- - ### SFR-01: 시스템 로그인 및 권한 관리 -**제안요청서 정의:** 사용자 유형별 안전한 인증 및 역할 기반 권한 관리 체계를 구축하여, 시스템 접근 보안을 확보하고 사용자 활동에 대한 감사 추적을 가능하게 한다. +**제안요청서 정의:** 사용자 유형별 안전한 인증 및 역할 기반 권한 관리 체계로 시스템 접근 보안 확보 + 사용자 활동 감사 추적. -**세부 요구사항 요약:** -- 해양경찰 SSO·공무원증·GPKI 등 기존 인증체계 로그인 연동 -- 역할 기반 접근 제어(RBAC) 구현 -- 감사 로그(Audit Log) 기록 및 조회 -- 비밀번호 정책 적용 (9자 이상, 영문+숫자+특수문자) -- 5회 연속 인증 실패 시 계정 잠금 (30분) +**구현 화면:** `features/auth/LoginPage.tsx`, `features/admin/AccessControl.tsx`, `features/admin/PermissionsPanel.tsx`, `features/admin/UserRoleAssignDialog.tsx` -**구현 화면:** 로그인 페이지 (`src/features/auth/LoginPage.tsx`), 접근 권한 관리 (`src/features/admin/AccessControl.tsx`) +**백엔드 연동 ✅ 운영**: +- `/api/auth/login|logout|me` — JWT 쿠키 기반 인증 +- `/api/perm-tree` + `/api/roles/*` + `/api/admin/users/*` — 트리 기반 RBAC (47 리소스 노드, Level 0 13개 + Level 1 32개 + 5 operation) +- `/api/admin/audit-logs` + `/api/admin/access-logs` + `/api/admin/login-history` + 통계 3종 — 감사 로그 전수 수집 +- `@Auditable` AOP + `AccessLogFilter` — 자동 audit_log 기록 +- 비밀번호 정책: 9자 이상 + 영문/숫자/특수, 5회 실패 계정 잠금 (Caffeine 10분 TTL) +- 데모 계정 5종 (admin/operator/analyst/field/viewer) -**화면 구성 요소:** -- 로그인 폼: ID/PW 입력, GPKI 인증서 로그인, SSO 연동 버튼 (3가지 인증 방식 탭) -- 역할별 데모 계정 5종: 관리자(ADMIN), 운용자(OPERATOR), 분석관(ANALYST), 현장요원(FIELD), 열람자(VIEWER) -- 비밀번호 정책 검증 UI (길이·복잡도 실시간 표시) -- 계정 잠금 카운터 및 잠금 상태 표시 -- 접근 권한 관리 테이블 (역할별 메뉴/기능 접근 매트릭스) - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 로그인 페이지 제목/설명, 접근 권한 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- Zustand: `settingsStore`를 통한 테마/언어 설정 관리 - -**미구현 항목:** -- 실제 SSO(해양경찰 통합인증) 연동 -- GPKI(정부 공인인증서) 인증 모듈 연동 -- 공무원증 기반 인증 연동 -- 백엔드 세션 관리 및 JWT 토큰 발급 -- 감사 로그 DB 저장 및 조회 API -- 인사 시스템 연동을 통한 역할 자동 부여 +**미구현 (기업 연동)**: 해양경찰 SSO / GPKI / 공무원증 인증 — 현재 자체 ID/PW + JWT --- ### SFR-02: 시스템 기본 환경설정 및 공통 기능 -**제안요청서 정의:** 시스템 운영에 필요한 공통 코드, 기준정보, 알림, 공통 UI 컴포넌트를 관리하며, GIS 지도 기반 웹서비스 및 범용 데이터 처리 기능을 제공한다. +**제안요청서 정의:** 공통 코드, 기준정보, 알림, GIS 지도 웹서비스, 파일 처리, 검색·페이징·엑셀 내보내기. -**세부 요구사항 요약:** -- 공통 코드 등록·수정·폐기 관리 -- 알림 관리 (팝업, 배너, 공지사항) -- GIS 지도 기반 웹서비스 제공 -- 파일 업로드/다운로드 -- 검색·페이징·정렬 -- 엑셀 내보내기 - -**구현 화면:** 시스템 설정 (`src/features/admin/SystemConfig.tsx`), 공지사항 관리 (`src/features/admin/NoticeManagement.tsx`) +**구현 화면:** `features/admin/SystemConfig.tsx`, `features/admin/NoticeManagement.tsx` **공통 컴포넌트:** -- `src/shared/components/common/DataTable.tsx` - 데이터 테이블 (정렬, 필터링) -- `src/shared/components/common/ExcelExport.tsx` - 엑셀 내보내기 -- `src/shared/components/common/Pagination.tsx` - 페이징 처리 -- `src/shared/components/common/SearchInput.tsx` - 검색 입력 -- `src/shared/components/common/FileUpload.tsx` - 파일 업로드 -- `src/shared/components/common/PrintButton.tsx` - 인쇄 버튼 -- `src/shared/components/common/SaveButton.tsx` - 저장 버튼 -- `src/shared/components/common/NotificationBanner.tsx` - 알림 배너 -- `src/shared/components/common/PageToolbar.tsx` - 페이지 도구 모음 -- `src/shared/components/ui/card.tsx` - 카드 컴포넌트 -- `src/shared/components/ui/badge.tsx` - 뱃지 컴포넌트 +- `shared/components/common/DataTable.tsx` (검색·정렬·페이징·엑셀·출력) +- `ExcelExport / Pagination / SearchInput / FileUpload / PrintButton / SaveButton / NotificationBanner / PageToolbar` +- `shared/components/ui/` 9종 (Button, Input, Select, Textarea, Checkbox, Radio, Card, Badge, TabBar+TabButton) +- `shared/components/layout/` 3종 (PageContainer, PageHeader, Section) -**화면 구성 요소:** -- 공통 코드 관리 테이블 (875건 기준정보 JSON) -- 공지사항 등록·수정·삭제 폼 -- 알림 설정 (팝업/배너/공지 유형 선택) -- GIS 지도 컴포넌트 (MapLibre 기반) +**백엔드 연동 🟡 부분**: +- ✅ `/api/codes + /api/codes/{id}/children` — CodeMaster (875건) +- ✅ `/api/gear-types` — GearTypeMaster CRUD +- ✅ `/api/patrol-ships` — 함정 마스터 +- ✅ `/api/vessel-permits` — 선박 허가 마스터 (2026-04-17 MasterDataService 계층 분리) +- 🔲 SystemConfig / NoticeManagement — UI 는 완성, 백엔드 API 미연동 (Mock) -**구현 상태:** UI 완료 +**지도 ✅ 운영**: MapLibre GL 5 + deck.gl 9 기반 `lib/map/BaseMap` + `createStaticLayers/createMarkerLayer/createRadiusLayer` + `useMapLayers` RAF 기반 레이어 동기화 -**통합 현황:** -- i18n: 시스템 설정/공지사항 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 (시맨틱 CSS 변수 기반) -- Zustand: `settingsStore`를 통한 시스템 설정 상태 관리 -- GIS 지도: MapLibre GL 기반 공통 지도 컴포넌트 - -**미구현 항목:** -- 공통 코드 CRUD 백엔드 API 연동 -- 기준정보 DB 저장 (현재 JSON 파일 기반) -- 파일 업로드/다운로드 서버 연동 -- 알림 발송 엔진 (푸시, 이메일) -- 엑셀 내보내기 서버사이드 렌더링 (대용량) +**미구현**: 알림 발송 엔진(푸시/이메일), 엑셀 서버사이드 렌더링, 공통 코드 CRUD UI --- ### SFR-03: 통합 데이터 허브 수집·연계 관리 -**제안요청서 정의:** AIS, V-PASS, 위성, 해양환경 등 이기종 데이터를 통합 수집하는 데이터 허브를 구축하고, 수집 파이프라인의 실시간 모니터링 및 이상 감지 기능을 제공한다. +**제안요청서 정의:** AIS, V-PASS, 위성, 해양환경 등 이기종 데이터 통합 수집 + 실시간 모니터링 + 이상 감지. -**세부 요구사항 요약:** -- 다중 데이터 소스 수집 파이프라인 구축 (AIS, V-PASS, 위성, 해양환경, 기상) -- 실시간 스트리밍 및 배치 수집 지원 -- 수집 상태 모니터링 대시보드 -- 이상 감지 시 자동 알림 -- 수집 이력 조회 화면 +**구현 화면:** `features/admin/DataHub.tsx` (🔲 Mock) -**구현 화면:** 데이터 허브 (`src/features/admin/DataHub.tsx`) +**실제 파이프라인 ✅ 운영**: prediction(redis-211:18092, 5분 주기) +- snpdb(`signal.t_vessel_tracks_5min`, `signal.t_vessel_static`, `signal.snp_ais_tracks`) → AIS 원천 수집 +- signal-batch(192.168.1.18:18090) → 정적정보 보강 (shipKindCode/status/heading/draught) +- vessel_store cache (24h sliding window, ~55,000 선박, 2.3M points) +- 분류 파이프라인 7단계 (preprocess → behavior → resample → feature → classify → cluster → seasonal) -**화면 구성 요소:** -- 선박신호 수신 현황: 5개 신호원(지상 AIS, 위성 AIS, V-PASS, LRIT, 해상VHF) 24시간 타임라인 히트맵 -- 선박위치정보 모니터링: 22개 연계 채널 상태 테이블 (수신율, 지연시간, 최종수신) -- 채널별 상태 표시 (정상/경고/오류) -- 수집 파이프라인 등록·시작·중지 관리 버튼 -- 수집 이력 로그 테이블 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 데이터 허브 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- Zustand: mock 데이터 기반 수집 현황 표시 - -**미구현 항목:** -- 실제 AIS 수신기 연동 (지상·위성) -- V-PASS 시스템 API 연동 -- LRIT 데이터센터 연동 -- 해양환경·기상 데이터 수집 배치 구현 -- 실시간 스트리밍 파이프라인 (Kafka 등) -- 수집 이상 감지 알림 엔진 -- 수집 이력 DB 저장 +**미구현**: DataHub 화면의 실시간 수집 상태 대시보드 (현재 Mock), V-PASS·위성·해양환경 연동, 이상 감지 자동 알림 --- ### SFR-04: AI 불법조업 예측모델 관리 -**제안요청서 정의:** 해역별 불법조업 위험도를 예측하는 AI 모델의 개발·훈련·배포·모니터링 전주기를 관리하는 체계를 제공한다. +**제안요청서 정의:** AI 모델 등록/버전 관리/배포/모니터링. -**세부 요구사항 요약:** -- 학습 데이터셋 관리 (수집·정제·라벨링) -- 모델 구조 설계 및 하이퍼파라미터 관리 -- 과적합 방지 (교차검증, 정규화) -- 재학습 파이프라인 (자동/수동) -- 예측 결과 API 제공 +**구현 화면:** `features/ai-operations/AIModelManagement.tsx` (🔲 Mock) -**구현 화면:** AI 모델 관리 (`src/features/ai-operations/AIModelManagement.tsx`) +**DB ✅**: `ai_model_versions`, `ai_model_metrics` (V013) -**화면 구성 요소:** -- 모델 레지스트리: 5개 버전 관리 (v1.0~v2.1), 배포 이력, 성능 지표 비교 테이블 -- 탐지 규칙 관리: 6개 규칙 (EEZ 침범, AIS 차단, 속력 이상, 선단 밀집, 환적 의심, MMSI 변조) ON/OFF 토글, 가중치 슬라이더 -- 피처 엔지니어링: 20개 피처 (Kinematic/Geometric/Temporal/Behavioral/Contextual 5개 카테고리) -- 학습 파이프라인: 6단계 시각화 (데이터 수집 → 전처리 → 피처추출 → 모델학습 → 평가 → 배포) -- 성능 모니터링: 정확도·Recall·F1·오탐률·리드타임 추이 차트 -- 어구 탐지 모델: GB/T 5147 어구 분류 체계 -- 7대 탐지 엔진: 불법조업 감시 알고리즘 v4.0 (906척 허가어선 기준) -- API 문서: 예측 결과 API 사양 표시 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: AI 모델 관리 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- ECharts: 성능 모니터링 추이 차트 렌더링 - -**미구현 항목:** -- 실제 ML 모델 학습 및 추론 엔진 연동 -- 학습 데이터셋 관리 (데이터 수집·정제·라벨링 파이프라인) -- 모델 버전 관리 저장소 (MLflow 등) -- 재학습 자동화 (스케줄러, 트리거 기반) -- 예측 결과 REST API 서버 구현 -- 과적합 감지 및 모델 드리프트 모니터링 +**미구현**: MLflow/Kubeflow 연동, 모델 배포 파이프라인, A/B 테스트, 실시간 모델 성능 추적 (현재 전부 Mock) --- -### SFR-05: 격자 기반 불법조업 위험도 지도 생성·시각화 +### SFR-05: 격자 기반 불법조업 위험도 지도 -**제안요청서 정의:** AI 예측 결과를 격자 및 해역 단위 위험도 지도로 시각화하여, 해역별 위험 수준을 직관적으로 파악할 수 있는 지리정보 기반 인터페이스를 제공한다. +**제안요청서 정의:** 격자 단위 위험도 예측 + 지도 시각화. -**세부 요구사항 요약:** -- 격자체계 정의 (해역 분할 기준) -- 5단계 등급화 위험도 지도 (매우높음/높음/보통/낮음/안전) -- 조건별 필터링 (기간, 해역, 위험등급) -- 격자 선택 시 상세 이력 조회 -- 지도 출력(인쇄) 기능 -- MTIS 해양사고 통계 연계 +**구현 화면:** `features/risk-assessment/RiskMap.tsx` (🔲 Mock) -**구현 화면:** 위험도 지도 (`src/features/risk-assessment/RiskMap.tsx`) +**DB ✅**: `prediction_risk_grid` (V015) — prediction 이 격자별 위험도 저장 -**화면 구성 요소:** -- 10x18 격자 히트맵: 위험도 5단계 색상 매핑 -- 6개 탭: 위험도 히트맵 / 년도별 통계 / 선박 특성별 / 사고종류별 / 시간적 특성별 / 사고율 -- 해역별 요약 테이블: 6개 구역 위험도·추세·선박수 -- 위험등급 분포 범례 (건수, 비율) -- GIS 지도 오버레이 (EEZ, NLL 경계선 표시) -- MTIS 해양사고 통계 차트 (중앙해양안전심판원 데이터 기반) -- 년도별 사고 추이, 선박 유형별·톤수별·선령별 분포, 사고종류별 분석 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 위험도 지도 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- deck.gl: 격자 히트맵 레이어 렌더링 (`useMapLayers` + RAF 제로 리렌더) -- ECharts: MTIS 해양사고 통계 차트, 년도별 추이 차트 - -**미구현 항목:** -- AI 예측 모델 연동을 통한 실시간 위험도 산출 -- 격자 선택 시 실제 이력 데이터 조회 (DB 연동) -- MTIS 실시간 데이터 API 연계 -- 위험도 지도 자동 갱신 (주기적 배치) -- 격자 해상도 동적 변경 +**미구현**: RiskMap 화면의 prediction_risk_grid 조회 연동, HexagonLayer/HeatmapLayer 실시간화 --- ### SFR-06: 단속 계획·경보 연계 -**제안요청서 정의:** AI 위험도 분석 결과를 기반으로 단속 우선지역을 자동 도출하고, 임계값 초과 시 경보를 발령하여 선제적 대응 체계를 구축한다. +**제안요청서 정의:** 고위험 구역 탐지 기반 단속 계획 수립 + 경보 연계. -**세부 요구사항 요약:** -- 위험도 기반 단속 우선지역 자동 추천 -- 적정 단속 계획 수립 (인력·함정 배치) -- 임계값 초과 시 자동 경보 발령 -- 단속 계획 이력 관리 +**구현 화면:** `features/risk-assessment/EnforcementPlan.tsx` -**구현 화면:** 단속 계획 (`src/features/risk-assessment/EnforcementPlan.tsx`) +**백엔드 연동 ✅**: +- `/api/enforcement/plans` (GET/POST) — EnforcementPlan CRUD +- `/api/enforcement/records` 연계 — 단속 이력 등록 +- `PatrolAssignment` — 함정 배정 +- 단일 함정 / 다함정 순찰 작전 탭 (2026-04-14 확장) -**화면 구성 요소:** -- 단속 계획 목록: 5개 계획 (계획명, 대상 해역, 기간, 투입 함정, 상태) -- 지도 기반 계획 시각화 (단속 구역·함정 배치 표시) -- 단속 계획 상세 테이블 (우선순위, 예상 위험도, 투입 자원) -- 경보 현황 패널 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 단속 계획 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- deck.gl: 단속 구역/함정 배치 지도 레이어 렌더링 -- Zustand: `enforcementStore`를 통한 단속 계획 데이터 관리 - -**미구현 항목:** -- 위험도 기반 단속 우선지역 자동 추천 알고리즘 -- 경보 발령 엔진 (임계값 설정 및 자동 발송) -- 단속 계획 승인 워크플로우 -- 함정·인력 자원 관리 시스템 연동 -- 단속 계획 이력 DB 저장 +**Prediction 연계**: 위험도 기반 자동 계획은 Mock (후속 과제) --- ### SFR-07: AI 경비함정 단일 함정 순찰·경로 추천 -**제안요청서 정의:** 개별 경비함정의 최적 순찰 경로를 AI가 산출하여 추천하며, 함정 성능·기상·위험도를 종합적으로 반영한 시나리오별 경로를 제공한다. +**제안요청서 정의:** 단일 함정 최적 순찰 경로 AI 추천. -**세부 요구사항 요약:** -- 함정 성능(속력, 항속거리), 기상 조건, 해역 위험도를 반영한 경로 산출 -- 시나리오별 가중치 조정 (위험도 중시 / 커버리지 중시 / 연료 효율) -- 경유점 기반 시뮬레이션 -- Human-in-the-loop (운용자 수정 → 재계산) +**구현 화면:** `features/patrol/PatrolRoute.tsx` (🔲 Mock) -**구현 화면:** 순찰 경로 (`src/features/patrol/PatrolRoute.tsx`) +**DB ✅**: `patrol_ship_master` (6 함정) + `patrol_assignments` -**화면 구성 요소:** -- 함정 선택: 4척 (3001함/3005함/3009함/5001함), 함급·속력·항속거리·상태 표시 -- 추천 경로 지도: 경유점(Waypoint) 마커, 경로 폴리라인, EEZ/NLL 경계 -- 3가지 시나리오: 위험도 중시 / 커버리지 중시 / 연료 효율 -- 경로 요약: 거리, 소요시간, 연료 소모, 감시 격자 수 -- 경유점 상세: ID, 명칭, 좌표, 예상도착시간(ETA), 설명 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 순찰 경로 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- deck.gl: 경유점 마커, 경로 폴리라인, EEZ/NLL 경계 레이어 렌더링 -- Zustand: `patrolStore`를 통한 함정/경로 데이터 관리 - -**미구현 항목:** -- AI 경로 최적화 엔진 (유전 알고리즘, TSP 기반) -- 실시간 기상 데이터 반영 -- 해류·조류 정보 연동 -- Human-in-the-loop: 운용자 경유점 수정 시 실시간 재계산 -- 경로 확정 및 함정 단말 전송 +**미구현**: A* / Dijkstra 기반 경로 최적화 알고리즘, 연료/시간 제약 고려, 실시간 위험도 반영 --- ### SFR-08: AI 경비함정 다함정 협력형 경로 최적화 -**제안요청서 정의:** 다수 경비함정의 협력 운용을 통해 해역 커버리지를 최대화하고, 함정 간 중복을 최소화하는 최적 배치 계획을 산출한다. +**제안요청서 정의:** 다수 함정 협력 운용 최적화. -**세부 요구사항 요약:** -- 다함정 동시 배치 계획 수립 -- 해역 커버리지 최적화 (전체 감시 면적 최대화) -- 함정 간 순찰 구역 중복 최소화 -- 최적화 전후 비교 시뮬레이션 +**구현 화면:** `features/patrol/FleetOptimization.tsx` (🔲 Mock) -**구현 화면:** 함대 최적화 (`src/features/patrol/FleetOptimization.tsx`) - -**화면 구성 요소:** -- 함정 현황: 5척 투입 함정 목록 (함명, 함급, 상태, 담당 구역) -- 커버리지 구역: 6개 구역 배치 현황 -- 최적화 전후 비교: 커버리지율, 중복율, 응답시간 개선 지표 -- 지도 기반 배치 시각화 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 함대 최적화 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- deck.gl: 함정 배치, 커버리지 구역 지도 레이어 렌더링 -- Zustand: `patrolStore`를 통한 함정 현황 데이터 관리 - -**미구현 항목:** -- 다함정 협력 최적화 AI 엔진 (다목적 최적화) -- 실시간 함정 위치 추적 연동 -- 커버리지 계산 알고리즘 (보로노이 분할 등) -- 시뮬레이션 실행 엔진 -- 최적화 결과 함정 단말 자동 배포 +**미구현**: VRP(Vehicle Routing Problem) / MIP 최적화 엔진, 함대 자원 할당 알고리즘 --- -### SFR-09: 불법 어선 패턴 탐지 +### SFR-09: 불법 어선 패턴 탐지 (Dark Vessel / Spoofing / 환적) -**제안요청서 정의:** AIS 조작·위장·Dark Vessel(AIS 미송출 선박) 등 불법조업 관련 이상 패턴을 AI로 탐지하여 의심 선박을 식별한다. +**제안요청서 정의:** AIS 끊김·스푸핑·환적 등 의심 패턴 탐지. -**세부 요구사항 요약:** -- AIS 송출 차단(Dark Vessel) 탐지 -- MMSI 변조 감지 (동일 선박 다중 MMSI 사용) -- 속력 변화 이상 패턴 탐지 -- 국적·선명 위장 감지 -- 위험도 스코어링 및 라벨링 +**구현 화면:** `features/detection/DarkVesselDetection.tsx`, `features/vessel/TransferDetection.tsx` -**구현 화면:** 다크베셀 탐지 (`src/features/detection/DarkVesselDetection.tsx`) +**Prediction 연동 ✅ 운영**: +- **Dark Vessel**: 11패턴 P1~P11 기반 0~100점 연속 점수, 4 tier (CRITICAL≥70/HIGH≥50/WATCH≥30/NONE) +- **Transshipment**: 5단계 필터 파이프라인 (이종 쌍 → 감시영역 → RENDEZVOUS 90분+ → 점수 50+ → 밀집 방폭), 3,647/h → 2/cycle +- **Spoofing**: 1h 윈도우 teleport + extreme speed — ⚠️ 중국 MID 412 전원 0 수렴 (후속 재설계) +- 2026-04-16.7: 경량 riskScore `dark_suspicion_score(0~100) × 0.3` + 기선 근접 + 반복 이력, 45점 포화 해소 -**화면 구성 요소:** -- 의심 선박 목록: 7척 (선박명, 위험도 스코어, 탐지 유형, 좌표, 패턴) -- 5가지 탐지 패턴: AIS 차단, MMSI 변조, 속력 이상, 국적 위장, 선단 밀집 -- 위험도 스코어링: 0~1.0 수치 기반 위험 등급 표시 -- 라벨링 기능: 의심 선박에 대한 분류 태그 부여 -- 지도 기반 의심 선박 위치 표시 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 다크베셀 탐지 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- deck.gl: 의심 선박 위치 마커, 이동 경로 레이어 렌더링 -- Zustand: `vesselStore`를 통한 의심 선박 데이터 관리 - -**미구현 항목:** -- AI 패턴 탐지 엔진 (AIS 시계열 분석 모델) -- 실시간 AIS 데이터 스트림 연동 -- 위성 영상 기반 Dark Vessel 교차 검증 -- MMSI 변조 이력 DB 축적 및 분석 -- 탐지 결과 자동 경보 발령 -- 라벨링 결과 재학습 파이프라인 연동 +**백엔드 연동 ✅**: `/api/analysis/vessels` + `/api/analysis/dark` + `/api/analysis/transship` + `/api/analysis/history` +- DarkDetailPanel: ScoreBreakdown + P1~P11 카탈로그 +- 2026-04-17 alertLevels 헬퍼(`ALERT_LEVEL_TIER_SCORE` 등) 적용 --- -### SFR-10: 불법 어망·어구 탐지 및 관리 +### SFR-10: 불법 어망·어구 탐지 및 관리 (DAR-03) -**제안요청서 정의:** 불법 어구 설치 현황을 탐지하고, AIS 조작 패턴과 연계하여 불법 어구 사용 선박을 식별하는 분석 체계를 제공한다. +**제안요청서 정의:** 불법 어구 설치/투기/이동 탐지 + 한·중 어구 비교 판별. -**세부 요구사항 요약:** -- AIS 조작 패턴 기반 불법 어구 사용 감지 -- 불법 어망·어구 분석 및 식별 -- 어구 유형별 분류 및 판정 +**구현 화면:** `features/detection/GearDetection.tsx`, `features/detection/GearIdentification.tsx` -**구현 화면:** 어구 탐지 (`src/features/detection/GearDetection.tsx`), 어구 식별 (`src/features/detection/GearIdentification.tsx`) +**Prediction 연동 ✅ 운영 (DAR-03 G-01~G-06)**: +- G-01 수역-어구 불일치 (ZONE_I~IV 폴리곤) +- G-02 금어기 조업 (fishery_permit_cn.fishing_period_1/2 파싱) +- G-03 미등록/허가외 어구 (fishery_code 허용 어구 맵) +- G-04 MMSI cycling (≤30분 on/off 반복) +- G-05 고정어구 500m drift +- G-06 쌍끌이 공조 — **tier 분류**: STRONG / PROBABLE / SUSPECT +- 페어 탐색 `find_pair_candidates` (bbox + 궤적 유사도 2차) +- 한중어업협정 906척 NAME_EXACT + NAME_FUZZY 매칭 53%+ -**화면 구성 요소:** -- 어구 탐지 현황: 6개 어구 유형별 탐지 목록 -- 어구 식별 결정 트리: 어구 유형 판정 로직 시각화 -- 판정 요인 분석: 선박 이동 패턴, 정박 시간, 조업 형태 등 요인별 기여도 -- 중국어선 어구 탐지 (`src/features/detection/ChinaFishing.tsx` 내 연계) +**백엔드 연동 ✅**: +- `/api/vessel-analysis/groups` + `/groups/{key}/detail|correlations|candidates/{mmsi}/metrics|resolve` — 모선 워크플로우 (VesselAnalysisGroupService, 2026-04-17 PARENT_RESOLVE @Auditable 추가) +- `/api/analysis/gear-detections` (2026-04-16.6 신설) — 자동 탐지 결과 MMSI 중복 제거 +- 24h 궤적 리플레이 (gearReplayStore + useGearReplayLayers + TripsLayer) +- GearDetailPanel: 후보 클릭 → 모선 확정/제외 UI -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 어구 탐지/어구 식별 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- Zustand: `gearStore`를 통한 어구 탐지 데이터 관리 - -**미구현 항목:** -- AI 어구 탐지 모델 (위성 영상 + AIS 패턴 융합) -- 어구 유형 자동 분류 모델 (GB/T 5147 기반) -- 실시간 AIS 패턴 분석을 통한 어구 사용 추정 -- 탐지 결과 DB 저장 및 이력 관리 -- 불법 어구 적발 통계 자동 집계 +**마스터**: fishery_permit_cn (V029, 29 컬럼) + fleet_vessels 확장 (permit_year/fishery_code) --- ### SFR-11: 단속·탐지 이력 관리 -**제안요청서 정의:** 단속 활동 및 AI 탐지 결과에 대한 이력을 체계적으로 관리하여, 과거 사례 조회 및 AI 매칭 검증이 가능한 이력 관리 체계를 구축한다. +**제안요청서 정의:** 이벤트/경보 수집 → 단속 등록 → 이력 조회. -**세부 요구사항 요약:** -- 단속·탐지 이력 통합 조회 -- AI 탐지 결과와 실제 단속 결과 매칭 검증 -- 이력 기반 통계 분석 +**구현 화면:** `features/enforcement/EnforcementHistory.tsx`, `features/enforcement/EventList.tsx` -**구현 화면:** 단속 이력 (`src/features/enforcement/EnforcementHistory.tsx`), 이벤트 목록 (`src/features/enforcement/EventList.tsx`) +**백엔드 연동 ✅ 운영**: +- `/api/events` (GET/PATCH ack/status) + `/api/events/stats` — 이벤트 조회/확인/상태 변경 +- `/api/events/{id}` — 이벤트 상세 +- `/api/enforcement/records` (GET/POST/PATCH) — 단속 이력 CRUD +- `/api/alerts` — 발송된 알림 이력 +- **@Auditable** (2026-04-17): ENFORCEMENT_CREATE / ENFORCEMENT_UPDATE / ENFORCEMENT_PLAN_CREATE +- 2026-04-17 PR #C: EventList 액션 버튼(확인/선박상세/단속등록/오탐처리) → Button variant + text-*-600 dark:text-*-400 -**화면 구성 요소:** -- 단속 이력 테이블: 6건 단속 기록 (일시, 해역, 선박, 위반 유형, 조치, 결과) -- 이벤트 목록: 15건 탐지 이벤트 (DataTable 기반 정렬·필터·페이징) -- 이력 상세 조회 패널 -- 검색·필터링 (기간, 해역, 위반 유형) - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 단속 이력/이벤트 목록 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- Zustand: `enforcementStore`, `eventStore`를 통한 이력 데이터 관리 - -**미구현 항목:** -- 단속·탐지 이력 DB 연동 (CRUD API) -- AI 탐지 결과 ↔ 실제 단속 결과 자동 매칭 로직 -- 이력 기반 통계 분석 백엔드 -- 단속 보고서 자동 생성 -- 첨부파일(증거 사진·영상) 관리 +**Prediction 연동**: `prediction_events` → event_generator 4룰 (g06_pair_trawl / g01_zone_gear / g04_mmsi_cycling / g05_gear_drift) --- -### SFR-12: 모니터링 및 경보 현황판(대시보드) +### SFR-12: 모니터링 및 경보 현황판 (대시보드) -**제안요청서 정의:** 해양 불법조업 감시 현황을 실시간으로 종합 모니터링하고, 경보 상태를 직관적으로 파악할 수 있는 통합 대시보드를 제공한다. +**제안요청서 정의:** 실시간 상황 인식 대시보드. -**세부 요구사항 요약:** -- 핵심 KPI 실시간 표시 -- 실시간 상황 모니터링 (선박 위치, 탐지 현황) -- 경보 현황 표시 및 이력 타임라인 +**구현 화면:** `features/dashboard/Dashboard.tsx`, `features/monitoring/MonitoringDashboard.tsx`, `features/detection/ChinaFishing.tsx` (중국어선 3탭) -**구현 화면:** 메인 대시보드 (`src/features/dashboard/Dashboard.tsx`), 모니터링 대시보드 (`src/features/monitoring/MonitoringDashboard.tsx`) +**백엔드 연동 ✅ 운영**: +- `/api/stats/kpi` + `/api/events` + `/api/alerts` — KPI/이벤트/알림 통합 +- `/api/analysis/stats` (2026-04-16.6 신설) — MMSI별 최신 row COUNT FILTER 집계 (total/dark/spoofing/transship/risk분포/zone분포/avgRiskScore) +- `/api/analysis/vessels|dark|transship` — 실시간 선박 분석 테이블 +- `/api/prediction/health|status` — 시스템 상태 -**화면 구성 요소:** -- KPI 카드 6종: 실시간 탐지(47건), EEZ 침범(18건), 다크베셀(12건), 불법환적 의심(8건), 추적 중(15건), 나포/검문(3건) — 전일 대비 증감 표시 -- 작전 경보 타임라인: 10건 시간순 이벤트 (CRITICAL/HIGH/MEDIUM/LOW 4단계) -- 위험 선박 TOP 8: 위험도 스코어 순위, 선박명·국적·위치·패턴 표시 -- 경비함정 현황: 6척 상태 (추적/검문/초계/귀항/대기) -- 해역별 위험도: 7개 구역 위험 수준·추세 -- 시간대별 탐지 추이 차트 -- GIS 지도: EEZ/NLL 경계선, 선박 위치 마커, 히트맵 레이어 -- 기상 정보 표시 (풍향, 풍속, 수온, 파고) +**Prediction 연동 ✅**: `prediction_kpi_realtime` + `prediction_stats_hourly/daily/monthly` 5분 주기 갱신 -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 대시보드/모니터링 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- deck.gl: GIS 지도에 선박 위치 마커, 히트맵 레이어 렌더링 -- Zustand: `kpiStore`, `vesselStore`, `eventStore`를 통한 대시보드 데이터 관리 -- ECharts: 시간대별 탐지 추이 차트, 해역별 위험도 차트 - -**미구현 항목:** -- 실시간 데이터 스트리밍 연동 (WebSocket) -- 실시간 AIS 선박 위치 표시 -- 경보 자동 발령 및 알림 연동 -- KPI 실시간 갱신 (DB 기반 집계) -- 기상청 API 연동 +**특이운항 미니맵 + 판별 패널** (2026-04-16.6): classifyAnomaly → groupAnomaliesToSegments → VesselMiniMap (24h 궤적 + severity 색 segment) + VesselAnomalyPanel --- ### SFR-13: 통계·지표·성과 분석 -**제안요청서 정의:** 단속 건수, 위반 유형, 해역별·계절별 패턴, AI 적중률 등 핵심 성과 지표를 체계적으로 분석하고 시각화한다. +**제안요청서 정의:** 월별/일별/시간별 통계 + 위반 유형 분석. -**세부 요구사항 요약:** -- 월별 단속 추이 분석 -- 위반 유형별 분포 통계 -- KPI 지표: 정확도, 오탐률, 리드타임, 성공률, 응답시간 +**구현 화면:** `features/statistics/Statistics.tsx`, `features/statistics/ReportManagement.tsx` -**구현 화면:** 통계 (`src/features/statistics/Statistics.tsx`) +**백엔드 연동 ✅ 운영**: +- `/api/stats/monthly|daily|hourly` — 시계열 통계 +- `/api/admin/stats/audit|access|login` (2026-04-17 AdminStatsService 계층 분리) -**화면 구성 요소:** -- 월별 단속 추이 차트 (AreaChart) -- 위반 유형별 분포 차트 (PieChart/BarChart) -- KPI 지표 테이블: 정확도, 오탐률, 리드타임, 성공률, 응답시간 -- 해역별·계절별 크로스탭 분석 -- 통계 기간 설정 필터 +**Prediction 연동 ✅**: stats_aggregator → `prediction_stats_monthly/daily/hourly` -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 통계 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- ECharts: 월별 단속 추이(AreaChart), 위반 유형별 분포(PieChart/BarChart) 렌더링 - -**미구현 항목:** -- 통계 데이터 집계 백엔드 (DB 기반) -- AI 적중률 자동 산출 (탐지 ↔ 단속 결과 매칭) -- 보고서 자동 생성 및 출력 -- 기간별·해역별 다차원 분석 엔진 -- 성과 지표 목표 대비 달성률 추적 +**Report 관리**: 🔲 Mock (실제 리포트 PDF/HWP 생성은 미연동) --- ### SFR-14: 외부 서비스 제공 결과 연계 -**제안요청서 정의:** 유관기관(해수부, 수협, 어업관리단 등) 간 데이터를 API 기반으로 상호 조회·공유하며, 개인정보 비식별화 처리를 통해 보안을 확보한다. +**제안요청서 정의:** 유관기관(해수부/법무부/국방부) API 연계. -**세부 요구사항 요약:** -- API 기반 데이터 제공 체계 구축 -- 개인정보 비식별화 처리 -- 유관기관별 접근 권한 관리 +**구현 화면:** `features/statistics/ExternalService.tsx` (🔲 Mock) -**구현 화면:** 외부 서비스 (`src/features/statistics/ExternalService.tsx`) - -**화면 구성 요소:** -- 외부 서비스 목록: 5개 연계 서비스 (서비스명, 대상기관, 데이터 유형, 상태) -- 프라이버시 등급 표시 (개인정보 비식별화 수준) -- API 사용 현황 모니터링 -- 서비스별 접근 권한 설정 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 외부 서비스 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 - -**미구현 항목:** -- 외부 기관 API Gateway 구축 -- 개인정보 비식별화 처리 모듈 (k-익명성, 차등 프라이버시) -- 유관기관 연계 프로토콜 합의 및 구현 -- API 인증·인가 (OAuth 2.0 / API Key) -- 데이터 제공 이력 감사 로그 +**미구현**: 유관기관 OpenAPI 연동, 결과 공유 포맷 표준화 --- ### SFR-15: 단속요원 이용 모바일 대응 서비스 -**제안요청서 정의:** 현장 단속요원이 모바일 기기를 통해 AI 예측 정보, 경로 추천, 지도 조회 등을 활용할 수 있는 모바일 대응 서비스를 제공한다. +**제안요청서 정의:** 현장 단속요원 모바일 UI. -**세부 요구사항 요약:** -- AI 예측 정보 수신 (위험도, 의심선박) -- 최적 경로 추천 수신 -- 모바일 지도 조회 -- 오프라인 모드 지원 +**구현 화면:** `features/field-ops/MobileService.tsx` -**구현 화면:** 모바일 서비스 (`src/features/field-ops/MobileService.tsx`) +**백엔드 연동 🟡**: `/api/alerts` 연동으로 긴급 알림 수신, 지도·KPI 는 Mock -**화면 구성 요소:** -- 모바일 화면 프리뷰: 반응형 레이아웃 미리보기 -- 푸시 알림 설정: 알림 유형별 수신 ON/OFF -- 미니맵: 축소 지도에서 주요 이벤트 표시 -- 모바일 기능 목록 (예측 조회, 경로 수신, 사진 보고 등) - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 모바일 서비스 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 - -**미구현 항목:** -- 네이티브 모바일 앱 개발 (현재 웹 시뮬레이션만 존재) -- 푸시 알림 서버 (FCM/APNs) -- 오프라인 데이터 캐싱 (Service Worker / SQLite) -- 모바일 전용 경량 지도 엔진 -- 현장 사진·영상 업로드 기능 -- 단속 보고서 모바일 작성 +**미구현**: 실제 모바일 앱 패키징 (React Native / PWA), 오프라인 동기화, 푸시 알림 --- ### SFR-16: 함정용 단말 Agent 개발 -**제안요청서 정의:** 경비함정 터미널에서 AI 기반 예측·경로분석·신속 정보 조회가 가능한 전용 Agent 소프트웨어를 개발한다. +**제안요청서 정의:** 함정 탑재 단말 Agent. -**세부 요구사항 요약:** -- AI 예측 결과 실시간 표시 -- 경로 분석 및 최적화 제안 -- 신속 정보 제공 (선박 DB, 위험 이력) -- 지도 기반 상황 질의 인터페이스 +**구현 화면:** `features/field-ops/ShipAgent.tsx` (🔲 Mock) -**구현 화면:** 함정 Agent (`src/features/field-ops/ShipAgent.tsx`) +**DB ✅**: `deviceStatuses` 카탈로그 (ONLINE/OFFLINE/SYNCING/NOT_DEPLOYED) -**화면 구성 요소:** -- Agent 상태 테이블: 6개 함정별 Agent 설치 현황 (함명, 버전, 상태, 최종접속) -- Agent 기능 목록 (예측 조회, 경로 수신, DB 조회, 상황 보고) - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: 함정 Agent 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 - -**미구현 항목:** -- 함정 단말용 Agent 소프트웨어 개발 (네이티브 or Electron) -- AI 예측 결과 실시간 수신 모듈 -- 함정 단말 ↔ 본부 서버 통신 프로토콜 -- 오프라인 동작 모드 (위성 통신 불안정 대비) -- 지도 기반 자연어 질의 인터페이스 +**미구현**: 실제 함정 단말 SDK, GPS/AIS 수신 모듈, 백그라운드 동기화 --- -### SFR-17: 현장 함정 즉각 대응 AI 알림 메시지 발송 +### SFR-17: 현장 함정 즉각 AI 알림 메시지 발송 -**제안요청서 정의:** AI 탐지 결과를 현장 함정에 즉각 알림 메시지로 자동 발송하고, 수신 확인 및 미수신 시 재발송 기능을 제공한다. +**제안요청서 정의:** 고위험 이벤트 발생 시 즉각 SMS/푸시 알림. -**세부 요구사항 요약:** -- AI 탐지 결과 자동 전송 (함정 단말 + 모바일) -- 수신 확인 관리 -- 미수신 시 자동 재발송 +**구현 화면:** `features/field-ops/AIAlert.tsx` -**구현 화면:** AI 알림 (`src/features/field-ops/AIAlert.tsx`) +**백엔드 연동 ✅**: `/api/alerts` (2026-04-17 AlertService 계층 분리) — PredictionAlert Entity + eventId 필터 +**DB ✅**: `prediction_alerts` 테이블 (V018) -**화면 구성 요소:** -- 알림 목록: 5건 발송 내역 (알림 유형, 대상 함정, 발송시간, 내용 요약) -- 전송 상태 표시: 발송 완료 / 수신 확인 / 미수신 -- 재발송 버튼 -- 알림 유형 필터 (긴급/일반/정보) - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: AI 알림 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 - -**미구현 항목:** -- 알림 발송 서버 (메시지 큐 기반) -- 함정 단말 수신 확인 프로토콜 -- 미수신 자동 재발송 로직 (재시도 정책) -- 위성 통신 기반 발송 채널 -- 알림 발송 이력 DB 저장 +**미구현**: 실제 SMS/푸시 발송 게이트웨이 연동, 템플릿 엔진 --- -### SFR-18: 기계학습 운영 기능 +### SFR-18: 기계학습 운영 기능 (MLOps) -**제안요청서 정의:** ML 모델의 학습·배포·모니터링 전주기를 관리하는 MLOps 체계를 구축하여, 모델 품질 유지 및 지속적 개선을 지원한다. +**제안요청서 정의:** AI 모델 학습/배포/모니터링 자동화. -**세부 요구사항 요약:** -- 실험(Experiment) 관리 및 비교 -- 모델 레지스트리 (버전 관리, 승인 워크플로우) -- 배포 파이프라인 (Canary/Blue-Green) -- 모델 성능 모니터링 (드리프트 감지) +**구현 화면:** `features/ai-operations/MLOpsPage.tsx` (🔲 Mock) -**구현 화면:** WING AI 플랫폼 (`src/features/ai-operations/MLOpsPage.tsx`) - -**화면 구성 요소:** -- 7개 탭 구성: - 1. **운영 대시보드:** KPI 4종 (고위험 탐지, 중위험 탐지, 배포 중 모델, 진행 중 실험), 리소스 사용량 - 2. **Experiment Studio:** 실험 목록, 하이퍼파라미터, 메트릭 비교 - 3. **Model Registry:** 등록 모델, 버전 이력, 승인 상태 - 4. **Deploy Center:** 배포 파이프라인, Canary/Blue-Green 전략 - 5. **API Playground:** 모델 API 테스트 인터페이스 - 6. **LLMOps:** 대규모 언어모델 운영 (SFR-19와 공유) - 7. **플랫폼 관리:** 리소스·사용자·권한 관리 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: WING AI 플랫폼 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 -- ECharts: 리소스 사용량, 실험 메트릭 비교 차트 - -**미구현 항목:** -- MLOps 플랫폼 백엔드 (Kubeflow / MLflow 등) -- GPU 클러스터 연동 및 리소스 관리 -- 실험 추적 및 메트릭 저장 (MLflow Tracking) -- 모델 레지스트리 저장소 (S3/MinIO) -- CI/CD 기반 배포 파이프라인 -- 모델 드리프트 자동 감지 및 재학습 트리거 +**미구현**: MLflow/Kubeflow 파이프라인 실연동, 실험 추적, 모델 레지스트리 --- ### SFR-19: 대규모 언어모델(LLM) 운영 기능 -**제안요청서 정의:** 대규모 언어모델(LLM)의 선택·학습·추론·튜닝 기능을 제공하여, 자연어 기반 AI 서비스의 운영 기반을 구축한다. +**제안요청서 정의:** LLM 학습/추론 운영. -**세부 요구사항 요약:** -- LLM 모델 선택 및 관리 -- Fine-tuning 학습 파이프라인 -- 추론 서빙 및 로그 관리 -- 하이퍼파라미터 튜닝(HPS) +**구현 화면:** `features/ai-operations/MLOpsPage.tsx` LLMOps 탭 (🔲 Mock) -**구현 화면:** WING AI 플랫폼 LLMOps 탭 (`src/features/ai-operations/MLOpsPage.tsx` — LLMOps 탭) - -**화면 구성 요소:** -- LLMOps 5개 서브탭: - 1. **학습(Train):** Fine-tuning Job 6건, 데이터셋·에포크·학습률 설정 - 2. **HPS:** 하이퍼파라미터 서치 설정 및 결과 - 3. **로그(Log):** 추론 로그, 토큰 사용량, 응답 시간 - 4. **워커(Worker):** GPU 워커 상태 모니터링 - 5. **LLM 테스트:** 모델별 프롬프트 테스트 인터페이스 -- LLM 모델 목록: 6개 모델 (모델명, 파라미터 크기, 상태, 용도) - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: LLMOps 탭 제목/설명 적용 (MLOpsPage 공유) -- 테마: Dark/Light 전환 지원 - -**미구현 항목:** -- LLM 서빙 인프라 (vLLM / TGI 등) -- Fine-tuning 학습 파이프라인 (LoRA/QLoRA) -- 하이퍼파라미터 자동 탐색 (Optuna 등) -- GPU 클러스터 연동 및 워커 관리 -- 추론 로그 수집 및 분석 -- LLM 모델 평가 벤치마크 +**미구현**: LLM 학습 파이프라인, 검색 서비스, 토큰 사용량 모니터링 --- -### SFR-20: 자연어 처리 기반 AI 의사결정 지원(Q&A) 서비스 +### SFR-20: 자연어 처리 AI 의사결정 지원 Q&A -**제안요청서 정의:** 자연어 질의를 통해 법령·사례·AI 예측결과를 통합 검색하고, RAG 기반 대화형 의사결정 지원 서비스를 제공한다. +**제안요청서 정의:** 운영자 자연어 질의 → AI 분석 기반 응답. -**세부 요구사항 요약:** -- RAG(검색 증강 생성) 기반 대화형 Q&A -- 법령 DB 연결 (수산업법, 해양경비법 등) -- 부적절 답변 필터링 (가드레일) -- 대화 이력 관리 +**구현 화면:** `features/ai-operations/AIAssistant.tsx` -**구현 화면:** AI 어시스턴트 (`src/features/ai-operations/AIAssistant.tsx`) +**백엔드 연동 🟡 stub**: `/api/prediction/chat` — 현재 stub 응답. Redis 컨텍스트 캐시 인프라 준비 완료 -**화면 구성 요소:** -- 채팅 UI: 대화형 인터페이스 (사용자 질문 → AI 답변) -- 샘플 대화: 불법조업 관련 법령·절차·사례 질의응답 시연 -- 대화 이력 사이드바: 과거 대화 세션 목록 -- 법령 참조 뱃지: 답변에 인용된 법령·규정 출처 표시 -- 관련 문서 링크 - -**구현 상태:** UI 완료 - -**통합 현황:** -- i18n: AI 어시스턴트 페이지 제목/설명 적용 -- 테마: Dark/Light 전환 지원 - -**미구현 항목:** -- LLM 백엔드 서빙 (GPT-4 / LLaMA 등) -- RAG 파이프라인 (Vector DB + Embedding + Retriever) -- 법령 DB 구축 및 인덱싱 (수산업법, 해양경비법, EEZ법 등) -- 답변 품질 가드레일 (Hallucination 방지, 부적절 답변 필터) -- 대화 이력 DB 저장 -- 사용자 피드백 수집 및 모델 개선 루프 +**미구현 (Phase 9)**: Ollama 또는 외부 LLM 연동, RAG (Retrieval-Augmented Generation), 법령 DB 임베딩 --- -## 부록: 파일 경로 매핑 요약 +## 부록 A: 파일 경로 매핑 (2026-04-17) | SFR | 주요 파일 경로 | |-----|--------------| -| SFR-01 | `src/features/auth/LoginPage.tsx`, `src/features/admin/AccessControl.tsx` | -| SFR-02 | `src/features/admin/SystemConfig.tsx`, `src/features/admin/NoticeManagement.tsx`, `src/shared/components/common/*` | -| SFR-03 | `src/features/admin/DataHub.tsx` | -| SFR-04 | `src/features/ai-operations/AIModelManagement.tsx` | -| SFR-05 | `src/features/risk-assessment/RiskMap.tsx` | -| SFR-06 | `src/features/risk-assessment/EnforcementPlan.tsx` | -| SFR-07 | `src/features/patrol/PatrolRoute.tsx` | -| SFR-08 | `src/features/patrol/FleetOptimization.tsx` | -| SFR-09 | `src/features/detection/DarkVesselDetection.tsx` | -| SFR-10 | `src/features/detection/GearDetection.tsx`, `src/features/detection/GearIdentification.tsx` | -| SFR-11 | `src/features/enforcement/EnforcementHistory.tsx`, `src/features/enforcement/EventList.tsx` | -| SFR-12 | `src/features/dashboard/Dashboard.tsx`, `src/features/monitoring/MonitoringDashboard.tsx` | -| SFR-13 | `src/features/statistics/Statistics.tsx` | -| SFR-14 | `src/features/statistics/ExternalService.tsx` | -| SFR-15 | `src/features/field-ops/MobileService.tsx` | -| SFR-16 | `src/features/field-ops/ShipAgent.tsx` | -| SFR-17 | `src/features/field-ops/AIAlert.tsx` | -| SFR-18 | `src/features/ai-operations/MLOpsPage.tsx` | -| SFR-19 | `src/features/ai-operations/MLOpsPage.tsx` (LLMOps 탭) | -| SFR-20 | `src/features/ai-operations/AIAssistant.tsx` | +| SFR-01 | `frontend/src/features/auth/LoginPage.tsx`, `features/admin/AccessControl.tsx`, `features/admin/PermissionsPanel.tsx`, `backend/.../auth/*`, `permission/*`, `audit/*` | +| SFR-02 | `features/admin/SystemConfig.tsx`, `NoticeManagement.tsx`, `shared/components/common/*`, `shared/components/ui/*`, `backend/.../master/MasterDataController+Service.java` | +| SFR-03 | `features/admin/DataHub.tsx`, `prediction/` 전체 (14 algo + 7 파이프라인), `signal-batch 프록시` | +| SFR-04 | `features/ai-operations/AIModelManagement.tsx`, `DB ai_model_versions/metrics` | +| SFR-05 | `features/risk-assessment/RiskMap.tsx`, `DB prediction_risk_grid` | +| SFR-06 | `features/risk-assessment/EnforcementPlan.tsx`, `backend/.../enforcement/EnforcementController+Service.java` | +| SFR-07 | `features/patrol/PatrolRoute.tsx` | +| SFR-08 | `features/patrol/FleetOptimization.tsx` | +| SFR-09 | `features/detection/DarkVesselDetection.tsx`, `features/detection/components/DarkDetailPanel.tsx`, `features/vessel/TransferDetection.tsx`, `prediction/algorithms/dark_vessel.py`, `spoofing.py`, `transship.py`, `risk.py` | +| SFR-10 | `features/detection/GearDetection.tsx`, `GearIdentification.tsx`, `features/detection/components/GearDetailPanel.tsx`, `GearReplayController.tsx`, `prediction/algorithms/pair_trawl.py`, `gear_violation.py`, `vessel_type_mapping.py`, `backend/.../analysis/VesselAnalysisGroupService.java` | +| SFR-11 | `features/enforcement/EnforcementHistory.tsx`, `EventList.tsx`, `backend/.../event/EventController+Service.java`, `AlertService.java`, `enforcement/EnforcementService.java` | +| SFR-12 | `features/dashboard/Dashboard.tsx`, `features/monitoring/MonitoringDashboard.tsx`, `features/detection/ChinaFishing.tsx`, `features/detection/components/VesselMiniMap.tsx`, `VesselAnomalyPanel.tsx`, `backend/.../analysis/VesselAnalysisController+Service.java` | +| SFR-13 | `features/statistics/Statistics.tsx`, `ReportManagement.tsx`, `backend/.../stats/`, `admin/AdminStatsService.java` | +| SFR-14 | `features/statistics/ExternalService.tsx` | +| SFR-15 | `features/field-ops/MobileService.tsx` | +| SFR-16 | `features/field-ops/ShipAgent.tsx` | +| SFR-17 | `features/field-ops/AIAlert.tsx`, `backend/.../event/AlertController+Service.java` | +| SFR-18 | `features/ai-operations/MLOpsPage.tsx` | +| SFR-19 | `features/ai-operations/MLOpsPage.tsx` (LLMOps 탭) | +| SFR-20 | `features/ai-operations/AIAssistant.tsx`, `backend/.../analysis/PredictionProxyController.java` (`/api/prediction/chat`) | --- -## 부록: 공통 컴포넌트 매핑 +## 부록 B: 공통 컴포넌트 매핑 -| 컴포넌트 | 파일 경로 | 관련 SFR | -|----------|----------|---------| -| DataTable | `src/shared/components/common/DataTable.tsx` | SFR-02, SFR-11, SFR-13 | -| ExcelExport | `src/shared/components/common/ExcelExport.tsx` | SFR-02 | -| Pagination | `src/shared/components/common/Pagination.tsx` | SFR-02 | -| SearchInput | `src/shared/components/common/SearchInput.tsx` | SFR-02 | -| FileUpload | `src/shared/components/common/FileUpload.tsx` | SFR-02 | -| PrintButton | `src/shared/components/common/PrintButton.tsx` | SFR-02, SFR-05 | -| SaveButton | `src/shared/components/common/SaveButton.tsx` | SFR-02 | -| NotificationBanner | `src/shared/components/common/NotificationBanner.tsx` | SFR-02 | -| PageToolbar | `src/shared/components/common/PageToolbar.tsx` | SFR-02 | -| Card / Badge | `src/shared/components/ui/card.tsx`, `badge.tsx` | 전체 SFR 공통 | +| 컴포넌트 | 파일 | 관련 SFR | +|----------|------|---------| +| DataTable | `shared/components/common/DataTable.tsx` | SFR-02, 10, 11, 13 | +| PageContainer / PageHeader / Section | `shared/components/layout/*` | 전체 | +| Button / Input / Select / Textarea / Checkbox / Radio | `shared/components/ui/*` | 전체 (2026-04-17 SSOT 전영역 준수) | +| Card / Badge | `shared/components/ui/*` | 전체 | +| TabBar / TabButton (underline/pill/segmented) | `shared/components/ui/tabs.tsx` | SFR-09, 10, 12, 18 등 | +| SearchInput / ExcelExport / Pagination / FileUpload / PrintButton / SaveButton / NotificationBanner / PageToolbar | `shared/components/common/*` | SFR-02, 11, 13 | +| BaseMap + useMapLayers | `lib/map/*` | SFR-02, 05, 09, 10, 12 | +| ScoreBreakdown | `shared/components/common/ScoreBreakdown.tsx` | SFR-09, 10 | +| VesselMiniMap / VesselAnomalyPanel | `features/detection/components/*` | SFR-12 | +| GearReplayController | `features/detection/components/*` | SFR-10 | --- -## 부록: Zustand Store 매핑 +## 부록 C: 카탈로그 매핑 (shared/constants/ 25개) -| Store | 파일 경로 | 관련 SFR | -|-------|----------|---------| -| kpiStore | `src/stores/kpiStore.ts` | SFR-12 | -| vesselStore | `src/stores/vesselStore.ts` | SFR-09, SFR-12 | -| eventStore | `src/stores/eventStore.ts` | SFR-11, SFR-12 | -| enforcementStore | `src/stores/enforcementStore.ts` | SFR-06, SFR-11 | -| patrolStore | `src/stores/patrolStore.ts` | SFR-07, SFR-08 | -| gearStore | `src/stores/gearStore.ts` | SFR-10 | -| transferStore | `src/stores/transferStore.ts` | SFR-09 | -| settingsStore | `src/stores/settingsStore.ts` | SFR-01, SFR-02 (테마/언어 설정) | +- **위험도 / 상태**: alertLevels, violationTypes, eventStatuses, engineSeverities, performanceStatus (2026-04-17 catalogRegistry 등록), deviceStatuses, connectionStatuses, statusIntent +- **단속 / 순찰**: enforcementActions, enforcementResults, patrolStatuses, trainingZoneTypes +- **탐지**: darkVesselPatterns(11), vesselAnalysisStatuses, vesselTypes(7종), gearGroupTypes, gearViolationCodes(G-01~G-06), zoneCodes +- **인증 / 권한**: userRoles, userAccountStatuses, loginResultStatuses, permissionStatuses +- **모선 워크플로우**: parentResolutionStatuses, modelDeploymentStatuses +- **API 통신**: httpStatusCodes, kpiUiMap +- **알림**: (i18n common.aria / error / dialog / message 2026-04-17 추가) --- -## 부록: 2차 개발 단계 우선순위 제안 +## 부록 D: Zustand Store 매핑 -아래는 백엔드 연동 및 실제 기능 구현 시 권장하는 우선순위이다. +| Store | 파일 | 관련 SFR | +|-------|------|---------| +| kpiStore | `stores/kpiStore.ts` | SFR-12, 13 | +| vesselStore | `stores/vesselStore.ts` | SFR-09, 12 | +| eventStore | `stores/eventStore.ts` | SFR-11, 12, 17 | +| enforcementStore | `stores/enforcementStore.ts` | SFR-06, 11 | +| patrolStore | `stores/patrolStore.ts` | SFR-07, 08 | +| gearStore | `stores/gearStore.ts` | SFR-10 | +| gearReplayStore | `stores/gearReplayStore.ts` | SFR-10 (24h 리플레이) | +| transferStore | `stores/transferStore.ts` | SFR-09 | +| settingsStore | `stores/settingsStore.ts` | SFR-01, 02 (테마/언어) | -### 1순위 (핵심 인프라) -| SFR | 항목 | 사유 | -|-----|------|------| -| SFR-01 | SSO/GPKI 인증 연동 | 시스템 접근 보안의 기본 요건 | -| SFR-03 | 데이터 수집 파이프라인 | 전체 시스템의 데이터 기반 | -| SFR-02 | 공통 기능 백엔드 | CRUD, 파일관리 등 기반 기능 | +--- -### 2순위 (AI 핵심 기능) -| SFR | 항목 | 사유 | -|-----|------|------| -| SFR-04 | AI 예측 모델 연동 | 시스템 핵심 가치 | -| SFR-09 | 패턴 탐지 엔진 | 불법조업 감시 핵심 | -| SFR-05 | 위험도 지도 실시간화 | 예측 결과 시각화 | +## 부록 E: 백엔드 계층 (2026-04-17 정비 완료) -### 3순위 (운용 기능) -| SFR | 항목 | 사유 | -|-----|------|------| -| SFR-07 | 단일 함정 경로 최적화 | 현장 운용 직결 | -| SFR-08 | 다함정 협력 최적화 | 함대 운용 효율화 | -| SFR-06 | 단속 계획·경보 | 예측→대응 연계 | -| SFR-12 | 대시보드 실시간화 | 상황 인식 핵심 | +모든 컨트롤러가 `controller → service → repository` 일관성 유지. controller 가 repository 를 직접 주입하는 사례 0건. -### 4순위 (확장 기능) -| SFR | 항목 | 사유 | -|-----|------|------| -| SFR-18 | MLOps 플랫폼 | AI 모델 운영 자동화 | -| SFR-19 | LLMOps | LLM 서비스 운영 | -| SFR-20 | AI Q&A 서비스 | 의사결정 지원 | -| SFR-10 | 어구 탐지 | 전문 탐지 모델 | -| SFR-11 | 이력 관리 DB | 데이터 축적 | -| SFR-13 | 통계 백엔드 | 성과 분석 | -| SFR-14 | 외부 연계 API | 유관기관 협업 | -| SFR-15 | 모바일 앱 | 현장 대응 | -| SFR-16 | 함정 Agent | 함정 단말 | -| SFR-17 | AI 알림 발송 | 즉각 대응 | +| 컨트롤러 | 서비스 | 관련 SFR | +|---------|-------|---------| +| AuthController / PermTreeController / UserManagementController / RoleManagementService | - | SFR-01 | +| MasterDataController | MasterDataService | SFR-02 | +| AdminStatsController | AdminStatsService | SFR-13 | +| AdminLogController | (직접 Repository) | SFR-01 | +| VesselAnalysisController | VesselAnalysisService | SFR-09, 12 | +| VesselAnalysisProxyController | VesselAnalysisGroupService (+`@Auditable PARENT_RESOLVE`) | SFR-10 | +| PredictionProxyController | RestClient `@Bean predictionRestClient` | SFR-03, 20 | +| EventController | EventService | SFR-11, 12 | +| AlertController | AlertService | SFR-12, 17 | +| EnforcementController | EnforcementService (+`@Auditable ENFORCEMENT_*`) | SFR-06, 11 | +| ParentInferenceWorkflowController | ParentInferenceWorkflowService | SFR-10 | +| StatsController | (Repository 조회) | SFR-13 | + +**공통 Bean 설정**: `config/RestClientConfig.java` — `predictionRestClient` + `signalBatchRestClient` + +--- + +## 부록 F: 후속 과제 우선순위 (2026-04-17 기준) + +### 1순위 — 탐지 품질 개선 +- **spoofing_score 산출 재설계** — 중국 MID 412 전원 0 수렴. BD-09 필터 + teleport 25kn 임계 재검토 +- **RiskMap 실시간화** (SFR-05) — prediction_risk_grid 연동, HexagonLayer/HeatmapLayer +- **spoofing + transship 프론트 실데이터** 고도화 + +### 2순위 — 인프라 +- **코드 스플리팅** — React.lazy + manualChunks (현재 단일 번들 3MB+) +- **JSX placeholder/텍스트 한글 35건** i18n 전환 +- **external.iran_backend 매니페스트 노드 완전 삭제** (1~2 릴리즈 후) + +### 3순위 — Mock 화면 실연동 +- SFR-03 DataHub 실시간 상태 (prediction 연동) +- SFR-04 AIModelManagement (MLflow 연동) +- SFR-07, 08 경로 최적화 엔진 +- SFR-14 유관기관 OpenAPI +- SFR-18, 19 MLOps/LLMOps +- SFR-20 `/api/prediction/chat` 실 LLM 연동 + +### 4순위 — 기업 연동 +- SFR-01 SSO / GPKI / 공무원증 인증 +- SFR-15 모바일 앱 패키징 (PWA 또는 React Native) +- SFR-16 함정 단말 SDK +- SFR-17 SMS/푸시 발송 게이트웨이 + +--- + +## 변경 이력 + +| 버전 | 일자 | 변경 내용 | +|------|------|---------| +| 1.0 | 2026-04-06 | 초기 작성 (프론트엔드 프로토타입 20 SFR UI 완료) | +| 2.0 | 2026-04-06 | SFR 상세 추적 매트릭스 완성 | +| 3.0 | 2026-04-17 | **전면 재작성** — 백엔드/prediction 운영 배포 반영, PR #A/#B/#C 구조 정비 결과, 부록 E 백엔드 계층 신설 | diff --git a/docs/sfr-user-guide.md b/docs/sfr-user-guide.md index 730a10b..fc3c4f2 100644 --- a/docs/sfr-user-guide.md +++ b/docs/sfr-user-guide.md @@ -1,7 +1,8 @@ # SFR 요구사항별 화면 사용 가이드 > **문서 작성일:** 2026-04-06 -> **시스템 버전:** v0.1.0 (프로토타입) +> **최종 업데이트:** 2026-04-17 (2026-04-17 릴리즈 기준) +> **시스템 버전:** 운영 배포 (rocky-211 + redis-211) > **다국어:** 한국어/영어 전환 지원 (헤더 우측 EN/한국어 버튼) > **테마:** 다크/라이트 전환 지원 (헤더 우측 해/달 아이콘 버튼) @@ -11,7 +12,12 @@ 이 문서는 **KCG AI 모니터링 시스템**의 각 SFR(소프트웨어 기능 요구사항)이 화면에서 어떻게 구현되어 있는지를 **비개발자**(일반 사용자, 사업 PM, 산출물 작성자)가 이해할 수 있도록 정리한 가이드입니다. -현재 시스템은 **프로토타입 단계(v0.1.0)**로, 모든 SFR의 UI가 완성되어 있으나 백엔드 서버 연동은 아직 이루어지지 않았습니다. 화면에 표시되는 데이터는 시연용 샘플 데이터입니다. +### 시스템 현황 (2026-04-17 기준) +- **프런트엔드·백엔드·분석엔진(prediction) 운영 배포 완료** — 자체 JWT 인증 + 트리 기반 RBAC + 감사 로그 + 65+ API +- **AI 분석 엔진(prediction)**: 5분 주기로 AIS 원천 데이터(snpdb)를 분석하여 결과를 `kcgaidb` 에 자동 저장 (14 알고리즘 + DAR-03 G-01~G-06) +- **실시간 연동 화면**: Dashboard / MonitoringDashboard / ChinaFishing / DarkVesselDetection / GearDetection / EnforcementHistory / EventList / AIAlert / Statistics / AccessControl / PermissionsPanel / Audit 등 **15+ 화면이 실 API + prediction 결과를 실시간으로 표시** +- **Mock 화면**: DataHub / AIModelManagement / RiskMap / PatrolRoute / FleetOptimization / ExternalService / ShipAgent / MLOpsPage / AIAssistant 는 UI 완성, 백엔드/AI 엔진 연동은 단계적 추가 중 +- **자세한 추적 매트릭스**: `docs/sfr-traceability.md` v3.0 참조 --- @@ -55,17 +61,18 @@ - 역할별 데모 계정 선택 (ADMIN, OPERATOR, ANALYST, FIELD, VIEWER) - 로그인 후 역할에 따른 메뉴 접근 제어 -**구현 완료:** -- ✅ 로그인 화면 UI 및 데모 계정 5종 로그인 기능 -- ✅ 역할 기반 세션 유지 및 메뉴 접근 제어 +**구현 완료 (2026-04-17 기준):** +- ✅ 로그인 화면 UI + 자체 ID/PW 인증 + JWT 쿠키 세션 + 역할별 데모 계정 5종 실 로그인 +- ✅ 비밀번호 정책(9자 이상 영문+숫자+특수) + 5회 실패 30분 잠금 + BCrypt 해시 +- ✅ 트리 기반 RBAC (47 리소스 노드, Level 0 13개 + Level 1 32개, 5 operation) + Caffeine 10분 TTL +- ✅ 모든 로그인 시도 감사 로그 저장 및 조회 (로그인 이력 화면) +- ✅ 역할 기반 세션 유지 및 메뉴 접근 제어 (사이드바/라우트 가드) -**향후 구현 예정:** -- 🔲 SSO(Single Sign-On) 연동 +**향후 구현 예정 (기업 환경 연동):** +- 🔲 SSO(해양경찰 통합인증) 연동 - 🔲 GPKI(정부 공인인증서) 인증 연동 -- 🔲 실제 사용자 DB 연동 및 비밀번호 암호화 - -**보완 필요:** -- ⚠️ 현재 데모 계정은 하드코딩되어 있으며, 운영 환경에서는 실제 인증 체계로 대체 필요 +- 🔲 공무원증 기반 인증 연동 +- 🔲 인사 시스템 연동 역할 자동 부여 --- @@ -83,16 +90,17 @@ - 역할별 접근 가능 메뉴 및 기능 권한 설정 - 사용자 목록 조회 및 역할 할당 -**구현 완료:** -- ✅ RBAC 5역할 체계 UI 및 역할별 권한 매트릭스 표시 -- ✅ 권한 설정 화면 레이아웃 및 인터랙션 +**구현 완료 (2026-04-17 기준):** +- ✅ 트리 기반 RBAC 실 운영 — 47 리소스 노드 × 5 operation (READ/CREATE/UPDATE/DELETE/EXPORT) × 다중 역할 OR 합집합 +- ✅ 역할별 권한 매트릭스 시각화 (셀 클릭 Y → N → 상속 사이클) +- ✅ 부모 READ 거부 시 자식 강제 거부, 상속 표시 +- ✅ 역할 CRUD (admin:role-management) + 권한 매트릭스 갱신 (admin:permission-management) +- ✅ 사용자-역할 할당 다이얼로그 (admin:user-management) +- ✅ 모든 권한 변경은 `auth_audit_log` 에 자동 기록 (ROLE_CREATE/UPDATE/DELETE/PERM_UPDATE/USER_ROLE_ASSIGN) **향후 구현 예정:** -- 🔲 실제 사용자 DB 연동을 통한 권한 CRUD -- 🔲 감사 로그(권한 변경 이력) 기록 - -**보완 필요:** -- ⚠️ 현재 화면의 데이터는 샘플이며 실제 저장/반영되지 않음 +- 🔲 권한 변경 이력 UI (auth_audit_log 조회는 현재 별도 화면) +- 🔲 역할 템플릿 복제 기능 --- @@ -369,17 +377,18 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 의심 선박 상세 프로필 및 이동 궤적 조회 - 위험도 등급별 분류 표시 -**구현 완료:** -- ✅ 의심 선박 7척 목록/지도 시각화 -- ✅ 5가지 행동 패턴 분석 결과 UI +**구현 완료 (2026-04-17 기준):** +- ✅ **AI 분석 엔진(prediction) 5분 주기 실시간 탐지 결과 표시** — snpdb AIS 원천 데이터 기반 +- ✅ Dark Vessel 11패턴 기반 0~100점 연속 점수 + 4단계 tier(CRITICAL≥70 / HIGH≥50 / WATCH≥30 / NONE) +- ✅ DarkDetailPanel — 선박 선택 시 ScoreBreakdown으로 P1~P11 각 패턴별 기여도 표시 +- ✅ 지도 기반 실시간 위치 + tier별 색상 구분 (라이트/다크 모드 대응) +- ✅ 최근 1시간 / 중국 선박(MMSI 412*) 필터, MMSI/선박명/패턴 검색 +- ✅ 특이운항 미니맵 (24h 궤적 + DARK/SPOOFING/TRANSSHIP/GEAR_VIOLATION/HIGH_RISK 구간 병합 하이라이트) **향후 구현 예정:** -- 🔲 AI Dark Vessel 탐지 엔진 연동 -- 🔲 실시간 AIS 데이터 분석 연동 +- 🔲 spoofing_score 산출 재설계 (중국 MID 412 선박 전원 0 수렴 이슈, BD-09 필터 + teleport 25kn 임계 재검토) - 🔲 SAR(위성영상) 기반 탐지 연동 - -**보완 필요:** -- ⚠️ 현재 탐지 결과는 샘플 데이터이며, AI 탐지 엔진 연동 후 실시간 탐지 결과로 교체 필요 +- 🔲 과거 이력 차트 (현재는 최근 24h 중심) --- @@ -398,16 +407,17 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 해역별 중국 어선 밀집도 분석 - 시계열 활동 패턴 분석 -**구현 완료:** -- ✅ 중국 어선 분석 종합 대시보드 UI -- ✅ 지도 기반 활동 현황 시각화 +**구현 완료 (2026-04-17 기준):** +- ✅ **3개 탭(AI 감시 대시보드 / 환적접촉탐지 / 어구·어망 판별) 전부 실데이터 연동** — `/api/analysis/*` 경유, MMSI prefix `412` 고정 +- ✅ 중국 선박 전체 분석 결과 실시간 그리드 (최근 1h, 위험도순 상위 200건) +- ✅ 특이운항 판별 — riskScore ≥ 40 상위 목록 + 선박 클릭 시 24h 궤적 미니맵 + 판별 구간 패널 +- ✅ 해역별 통항량 + 안전도 분석 (종합 위험/안전 지수) + 위험도 도넛 +- ✅ 자동탐지 결과(어구 판별 탭) row 클릭 시 상단 입력 폼 자동 프리필 **향후 구현 예정:** -- 🔲 AI 탐지 엔진 연동 (Dark Vessel + 어구 탐지 통합) -- 🔲 실시간 데이터 기반 분석 갱신 - -**보완 필요:** -- ⚠️ 현재 분석 데이터는 샘플이며, 실제 탐지 엔진 연동 필요 +- 🔲 관심영역 / VIIRS 위성영상 / 기상 예보 / VTS연계 (현재 "데모 데이터" 배지) +- 🔲 비허가 / 제재 / 관심 선박 탭 데이터 소스 연동 (현재 "준비중" 배지) +- 🔲 월별 집계 API 연동 (현재 통계 탭 "준비중") --- @@ -426,17 +436,17 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 탐지 결과 상세 정보 (위치, 크기, 어구 유형, 위험도) - 탐지 이미지 확인 -**구현 완료:** -- ✅ 어구 6건 탐지 결과 목록/지도 UI -- ✅ 어구 식별 결정트리 시각화 +**구현 완료 (2026-04-17 기준):** +- ✅ **DAR-03 G-01~G-06 실시간 탐지 결과** — prediction 5분 주기 + 한중어업협정 906척 레지스트리(V029) 매칭 53%+ +- ✅ G코드별 탐지: G-01(수역-어구 불일치) / G-02(금어기) / G-03(미등록 어구) / G-04(MMSI cycling) / G-05(고정어구 drift) / G-06(쌍끌이 — STRONG/PROBABLE/SUSPECT tier) +- ✅ 어구 그룹 지도 (ZONE_I~IV 폴리곤 + GeoJsonLayer + IconLayer) + 세부 필터 패널(해역/판정/위험도/모선 상태/허가/멤버 수) + localStorage 영속화 +- ✅ GearDetailPanel — 후보 클릭 → 점수 근거(관측 7종 + 보정 3종) + 모선 확정/제외 버튼 +- ✅ 24h 궤적 리플레이 (GearReplayController + TripsLayer, SPEED_FACTOR=2880, 24h→30s) +- ✅ 어구/어망 판별 화면 — 허가코드/어구물리특성/발견위치 입력 → 국적 판별(한/중/미확인) + 판별 근거·경고·AI 탐지 Rule·교차 검증 파이프라인 **향후 구현 예정:** -- 🔲 AI 어구 탐지 모델 연동 (영상 분석 기반) -- 🔲 실시간 CCTV/SAR 영상 분석 연동 -- 🔲 탐지 결과 자동 분류 및 알림 - -**보완 필요:** -- ⚠️ 현재 탐지 결과는 샘플 데이터이며, AI 탐지 모델 연동 후 실제 탐지 결과로 교체 필요 +- 🔲 영상(CCTV/SAR) 기반 어구 자동 분류 +- 🔲 한·중 어구 5종 구조 비교 이미지 라이브러리 확장 --- @@ -455,17 +465,17 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 이력 상세 정보 조회 및 검색/필터 - 이력 데이터 엑셀 내보내기 -**구현 완료:** -- ✅ 단속 이력 6건 목록/상세 UI -- ✅ AI 매칭 검증 결과 표시 +**구현 완료 (2026-04-17 기준):** +- ✅ **실시간 이벤트 조회** — `/api/events` 페이징/필터/확인(ACK)/상태 변경 +- ✅ **단속 이력 CRUD** — `/api/enforcement/records` (GET/POST/PATCH) + ENF-yyyyMMdd-NNNN UID 자동 발급 +- ✅ 이벤트 발생 → 확인 → 단속 등록 → 오탐 처리 워크플로우 (액션 버튼 4종) +- ✅ 모든 쓰기 액션 `auth_audit_log` 자동 기록 (ENFORCEMENT_CREATE / ENFORCEMENT_UPDATE / ACK_EVENT / UPDATE_EVENT_STATUS) +- ✅ KPI 카운트 (CRITICAL/HIGH/MEDIUM/LOW) + 엑셀 내보내기 + 출력 +- ✅ 단속 완료 시 prediction_events.status 자동 RESOLVED 갱신 **향후 구현 예정:** -- 🔲 단속 이력 DB 연동 (조회/등록/수정) -- 🔲 AI 매칭 검증 엔진 연동 -- 🔲 탐지-단속 연계 자동 분석 - -**보완 필요:** -- ⚠️ 현재 이력 데이터는 샘플이며, DB 연동 후 실제 단속 데이터로 교체 필요 +- 🔲 증거 파일(사진/영상) 업로드 서버 연동 +- 🔲 AI 매칭 검증 정량 지표 (탐지↔단속 confusion matrix) --- @@ -487,17 +497,15 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 함정 배치 현황 요약 - 실시간 경보 알림 표시 -**구현 완료:** -- ✅ KPI 카드 + 히트맵 + 타임라인 + 함정 현황 통합 대시보드 UI -- ✅ 반응형 레이아웃 (화면 크기에 따른 자동 배치) +**구현 완료 (2026-04-17 기준):** +- ✅ **실시간 KPI 카드** — `/api/stats/kpi` 연동, prediction 5분 주기 결과 기반 +- ✅ 실시간 상황 타임라인 — 최근 `prediction_events` 스트림 (긴급/경고 카운트 실시간) +- ✅ 함정 배치 현황 + 경보 알림 + 순찰 현황 통합 +- ✅ 라이트/다크 모드 반응형 (2026-04-17 PR #C 하드코딩 색상 제거) **향후 구현 예정:** -- 🔲 실시간 데이터 연동 (WebSocket 등) -- 🔲 KPI 수치 실시간 갱신 -- 🔲 히트맵/타임라인 실시간 업데이트 - -**보완 필요:** -- ⚠️ 현재 모든 수치는 샘플 데이터이며, 실시간 연동 후 정확한 운영 데이터로 교체 필요 +- 🔲 WebSocket 기반 실시간 push (현재는 주기 polling) +- 🔲 맞춤형 대시보드 레이아웃 (드래그/리사이즈) --- @@ -516,17 +524,15 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 경보 처리(확인/대응/종결) 워크플로우 - 경보 발생 이력 조회 -**구현 완료:** -- ✅ 경보 등급별 현황판 UI -- ✅ 경보 목록/상세 조회 화면 +**구현 완료 (2026-04-17 기준):** +- ✅ **실시간 경보 수신** — `/api/events` + `/api/alerts` 실 API 연동, prediction event_generator 4룰 기반 +- ✅ 경보 등급별(CRITICAL/HIGH/MEDIUM/LOW) 현황 + KPI 카운트 +- ✅ 경보 처리 워크플로우 — 확인(ACK) → 단속 등록 → 오탐 처리 (DB 저장 + `auth_audit_log` 기록) +- ✅ 시스템 상태 패널 (백엔드/AI 분석 엔진/DB 상태 실시간 표시, 30초 자동 갱신) **향후 구현 예정:** -- 🔲 실시간 경보 수신 연동 -- 🔲 경보 처리 워크플로우 DB 연동 -- 🔲 경보 자동 에스컬레이션 - -**보완 필요:** -- ⚠️ 현재 경보 데이터는 샘플이며, 실시간 연동 후 실제 경보 데이터로 교체 필요 +- 🔲 경보 자동 에스컬레이션 정책 +- 🔲 경보 룰 커스터마이즈 UI --- @@ -545,17 +551,15 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 선박/이벤트 클릭 시 상세 정보 팝업 - 지도 확대/축소 및 해역 필터링 -**구현 완료:** -- ✅ LiveMap 기반 실시간 감시 지도 UI -- ✅ 선박/이벤트 마커 및 팝업 인터랙션 +**구현 완료 (2026-04-17 기준):** +- ✅ **실시간 선박 위치 + 이벤트 마커** — prediction 5분 주기 분석 결과(`vessel_analysis_results.lat/lon`) + `prediction_events` 기반 +- ✅ MapLibre GL 5 + deck.gl 9 GPU 렌더링 (40만척+ 지원) +- ✅ 위험도별 마커 opacity/radius 차등 (2026-04-17 `ALERT_LEVEL_MARKER_OPACITY/RADIUS` 헬퍼 적용) +- ✅ 이벤트 상세 패널 + 고위험 사건 실시간 알림 (LIVE 표시) **향후 구현 예정:** -- 🔲 실시간 AIS/VMS 데이터 연동 -- 🔲 WebSocket 기반 실시간 위치 업데이트 -- 🔲 이벤트 발생 시 자동 지도 포커스 이동 - -**보완 필요:** -- ⚠️ 현재 선박 위치는 샘플 데이터이며, 실시간 데이터 연동 필요 +- 🔲 WebSocket 기반 실시간 push (현재는 주기 갱신) +- 🔲 SAR 위성영상 오버레이 --- @@ -601,17 +605,15 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V - 기간별/해역별/유형별 필터링 - 통계 데이터 엑셀 내보내기 및 인쇄 -**구현 완료:** -- ✅ 월별 추이 차트 및 KPI 5개 대시보드 UI -- ✅ 필터링 및 엑셀 내보내기/인쇄 기능 +**구현 완료 (2026-04-17 기준):** +- ✅ **실시간 통계 데이터** — `/api/stats/monthly|daily|hourly` 연동, prediction `stats_aggregator` 집계 결과 기반 +- ✅ 월별/일별/시간별 추이 그래프 (ECharts, KST 기준) +- ✅ 해역별/유형별 필터링 + 엑셀 내보내기/인쇄 +- ✅ 감사·보안 통계 — `/api/admin/stats/audit|access|login` (2026-04-17 AdminStatsService 계층 분리) **향후 구현 예정:** -- 🔲 통계 데이터 DB 연동 -- 🔲 실제 운영 데이터 기반 KPI 자동 산출 -- 🔲 맞춤형 보고서 생성 기능 - -**보완 필요:** -- ⚠️ 현재 KPI 수치(정확도 93.2%, 오탐율 7.8% 등)는 샘플 데이터이며, 실제 운영 데이터 기반으로 교체 필요 +- 🔲 보고서 자동 생성 (PDF/HWP) — 현재는 UI만 +- 🔲 맞춤형 지표 대시보드 설정 --- @@ -743,17 +745,15 @@ AI가 분석한 결과를 기반으로 관련 담당자에게 알림을 발송 - 알림 수신자 설정 및 발송 - 알림 전송 결과(성공/실패) 확인 -**구현 완료:** -- ✅ 알림 5건 전송 현황 UI -- ✅ 알림 유형별 분류 및 상세 조회 +**구현 완료 (2026-04-17 기준):** +- ✅ **AI 알림 이력 실 API 조회** — `/api/alerts` 연동 (2026-04-17 AlertService 계층 분리) +- ✅ prediction `alert_dispatcher` 모듈이 event_generator 결과 기반으로 `prediction_alerts` 테이블에 자동 기록 +- ✅ 알림 유형별 분류 + DataTable 검색/정렬/페이징/엑셀 내보내기 **향후 구현 예정:** -- 🔲 실제 알림 발송 기능 구현 (SMS, 이메일, Push 등) -- 🔲 AI 분석 결과 기반 자동 알림 트리거 -- 🔲 알림 발송 이력 DB 연동 - -**보완 필요:** -- ⚠️ 현재 알림은 실제 발송되지 않으며, 발송 채널(SMS/이메일/Push) 연동 필요 +- 🔲 실제 SMS/푸시 발송 게이트웨이 연동 (현재는 DB 기록만) +- 🔲 알림 템플릿 엔진 +- 🔲 수신자 그룹 관리 --- @@ -857,15 +857,27 @@ AI에게 질문하고 답변을 받을 수 있는 대화형(채팅) 인터페이 --- -## 부록: 현재 시스템 상태 요약 +## 부록: 현재 시스템 상태 요약 (2026-04-17 기준) | 항목 | 상태 | |------|------| | UI 구현 | 모든 SFR 완료 | -| 백엔드 연동 | 미구현 (전체) | -| 데이터 | 시연용 샘플 데이터 | -| 인증 체계 | 데모 계정 5종 (SSO/GPKI 미연동) | -| 실시간 기능 | 미구현 (WebSocket 등 미연동) | -| AI 모델 | 미연동 (탐지/예측/최적화 등) | -| 외부 시스템 | 미연동 (GICOMS, MTIS 등) | -| 모바일 앱 | 웹 시뮬레이션만 제공 (네이티브 앱 미개발) | +| **백엔드 연동** | **15+ 화면 실 API 연동 완료** (Auth/RBAC/Audit/Events/Alerts/Enforcement/Stats/Analysis/Master 등 65+ API) | +| **AI 분석 엔진 (prediction)** | **운영 중** — 5분 주기로 snpdb 분석 → kcgaidb 저장, 14 알고리즘 + DAR-03 G-01~G-06 | +| **데이터** | 실 AIS 원천(snpdb) + prediction 분석 결과 + 자체 DB 저장 데이터 (일부 화면은 여전히 Mock) | +| **인증 체계** | 자체 ID/PW + JWT + 트리 기반 RBAC + 5회 실패 잠금 (SSO/GPKI 미연동) | +| **실시간 기능** | prediction 5분 주기 + 프론트 30초 폴링 (WebSocket push 미도입) | +| **AI 모델** | Dark Vessel 11패턴 / DAR-03 G-01~G-06 / 환적 5단계 / 경량 risk 등 14종 운영 중 (일부 모델은 Mock 계획 단계) | +| **외부 시스템** | snpdb / gc-signal-batch 연동 완료. 유관기관 OpenAPI(GICOMS/MTIS 등)는 미연동 | +| **디자인 시스템** | `design-system.html` 쇼케이스 SSOT 전영역 준수, 라이트/다크 모드 완전 대응 | +| **다국어** | 한/영 alert/confirm/aria-label 전수 치환 완료 (JSX placeholder 35건은 후속 과제) | +| **모바일 앱** | 웹 시뮬레이션만 제공 (PWA/네이티브 앱 미개발) | + +--- + +## 변경 이력 + +| 일자 | 내용 | +|------|------| +| 2026-04-06 | 초기 작성 (프론트엔드 프로토타입 v0.1.0 기준) | +| 2026-04-17 | 헤더 + SFR-01/02/09/10/11/12/13/17 주요 섹션 업데이트. 실 API 연동 / prediction 운영 상태 / 2026-04-17 PR #A/#B/#C 반영 | From a4e29629fca4cf58045770138202f2faa4d99706 Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 17 Apr 2026 06:53:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(detection):=20GEAR=5FIDENTITY=5FCOLLIS?= =?UTF-8?q?ION=20=ED=83=90=EC=A7=80=20=ED=8C=A8=ED=84=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 동일 어구 이름이 서로 다른 MMSI 로 같은 5분 사이클에 동시 AIS 송출되는 공존 케이스를 신규 탐지 패턴으로 분리해 기록·분류한다. 부수 효과로 fleet_tracker.track_gear_identity 의 PK 충돌로 인한 사이클 실패도 해소. Prediction - algorithms/gear_identity.py: detect_gear_name_collisions + classify_severity - fleet_tracker.py: 공존/교체 분기 분리, UPSERT helper, savepoint 점수 이전 - output/event_generator.py: run_gear_identity_collision_events 추가 - scheduler.py: track_gear_identity 직후 이벤트 승격 호출 Backend (domain/analysis) - GearIdentityCollision 엔티티 + Repository(Specification+stats) - GearIdentityCollisionService (@Transactional readOnly / @Auditable resolve) - GearCollisionController /api/analysis/gear-collisions (list/stats/detail/resolve) - GearCollisionResponse / StatsResponse / ResolveRequest (record) DB - V030__gear_identity_collision.sql: gear_identity_collisions 테이블 + auth_perm_tree 엔트리(detection:gear-collision nav_sort=950) + 역할별 권한 Frontend - shared/constants/gearCollisionStatuses.ts + catalogRegistry 등록 - services/gearCollisionApi.ts (list/stats/get/resolve) - features/detection/GearCollisionDetection.tsx (PageContainer+Section+DataTable + 분류 액션 폼, design system SSOT 준수) - componentRegistry + feature index + i18n detection.json / common.json(ko/en) --- .../analysis/GearCollisionController.java | 64 +++ .../analysis/GearCollisionResolveRequest.java | 18 + .../analysis/GearCollisionResponse.java | 57 +++ .../analysis/GearCollisionStatsResponse.java | 14 + .../analysis/GearIdentityCollision.java | 99 ++++ .../GearIdentityCollisionRepository.java | 41 ++ .../GearIdentityCollisionService.java | 133 ++++++ .../V030__gear_identity_collision.sql | 90 ++++ frontend/src/app/componentRegistry.ts | 3 + .../detection/GearCollisionDetection.tsx | 427 ++++++++++++++++++ frontend/src/features/detection/index.ts | 1 + frontend/src/lib/i18n/locales/en/common.json | 1 + .../src/lib/i18n/locales/en/detection.json | 72 +++ frontend/src/lib/i18n/locales/ko/common.json | 1 + .../src/lib/i18n/locales/ko/detection.json | 72 +++ frontend/src/services/gearCollisionApi.ts | 114 +++++ .../src/shared/constants/catalogRegistry.ts | 10 + .../shared/constants/gearCollisionStatuses.ts | 74 +++ prediction/algorithms/gear_identity.py | 157 +++++++ prediction/fleet_tracker.py | 284 +++++++++--- prediction/output/event_generator.py | 122 +++++ prediction/scheduler.py | 15 + 22 files changed, 1804 insertions(+), 65 deletions(-) create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionController.java create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResolveRequest.java create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResponse.java create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionStatsResponse.java create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollision.java create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionRepository.java create mode 100644 backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionService.java create mode 100644 backend/src/main/resources/db/migration/V030__gear_identity_collision.sql create mode 100644 frontend/src/features/detection/GearCollisionDetection.tsx create mode 100644 frontend/src/services/gearCollisionApi.ts create mode 100644 frontend/src/shared/constants/gearCollisionStatuses.ts create mode 100644 prediction/algorithms/gear_identity.py diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionController.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionController.java new file mode 100644 index 0000000..56e2893 --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionController.java @@ -0,0 +1,64 @@ +package gc.mda.kcg.domain.analysis; + +import gc.mda.kcg.permission.annotation.RequirePermission; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.web.bind.annotation.*; + +/** + * 어구 정체성 충돌(GEAR_IDENTITY_COLLISION) 조회 + 분류 API. + * + * 경로: /api/analysis/gear-collisions + * - GET / 목록 (status/severity/name 필터, hours 윈도우) + * - GET /stats status/severity 집계 + * - GET /{id} 단건 상세 + * - POST /{id}/resolve 분류 (REVIEWED / CONFIRMED_ILLEGAL / FALSE_POSITIVE / REOPEN) + */ +@RestController +@RequestMapping("/api/analysis/gear-collisions") +@RequiredArgsConstructor +public class GearCollisionController { + + private static final String RESOURCE = "detection:gear-collision"; + + private final GearIdentityCollisionService service; + + @GetMapping + @RequirePermission(resource = RESOURCE, operation = "READ") + public Page list( + @RequestParam(required = false) String status, + @RequestParam(required = false) String severity, + @RequestParam(required = false) String name, + @RequestParam(defaultValue = "48") int hours, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "50") int size + ) { + return service.list(status, severity, name, hours, + PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "lastSeenAt"))) + .map(GearCollisionResponse::from); + } + + @GetMapping("/stats") + @RequirePermission(resource = RESOURCE, operation = "READ") + public GearCollisionStatsResponse stats(@RequestParam(defaultValue = "48") int hours) { + return service.stats(hours); + } + + @GetMapping("/{id}") + @RequirePermission(resource = RESOURCE, operation = "READ") + public GearCollisionResponse get(@PathVariable Long id) { + return GearCollisionResponse.from(service.get(id)); + } + + @PostMapping("/{id}/resolve") + @RequirePermission(resource = RESOURCE, operation = "UPDATE") + public GearCollisionResponse resolve( + @PathVariable Long id, + @Valid @RequestBody GearCollisionResolveRequest req + ) { + return GearCollisionResponse.from(service.resolve(id, req)); + } +} diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResolveRequest.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResolveRequest.java new file mode 100644 index 0000000..2925490 --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResolveRequest.java @@ -0,0 +1,18 @@ +package gc.mda.kcg.domain.analysis; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * gear_identity_collisions 분류(해결) 액션 요청. + * + * action: REVIEWED | CONFIRMED_ILLEGAL | FALSE_POSITIVE | REOPEN + * note : 선택 (운영자 메모) + */ +public record GearCollisionResolveRequest( + @NotBlank + @Pattern(regexp = "REVIEWED|CONFIRMED_ILLEGAL|FALSE_POSITIVE|REOPEN") + String action, + String note +) { +} diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResponse.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResponse.java new file mode 100644 index 0000000..139f79a --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionResponse.java @@ -0,0 +1,57 @@ +package gc.mda.kcg.domain.analysis; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; + +/** + * gear_identity_collisions 조회 응답 DTO. + */ +public record GearCollisionResponse( + Long id, + String name, + String mmsiLo, + String mmsiHi, + String parentName, + Long parentVesselId, + OffsetDateTime firstSeenAt, + OffsetDateTime lastSeenAt, + Integer coexistenceCount, + Integer swapCount, + BigDecimal maxDistanceKm, + BigDecimal lastLatLo, + BigDecimal lastLonLo, + BigDecimal lastLatHi, + BigDecimal lastLonHi, + String severity, + String status, + String resolutionNote, + List> evidence, + OffsetDateTime updatedAt +) { + public static GearCollisionResponse from(GearIdentityCollision e) { + return new GearCollisionResponse( + e.getId(), + e.getName(), + e.getMmsiLo(), + e.getMmsiHi(), + e.getParentName(), + e.getParentVesselId(), + e.getFirstSeenAt(), + e.getLastSeenAt(), + e.getCoexistenceCount(), + e.getSwapCount(), + e.getMaxDistanceKm(), + e.getLastLatLo(), + e.getLastLonLo(), + e.getLastLatHi(), + e.getLastLonHi(), + e.getSeverity(), + e.getStatus(), + e.getResolutionNote(), + e.getEvidence(), + e.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionStatsResponse.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionStatsResponse.java new file mode 100644 index 0000000..e82b243 --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearCollisionStatsResponse.java @@ -0,0 +1,14 @@ +package gc.mda.kcg.domain.analysis; + +import java.util.Map; + +/** + * gear_identity_collisions status/severity 별 집계 응답. + */ +public record GearCollisionStatsResponse( + long total, + Map byStatus, + Map bySeverity, + int hours +) { +} diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollision.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollision.java new file mode 100644 index 0000000..01e2ca8 --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollision.java @@ -0,0 +1,99 @@ +package gc.mda.kcg.domain.analysis; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * gear_identity_collisions 엔티티 (GEAR_IDENTITY_COLLISION 탐지 패턴). + * + * 동일 어구 이름이 서로 다른 MMSI 로 같은 cycle 내 동시 송출되는 공존 이력. + * prediction 엔진이 5분 주기로 UPSERT, 백엔드는 조회 및 운영자 분류(status) 만 갱신. + */ +@Entity +@Table(name = "gear_identity_collisions", schema = "kcg") +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class GearIdentityCollision { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false, length = 200) + private String name; + + @Column(name = "mmsi_lo", nullable = false, length = 20) + private String mmsiLo; + + @Column(name = "mmsi_hi", nullable = false, length = 20) + private String mmsiHi; + + @Column(name = "parent_name", length = 100) + private String parentName; + + @Column(name = "parent_vessel_id") + private Long parentVesselId; + + @Column(name = "first_seen_at", nullable = false) + private OffsetDateTime firstSeenAt; + + @Column(name = "last_seen_at", nullable = false) + private OffsetDateTime lastSeenAt; + + @Column(name = "coexistence_count", nullable = false) + private Integer coexistenceCount; + + @Column(name = "swap_count", nullable = false) + private Integer swapCount; + + @Column(name = "max_distance_km", precision = 8, scale = 2) + private BigDecimal maxDistanceKm; + + @Column(name = "last_lat_lo", precision = 9, scale = 6) + private BigDecimal lastLatLo; + + @Column(name = "last_lon_lo", precision = 10, scale = 6) + private BigDecimal lastLonLo; + + @Column(name = "last_lat_hi", precision = 9, scale = 6) + private BigDecimal lastLatHi; + + @Column(name = "last_lon_hi", precision = 10, scale = 6) + private BigDecimal lastLonHi; + + @Column(name = "severity", nullable = false, length = 20) + private String severity; + + @Column(name = "status", nullable = false, length = 30) + private String status; + + @Column(name = "resolved_by") + private UUID resolvedBy; + + @Column(name = "resolved_at") + private OffsetDateTime resolvedAt; + + @Column(name = "resolution_note", columnDefinition = "text") + private String resolutionNote; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "evidence", columnDefinition = "jsonb") + private List> evidence; + + @Column(name = "created_at", nullable = false, updatable = false) + private OffsetDateTime createdAt; + + @Column(name = "updated_at", nullable = false) + private OffsetDateTime updatedAt; +} diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionRepository.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionRepository.java new file mode 100644 index 0000000..32c249d --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionRepository.java @@ -0,0 +1,41 @@ +package gc.mda.kcg.domain.analysis; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; + +import java.time.OffsetDateTime; +import java.util.List; + +public interface GearIdentityCollisionRepository + extends JpaRepository, + JpaSpecificationExecutor { + + Page findAllByLastSeenAtAfterOrderByLastSeenAtDesc( + OffsetDateTime after, Pageable pageable); + + /** + * status 별 카운트 집계 (hours 윈도우). + * 반환: [{status, count}, ...] — Object[] {String status, Long count} + */ + @Query(""" + SELECT g.status AS status, COUNT(g) AS cnt + FROM GearIdentityCollision g + WHERE g.lastSeenAt > :after + GROUP BY g.status + """) + List countByStatus(OffsetDateTime after); + + /** + * severity 별 카운트 집계 (hours 윈도우). + */ + @Query(""" + SELECT g.severity AS severity, COUNT(g) AS cnt + FROM GearIdentityCollision g + WHERE g.lastSeenAt > :after + GROUP BY g.severity + """) + List countBySeverity(OffsetDateTime after); +} diff --git a/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionService.java b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionService.java new file mode 100644 index 0000000..d2790b4 --- /dev/null +++ b/backend/src/main/java/gc/mda/kcg/domain/analysis/GearIdentityCollisionService.java @@ -0,0 +1,133 @@ +package gc.mda.kcg.domain.analysis; + +import gc.mda.kcg.audit.annotation.Auditable; +import gc.mda.kcg.auth.AuthPrincipal; +import jakarta.persistence.EntityNotFoundException; +import jakarta.persistence.criteria.Predicate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 어구 정체성 충돌(GEAR_IDENTITY_COLLISION) 조회/분류 서비스. + * + * 조회는 모두 {@link Transactional}(readOnly=true), 분류 액션은 {@link Auditable} 로 + * 감사로그 기록. 상태 전이 화이트리스트는 REVIEWED / CONFIRMED_ILLEGAL / FALSE_POSITIVE / REOPEN. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class GearIdentityCollisionService { + + private static final String RESOURCE_TYPE = "GEAR_COLLISION"; + + private final GearIdentityCollisionRepository repository; + + @Transactional(readOnly = true) + public Page list( + String status, + String severity, + String name, + int hours, + Pageable pageable + ) { + OffsetDateTime after = OffsetDateTime.now().minusHours(Math.max(hours, 1)); + Specification spec = (root, query, cb) -> { + List preds = new ArrayList<>(); + preds.add(cb.greaterThan(root.get("lastSeenAt"), after)); + if (status != null && !status.isBlank()) { + preds.add(cb.equal(root.get("status"), status)); + } + if (severity != null && !severity.isBlank()) { + preds.add(cb.equal(root.get("severity"), severity)); + } + if (name != null && !name.isBlank()) { + preds.add(cb.like(root.get("name"), "%" + name + "%")); + } + return cb.and(preds.toArray(new Predicate[0])); + }; + return repository.findAll(spec, pageable); + } + + @Transactional(readOnly = true) + public GearIdentityCollision get(Long id) { + return repository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("GEAR_COLLISION_NOT_FOUND: " + id)); + } + + @Transactional(readOnly = true) + public GearCollisionStatsResponse stats(int hours) { + OffsetDateTime after = OffsetDateTime.now().minusHours(Math.max(hours, 1)); + Map byStatus = new HashMap<>(); + long total = 0; + for (Object[] row : repository.countByStatus(after)) { + String s = (String) row[0]; + long c = ((Number) row[1]).longValue(); + byStatus.put(s, c); + total += c; + } + Map bySeverity = new HashMap<>(); + for (Object[] row : repository.countBySeverity(after)) { + bySeverity.put((String) row[0], ((Number) row[1]).longValue()); + } + return new GearCollisionStatsResponse(total, byStatus, bySeverity, hours); + } + + @Auditable(action = "GEAR_COLLISION_RESOLVE", resourceType = RESOURCE_TYPE) + @Transactional + public GearIdentityCollision resolve(Long id, GearCollisionResolveRequest req) { + GearIdentityCollision row = repository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("GEAR_COLLISION_NOT_FOUND: " + id)); + AuthPrincipal principal = currentPrincipal(); + OffsetDateTime now = OffsetDateTime.now(); + + switch (req.action().toUpperCase()) { + case "REVIEWED" -> { + row.setStatus("REVIEWED"); + row.setResolvedBy(principal != null ? principal.getUserId() : null); + row.setResolvedAt(now); + row.setResolutionNote(req.note()); + } + case "CONFIRMED_ILLEGAL" -> { + row.setStatus("CONFIRMED_ILLEGAL"); + row.setResolvedBy(principal != null ? principal.getUserId() : null); + row.setResolvedAt(now); + row.setResolutionNote(req.note()); + } + case "FALSE_POSITIVE" -> { + row.setStatus("FALSE_POSITIVE"); + row.setResolvedBy(principal != null ? principal.getUserId() : null); + row.setResolvedAt(now); + row.setResolutionNote(req.note()); + } + case "REOPEN" -> { + row.setStatus("OPEN"); + row.setResolvedBy(null); + row.setResolvedAt(null); + row.setResolutionNote(req.note()); + } + default -> throw new IllegalArgumentException("UNKNOWN_ACTION: " + req.action()); + } + row.setUpdatedAt(now); + return repository.save(row); + } + + private AuthPrincipal currentPrincipal() { + var auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.getPrincipal() instanceof AuthPrincipal p) { + return p; + } + return null; + } +} diff --git a/backend/src/main/resources/db/migration/V030__gear_identity_collision.sql b/backend/src/main/resources/db/migration/V030__gear_identity_collision.sql new file mode 100644 index 0000000..dbc28c6 --- /dev/null +++ b/backend/src/main/resources/db/migration/V030__gear_identity_collision.sql @@ -0,0 +1,90 @@ +-- ============================================================================ +-- V030: 어구 정체성 충돌(GEAR_IDENTITY_COLLISION) 탐지 패턴 +-- 동일 어구 이름이 서로 다른 MMSI 로 같은 5분 사이클 내 동시 AIS 송출되는 +-- 케이스를 독립 탐지 패턴으로 기록. 공존 이력·심각도·운영자 분류 상태를 보존한다. +-- ============================================================================ + +-- ────────────────────────────────────────────────────────────────── +-- 1. 충돌 이력 테이블 +-- ────────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS kcg.gear_identity_collisions ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, -- 동일한 어구 이름 + mmsi_lo VARCHAR(20) NOT NULL, -- 정렬된 쌍 (lo < hi) + mmsi_hi VARCHAR(20) NOT NULL, + parent_name VARCHAR(100), + parent_vessel_id BIGINT, -- fleet_vessels.id + first_seen_at TIMESTAMPTZ NOT NULL, + last_seen_at TIMESTAMPTZ NOT NULL, + coexistence_count INT NOT NULL DEFAULT 1, -- 동일 cycle 공존 누적 + swap_count INT NOT NULL DEFAULT 0, -- 시간차 스왑 누적(참고) + max_distance_km NUMERIC(8,2), -- 양 위치 최대 거리 + last_lat_lo NUMERIC(9,6), + last_lon_lo NUMERIC(10,6), + last_lat_hi NUMERIC(9,6), + last_lon_hi NUMERIC(10,6), + severity VARCHAR(20) NOT NULL DEFAULT 'MEDIUM', -- CRITICAL/HIGH/MEDIUM/LOW + status VARCHAR(30) NOT NULL DEFAULT 'OPEN', -- OPEN/REVIEWED/CONFIRMED_ILLEGAL/FALSE_POSITIVE + resolved_by UUID, + resolved_at TIMESTAMPTZ, + resolution_note TEXT, + evidence JSONB, -- 최근 관측 요약 + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT gear_identity_collisions_pair_uk UNIQUE (name, mmsi_lo, mmsi_hi), + CONSTRAINT gear_identity_collisions_pair_ord CHECK (mmsi_lo < mmsi_hi) +); + +CREATE INDEX IF NOT EXISTS idx_gic_status + ON kcg.gear_identity_collisions(status, last_seen_at DESC); +CREATE INDEX IF NOT EXISTS idx_gic_severity + ON kcg.gear_identity_collisions(severity, last_seen_at DESC); +CREATE INDEX IF NOT EXISTS idx_gic_parent + ON kcg.gear_identity_collisions(parent_vessel_id) + WHERE parent_vessel_id IS NOT NULL; +CREATE INDEX IF NOT EXISTS idx_gic_name + ON kcg.gear_identity_collisions(name); +CREATE INDEX IF NOT EXISTS idx_gic_last_seen + ON kcg.gear_identity_collisions(last_seen_at DESC); + +COMMENT ON TABLE kcg.gear_identity_collisions IS + '동일 어구 이름이 서로 다른 MMSI 로 공존 송출되는 스푸핑 의심 패턴 (GEAR_IDENTITY_COLLISION).'; + +-- ────────────────────────────────────────────────────────────────── +-- 2. 권한 트리 / 메뉴 슬롯 (V024 이후 detection 그룹은 평탄화됨: parent_cd=NULL) +-- ────────────────────────────────────────────────────────────────── +INSERT INTO kcg.auth_perm_tree + (rsrc_cd, parent_cd, rsrc_nm, rsrc_level, sort_ord, + url_path, label_key, component_key, nav_sort, labels) +VALUES + ('detection:gear-collision', NULL, '어구 정체성 충돌', 1, 40, + '/gear-collision', 'nav.gearCollision', + 'features/detection/GearCollisionDetection', 950, + '{"ko":"어구 정체성 충돌","en":"Gear Identity Collision"}'::jsonb) +ON CONFLICT (rsrc_cd) DO NOTHING; + +-- ────────────────────────────────────────────────────────────────── +-- 3. 권한 부여 +-- ADMIN : 전체 op (READ/CREATE/UPDATE/DELETE/EXPORT) +-- OPERATOR: READ + UPDATE (분류 액션) +-- VIEWER/ANALYST/FIELD: READ +-- ────────────────────────────────────────────────────────────────── +INSERT INTO kcg.auth_perm(role_sn, rsrc_cd, oper_cd, grant_yn) +SELECT r.role_sn, 'detection:gear-collision', op.oper_cd, 'Y' +FROM kcg.auth_role r +CROSS JOIN (VALUES ('READ'), ('CREATE'), ('UPDATE'), ('DELETE'), ('EXPORT')) AS op(oper_cd) +WHERE r.role_cd = 'ADMIN' +ON CONFLICT (role_sn, rsrc_cd, oper_cd) DO NOTHING; + +INSERT INTO kcg.auth_perm(role_sn, rsrc_cd, oper_cd, grant_yn) +SELECT r.role_sn, 'detection:gear-collision', op.oper_cd, 'Y' +FROM kcg.auth_role r +CROSS JOIN (VALUES ('READ'), ('UPDATE')) AS op(oper_cd) +WHERE r.role_cd = 'OPERATOR' +ON CONFLICT (role_sn, rsrc_cd, oper_cd) DO NOTHING; + +INSERT INTO kcg.auth_perm(role_sn, rsrc_cd, oper_cd, grant_yn) +SELECT r.role_sn, 'detection:gear-collision', 'READ', 'Y' +FROM kcg.auth_role r +WHERE r.role_cd IN ('VIEWER', 'ANALYST', 'FIELD') +ON CONFLICT (role_sn, rsrc_cd, oper_cd) DO NOTHING; diff --git a/frontend/src/app/componentRegistry.ts b/frontend/src/app/componentRegistry.ts index 8f255e7..2bbd930 100644 --- a/frontend/src/app/componentRegistry.ts +++ b/frontend/src/app/componentRegistry.ts @@ -39,6 +39,9 @@ export const COMPONENT_REGISTRY: Record = { 'features/detection/ChinaFishing': lazy(() => import('@features/detection').then((m) => ({ default: m.ChinaFishing })), ), + 'features/detection/GearCollisionDetection': lazy(() => + import('@features/detection').then((m) => ({ default: m.GearCollisionDetection })), + ), // ── 단속·이벤트 ── 'features/enforcement/EnforcementHistory': lazy(() => import('@features/enforcement').then((m) => ({ default: m.EnforcementHistory })), diff --git a/frontend/src/features/detection/GearCollisionDetection.tsx b/frontend/src/features/detection/GearCollisionDetection.tsx new file mode 100644 index 0000000..86f52ff --- /dev/null +++ b/frontend/src/features/detection/GearCollisionDetection.tsx @@ -0,0 +1,427 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AlertOctagon, RefreshCw } from 'lucide-react'; + +import { PageContainer, PageHeader, Section } from '@shared/components/layout'; +import { Badge } from '@shared/components/ui/badge'; +import { Button } from '@shared/components/ui/button'; +import { Input } from '@shared/components/ui/input'; +import { Select } from '@shared/components/ui/select'; +import { Textarea } from '@shared/components/ui/textarea'; +import { Card, CardContent } from '@shared/components/ui/card'; +import { DataTable, type DataColumn } from '@shared/components/common/DataTable'; + +import { formatDateTime } from '@shared/utils/dateFormat'; +import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels'; +import { + GEAR_COLLISION_STATUS_ORDER, + getGearCollisionStatusIntent, + getGearCollisionStatusLabel, +} from '@shared/constants/gearCollisionStatuses'; +import { + getGearCollisionStats, + listGearCollisions, + resolveGearCollision, + type GearCollision, + type GearCollisionResolveAction, + type GearCollisionStats, +} from '@/services/gearCollisionApi'; +import { useSettingsStore } from '@stores/settingsStore'; + +/** + * 어구 정체성 충돌(GEAR_IDENTITY_COLLISION) 탐지 페이지. + * + * 동일 어구 이름이 서로 다른 MMSI 로 같은 cycle 에 공존 송출되는 경우를 목록화하고 + * 운영자가 REVIEWED / CONFIRMED_ILLEGAL / FALSE_POSITIVE 로 분류할 수 있게 한다. + */ + +type SeverityCode = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'; +const SEVERITY_OPTIONS: SeverityCode[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']; +const DEFAULT_HOURS = 48; + +export function GearCollisionDetection() { + const { t } = useTranslation('detection'); + const { t: tc } = useTranslation('common'); + const lang = useSettingsStore((s) => s.language) as 'ko' | 'en'; + + const [rows, setRows] = useState([]); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [statusFilter, setStatusFilter] = useState(''); + const [severityFilter, setSeverityFilter] = useState(''); + const [nameFilter, setNameFilter] = useState(''); + const [selected, setSelected] = useState(null); + const [resolveAction, setResolveAction] = useState('REVIEWED'); + const [resolveNote, setResolveNote] = useState(''); + const [resolving, setResolving] = useState(false); + + const loadData = useCallback(async () => { + setLoading(true); + setError(''); + try { + const [page, summary] = await Promise.all([ + listGearCollisions({ + status: statusFilter || undefined, + severity: severityFilter || undefined, + name: nameFilter || undefined, + hours: DEFAULT_HOURS, + size: 200, + }), + getGearCollisionStats(DEFAULT_HOURS), + ]); + setRows(page.content); + setStats(summary); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : t('gearCollision.error.loadFailed')); + } finally { + setLoading(false); + } + }, [statusFilter, severityFilter, nameFilter, t]); + + useEffect(() => { loadData(); }, [loadData]); + + // 선택된 row 와 현재 목록의 동기화 + const syncedSelected = useMemo( + () => selected ? rows.find((r) => r.id === selected.id) ?? selected : null, + [rows, selected], + ); + + const cols: DataColumn>[] = useMemo(() => [ + { + key: 'name', label: t('gearCollision.columns.name'), minWidth: '120px', sortable: true, + render: (v) => {v as string}, + }, + { + key: 'mmsiLo', label: t('gearCollision.columns.mmsiPair'), minWidth: '160px', + render: (_, row) => ( + + {row.mmsiLo} ↔ {row.mmsiHi} + + ), + }, + { + key: 'parentName', label: t('gearCollision.columns.parentName'), minWidth: '110px', + render: (v) => {(v as string) || '-'}, + }, + { + key: 'coexistenceCount', label: t('gearCollision.columns.coexistenceCount'), + width: '90px', align: 'center', sortable: true, + render: (v) => {v as number}, + }, + { + key: 'maxDistanceKm', label: t('gearCollision.columns.maxDistance'), + width: '110px', align: 'right', sortable: true, + render: (v) => { + const n = typeof v === 'number' ? v : Number(v ?? 0); + return {n.toFixed(2)}; + }, + }, + { + key: 'severity', label: t('gearCollision.columns.severity'), + width: '90px', align: 'center', sortable: true, + render: (v) => ( + + {getAlertLevelLabel(v as string, tc, lang)} + + ), + }, + { + key: 'status', label: t('gearCollision.columns.status'), + width: '110px', align: 'center', sortable: true, + render: (v) => ( + + {getGearCollisionStatusLabel(v as string, t, lang)} + + ), + }, + { + key: 'lastSeenAt', label: t('gearCollision.columns.lastSeen'), + width: '130px', sortable: true, + render: (v) => ( + {formatDateTime(v as string)} + ), + }, + ], [t, tc, lang]); + + const handleResolve = useCallback(async () => { + if (!syncedSelected) return; + const ok = window.confirm(t('gearCollision.resolve.confirmPrompt')); + if (!ok) return; + setResolving(true); + try { + const updated = await resolveGearCollision(syncedSelected.id, { + action: resolveAction, + note: resolveNote || undefined, + }); + setSelected(updated); + setResolveNote(''); + await loadData(); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : t('gearCollision.error.resolveFailed')); + } finally { + setResolving(false); + } + }, [syncedSelected, resolveAction, resolveNote, loadData, t]); + + const statusCount = (code: string) => stats?.byStatus?.[code] ?? 0; + + return ( + + } + > + {t('gearCollision.list.refresh')} + + } + /> + + {error && ( + + {error} + + )} + +
+
+ + + + + +
+
+ +
+
+ + + setNameFilter(e.target.value)} + /> +
+ + {t('gearCollision.filters.hours')} · {DEFAULT_HOURS}h + +
+
+ + {rows.length === 0 && !loading ? ( +

+ {t('gearCollision.list.empty', { hours: DEFAULT_HOURS })} +

+ ) : ( + )[]} + columns={cols} + pageSize={20} + showSearch={false} + showExport={false} + showPrint={false} + onRowClick={(row) => setSelected(row as GearCollision)} + /> + )} +
+ + {syncedSelected && ( +
+
+
+ + + + + + + + +
+
+
+ + {t('gearCollision.columns.severity')}: + + + {getAlertLevelLabel(syncedSelected.severity, tc, lang)} + + + {t('gearCollision.columns.status')}: + + + {getGearCollisionStatusLabel(syncedSelected.status, t, lang)} + +
+ {syncedSelected.resolutionNote && ( +

+ {syncedSelected.resolutionNote} +

+ )} +
+ + +