14 KiB
KCG AI Monitoring (모노레포)
해양경찰청 AI 기반 불법어선 탐지 및 단속 지원 플랫폼
🚨 절대 지침 (Absolute Rules)
아래 두 지침은 모든 작업에 우선 적용된다. 사용자가 명시적으로 해제하지 않는 한 우회 금지.
1. 신규 기능 설계·구현 착수 시: 원격 develop 동기화 필수
신규 기능/버그 수정/리팩터 등 어떤 작업이든 브랜치를 새로 만들기 전에는 아래 절차를 반드시 수행한다.
git fetch origin --prune
# origin/develop이 로컬 develop보다 앞서 있는지 확인
git log --oneline develop..origin/develop | head
- 로컬 develop이 뒤처진 경우 → 사용자에게 다음을 권유하고 동의를 받은 후 진행:
"
origin/develop이 로컬보다 N개 커밋 앞서 있습니다. 최신화 후 신규 브랜치를 생성하는 것을 권장합니다. 진행할까요?" 승인 시:git checkout develop && git pull --ff-only origin develop→ 그 위에서git checkout -b <new-branch> - 로컬 develop이 최신인 경우 → 그대로 develop에서 신규 브랜치 분기
- 로컬 develop이 없는 경우 →
git checkout -b develop origin/develop로 tracking branch 먼저 생성 - 로컬에 unstaged/uncommitted 변경이 있을 때 → 사용자에게 먼저 알리고 stash/commit 여부 확인 후 진행. 임의로 폐기 금지.
이유: 오래된 develop 위에서 작업하면 머지 충돌·리베이스 비용이 커지고, 이미 해결된 이슈를 중복 해결할 위험이 있다. 브랜치 분기 시점의 기반을 항상 최신으로 맞춘다.
적용 범위: /push, /mr, /create-mr, /release, /fix-issue 스킬 실행 시, 그리고 Claude가 자발적으로 새 브랜치를 만들 때 모두.
2. 프론트엔드 개발 시: design-system.html 쇼케이스 규칙 전면 준수
frontend/ 하위의 모든 페이지·컴포넌트·스타일 작성은 design-system.html(쇼케이스)에 정의된 컴포넌트·토큰·카탈로그만 사용한다. 이 문서 하단 "디자인 시스템 (필수 준수)" 섹션의 규칙을 예외 없이 따른다.
핵심 요약 (상세는 하단 섹션 참조):
- 공통 컴포넌트 우선 사용:
Badge,Button,Input,Select,TabBar,Card,PageContainer,PageHeader,Section - 라벨/색상은
shared/constants/카탈로그 API(getAlertLevelIntent등) 경유, ad-hoc 문자열 매핑 금지 - 인라인 색상·하드코딩 Tailwind 색상·
!important전면 금지 - 접근성:
<button type="button">, 아이콘 전용은aria-label, 폼 요소는aria-label/<label>필수
위반 시 리뷰 단계에서 반려 대상. 신규 페이지는 하단의 "페이지 작성 표준 템플릿" 을 시작점으로 사용한다.
모노레포 구조
kcg-ai-monitoring/
├── frontend/ # React 19 + TypeScript + Vite (UI)
├── backend/ # Spring Boot 3.x + Java 21 (인증/권한/감사 + 분석 API)
├── prediction/ # Python 3.9 + FastAPI (AIS 분석 엔진, 5분 주기)
├── database/ # PostgreSQL 마이그레이션 (Flyway V001~V016, 48 테이블)
│ └── migration/
├── deploy/ # 배포 가이드 + 서버 설정 문서
├── docs/ # 프로젝트 문서 (SFR, 아키텍처)
├── .gitea/ # Gitea Actions CI/CD (프론트 자동배포)
├── .claude/ # Claude Code 워크플로우
├── .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 를 통해서만 연동
명령어
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
- 14개 알고리즘 (어구 추론, 다크베셀, 스푸핑, 환적, 위험도 등)
- 7단계 분류 파이프라인 (전처리→행동→리샘플→특징→분류→클러스터→계절)
- AIS 원본: SNPDB (5분 증분), 결과: kcgaidb (직접 write)
- prediction과 backend는 DB만 공유 (HTTP 호출 X)
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 단위 개별 제어.
상세는 .claude/plans/vast-tinkering-knuth.md 참조.
팀 컨벤션
- 팀 규칙:
.claude/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이며, 쇼케이스가 자동 열거한다.
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 작성 규칙
- 인라인 색상 금지 —
style={{ backgroundColor: '#ef4444' }}같은 정적 색상은 작성 금지- 예외: 동적 데이터 기반 (
backgroundColor: meta.hex, progress width${value}%)
- 예외: 동적 데이터 기반 (
- 하드코딩 Tailwind 색상 금지 —
bg-red-500/20 text-red-400같은 직접 작성 금지- 반드시 Badge intent 또는 카탈로그 API 호출
- className override 정책
- ✅ 레이아웃/위치 보정:
<Badge intent="info" className="w-full justify-center"> - ❌ 색상/글자 크기 override:
<Badge intent="info" className="bg-red-500 text-xs">
- ✅ 레이아웃/위치 보정:
- 시맨틱 토큰 우선 —
theme.css @layer utilities의 토큰 사용text-heading/text-label/text-hint/text-on-vivid/text-on-brightbg-surface-raised/bg-surface-overlay/bg-card/bg-background
- !important 절대 금지 —
cn()+tailwind-merge로 충돌 해결 -webkit-벤더 prefix — 수동 작성 CSS는backdrop-filter등 prefix 직접 추가 (Tailwind는 자동)
페이지 작성 표준 템플릿
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 경고
변경 사이클
- 디자인 변경이 필요하면 → 쇼케이스에서 먼저 미세조정 → 시각 검증
- 카탈로그 라벨/색상 변경 →
shared/constants/*또는variantMeta.ts만 수정 - 컴포넌트 변형 추가 →
lib/theme/variants.tsCVA에만 추가 - 실 페이지는 컴포넌트만 사용, 변경 시 자동 반영
금지 패턴 체크리스트
- ❌
<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-4negative 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 버전이 결정되면, Claude는 이어서 다음 작업을 자동으로 수행한다 (/version 스킬 자체는 팀 공통 파일이라 직접 수정하지 않음):
- manifest 동기화:
/version이 결정한 새 버전을frontend/src/flow/manifest/meta.json에 반영version: 새 SemVer (예:"1.2.0")updatedAt: 현재 ISO datetime (new Date().toISOString())releaseDate: 오늘 날짜 (YYYY-MM-DD)
- 같은 커밋에 포함:
frontend/src/flow/manifest/meta.json을/version스킬이 만든 커밋에 amend하거나,docs: VERSION-HISTORY 갱신 + system-flow manifest 동기화로 통합 커밋 - 서버 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'