동일 어구 이름이 서로 다른 MMSI 로 같은 5분 사이클에 동시 AIS 송출되는 공존 케이스를 신규 탐지 패턴으로 분리해 기록·분류한다. 부수 효과로 fleet_tracker.track_gear_identity 의 PK 충돌로 인한 사이클 실패도 해소. Prediction - algorithms/gear_identity.py: detect_gear_name_collisions + classify_severity - fleet_tracker.py: 공존/교체 분기 분리, UPSERT helper, savepoint 점수 이전 - output/event_generator.py: run_gear_identity_collision_events 추가 - scheduler.py: track_gear_identity 직후 이벤트 승격 호출 Backend (domain/analysis) - GearIdentityCollision 엔티티 + Repository(Specification+stats) - GearIdentityCollisionService (@Transactional readOnly / @Auditable resolve) - GearCollisionController /api/analysis/gear-collisions (list/stats/detail/resolve) - GearCollisionResponse / StatsResponse / ResolveRequest (record) DB - V030__gear_identity_collision.sql: gear_identity_collisions 테이블 + auth_perm_tree 엔트리(detection:gear-collision nav_sort=950) + 역할별 권한 Frontend - shared/constants/gearCollisionStatuses.ts + catalogRegistry 등록 - services/gearCollisionApi.ts (list/stats/get/resolve) - features/detection/GearCollisionDetection.tsx (PageContainer+Section+DataTable + 분류 액션 폼, design system SSOT 준수) - componentRegistry + feature index + i18n detection.json / common.json(ko/en)
75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
/**
|
|
* 어구 정체성 충돌(GEAR_IDENTITY_COLLISION) 운영자 분류 상태 카탈로그
|
|
*
|
|
* SSOT: backend GearIdentityCollision.status 컬럼 (V030 마이그레이션)
|
|
* 사용처: GearCollisionDetection 페이지 필터/테이블 Badge
|
|
*/
|
|
|
|
import type { BadgeIntent } from '@lib/theme/variants';
|
|
|
|
export type GearCollisionStatus =
|
|
| 'OPEN' // 신규 탐지 (미검토)
|
|
| 'REVIEWED' // 검토됨 (확정 보류)
|
|
| 'CONFIRMED_ILLEGAL' // 불법 확정
|
|
| 'FALSE_POSITIVE'; // 오탐 처리
|
|
|
|
export interface GearCollisionStatusMeta {
|
|
code: GearCollisionStatus;
|
|
i18nKey: string;
|
|
fallback: { ko: string; en: string };
|
|
intent: BadgeIntent;
|
|
}
|
|
|
|
export const GEAR_COLLISION_STATUSES: Record<GearCollisionStatus, GearCollisionStatusMeta> = {
|
|
OPEN: {
|
|
code: 'OPEN',
|
|
i18nKey: 'gearCollision.status.open',
|
|
fallback: { ko: '미검토', en: 'Open' },
|
|
intent: 'warning',
|
|
},
|
|
REVIEWED: {
|
|
code: 'REVIEWED',
|
|
i18nKey: 'gearCollision.status.reviewed',
|
|
fallback: { ko: '검토됨', en: 'Reviewed' },
|
|
intent: 'info',
|
|
},
|
|
CONFIRMED_ILLEGAL: {
|
|
code: 'CONFIRMED_ILLEGAL',
|
|
i18nKey: 'gearCollision.status.confirmedIllegal',
|
|
fallback: { ko: '불법 확정', en: 'Confirmed Illegal' },
|
|
intent: 'critical',
|
|
},
|
|
FALSE_POSITIVE: {
|
|
code: 'FALSE_POSITIVE',
|
|
i18nKey: 'gearCollision.status.falsePositive',
|
|
fallback: { ko: '오탐', en: 'False Positive' },
|
|
intent: 'muted',
|
|
},
|
|
};
|
|
|
|
export function getGearCollisionStatusMeta(s: string): GearCollisionStatusMeta | undefined {
|
|
return GEAR_COLLISION_STATUSES[s as GearCollisionStatus];
|
|
}
|
|
|
|
export function getGearCollisionStatusIntent(s: string): BadgeIntent {
|
|
return getGearCollisionStatusMeta(s)?.intent ?? 'muted';
|
|
}
|
|
|
|
export function getGearCollisionStatusLabel(
|
|
s: string,
|
|
t: (key: string, opts?: Record<string, unknown>) => string,
|
|
lang: 'ko' | 'en' = 'ko',
|
|
): string {
|
|
const meta = getGearCollisionStatusMeta(s);
|
|
if (!meta) return s;
|
|
const translated = t(meta.i18nKey, { defaultValue: '' });
|
|
return translated || meta.fallback[lang];
|
|
}
|
|
|
|
export const GEAR_COLLISION_STATUS_ORDER: GearCollisionStatus[] = [
|
|
'OPEN',
|
|
'REVIEWED',
|
|
'CONFIRMED_ILLEGAL',
|
|
'FALSE_POSITIVE',
|
|
];
|