kcg-ai-monitoring/frontend/src/services/analysisApi.ts
htlee 0679c04bfe feat(frontend): 워크플로우 연결 Step 2 — EventList 워크플로우 + MMSI 링크
- 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>
2026-04-09 10:50:31 +09:00

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();
}