feat: S1 마스터 데이터 + prediction 기반 DB 스키마 (V008~V013)
prediction 모노레포 이관을 위한 DB 기반 구축: - V008: 계층형 code_master (12그룹 72코드, 위반유형/이벤트/단속/허가/함정 등) - V009: gear_type_master 어구 유형 6종 (분류 룰 + 합법성 기준) - V010: zone_polygon_master PostGIS 해역 폴리곤 (8개 주요 해역) - V011: vessel_permit_master + patrol_ship_master + fleet_companies 시드 - V012: vessel_analysis_results(파티션) + prediction_events 허브 + 알림 + 통계 + KPI - V013: enforcement_records/plans + patrol_assignments + ai_model 메타 - Hibernate Spatial 의존성 추가 (PostGIS 지원) - 프론트엔드 더미 데이터 기반 시드 (이벤트 15건, 단속 6건, 계획 5건, 월별통계 7개월) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
f545aeafac
커밋
883b347359
@ -110,6 +110,11 @@
|
|||||||
<artifactId>spring-security-test</artifactId>
|
<artifactId>spring-security-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- PostGIS / Hibernate Spatial -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.orm</groupId>
|
||||||
|
<artifactId>hibernate-spatial</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ spring:
|
|||||||
hibernate:
|
hibernate:
|
||||||
default_schema: kcg
|
default_schema: kcg
|
||||||
format_sql: true
|
format_sql: true
|
||||||
|
dialect: org.hibernate.spatial.dialect.postgis.PostgisPG10Dialect
|
||||||
jdbc:
|
jdbc:
|
||||||
time_zone: Asia/Seoul
|
time_zone: Asia/Seoul
|
||||||
open-in-view: false
|
open-in-view: false
|
||||||
|
|||||||
163
backend/src/main/resources/db/migration/V008__code_master.sql
Normal file
163
backend/src/main/resources/db/migration/V008__code_master.sql
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- V008: code_master (계층형 트리 코드 테이블 + 시드)
|
||||||
|
-- auth_perm_tree와 동일한 self-referencing 패턴
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE kcg.code_master (
|
||||||
|
code_id VARCHAR(100) PRIMARY KEY,
|
||||||
|
parent_id VARCHAR(100) REFERENCES kcg.code_master(code_id),
|
||||||
|
group_code VARCHAR(50) NOT NULL, -- 루트 그룹 (빠른 필터용 비정규화)
|
||||||
|
code VARCHAR(50) NOT NULL, -- 이 레벨의 코드값
|
||||||
|
depth INT NOT NULL DEFAULT 0,
|
||||||
|
name_ko VARCHAR(100) NOT NULL,
|
||||||
|
name_en VARCHAR(100),
|
||||||
|
sort_order INT DEFAULT 0,
|
||||||
|
color_hex VARCHAR(10),
|
||||||
|
icon VARCHAR(30),
|
||||||
|
metadata JSONB,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_code_group ON kcg.code_master(group_code, depth);
|
||||||
|
CREATE INDEX idx_code_parent ON kcg.code_master(parent_id);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 위반 유형 (VIOLATION_TYPE)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('VIOLATION_TYPE', NULL, 'VIOLATION_TYPE', 'VIOLATION_TYPE', 0, '위반 유형', 'Violation Type', 10, NULL),
|
||||||
|
('VIOLATION_TYPE:EEZ_VIOLATION', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'EEZ_VIOLATION', 1, 'EEZ 침범', 'EEZ Violation', 10, '#EF4444'),
|
||||||
|
('VIOLATION_TYPE:DARK_VESSEL', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'DARK_VESSEL', 1, '다크베셀', 'Dark Vessel', 20, '#7C3AED'),
|
||||||
|
('VIOLATION_TYPE:MMSI_TAMPERING', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'MMSI_TAMPERING', 1, 'MMSI 변조', 'MMSI Tampering', 30, '#F59E0B'),
|
||||||
|
('VIOLATION_TYPE:ILLEGAL_TRANSSHIP', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'ILLEGAL_TRANSSHIP',1,'불법환적', 'Illegal Transship', 40, '#EC4899'),
|
||||||
|
('VIOLATION_TYPE:ILLEGAL_GEAR', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'ILLEGAL_GEAR', 1, '어구 불법', 'Illegal Gear', 50, '#F97316'),
|
||||||
|
('VIOLATION_TYPE:ZONE_DEPARTURE', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'ZONE_DEPARTURE', 1, '조업구역 이탈', 'Zone Departure', 60, '#06B6D4'),
|
||||||
|
('VIOLATION_TYPE:RISK_BEHAVIOR', 'VIOLATION_TYPE', 'VIOLATION_TYPE', 'RISK_BEHAVIOR', 1, '위험 행동', 'Risk Behavior', 70, '#64748B');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 이벤트 레벨 (EVENT_LEVEL)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('EVENT_LEVEL', NULL, 'EVENT_LEVEL', 'EVENT_LEVEL', 0, '이벤트 심각도', 'Event Level', 20, NULL),
|
||||||
|
('EVENT_LEVEL:CRITICAL', 'EVENT_LEVEL', 'EVENT_LEVEL', 'CRITICAL', 1, '심각', 'Critical', 10, '#EF4444'),
|
||||||
|
('EVENT_LEVEL:HIGH', 'EVENT_LEVEL', 'EVENT_LEVEL', 'HIGH', 1, '높음', 'High', 20, '#F59E0B'),
|
||||||
|
('EVENT_LEVEL:MEDIUM', 'EVENT_LEVEL', 'EVENT_LEVEL', 'MEDIUM', 1, '보통', 'Medium', 30, '#3B82F6'),
|
||||||
|
('EVENT_LEVEL:LOW', 'EVENT_LEVEL', 'EVENT_LEVEL', 'LOW', 1, '낮음', 'Low', 40, '#6B7280');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 이벤트 상태 (EVENT_STATUS)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('EVENT_STATUS', NULL, 'EVENT_STATUS', 'EVENT_STATUS', 0, '이벤트 상태', 'Event Status', 30, NULL),
|
||||||
|
('EVENT_STATUS:NEW', 'EVENT_STATUS', 'EVENT_STATUS', 'NEW', 1, '신규', 'New', 10, '#EF4444'),
|
||||||
|
('EVENT_STATUS:ACK', 'EVENT_STATUS', 'EVENT_STATUS', 'ACK', 1, '확인', 'Acknowledged', 20, '#F59E0B'),
|
||||||
|
('EVENT_STATUS:IN_PROGRESS', 'EVENT_STATUS', 'EVENT_STATUS', 'IN_PROGRESS', 1, '처리중', 'In Progress', 30, '#3B82F6'),
|
||||||
|
('EVENT_STATUS:RESOLVED', 'EVENT_STATUS', 'EVENT_STATUS', 'RESOLVED', 1, '완료', 'Resolved', 40, '#22C55E'),
|
||||||
|
('EVENT_STATUS:FALSE_POSITIVE', 'EVENT_STATUS', 'EVENT_STATUS', 'FALSE_POSITIVE',1, '오탐', 'False Positive', 50, '#6B7280');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 이벤트 카테고리 (EVENT_CATEGORY) — 이벤트 유형 세분화
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('EVENT_CATEGORY', NULL, 'EVENT_CATEGORY', 'EVENT_CATEGORY', 0, '이벤트 유형', 'Event Category', 35, NULL),
|
||||||
|
('EVENT_CATEGORY:EEZ_INTRUSION', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'EEZ_INTRUSION', 1, 'EEZ 침범', 'EEZ Intrusion', 10, '#EF4444'),
|
||||||
|
('EVENT_CATEGORY:DARK_VESSEL', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'DARK_VESSEL', 1, '다크베셀', 'Dark Vessel', 20, '#7C3AED'),
|
||||||
|
('EVENT_CATEGORY:FLEET_CLUSTER', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'FLEET_CLUSTER', 1, '선단밀집', 'Fleet Cluster', 30, '#F97316'),
|
||||||
|
('EVENT_CATEGORY:ILLEGAL_TRANSSHIP', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'ILLEGAL_TRANSSHIP',1, '불법환적', 'Illegal Transship',40, '#EC4899'),
|
||||||
|
('EVENT_CATEGORY:MMSI_TAMPERING', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'MMSI_TAMPERING', 1, 'MMSI 변조', 'MMSI Tampering', 50, '#F59E0B'),
|
||||||
|
('EVENT_CATEGORY:AIS_LOSS', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'AIS_LOSS', 1, 'AIS 소실', 'AIS Loss', 60, '#64748B'),
|
||||||
|
('EVENT_CATEGORY:SPEED_ANOMALY', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'SPEED_ANOMALY', 1, '속력 이상', 'Speed Anomaly', 70, '#06B6D4'),
|
||||||
|
('EVENT_CATEGORY:ZONE_DEPARTURE', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'ZONE_DEPARTURE', 1, '구역 이탈', 'Zone Departure', 80, '#8B5CF6'),
|
||||||
|
('EVENT_CATEGORY:GEAR_ILLEGAL', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'GEAR_ILLEGAL', 1, '불법어구', 'Illegal Gear', 90, '#F97316'),
|
||||||
|
('EVENT_CATEGORY:AIS_RESUME', 'EVENT_CATEGORY', 'EVENT_CATEGORY', 'AIS_RESUME', 1, 'AIS 재송출', 'AIS Resume', 100, '#22C55E');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 단속 조치 유형 (ENFORCEMENT_ACTION)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('ENFORCEMENT_ACTION', NULL, 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 0, '단속 조치', 'Enforcement Action', 40, NULL),
|
||||||
|
('ENFORCEMENT_ACTION:CAPTURE', 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 'CAPTURE', 1, '나포', 'Capture', 10, '#EF4444'),
|
||||||
|
('ENFORCEMENT_ACTION:INSPECT', 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 'INSPECT', 1, '검문', 'Inspect', 20, '#F59E0B'),
|
||||||
|
('ENFORCEMENT_ACTION:WARN', 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 'WARN', 1, '경고', 'Warn', 30, '#3B82F6'),
|
||||||
|
('ENFORCEMENT_ACTION:DISPERSE', 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 'DISPERSE', 1, '퇴거', 'Disperse', 40, '#8B5CF6'),
|
||||||
|
('ENFORCEMENT_ACTION:TRACK', 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 'TRACK', 1, '추적', 'Track', 50, '#06B6D4'),
|
||||||
|
('ENFORCEMENT_ACTION:EVIDENCE', 'ENFORCEMENT_ACTION', 'ENFORCEMENT_ACTION', 'EVIDENCE', 1, '증거수집', 'Evidence', 60, '#64748B');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 단속 결과 (ENFORCEMENT_RESULT)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('ENFORCEMENT_RESULT', NULL, 'ENFORCEMENT_RESULT', 'ENFORCEMENT_RESULT', 0, '단속 결과', 'Enforcement Result', 50, NULL),
|
||||||
|
('ENFORCEMENT_RESULT:PUNISHED', 'ENFORCEMENT_RESULT', 'ENFORCEMENT_RESULT', 'PUNISHED', 1, '처벌', 'Punished', 10, '#EF4444'),
|
||||||
|
('ENFORCEMENT_RESULT:WARNED', 'ENFORCEMENT_RESULT', 'ENFORCEMENT_RESULT', 'WARNED', 1, '경고', 'Warned', 20, '#F59E0B'),
|
||||||
|
('ENFORCEMENT_RESULT:RELEASED', 'ENFORCEMENT_RESULT', 'ENFORCEMENT_RESULT', 'RELEASED', 1, '훈방', 'Released', 30, '#22C55E'),
|
||||||
|
('ENFORCEMENT_RESULT:REFERRED', 'ENFORCEMENT_RESULT', 'ENFORCEMENT_RESULT', 'REFERRED', 1, '수사의뢰', 'Referred', 40, '#7C3AED'),
|
||||||
|
('ENFORCEMENT_RESULT:FALSE_POSITIVE', 'ENFORCEMENT_RESULT', 'ENFORCEMENT_RESULT', 'FALSE_POSITIVE', 1, '오탐(정상)', 'False Positive', 50, '#6B7280');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- AI 일치도 (AI_MATCH)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('AI_MATCH', NULL, 'AI_MATCH', 'AI_MATCH', 0, 'AI 일치도', 'AI Match', 60, NULL),
|
||||||
|
('AI_MATCH:MATCH', 'AI_MATCH', 'AI_MATCH', 'MATCH', 1, '일치', 'Match', 10, '#22C55E'),
|
||||||
|
('AI_MATCH:PARTIAL', 'AI_MATCH', 'AI_MATCH', 'PARTIAL', 1, '부분일치', 'Partial', 20, '#F59E0B'),
|
||||||
|
('AI_MATCH:MISMATCH', 'AI_MATCH', 'AI_MATCH', 'MISMATCH', 1, '불일치', 'Mismatch', 30, '#EF4444');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 다크베셀 패턴 (DARK_PATTERN)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('DARK_PATTERN', NULL, 'DARK_PATTERN', 'DARK_PATTERN', 0, '다크베셀 패턴', 'Dark Pattern', 70, NULL),
|
||||||
|
('DARK_PATTERN:COMPLETE_BLACKOUT', 'DARK_PATTERN', 'DARK_PATTERN', 'COMPLETE_BLACKOUT', 1, 'AIS 완전차단', 'Complete Blackout', 10, '#EF4444'),
|
||||||
|
('DARK_PATTERN:INTERMITTENT', 'DARK_PATTERN', 'DARK_PATTERN', 'INTERMITTENT', 1, '간헐 송출', 'Intermittent', 20, '#F59E0B'),
|
||||||
|
('DARK_PATTERN:MMSI_SPOOFING', 'DARK_PATTERN', 'DARK_PATTERN', 'MMSI_SPOOFING', 1, 'MMSI 변경 의심', 'MMSI Spoofing', 30, '#F97316'),
|
||||||
|
('DARK_PATTERN:ZONE_BOUNDARY_DISAPPEAR', 'DARK_PATTERN', 'DARK_PATTERN', 'ZONE_BOUNDARY_DISAPPEAR',1,'수역 경계 소실', 'Zone Boundary Disappear',40, '#7C3AED'),
|
||||||
|
('DARK_PATTERN:RAPID_REAPPEAR', 'DARK_PATTERN', 'DARK_PATTERN', 'RAPID_REAPPEAR', 1, '급재출현', 'Rapid Reappear', 50, '#EC4899'),
|
||||||
|
('DARK_PATTERN:SCHEDULED_BLACKOUT', 'DARK_PATTERN', 'DARK_PATTERN', 'SCHEDULED_BLACKOUT', 1, '정기 차단(저위험)','Scheduled Blackout', 60, '#6B7280');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 허가 상태 (PERMIT_STATUS)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('PERMIT_STATUS', NULL, 'PERMIT_STATUS', 'PERMIT_STATUS', 0, '허가 상태', 'Permit Status', 80, NULL),
|
||||||
|
('PERMIT_STATUS:PERMITTED', 'PERMIT_STATUS', 'PERMIT_STATUS', 'PERMITTED', 1, '유효', 'Permitted', 10, '#22C55E'),
|
||||||
|
('PERMIT_STATUS:EXPIRED', 'PERMIT_STATUS', 'PERMIT_STATUS', 'EXPIRED', 1, '기간 초과', 'Expired', 20, '#F59E0B'),
|
||||||
|
('PERMIT_STATUS:NONE', 'PERMIT_STATUS', 'PERMIT_STATUS', 'NONE', 1, '무허가', 'None', 30, '#EF4444'),
|
||||||
|
('PERMIT_STATUS:REVOKED', 'PERMIT_STATUS', 'PERMIT_STATUS', 'REVOKED', 1, '취소', 'Revoked', 40, '#7C3AED'),
|
||||||
|
('PERMIT_STATUS:UNKNOWN', 'PERMIT_STATUS', 'PERMIT_STATUS', 'UNKNOWN', 1, '미상', 'Unknown', 50, '#6B7280');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 어구 판정 (GEAR_JUDGMENT) — illegal_gear_classifier 산출
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('GEAR_JUDGMENT', NULL, 'GEAR_JUDGMENT', 'GEAR_JUDGMENT', 0, '어구 판정', 'Gear Judgment', 90, NULL),
|
||||||
|
('GEAR_JUDGMENT:LEGAL', 'GEAR_JUDGMENT', 'GEAR_JUDGMENT', 'LEGAL', 1, '합법', 'Legal', 10, '#22C55E'),
|
||||||
|
('GEAR_JUDGMENT:NO_PERMIT', 'GEAR_JUDGMENT', 'GEAR_JUDGMENT', 'NO_PERMIT', 1, '무허가', 'No Permit', 20, '#EF4444'),
|
||||||
|
('GEAR_JUDGMENT:GEAR_MISMATCH', 'GEAR_JUDGMENT', 'GEAR_JUDGMENT', 'GEAR_MISMATCH', 1, '어구 불일치', 'Gear Mismatch', 30, '#F59E0B'),
|
||||||
|
('GEAR_JUDGMENT:ZONE_VIOLATION', 'GEAR_JUDGMENT', 'GEAR_JUDGMENT', 'ZONE_VIOLATION', 1, '해역 위반', 'Zone Violation',40, '#F97316'),
|
||||||
|
('GEAR_JUDGMENT:SEASON_VIOLATION', 'GEAR_JUDGMENT', 'GEAR_JUDGMENT', 'SEASON_VIOLATION',1, '금어기 위반', 'Season Violation',50,'#7C3AED');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 함정 상태 (PATROL_STATUS)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('PATROL_STATUS', NULL, 'PATROL_STATUS', 'PATROL_STATUS', 0, '함정 상태', 'Patrol Status', 100, NULL),
|
||||||
|
('PATROL_STATUS:AVAILABLE', 'PATROL_STATUS', 'PATROL_STATUS', 'AVAILABLE', 1, '가용', 'Available', 10, '#22C55E'),
|
||||||
|
('PATROL_STATUS:ON_PATROL', 'PATROL_STATUS', 'PATROL_STATUS', 'ON_PATROL', 1, '초계중', 'On Patrol', 20, '#3B82F6'),
|
||||||
|
('PATROL_STATUS:IN_PURSUIT', 'PATROL_STATUS', 'PATROL_STATUS', 'IN_PURSUIT', 1, '추적중', 'In Pursuit', 30, '#EF4444'),
|
||||||
|
('PATROL_STATUS:INSPECTING', 'PATROL_STATUS', 'PATROL_STATUS', 'INSPECTING', 1, '검문중', 'Inspecting', 40, '#F59E0B'),
|
||||||
|
('PATROL_STATUS:RETURNING', 'PATROL_STATUS', 'PATROL_STATUS', 'RETURNING', 1, '귀항중', 'Returning', 50, '#8B5CF6'),
|
||||||
|
('PATROL_STATUS:STANDBY', 'PATROL_STATUS', 'PATROL_STATUS', 'STANDBY', 1, '대기', 'Standby', 60, '#64748B'),
|
||||||
|
('PATROL_STATUS:MAINTENANCE', 'PATROL_STATUS', 'PATROL_STATUS', 'MAINTENANCE', 1, '정비중', 'Maintenance', 70, '#6B7280');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 선박 국적 (VESSEL_FLAG)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.code_master (code_id, parent_id, group_code, code, depth, name_ko, name_en, sort_order, color_hex) VALUES
|
||||||
|
('VESSEL_FLAG', NULL, 'VESSEL_FLAG', 'VESSEL_FLAG', 0, '선박 국적', 'Vessel Flag', 110, NULL),
|
||||||
|
('VESSEL_FLAG:CN', 'VESSEL_FLAG', 'VESSEL_FLAG', 'CN', 1, '중국', 'China', 10, '#EF4444'),
|
||||||
|
('VESSEL_FLAG:KR', 'VESSEL_FLAG', 'VESSEL_FLAG', 'KR', 1, '한국', 'Korea', 20, '#3B82F6'),
|
||||||
|
('VESSEL_FLAG:JP', 'VESSEL_FLAG', 'VESSEL_FLAG', 'JP', 1, '일본', 'Japan', 30, '#22C55E'),
|
||||||
|
('VESSEL_FLAG:RU', 'VESSEL_FLAG', 'VESSEL_FLAG', 'RU', 1, '러시아', 'Russia', 40, '#F59E0B'),
|
||||||
|
('VESSEL_FLAG:UNKNOWN', 'VESSEL_FLAG', 'VESSEL_FLAG', 'UNKNOWN', 1, '미상', 'Unknown', 50, '#6B7280');
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- V009: gear_type_master (어구 유형 마스터)
|
||||||
|
-- prediction classifier 출력값과 1:1 매칭
|
||||||
|
-- GearIdentification 화면에서 관리자가 룰 편집
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE kcg.gear_type_master (
|
||||||
|
gear_code VARCHAR(20) PRIMARY KEY,
|
||||||
|
gear_name_ko VARCHAR(50) NOT NULL,
|
||||||
|
gear_name_en VARCHAR(50),
|
||||||
|
category VARCHAR(20), -- NET, TRAP, LINE
|
||||||
|
-- 분류 룰 (prediction classifier가 사용)
|
||||||
|
speed_min_kn NUMERIC(5,2), -- 조업 속도 범위
|
||||||
|
speed_max_kn NUMERIC(5,2),
|
||||||
|
duration_min_minutes INT, -- 최소 지속시간
|
||||||
|
pattern_signature JSONB, -- 추가 분류 규칙 (확장용)
|
||||||
|
polygon_shape_hint VARCHAR(20), -- LINEAR, CIRCULAR, CLUSTERED
|
||||||
|
-- 합법성 기준
|
||||||
|
legal_zones TEXT[], -- zone_polygon_master.zone_code 참조
|
||||||
|
legal_seasons JSONB, -- [{"start":"03-01","end":"06-30"}]
|
||||||
|
permit_required BOOLEAN DEFAULT true,
|
||||||
|
-- 표시
|
||||||
|
display_color VARCHAR(7),
|
||||||
|
display_icon VARCHAR(30),
|
||||||
|
display_order INT DEFAULT 0,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_by UUID,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 프론트엔드 더미 기반 6종
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.gear_type_master (gear_code, gear_name_ko, gear_name_en, category,
|
||||||
|
speed_min_kn, speed_max_kn, duration_min_minutes, polygon_shape_hint,
|
||||||
|
permit_required, display_color, display_order, description) VALUES
|
||||||
|
('TRAWL_BOTTOM', '저층트롤', 'Bottom Trawl', 'NET',
|
||||||
|
2.0, 5.0, 60, 'LINEAR',
|
||||||
|
true, '#EF4444', 10, '저층을 끌어 조업하는 그물. 해저 생태계 영향 크고 대부분의 수역에서 규제됨'),
|
||||||
|
|
||||||
|
('GILLNET', '유자망', 'Gill Net', 'NET',
|
||||||
|
0.0, 2.0, 120, 'LINEAR',
|
||||||
|
true, '#F59E0B', 20, '그물을 설치하고 기다리는 수동 어구. 부유/저층 구분'),
|
||||||
|
|
||||||
|
('TRAP', '통발', 'Trap/Pot', 'TRAP',
|
||||||
|
0.0, 1.0, 240, 'CLUSTERED',
|
||||||
|
true, '#3B82F6', 30, '바닥에 설치하는 덫 방식 어구. 특정 어종 대상'),
|
||||||
|
|
||||||
|
('PURSE_SEINE', '선망', 'Purse Seine', 'NET',
|
||||||
|
3.0, 8.0, 30, 'CIRCULAR',
|
||||||
|
true, '#22C55E', 40, '원형으로 그물을 던져 감싸는 대규모 어법. 선단 규모 필요'),
|
||||||
|
|
||||||
|
('LONGLINE', '연승', 'Long Line', 'LINE',
|
||||||
|
1.0, 4.0, 180, 'LINEAR',
|
||||||
|
true, '#8B5CF6', 50, '긴 줄에 낚시바늘을 다수 부착하는 어법. 궤적이 선형'),
|
||||||
|
|
||||||
|
('UNKNOWN', '미분류', 'Unknown', NULL,
|
||||||
|
NULL, NULL, NULL, NULL,
|
||||||
|
false, '#6B7280', 99, '분류 불가능한 어구 또는 어구 미사용 선박');
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- V010: zone_polygon_master (해역 폴리곤 마스터)
|
||||||
|
-- PostGIS GEOMETRY 사용, MapControl 화면에서 관리자가 편집
|
||||||
|
-- prediction location.py가 매 사이클 참조
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE kcg.zone_polygon_master (
|
||||||
|
zone_code VARCHAR(30) PRIMARY KEY,
|
||||||
|
zone_name_ko VARCHAR(100) NOT NULL,
|
||||||
|
zone_name_en VARCHAR(100),
|
||||||
|
zone_type VARCHAR(30) NOT NULL, -- TERRITORIAL, EEZ, SPECIAL_FISHING, NLL, BUFFER, PROHIBITED, PATROL_SECTOR
|
||||||
|
parent_zone_code VARCHAR(30) REFERENCES kcg.zone_polygon_master(zone_code),
|
||||||
|
polygon_geom GEOMETRY(MULTIPOLYGON, 4326),
|
||||||
|
-- 단속 정책
|
||||||
|
baseline_distance_nm NUMERIC(8,2),
|
||||||
|
enforcement_priority INT DEFAULT 5, -- 1=최우선
|
||||||
|
default_risk_level VARCHAR(20), -- 진입만으로 부여되는 기본 위험 레벨
|
||||||
|
-- 어구 정책
|
||||||
|
allowed_gear_codes TEXT[],
|
||||||
|
prohibited_gear_codes TEXT[],
|
||||||
|
-- 표시
|
||||||
|
display_color VARCHAR(7),
|
||||||
|
display_opacity NUMERIC(3,2) DEFAULT 0.3,
|
||||||
|
display_order INT DEFAULT 0,
|
||||||
|
description TEXT,
|
||||||
|
metadata JSONB, -- 관할서, 면적, 기타
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_by UUID,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_zone_geom ON kcg.zone_polygon_master USING GIST(polygon_geom);
|
||||||
|
CREATE INDEX idx_zone_type ON kcg.zone_polygon_master(zone_type);
|
||||||
|
CREATE INDEX idx_zone_parent ON kcg.zone_polygon_master(parent_zone_code);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 주요 한국 해역 (간략 폴리곤 — 향후 정밀 GeoJSON import)
|
||||||
|
-- prediction의 data/zones/ GeoJSON이 정밀 데이터 소스
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 한국 영해 (12해리, 간략 바운딩)
|
||||||
|
INSERT INTO kcg.zone_polygon_master (zone_code, zone_name_ko, zone_name_en, zone_type,
|
||||||
|
enforcement_priority, default_risk_level, display_color, display_opacity, display_order, description)
|
||||||
|
VALUES
|
||||||
|
('TERRITORIAL_SEA', '영해', 'Territorial Sea', 'TERRITORIAL',
|
||||||
|
3, NULL, '#3B82F6', 0.15, 10, '기선으로부터 12해리 이내');
|
||||||
|
|
||||||
|
-- 한국 EEZ
|
||||||
|
INSERT INTO kcg.zone_polygon_master (zone_code, zone_name_ko, zone_name_en, zone_type,
|
||||||
|
enforcement_priority, default_risk_level, display_color, display_opacity, display_order, description)
|
||||||
|
VALUES
|
||||||
|
('EEZ_KR', '한국 EEZ', 'Korea EEZ', 'EEZ',
|
||||||
|
2, 'LOW', '#06B6D4', 0.1, 20, '한국 배타적 경제수역');
|
||||||
|
|
||||||
|
-- NLL
|
||||||
|
INSERT INTO kcg.zone_polygon_master (zone_code, zone_name_ko, zone_name_en, zone_type,
|
||||||
|
enforcement_priority, default_risk_level, display_color, display_opacity, display_order, description)
|
||||||
|
VALUES
|
||||||
|
('NLL', '북방한계선', 'Northern Limit Line', 'NLL',
|
||||||
|
1, 'CRITICAL', '#EF4444', 0.3, 5, '서해 북방한계선. 최우선 감시 구역');
|
||||||
|
|
||||||
|
-- 특정어업수역 Ⅰ~Ⅳ (iran prediction의 zones/ GeoJSON 참조)
|
||||||
|
INSERT INTO kcg.zone_polygon_master (zone_code, zone_name_ko, zone_name_en, zone_type,
|
||||||
|
parent_zone_code, enforcement_priority, default_risk_level,
|
||||||
|
display_color, display_opacity, display_order, description) VALUES
|
||||||
|
('SPECIAL_FISHING_1', '특정어업수역 Ⅰ', 'Special Fishing Zone 1', 'SPECIAL_FISHING',
|
||||||
|
'EEZ_KR', 2, 'HIGH', '#F59E0B', 0.25, 30, '한중 잠정조치수역 인접'),
|
||||||
|
('SPECIAL_FISHING_2', '특정어업수역 Ⅱ', 'Special Fishing Zone 2', 'SPECIAL_FISHING',
|
||||||
|
'EEZ_KR', 2, 'HIGH', '#F97316', 0.25, 31, '서해 중부 어업 수역'),
|
||||||
|
('SPECIAL_FISHING_3', '특정어업수역 Ⅲ', 'Special Fishing Zone 3', 'SPECIAL_FISHING',
|
||||||
|
'EEZ_KR', 3, 'MEDIUM', '#8B5CF6', 0.2, 32, '남해 어업 수역'),
|
||||||
|
('SPECIAL_FISHING_4', '특정어업수역 Ⅳ', 'Special Fishing Zone 4', 'SPECIAL_FISHING',
|
||||||
|
'EEZ_KR', 3, 'MEDIUM', '#EC4899', 0.2, 33, '동해 어업 수역');
|
||||||
|
|
||||||
|
-- 서해 5도 (patrol store 더미 참조)
|
||||||
|
INSERT INTO kcg.zone_polygon_master (zone_code, zone_name_ko, zone_name_en, zone_type,
|
||||||
|
enforcement_priority, default_risk_level, display_color, display_opacity, display_order, description)
|
||||||
|
VALUES
|
||||||
|
('WEST_5_ISLANDS', '서해 5도', 'West Sea 5 Islands', 'PATROL_SECTOR',
|
||||||
|
1, 'HIGH', '#EF4444', 0.2, 6, '백령도/대청도/소청도/연평도/우도 인근');
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- V011: vessel_permit_master + patrol_ship_master + fleet_companies + 시드
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 선단 회사 (중국 선단 회사 레지스트리)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.fleet_companies (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name_cn VARCHAR(200),
|
||||||
|
name_en VARCHAR(200),
|
||||||
|
name_ko VARCHAR(200),
|
||||||
|
country VARCHAR(10) DEFAULT 'CN',
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 어선 허가/등록 마스터
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.vessel_permit_master (
|
||||||
|
mmsi VARCHAR(20) PRIMARY KEY,
|
||||||
|
vessel_name VARCHAR(100),
|
||||||
|
vessel_name_cn VARCHAR(100),
|
||||||
|
flag_country VARCHAR(10), -- KR, CN, JP, RU, UNKNOWN
|
||||||
|
vessel_type VARCHAR(30), -- FISHING, CARGO, TANKER, PATROL, UNKNOWN
|
||||||
|
tonnage NUMERIC(10,2),
|
||||||
|
length_m NUMERIC(6,2),
|
||||||
|
build_year INT,
|
||||||
|
-- 허가 상태
|
||||||
|
permit_status VARCHAR(20) DEFAULT 'UNKNOWN', -- PERMITTED, EXPIRED, NONE, REVOKED, UNKNOWN
|
||||||
|
permit_no VARCHAR(50),
|
||||||
|
permitted_gear_codes TEXT[], -- gear_type_master.gear_code 참조
|
||||||
|
permitted_zones TEXT[], -- zone_polygon_master.zone_code 참조
|
||||||
|
permit_valid_from DATE,
|
||||||
|
permit_valid_to DATE,
|
||||||
|
-- 소속
|
||||||
|
company_id BIGINT REFERENCES kcg.fleet_companies(id),
|
||||||
|
-- 메타
|
||||||
|
data_source VARCHAR(50) DEFAULT 'MANUAL', -- KR_FISHERIES, CHINA_REGISTRY, IRAN_REGISTRY, MANUAL
|
||||||
|
last_synced_at TIMESTAMPTZ,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_vessel_flag ON kcg.vessel_permit_master(flag_country);
|
||||||
|
CREATE INDEX idx_vessel_permit ON kcg.vessel_permit_master(permit_status);
|
||||||
|
CREATE INDEX idx_vessel_company ON kcg.vessel_permit_master(company_id);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 함정 마스터
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.patrol_ship_master (
|
||||||
|
ship_id BIGSERIAL PRIMARY KEY,
|
||||||
|
ship_code VARCHAR(20) UNIQUE NOT NULL, -- '3001함'
|
||||||
|
ship_name VARCHAR(100),
|
||||||
|
ship_class VARCHAR(50), -- 태극급, 참수리급, 삼봉급
|
||||||
|
tonnage NUMERIC(10,2),
|
||||||
|
max_speed_kn NUMERIC(5,2),
|
||||||
|
fuel_capacity_l NUMERIC(10,2),
|
||||||
|
base_port VARCHAR(50),
|
||||||
|
-- 현재 상태 (실시간 갱신)
|
||||||
|
current_status VARCHAR(20) DEFAULT 'STANDBY', -- code_master PATROL_STATUS 참조
|
||||||
|
current_lat DOUBLE PRECISION,
|
||||||
|
current_lon DOUBLE PRECISION,
|
||||||
|
current_zone_code VARCHAR(30), -- zone_polygon_master FK
|
||||||
|
fuel_pct INT,
|
||||||
|
crew_count INT,
|
||||||
|
-- 메타
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 프론트엔드 더미 기반 선박 (9척)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.fleet_companies (id, name_cn, name_en, name_ko, country) VALUES
|
||||||
|
(1, '荣成远洋渔业', 'Rongcheng Ocean Fishery', '영성원양어업', 'CN'),
|
||||||
|
(2, '大连海洋', 'Dalian Ocean', '대련해양', 'CN');
|
||||||
|
SELECT setval('kcg.fleet_companies_id_seq', 10);
|
||||||
|
|
||||||
|
INSERT INTO kcg.vessel_permit_master (mmsi, vessel_name, vessel_name_cn, flag_country, vessel_type,
|
||||||
|
tonnage, permit_status, permitted_gear_codes, company_id, data_source) VALUES
|
||||||
|
('412345678', '鲁荣渔56555', '鲁荣渔56555', 'CN', 'FISHING', 450.0, 'NONE', '{TRAWL_BOTTOM}', 1, 'IRAN_REGISTRY'),
|
||||||
|
('412345679', '鲁荣渔56556', '鲁荣渔56556', 'CN', 'FISHING', 380.0, 'EXPIRED', '{GILLNET}', 1, 'IRAN_REGISTRY'),
|
||||||
|
('412345680', '辽大渔42881', '辽大渔42881', 'CN', 'FISHING', 520.0, 'NONE', '{PURSE_SEINE}', 2, 'IRAN_REGISTRY'),
|
||||||
|
('412345681', '浙象渔23166', '浙象渔23166', 'CN', 'FISHING', 290.0, 'NONE', '{LONGLINE}', NULL, 'IRAN_REGISTRY'),
|
||||||
|
('412345682', '闽霞渔09876', '闽霞渔09876', 'CN', 'FISHING', 410.0, 'NONE', '{TRAP}', NULL, 'IRAN_REGISTRY'),
|
||||||
|
('412345683', '苏赣渔05512', '苏赣渔05512', 'CN', 'FISHING', 350.0, 'NONE', '{GILLNET}', NULL, 'IRAN_REGISTRY'),
|
||||||
|
('440012345', '제주해양호', NULL, 'KR', 'FISHING', 85.0, 'PERMITTED','{LONGLINE,TRAP}',NULL, 'KR_FISHERIES'),
|
||||||
|
('440012346', '통영수산호', NULL, 'KR', 'FISHING', 120.0, 'PERMITTED','{GILLNET}', NULL, 'KR_FISHERIES'),
|
||||||
|
('000000001', 'Unknown-001', NULL, 'UNKNOWN','UNKNOWN',NULL,'UNKNOWN', NULL, NULL, 'MANUAL');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 함정 6척 (프론트엔드 patrolStore 기반)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.patrol_ship_master (ship_code, ship_name, ship_class, tonnage, max_speed_kn,
|
||||||
|
fuel_capacity_l, base_port, current_status, current_lat, current_lon,
|
||||||
|
current_zone_code, fuel_pct, crew_count) VALUES
|
||||||
|
('3001', '3001함', '태극급', 3000.0, 30.0, 500000, '인천', 'IN_PURSUIT', 37.52, 124.78, 'NLL', 78, 120),
|
||||||
|
('3005', '3005함', '삼봉급', 1500.0, 25.0, 300000, '목포', 'ON_PATROL', 34.85, 125.42, 'SPECIAL_FISHING_1', 92, 80),
|
||||||
|
('3009', '3009함', '참수리급', 500.0, 35.0, 100000, '속초', 'AVAILABLE', 38.12, 128.95, NULL, 100, 35),
|
||||||
|
('5001', '5001함', '태극급', 3000.0, 30.0, 500000, '부산', 'ON_PATROL', 34.42, 129.38, 'SPECIAL_FISHING_3', 65, 120),
|
||||||
|
('1502', '1502함', '참수리급', 500.0, 35.0, 100000, '인천', 'INSPECTING', 37.38, 124.55, 'WEST_5_ISLANDS', 45, 35),
|
||||||
|
('2003', '2003함', '삼봉급', 1500.0, 25.0, 300000, '서귀포', 'STANDBY', 33.15, 126.58, NULL, 88, 80);
|
||||||
@ -0,0 +1,252 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- V012: prediction 산출 테이블 (이벤트 허브 + 알림 + 통계)
|
||||||
|
-- prediction이 write, backend가 상태만 update
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 분석 결과 확장 (vessel_analysis_results)
|
||||||
|
-- prediction이 직접 INSERT. 기존 iran 28컬럼 + 확장
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.vessel_analysis_results (
|
||||||
|
id BIGSERIAL NOT NULL,
|
||||||
|
mmsi VARCHAR(20) NOT NULL,
|
||||||
|
analyzed_at TIMESTAMPTZ NOT NULL,
|
||||||
|
-- 분류
|
||||||
|
vessel_type VARCHAR(30),
|
||||||
|
confidence NUMERIC(5,4),
|
||||||
|
fishing_pct NUMERIC(5,4),
|
||||||
|
cluster_id INT,
|
||||||
|
season VARCHAR(20),
|
||||||
|
-- 위치
|
||||||
|
lat DOUBLE PRECISION,
|
||||||
|
lon DOUBLE PRECISION,
|
||||||
|
zone_code VARCHAR(30), -- zone_polygon_master FK
|
||||||
|
dist_to_baseline_nm NUMERIC(8,2),
|
||||||
|
-- 행동 분석
|
||||||
|
activity_state VARCHAR(20), -- STATIONARY, FISHING, SAILING
|
||||||
|
ucaf_score NUMERIC(5,4),
|
||||||
|
ucft_score NUMERIC(5,4),
|
||||||
|
-- 위협 탐지
|
||||||
|
is_dark BOOLEAN DEFAULT false,
|
||||||
|
gap_duration_min INT,
|
||||||
|
dark_pattern VARCHAR(30), -- code_master DARK_PATTERN 참조
|
||||||
|
spoofing_score NUMERIC(5,4),
|
||||||
|
bd09_offset_m NUMERIC(8,2),
|
||||||
|
speed_jump_count INT,
|
||||||
|
-- 환적
|
||||||
|
transship_suspect BOOLEAN DEFAULT false,
|
||||||
|
transship_pair_mmsi VARCHAR(20),
|
||||||
|
transship_duration_min INT,
|
||||||
|
-- 선단
|
||||||
|
fleet_cluster_id INT,
|
||||||
|
fleet_role VARCHAR(20), -- LEADER, FOLLOWER, NOISE
|
||||||
|
fleet_is_leader BOOLEAN DEFAULT false,
|
||||||
|
-- 위험도
|
||||||
|
risk_score INT, -- 0~100
|
||||||
|
risk_level VARCHAR(20), -- CRITICAL, HIGH, MEDIUM, LOW
|
||||||
|
-- ★ 확장 컬럼
|
||||||
|
gear_code VARCHAR(20), -- gear_type_master FK (분류 결과)
|
||||||
|
violation_categories TEXT[], -- code_master VIOLATION_TYPE 참조
|
||||||
|
gear_judgment VARCHAR(30), -- code_master GEAR_JUDGMENT 참조
|
||||||
|
permit_status VARCHAR(20), -- 분석 시점 허가 상태 스냅샷
|
||||||
|
-- 특징 벡터 (선택)
|
||||||
|
features JSONB,
|
||||||
|
-- 메타
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
PRIMARY KEY (id, analyzed_at)
|
||||||
|
) PARTITION BY RANGE (analyzed_at);
|
||||||
|
|
||||||
|
-- 파티션 (prediction partition_manager가 자동 생성하지만, 초기 1개 생성)
|
||||||
|
CREATE TABLE kcg.vessel_analysis_results_default PARTITION OF kcg.vessel_analysis_results DEFAULT;
|
||||||
|
|
||||||
|
CREATE INDEX idx_var_mmsi ON kcg.vessel_analysis_results(mmsi, analyzed_at DESC);
|
||||||
|
CREATE INDEX idx_var_risk ON kcg.vessel_analysis_results(risk_level, analyzed_at DESC);
|
||||||
|
CREATE INDEX idx_var_dark ON kcg.vessel_analysis_results(is_dark) WHERE is_dark = true;
|
||||||
|
CREATE INDEX idx_var_zone ON kcg.vessel_analysis_results(zone_code, analyzed_at DESC);
|
||||||
|
CREATE INDEX idx_var_gear ON kcg.vessel_analysis_results(gear_code) WHERE gear_code IS NOT NULL;
|
||||||
|
CREATE INDEX idx_var_violation ON kcg.vessel_analysis_results USING GIN(violation_categories);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 이벤트 허브 (★ 모든 운영 흐름의 단일 진입점)
|
||||||
|
-- prediction event_generator가 INSERT, backend가 status만 UPDATE
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_events (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
event_uid VARCHAR(50) UNIQUE NOT NULL, -- EVT-YYYYMMDD-NNNN
|
||||||
|
occurred_at TIMESTAMPTZ NOT NULL,
|
||||||
|
level VARCHAR(20) NOT NULL, -- CRITICAL, HIGH, MEDIUM, LOW
|
||||||
|
category VARCHAR(50) NOT NULL, -- code_master EVENT_CATEGORY 참조
|
||||||
|
title VARCHAR(200) NOT NULL,
|
||||||
|
detail TEXT,
|
||||||
|
-- 대상 선박
|
||||||
|
vessel_mmsi VARCHAR(20),
|
||||||
|
vessel_name VARCHAR(100),
|
||||||
|
-- 위치
|
||||||
|
area_name VARCHAR(100),
|
||||||
|
zone_code VARCHAR(30),
|
||||||
|
lat DOUBLE PRECISION,
|
||||||
|
lon DOUBLE PRECISION,
|
||||||
|
speed_kn NUMERIC(5,2),
|
||||||
|
-- 분석 출처
|
||||||
|
source_type VARCHAR(50), -- VESSEL_ANALYSIS, GEAR_GROUP, TRANSSHIP, FLEET
|
||||||
|
source_ref_id BIGINT, -- 원본 분석결과 PK
|
||||||
|
ai_confidence NUMERIC(5,4),
|
||||||
|
-- 운영 상태 (backend가 갱신)
|
||||||
|
status VARCHAR(20) DEFAULT 'NEW', -- code_master EVENT_STATUS 참조
|
||||||
|
assignee_id UUID,
|
||||||
|
assignee_name VARCHAR(100),
|
||||||
|
acked_at TIMESTAMPTZ,
|
||||||
|
resolved_at TIMESTAMPTZ,
|
||||||
|
resolution_note TEXT,
|
||||||
|
-- dedup
|
||||||
|
dedup_key VARCHAR(200), -- mmsi + category + window
|
||||||
|
-- 메타
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_event_status ON kcg.prediction_events(status, occurred_at DESC);
|
||||||
|
CREATE INDEX idx_event_level ON kcg.prediction_events(level, occurred_at DESC);
|
||||||
|
CREATE INDEX idx_event_category ON kcg.prediction_events(category, occurred_at DESC);
|
||||||
|
CREATE INDEX idx_event_mmsi ON kcg.prediction_events(vessel_mmsi, occurred_at DESC);
|
||||||
|
CREATE INDEX idx_event_dedup ON kcg.prediction_events(dedup_key, occurred_at DESC);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 이벤트 처리 워크플로우 (상태 변경 이력)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.event_workflow (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
event_id BIGINT NOT NULL REFERENCES kcg.prediction_events(id),
|
||||||
|
prev_status VARCHAR(20),
|
||||||
|
new_status VARCHAR(20) NOT NULL,
|
||||||
|
actor_id UUID,
|
||||||
|
actor_name VARCHAR(100),
|
||||||
|
comment TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_ew_event ON kcg.event_workflow(event_id, created_at DESC);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- AI 알림 발송 이력
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_alerts (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
event_id BIGINT NOT NULL REFERENCES kcg.prediction_events(id),
|
||||||
|
channel VARCHAR(20) NOT NULL, -- DASHBOARD, MOBILE, SMS, EMAIL
|
||||||
|
recipient VARCHAR(200),
|
||||||
|
sent_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
delivery_status VARCHAR(20) DEFAULT 'SENT', -- SENT, DELIVERED, READ, FAILED
|
||||||
|
ai_confidence NUMERIC(5,4),
|
||||||
|
metadata JSONB
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_alert_event ON kcg.prediction_alerts(event_id);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 사전 집계 통계 — 시간별 (최근 48h 보존)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_stats_hourly (
|
||||||
|
stat_hour TIMESTAMPTZ PRIMARY KEY,
|
||||||
|
total_detections INT DEFAULT 0,
|
||||||
|
by_category JSONB, -- {"EEZ_VIOLATION": 3, "DARK_VESSEL": 1, ...}
|
||||||
|
by_zone JSONB, -- {"NLL": 5, "EEZ_KR": 8, ...}
|
||||||
|
by_risk_level JSONB, -- {"CRITICAL": 2, "HIGH": 5, ...}
|
||||||
|
event_count INT DEFAULT 0,
|
||||||
|
critical_count INT DEFAULT 0,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 사전 집계 통계 — 일별
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_stats_daily (
|
||||||
|
stat_date DATE PRIMARY KEY,
|
||||||
|
total_detections INT DEFAULT 0,
|
||||||
|
by_category JSONB,
|
||||||
|
by_zone JSONB,
|
||||||
|
by_risk_level JSONB,
|
||||||
|
by_gear_type JSONB, -- {"TRAWL_BOTTOM": 12, "GILLNET": 8, ...}
|
||||||
|
by_violation_type JSONB, -- {"EEZ_VIOLATION": 15, ...}
|
||||||
|
event_count INT DEFAULT 0,
|
||||||
|
critical_event_count INT DEFAULT 0,
|
||||||
|
enforcement_count INT DEFAULT 0, -- 단속 건수 (backend가 주입)
|
||||||
|
false_positive_count INT DEFAULT 0,
|
||||||
|
ai_accuracy_pct NUMERIC(5,2),
|
||||||
|
manual_confirmed_parents INT DEFAULT 0, -- 운영자 모선 확정 건수
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 사전 집계 통계 — 월별
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_stats_monthly (
|
||||||
|
stat_month CHAR(7) PRIMARY KEY, -- 'YYYY-MM'
|
||||||
|
total_detections INT DEFAULT 0,
|
||||||
|
total_enforcements INT DEFAULT 0,
|
||||||
|
by_category JSONB,
|
||||||
|
by_zone JSONB,
|
||||||
|
by_risk_level JSONB,
|
||||||
|
by_gear_type JSONB,
|
||||||
|
by_violation_type JSONB,
|
||||||
|
event_count INT DEFAULT 0,
|
||||||
|
critical_event_count INT DEFAULT 0,
|
||||||
|
false_positive_count INT DEFAULT 0,
|
||||||
|
ai_accuracy_pct NUMERIC(5,2),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 실시간 KPI (프론트엔드 더미 kpiStore 대체)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_kpi_realtime (
|
||||||
|
kpi_key VARCHAR(50) PRIMARY KEY,
|
||||||
|
kpi_label VARCHAR(100) NOT NULL,
|
||||||
|
value INT DEFAULT 0,
|
||||||
|
trend VARCHAR(10), -- up, down, flat
|
||||||
|
delta_pct NUMERIC(5,2),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 시드: 프론트엔드 kpiStore 기반 6개 KPI
|
||||||
|
INSERT INTO kcg.prediction_kpi_realtime (kpi_key, kpi_label, value, trend, delta_pct) VALUES
|
||||||
|
('realtime_detection', '실시간 탐지', 47, 'up', 8.2),
|
||||||
|
('eez_violation', 'EEZ 침범', 18, 'up', 12.5),
|
||||||
|
('dark_vessel', '다크베셀', 12, 'down', -5.3),
|
||||||
|
('illegal_transship', '불법환적 의심',8, 'flat', 0.0),
|
||||||
|
('tracking_active', '추적 중', 15, 'up', 3.1),
|
||||||
|
('captured_inspected', '나포/검문', 3, 'flat', 0.0);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 위험도 격자 (RiskMap용, 1시간 단위)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_risk_grid (
|
||||||
|
cell_id VARCHAR(20) NOT NULL, -- 'lat_lon' 형식 (e.g., '3400_12500')
|
||||||
|
stat_hour TIMESTAMPTZ NOT NULL,
|
||||||
|
avg_risk NUMERIC(5,2),
|
||||||
|
max_risk INT,
|
||||||
|
vessel_count INT DEFAULT 0,
|
||||||
|
critical_count INT DEFAULT 0,
|
||||||
|
metadata JSONB,
|
||||||
|
PRIMARY KEY (cell_id, stat_hour)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_grid_hour ON kcg.prediction_risk_grid(stat_hour DESC);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- prediction 학습 피드백 입력 (backend가 write, prediction이 read)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.prediction_label_input (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
input_type VARCHAR(30) NOT NULL, -- PARENT_CONFIRM, PARENT_REJECT, FALSE_POSITIVE, GEAR_CORRECTION
|
||||||
|
group_key VARCHAR(255),
|
||||||
|
sub_cluster_id INT,
|
||||||
|
mmsi VARCHAR(20),
|
||||||
|
label_value VARCHAR(100), -- 확정된 모선 MMSI, 수정된 어구 코드 등
|
||||||
|
confidence NUMERIC(5,4),
|
||||||
|
actor_id UUID,
|
||||||
|
consumed_at TIMESTAMPTZ, -- prediction이 사용하면 timestamp 기록
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_label_unconsumed ON kcg.prediction_label_input(consumed_at) WHERE consumed_at IS NULL;
|
||||||
@ -0,0 +1,272 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- V013: 운영 도메인 테이블 (단속/계획/배치/AI모델)
|
||||||
|
-- 백엔드 전용 — 운영자 의사결정 데이터
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 단속 이력 (EnforcementHistory 화면)
|
||||||
|
-- enforcement_records.event_id → prediction_events.id (자동 매칭 + 수동 변경)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.enforcement_records (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
enf_uid VARCHAR(50) UNIQUE NOT NULL, -- ENF-YYYYMMDD-NNNN
|
||||||
|
event_id BIGINT REFERENCES kcg.prediction_events(id),
|
||||||
|
-- 시간/위치
|
||||||
|
enforced_at TIMESTAMPTZ NOT NULL,
|
||||||
|
zone_code VARCHAR(30), -- zone_polygon_master FK
|
||||||
|
area_name VARCHAR(100),
|
||||||
|
lat DOUBLE PRECISION,
|
||||||
|
lon DOUBLE PRECISION,
|
||||||
|
-- 대상 선박
|
||||||
|
vessel_mmsi VARCHAR(20),
|
||||||
|
vessel_name VARCHAR(100),
|
||||||
|
flag_country VARCHAR(10),
|
||||||
|
-- 단속 내용
|
||||||
|
violation_type VARCHAR(50), -- code_master VIOLATION_TYPE 참조
|
||||||
|
action VARCHAR(50) NOT NULL, -- code_master ENFORCEMENT_ACTION 참조
|
||||||
|
result VARCHAR(50), -- code_master ENFORCEMENT_RESULT 참조
|
||||||
|
-- AI 일치도
|
||||||
|
ai_match_status VARCHAR(20), -- code_master AI_MATCH 참조
|
||||||
|
ai_confidence NUMERIC(5,4),
|
||||||
|
-- 수행 함정
|
||||||
|
patrol_ship_id BIGINT REFERENCES kcg.patrol_ship_master(ship_id),
|
||||||
|
-- 담당
|
||||||
|
enforced_by UUID,
|
||||||
|
enforced_by_name VARCHAR(100),
|
||||||
|
remarks TEXT,
|
||||||
|
-- 메타
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_enf_date ON kcg.enforcement_records(enforced_at DESC);
|
||||||
|
CREATE INDEX idx_enf_event ON kcg.enforcement_records(event_id);
|
||||||
|
CREATE INDEX idx_enf_mmsi ON kcg.enforcement_records(vessel_mmsi);
|
||||||
|
CREATE INDEX idx_enf_type ON kcg.enforcement_records(violation_type);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 단속 계획 (EnforcementPlan 화면)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.enforcement_plans (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
plan_uid VARCHAR(50) UNIQUE NOT NULL, -- PLN-YYYYMMDD-NNNN
|
||||||
|
title VARCHAR(200) NOT NULL,
|
||||||
|
zone_code VARCHAR(30),
|
||||||
|
area_name VARCHAR(100),
|
||||||
|
lat DOUBLE PRECISION,
|
||||||
|
lon DOUBLE PRECISION,
|
||||||
|
-- 일정
|
||||||
|
planned_date DATE NOT NULL,
|
||||||
|
planned_from TIMESTAMPTZ,
|
||||||
|
planned_to TIMESTAMPTZ,
|
||||||
|
-- 위험도 평가
|
||||||
|
risk_level VARCHAR(20),
|
||||||
|
risk_score INT,
|
||||||
|
-- 배치
|
||||||
|
assigned_ship_count INT DEFAULT 0,
|
||||||
|
assigned_crew INT DEFAULT 0,
|
||||||
|
-- 상태
|
||||||
|
status VARCHAR(20) DEFAULT 'DRAFT', -- DRAFT, APPROVED, IN_PROGRESS, COMPLETED, CANCELLED
|
||||||
|
alert_status VARCHAR(20), -- 경보 발령 여부
|
||||||
|
-- 담당
|
||||||
|
created_by UUID,
|
||||||
|
approved_by UUID,
|
||||||
|
remarks TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_plan_date ON kcg.enforcement_plans(planned_date DESC);
|
||||||
|
CREATE INDEX idx_plan_status ON kcg.enforcement_plans(status);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 함정 배치 (PatrolRoute, FleetOptimization)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.patrol_assignments (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
ship_id BIGINT NOT NULL REFERENCES kcg.patrol_ship_master(ship_id),
|
||||||
|
plan_id BIGINT REFERENCES kcg.enforcement_plans(id),
|
||||||
|
-- 배치 해역
|
||||||
|
zone_code VARCHAR(30),
|
||||||
|
-- 기간
|
||||||
|
assigned_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
-- 경로
|
||||||
|
waypoints JSONB, -- [{lat, lon, name, eta}]
|
||||||
|
route_distance_nm NUMERIC(8,2),
|
||||||
|
estimated_hours NUMERIC(5,2),
|
||||||
|
fuel_estimate_l NUMERIC(10,2),
|
||||||
|
-- 상태
|
||||||
|
status VARCHAR(20) DEFAULT 'ASSIGNED',-- ASSIGNED, EN_ROUTE, ON_STATION, COMPLETED, CANCELLED
|
||||||
|
-- 담당
|
||||||
|
assigned_by UUID,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_pa_ship ON kcg.patrol_assignments(ship_id, assigned_at DESC);
|
||||||
|
CREATE INDEX idx_pa_plan ON kcg.patrol_assignments(plan_id);
|
||||||
|
CREATE INDEX idx_pa_status ON kcg.patrol_assignments(status);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- AI 모델 버전 (AIModelManagement)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE kcg.ai_model_versions (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
model_name VARCHAR(100) NOT NULL, -- 'gear_classifier', 'risk_scorer', 'parent_inference'
|
||||||
|
version VARCHAR(50) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
-- 성능 메트릭
|
||||||
|
accuracy_pct NUMERIC(5,2),
|
||||||
|
precision_pct NUMERIC(5,2),
|
||||||
|
recall_pct NUMERIC(5,2),
|
||||||
|
f1_score NUMERIC(5,4),
|
||||||
|
-- 상태
|
||||||
|
status VARCHAR(20) DEFAULT 'TRAINING', -- TRAINING, EVALUATING, DEPLOYED, ARCHIVED
|
||||||
|
deployed_at TIMESTAMPTZ,
|
||||||
|
-- 메타
|
||||||
|
train_config JSONB,
|
||||||
|
eval_metrics JSONB,
|
||||||
|
created_by UUID,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE kcg.ai_model_metrics (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
model_id BIGINT NOT NULL REFERENCES kcg.ai_model_versions(id),
|
||||||
|
metric_date DATE NOT NULL,
|
||||||
|
metric_name VARCHAR(50) NOT NULL, -- accuracy, precision, recall, f1, latency_ms
|
||||||
|
metric_value NUMERIC(10,4),
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_amm_model ON kcg.ai_model_metrics(model_id, metric_date DESC);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 단속 이력 6건 (프론트 enforcementStore 기반)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.enforcement_records (enf_uid, enforced_at, zone_code, area_name,
|
||||||
|
vessel_mmsi, vessel_name, flag_country, violation_type, action, result,
|
||||||
|
ai_match_status, ai_confidence, patrol_ship_id, remarks) VALUES
|
||||||
|
('ENF-20260403-0001', '2026-04-03 14:30:00+09', 'NLL', '서해 NLL 인근',
|
||||||
|
'412345678', '鲁荣渔56555', 'CN', 'EEZ_VIOLATION', 'CAPTURE', 'PUNISHED',
|
||||||
|
'MATCH', 0.95, (SELECT ship_id FROM kcg.patrol_ship_master WHERE ship_code='3001'),
|
||||||
|
'NLL 인근 불법조업 현행범'),
|
||||||
|
('ENF-20260402-0001', '2026-04-02 09:15:00+09', 'SPECIAL_FISHING_1', '서해 중부',
|
||||||
|
'412345680', '辽大渔42881', 'CN', 'ILLEGAL_GEAR', 'INSPECT', 'WARNED',
|
||||||
|
'PARTIAL', 0.72, (SELECT ship_id FROM kcg.patrol_ship_master WHERE ship_code='3005'),
|
||||||
|
'무허가 선망 사용'),
|
||||||
|
('ENF-20260401-0001', '2026-04-01 16:45:00+09', 'SPECIAL_FISHING_2', '서해 5도 인근',
|
||||||
|
'412345679', '鲁荣渔56556', 'CN', 'DARK_VESSEL', 'TRACK', 'REFERRED',
|
||||||
|
'MATCH', 0.88, (SELECT ship_id FROM kcg.patrol_ship_master WHERE ship_code='1502'),
|
||||||
|
'AIS 차단 후 도주, 수사의뢰'),
|
||||||
|
('ENF-20260331-0001', '2026-03-31 11:20:00+09', 'EEZ_KR', 'EEZ 남부',
|
||||||
|
'412345682', '闽霞渔09876', 'CN', 'ILLEGAL_TRANSSHIP', 'EVIDENCE', 'PUNISHED',
|
||||||
|
'MATCH', 0.91, (SELECT ship_id FROM kcg.patrol_ship_master WHERE ship_code='5001'),
|
||||||
|
'해상 환적 현장 증거 확보'),
|
||||||
|
('ENF-20260330-0001', '2026-03-30 08:00:00+09', 'WEST_5_ISLANDS', '연평도 서방',
|
||||||
|
'412345681', '浙象渔23166', 'CN', 'ZONE_DEPARTURE', 'DISPERSE', 'RELEASED',
|
||||||
|
'MISMATCH', 0.45, (SELECT ship_id FROM kcg.patrol_ship_master WHERE ship_code='1502'),
|
||||||
|
'조업구역 이탈, 퇴거 조치 (오탐 가능성)'),
|
||||||
|
('ENF-20260329-0001', '2026-03-29 22:30:00+09', 'NLL', 'NLL 동부',
|
||||||
|
'412345683', '苏赣渔05512', 'CN', 'EEZ_VIOLATION', 'CAPTURE', 'PUNISHED',
|
||||||
|
'MATCH', 0.97, (SELECT ship_id FROM kcg.patrol_ship_master WHERE ship_code='3001'),
|
||||||
|
'야간 EEZ 침범, 고속 도주 후 나포');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 이벤트 15건 (프론트 eventStore 기반)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.prediction_events (event_uid, occurred_at, level, category, title, detail,
|
||||||
|
vessel_mmsi, vessel_name, area_name, zone_code, lat, lon, speed_kn,
|
||||||
|
source_type, ai_confidence, status) VALUES
|
||||||
|
('EVT-20260407-0001', '2026-04-07 06:12:00+09', 'CRITICAL', 'EEZ_INTRUSION',
|
||||||
|
'EEZ 침범 탐지', '중국 어선 鲁荣渔56555 EEZ 침범, 위험도 96',
|
||||||
|
'412345678', '鲁荣渔56555', '서해 NLL', 'NLL', 37.52, 124.78, 8.5,
|
||||||
|
'VESSEL_ANALYSIS', 0.96, 'IN_PROGRESS'),
|
||||||
|
('EVT-20260407-0002', '2026-04-07 05:48:00+09', 'HIGH', 'DARK_VESSEL',
|
||||||
|
'다크베셀 장기 소실', 'AIS 신호 180분 소실, 완전차단 패턴',
|
||||||
|
'412345680', '辽大渔42881', '서해 중부', 'SPECIAL_FISHING_1', 34.85, 125.42, 0.0,
|
||||||
|
'VESSEL_ANALYSIS', 0.88, 'ACK'),
|
||||||
|
('EVT-20260407-0003', '2026-04-07 05:30:00+09', 'HIGH', 'FLEET_CLUSTER',
|
||||||
|
'선단 밀집 감지', '중국어선 12척 선단 밀집, 리더 선박 식별',
|
||||||
|
'412345681', '浙象渔23166', 'EEZ 북부', 'EEZ_KR', 36.90, 124.20, 6.2,
|
||||||
|
'VESSEL_ANALYSIS', 0.82, 'NEW'),
|
||||||
|
('EVT-20260407-0004', '2026-04-07 04:55:00+09', 'HIGH', 'ILLEGAL_TRANSSHIP',
|
||||||
|
'불법환적 의심', '2척 60분 이상 근접 정박, 환적 의심',
|
||||||
|
'412345682', '闽霞渔09876', '남해', 'SPECIAL_FISHING_3', 34.42, 129.38, 0.5,
|
||||||
|
'VESSEL_ANALYSIS', 0.79, 'NEW'),
|
||||||
|
('EVT-20260407-0005', '2026-04-07 04:30:00+09', 'CRITICAL', 'MMSI_TAMPERING',
|
||||||
|
'MMSI 3회 변경', 'MMSI 변경 3회 탐지, GPS 스푸핑 의심',
|
||||||
|
'412345683', '苏赣渔05512', '서해 5도', 'WEST_5_ISLANDS', 37.38, 124.55, 12.3,
|
||||||
|
'VESSEL_ANALYSIS', 0.93, 'IN_PROGRESS'),
|
||||||
|
('EVT-20260407-0006', '2026-04-07 04:10:00+09', 'MEDIUM', 'ZONE_DEPARTURE',
|
||||||
|
'구역 이탈', '허가 해역 이탈 감지',
|
||||||
|
'440012345', '제주해양호', 'EEZ 남부', 'EEZ_KR', 33.15, 126.58, 5.1,
|
||||||
|
'VESSEL_ANALYSIS', 0.65, 'NEW'),
|
||||||
|
('EVT-20260407-0007', '2026-04-07 03:45:00+09', 'LOW', 'AIS_RESUME',
|
||||||
|
'AIS 재송출', 'AIS 신호 회복, 120분 소실 후',
|
||||||
|
'412345679', '鲁荣渔56556', '서해 NLL', 'NLL', 37.45, 124.65, 3.2,
|
||||||
|
'VESSEL_ANALYSIS', 0.55, 'RESOLVED'),
|
||||||
|
('EVT-20260407-0008', '2026-04-07 03:20:00+09', 'MEDIUM', 'SPEED_ANOMALY',
|
||||||
|
'속력 이상', '급격한 속력 변화 탐지 (2kn → 18kn)',
|
||||||
|
'412345678', '鲁荣渔56555', '서해 NLL', 'NLL', 37.50, 124.80, 18.0,
|
||||||
|
'VESSEL_ANALYSIS', 0.71, 'ACK'),
|
||||||
|
('EVT-20260407-0009', '2026-04-07 02:55:00+09', 'HIGH', 'AIS_LOSS',
|
||||||
|
'AIS 소실', '45분간 AIS 신호 없음, 간헐송출 패턴',
|
||||||
|
'412345681', '浙象渔23166', 'EEZ 북부', 'EEZ_KR', 36.88, 124.18, 0.0,
|
||||||
|
'VESSEL_ANALYSIS', 0.75, 'NEW'),
|
||||||
|
('EVT-20260407-0010', '2026-04-07 02:30:00+09', 'MEDIUM', 'GEAR_ILLEGAL',
|
||||||
|
'EEZ 내 어구 설치', '무허가 저층트롤 어구 그룹 탐지',
|
||||||
|
'412345680', '辽大渔42881', '서해 중부', 'SPECIAL_FISHING_1', 34.90, 125.40, 2.8,
|
||||||
|
'GEAR_GROUP', 0.68, 'NEW'),
|
||||||
|
('EVT-20260407-0011', '2026-04-07 02:00:00+09', 'MEDIUM', 'FLEET_CLUSTER',
|
||||||
|
'어구 그룹 신규 탐지', '4척 어구 그룹 신규 형성',
|
||||||
|
NULL, NULL, '서해 5도', 'WEST_5_ISLANDS', 37.40, 124.50, 0.0,
|
||||||
|
'GEAR_GROUP', 0.62, 'NEW'),
|
||||||
|
('EVT-20260407-0012', '2026-04-07 01:30:00+09', 'LOW', 'ZONE_DEPARTURE',
|
||||||
|
'접안 후 출항', '검문 대상 선박 출항',
|
||||||
|
'412345679', '鲁荣渔56556', '인천항', NULL, 37.45, 126.60, 5.0,
|
||||||
|
'VESSEL_ANALYSIS', 0.40, 'RESOLVED'),
|
||||||
|
('EVT-20260407-0013', '2026-04-07 01:00:00+09', 'CRITICAL', 'EEZ_INTRUSION',
|
||||||
|
'NLL 근접 EEZ 침범', '위험도 최상위, 야간 침입',
|
||||||
|
'412345683', '苏赣渔05512', 'NLL 동부', 'NLL', 37.55, 124.90, 11.0,
|
||||||
|
'VESSEL_ANALYSIS', 0.98, 'RESOLVED'),
|
||||||
|
('EVT-20260406-0001', '2026-04-06 22:00:00+09', 'HIGH', 'DARK_VESSEL',
|
||||||
|
'다크베셀 탐지', 'MMSI 변조 후 AIS 차단',
|
||||||
|
'412345682', '闽霞渔09876', '남해', 'SPECIAL_FISHING_3', 34.40, 129.35, 0.0,
|
||||||
|
'VESSEL_ANALYSIS', 0.85, 'RESOLVED'),
|
||||||
|
('EVT-20260406-0002', '2026-04-06 20:30:00+09', 'MEDIUM', 'ILLEGAL_TRANSSHIP',
|
||||||
|
'환적 의심', '야간 근접 정박 90분',
|
||||||
|
'412345681', '浙象渔23166', 'EEZ 서부', 'EEZ_KR', 35.20, 124.80, 0.3,
|
||||||
|
'VESSEL_ANALYSIS', 0.70, 'FALSE_POSITIVE');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 단속 계획 5건 (프론트 enforcementStore 기반)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.enforcement_plans (plan_uid, title, zone_code, area_name,
|
||||||
|
lat, lon, planned_date, risk_level, risk_score,
|
||||||
|
assigned_ship_count, assigned_crew, status, alert_status) VALUES
|
||||||
|
('PLN-20260408-0001', 'NLL 집중 단속', 'NLL', '서해 NLL', 37.52, 124.78,
|
||||||
|
'2026-04-08', 'CRITICAL', 92, 3, 180, 'APPROVED', '경보 발령'),
|
||||||
|
('PLN-20260409-0001', 'EEZ 북부 순찰', 'EEZ_KR', 'EEZ 북부', 36.90, 124.20,
|
||||||
|
'2026-04-09', 'HIGH', 78, 2, 120, 'DRAFT', NULL),
|
||||||
|
('PLN-20260410-0001', '서해 5도 초계', 'WEST_5_ISLANDS', '서해 5도', 37.38, 124.55,
|
||||||
|
'2026-04-10', 'HIGH', 85, 2, 100, 'APPROVED', '주의'),
|
||||||
|
('PLN-20260411-0001', '남해 환적 감시', 'SPECIAL_FISHING_3', '남해', 34.42, 129.38,
|
||||||
|
'2026-04-11', 'MEDIUM', 65, 1, 60, 'DRAFT', NULL),
|
||||||
|
('PLN-20260412-0001', '서해 중부 야간 작전', 'SPECIAL_FISHING_1', '서해 중부', 34.85, 125.42,
|
||||||
|
'2026-04-12', 'HIGH', 80, 2, 140, 'APPROVED', '경보 발령');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 시드: 일별/월별 통계 (프론트 kpiStore/monthlyTrends 기반)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO kcg.prediction_stats_monthly (stat_month, total_detections, total_enforcements,
|
||||||
|
by_violation_type, event_count, critical_event_count, false_positive_count, ai_accuracy_pct) VALUES
|
||||||
|
('2025-10', 128, 42, '{"EEZ_VIOLATION":45,"DARK_VESSEL":32,"MMSI_TAMPERING":23,"ILLEGAL_TRANSSHIP":15,"ILLEGAL_GEAR":13}', 85, 12, 16, 81.0),
|
||||||
|
('2025-11', 145, 38, '{"EEZ_VIOLATION":51,"DARK_VESSEL":36,"MMSI_TAMPERING":26,"ILLEGAL_TRANSSHIP":17,"ILLEGAL_GEAR":15}', 97, 15, 14, 84.0),
|
||||||
|
('2025-12', 167, 55, '{"EEZ_VIOLATION":59,"DARK_VESSEL":42,"MMSI_TAMPERING":30,"ILLEGAL_TRANSSHIP":20,"ILLEGAL_GEAR":16}', 112, 18, 12, 86.0),
|
||||||
|
('2026-01', 189, 61, '{"EEZ_VIOLATION":66,"DARK_VESSEL":47,"MMSI_TAMPERING":34,"ILLEGAL_TRANSSHIP":23,"ILLEGAL_GEAR":19}', 126, 22, 10, 88.0),
|
||||||
|
('2026-02', 156, 48, '{"EEZ_VIOLATION":55,"DARK_VESSEL":39,"MMSI_TAMPERING":28,"ILLEGAL_TRANSSHIP":19,"ILLEGAL_GEAR":15}', 104, 17, 9, 89.0),
|
||||||
|
('2026-03', 172, 52, '{"EEZ_VIOLATION":60,"DARK_VESSEL":43,"MMSI_TAMPERING":31,"ILLEGAL_TRANSSHIP":21,"ILLEGAL_GEAR":17}', 115, 19, 8, 90.0),
|
||||||
|
('2026-04', 67, 15, '{"EEZ_VIOLATION":24,"DARK_VESSEL":17,"MMSI_TAMPERING":12,"ILLEGAL_TRANSSHIP":8,"ILLEGAL_GEAR":6}', 45, 8, 2, 93.0);
|
||||||
@ -1,30 +1,73 @@
|
|||||||
# Database Migrations
|
# Database Migrations
|
||||||
|
|
||||||
PostgreSQL 마이그레이션 (Flyway 형식).
|
> ⚠️ **실제 SQL 파일 위치**: [`backend/src/main/resources/db/migration/`](../../backend/src/main/resources/db/migration/)
|
||||||
|
>
|
||||||
|
> Spring Boot Flyway 표준 위치를 따르므로 SQL 파일은 백엔드 모듈 안에 있습니다.
|
||||||
|
> Spring Boot 기동 시 Flyway가 자동으로 적용합니다.
|
||||||
|
|
||||||
## DB 정보
|
## DB 정보
|
||||||
- DB Name: `kcgaidb`
|
- **DB Name**: `kcgaidb`
|
||||||
- User: `kcg-app`
|
- **User**: `kcg-app`
|
||||||
- Schema: `kcg`
|
- **Schema**: `kcg`
|
||||||
|
- **Host**: `211.208.115.83:5432`
|
||||||
|
|
||||||
## 마이그레이션 파일 (Phase 2에서 작성)
|
## 적용된 마이그레이션 (V001~V013)
|
||||||
|
|
||||||
|
### Phase 1~8: 인증/권한/감사 (V001~V007)
|
||||||
|
|
||||||
| 파일 | 내용 |
|
| 파일 | 내용 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `V001__auth_init.sql` | 사용자, 조직, 역할, 로그인 이력 |
|
| `V001__auth_init.sql` | 인증/조직/역할/사용자-역할/로그인 이력 |
|
||||||
| `V002__perm_tree.sql` | 권한 트리 + 권한 매트릭스 |
|
| `V002__perm_tree.sql` | 권한 트리 + 권한 매트릭스 |
|
||||||
| `V003__perm_seed.sql` | 초기 역할 + 트리 노드 시드 |
|
| `V003__perm_seed.sql` | 초기 역할 5종 + 트리 노드 45개 + 권한 매트릭스 시드 |
|
||||||
| `V004__access_logs.sql` | 감사로그, 접근 이력 |
|
| `V004__access_logs.sql` | 감사로그/접근이력 |
|
||||||
| `V005__parent_workflow.sql` | 모선 워크플로우 (운영자 결정/제외/학습 세션) |
|
| `V005__parent_workflow.sql` | 모선 워크플로우 (resolution/review_log/exclusions/label_sessions) |
|
||||||
|
| `V006__demo_accounts.sql` | 데모 계정 5종 |
|
||||||
|
| `V007__perm_tree_label_align.sql` | 트리 노드 명칭을 사이드바 i18n 라벨과 일치 |
|
||||||
|
|
||||||
|
### S1: 마스터 데이터 + Prediction 기반 (V008~V013)
|
||||||
|
|
||||||
|
| 파일 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| `V008__code_master.sql` | 계층형 코드 마스터 (12그룹, 72코드: 위반유형/이벤트/단속/허가/함정 등) |
|
||||||
|
| `V009__gear_type_master.sql` | 어구 유형 마스터 6종 (분류 룰 + 합법성 기준) |
|
||||||
|
| `V010__zone_polygon_master.sql` | 해역 폴리곤 마스터 (PostGIS GEOMETRY, 8개 해역 시드) |
|
||||||
|
| `V011__vessel_permit_patrol.sql` | 어선 허가 마스터 + 함정 마스터 + fleet_companies (선박 9척, 함정 6척) |
|
||||||
|
| `V012__prediction_events_stats.sql` | vessel_analysis_results(파티션) + 이벤트 허브 + 알림 + 통계(시/일/월) + KPI + 위험격자 + 학습피드백 |
|
||||||
|
| `V013__enforcement_operations.sql` | 단속 이력/계획 + 함정 배치 + AI모델 버전/메트릭 (시드 포함) |
|
||||||
|
|
||||||
## 실행 방법
|
## 실행 방법
|
||||||
|
|
||||||
```bash
|
### 최초 1회 - DB/사용자 생성 (관리자 권한 필요)
|
||||||
# DB 생성 (1회)
|
```sql
|
||||||
psql -U postgres -c "CREATE DATABASE kcgaidb;"
|
-- snp 관리자 계정으로 접속
|
||||||
psql -U postgres -c "CREATE USER \"kcg-app\" WITH PASSWORD 'Kcg2026ai';"
|
psql -h 211.208.115.83 -U snp -d postgres
|
||||||
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE kcgaidb TO \"kcg-app\";"
|
|
||||||
|
|
||||||
# 마이그레이션은 backend Spring Boot가 기동 시 자동 실행 (Flyway)
|
CREATE DATABASE kcgaidb;
|
||||||
|
CREATE USER "kcg-app" WITH PASSWORD 'Kcg2026ai';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE kcgaidb TO "kcg-app";
|
||||||
|
|
||||||
|
\c kcgaidb
|
||||||
|
CREATE SCHEMA IF NOT EXISTS kcg AUTHORIZATION "kcg-app";
|
||||||
|
GRANT ALL ON SCHEMA kcg TO "kcg-app";
|
||||||
|
ALTER DATABASE kcgaidb OWNER TO "kcg-app";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 마이그레이션 실행 (자동)
|
||||||
|
백엔드 기동 시 Flyway가 자동 적용:
|
||||||
|
```bash
|
||||||
cd backend && ./mvnw spring-boot:run
|
cd backend && ./mvnw spring-boot:run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 수동 적용
|
||||||
|
```bash
|
||||||
|
cd backend && ./mvnw flyway:migrate -Dflyway.url=jdbc:postgresql://211.208.115.83:5432/kcgaidb -Dflyway.user=kcg-app -Dflyway.password=Kcg2026ai -Dflyway.schemas=kcg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Checksum 불일치 시 (마이그레이션 파일 수정 후)
|
||||||
|
```bash
|
||||||
|
cd backend && ./mvnw flyway:repair -Dflyway.url=... (위와 동일)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 신규 마이그레이션 추가
|
||||||
|
[`backend/src/main/resources/db/migration/`](../../backend/src/main/resources/db/migration/)에 `V00N__설명.sql` 형식으로 추가하면 다음 기동 시 자동 적용됩니다.
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user