From 1aa887fce4e05c5f9d885e4464a8704cdc57dbcf Mon Sep 17 00:00:00 2001 From: Nan Kyung Lee Date: Tue, 24 Mar 2026 15:51:04 +0900 Subject: [PATCH] =?UTF-8?q?feat(korea):=20=EC=9E=91=EC=A0=84=EA=B0=80?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=203=ED=83=AD=20=EA=B5=AC=EC=84=B1=20?= =?UTF-8?q?=E2=80=94=20=EC=8B=A4=EC=8B=9C=EA=B0=84=ED=83=90=EC=A7=80=20+?= =?UTF-8?q?=20=EB=8C=80=EC=9D=91=EC=A0=88=EC=B0=A8=20+=20=EC=A1=B0?= =?UTF-8?q?=EC=B9=98=EA=B8=B0=EC=A4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 3개 탭: 실시간 탐지 / 대응 절차 / 조치 기준 - 의심 선박 클릭 → 자동으로 대응 절차 탭 전환 - 선박 추정 업종(PT/GN/PS/FC/GEAR) 자동 분류 → 해당 STEP 표시 - 중국어 경고문 업종별 배치 (클릭 → 클립보드 복사) PT: 4개, GN: 4개, PS: 4개, FC: 3개, GEAR: 1개 - 조치 기준 탭: 8대 위반유형 테이블 + 감시 강화 시기 - GC-KCG-2026-001 제7장 작전가이드 PDF 전문 반영 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/korea/OpsGuideModal.tsx | 453 +++++++++++------- 1 file changed, 277 insertions(+), 176 deletions(-) diff --git a/frontend/src/components/korea/OpsGuideModal.tsx b/frontend/src/components/korea/OpsGuideModal.tsx index d17ba21..d928e94 100644 --- a/frontend/src/components/korea/OpsGuideModal.tsx +++ b/frontend/src/components/korea/OpsGuideModal.tsx @@ -21,13 +21,14 @@ interface Props { interface SuspectVessel { ship: Ship; - distance: number; // NM from selected KCG + distance: number; reasons: string[]; riskLevel: 'CRITICAL' | 'HIGH' | 'MEDIUM'; + estimatedType: 'PT' | 'GN' | 'PS' | 'FC' | 'GEAR' | 'UNKNOWN'; } function haversineNM(lat1: number, lng1: number, lat2: number, lng2: number): number { - const R = 3440.065; // Earth radius in NM + const R = 3440.065; const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) ** 2; @@ -36,11 +37,61 @@ function haversineNM(lat1: number, lng1: number, lat2: number, lng2: number): nu const RISK_COLOR = { CRITICAL: '#ef4444', HIGH: '#f59e0b', MEDIUM: '#3b82f6' }; const RISK_ICON = { CRITICAL: '🔴', HIGH: '🟡', MEDIUM: '🔵' }; +type Tab = 'detect' | 'procedure' | 'alert'; + +// ── 중국어 경고문 ── +const CN_WARNINGS: Record = { + PT: [ + { zh: '请立即停船接受检查', ko: '즉시 정선하여 검사를 받으시오', usage: 'VHF Ch.16 + 확성기' }, + { zh: '请出示捕捞许可证', ko: '어업허가증을 제시하시오', usage: '승선 검사 시' }, + { zh: '请出示作业日志', ko: '조업일지를 제시하시오', usage: '어획량 확인' }, + { zh: '你的网目不符合规定', ko: '망목이 규정에 미달합니다', usage: '어구 검사 (54mm 미만)' }, + ], + GN: [ + { zh: '请打开AIS', ko: 'AIS를 켜시오', usage: '다크베셀 대응' }, + { zh: '请立即停船接受检查', ko: '즉시 정선하여 검사를 받으시오', usage: 'VHF + 확성기' }, + { zh: '你在非许可区域作业', ko: '비허가 구역에서 조업 중입니다', usage: '수역 이탈 시' }, + { zh: '请立即收回渔网', ko: '어망을 즉시 회수하시오', usage: '불법 자망 발견' }, + ], + PS: [ + { zh: '所有船只立即停止作业', ko: '모든 선박 즉시 조업 중단', usage: '선단 제압 시' }, + { zh: '请立即停船接受检查', ko: '즉시 정선하여 검사를 받으시오', usage: 'VHF + 확성기' }, + { zh: '关闭集鱼灯', ko: '집어등을 끄시오', usage: '조명선 대응' }, + { zh: '不要试图逃跑', ko: '도주를 시도하지 마시오', usage: '도주 시' }, + ], + FC: [ + { zh: '请立即停船接受检查', ko: '즉시 정선하여 검사를 받으시오', usage: 'VHF + 확성기' }, + { zh: '请出示货物清单', ko: '화물 목록을 제시하시오', usage: '환적 검사' }, + { zh: '禁止转运渔获物', ko: '어획물 환적을 금지합니다', usage: '환적 현장' }, + ], + GEAR: [ + { zh: '这些渔具属于非法设置', ko: '이 어구는 불법 설치되었습니다', usage: '어구 수거 시' }, + ], + UNKNOWN: [ + { zh: '请立即停船接受检查', ko: '즉시 정선하여 검사를 받으시오', usage: '기본 경고' }, + { zh: '请打开AIS', ko: 'AIS를 켜시오', usage: '다크베셀' }, + ], +}; + +function estimateVesselType(ship: Ship): 'PT' | 'GN' | 'PS' | 'FC' | 'GEAR' | 'UNKNOWN' { + const cat = getMarineTrafficCategory(ship.typecode, ship.category); + const isGear = /[_]\d+[_]|%$/.test(ship.name); + if (isGear) return 'GEAR'; + if (cat === 'cargo' || (cat === 'unspecified' && (ship.length || 0) > 50)) return 'FC'; + if (cat !== 'fishing' && ship.category !== 'fishing' && ship.typecode !== '30') return 'UNKNOWN'; + const spd = ship.speed || 0; + if (spd >= 7) return 'PS'; + if (spd < 1.5) return 'GN'; + return 'PT'; +} export function OpsGuideModal({ ships, onClose, onFlyTo, onRouteSelect }: Props) { const [selectedKCG, setSelectedKCG] = useState(null); - const [searchRadius, setSearchRadius] = useState(30); // NM + const [searchRadius, setSearchRadius] = useState(30); const [pos, setPos] = useState({ x: 60, y: 60 }); + const [tab, setTab] = useState('detect'); + const [selectedSuspect, setSelectedSuspect] = useState(null); + const [copiedIdx, setCopiedIdx] = useState(null); const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null); const onDragStart = useCallback((e: React.MouseEvent) => { @@ -48,226 +99,276 @@ export function OpsGuideModal({ ships, onClose, onFlyTo, onRouteSelect }: Props) dragRef.current = { startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y }; const onMove = (ev: MouseEvent) => { if (!dragRef.current) return; - setPos({ - x: dragRef.current.origX + (ev.clientX - dragRef.current.startX), - y: dragRef.current.origY + (ev.clientY - dragRef.current.startY), - }); + setPos({ x: dragRef.current.origX + (ev.clientX - dragRef.current.startX), y: dragRef.current.origY + (ev.clientY - dragRef.current.startY) }); }; const onUp = () => { dragRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); }; window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp); }, [pos]); - // 해경서/지방청만 (파출소 제외) const kcgBases = useMemo(() => - COAST_GUARD_FACILITIES.filter(f => ['hq', 'regional', 'station', 'navy'].includes(f.type)) - .sort((a, b) => a.name.localeCompare(b.name)), + COAST_GUARD_FACILITIES.filter(f => ['hq', 'regional', 'station', 'navy'].includes(f.type)).sort((a, b) => a.name.localeCompare(b.name)), []); - // 선택된 해경 기지 주변 의심 선박 탐지 const suspects = useMemo(() => { if (!selectedKCG) return []; - const results: SuspectVessel[] = []; - for (const ship of ships) { if (ship.flag !== 'CN') continue; const dist = haversineNM(selectedKCG.lat, selectedKCG.lng, ship.lat, ship.lng); if (dist > searchRadius) continue; - const cat = getMarineTrafficCategory(ship.typecode, ship.category); const isFishing = cat === 'fishing' || ship.category === 'fishing' || ship.typecode === '30'; const isGear = /[_]\d+[_]|%$/.test(ship.name); - const analysis = isFishing ? analyzeFishing(ship) : null; const zone = classifyFishingZone(ship.lat, ship.lng); - const reasons: string[] = []; let riskLevel: 'CRITICAL' | 'HIGH' | 'MEDIUM' = 'MEDIUM'; - - // 수역 외 어선 - if (isFishing && zone.zone === 'OUTSIDE') { - reasons.push('비허가 수역 진입'); - riskLevel = 'CRITICAL'; - } - - // 수역 I에 PT/OT 선박 - if (isFishing && zone.zone === 'ZONE_I' && ship.speed >= 2 && ship.speed <= 5) { - reasons.push('수역I 저인망 의심 (PT/OT 비허가)'); - riskLevel = 'HIGH'; - } - - // 다크베셀 의심 (속도 0, 방향 0) - if (isFishing && ship.speed === 0 && (!ship.heading || ship.heading === 0)) { - reasons.push('AIS 비정상 (다크베셀 의심)'); - if (riskLevel === 'MEDIUM') riskLevel = 'HIGH'; - } - - // 조업 중 (2-6kn) - if (isFishing && ship.speed >= 2 && ship.speed <= 6) { - reasons.push(`조업 추정 (${ship.speed.toFixed(1)}kn)`); - } - - // 어구/어망 - if (isGear) { - reasons.push('어구/어망 AIS 신호'); - if (riskLevel === 'MEDIUM') riskLevel = 'HIGH'; - } - - // 대형 선박 (운반선 의심) - if (!isFishing && (cat === 'cargo' || cat === 'unspecified') && ship.speed < 3) { - reasons.push('운반선/환적 의심 (저속 대형)'); - } - - if (reasons.length > 0) { - results.push({ ship, distance: dist, reasons, riskLevel }); - } + if (isFishing && zone.zone === 'OUTSIDE') { reasons.push('비허가 수역 진입'); riskLevel = 'CRITICAL'; } + if (isFishing && zone.zone === 'ZONE_I' && ship.speed >= 2 && ship.speed <= 5) { reasons.push('수역I 저인망 의심'); riskLevel = 'HIGH'; } + if (isFishing && ship.speed === 0 && (!ship.heading || ship.heading === 0)) { reasons.push('다크베셀 의심'); if (riskLevel === 'MEDIUM') riskLevel = 'HIGH'; } + if (isFishing && ship.speed >= 2 && ship.speed <= 6) reasons.push(`조업 추정 (${ship.speed.toFixed(1)}kn)`); + if (isGear) { reasons.push('어구/어망 AIS'); if (riskLevel === 'MEDIUM') riskLevel = 'HIGH'; } + if (!isFishing && (cat === 'cargo' || cat === 'unspecified') && ship.speed < 3) reasons.push('운반선/환적 의심'); + if (reasons.length > 0) results.push({ ship, distance: dist, reasons, riskLevel, estimatedType: estimateVesselType(ship) }); } - - return results.sort((a, b) => { - const riskOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2 }; - return riskOrder[a.riskLevel] - riskOrder[b.riskLevel] || a.distance - b.distance; - }); + return results.sort((a, b) => ({ CRITICAL: 0, HIGH: 1, MEDIUM: 2 }[a.riskLevel] - { CRITICAL: 0, HIGH: 1, MEDIUM: 2 }[b.riskLevel]) || a.distance - b.distance); }, [selectedKCG, ships, searchRadius]); const criticalCount = suspects.filter(s => s.riskLevel === 'CRITICAL').length; const highCount = suspects.filter(s => s.riskLevel === 'HIGH').length; + const copyToClipboard = (text: string, idx: number) => { + navigator.clipboard.writeText(text).then(() => { setCopiedIdx(idx); setTimeout(() => setCopiedIdx(null), 1500); }); + }; + + const handleSuspectClick = (s: SuspectVessel) => { + setSelectedSuspect(s); + setTab('procedure'); + onFlyTo?.(s.ship.lat, s.ship.lng, 10); + if (selectedKCG) { + onRouteSelect?.({ from: { lat: selectedKCG.lat, lng: selectedKCG.lng, name: selectedKCG.name }, to: { lat: s.ship.lat, lng: s.ship.lng, name: s.ship.name || s.ship.mmsi, mmsi: s.ship.mmsi }, distanceNM: s.distance, riskLevel: s.riskLevel }); + } + }; + + const TYPE_LABEL: Record = { PT: '저인망(PT)', GN: '유자망(GN)', PS: '위망(PS)', FC: '운반선(FC)', GEAR: '어구/어망', UNKNOWN: '미분류' }; + return ( -
-
+
+ {/* Header */} +
+ + + 경비함정 작전 가이드 + +
- {/* Header — drag handle */} -
- - - 경비함정 작전 가이드 - 해경 기지 기준 주변 불법어선·어구 탐지 - -
+ {/* Tabs */} +
+ {([['detect', '🔍 실시간 탐지'], ['procedure', '📋 대응 절차'], ['alert', '🚨 조치 기준']] as [Tab, string][]).map(([k, l]) => ( + + ))} +
- {/* Controls */} -
- - { const f = kcgBases.find(b => b.id === Number(e.target.value)); setSelectedKCG(f || null); }} style={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 4, padding: '3px 8px', fontSize: 10, color: '#e2e8f0', minWidth: 160 }}> + + {kcgBases.map(b => )} - - - setSearchRadius(Number(e.target.value))} style={{ background: '#1e293b', border: '1px solid #334155', borderRadius: 4, padding: '3px 8px', fontSize: 10, color: '#e2e8f0' }}> + {[10, 20, 30, 50, 100].map(n => )} - - {selectedKCG && ( -
- 🔴 CRITICAL {criticalCount} - 🟡 HIGH {highCount} - 🔵 TOTAL {suspects.length} -
- )} + {selectedKCG &&
+ 🔴 {criticalCount} + 🟡 {highCount} + 🔵 {suspects.length} +
}
+ )} - {/* Content */} -
+ {/* Content */} +
+ + {/* ── TAB: 실시간 탐지 ── */} + {tab === 'detect' && (<> {!selectedKCG ? ( -
-
-
출동 기지를 선택하면 주변 불법어선·어구를 자동 탐지합니다
-
해경서/지방청/해군부대 기준 반경 내 중국 선박 분석
-
+
⚓ 출동 기지를 선택하면 주변 불법어선·어구를 자동 탐지합니다
) : suspects.length === 0 ? ( -
-
-
{selectedKCG.name} 반경 {searchRadius}NM 내 의심 선박 없음
-
+
✅ {selectedKCG.name} 반경 {searchRadius}NM 내 의심 선박 없음
) : ( -
- {/* Route summary */} -
-
- 📍 {selectedKCG.name} → 순찰 루트 제안 ({suspects.length}건) -
-
- 우선순위: CRITICAL → HIGH → MEDIUM | 거리순 정렬 | 가장 가까운 고위험 대상부터 순찰 -
- {criticalCount > 0 && ( -
- ⚠ CRITICAL {criticalCount}건 — 즉시 출동 권고 -
- )} -
- - {/* Suspect list */} +
{suspects.map((s, i) => ( -
{ - onFlyTo?.(s.ship.lat, s.ship.lng, 10); - if (selectedKCG) { - onRouteSelect?.({ - from: { lat: selectedKCG.lat, lng: selectedKCG.lng, name: selectedKCG.name }, - to: { lat: s.ship.lat, lng: s.ship.lng, name: s.ship.name || s.ship.mmsi, mmsi: s.ship.mmsi }, - distanceNM: s.distance, - riskLevel: s.riskLevel, - }); - } - }} - > -
- #{i + 1} - {RISK_ICON[s.riskLevel]} - {s.riskLevel} - {s.ship.name || s.ship.mmsi} - MMSI: {s.ship.mmsi} - {s.distance.toFixed(1)} NM +
handleSuspectClick(s)} style={{ + background: '#111827', borderRadius: 4, padding: '6px 10px', borderLeft: `3px solid ${RISK_COLOR[s.riskLevel]}`, cursor: 'pointer', + }}> +
+ #{i + 1} + {RISK_ICON[s.riskLevel]} + {s.riskLevel} + {s.ship.name || s.ship.mmsi} + [{TYPE_LABEL[s.estimatedType]}] + {s.distance.toFixed(1)} NM
-
- {s.reasons.map((r, j) => ( - {r} - ))} -
-
- SOG: {s.ship.speed?.toFixed(1) ?? '-'} kn - HDG: {s.ship.heading ?? '-'}° - {s.ship.lat.toFixed(4)}°N, {s.ship.lng.toFixed(4)}°E - {onFlyTo && 클릭 → 지도 이동} +
+ {s.reasons.map((r, j) => {r})}
))}
)} -
+ )} - {/* Footer */} -
- 한중어업협정 허가현황 (906척) 기반 자동 분석 | 수역 판정: Point-in-Polygon | 위험도: 속도·위치·AIS 패턴 종합 -
+ {/* ── TAB: 대응 절차 ── */} + {tab === 'procedure' && (<> + {selectedSuspect ? ( +
+ {/* 선박 정보 */} +
+
+ {RISK_ICON[selectedSuspect.riskLevel]} + {selectedSuspect.ship.name || selectedSuspect.ship.mmsi} + {selectedSuspect.riskLevel} + 추정: {TYPE_LABEL[selectedSuspect.estimatedType]} +
+
MMSI: {selectedSuspect.ship.mmsi} | SOG: {selectedSuspect.ship.speed?.toFixed(1)}kn | {selectedSuspect.distance.toFixed(1)} NM
+
+ + {/* 업종별 대응 절차 */} + + + {/* 중국어 경고문 */} +
+
📢 중국어 경고문 (클릭하여 복사)
+ {(CN_WARNINGS[selectedSuspect.estimatedType] || CN_WARNINGS.UNKNOWN).map((w, i) => ( +
copyToClipboard(w.zh, i)} style={{ + background: copiedIdx === i ? 'rgba(34,197,94,0.15)' : '#111827', + border: copiedIdx === i ? '1px solid rgba(34,197,94,0.4)' : '1px solid #1e293b', + borderRadius: 4, padding: '6px 10px', marginBottom: 4, cursor: 'pointer', transition: 'all 0.2s', + }}> +
{w.zh}
+
{w.ko}
+
사용: {w.usage} {copiedIdx === i && ✓ 복사됨}
+
+ ))} +
+
+ ) : ( +
+ 실시간 탐지 탭에서 의심 선박을 클릭하면
해당 업종별 대응 절차가 자동 표시됩니다 +
+ )} + )} + + {/* ── TAB: 조치 기준 ── */} + {tab === 'alert' && ()} +
+ + {/* Footer */} +
+ GC-KCG-2026-001 기반 | 허가현황 906척 | 수역: Point-in-Polygon | 중국어 경고문 클릭 시 클립보드 복사
); } + +// ── 업종별 대응 절차 컴포넌트 ── +const step: React.CSSProperties = { background: '#1e293b', borderRadius: 4, padding: '6px 10px', margin: '4px 0' }; +const stepN: React.CSSProperties = { display: 'inline-block', background: '#3b82f6', color: '#fff', borderRadius: 3, padding: '0 5px', fontSize: 8, fontWeight: 700, marginRight: 4 }; +const warn: React.CSSProperties = { background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 4, padding: '4px 8px', margin: '4px 0', fontSize: 9, color: '#fca5a5' }; + +function ProcedureSteps({ type }: { type: string }) { + switch (type) { + case 'PT': return (<> +
🔴 2척식 저인망 (PT) 대응 절차
+
⚠ 선미(船尾) 방향 접근 절대 금지 — 예인삭 스크루 감김 위험
+
1탐지/식별 — AIS MMSI → 허가DB 대조. 본선·부속선 쌍 확인, 이격거리 측정
+
2접근/경고 — 선수 45° 측면 접근. VHF Ch.16 경고 3회. 중국어 방송 병행
+
3승선 검사 — ①허가증(C21-xxxxx) ②조업일지(할당량 100톤/척) ③망목 실측(54mm)
+
4위반 판정 — 휴어기(4/16~10/15)→나포 | 할당초과→압수 | 부속선 분리→양선 나포
+
5나포/방면 — 위반: 목포·여수·제주·태안 입항. 경미: 경고 후 방면. 알람 기록 등록
+ ); + case 'GN': return (<> +
🟡 유자망 (GN) 대응 — 다크베셀 주의
+
⚠ 부표 위치 먼저 확인 → 그물 범위 외곽으로 접근 (스크루 감김 방지)
+
1다크베셀 탐지 — 레이더 탐색 + SAR 요청. 부표 다수 발견 → 1NM 이내 집중 수색
+
2그물 확인 후 접근 — 부표 배치방향 → 자망 연장선 추정 → 수직 90° 외곽 접근
+
3AIS 재가동 — "请打开AIS" 경고. 재개 확인 후 MMSI 기록. 거부 시 강제 임검
+
4승선 검사 — ①허가증(C25-xxxxx) ②수역확인(I발견→위반) ③어획량(28톤/척) ④망목·규모
+
5어구 판정 — 허가외 자망→수거/절단. 망목미달→전량압수. GPS·사진 기록
+ ); + case 'PS': return (<> +
🟣 위망 (PS) 선단 대응 — 선단 분산 주의
+
⚠ 단독 접근 금지 — 조명선 시야교란, 분산도주 전술 대비. 대형 함정 지원 후 동시 제압
+
1선단 확인/보고 — 원형궤적 + 고속→저속 패턴. 3척+ 클러스터. 즉시 상급 보고
+
2집어등 식별 — 야간 EO/육안. 조명선 MMSI 기록. 차단은 최후 단계
+
3선단 포위 — 모선·운반선·조명선 동시 포위. 서방(중국측) 탈주 차단 우선
+
4일제 임검 — 모선: C23-xxxxx, 1,500톤/척. 운반선/조명선: 0톤→적재 시 불법
+
5나포/증거 — 어획물·냉동설비 촬영. 宁波海裕 VHF 교신 확보. 목포·여수항 인계
+ ); + case 'FC': return (<> +
🟠 운반선 (FC) 환적 대응
+
1환적 알람 — FC+조업선 0.5NM + 양쪽 2kn + 30분 → HIGH. 좌표 즉시 이동
+
2증거 촬영 — 접현/고무보트 확인. 드론 항공촬영. MMSI·선명·접현 흔적 기록
+
3양선 임검 — 운반선: 화물·출발지·도착지. 조업선: 허가량 대비 실어획량
+
4증거/조치 — 사진·중량 확보. 필요시 전량 압수. 도주시 경고사격. 최근접 항구 입항
+ ); + case 'GEAR': return (<> +
🪤 불법 어구 수거 절차
+
⚠ 방치 자망 스크루 감김 주의 — 엔진 정지/저속 상태에서 수동 회수. 야간 수거 원칙적 연기
+
1발견/기록 — GPS(WGS84), 종류 추정, 사진, 소유자번호, 규모(길이·폭·그물코)
+
2중국어구 판단 — 중국어 부표, 광폭·장형 구조. 인근 중국어선 확인. 불가→항구 감식
+
3수거 실행 — RIB/크레인. 어획물→전량 압수. 절단 시 위치·잔존 기록
+
4수거 보고 — 감시 시스템 등록. 항구 감식·증거 보존. 반복 발견→집중 감시 지정
+ ); + default: return (
선박 유형을 식별할 수 없습니다. 기본 임검 절차를 적용하세요.
); + } +} + +function AlertTable() { + const rows = [ + { type: '미등록 선박', criteria: 'MMSI 허가DB 미등록', action: '즉시 정선·나포', level: 'CRITICAL', note: '허가증 불소지 추가 확인' }, + { type: '휴어기 조업', criteria: 'C21·C22: 4/16~10/15\nC25: 6/2~8/31', action: '즉시 나포', level: 'CRITICAL', note: '날짜 자동 판별' }, + { type: '허가 수역 이탈', criteria: '비허가 수역 진입', action: '경고 후 나포', level: 'HIGH', note: 'PT: I·IV이탈 GN: I이탈' }, + { type: 'PT 부속선 분리', criteria: '본선 이격 3NM+', action: '양선 동시 나포', level: 'HIGH→CRIT', note: '311쌍 실시간 모니터링' }, + { type: '환적 현장 포착', criteria: 'FC+조업선 0.5NM+2kn+30분', action: '촬영 후 양선 나포', level: 'HIGH', note: '증거 촬영 최우선' }, + { type: '불법 어구 발견', criteria: '표지 없음/미허가', action: '즉시 수거·기록', level: '자체판단', note: 'GPS 등록, 반복 요주의' }, + { type: '할당량 초과', criteria: '80~100%+ 초과', action: '계량·초과 시 압수', level: 'CRITICAL', note: 'GN 28톤 현장 계량' }, + { type: '다크베셀', criteria: 'AIS 공백 6시간+', action: '접근·임검', level: 'HIGH', note: 'SAR 교차 확인' }, + ]; + const lc = (l: string) => l.includes('CRIT') ? '#ef4444' : l === 'HIGH' ? '#f59e0b' : '#64748b'; + return ( +
+
🚨 단속 상황별 조치 기준
+ + + + + + {rows.map((r, i) => ( + + + ))} + +
위반 유형판정 기준즉시 조치알람비고
{r.type}{r.criteria}{r.action}{r.level}{r.note}
+
📅 감시 강화 시기
+ + + + + + + + +
시기상황대응
7~8월PS 16척만 허가C21·C22·C25 전원 비허가
5월GN만 허가저인망(C21·C22) 즉시 위반
4월·10월기간 경계4/16, 10/16 집중 모니터링
1~3월전 업종 가능수역이탈·할당초과 중심
+
+ ); +} + +const th: React.CSSProperties = { border: '1px solid #1e293b', padding: '3px 6px', textAlign: 'left', color: '#e2e8f0', fontSize: 8, fontWeight: 700 }; +const td: React.CSSProperties = { border: '1px solid #1e293b', padding: '2px 6px', fontSize: 9 };