- EventList 인라인 액션 버튼 4종 추가 (확인/선박상세/단속등록/오탐)
- 확인(ACK): NEW 상태 이벤트만 활성, ackEvent API 연동
- 선박 상세: /vessel/{mmsi} 네비게이션
- 단속 등록: createEnforcementRecord API → 이벤트 RESOLVED 자동 전환
- 오탐 처리: updateEventStatus(FALSE_POSITIVE) 연동
- MMSI → VesselDetail 링크 3개 화면 적용
- EventList: MMSI 컬럼 클릭 → /vessel/{mmsi}
- DarkVesselDetection: MMSI 컬럼 클릭 → /vessel/{mmsi}
- EnforcementHistory: 대상 선박 컬럼 클릭 → /vessel/{mmsi}
- PredictionEvent 타입에 features 필드 추가 (dark_tier, transship_score 등)
- analysisApi.ts 서비스 신규 생성 (직접 조회 API 5개 연동)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
/**
|
|
* 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<string, unknown> | 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<AnalysisPageResponse> {
|
|
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<VesselAnalysis> {
|
|
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<VesselAnalysis[]> {
|
|
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<AnalysisPageResponse> {
|
|
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<AnalysisPageResponse> {
|
|
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();
|
|
}
|