Codex Lab 환경(iran-airstrike-replay-codex)에서 검증 완료된 어구 모선 자동 추론 + 검토 워크플로우 전체를 이식. ## Python (prediction/) - gear_parent_inference(1,428줄): 다층 점수 모델 (correlation + name + track + prior bonus) - gear_parent_episode(631줄): Episode 연속성 (Jaccard + 공간거리) - gear_name_rules: 모선 이름 정규화 + 4자 미만 필터 - scheduler: 추론 호출 단계 추가 (4.8) - fleet_tracker/kcgdb: SQL qualified_table() 동적화 - gear_correlation: timestamp 필드 추가 ## DB (database/migration/ 012~015) - 후보 스냅샷, resolution, episode, 라벨 세션, 제외 관리 테이블 9개 + VIEW 2개 ## Backend (Java) - 12개 DTO/Controller (ParentInferenceWorkflowController 등) - GroupPolygonService: parent_resolution LEFT JOIN + 15개 API 메서드 ## Frontend - ParentReviewPanel: 모선 검토 대시보드 - vesselAnalysis: 10개 신규 API 함수 + 6개 타입 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
21 KiB
Gear Parent Inference Workflow V2
문서 목적
이 문서는 lab 환경의 어구 모선 추적 워크플로우를 v1 운영 override 중심 구조에서,
평가 데이터 축적 + 후보 제외 관리 + 기간형 정답 라벨 추적 중심 구조로 재정의하는 설계서다.
대상 범위는 아래와 같다.
kcg_lab스키마backend-lab(192.168.1.20:18083)prediction-lab(192.168.1.18:18091)- 로컬 프론트
yarn dev:lab
운영 kcg 스키마와 기존 데모 동작은 이번 설계 단계에서 변경하지 않는다.
현재 구현 기준으로는 v2 Phase 1 저장소/API가 이미 lab에 반영되어 있고, 그 위에 015_gear_parent_episode_tracking.sql과 prediction/algorithms/gear_parent_episode.py를 통해 episode continuity + prior bonus 계층이 추가되었다. 이 문서는 여전히 워크플로우 설계서지만, 사람 판단 저장소와 자동 추론 저장소 분리 원칙은 현재 코드의 실제 기준이기도 하다.
배경
현재 v1은 자동 추론 결과와 사람 판단이 같은 저장소에 섞여 있다.
확정은gear_group_parent_resolution을MANUAL_CONFIRMED로 덮어쓴다.24시간 제외는 특정 그룹에서 후보 1개를 24시간 숨긴다.- 자동 추론은 계속 돌지만, 수동 판단이 최종 상태를 override한다.
이 구조는 단기 운용에는 편하지만, 아래 목적에는 맞지 않는다.
- 사람이 보면서 모델 가중치와 후보 생성 품질을 평가
- 정답/오답 사례를 데이터셋으로 축적
- 충분한 정확도 확보 후 자동화 또는 LLM 연결
따라서 v2에서는 자동 추론, 사람 라벨, 후보 제외를 분리한다.
핵심 목표
- 자동 추론 상태는 계속 독립적으로 유지한다.
- 사람 판단은 override가 아니라 별도 라벨/제외 데이터로 저장한다.
- 그룹 단위 오답 라벨은
1일 / 3일 / 5일기간형 후보 제외로 관리한다. - 전역 후보 제외는 모든 어구 그룹에서 동일 MMSI를 후보군에서 제거한다.
- 정답 라벨은
1일 / 3일 / 5일세션으로 만들고, 활성 기간 동안 자동 추론 결과를 별도 추적 로그로 남긴다. - 알고리즘은 DB exclusion/label 정보를 읽어 다음 cycle부터 바로 반영한다.
- 향후 threshold 튜닝, 가산점 실험, LLM 연결 평가에 쓰일 수 있는 정량 지표를 만든다.
용어
- 자동 추론
gear_parent_inference가 계산한 현재 cycle의 후보 점수와 추천 결과
- 그룹 제외
- 특정
group_key + sub_cluster_id에서 특정 후보 MMSI를 일정 기간 후보군에서 제거
- 특정
- 전역 후보 제외
- 특정 MMSI를 모든 어구 그룹의 모선 후보군에서 제거
- 정답 라벨 세션
- 특정 어구 그룹에 대해 “이 MMSI가 정답 모선”이라고 사람이 지정하고, 일정 기간 자동 추론 결과를 추적하는 세션
- 라벨 추적
- 정답 라벨 세션 활성 기간 동안 자동 추론이 정답 후보를 어떻게 rank/score하는지 누적 저장하는 기록
현재 v1의 한계
1. 확정이 평가 라벨이 아니라 운영 override다
- 현재
CONFIRM은 resolution을MANUAL_CONFIRMED로 덮어쓴다. - 이 경우 자동 추론의 실제 성능과 사람 판단이 섞여, 나중에 모델 정확도를 평가하기 어렵다.
2. 24시간 제외는 기간과 범위가 너무 좁다
- 현재는 그룹 단위 24시간 mute만 가능하다.
1/3/5일처럼 길이를 다르게 두고 비교할 수 없다.- “이 MMSI는 아예 모선 후보 대상이 아니다”라는 전역 규칙을 넣을 수 없다.
3. 백데이터 축적 구조가 없다
- 현재는 review log는 남지만, “정답 후보가 cycle별로 몇 위였는지”, “점수가 어떻게 변했는지”, “후보군에 들어왔는지”를 체계적으로 저장하지 않는다.
4. 장기 세션에 대한 그룹 스코프가 약하다
- 현재 그룹 기준은
group_key + sub_cluster_id다. - 기간형 라벨/제외를 도입하면 subcluster 재편성 리스크를 고려해야 한다.
v2 설계 원칙
1. 자동 추론 저장소는 그대로 유지한다
아래 기존 저장소는 계속 자동 추론 전용으로 유지한다.
gear_group_parent_candidate_snapshotsgear_group_parent_resolutiongear_group_parent_review_log
단, review_log의 의미는 “UI action audit”로 바꾸고, 더 이상 최종 라벨 저장소로 보지 않는다.
2. 사람 판단은 새 저장소로 분리한다
사람이 내린 판단은 아래 두 축으로 분리한다.
- 제외 축
- 이 그룹에서 제외
- 전체 후보 제외
- 정답 축
- 기간형 정답 라벨 세션
3. 제외는 후보 생성 이후의 gating layer로 둔다
전역 후보 제외는 raw correlation이나 원시 선박 분류를 지우지 않는다.
gear_correlation_scores는 계속 쌓는다.- exclusion은 parent inference candidate set에서만 hard filter로 적용한다.
이렇게 해야 원시 모델 출력과 사람 개입의 차이를 비교할 수 있다.
4. 라벨 세션 동안 자동 추론은 계속 돈다
정답 라벨 세션이 활성화되어도 자동 추론은 그대로 수행한다.
- UI의 기본 검토 대기에서는 숨길 수 있다.
- 하지만 prediction은 계속 candidate snapshot과 tracking record를 남긴다.
5. lab에서는 override보다 평가를 우선한다
v2 이후 lab에서 사람 버튼은 기본적으로 자동 resolution을 덮어쓰지 않는다.
- 운영 override가 필요해지면 추후 별도 action으로 분리한다.
- lab의 기본 목적은 평가 데이터 생성이다.
사용자 액션 재정의
정답 라벨
의미:
- 해당 어구 그룹의 정답 모선으로 특정 MMSI를 지정
1일 / 3일 / 5일중 하나의 기간 동안 자동 추론 결과를 추적
동작:
gear_parent_label_sessions에 active session 생성- 다음 cycle부터 prediction이 이 그룹에 대한 추적 로그를
gear_parent_label_tracking_cycles에 누적 - 기본 review queue에서는 해당 그룹을 숨기고, 별도
라벨 추적목록으로 이동 - 세션 종료 후에는 completed label dataset으로 남음
중요:
- 자동 resolution은 계속 자동 상태를 유지
- 점수에 수동 가산점/감점은 넣지 않음
이 그룹에서 제외
의미:
- 해당 어구 그룹에서만 특정 후보 MMSI를 일정 기간 후보군에서 제외
기간:
1일3일5일
동작:
gear_parent_candidate_exclusions에scope_type='GROUP'row 생성- 다음 cycle부터 해당 그룹의 candidate set에서 제거
- 다른 그룹에는 영향 없음
- 기간이 끝나면 자동으로 inactive 처리
용도:
- 이 후보는 이 어구 그룹의 모선이 아니라고 사람이 판단한 경우
- 단기/중기 관찰을 위해 일정 기간만 빼고 싶을 때
전체 후보 제외
의미:
- 특정 MMSI는 모든 어구 그룹에서 모선 후보 대상이 아님
동작:
gear_parent_candidate_exclusions에scope_type='GLOBAL'row 생성- prediction candidate generation에서 모든 그룹에 대해 hard filter
- 해제 전까지 계속 적용
초기 정책:
- 전역 후보 제외는 기본적으로 기간 없이 active 상태 유지
- 수동
해제전까지 유지
용도:
- 패턴 분류상 선박으로 들어왔지만 실제 모선 후보가 아니라고 판단한 AIS
- 잘못된 유형의 신호가 반복적으로 후보군에 유입되는 경우
해제
의미:
- 활성 그룹 제외, 전역 제외, 정답 라벨 세션을 조기 종료
동작:
- exclusion/session row에
released_at,released_by또는status='CANCELLED'를 기록 - 다음 cycle부터 알고리즘 적용 대상에서 빠짐
DB 설계
1. gear_parent_candidate_exclusions
역할:
- 그룹 단위 제외와 전역 후보 제외를 모두 저장
- active list의 단일 진실원
권장 컬럼:
CREATE TABLE kcg_lab.gear_parent_candidate_exclusions (
id BIGSERIAL PRIMARY KEY,
scope_type VARCHAR(16) NOT NULL, -- GROUP | GLOBAL
group_key VARCHAR(100), -- GROUP scope에서만 사용
sub_cluster_id SMALLINT,
candidate_mmsi VARCHAR(20) NOT NULL,
reason_type VARCHAR(32) NOT NULL, -- GROUP_WRONG_PARENT | GLOBAL_NOT_PARENT_TARGET
duration_days INT, -- GROUP scope는 1|3|5, GLOBAL은 NULL 허용
active_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
active_until TIMESTAMPTZ, -- GROUP scope는 필수, GLOBAL은 NULL 가능
released_at TIMESTAMPTZ,
released_by VARCHAR(100),
actor VARCHAR(100) NOT NULL,
comment TEXT,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
권장 인덱스:
(scope_type, candidate_mmsi)(group_key, sub_cluster_id, active_from DESC)(released_at, active_until)
조회 규칙:
active exclusion은 아래 조건으로 판단한다.
released_at IS NULL
AND active_from <= NOW()
AND (active_until IS NULL OR active_until > NOW())
2. gear_parent_label_sessions
역할:
- 특정 그룹에 대한 정답 라벨 세션 저장
권장 컬럼:
CREATE TABLE kcg_lab.gear_parent_label_sessions (
id BIGSERIAL PRIMARY KEY,
group_key VARCHAR(100) NOT NULL,
sub_cluster_id SMALLINT NOT NULL,
label_parent_mmsi VARCHAR(20) NOT NULL,
label_parent_name VARCHAR(200),
label_parent_vessel_id INT REFERENCES kcg_lab.fleet_vessels(id) ON DELETE SET NULL,
duration_days INT NOT NULL, -- 1 | 3 | 5
active_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
active_until TIMESTAMPTZ NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE | EXPIRED | CANCELLED
actor VARCHAR(100) NOT NULL,
comment TEXT,
anchor_snapshot_time TIMESTAMPTZ,
anchor_center_point geometry(Point, 4326),
anchor_member_mmsis JSONB NOT NULL DEFAULT '[]'::jsonb,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
설명:
anchor_*컬럼은 기간형 라벨 동안 subcluster가 재편성될 가능성에 대비한 보조 식별자다.- phase 1에서는 실제 매칭은
group_key + sub_cluster_id를 기본으로 쓰고, anchor 정보는 저장만 한다.
3. gear_parent_label_tracking_cycles
역할:
- 활성 정답 라벨 세션 동안 cycle별 자동 추론 결과 저장
- 향후 정확도 지표의 기준 데이터
권장 컬럼:
CREATE TABLE kcg_lab.gear_parent_label_tracking_cycles (
id BIGSERIAL PRIMARY KEY,
label_session_id BIGINT NOT NULL REFERENCES kcg_lab.gear_parent_label_sessions(id) ON DELETE CASCADE,
observed_at TIMESTAMPTZ NOT NULL,
candidate_snapshot_observed_at TIMESTAMPTZ,
auto_status VARCHAR(40),
top_candidate_mmsi VARCHAR(20),
top_candidate_name VARCHAR(200),
top_candidate_score DOUBLE PRECISION,
top_candidate_margin DOUBLE PRECISION,
candidate_count INT NOT NULL DEFAULT 0,
labeled_candidate_present BOOLEAN NOT NULL DEFAULT FALSE,
labeled_candidate_rank INT,
labeled_candidate_score DOUBLE PRECISION,
labeled_candidate_pre_bonus_score DOUBLE PRECISION,
labeled_candidate_margin_from_top DOUBLE PRECISION,
matched_top1 BOOLEAN NOT NULL DEFAULT FALSE,
matched_top3 BOOLEAN NOT NULL DEFAULT FALSE,
evidence_summary JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
설명:
- 전체 후보 상세는 기존
gear_group_parent_candidate_snapshots를 그대로 사용한다. - 여기에는 지표 계산에 직접 필요한 값만 요약 저장한다.
4. 기존 gear_group_parent_review_log 재사용
새 action 이름 예시:
LABEL_PARENTEXCLUDE_GROUPEXCLUDE_GLOBALRELEASE_EXCLUSIONCANCEL_LABEL
즉, 별도 audit table를 또 만들기보다 기존 review log를 action log로 재사용한다.
prediction 변경 설계
적용 지점
핵심 변경 지점은 gear_parent_inference.py, fleet_tracker.py, polygon_builder.py 중 gear_parent_inference.py가 중심이다.
1. active exclusion load
cycle 시작 시 아래 두 집합을 읽는다.
global_excluded_mmsisgroup_excluded_mmsis[(group_key, sub_cluster_id)]
적용 위치:
_build_candidate_scores()에서 candidate union 이후, 실제 scoring 전에 hard filter
규칙:
- GLOBAL exclusion은 모든 그룹에 적용
- GROUP exclusion은 해당 그룹에만 적용
- exclusion된 후보는 candidate snapshot에도 남기지 않음
중요:
- raw correlation score는 그대로 계산/저장
- exclusion은 parent inference candidate set에서만 적용
2. active label session load
cycle 시작 시 현재 unresolved/active gear group에 매칭되는 active label session을 읽는다.
phase 1 매칭 기준:
group_keysub_cluster_id
phase 2 보강 기준:
- member overlap
- center distance
- anchor snapshot similarity
3. tracking cycle write
각 그룹의 자동 추론이 끝난 뒤, active label session이 있으면 gear_parent_label_tracking_cycles에 1 row를 쓴다.
기록 항목:
- 현재 auto top-1 후보
- auto top-1 점수/격차
- 후보 수
- 라벨 대상 MMSI가 현재 후보군에 존재하는지
- 존재한다면 rank/score/pre-bonus score
- top1/top3 일치 여부
4. resolution 저장 원칙 변경
v2 이후 lab에서는 아래를 원칙으로 한다.
- 자동 resolution은 자동 추론만 반영
- 사람 라벨은 resolution을 덮어쓰지 않음
즉 아래 legacy 상태는 새로 만들지 않는다.
MANUAL_CONFIRMEDMANUAL_REJECT
기존 row는 읽기 전용으로 남겨둘 수 있지만, v2 새 액션은 이 상태를 만들지 않는다.
5. exclusion이 적용된 경우의 상태 전이
후보 pruning 이후:
- 후보가 남으면 기존 자동 상태 전이 사용
- top1이 제외되어 후보가 비면
NO_CANDIDATE - top1이 제외되어 top2가 승격되면 새 top1 기준으로
AUTO_PROMOTED / REVIEW_REQUIRED / UNRESOLVED재판정
backend API 설계
1. 정답 라벨 세션 생성
POST /api/vessel-analysis/groups/{groupKey}/parent-inference/{subClusterId}/label-session
request:
{
"selectedParentMmsi": "412333326",
"durationDays": 3,
"actor": "analyst-01",
"comment": "수동 확인"
}
response:
- 생성된 label session
- 현재 active label summary
2. 그룹 후보 제외 생성
POST /api/vessel-analysis/groups/{groupKey}/parent-inference/{subClusterId}/candidate-exclusions
request:
{
"candidateMmsi": "412333326",
"scopeType": "GROUP",
"durationDays": 3,
"actor": "analyst-01",
"comment": "이 그룹에서는 오답"
}
3. 전역 후보 제외 생성
POST /api/vessel-analysis/parent-inference/candidate-exclusions
request:
{
"candidateMmsi": "412333326",
"scopeType": "GLOBAL",
"actor": "analyst-01",
"comment": "모든 어구에서 모선 후보 대상 제외"
}
4. exclusion 해제
POST /api/vessel-analysis/parent-inference/candidate-exclusions/{id}/release
5. label session 종료
POST /api/vessel-analysis/parent-inference/label-sessions/{id}/cancel
6. active exclusion 조회
GET /api/vessel-analysis/parent-inference/candidate-exclusions?status=ACTIVE&scopeType=GLOBAL
용도:
- “대상 선박이 어느 어구에서 제외중인지” 목록 관리
- 운영자 관리 화면
7. active label tracking 조회
GET /api/vessel-analysis/parent-inference/label-sessions?status=ACTIVE
GET /api/vessel-analysis/parent-inference/label-sessions/{id}/tracking
8. 기존 review/detail API 확장
기존 GroupParentInferenceDto에 아래 요약을 추가한다.
activeLabelSessiongroupExclusionCounthasGlobalExclusionCandidateavailableActions
ParentInferenceCandidateDto에는 아래를 추가한다.
isExcludedInGroupisExcludedGloballyactiveExclusionIds
프론트엔드 설계
버튼 재구성
현재:
확정24시간 제외
v2:
정답 라벨이 그룹에서 제외전체 후보 제외해제
기간 선택
정답 라벨과 이 그룹에서 제외는 버튼 클릭 후 아래 중 하나를 고르게 한다.
1일3일5일
우측 모선 검토 패널 변화
- 후보 카드 상단 action area를 아래처럼 재구성
정답 라벨이 그룹에서 제외전체 후보 제외
- 현재 후보에 active exclusion이 있으면 badge 표시
이 그룹 제외 중전체 후보 제외 중
- 현재 그룹에 active label session이 있으면 summary box 표시
- 라벨 MMSI
- 남은 기간
- 최근 top1 일치율
새 목록
검토 대기- active label session이 없는 그룹만 기본 표시
라벨 추적- active label session이 있는 그룹
제외 대상 관리- active group/global exclusions
지도 표시 원칙
- active label session 그룹은 기본 review 색과 다른 badge 색을 사용
- globally excluded candidate는 raw correlation 패널에서는 참고로 보일 수 있지만, parent-review actionable candidate 목록에서는 숨김
지표 설계
정답 라벨 세션을 기반으로 최소 아래 지표를 계산한다.
핵심 지표
- top1 exact match rate
- top3 hit rate
- labeled candidate mean rank
- labeled candidate mean score
- time-to-first-top1
- session duration 동안 top1 일치 지속률
보정/실험 지표
412/413가산점 적용 전후 top1/top3 uplift- pre-bonus score 대비 final score uplift
- global exclusion 적용 전후 오탐 감소량
- group exclusion 이후 대체 top1 품질 변화
운영 준비 지표
- auto-promoted 후보 중 라벨과 일치하는 비율
- high-confidence (
>= 0.72) 구간 calibration - label session 종료 시점 기준
실무 참고 가능threshold
단계별 구현 순서
Phase 1. DB/Backend 계약
- 마이그레이션 추가
gear_parent_candidate_exclusionsgear_parent_label_sessionsgear_parent_label_tracking_cycles
- backend DTO/API 추가
- 기존
CONFIRM/REJECT/RESET는 lab UI에서 숨기고 legacy로만 남김
Phase 2. prediction 연동
- active exclusion load
- candidate pruning
- active label session load
- tracking cycle write
Phase 3. 프론트 UI 전환
- 버튼 재구성
- 기간 선택 UI
- 라벨 추적 목록
- 제외 대상 관리 화면
Phase 4. 지표와 리포트
- label session summary endpoint
- exclusion usage summary endpoint
- 실험 리포트 화면 또는 문서 산출
마이그레이션 전략
기존 v1 상태 처리
MANUAL_CONFIRMED,MANUAL_REJECT는 새로 생성하지 않는다.- 기존 row는 history로 남긴다.
- 필요하면 one-time migration으로 legacy
MANUAL_CONFIRMED를expired label session으로 변환할 수 있다.
운영 영향 제한
- v2는 우선
kcg_lab에만 적용 - 운영
kcg반영 전에는 사람이 직접 누르는 흐름과 tracking 지표가 충분히 쌓여야 함
수용 기준
기능 기준
- 그룹 제외가 다음 cycle부터 해당 그룹에서만 적용된다.
- 전역 후보 제외가 다음 cycle부터 모든 그룹에 적용된다.
- active exclusion list가 DB/API/UI에서 동일하게 보인다.
- 정답 라벨 세션 동안 cycle별 tracking row가 누락 없이 쌓인다.
데이터 기준
- label session당 최소 아래 값이 저장된다.
- top1 후보
- labeled candidate rank
- labeled candidate score
- candidate count
- observed_at
- exclusion row에는 scope, duration, actor, comment, active 기간이 남는다.
평가 기준
412/413가산점, threshold, exclusion 정책 변경 전후를 label session 데이터로 비교 가능해야 한다.- 일정 기간 후 “자동 top1을 운영 참고값으로 써도 되는지”를 정량으로 판단할 수 있어야 한다.
열린 이슈
1. 그룹 스코프 안정성
group_key + sub_cluster_id가 며칠 동안 완전히 안정적인지 추가 확인이 필요하다.
현재 권장:
- phase 1은 기존 키를 그대로 사용
- 대신
anchor_snapshot_time,anchor_center_point,anchor_member_mmsis를 저장
2. 전역 후보 제외의 기간 정책
현재 제안은 “수동 해제 전까지 유지”다.
이유:
- 전역 제외는 단기 오답보다 “이 AIS는 parent candidate class가 아님”에 가깝다.
필요 시 추후 1/3/5일 옵션을 추가할 수 있다.
3. raw correlation UI 노출
전역 제외된 후보를 모델 패널에서 완전히 숨길지, 참고 제외 badge만 붙여 남길지는 사용성 확인이 필요하다.
현재 권장은 아래다.
- parent-review actionable 후보 목록에서는 숨김
- raw model/correlation 참고 패널에서는 badge와 함께 유지
권장 결론
v2의 핵심은 사람 판단을 자동 추론의 override가 아니라 평가 데이터로 축적하는 것이다.
따라서 다음 구현 우선순위는 아래가 맞다.
- exclusion/label DB 추가
- prediction candidate gating + tracking write
- UI 액션 재정의
- 지표 산출
그 다음 단계에서만 threshold 자동화, 가산점 조정, LLM 연결을 검토하는 것이 안전하다.