Compare commits
3 커밋
ddcb493160
...
bae2dbde08
| 작성자 | SHA1 | 날짜 | |
|---|---|---|---|
| bae2dbde08 | |||
| 451f38036a | |||
| b37e18d952 |
248
AGENTS.md
Normal file
248
AGENTS.md
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# KCG AI Monitoring (모노레포)
|
||||||
|
|
||||||
|
해양경찰청 AI 기반 불법어선 탐지 및 단속 지원 플랫폼
|
||||||
|
|
||||||
|
## 모노레포 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
kcg-ai-monitoring/
|
||||||
|
├── frontend/ # React 19 + TypeScript + Vite (UI)
|
||||||
|
├── backend/ # Spring Boot 3.x + Java 21 (인증/권한/감사 + 분석 API)
|
||||||
|
├── prediction/ # Python 3.11+ + FastAPI (AIS 분석 엔진, 5분 주기)
|
||||||
|
├── database/ # PostgreSQL 마이그레이션 참조용 README (실제 Flyway 파일은 backend/src/main/resources/db/migration/ V001~V030, 51 테이블)
|
||||||
|
│ └── migration/
|
||||||
|
├── deploy/ # 배포 가이드 + 서버 설정 문서
|
||||||
|
├── docs/ # 프로젝트 문서 (SFR, 아키텍처)
|
||||||
|
├── .gitea/ # Gitea Actions CI/CD (프론트 자동배포)
|
||||||
|
├── .Codex/ # Codex 워크플로우
|
||||||
|
├── .githooks/ # Git hooks
|
||||||
|
└── Makefile # 통합 dev/build 명령
|
||||||
|
```
|
||||||
|
|
||||||
|
## 시스템 구성
|
||||||
|
|
||||||
|
```
|
||||||
|
[Frontend Vite :5173] ──→ [Backend Spring :8080] ──→ [PostgreSQL kcgaidb]
|
||||||
|
↑ write
|
||||||
|
[Prediction FastAPI :18092] ─────┘ (5분 주기 분석 결과 저장)
|
||||||
|
↑ read
|
||||||
|
[SNPDB PostgreSQL] (AIS 원본)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **자체 백엔드**: 인증/권한/감사로그/관리자 + 운영자 의사결정 (확정/제외/학습) + prediction 분석 결과 조회 API (`/api/analysis/*`)
|
||||||
|
- **Prediction**: AIS → 분석 결과를 kcgaidb 에 직접 write (백엔드 호출 없음)
|
||||||
|
- **DB 공유 아키텍처**: 백엔드와 prediction 은 HTTP 호출 없이 kcgaidb 를 통해서만 연동
|
||||||
|
|
||||||
|
## 명령어
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make install # 전체 의존성 설치
|
||||||
|
make dev # 프론트 + 백엔드 동시 실행
|
||||||
|
make dev-all # 프론트 + 백엔드 + prediction 동시 실행
|
||||||
|
make dev-frontend # 프론트만
|
||||||
|
make dev-backend # 백엔드만
|
||||||
|
make dev-prediction # prediction 분석 엔진만 (FastAPI :8001)
|
||||||
|
make build # 전체 빌드
|
||||||
|
make lint # 프론트 lint
|
||||||
|
make format # 프론트 prettier
|
||||||
|
```
|
||||||
|
|
||||||
|
## 기술 스택
|
||||||
|
|
||||||
|
### Frontend (`frontend/`)
|
||||||
|
- React 19, TypeScript 5.9, Vite 8
|
||||||
|
- Tailwind CSS 4 + CVA
|
||||||
|
- MapLibre GL 5 + deck.gl 9 (지도)
|
||||||
|
- ECharts 6 (차트)
|
||||||
|
- Zustand 5 (상태관리)
|
||||||
|
- i18next (ko/en)
|
||||||
|
- React Router 7
|
||||||
|
- ESLint 10 + Prettier
|
||||||
|
|
||||||
|
### Prediction (`prediction/`) — 분석 엔진
|
||||||
|
- Python 3.11+, FastAPI, APScheduler
|
||||||
|
- 17개 알고리즘 모듈 (다크베셀, 스푸핑, 환적, 어구 상관·부모·정체성 충돌, 쌍끌이, 위험도, 어선 분류 등)
|
||||||
|
- 7단계 분류 파이프라인 (전처리→행동→리샘플→특징→분류→클러스터→계절)
|
||||||
|
- AIS 원본: SNPDB (5분 증분), 결과: kcgaidb (직접 write)
|
||||||
|
- prediction과 backend는 DB만 공유 (HTTP 호출 X, 단 실시간 상태 조회용 FastAPI 프록시 `/api/prediction/*` 예외)
|
||||||
|
|
||||||
|
### Backend (`backend/`)
|
||||||
|
- Spring Boot 3.x + Java 21
|
||||||
|
- Spring Security + JWT
|
||||||
|
- PostgreSQL + Flyway
|
||||||
|
- Caffeine (권한 캐싱)
|
||||||
|
- 트리 기반 RBAC (wing 패턴)
|
||||||
|
|
||||||
|
### Database (`kcgaidb`)
|
||||||
|
- PostgreSQL
|
||||||
|
- 사용자: `kcg-app`
|
||||||
|
- 스키마: `kcg`
|
||||||
|
|
||||||
|
## 배포 환경
|
||||||
|
|
||||||
|
| 서비스 | 서버 (SSH) | 포트 | 관리 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 프론트엔드 | rocky-211 | nginx 443 | Gitea Actions 자동배포 |
|
||||||
|
| 백엔드 | rocky-211 | 18080 | `systemctl restart kcg-ai-backend` |
|
||||||
|
| prediction | redis-211 | 18092 | `systemctl restart kcg-ai-prediction` |
|
||||||
|
|
||||||
|
- **URL**: https://kcg-ai-monitoring.gc-si.dev
|
||||||
|
- **배포 상세**: `deploy/README.md` 참조
|
||||||
|
- **CI/CD**: `.gitea/workflows/deploy.yml` (프론트만 자동, 백엔드/prediction 수동)
|
||||||
|
|
||||||
|
## 권한 체계
|
||||||
|
|
||||||
|
좌측 탭(메뉴) = 권한 그룹, 내부 패널/액션 = 자식 자원, CRUD 단위 개별 제어.
|
||||||
|
상세는 `.Codex/plans/vast-tinkering-knuth.md` 참조.
|
||||||
|
|
||||||
|
## 팀 컨벤션
|
||||||
|
|
||||||
|
- 팀 규칙: `.Codex/rules/`
|
||||||
|
- 커밋: Conventional Commits (한국어), `.githooks/commit-msg` 검증
|
||||||
|
- pre-commit: `frontend/` 디렉토리 기준 TypeScript + ESLint 검증
|
||||||
|
|
||||||
|
## 디자인 시스템 (필수 준수)
|
||||||
|
|
||||||
|
프론트엔드 UI는 **`/design-system.html` 쇼케이스를 단일 진실 공급원(SSOT)** 으로 한다.
|
||||||
|
모든 페이지/컴포넌트는 쇼케이스에 정의된 컴포넌트와 토큰만 사용한다.
|
||||||
|
|
||||||
|
### 쇼케이스 진입
|
||||||
|
- **URL**: https://kcg-ai-monitoring.gc-si.dev/design-system.html (메인 SPA와 별개)
|
||||||
|
- **소스**: `frontend/design-system.html` + `frontend/src/designSystemMain.tsx` + `frontend/src/design-system/`
|
||||||
|
- **추적 ID 체계**: `TRK-<카테고리>-<슬러그>` (예: `TRK-BADGE-critical-sm`)
|
||||||
|
- 호버 시 툴팁, "ID 복사 모드"에서 클릭 시 클립보드 복사
|
||||||
|
- URL 해시 딥링크: `#trk=TRK-BUTTON-primary-md`
|
||||||
|
- **단축키 `A`**: 다크/라이트 테마 토글
|
||||||
|
|
||||||
|
### 공통 컴포넌트 (반드시 사용)
|
||||||
|
|
||||||
|
| 컴포넌트 | 위치 | 용도 |
|
||||||
|
|---|---|---|
|
||||||
|
| `Badge` | `@shared/components/ui/badge` | 8 intent × 4 size, **className으로 색상 override 금지** |
|
||||||
|
| `Button` | `@shared/components/ui/button` | 5 variant × 3 size (primary/secondary/ghost/outline/destructive) |
|
||||||
|
| `Input` / `Select` / `Textarea` / `Checkbox` / `Radio` | `@shared/components/ui/` | 폼 요소 (Select는 aria-label 타입 강제) |
|
||||||
|
| `TabBar` / `TabButton` | `@shared/components/ui/tabs` | underline / pill / segmented 3 variant |
|
||||||
|
| `Card` / `CardHeader` / `CardTitle` / `CardContent` | `@shared/components/ui/card` | 4 variant |
|
||||||
|
| `PageContainer` | `@shared/components/layout` | 페이지 루트 (size sm/md/lg + fullBleed) |
|
||||||
|
| `PageHeader` | `@shared/components/layout` | 페이지 헤더 (icon + title + description + demo + actions) |
|
||||||
|
| `Section` | `@shared/components/layout` | Card + CardHeader + CardTitle + CardContent 단축 |
|
||||||
|
|
||||||
|
### 카탈로그 기반 라벨/색상
|
||||||
|
|
||||||
|
분류 데이터는 `frontend/src/shared/constants/`의 19+ 카탈로그를 참조한다.
|
||||||
|
중앙 레지스트리는 `catalogRegistry.ts`이며, 쇼케이스가 자동 열거한다.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Badge } from '@shared/components/ui/badge';
|
||||||
|
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
|
||||||
|
|
||||||
|
<Badge intent={getAlertLevelIntent(event.level)} size="sm">
|
||||||
|
{getAlertLevelLabel(event.level, t, lang)}
|
||||||
|
</Badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
ad-hoc 한글/영문 상태 문자열은 `getStatusIntent()` (statusIntent.ts) 사용.
|
||||||
|
숫자 위험도는 `getRiskIntent(0~100)` 사용.
|
||||||
|
|
||||||
|
### CSS 작성 규칙
|
||||||
|
|
||||||
|
1. **인라인 색상 금지** — `style={{ backgroundColor: '#ef4444' }}` 같은 정적 색상은 작성 금지
|
||||||
|
- 예외: 동적 데이터 기반 (`backgroundColor: meta.hex`, progress width `${value}%`)
|
||||||
|
2. **하드코딩 Tailwind 색상 금지** — `bg-red-500/20 text-red-400` 같은 직접 작성 금지
|
||||||
|
- 반드시 Badge intent 또는 카탈로그 API 호출
|
||||||
|
3. **className override 정책**
|
||||||
|
- ✅ 레이아웃/위치 보정: `<Badge intent="info" className="w-full justify-center">`
|
||||||
|
- ❌ 색상/글자 크기 override: `<Badge intent="info" className="bg-red-500 text-xs">`
|
||||||
|
4. **시맨틱 토큰 우선** — `theme.css @layer utilities`의 토큰 사용
|
||||||
|
- `text-heading` / `text-label` / `text-hint` / `text-on-vivid` / `text-on-bright`
|
||||||
|
- `bg-surface-raised` / `bg-surface-overlay` / `bg-card` / `bg-background`
|
||||||
|
5. **!important 절대 금지** — `cn()` + `tailwind-merge`로 충돌 해결
|
||||||
|
6. **`-webkit-` 벤더 prefix** — 수동 작성 CSS는 `backdrop-filter` 등 prefix 직접 추가 (Tailwind는 자동)
|
||||||
|
|
||||||
|
### 페이지 작성 표준 템플릿
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { PageContainer, PageHeader, Section } from '@shared/components/layout';
|
||||||
|
import { Button } from '@shared/components/ui/button';
|
||||||
|
import { Badge } from '@shared/components/ui/badge';
|
||||||
|
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
|
||||||
|
import { Shield, Plus } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export function MyPage() {
|
||||||
|
const { t, i18n } = useTranslation('common');
|
||||||
|
const lang = i18n.language as 'ko' | 'en';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<PageHeader
|
||||||
|
icon={Shield}
|
||||||
|
iconColor="text-blue-400"
|
||||||
|
title="페이지 제목"
|
||||||
|
description="페이지 설명"
|
||||||
|
actions={
|
||||||
|
<Button variant="primary" icon={<Plus className="w-4 h-4" />}>
|
||||||
|
추가
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Section title="데이터 목록">
|
||||||
|
<Badge intent={getAlertLevelIntent('HIGH')} size="sm">
|
||||||
|
{getAlertLevelLabel('HIGH', t, lang)}
|
||||||
|
</Badge>
|
||||||
|
</Section>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 접근성 (a11y) 필수
|
||||||
|
|
||||||
|
- **`<button>`**: `type="button"` 명시 + 아이콘 전용은 `aria-label` 필수
|
||||||
|
- **`<input>` / `<textarea>` / `<select>`**: `aria-label` 또는 `<label htmlFor>` 필수
|
||||||
|
- **`Select` 컴포넌트**: TypeScript union type으로 `aria-label`/`aria-labelledby`/`title` 중 하나 컴파일 타임 강제
|
||||||
|
- 위반 시 WCAG 2.1 Level A 위반 + axe DevTools 경고
|
||||||
|
|
||||||
|
### 변경 사이클
|
||||||
|
|
||||||
|
1. 디자인 변경이 필요하면 → **쇼케이스에서 먼저 미세조정** → 시각 검증
|
||||||
|
2. 카탈로그 라벨/색상 변경 → `shared/constants/*` 또는 `variantMeta.ts`만 수정
|
||||||
|
3. 컴포넌트 변형 추가 → `lib/theme/variants.ts` CVA에만 추가
|
||||||
|
4. 실 페이지는 **컴포넌트만 사용**, 변경 시 자동 반영
|
||||||
|
|
||||||
|
### 금지 패턴 체크리스트
|
||||||
|
|
||||||
|
- ❌ `<Badge className="bg-red-500/20 text-red-400">` → `<Badge intent="critical">`
|
||||||
|
- ❌ `<button className="bg-blue-600 ...">` → `<Button variant="primary">`
|
||||||
|
- ❌ `<input className="bg-surface ...">` → `<Input>`
|
||||||
|
- ❌ `<div className="p-5 space-y-4">` 페이지 루트 → `<PageContainer>`
|
||||||
|
- ❌ `-m-4` negative margin 해킹 → `<PageContainer fullBleed>`
|
||||||
|
- ❌ `style={{ color: '#ef4444' }}` 정적 색상 → 시맨틱 토큰 또는 카탈로그
|
||||||
|
- ❌ `!important` → `cn()` 활용
|
||||||
|
- ❌ 페이지 내 `const STATUS_COLORS = {...}` 로컬 재정의 → shared/constants 카탈로그
|
||||||
|
|
||||||
|
## System Flow 뷰어 (개발 단계용)
|
||||||
|
|
||||||
|
- **URL**: https://kcg-ai-monitoring.gc-si.dev/system-flow.html (메인 SPA와 별개)
|
||||||
|
- **소스**: `frontend/system-flow.html` + `frontend/src/systemFlowMain.tsx` + `frontend/src/flow/`
|
||||||
|
- **매니페스트**: `frontend/src/flow/manifest/` (10개 카테고리 JSON + meta.json + edges.json)
|
||||||
|
- **노드 ID 명명**: `<category>.<snake_case>` (예: `output.event_generator`, `ui.parent_review`)
|
||||||
|
- **딥링크**: `/system-flow.html#node=<node_id>` — 산출문서에서 노드 직접 참조
|
||||||
|
- **가이드**: `docs/system-flow-guide.md` 참조
|
||||||
|
|
||||||
|
### `/version` 스킬 사후 처리 (필수)
|
||||||
|
|
||||||
|
`/version` 스킬을 실행하여 새 SemVer 버전이 결정되면, Codex는 이어서 다음 작업을 **자동으로** 수행한다 (`/version` 스킬 자체는 팀 공통 파일이라 직접 수정하지 않음):
|
||||||
|
|
||||||
|
1. **manifest 동기화**: `/version`이 결정한 새 버전을 `frontend/src/flow/manifest/meta.json`에 반영
|
||||||
|
- `version`: 새 SemVer (예: `"1.2.0"`)
|
||||||
|
- `updatedAt`: 현재 ISO datetime (`new Date().toISOString()`)
|
||||||
|
- `releaseDate`: 오늘 날짜 (`YYYY-MM-DD`)
|
||||||
|
2. **같은 커밋에 포함**: `frontend/src/flow/manifest/meta.json`을 `/version` 스킬이 만든 커밋에 amend하거나, `docs: VERSION-HISTORY 갱신 + system-flow manifest 동기화`로 통합 커밋
|
||||||
|
3. **서버 archive는 CI/CD가 자동 처리**: 별도 작업 불필요. main 머지 후 Gitea Actions가 빌드 + dist 배포 + `/deploy/kcg-ai-monitoring-archive/system-flow/v{version}_{date}/`에 스냅샷 영구 보존
|
||||||
|
|
||||||
|
### 노드 ID 안정성
|
||||||
|
|
||||||
|
- **노드 ID는 절대 변경 금지** (산출문서가 참조하므로 깨짐)
|
||||||
|
- 노드 제거 시 `status: 'deprecated'`로 마킹 (1~2 릴리즈 유지 후 삭제)
|
||||||
|
- 새 노드 추가 시 `status: 'implemented'` 또는 `'planned'`
|
||||||
@ -51,6 +51,7 @@ src/
|
|||||||
| [docs/page-workflow.md](docs/page-workflow.md) | 31개 페이지 역할 + 4개 업무 파이프라인 |
|
| [docs/page-workflow.md](docs/page-workflow.md) | 31개 페이지 역할 + 4개 업무 파이프라인 |
|
||||||
| [docs/data-sharing-analysis.md](docs/data-sharing-analysis.md) | 데이터 공유 분석 + mock 통합 결과 |
|
| [docs/data-sharing-analysis.md](docs/data-sharing-analysis.md) | 데이터 공유 분석 + mock 통합 결과 |
|
||||||
| [docs/next-refactoring.md](docs/next-refactoring.md) | 다음 단계 TODO (API 연동, 실시간, 코드 스플리팅) |
|
| [docs/next-refactoring.md](docs/next-refactoring.md) | 다음 단계 TODO (API 연동, 실시간, 코드 스플리팅) |
|
||||||
|
| [docs/prediction-analysis.md](docs/prediction-analysis.md) | Prediction 모듈 구조/방향 심층 분석 (2026-04-17, opus 4.7 독립 리뷰) |
|
||||||
|
|
||||||
## SFR 요구사항 대응 현황
|
## SFR 요구사항 대응 현황
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ src/
|
|||||||
| SFR-07 | 단일함정 순찰경로 | `/patrol-route` | UI 완료 |
|
| SFR-07 | 단일함정 순찰경로 | `/patrol-route` | UI 완료 |
|
||||||
| SFR-08 | 다함정 경로최적화 | `/fleet-optimization` | UI 완료 |
|
| SFR-08 | 다함정 경로최적화 | `/fleet-optimization` | UI 완료 |
|
||||||
| SFR-09 | Dark Vessel 탐지 | `/dark-vessel` | UI 완료 |
|
| SFR-09 | Dark Vessel 탐지 | `/dark-vessel` | UI 완료 |
|
||||||
| SFR-10 | 어구 탐지 | `/gear-detection` | UI 완료 |
|
| SFR-10 | 어구 탐지 | `/gear-detection`, `/gear-collision`(V030) | UI 완료 |
|
||||||
| SFR-11 | 단속·탐지 이력 | `/enforcement-history` | UI 완료 |
|
| SFR-11 | 단속·탐지 이력 | `/enforcement-history` | UI 완료 |
|
||||||
| SFR-12 | 모니터링 대시보드 | `/dashboard`, `/monitoring` | UI 완료 |
|
| SFR-12 | 모니터링 대시보드 | `/dashboard`, `/monitoring` | UI 완료 |
|
||||||
| SFR-13 | 통계·성과 분석 | `/statistics` | UI 완료 |
|
| SFR-13 | 통계·성과 분석 | `/statistics` | UI 완료 |
|
||||||
|
|||||||
@ -1,18 +1,88 @@
|
|||||||
# Backend (Spring Boot)
|
# Backend (Spring Boot)
|
||||||
|
|
||||||
Phase 2에서 초기화 예정.
|
운영 배포 중 — rocky-211 `:18080` (`kcg-ai-backend` systemd).
|
||||||
|
|
||||||
## 계획된 구성
|
## 구성
|
||||||
- Spring Boot 3.x + Java 21
|
|
||||||
- PostgreSQL + Flyway
|
- **Runtime**: Spring Boot 3.5.7 + Java 21
|
||||||
- Spring Security + JWT
|
- **DB**: PostgreSQL (kcgaidb) + Flyway V001~V030 (51 테이블)
|
||||||
- Caffeine 캐시
|
- **Auth**: Spring Security + JWT 쿠키 + BCrypt
|
||||||
- 트리 기반 RBAC 권한 체계 (wing 패턴)
|
- **Cache**: Caffeine (권한 트리 10분 TTL)
|
||||||
|
- **Permission**: 트리 기반 RBAC (47 리소스 × 5 operation)
|
||||||
|
- **HTTP client**: `RestClient` + 명시적 `@Bean` 주입 (`predictionRestClient`, `signalBatchRestClient`)
|
||||||
|
|
||||||
## 책임
|
## 책임
|
||||||
- 자체 인증/권한/감사로그
|
|
||||||
- 운영자 의사결정 (모선 확정/제외/학습)
|
|
||||||
- prediction 분석 결과 조회 API (`/api/analysis/*`)
|
|
||||||
- 관리자 화면 API
|
|
||||||
|
|
||||||
상세 설계: `.claude/plans/vast-tinkering-knuth.md`
|
- 자체 인증/권한/감사 로그 + 관리자 API
|
||||||
|
- 운영자 의사결정 (모선 확정·제외·학습, 어구 정체성 충돌 분류)
|
||||||
|
- prediction 분석 결과 조회 (`/api/analysis/*`) + 이벤트 허브 (`/api/events`, `/api/alerts`)
|
||||||
|
- prediction 실시간 상태 프록시 (`/api/prediction/*`)
|
||||||
|
|
||||||
|
## 빌드 · 배포
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 로컬 실행
|
||||||
|
./mvnw spring-boot:run
|
||||||
|
|
||||||
|
# 프로덕션 빌드
|
||||||
|
./mvnw clean package -DskipTests
|
||||||
|
# → target/kcg-ai-backend-*.jar
|
||||||
|
|
||||||
|
# 운영 배포 (수동)
|
||||||
|
scp target/kcg-ai-backend-*.jar rocky-211:/opt/kcg-ai-backend/
|
||||||
|
ssh rocky-211 "sudo systemctl restart kcg-ai-backend"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 필수 컴파일 설정 (PR #79 hotfix)
|
||||||
|
|
||||||
|
Spring 6.1 의 parameter-level `@Qualifier` 주입이 동작하려면 두 가지가 **필수**:
|
||||||
|
|
||||||
|
1. **`pom.xml`** — `maven-compiler-plugin` 의 `default-compile` 과 `default-testCompile` 양쪽 execution 에
|
||||||
|
`<parameters>true</parameters>` 설정. 파라미터 이름을 바이트코드에 보존해야 `@Qualifier` resolve 가 가능.
|
||||||
|
2. **`src/main/java/lombok.config`** — `lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier`
|
||||||
|
설정. Lombok `@RequiredArgsConstructor` 가 필드의 `@Qualifier` 를 생성자 파라미터에 복사해야 Spring 이 인식.
|
||||||
|
|
||||||
|
둘 중 하나라도 누락되면 `PredictionProxyController` 같은 다중 `RestClient` bean 주입 컨트롤러가 기동 시점에
|
||||||
|
`NoUniqueBeanDefinitionException` 으로 크래시 루프에 빠진다. 로컬에서 `./mvnw spring-boot:run` 실패는
|
||||||
|
운영 restart 시 동일하게 재현되므로 **MR 범위 밖이어도 우선 해결**.
|
||||||
|
|
||||||
|
## 주요 패키지 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
src/main/java/gc/mda/kcg/
|
||||||
|
├── config/ # RestClientConfig, SecurityConfig, OpenApi, CorsConfig, CaffeineConfig 등
|
||||||
|
├── auth/ # LoginController, JWT, 비밀번호 정책
|
||||||
|
├── permission/ # 트리 RBAC, @RequirePermission AOP, 캐시
|
||||||
|
├── audit/ # @Auditable AOP, AuditLogService, AccessLogFilter
|
||||||
|
├── domain/
|
||||||
|
│ ├── analysis/ # VesselAnalysisController, GearCollisionController (V030), PredictionProxyController 등
|
||||||
|
│ ├── fleet/ # ParentInferenceWorkflowController, FleetService
|
||||||
|
│ ├── event/ # EventController, AlertController
|
||||||
|
│ ├── enforcement/ # EnforcementController, EnforcementPlanController
|
||||||
|
│ ├── master/ # MasterDataController (CodeMaster, GearTypeMaster, VesselPermit 등)
|
||||||
|
│ ├── admin/ # AdminLogController, AdminStatsController, UserManagementController
|
||||||
|
│ └── stats/ # StatsController (KPI 집계)
|
||||||
|
└── Application.java
|
||||||
|
```
|
||||||
|
|
||||||
|
모든 컨트롤러는 `controller → service → repository` 계층을 준수하며, 쓰기 액션은
|
||||||
|
`@RequirePermission` + `@Auditable` 로 권한·감사 일관 적용.
|
||||||
|
|
||||||
|
## Flyway 마이그레이션
|
||||||
|
|
||||||
|
- 경로: [src/main/resources/db/migration/V001__*.sql ~ V030__gear_identity_collision.sql](src/main/resources/db/migration/)
|
||||||
|
- 최신: **V030** (2026-04-17) — `gear_identity_collisions` 테이블 + `detection:gear-collision` 권한 트리
|
||||||
|
- Flyway 자동 적용: Spring Boot 기동 시점
|
||||||
|
|
||||||
|
## 디렉토리 밖 의존성
|
||||||
|
|
||||||
|
- **prediction → kcgaidb**: prediction 이 직접 write. backend 는 HTTP 호출 없이 DB 조회로만 연동
|
||||||
|
- **signal-batch**: `http://192.168.1.18:18090/signal-batch` (정적정보 보강, `signalBatchRestClient` 주입)
|
||||||
|
- **prediction (FastAPI)**: `http://redis-211:18092` (실시간 상태/채팅 프록시 전용)
|
||||||
|
|
||||||
|
## 운영 체크리스트
|
||||||
|
|
||||||
|
1. 빌드 성공 → local `./mvnw spring-boot:run` 기동 확인 → curl `/api/auth/me` 200 확인
|
||||||
|
2. scp 배포 → `systemctl restart kcg-ai-backend` → `journalctl -u kcg-ai-backend -n 100`
|
||||||
|
3. 응답 확인: `curl -k https://kcg-ai-monitoring.gc-si.dev/api/analysis/vessels?hours=1`
|
||||||
|
4. Flyway 에러 시: `backend.application` 로그에서 `Migration ... failed` 확인
|
||||||
|
|||||||
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### 문서
|
||||||
|
- **Prediction 모듈 심층 분석 리포트 신설** — `docs/prediction-analysis.md` (9개 섹션, 250 라인). opus 4.7 독립 리뷰 관점에서 현재 17 알고리즘의 레이어 분리·5분 사이클 시퀀스·4대 도메인 커버리지를 평가하고, 6축(관심사 분리/재사용성/테스트 가능성/에러 격리/동시성/설정 가능성)으로 구조 채점 + P1~P4 개선 제안·임계값 전수표 제공
|
||||||
|
- **루트·SFR 문서 drift 해소** — V001~V016 → V030 + 51 테이블, Python 3.9 → 3.11+, 14 → 17 알고리즘 모듈 실측 반영. SFR-10 에 GEAR_IDENTITY_COLLISION 패턴 + GearCollisionDetection 페이지 섹션 추가 (sfr-traceability/sfr-user-guide), `/gear-collision` 라우트 architecture.md 포함, system-flow-guide 노드 수 102→115 + V030 manifest 미반영 경고, backend/README "Phase 2 예정" 상태 → 실제 운영 구성 전면 재작성 (PR #79 hotfix 요구사항 명시)
|
||||||
|
|
||||||
## [2026-04-17.4]
|
## [2026-04-17.4]
|
||||||
|
|
||||||
### 수정
|
### 수정
|
||||||
|
|||||||
@ -318,7 +318,7 @@ deps 변경 → useMapLayers → RAF → overlay.setProps() (React 리렌
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 라우팅 구조 (26 보호 경로 + login)
|
## 라우팅 구조 (27 보호 경로 + login)
|
||||||
|
|
||||||
`App.tsx`에서 `BrowserRouter` > `AuthProvider` > `Routes`로 구성된다.
|
`App.tsx`에서 `BrowserRouter` > `AuthProvider` > `Routes`로 구성된다.
|
||||||
|
|
||||||
@ -331,6 +331,7 @@ deps 변경 → useMapLayers → RAF → overlay.setProps() (React 리렌
|
|||||||
- `/enforcement-plan` — 단속계획 (SFR-06)
|
- `/enforcement-plan` — 단속계획 (SFR-06)
|
||||||
- `/dark-vessel` — 무등화 선박 탐지 (SFR-09)
|
- `/dark-vessel` — 무등화 선박 탐지 (SFR-09)
|
||||||
- `/gear-detection` — 어구 탐지 (SFR-10)
|
- `/gear-detection` — 어구 탐지 (SFR-10)
|
||||||
|
- `/gear-collision` — 어구 정체성 충돌 (SFR-10, V030 — 동일 어구 이름 × 복수 MMSI 공존 감지)
|
||||||
- `/china-fishing` — 중국어선 탐지
|
- `/china-fishing` — 중국어선 탐지
|
||||||
- `/patrol-route` — 순찰경로 (SFR-07)
|
- `/patrol-route` — 순찰경로 (SFR-07)
|
||||||
- `/fleet-optimization` — 함대 최적화 (SFR-08)
|
- `/fleet-optimization` — 함대 최적화 (SFR-08)
|
||||||
|
|||||||
250
docs/prediction-analysis.md
Normal file
250
docs/prediction-analysis.md
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
# Prediction 모듈 심층 분석 — 구조·방향 리뷰
|
||||||
|
|
||||||
|
**대상:** `prediction/` (Python 3.11+, FastAPI, APScheduler, 59 `.py` 파일)
|
||||||
|
**작성일:** 2026-04-17
|
||||||
|
**작성 관점:** opus 4.7 독립 리뷰 — 정밀도 튜닝이 아닌 **방향성·코드 구조**
|
||||||
|
**전제:** 프로토타입·데모 단계. 정밀도 미흡은 인지된 상태.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR — 3줄 요약
|
||||||
|
|
||||||
|
1. **뼈대는 튼튼하다.** 레이어 분리(algorithms/pipeline/output/db/cache), 순수함수 위주 알고리즘, 카테고리별 dedup 윈도우 분리까지 프로토타입치고는 일관된 설계.
|
||||||
|
2. **약한 고리는 오케스트레이터.** [scheduler.py run_analysis_cycle()](../prediction/scheduler.py) 한 함수가 700+ 라인, 지역 try/except + `logger.warning` 로 흡수된 실패가 많아 "어디서 깨졌는지 조용히 묻힌다". 상태 누적(모듈 전역 `_transship_pair_history`)도 여기 묶여 있음.
|
||||||
|
3. **커버리지 매트릭스는 4/4 이지만 UI 비대칭.** prediction 이 생산하는 결과 중 `ILLEGAL_FISHING_PATTERN` 이벤트·환적 의심은 DB·백엔드까지 도달하지만 전용 detection UI 가 없다. prediction 품질 개선과 무관하게 운영자가 쓸 수 없는 상태.
|
||||||
|
|
||||||
|
**권고 최우선 3가지** — 신규 알고리즘 추가보다 아래가 선행:
|
||||||
|
- **P1:** 사이클 스테이지 단위 에러 경계(`_stage(...)` 유틸)로 교체해 실패 스테이지 명시 로깅 + 부분 실패 시에도 후속 단계 진행
|
||||||
|
- **P1:** 하드코딩 임계값(MID prefix, 커버리지 박스, SOG band, 11 pattern 점수) 을 `correlation_param_models` 패턴처럼 DB/config 로 외부화
|
||||||
|
- **P1:** 환적 전용 + ILLEGAL_FISHING_PATTERN 전용 프론트 페이지 추가 — 이미 DB·API 는 있음
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 아키텍처 레이어 — 책임과 결합도
|
||||||
|
|
||||||
|
```
|
||||||
|
prediction/
|
||||||
|
├── config.py Pydantic Settings + qualified_table() — SSOT 설정
|
||||||
|
├── scheduler.py APScheduler + run_analysis_cycle() (단일 엔트리)
|
||||||
|
├── main.py FastAPI app + /health /status /chat 등
|
||||||
|
├── fleet_tracker.py 상태 보유 (선단 레지스트리, 어구 정체성)
|
||||||
|
├── time_bucket.py 안전 지연 12분 + backfill 3 버킷
|
||||||
|
├── algorithms/ 17개 모듈 — 순수함수 중심
|
||||||
|
├── pipeline/ 8개 모듈 — 7단계 분류 파이프라인
|
||||||
|
├── output/ 5개 모듈 — event/violation/kpi/stats/alert
|
||||||
|
├── db/ 4개 모듈 — snpdb / kcgdb / signal_api / partition_manager
|
||||||
|
├── cache/ vessel_store.py — 24h sliding window 인메모리
|
||||||
|
├── chat/ Ollama + RAG 스텁
|
||||||
|
├── models/ result.py — AnalysisResult dataclass
|
||||||
|
├── data/ monitoring zones JSON 정적 설정
|
||||||
|
└── tests/ time_bucket / gear_parent_episode / gear_parent_inference 3종
|
||||||
|
```
|
||||||
|
|
||||||
|
| 레이어 | 책임 | 결합도 평가 |
|
||||||
|
|---|---|---|
|
||||||
|
| `config.py` | 환경 + SQL identifier 검증 | ✅ SSOT, `qualified_table()` 로 스키마 주입 공격 방지 |
|
||||||
|
| `algorithms/` | 탐지 로직 (순수) | ✅ 대부분 `df, params -> dict/tuple`. 상호 의존 적음 |
|
||||||
|
| `pipeline/` | 7단계 sequential | ✅ `orchestrator.ChineseFishingVesselPipeline.run()` 이 DF 를 그대로 파이프 |
|
||||||
|
| `output/` | 룰 엔진 + DB write | ✅ 룰을 lambda 리스트(event_generator.RULES)로 선언적 관리 |
|
||||||
|
| `db/` | Connection pool + SQL | ⚠️ `kcgdb.upsert_results(results)` 가 한 트랜잭션에 전부 묶임 (파티션 unique index 활용은 적절) |
|
||||||
|
| `cache/vessel_store.py` | 전역 싱글턴, 24h 궤적 인메모리 | ⚠️ 모듈 싱글턴 → 테스트 시 mock 어려움 |
|
||||||
|
| `fleet_tracker.py` | 레지스트리·어구 정체성 상태 | ⚠️ 싱글턴 + 모듈 전역 캐시 |
|
||||||
|
| `scheduler.py` | 전체 오케스트레이션 | ❌ **700+ 라인 모놀리식** — 아래 §2 상세 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 5분 사이클 시퀀스 — [scheduler.py:80-804](../prediction/scheduler.py#L80-L804)
|
||||||
|
|
||||||
|
사이클 전체가 **한 함수** 안에서 9단계로 진행된다.
|
||||||
|
|
||||||
|
| 단계 | 라인 | 역할 | 실패 처리 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1. 증분 로드 | [97-106](../prediction/scheduler.py#L97-L106) | `snpdb.fetch_incremental()` → vessel_store merge | 전체 try/except 포함 |
|
||||||
|
| 2. 정적 보강 | [108-112](../prediction/scheduler.py#L108-L112) | signal-batch API 호출 | 전체 try/except |
|
||||||
|
| 3. 대상 선별 | [114-128](../prediction/scheduler.py#L114-L128) | SOG/COG 계산 + 0건 시 조기 return | ✅ 조기 반환 |
|
||||||
|
| 4. 파이프라인 | [122-128](../prediction/scheduler.py#L122-L128) | `ChineseFishingVesselPipeline.run()` | 전체 try/except |
|
||||||
|
| 5. 선단 분석 | [131-177](../prediction/scheduler.py#L131-L177) | fleet_tracker + gear_identity 충돌 감지 | ⚠️ 내부 try/except 로 warning, 전진 |
|
||||||
|
| 5.5. 어구 그룹·상관·부모 추론 | [181-229](../prediction/scheduler.py#L181-L229) | polygon_builder + gear_correlation | ⚠️ 내부 try/except, 결과 없이 진행 |
|
||||||
|
| 5.9. 쌍끌이 후보·판정 | [231-303](../prediction/scheduler.py#L231-L303) | pair_trawl STRONG/PROBABLE/SUSPECT | ⚠️ 내부 try/except |
|
||||||
|
| 6. 개별 선박 분석 | [305-515](../prediction/scheduler.py#L305-L515) | AnalysisResult 생성 (파이프라인 통과자) | 루프 내 continue |
|
||||||
|
| 6.5. 경량 분석 | [523-682](../prediction/scheduler.py#L523-L682) | 파이프라인 미통과 중국 MID — `compute_lightweight_risk_score` | 루프 내 try/except |
|
||||||
|
| 7. 환적 의심 | [685-713](../prediction/scheduler.py#L685-L713) | `detect_transshipment` + `_transship_pair_history` 누적 | 전체 try/except |
|
||||||
|
| 8. DB upsert | [716-717](../prediction/scheduler.py#L716-L717) | `kcgdb.upsert_results()` + `cleanup_old(48h)` | 전체 try/except |
|
||||||
|
| 9. 출력 모듈 | [720-745](../prediction/scheduler.py#L720-L745) | violation_classifier → event_generator → kpi_writer → aggregate_hourly/daily → alert_dispatcher | ⚠️ **5개 단계를 한 try/except 로 묶음** → 어디서 실패했는지 단일 warning 으로 흡수 |
|
||||||
|
| 10. 채팅 컨텍스트 캐싱 | [748-791](../prediction/scheduler.py#L748-L791) | Redis | 전체 try/except |
|
||||||
|
|
||||||
|
### 구조적 관찰
|
||||||
|
|
||||||
|
- **전체 try/except 는 있다** ([97](../prediction/scheduler.py#L97) ~ [803](../prediction/scheduler.py#L803)) — 치명 실패가 다음 사이클을 막지는 않음
|
||||||
|
- **그러나 내부 스테이지가 너무 무겁다.** 9번째 출력 단계가 5개 모듈을 한 덩어리로 묶어 `logger.warning('output modules failed (non-fatal): %s', e)` 로 흡수. 어느 모듈이 깨졌는지 디버깅하려면 stacktrace 를 `logger.exception` 으로 바꿔야 함
|
||||||
|
- **Lazy import** 가 스테이지마다 반복 (`from output.event_generator import ...` 등). 시작 시간 단축에는 도움이지만 import 오류가 **첫 사이클 실행 시점에만 드러남** — 배포 후 5분 지연 발견 경험 다수
|
||||||
|
|
||||||
|
### 권고 (사이클 구조 재정비)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _stage(name: str, fn, *args, required=False, **kwargs):
|
||||||
|
t0 = time.time()
|
||||||
|
try:
|
||||||
|
result = fn(*args, **kwargs)
|
||||||
|
logger.info('stage %s ok in %.2fs', name, time.time() - t0)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception('stage %s failed: %s', name, e)
|
||||||
|
if required:
|
||||||
|
raise
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
- 각 스테이지를 `_stage('pair_detection', _run_pair_detection, ...)` 로 감싸면 실패 스테이지 명시 로깅 + stacktrace + 부분 실패 허용 정책을 일관화.
|
||||||
|
- 9번 단계의 5개 모듈은 각각 별도 `_stage(...)` 호출로 쪼갤 것.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 알고리즘 카탈로그 (17 모듈 × 담당 도메인)
|
||||||
|
|
||||||
|
| 파일 | 주 역할 | 입력 | 출력 | 주요 상수 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| [dark_vessel.py](../prediction/algorithms/dark_vessel.py) | AIS gap + 11 패턴 점수화 | 선박 DF(timestamp, lat, lon, sog, cog) | (score 0~100, patterns[], tier) | GAP_SUSPICIOUS=6000s, VIOLATION=86400s, KR_COVERAGE box |
|
||||||
|
| [spoofing.py](../prediction/algorithms/spoofing.py) | 텔레포트·극속·BD09 오프셋 | 선박 DF | spoofing_score 0~1 | EXTREME_SPEED=50kn (주석 기준), fishing max=25kn |
|
||||||
|
| [risk.py](../prediction/algorithms/risk.py) | 종합 risk + 경량 risk 2종 | DF, zone, is_permitted, 외부 점수 | (risk 0~100, level) | tier: 70+=CRITICAL, 50+=HIGH, 30+=MEDIUM |
|
||||||
|
| [fishing_pattern.py](../prediction/algorithms/fishing_pattern.py) | UCAF/UCFT gear SOG band | DF, gear | (ucaf, ucft) | PT 2.5~4.5, OT 2.0~4.0, GN 0.5~2.5 |
|
||||||
|
| [transshipment.py](../prediction/algorithms/transshipment.py) | 5단계 필터 파이프라인 | DF targets, pair_history, zone_fn | list of dict | PROXIMITY ~220m, RENDEZVOUS 90min, WATCH 제외 |
|
||||||
|
| [location.py](../prediction/algorithms/location.py) | zone 분류, haversine_nm, BD09 | (lat, lon) | zone, dist_nm | 12/24 NM 밴드 |
|
||||||
|
| [gear_correlation.py](../prediction/algorithms/gear_correlation.py) | 멀티모델 EMA + streak | vessel_store, gear_groups, conn | UPDATE gear_correlation_scores | α_base=0.30, polygon=0.70 |
|
||||||
|
| [gear_identity.py](../prediction/algorithms/gear_identity.py) | 공존 쌍 추출 (V030/PR #73) | gear_signals | collisions[] | CRITICAL_DIST=50km, COEXIST=3회 |
|
||||||
|
| [gear_parent_inference.py](../prediction/algorithms/gear_parent_inference.py) | 어구 → 모선 assignment | gear_groups + tracks | parent 후보 + confidence | 2-pass: direct-match → candidates |
|
||||||
|
| [gear_parent_episode.py](../prediction/algorithms/gear_parent_episode.py) | 에피소드 delineation (first_seen~last_seen) | gear_signals 시계열 | episodes[] | gap tolerance |
|
||||||
|
| [gear_violation.py](../prediction/algorithms/gear_violation.py) | G-01~G-06 통합 판정 (DAR-03) | DF + zone + pair_result + permits | g_codes[], evidence, score | G-06=20pts, G-02=18pts, G-01=15pts |
|
||||||
|
| [gear_name_rules.py](../prediction/algorithms/gear_name_rules.py) | 어구 이름 정규표현식 | string | parent_code (Optional) | regex set |
|
||||||
|
| [pair_trawl.py](../prediction/algorithms/pair_trawl.py) | 쌍끌이 tier 분류 | DF×2, 6h | (pair_detected, tier, pair_type) | PROXIMITY=0.27NM, MIN_SYNC=2h |
|
||||||
|
| [track_similarity.py](../prediction/algorithms/track_similarity.py) | DTW 궤적 유사도 | DF×2 | 0~1 | - |
|
||||||
|
| [fleet.py](../prediction/algorithms/fleet.py) | leader/follower/independent | DF, tracker | role | - |
|
||||||
|
| [polygon_builder.py](../prediction/algorithms/polygon_builder.py) | gear group convex hull | vessel_store, companies | 스냅샷[] | 시간버킷 |
|
||||||
|
| [vessel_type_mapping.py](../prediction/algorithms/vessel_type_mapping.py) | fishery_code → vessel_type 폴백 | fishery_code | 'TRAWL'/'PT'/... | - |
|
||||||
|
|
||||||
|
**관찰:**
|
||||||
|
- 대부분 **순수 함수** → 재사용·단위테스트 용이. 단 `gear_correlation.run_gear_correlation` 은 `conn` 을 받아 DB 를 직접 UPDATE 함 (알고리즘 + I/O 혼재)
|
||||||
|
- **상수가 모듈 상단에 모여 있는 것은 좋으나 config 외부화는 안 됨**. 현장 운영자가 임계값을 바꾸려면 코드 배포 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 4대 도메인 커버리지 매트릭스
|
||||||
|
|
||||||
|
| 도메인 | 담당 파일 | 접근 방식 | 구조적 강점 | 구조적 공백 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Dark Vessel** | `dark_vessel.py` + `risk.py` + `compute_dark_suspicion` | gap(100분 임계) + 11 패턴 점수 + 4-tier (CRITICAL≥70) | 패턴 분리(P1~P11) + 한국 커버리지 감점(−50pt)으로 자연 gap 구분 | MID/커버리지 박스 하드코딩. 감점 로직은 탐지 누락 위험(coverage box 바깥=자연 gap 가정) — 중국 EEZ 해역 근접 공해에서 의도적 OFF 가 감점 맞을 수 있음 |
|
||||||
|
| **Gear Tracking** | `gear_correlation` + `gear_identity` + `gear_parent_inference` + `polygon_builder` | 어구 이름 regex → 공존/교체 분기 + 멀티모델 EMA + 모선 추론 + 폴리곤 | 공존(GEAR_IDENTITY_COLLISION)을 1순위 증거로 재정의 (V030). SAVEPOINT 로 PK 충돌 격리 | 어구 신호는 **이름 regex 의존**. 이름이 GEAR_PATTERN 에 맞지 않으면 파이프라인 전체 진입 실패 |
|
||||||
|
| **Chinese Fishing** | `pipeline/` 7단계 + MID 412/413/414 필터 | preprocess → behavior → resample → feature → classify → cluster → seasonal + rule-based 분류 | 룰 + 군집으로 해석 가능 | MID prefix 하드코딩 2곳 중복. 전체 인구(55k)대비 500척만 파이프라인 통과 — 경량 분석(§6)으로 보완하나 여전히 <10% |
|
||||||
|
| **Illegal Pattern** | `gear_violation` (G-01~G-06) + `event_generator.RULES` (15+ 룰) + `transshipment` 5단계 | 임계값 기반 독립 룰 × 카테고리별 dedup 윈도우 | 룰이 lambda 리스트로 선언적 | **UI 미노출** — DB `prediction_events` 카테고리 `ILLEGAL_*` 는 생산되나 전용 detection UI 없음. 운영자는 EventList(`/event-list`) 에서만 조회. 환적도 동일 문제 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 코드 구조 평가 (6축)
|
||||||
|
|
||||||
|
| 축 | 평가 | 근거 |
|
||||||
|
|---|---|---|
|
||||||
|
| **관심사 분리** | B+ | algorithms / pipeline / output / db 레이어는 깔끔. 단 scheduler 는 오케스트레이터가 아니라 **메가 함수** |
|
||||||
|
| **재사용성** | A- | 17 알고리즘 모듈 중 ~15개가 순수함수. `run_gear_correlation` 만 conn 혼재 |
|
||||||
|
| **테스트 가능성** | C+ | unit test 3개만 (time_bucket / gear_parent_episode / gear_parent_inference). `vessel_store` / `fleet_tracker` 싱글턴 → integration test 어려움 |
|
||||||
|
| **에러 격리** | C | 사이클 전체 try/except + 내부 지역 try/except 혼재. 출력 5모듈이 한 덩어리 → 실패 지점 특정 불가 |
|
||||||
|
| **동시성** | A- | `ThreadedConnectionPool(1,5)`, `max_instances=1` 스케줄러 — 단일 프로세스 가정 하에서 안전 |
|
||||||
|
| **설정 가능성** | C- | 임계값 대부분 파일 상수. `correlation_param_models` 패턴만 DB 기반 (예외) |
|
||||||
|
|
||||||
|
### 주목할 잘된 점
|
||||||
|
- **Dedup 윈도우 카테고리별 차등** ([event_generator.py:26-39](../prediction/output/event_generator.py#L26-L39)) — 5분 boundary 집단 만료를 피하기 위해 33/67/89/127/131/181/367분 등 **의도적으로 5의 배수 회피**. 룰 기반 탐지의 대표적인 "튜닝 knob" 이 코드에 명시.
|
||||||
|
- **gear_identity 공존/교체 분기** ([fleet_tracker.py](../prediction/fleet_tracker.py) 트랜잭션 설계) — SAVEPOINT 로 부분 실패를 사이클 전체 abort 와 분리. 이전 13h 공백 사고의 재발 방지 설계가 구조에 반영됨.
|
||||||
|
- **Lightweight path** — 파이프라인 통과 못한 중국 MID 를 경량 경로로 계속 커버 ([scheduler.py:523-682](../prediction/scheduler.py#L523-L682)). "정밀 vs 커버리지" 를 두 경로로 나누는 의사결정 자체는 탁월.
|
||||||
|
|
||||||
|
### 구조적 채무
|
||||||
|
- **환경 분기 부재**: `config.py` 에 dev/prod 분기가 없음. `.env` 파일 하나에 의존. 로컬 실행 시 운영 DB 를 건드리는 위험 ([config.py:8-22](../prediction/config.py#L8-L22))
|
||||||
|
- **상태 있는 모듈 전역 변수**: `_transship_pair_history`, `_last_run`, `_scheduler` ([scheduler.py:16-26](../prediction/scheduler.py#L16-L26)). 테스트 격리 어렵고, 재시작 시 pair 누적 상태 증발
|
||||||
|
- **DB 쓰기 산재**: `kcgdb.upsert_results` / `save_group_snapshots` / `gear_correlation UPDATE` / `gear_identity UPSERT` / `prediction_events INSERT` 가 서로 다른 트랜잭션. 한 사이클 원자성 X — 의도적일 수 있으나 명시 설계 문서 없음
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 방향성 진단 — 프로토타입 → MVP → 운영
|
||||||
|
|
||||||
|
### 지금 강점
|
||||||
|
- **룰 기반 탐지를 탄탄히 다져둔 토대** — 임계값이 드러나 있고 dedup 설계가 명시적. 향후 ML overlay 를 얹을 때 "어디에 얹을지" 가 명확 (dark_suspicion score, transship score, gear_violation score 가 모두 연속값으로 산출)
|
||||||
|
- **운영자 의사결정 통합 설계** — V030 GEAR_IDENTITY_COLLISION 에서 status(OPEN/REVIEWED/CONFIRMED_ILLEGAL/FALSE_POSITIVE) 가 severity 재계산을 억제하는 패턴 — 사람 loop back 이 설계된 유일한 자리. 다른 도메인에도 이 패턴 확장 가능
|
||||||
|
|
||||||
|
### 지금 약점
|
||||||
|
- **ML 부재** — sklearn/torch 없음. 2026 기준 프로토타입으로는 적절하나, sequence anomaly (dark gap 의 시계열 반복 패턴) 나 behavioral classifier 는 룰만으론 한계
|
||||||
|
- **하드코딩 지대**: MID prefix(4개소), KR coverage box, SOG band, 11 패턴 점수, 5단계 transship 임계 — 모두 "이 프로젝트에서 튜닝해야 할 핫스팟" 인데 DB/config 분리 안 됨
|
||||||
|
- **UI 비대칭**: `prediction_events.category='ILLEGAL_FISHING_PATTERN'` 이 생산되지만 전용 UI 없음. 환적도 동일. 결과적으로 **prediction 이 만드는 가치의 일부가 운영자에게 도달하지 못함**
|
||||||
|
- **테스트 빈곤**: 17 알고리즘 중 3개만 유닛테스트. 사이클 단위 integration test 전혀 없음 — 사이클 회귀가 항상 운영 로그로만 드러남 (13h 공백 사고가 대표 사례)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 구조적 개선 제안 (우선순위별)
|
||||||
|
|
||||||
|
### P1 — 지금 해야 할 것 (운영 안정성)
|
||||||
|
|
||||||
|
1. **사이클 스테이지 단위 에러 경계** — `_stage(name, fn, required=False)` 유틸로 9번 출력 5모듈을 쪼갤 것. `logger.exception` 으로 stacktrace 보존. `required=True` 를 `fetch_incremental` 같은 fatal 에만 적용 → 실패 시 조기 반환
|
||||||
|
2. **임계값 외부화** — `correlation_param_models` 패턴을 확장해 `detection_params` 테이블 신설 (algo_name, param_key, value, active_from, active_to). 배포 없이 해상도 튜닝 가능. 운영자 권한으로 접근 시 감사 로그
|
||||||
|
3. **ILLEGAL_FISHING_PATTERN 전용 페이지** + **환적 전용 페이지** — 백엔드 API·DB 는 이미 존재. 프론트만 GearCollisionDetection 패턴으로 추가 (`PageContainer` + `DataTable` + `Badge intent`)
|
||||||
|
4. **사이클 부분 원자성 명시** — DB 쓰기 경계 문서화 (어디까지가 한 트랜잭션인지). 최소한 [architecture.md](architecture.md) 또는 신설 `docs/prediction-transactions.md` 에 다이어그램
|
||||||
|
|
||||||
|
### P2 — 다음 (품질 확보)
|
||||||
|
|
||||||
|
5. **알고리즘 유닛테스트 커버리지** — 17 모듈 중 최소 10 개 (dark_vessel 11 패턴 / transshipment 5단계 / gear_violation 6 G-code / spoofing / risk) 에 fixture 기반 테스트. `tests/fixtures/` 에 AIS DF CSV 샘플
|
||||||
|
6. **DB fixture integration test** — testcontainers-python 으로 PostgreSQL 띄워 한 사이클 실행 + 결과 테이블 assert. CI 에서 돌릴 수 있도록 데이터 10 척 x 1h 정도 경량
|
||||||
|
7. **`vessel_store` / `fleet_tracker` 의존성 주입 개편** — 모듈 싱글턴 → `AnalysisContext` dataclass 로 명시 주입. 테스트 mock 가능
|
||||||
|
8. **MID prefix·커버리지 box 를 `monitoring_zones` JSON 연장** — 이미 `data/monitoring_zones.json` 이 있음. 동일 포맷으로 `mid_prefixes.json` / `kr_coverage.json` 추가
|
||||||
|
|
||||||
|
### P3 — 중기 (가치 확장)
|
||||||
|
|
||||||
|
9. **ML overlay 타겟 설정** — dark_suspicion score (11 패턴 합산) 은 classifier training target 으로 최적. GCN/Transformer 로 **"gap 시퀀스가 의도적인가"** 를 학습. 룰 유지 + 게이트만 ML 로 대체 (shadow mode 로 비교)
|
||||||
|
10. **correlation 파라미터 MLOps 연동** — `correlation_param_models` 를 MLflow 로 실험 기록 → 성능 좋은 모델 자동 active 전환
|
||||||
|
11. **AIS 벤치마크 데이터셋** — 한중어업협정 906척 중 과거 단속 이력 있는 선박을 positive label. 현재 매칭률 53%+ 이므로 샘플 확보 가능. tier 별 precision/recall 산출
|
||||||
|
|
||||||
|
### P4 — 장기 (스케일)
|
||||||
|
|
||||||
|
12. **multi-process / async** — APScheduler + 단일 스레드 한계. 현 55k 선박 / 2.3M points / 110초 사이클에서 8k 중국 증가 + 한국 확장 시 5분 주기 내 완료 불가 예측. asyncio + ray / dask 로 스테이지 병렬
|
||||||
|
13. **Event bus 분리** — 지금은 `prediction_events` INSERT 가 동기. outbox 패턴으로 비동기 분리 시 백엔드/프론트 실시간 push 기반 (WebSocket) 로 진화 가능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 부록 — 임계값 전수표 (외부화 우선순위)
|
||||||
|
|
||||||
|
| 위치 | 상수 | 현재값 | P1 외부화 | 비고 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| [scheduler.py:28](../prediction/scheduler.py#L28) | `_KR_DOMESTIC_PREFIXES` | `('440','441')` | ✅ | 한국 MID |
|
||||||
|
| [scheduler.py:140,247](../prediction/scheduler.py#L140) | 중국 MID prefix | `'412' '413' '414'` 하드코딩 2곳 | ✅ | `mid_prefixes.json` |
|
||||||
|
| [scheduler.py:256](../prediction/scheduler.py#L256) | pair pool SOG band | `1.5 <= mean_sog <= 5.0` | ✅ | 조업 속력대 |
|
||||||
|
| [dark_vessel.py:6-8](../prediction/algorithms/dark_vessel.py#L6-L8) | GAP 임계 3종 | 6000/10800/86400s | ✅ | 100분/3h/24h |
|
||||||
|
| [dark_vessel.py:11-12](../prediction/algorithms/dark_vessel.py#L11-L12) | `_KR_COVERAGE_LAT/LON` | 32.0~39.5 / 124.0~132.0 | ✅ | AIS 수신 박스 |
|
||||||
|
| [dark_vessel.py:257-359](../prediction/algorithms/dark_vessel.py#L257-L359) | 11 패턴 점수 P1~P11 | 10~30 pt 분산 | ⚠️ | 탐지 정책 튜닝 대상 |
|
||||||
|
| [event_generator.py:26-39](../prediction/output/event_generator.py#L26-L39) | DEDUP_WINDOWS 12 카테고리 | 33~367분 | ⚠️ | 이미 의도적 |
|
||||||
|
| [config.py:25-37](../prediction/config.py#L25-L37) | 파이프라인 주기 등 | 5/24/60/30/12/3 | ✅ env | `.env` 로 이미 가능 |
|
||||||
|
| [transshipment.py](../prediction/algorithms/transshipment.py) | PROXIMITY / RENDEZVOUS | 0.002deg / 90min | ✅ | 환적 민감도 |
|
||||||
|
| [pair_trawl.py](../prediction/algorithms/pair_trawl.py) | PROXIMITY / SOG_Δ / COG_Δ / MIN_SYNC | 0.27NM / 0.5kn / 10° / 2h | ⚠️ | tier 재분류 기준 |
|
||||||
|
|
||||||
|
**P1:** 배포 없이 튜닝 가능해야 할 것
|
||||||
|
**⚠️:** 튜닝 자체가 탐지 정책 변경 → 릴리즈 노트 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 관련 파일 인덱스
|
||||||
|
|
||||||
|
- 오케스트레이터: [scheduler.py](../prediction/scheduler.py)
|
||||||
|
- 설정: [config.py](../prediction/config.py)
|
||||||
|
- 상태 컴포넌트: [fleet_tracker.py](../prediction/fleet_tracker.py), [cache/vessel_store.py](../prediction/cache/vessel_store.py)
|
||||||
|
- 알고리즘: [algorithms/](../prediction/algorithms/)
|
||||||
|
- 파이프라인: [pipeline/orchestrator.py](../prediction/pipeline/orchestrator.py)
|
||||||
|
- 출력: [output/event_generator.py](../prediction/output/event_generator.py), [output/violation_classifier.py](../prediction/output/violation_classifier.py), [output/kpi_writer.py](../prediction/output/kpi_writer.py), [output/stats_aggregator.py](../prediction/output/stats_aggregator.py), [output/alert_dispatcher.py](../prediction/output/alert_dispatcher.py)
|
||||||
|
- DB: [db/kcgdb.py](../prediction/db/kcgdb.py), [db/snpdb.py](../prediction/db/snpdb.py), [db/partition_manager.py](../prediction/db/partition_manager.py)
|
||||||
|
|
||||||
|
연관 운영 문서:
|
||||||
|
- [architecture.md](architecture.md) — 프론트엔드 아키텍처
|
||||||
|
- [sfr-traceability.md](sfr-traceability.md) — SFR 매트릭스
|
||||||
|
- [system-flow-guide.md](system-flow-guide.md) — system-flow 노드 명세
|
||||||
|
- `backend/README.md` — 백엔드 구성 (V030 + PR #79 hotfix 요구사항 명시)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 변경 이력
|
||||||
|
|
||||||
|
| 일자 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 2026-04-17 | 초판 — opus 4.7 독립 리뷰. 구조/방향 중심 + 우선순위별 개선 제안 |
|
||||||
@ -13,9 +13,9 @@
|
|||||||
| 레이어 | 기술 | 상태 |
|
| 레이어 | 기술 | 상태 |
|
||||||
|-------|------|------|
|
|-------|------|------|
|
||||||
| 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) |
|
| 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) |
|
| Backend | Spring Boot 3.5.7 + Java 21 + PostgreSQL 14.19 + Flyway V001~V030 + Spring Security + JWT + Caffeine + 트리 RBAC | 운영 배포 (rocky-211 :18080) |
|
||||||
| Prediction | Python 3.11+ + FastAPI + APScheduler, 14 알고리즘, 7단계 분류 파이프라인 | 운영 배포 (redis-211 :18092, 5분 주기) |
|
| Prediction | Python 3.11+ + FastAPI + APScheduler, 17 알고리즘 모듈 + 7단계 분류 파이프라인 + 5 출력/룰 모듈 | 운영 배포 (redis-211 :18092, 5분 주기) |
|
||||||
| Database | PostgreSQL `kcgaidb` / 48 테이블 / schema `kcg` + snpdb(AIS 원천) | 운영 |
|
| Database | PostgreSQL `kcgaidb` / 51 테이블 / schema `kcg` + snpdb(AIS 원천) | 운영 |
|
||||||
| Design System | `/design-system.html` 쇼케이스 SSOT + `shared/constants/` 25개 카탈로그 + `shared/components/ui/` 9개 공통 컴포넌트 | SSOT 전영역 준수 (2026-04-17 PR #C 완료) |
|
| 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) |
|
| i18n | 10 네임스페이스 × ko/en, `common.json` 에 aria/error/dialog/message 54키 추가 | alert/confirm/aria-label 하드코딩 제거 완료 (2026-04-17 PR #B) |
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ Frontend ← Backend /api/analysis/* + /api/events + /api/alerts + ... (65+ API)
|
|||||||
| SFR-07 | AI 경비함정 단일 함정 순찰·경로 | PatrolRoute | 🔲 Mock | - |
|
| SFR-07 | AI 경비함정 단일 함정 순찰·경로 | PatrolRoute | 🔲 Mock | - |
|
||||||
| SFR-08 | AI 경비함정 다함정 협력형 경로 | FleetOptimization | 🔲 Mock | - |
|
| SFR-08 | AI 경비함정 다함정 협력형 경로 | FleetOptimization | 🔲 Mock | - |
|
||||||
| SFR-09 | 불법 어선 패턴 탐지 (Dark Vessel) | DarkVesselDetection, TransferDetection | ✅ /api/analysis/* | ✅ Dark 11패턴 + Transship 5단계 |
|
| 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-10 | 불법 어망·어구 탐지 및 관리 | GearDetection, GearIdentification, GearCollisionDetection(V030) | ✅ /api/vessel-analysis/groups + /api/analysis/gear-detections + /api/analysis/gear-collisions | ✅ DAR-03 G-01~G-06 + pair tier + GEAR_IDENTITY_COLLISION(PR #73) |
|
||||||
| SFR-11 | 단속·탐지 이력 관리 | EnforcementHistory, EventList | ✅ /api/events + /api/enforcement/records | ✅ prediction_events |
|
| 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-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-13 | 통계·지표·성과 분석 | Statistics | ✅ /api/stats (daily/monthly/hourly) | ✅ prediction_stats_* |
|
||||||
@ -257,14 +257,16 @@ Frontend ← Backend /api/analysis/* + /api/events + /api/alerts + ... (65+ API)
|
|||||||
- G-06 쌍끌이 공조 — **tier 분류**: STRONG / PROBABLE / SUSPECT
|
- G-06 쌍끌이 공조 — **tier 분류**: STRONG / PROBABLE / SUSPECT
|
||||||
- 페어 탐색 `find_pair_candidates` (bbox + 궤적 유사도 2차)
|
- 페어 탐색 `find_pair_candidates` (bbox + 궤적 유사도 2차)
|
||||||
- 한중어업협정 906척 NAME_EXACT + NAME_FUZZY 매칭 53%+
|
- 한중어업협정 906척 NAME_EXACT + NAME_FUZZY 매칭 53%+
|
||||||
|
- **GEAR_IDENTITY_COLLISION (V030/PR #73)** — 동일 어구 이름이 서로 다른 MMSI 로 동일 사이클 내 공존 감지 → `gear_identity_collisions` UPSERT(name, mmsi_lo, mmsi_hi), CRITICAL/HIGH/MEDIUM/LOW severity 분류. 이전 "교체(sequential)" 로 오해하던 케이스를 "어구 복제/스푸핑 증거" 로 재정의. SAVEPOINT + try/except 로 `gear_correlation_scores_pkey` 충돌 격리
|
||||||
|
|
||||||
**백엔드 연동 ✅**:
|
**백엔드 연동 ✅**:
|
||||||
- `/api/vessel-analysis/groups` + `/groups/{key}/detail|correlations|candidates/{mmsi}/metrics|resolve` — 모선 워크플로우 (VesselAnalysisGroupService, 2026-04-17 PARENT_RESOLVE @Auditable 추가)
|
- `/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 중복 제거
|
- `/api/analysis/gear-detections` (2026-04-16.6 신설) — 자동 탐지 결과 MMSI 중복 제거
|
||||||
|
- `/api/analysis/gear-collisions` (V030, 4 엔드포인트) — GET list/stats/{id} + POST {id}/resolve (REVIEWED/CONFIRMED_ILLEGAL/FALSE_POSITIVE/REOPEN), `@Auditable GEAR_COLLISION_RESOLVE`
|
||||||
- 24h 궤적 리플레이 (gearReplayStore + useGearReplayLayers + TripsLayer)
|
- 24h 궤적 리플레이 (gearReplayStore + useGearReplayLayers + TripsLayer)
|
||||||
- GearDetailPanel: 후보 클릭 → 모선 확정/제외 UI
|
- GearDetailPanel: 후보 클릭 → 모선 확정/제외 UI
|
||||||
|
|
||||||
**마스터**: fishery_permit_cn (V029, 29 컬럼) + fleet_vessels 확장 (permit_year/fishery_code)
|
**마스터**: fishery_permit_cn (V029, 29 컬럼) + fleet_vessels 확장 (permit_year/fishery_code) + gear_identity_collisions (V030)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -412,7 +414,7 @@ Frontend ← Backend /api/analysis/* + /api/events + /api/alerts + ... (65+ API)
|
|||||||
| SFR-07 | `features/patrol/PatrolRoute.tsx` |
|
| SFR-07 | `features/patrol/PatrolRoute.tsx` |
|
||||||
| SFR-08 | `features/patrol/FleetOptimization.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-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-10 | `features/detection/GearDetection.tsx`, `GearIdentification.tsx`, `GearCollisionDetection.tsx`(V030), `features/detection/components/GearDetailPanel.tsx`, `GearReplayController.tsx`, `prediction/algorithms/pair_trawl.py`, `gear_violation.py`, `gear_identity.py`(V030), `gear_correlation.py`, `gear_parent_inference.py`, `vessel_type_mapping.py`, `backend/.../analysis/VesselAnalysisGroupService.java`, `GearCollisionController+Service.java`(V030) |
|
||||||
| SFR-11 | `features/enforcement/EnforcementHistory.tsx`, `EventList.tsx`, `backend/.../event/EventController+Service.java`, `AlertService.java`, `enforcement/EnforcementService.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-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-13 | `features/statistics/Statistics.tsx`, `ReportManagement.tsx`, `backend/.../stats/`, `admin/AdminStatsService.java` |
|
||||||
@ -483,6 +485,7 @@ Frontend ← Backend /api/analysis/* + /api/events + /api/alerts + ... (65+ API)
|
|||||||
| AdminLogController | (직접 Repository) | SFR-01 |
|
| AdminLogController | (직접 Repository) | SFR-01 |
|
||||||
| VesselAnalysisController | VesselAnalysisService | SFR-09, 12 |
|
| VesselAnalysisController | VesselAnalysisService | SFR-09, 12 |
|
||||||
| VesselAnalysisProxyController | VesselAnalysisGroupService (+`@Auditable PARENT_RESOLVE`) | SFR-10 |
|
| VesselAnalysisProxyController | VesselAnalysisGroupService (+`@Auditable PARENT_RESOLVE`) | SFR-10 |
|
||||||
|
| GearCollisionController (V030) | GearCollisionService (+`@Auditable GEAR_COLLISION_RESOLVE`) | SFR-10 |
|
||||||
| PredictionProxyController | RestClient `@Bean predictionRestClient` | SFR-03, 20 |
|
| PredictionProxyController | RestClient `@Bean predictionRestClient` | SFR-03, 20 |
|
||||||
| EventController | EventService | SFR-11, 12 |
|
| EventController | EventService | SFR-11, 12 |
|
||||||
| AlertController | AlertService | SFR-12, 17 |
|
| AlertController | AlertService | SFR-12, 17 |
|
||||||
|
|||||||
@ -450,6 +450,37 @@ AIS(선박자동식별장치) 신호를 의도적으로 끈 의심 선박(Dark V
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 어구 정체성 충돌 (V030, 2026-04-17 추가)
|
||||||
|
|
||||||
|
**메뉴 위치:** 탐지/분석 > 어구 정체성 충돌
|
||||||
|
**URL:** `/gear-collision`
|
||||||
|
**접근 권한:** ADMIN, OPERATOR, ANALYST, FIELD, VIEWER (READ) / ADMIN, OPERATOR (UPDATE)
|
||||||
|
|
||||||
|
**화면 설명:**
|
||||||
|
동일한 어구 이름이 서로 다른 MMSI 로 같은 5분 사이클에 동시 송출되는 공존 케이스를 탐지해
|
||||||
|
어구 복제/스푸핑 증거로 기록·분류하는 화면입니다. 이전에는 "MMSI 교체"로 오해해 덮어쓰는 바람에
|
||||||
|
`gear_correlation_scores_pkey` 제약 충돌로 prediction 사이클 전체가 실패하던 이슈(PR #73)를
|
||||||
|
"공존 패턴"으로 재정의하면서 신설되었습니다.
|
||||||
|
|
||||||
|
**주요 기능:**
|
||||||
|
- 충돌 쌍(name, MMSI 저/고) 목록 + 공존 누적 횟수 + 양 위치 최대 거리 + severity tier
|
||||||
|
- 운영자 분류 워크플로우: OPEN → REVIEWED / CONFIRMED_ILLEGAL / FALSE_POSITIVE (REOPEN 가능)
|
||||||
|
- severity 자동 산정: CRITICAL(거리≥50km OR 공존≥3회) / HIGH(거리≥10km OR 공존≥2회) / MEDIUM / LOW
|
||||||
|
- 상세 패널 — 양 MMSI 최근 궤적, 어구 이름 파싱(선박명+어구코드), 매칭된 모선(fleet_vessels)
|
||||||
|
|
||||||
|
**구현 완료 (V030 / PR #73, 2026-04-17):**
|
||||||
|
- ✅ `gear_identity_collisions` 테이블 + UNIQUE(name, mmsi_lo, mmsi_hi) 제약 + 5 인덱스
|
||||||
|
- ✅ prediction `algorithms/gear_identity.py` 순수함수 + `fleet_tracker.track_gear_identity()` 공존/교체 분기
|
||||||
|
- ✅ 백엔드 4 엔드포인트 `/api/analysis/gear-collisions` + `GEAR_COLLISION_RESOLVE` @Auditable
|
||||||
|
- ✅ 프론트 페이지 `GearCollisionDetection.tsx` (DataTable + KPI 5장 + SidePanel + 상태/심각도 필터)
|
||||||
|
- ✅ 이벤트 허브 연동 — HIGH/CRITICAL 은 `prediction_events` 에 자동 등록 (dedup 367분)
|
||||||
|
|
||||||
|
**향후 구현 예정:**
|
||||||
|
- 🔲 FALSE_POSITIVE 반복 쌍 자동 화이트리스트 (v2 학습 피드백 루프)
|
||||||
|
- 🔲 KPI 테이블 (`prediction_stats_*`) 에 severity 별 집계 반영
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## SFR-11: 단속/탐지 이력 관리
|
## SFR-11: 단속/탐지 이력 관리
|
||||||
|
|
||||||
**메뉴 위치:** 단속/이력 > 단속/탐지 이력
|
**메뉴 위치:** 단속/이력 > 단속/탐지 이력
|
||||||
|
|||||||
@ -6,10 +6,15 @@ KCG AI Monitoring 시스템 워크플로우 플로우차트 뷰어 사용법.
|
|||||||
|
|
||||||
`/system-flow.html`은 snpdb 5분 원천 궤적 수집부터 prediction 분석, 이벤트 생성, 운영자 의사결정까지 시스템 전체 데이터 흐름을 노드/엣지로 시각화한 **개발 단계 활용 페이지**입니다.
|
`/system-flow.html`은 snpdb 5분 원천 궤적 수집부터 prediction 분석, 이벤트 생성, 운영자 의사결정까지 시스템 전체 데이터 흐름을 노드/엣지로 시각화한 **개발 단계 활용 페이지**입니다.
|
||||||
|
|
||||||
- 102개 노드 + 133개 엣지 (v1.0.0 기준)
|
- 115개 노드 + 133개 엣지 (manifest 현재 상태, `meta.json` 은 아직 v1.0.0/2026-04-07 로 미갱신)
|
||||||
- 메인 SPA(`/`)와 완전 분리된 별도 React 앱
|
- 메인 SPA(`/`)와 완전 분리된 별도 React 앱
|
||||||
- 메뉴/링크 노출 없음 — 직접 URL 접근만
|
- 메뉴/링크 노출 없음 — 직접 URL 접근만
|
||||||
|
|
||||||
|
> ⚠️ **V030 미반영 경고**: 2026-04-17 V030 로 추가된 GEAR_IDENTITY_COLLISION 파이프라인 (
|
||||||
|
> `algo.gear_identity_collision`, `storage.gear_identity_collisions`, `api.gear_collisions_*`,
|
||||||
|
> `ui.gear_collision`, `decision.gear_collision_resolve`) 노드가 아직 manifest 에 등록되지
|
||||||
|
> 않았다. 다음 `/version` 릴리즈 시 매니페스트 동기화 필요.
|
||||||
|
|
||||||
## 접근 URL
|
## 접근 URL
|
||||||
|
|
||||||
- **운영**: https://kcg-ai-monitoring.gc-si.dev/system-flow.html
|
- **운영**: https://kcg-ai-monitoring.gc-si.dev/system-flow.html
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user