From 98c81cd54889b015772d483d98a2faf7bd8dd9c9 Mon Sep 17 00:00:00 2001 From: htlee Date: Wed, 25 Mar 2026 10:44:28 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=ED=98=84=EC=9E=A5=EB=B6=84?= =?UTF-8?q?=EC=84=9D/=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EB=8D=94=EB=AF=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=8B=A4=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AI 파이프라인 PROC 순환 애니메이션 → analysisMap 기반 ON/OFF 상태 - BD-09 STANDBY → bd09OffsetM 실측 탐지 수 표시 - 보고서 수역별 허가업종: ZONE_ALLOWED 상수 동적 참조 - 건의사항: 월/최대 어구 선단 실데이터 연동 - 보고서 버튼: 헤더 → 현장분석 내부로 이동 --- .../components/korea/FieldAnalysisModal.tsx | 61 +++++++++++++------ .../src/components/korea/KoreaDashboard.tsx | 14 +++-- frontend/src/components/korea/ReportModal.tsx | 56 ++++++++++++++--- frontend/src/utils/fishingAnalysis.ts | 2 +- 4 files changed, 98 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/korea/FieldAnalysisModal.tsx b/frontend/src/components/korea/FieldAnalysisModal.tsx index d2eb546..fdf0abd 100644 --- a/frontend/src/components/korea/FieldAnalysisModal.tsx +++ b/frontend/src/components/korea/FieldAnalysisModal.tsx @@ -110,6 +110,7 @@ interface Props { ships: Ship[]; vesselAnalysis?: UseVesselAnalysisResult; onClose: () => void; + onShowReport?: () => void; } const PIPE_STEPS = [ @@ -124,14 +125,14 @@ const PIPE_STEPS = [ const ALERT_ORDER: Record = { CRITICAL: 0, WATCH: 1, MONITOR: 2, NORMAL: 3 }; -export function FieldAnalysisModal({ ships, vesselAnalysis, onClose }: Props) { +export function FieldAnalysisModal({ ships, vesselAnalysis, onClose, onShowReport }: Props) { const emptyMap = useMemo(() => new Map(), []); const analysisMap = vesselAnalysis?.analysisMap ?? emptyMap; const [activeFilter, setActiveFilter] = useState('ALL'); const [search, setSearch] = useState(''); const [selectedMmsi, setSelectedMmsi] = useState(null); const [logs, setLogs] = useState([]); - const [pipeStep, setPipeStep] = useState(0); + // pipeStep 제거 — 파이프라인 상태는 analysisMap 존재 여부로 판단 const [tick, setTick] = useState(0); // 중국 어선만 필터 @@ -189,9 +190,13 @@ export function FieldAnalysisModal({ ships, vesselAnalysis, onClose }: Props) { // 통계 — Python 분석 결과 기반 const stats = useMemo(() => { let gpsAnomaly = 0; + let bd09Detected = 0; for (const v of processed) { const dto = analysisMap.get(v.ship.mmsi); - if (dto && dto.algorithms.gpsSpoofing.spoofingScore > 0.5) gpsAnomaly++; + if (dto) { + if (dto.algorithms.gpsSpoofing.spoofingScore > 0.5) gpsAnomaly++; + if (dto.algorithms.gpsSpoofing.bd09OffsetM > 100) bd09Detected++; + } } return { total: processed.length, @@ -199,6 +204,7 @@ export function FieldAnalysisModal({ ships, vesselAnalysis, onClose }: Props) { fishing: processed.filter(v => v.state === 'FISHING').length, aisLoss: processed.filter(v => v.state === 'AIS_LOSS' || v.state === 'UNKNOWN').length, gpsAnomaly, + bd09Detected, clusters: new Set(processed.filter(v => v.cluster !== '—').map(v => v.cluster)).size, trawl: processed.filter(v => v.vtype === 'TRAWL').length, purse: processed.filter(v => v.vtype === 'PURSE').length, @@ -231,12 +237,6 @@ export function FieldAnalysisModal({ ships, vesselAnalysis, onClose }: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // AI 파이프라인 애니메이션 - useEffect(() => { - const t = setInterval(() => setPipeStep(s => s + 1), 1200); - return () => clearInterval(t); - }, []); - // 시계 tick useEffect(() => { const t = setInterval(() => setTick(s => s + 1), 1000); @@ -349,6 +349,19 @@ export function FieldAnalysisModal({ ships, vesselAnalysis, onClose }: Props) { LIVE {new Date().toLocaleTimeString('ko-KR')} + {onShowReport && ( + + )} -