kcg-ai-monitoring/frontend/src/shared/constants/gearCollisionStatuses.ts
htlee a4e29629fc feat(detection): GEAR_IDENTITY_COLLISION 탐지 패턴 추가
동일 어구 이름이 서로 다른 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)
2026-04-17 06:53:12 +09:00

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',
];