/** * vessel_analysis_results 직접 조회 API 서비스. * 백엔드 /api/analysis/* 엔드포인트 연동. */ const API_BASE = import.meta.env.VITE_API_URL ?? '/api'; export interface VesselAnalysis { id: number; mmsi: string; analyzedAt: string; vesselType: string | null; confidence: number | null; fishingPct: number | null; season: string | null; lat: number | null; lon: number | null; zoneCode: string | null; distToBaselineNm: number | null; activityState: string | null; isDark: boolean | null; gapDurationMin: number | null; darkPattern: string | null; spoofingScore: number | null; speedJumpCount: number | null; transshipSuspect: boolean | null; transshipPairMmsi: string | null; transshipDurationMin: number | null; fleetClusterId: number | null; fleetRole: string | null; fleetIsLeader: boolean | null; riskScore: number | null; riskLevel: string | null; gearCode: string | null; gearJudgment: string | null; permitStatus: string | null; features: Record | null; } export interface AnalysisPageResponse { content: VesselAnalysis[]; totalElements: number; totalPages: number; number: number; size: number; } /** 분석 결과 목록 조회 */ export async function getAnalysisVessels(params?: { mmsi?: string; zoneCode?: string; riskLevel?: string; isDark?: boolean; hours?: number; page?: number; size?: number; }): Promise { const query = new URLSearchParams(); if (params?.mmsi) query.set('mmsi', params.mmsi); if (params?.zoneCode) query.set('zoneCode', params.zoneCode); if (params?.riskLevel) query.set('riskLevel', params.riskLevel); if (params?.isDark != null) query.set('isDark', String(params.isDark)); query.set('hours', String(params?.hours ?? 1)); query.set('page', String(params?.page ?? 0)); query.set('size', String(params?.size ?? 50)); const res = await fetch(`${API_BASE}/analysis/vessels?${query}`, { credentials: 'include' }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } /** 특정 선박 최신 분석 결과 */ export async function getAnalysisLatest(mmsi: string): Promise { const res = await fetch(`${API_BASE}/analysis/vessels/${mmsi}`, { credentials: 'include' }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } /** 특정 선박 분석 이력 */ export async function getAnalysisHistory(mmsi: string, hours = 24): Promise { const res = await fetch(`${API_BASE}/analysis/vessels/${mmsi}/history?hours=${hours}`, { credentials: 'include' }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } /** 다크 베셀 목록 */ export async function getDarkVessels(params?: { hours?: number; page?: number; size?: number; }): Promise { const query = new URLSearchParams(); query.set('hours', String(params?.hours ?? 1)); query.set('page', String(params?.page ?? 0)); query.set('size', String(params?.size ?? 50)); const res = await fetch(`${API_BASE}/analysis/dark?${query}`, { credentials: 'include' }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } /** 환적 의심 목록 */ export async function getTransshipSuspects(params?: { hours?: number; page?: number; size?: number; }): Promise { const query = new URLSearchParams(); query.set('hours', String(params?.hours ?? 1)); query.set('page', String(params?.page ?? 0)); query.set('size', String(params?.size ?? 50)); const res = await fetch(`${API_BASE}/analysis/transship?${query}`, { credentials: 'include' }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); }