# 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에서는 `자동 추론`, `사람 라벨`, `후보 제외`를 분리한다. ## 핵심 목표 1. 자동 추론 상태는 계속 독립적으로 유지한다. 2. 사람 판단은 override가 아니라 별도 라벨/제외 데이터로 저장한다. 3. 그룹 단위 오답 라벨은 `1일 / 3일 / 5일` 기간형 후보 제외로 관리한다. 4. 전역 후보 제외는 모든 어구 그룹에서 동일 MMSI를 후보군에서 제거한다. 5. 정답 라벨은 `1일 / 3일 / 5일` 세션으로 만들고, 활성 기간 동안 자동 추론 결과를 별도 추적 로그로 남긴다. 6. 알고리즘은 DB exclusion/label 정보를 읽어 다음 cycle부터 바로 반영한다. 7. 향후 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_snapshots` - `gear_group_parent_resolution` - `gear_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일` 중 하나의 기간 동안 자동 추론 결과를 추적 동작: 1. `gear_parent_label_sessions`에 active session 생성 2. 다음 cycle부터 prediction이 이 그룹에 대한 추적 로그를 `gear_parent_label_tracking_cycles`에 누적 3. 기본 review queue에서는 해당 그룹을 숨기고, 별도 `라벨 추적` 목록으로 이동 4. 세션 종료 후에는 completed label dataset으로 남음 중요: - 자동 resolution은 계속 자동 상태를 유지 - 점수에 수동 가산점/감점은 넣지 않음 ### `이 그룹에서 제외` 의미: - 해당 어구 그룹에서만 특정 후보 MMSI를 일정 기간 후보군에서 제외 기간: - `1일` - `3일` - `5일` 동작: 1. `gear_parent_candidate_exclusions`에 `scope_type='GROUP'` row 생성 2. 다음 cycle부터 해당 그룹의 candidate set에서 제거 3. 다른 그룹에는 영향 없음 4. 기간이 끝나면 자동으로 inactive 처리 용도: - 이 후보는 이 어구 그룹의 모선이 아니라고 사람이 판단한 경우 - 단기/중기 관찰을 위해 일정 기간만 빼고 싶을 때 ### `전체 후보 제외` 의미: - 특정 MMSI는 모든 어구 그룹에서 모선 후보 대상이 아님 동작: 1. `gear_parent_candidate_exclusions`에 `scope_type='GLOBAL'` row 생성 2. prediction candidate generation에서 모든 그룹에 대해 hard filter 3. 해제 전까지 계속 적용 초기 정책: - 전역 후보 제외는 기본적으로 기간 없이 active 상태 유지 - 수동 `해제` 전까지 유지 용도: - 패턴 분류상 선박으로 들어왔지만 실제 모선 후보가 아니라고 판단한 AIS - 잘못된 유형의 신호가 반복적으로 후보군에 유입되는 경우 ### `해제` 의미: - 활성 그룹 제외, 전역 제외, 정답 라벨 세션을 조기 종료 동작: - exclusion/session row에 `released_at`, `released_by` 또는 `status='CANCELLED'`를 기록 - 다음 cycle부터 알고리즘 적용 대상에서 빠짐 ## DB 설계 ### 1. `gear_parent_candidate_exclusions` 역할: - 그룹 단위 제외와 전역 후보 제외를 모두 저장 - active list의 단일 진실원 권장 컬럼: ```sql 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은 아래 조건으로 판단한다. ```sql released_at IS NULL AND active_from <= NOW() AND (active_until IS NULL OR active_until > NOW()) ``` ### 2. `gear_parent_label_sessions` 역할: - 특정 그룹에 대한 정답 라벨 세션 저장 권장 컬럼: ```sql 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별 자동 추론 결과 저장 - 향후 정확도 지표의 기준 데이터 권장 컬럼: ```sql 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_PARENT` - `EXCLUDE_GROUP` - `EXCLUDE_GLOBAL` - `RELEASE_EXCLUSION` - `CANCEL_LABEL` 즉, 별도 audit table를 또 만들기보다 기존 review log를 action log로 재사용한다. ## prediction 변경 설계 ### 적용 지점 핵심 변경 지점은 [gear_parent_inference.py](/Users/lht/work/devProjects/iran-airstrike-replay-codex/prediction/algorithms/gear_parent_inference.py), [fleet_tracker.py](/Users/lht/work/devProjects/iran-airstrike-replay-codex/prediction/fleet_tracker.py), [polygon_builder.py](/Users/lht/work/devProjects/iran-airstrike-replay-codex/prediction/algorithms/polygon_builder.py) 중 `gear_parent_inference.py`가 중심이다. ### 1. active exclusion load cycle 시작 시 아래 두 집합을 읽는다. - `global_excluded_mmsis` - `group_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_key` - `sub_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_CONFIRMED` - `MANUAL_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: ```json { "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: ```json { "candidateMmsi": "412333326", "scopeType": "GROUP", "durationDays": 3, "actor": "analyst-01", "comment": "이 그룹에서는 오답" } ``` ### 3. 전역 후보 제외 생성 `POST /api/vessel-analysis/parent-inference/candidate-exclusions` request: ```json { "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`에 아래 요약을 추가한다. - `activeLabelSession` - `groupExclusionCount` - `hasGlobalExclusionCandidate` - `availableActions` `ParentInferenceCandidateDto`에는 아래를 추가한다. - `isExcludedInGroup` - `isExcludedGlobally` - `activeExclusionIds` ## 프론트엔드 설계 ### 버튼 재구성 현재: - `확정` - `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_exclusions` - `gear_parent_label_sessions` - `gear_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가 아니라 평가 데이터로 축적하는 것`이다. 따라서 다음 구현 우선순위는 아래가 맞다. 1. exclusion/label DB 추가 2. prediction candidate gating + tracking write 3. UI 액션 재정의 4. 지표 산출 그 다음 단계에서만 threshold 자동화, 가산점 조정, LLM 연결을 검토하는 것이 안전하다.