docs/prediction-analysis.md §7 P1 권고의 "UI 미노출 탐지" 해소 중 두 번째.
prediction algorithms/transshipment.py 5단계 필터 파이프라인 결과를 전체 목록·
집계·상세 수준으로 조회하는 READ 전용 대시보드.
### 배경
기존 features/vessel/TransferDetection.tsx 는 선박 상세 수준(특정 MMSI 의 환적
이력)이고, 환적 의심 선박 전체 목록을 보려면 ChinaFishing 의 탭 중 하나를 거쳐야
했다. /api/analysis/transship 엔드포인트는 이미 존재하나 전용 페이지가 없었음.
### 변경
- frontend/src/features/detection/TransshipmentDetection.tsx 신설 (405 라인)
- PageContainer + PageHeader(ArrowLeftRight) + KPI 5장
(Total / Transship tier CRITICAL/HIGH/MEDIUM / Risk CRITICAL)
- DataTable 8컬럼 (analyzedAt / mmsi / pairMmsi / duration / tier / risk / zone)
- features.transship_tier 읽어 Badge 로 심각도 표시
- 필터: hours(1/6/12/24/48) / riskLevel / mmsi 검색
- 상세 패널: 분석 피처 JSON 원본 + 좌표 + transship_score
- 기존 analysisApi.getTransshipSuspects 재사용 — backend 변경 없음
- index.ts + componentRegistry.ts 등록
- detection.json (ko/en) transshipment.* 네임스페이스 추가 (각 44키)
- common.json (ko/en) nav.transshipment 추가
- V033__menu_transshipment_detection.sql
- auth_perm_tree(detection:transshipment, nav_sort=910)
- ADMIN 5 ops + OPERATOR/ANALYST/FIELD/VIEWER READ
### 권한 주의
/api/analysis/transship 의 @RequirePermission 은 현재 detection:dark-vessel.
이 메뉴 READ 만으로는 API 호출 불가. 현행 운영자 역할(OPERATOR/ANALYST/FIELD)
은 dark-vessel READ 도 보유하므로 실용 동작.
향후 VesselAnalysisController.listTransshipSuspects 의 @RequirePermission 을
detection:transshipment 로 교체하는 권한 일관화는 별도 MR (후속).
### 검증
- npx tsc --noEmit 통과
- pre-commit tsc + ESLint 통과 예정
- Flyway V033 자동 적용 (백엔드 재배포 필요)
gear_group_parent_candidate_snapshots.candidate_source 의 VARCHAR(30) 제약
때문에 prediction gear_correlation 스테이지가 매 사이클 실패하던 문제 해소.
원인:
- prediction/algorithms/gear_parent_inference.py:875 의
candidate_source = ','.join(sorted(meta['sources']))
가 복수 source 라벨 (CORRELATION/EPISODE/LABEL/LINEAGE/MATCH) 을 쉼표 join
하며 최대 약 39자. VARCHAR(30) 초과 시 psycopg2.errors.StringDataRightTruncation
을 유발해 _insert_candidate_snapshots 전체 ROLLBACK.
발견 경위:
- Phase 0-1 (PR #83) 의 stage_runner + logger.exception 전환 후 journal 에
찍힌 풀 스택트레이스로 드러남. 기존에는 logger.warning 한 줄 ("gear
correlation failed: ...") 만 남아 원인 특정 불가.
영향 범위:
- 백엔드 JPA 엔티티 미참조 → 재빌드·재배포 불필요
- Flyway 자동 적용 (백엔드 기동 시)
- prediction 재기동만 필요 (기존 코드 그대로, 이제 INSERT 성공 기대)
검증:
- 재배포 후 journalctl 에서 'gear correlation failed' 로그 사라짐 확인
- kcg.gear_group_parent_candidate_snapshots 에 최근 15분 건수 증가 확인
docs/prediction-analysis.md P1 권고 반영. 5분 사이클의 각 스테이지를
한 try/except 로 뭉친 기존 구조를 스테이지 단위로 분리해 실패 지점을
명시적으로 특정하고 부분 실패 시에도 후속 스테이지가 계속 돌아가도록 개선.
- prediction/pipeline/stage_runner.py 신설
- run_stage(name, fn, *args, required=False, **kwargs) 유틸
- required=True 면 예외 re-raise (상위 사이클 try/except 가 잡도록)
- required=False 면 logger.exception 으로 stacktrace 보존 + None 반환
- 지속시간 로깅 포함
- prediction/scheduler.py run_analysis_cycle() 수정
- 출력 단계 6모듈을 각각 run_stage() 로 분리:
violation_classifier / event_generator / kpi_writer /
stats_aggregate_hourly / stats_aggregate_daily / alert_dispatcher
- upsert_results / cleanup_old 도 run_stage 로 래핑 (upsert 는 required=True)
- 내부 try/except 의 logger.warning → logger.exception 으로 업그레이드
(fetch_dark_history, gear collision event promotion, group polygon,
gear correlation, pair detection, chat cache)
- 스테이지 실패 시 journalctl -u kcg-ai-prediction 에서 stacktrace 로
원인 바로 특정 가능 (기존은 "failed: X" 한 줄만 남아 디버깅 불가)
검증:
- python3 -c "import ast; ast.parse(...)" scheduler.py / stage_runner.py 통과
- run_stage smoke test (정상/실패 흡수/required 재raise 3가지) 통과
범위 밖 (후속):
- Phase 0-2 ILLEGAL_FISHING_PATTERN 전용 페이지 (다음 MR)
- Phase 0-3 Transshipment 전용 페이지 (다음 MR)
- docs/prediction-analysis.md 신설 — opus 4.7 독립 리뷰 기반 prediction 구조/방향 심층 분석
(9개 섹션: 아키텍처·5분 사이클·17 알고리즘·4대 도메인 커버리지·6축 구조 평가·개선 제안 P1~P4·임계값 전수표)
- AGENTS.md / README.md — V001~V016→V030, Python 3.9→3.11+, 14→17 알고리즘 모듈
- docs/architecture.md — /gear-collision 라우트 추가 (26→27 보호 경로)
- docs/sfr-traceability.md — V029→V030, 48→51 테이블, SFR-10 에 GEAR_IDENTITY_COLLISION 추가
- docs/sfr-user-guide.md — 어구 정체성 충돌 페이지 섹션 신설
- docs/system-flow-guide.md — 노드 수 102→115, V030 manifest 미반영 경고
- backend/README.md — "Phase 2 예정" 상태 → 실제 운영 구성 + PR #79 hotfix 요구사항 전면 재작성
증상: rocky-211 의 kcg-ai-backend 가 `No qualifying bean of type RestClient,
but 2 were found: predictionRestClient, signalBatchRestClient` 로 기동 실패 반복.
PR #A 의 RestClientConfig 도입 이후 잠복해 있던 문제로, PredictionProxyController /
VesselAnalysisProxyController 의 필드 @Qualifier 가 Lombok `@RequiredArgsConstructor`
가 만든 constructor parameter 로 복사되지 않아 Spring 6.1 의 bean 이름 fallback 이
실패한 것.
- backend/pom.xml — default-compile / default-testCompile 의 configuration 에
`<parameters>true</parameters>` 추가. spring-boot-starter-parent 기본값을 executions
override 과정에서 덮어쓰지 않도록 명시.
- backend/src/main/java/lombok.config — `lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier`.
Lombok 이 필드의 @Qualifier 를 생성된 constructor parameter 로 복사해야 Spring 이
파라미터 레벨 annotation 으로 해당 bean 을 식별할 수 있음.
검증: javap 로 PredictionProxyController 생성자의 RuntimeVisibleParameterAnnotations 에
@Qualifier("predictionRestClient") 가 실제 복사되었는지 확인, 재빌드/재배포 후 rocky-211
기동 성공("Started KcgAiApplication in 7.333 seconds") + Tomcat 18080 정상 리스닝.
iran 백엔드 프록시 잔재 제거:
- IranBackendClient dead class 삭제, AppProperties/application.yml iran-backend 블록 제거
- Frontend UI 라벨/주석/system-flow manifest deprecated 마킹
- CLAUDE.md 시스템 구성 다이어그램 최신화
백엔드 계층 분리:
- AlertController/MasterDataController/AdminStatsController 에서 repository/JdbcTemplate 직접 주입 제거
- AlertService/MasterDataService/AdminStatsService 신규 계층 도입 + @Transactional(readOnly=true)
- Proxy controller 의 @PostConstruct RestClient 생성 → RestClientConfig @Bean 으로 통합
감사 로그 보강:
- EnforcementService createRecord/updateRecord/createPlan 에 @Auditable 추가
- VesselAnalysisGroupService.resolveParent 에 PARENT_RESOLVE 액션 기록
카탈로그 정합성:
- performanceStatus 를 catalogRegistry 에 등록 (쇼케이스 자동 노출)
- alertLevels 확장: isValidAlertLevel / isHighSeverity / getAlertLevelOrder
- LiveMapView/DarkVesselDetection 시각 매핑(opacity/radius/tier score) 상수로 추출
- GearIdentification/vesselAnomaly 직접 분기를 타입 가드/헬퍼로 치환
vessel_type 카탈로그
- shared/constants/vesselTypes.ts 신규 — TRAWL/PURSE/GILLNET/LONGLINE/
TRAP/CARGO/UNKNOWN 7종 + getVesselTypeLabel / getVesselTypeIntent
헬퍼. 기존 alertLevels 카탈로그 패턴 답습
- catalogRegistry 에 VESSEL_TYPES 등록 — design-system 쇼케이스에 자동
노출
RealVesselAnalysis 필터 props 확장
- Props 에 mmsiPrefix / minRiskScore / size 추가 (all·spoofing mode)
- 선박 유형 컬럼을 한글 라벨로 렌더
- RealAllVessels 편의 export 를 mmsiPrefix='412' 로 고정 + 제목을
'중국 선박 전체 분석 결과 (실시간)' 로 변경
효과
- Tab 1 상단 그리드가 중국 선박만 표시해 페이지 성격과 일치
- 선박 유형 '저인망/선망/유자망/연승/통발/운반선/미분류' 한글 표시
- 55점 HIGH 같은 중국 선박이 상단/하단 양쪽에 일관되게 노출